Simon Peyton Jones pushed to branch wip/spj-try-opt-coercion at Glasgow Haskell Compiler / GHC

Commits:

8 changed files:

Changes:

  • compiler/GHC/Core/Coercion/Opt.hs
    ... ... @@ -42,13 +42,32 @@ import Control.Monad ( zipWithM )
    42 42
     %*                                                                      *
    
    43 43
     %************************************************************************
    
    44 44
     
    
    45
    +Note [Coercion optimisation]
    
    46
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    45 47
     This module does coercion optimisation.  See the paper
    
    46
    -
    
    47 48
        Evidence normalization in Systtem FV (RTA'13)
    
    48 49
        https://simon.peytonjones.org/evidence-normalization/
    
    49
    -
    
    50 50
     The paper is also in the GHC repo, in docs/opt-coercion.
    
    51 51
     
    
    52
    +However, although powerful and occasionally very effective, coercion optimisation
    
    53
    +can be very expensive (#26679).  So we apply it sparingly:
    
    54
    +
    
    55
    +* In the Simplifier, function `rebuild_go`, we use `isReflexiveCo` (which
    
    56
    +  computes the type of the coercion) to eliminate reflexive coercion, just
    
    57
    +  before we build a cast (e |> co).
    
    58
    +
    
    59
    +  (More precisely, we use `isReflexiveCoIgnoringMultiplicity.)
    
    60
    +
    
    61
    +* We have a whole pass, `optCoProgram` that runs the coercion optimiser on all
    
    62
    +  the coercions in the program.
    
    63
    +
    
    64
    +  - We run it once in all optimisation levels
    
    65
    +    (see GHC.Driver.DynFlags.optLevelFlags)
    
    66
    +
    
    67
    +  - We run it early in the optimisation pipeline
    
    68
    +    (see GHC.Core.Opt.Pipeline.getCoreToDo).
    
    69
    +
    
    70
    +
    
    52 71
     Note [Optimising coercion optimisation]
    
    53 72
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    54 73
     Looking up a coercion's role or kind is linear in the size of the
    

  • compiler/GHC/Core/Opt/Pipeline.hs
    ... ... @@ -227,6 +227,7 @@ getCoreToDo dflags hpt_rule_base extra_vars
    227 227
             -- Optimise coercions
    
    228 228
             -- With -O do this after one run of the Simplifier.
    
    229 229
             -- Without -O, just take what the desugarer produced
    
    230
    +        -- See Note [Coercion optimisation] in GHC.Core.Coercion.Opt
    
    230 231
             runWhen do_co_opt CoreOptCoercion,
    
    231 232
     
    
    232 233
             if full_laziness then
    

  • compiler/GHC/Core/Opt/Simplify/Env.hs
    ... ... @@ -161,15 +161,16 @@ following table:
    161 161
     
    
    162 162
     Note [Inline depth]
    
    163 163
     ~~~~~~~~~~~~~~~~~~~
    
    164
    +The seInlineDepth tells us how deep in inlining we are.
    
    165
    +
    
    164 166
     When we inline an /already-simplified/ unfolding, we
    
    165 167
     * Zap the substitution environment; the inlined thing is an OutExpr
    
    166 168
     * Bump the seInlineDepth in the SimplEnv
    
    167 169
     Both these tasks are done in zapSubstEnv.
    
    168 170
     
    
    169
    -The seInlineDepth tells us how deep in inlining we are.  Currently,
    
    170
    -seInlineDepth is used for just one purpose: when we encounter a
    
    171
    -coercion we don't apply optCoercion to it if seInlineDepth>0.
    
    172
    -Reason: it has already been optimised once, no point in doing so again.
    
    171
    +Currently, `seInlineDepth` is entirely unused! (It was previously used to avoid
    
    172
    +repeatedly optimising coercions.)  But it's cheap to maintain and might prove
    
    173
    +useful, so I have no removed it.
    
    173 174
     -}
    
    174 175
     
    
    175 176
     data SimplEnv
    

  • compiler/GHC/Core/Opt/Simplify/Iteration.hs
    ... ... @@ -27,7 +27,6 @@ import GHC.Core.Make ( FloatBind, mkImpossibleExpr, castBottomExpr )
    27 27
     import qualified GHC.Core.Make
    
    28 28
     import GHC.Core.Coercion hiding ( substCo, substCoVar )
    
    29 29
     import GHC.Core.Reduction
    
    30
    -import GHC.Core.Coercion.Opt    ( optCoercion )
    
    31 30
     import GHC.Core.FamInstEnv      ( FamInstEnv, topNormaliseType_maybe )
    
    32 31
     import GHC.Core.DataCon
    
    33 32
     import GHC.Core.Opt.Stats ( Tick(..) )
    
    ... ... @@ -1358,16 +1357,8 @@ simplCoercionF env co cont
    1358 1357
     
    
    1359 1358
     simplCoercion :: SimplEnv -> InCoercion -> SimplM OutCoercion
    
    1360 1359
     simplCoercion env co
    
    1361
    -  = do { let opt_co | reSimplifying env = substCo env co
    
    1362
    -                    | otherwise         = optCoercion opts subst co
    
    1363
    -             -- If (reSimplifying env) is True we have already simplified
    
    1364
    -             -- this coercion once, and we don't want do so again; doing
    
    1365
    -             -- so repeatedly risks non-linear behaviour
    
    1366
    -             -- See Note [Inline depth] in GHC.Core.Opt.Simplify.Env
    
    1367
    -       ; seqCo opt_co `seq` return opt_co }
    
    1368
    -  where
    
    1369
    -    subst = getTCvSubst env
    
    1370
    -    opts  = seOptCoercionOpts env
    
    1360
    +  = do { let out_co = substCo env co
    
    1361
    +       ; seqCo out_co `seq` return out_co }
    
    1371 1362
     
    
    1372 1363
     -----------------------------------
    
    1373 1364
     -- | Push a TickIt context outwards past applications and cases, as
    
    ... ... @@ -1538,15 +1529,13 @@ rebuild_go env expr cont
    1538 1529
         case cont of
    
    1539 1530
           Stop {}          -> return (emptyFloats env, expr)
    
    1540 1531
           TickIt t cont    -> rebuild_go env (mkTick t expr) cont
    
    1541
    -      CastIt { sc_co = co, sc_opt = opt, sc_cont = cont }
    
    1532
    +      CastIt { sc_co = co, sc_cont = cont }
    
    1542 1533
             | isReflexiveCoIgnoringMultiplicity co
    
    1543 1534
                   -- ignoring multiplicity: c.f. GHC.Core.Coercion.Opt.opt_univ
    
    1544 1535
             -> rebuild_go env expr cont
    
    1545 1536
             | otherwise
    
    1546
    -        -> rebuild_go env (mkCast expr co') cont
    
    1537
    +        -> rebuild_go env (mkCast expr co) cont
    
    1547 1538
                -- NB: mkCast implements the (Coercion co |> g) optimisation
    
    1548
    -        where
    
    1549
    -          co' = optOutCoercion env co opt
    
    1550 1539
     
    
    1551 1540
           Select { sc_bndr = bndr, sc_alts = alts, sc_env = se, sc_cont = cont }
    
    1552 1541
             -> rebuildCase (se `setInScopeFromE` env) expr bndr alts cont
    
    ... ... @@ -1645,45 +1634,9 @@ isReflexiveCo
    1645 1634
     
    
    1646 1635
     In investigating this I saw missed opportunities for on-the-fly
    
    1647 1636
     coercion shrinkage. See #15090.
    
    1648
    -
    
    1649
    -Note [Avoid re-simplifying coercions]
    
    1650
    -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    1651
    -In some benchmarks (with deeply nested cases) we successively push
    
    1652
    -casts onto the SimplCont.  We don't want to call the coercion optimiser
    
    1653
    -on each successive composition -- that's at least quadratic.  So:
    
    1654
    -
    
    1655
    -* The CastIt constructor in SimplCont has a `sc_opt :: Bool` flag to
    
    1656
    -  record whether the coercion optimiser has been applied to the coercion.
    
    1657
    -
    
    1658
    -* In `simplCast`, when we see (Cast e co), we simplify `co` to get
    
    1659
    -  an OutCoercion, and built a CastIt with sc_opt=True.
    
    1660
    -
    
    1661
    -  Actually not quite: if we are simplifying the result of inlining an
    
    1662
    -  unfolding (seInlineDepth > 0), then instead of /optimising/ it again,
    
    1663
    -  just /substitute/ which is cheaper.  See `simplCoercion`.
    
    1664
    -
    
    1665
    -* In `addCoerce` (in `simplCast`) if we combine this new coercion with
    
    1666
    -  an existing once, we build a CastIt for (co1 ; co2) with sc_opt=False.
    
    1667
    -
    
    1668
    -* When unpacking a CastIt, in `rebuildCall` and `rebuild`, we optimise
    
    1669
    -  the (presumably composed) coercion if sc_opt=False; this is done
    
    1670
    -  by `optOutCoercion`.
    
    1671
    -
    
    1672
    -* When duplicating a continuation in `mkDupableContWithDmds`, before
    
    1673
    -  duplicating a CastIt, optimise the coercion. Otherwise we'll end up
    
    1674
    -  optimising it separately in the duplicate copies.
    
    1675 1637
     -}
    
    1676 1638
     
    
    1677 1639
     
    
    1678
    -optOutCoercion :: SimplEnvIS -> OutCoercion -> Bool -> OutCoercion
    
    1679
    --- See Note [Avoid re-simplifying coercions]
    
    1680
    -optOutCoercion env co already_optimised
    
    1681
    -  | already_optimised = co  -- See Note [Avoid re-simplifying coercions]
    
    1682
    -  | otherwise         = optCoercion opts empty_subst co
    
    1683
    -  where
    
    1684
    -    empty_subst = mkEmptySubst (seInScope env)
    
    1685
    -    opts = seOptCoercionOpts env
    
    1686
    -
    
    1687 1640
     simplCast :: SimplEnv -> InExpr -> InCoercion -> SimplCont
    
    1688 1641
               -> SimplM (SimplFloats, OutExpr)
    
    1689 1642
     simplCast env body co0 cont0
    
    ... ... @@ -1691,27 +1644,25 @@ simplCast env body co0 cont0
    1691 1644
             ; cont1 <- {-#SCC "simplCast-addCoerce" #-}
    
    1692 1645
                        if isReflCo co1
    
    1693 1646
                        then return cont0  -- See Note [Optimising reflexivity]
    
    1694
    -                   else addCoerce co1 True cont0
    
    1695
    -                        -- True <=> co1 is optimised
    
    1647
    +                   else addCoerce co1 cont0
    
    1696 1648
             ; {-#SCC "simplCast-simplExprF" #-} simplExprF env body cont1 }
    
    1697 1649
       where
    
    1698
    -
    
    1699 1650
             -- If the first parameter is MRefl, then simplifying revealed a
    
    1700 1651
             -- reflexive coercion. Omit.
    
    1701
    -        addCoerceM :: MOutCoercion -> Bool -> SimplCont -> SimplM SimplCont
    
    1702
    -        addCoerceM MRefl    _   cont = return cont
    
    1703
    -        addCoerceM (MCo co) opt cont = addCoerce co opt cont
    
    1652
    +        addCoerceM :: MOutCoercion -> SimplCont -> SimplM SimplCont
    
    1653
    +        addCoerceM MRefl    cont = return cont
    
    1654
    +        addCoerceM (MCo co) cont = addCoerce co cont
    
    1704 1655
     
    
    1705
    -        addCoerce :: OutCoercion -> Bool -> SimplCont -> SimplM SimplCont
    
    1706
    -        addCoerce co1 _ (CastIt { sc_co = co2, sc_cont = cont })  -- See Note [Optimising reflexivity]
    
    1707
    -          = addCoerce (mkTransCo co1 co2) False cont
    
    1656
    +        addCoerce :: OutCoercion -> SimplCont -> SimplM SimplCont
    
    1657
    +        addCoerce co1 (CastIt { sc_co = co2, sc_cont = cont })  -- See Note [Optimising reflexivity]
    
    1658
    +          = addCoerce (mkTransCo co1 co2) cont
    
    1708 1659
                           -- False: (mkTransCo co1 co2) is not fully optimised
    
    1709 1660
                           -- See Note [Avoid re-simplifying coercions]
    
    1710 1661
     
    
    1711
    -        addCoerce co co_is_opt (ApplyToTy { sc_arg_ty = arg_ty, sc_cont = tail })
    
    1662
    +        addCoerce co (ApplyToTy { sc_arg_ty = arg_ty, sc_cont = tail })
    
    1712 1663
               | Just (arg_ty', m_co') <- pushCoTyArg co arg_ty
    
    1713 1664
               = {-#SCC "addCoerce-pushCoTyArg" #-}
    
    1714
    -            do { tail' <- addCoerceM m_co' co_is_opt tail
    
    1665
    +            do { tail' <- addCoerceM m_co' tail
    
    1715 1666
                    ; return (ApplyToTy { sc_arg_ty  = arg_ty'
    
    1716 1667
                                        , sc_cont    = tail'
    
    1717 1668
                                        , sc_hole_ty = coercionLKind co }) }
    
    ... ... @@ -1721,15 +1672,12 @@ simplCast env body co0 cont0
    1721 1672
             -- where   co :: (s1->s2) ~ (t1->t2)
    
    1722 1673
             --         co1 :: t1 ~ s1
    
    1723 1674
             --         co2 :: s2 ~ t2
    
    1724
    -        addCoerce co co_is_opt cont@(ApplyToVal { sc_arg = arg, sc_env = arg_se
    
    1725
    -                                                , sc_dup = dup, sc_cont = tail
    
    1726
    -                                                , sc_hole_ty = fun_ty })
    
    1727
    -          | not co_is_opt  -- pushCoValArg duplicates the coercion, so optimise first
    
    1728
    -          = addCoerce (optOutCoercion (zapSubstEnv env) co co_is_opt) True cont
    
    1729
    -
    
    1675
    +        addCoerce co cont@(ApplyToVal { sc_arg = arg, sc_env = arg_se
    
    1676
    +                                      , sc_dup = dup, sc_cont = tail
    
    1677
    +                                      , sc_hole_ty = fun_ty })
    
    1730 1678
               | Just (m_co1, m_co2) <- pushCoValArg co
    
    1731 1679
               = {-#SCC "addCoerce-pushCoValArg" #-}
    
    1732
    -            do { tail' <- addCoerceM m_co2 co_is_opt tail
    
    1680
    +            do { tail' <- addCoerceM m_co2 tail
    
    1733 1681
                    ; case m_co1 of {
    
    1734 1682
                        MRefl -> return (cont { sc_cont = tail'
    
    1735 1683
                                              , sc_hole_ty = coercionLKind co }) ;
    
    ... ... @@ -1748,11 +1696,11 @@ simplCast env body co0 cont0
    1748 1696
                                         , sc_cont = tail'
    
    1749 1697
                                         , sc_hole_ty = coercionLKind co }) } } }
    
    1750 1698
     
    
    1751
    -        addCoerce co co_is_opt cont
    
    1699
    +        addCoerce co cont
    
    1752 1700
               | isReflCo co = return cont  -- Having this at the end makes a huge
    
    1753 1701
                                            -- difference in T12227, for some reason
    
    1754 1702
                                            -- See Note [Optimising reflexivity]
    
    1755
    -          | otherwise = return (CastIt { sc_co = co, sc_opt = co_is_opt, sc_cont = cont })
    
    1703
    +          | otherwise = return (CastIt { sc_co = co, sc_cont = cont })
    
    1756 1704
     
    
    1757 1705
     simplLazyArg :: SimplEnvIS              -- ^ Used only for its InScopeSet
    
    1758 1706
                  -> DupFlag
    
    ... ... @@ -3877,11 +3825,9 @@ mkDupableContWithDmds env _ cont
    3877 3825
     
    
    3878 3826
     mkDupableContWithDmds _ _ (Stop {}) = panic "mkDupableCont"     -- Handled by previous eqn
    
    3879 3827
     
    
    3880
    -mkDupableContWithDmds env dmds (CastIt { sc_co = co, sc_opt = opt, sc_cont = cont })
    
    3828
    +mkDupableContWithDmds env dmds (CastIt { sc_co = co, sc_cont = cont })
    
    3881 3829
       = do  { (floats, cont') <- mkDupableContWithDmds env dmds cont
    
    3882
    -        ; return (floats, CastIt { sc_co = optOutCoercion env co opt
    
    3883
    -                                 , sc_opt = True, sc_cont = cont' }) }
    
    3884
    -                 -- optOutCoercion: see Note [Avoid re-simplifying coercions]
    
    3830
    +        ; return (floats, CastIt { sc_co = co, sc_cont = cont' }) }
    
    3885 3831
     
    
    3886 3832
     -- Duplicating ticks for now, not sure if this is good or not
    
    3887 3833
     mkDupableContWithDmds env dmds (TickIt t cont)
    

  • compiler/GHC/Core/Opt/Simplify/Utils.hs
    ... ... @@ -164,9 +164,6 @@ data SimplCont
    164 164
       | CastIt              -- (CastIt co K)[e] = K[ e `cast` co ]
    
    165 165
           { sc_co   :: OutCoercion  -- The coercion simplified
    
    166 166
                                     -- Invariant: never an identity coercion
    
    167
    -      , sc_opt  :: Bool         -- True <=> sc_co has had optCoercion applied to it
    
    168
    -                                --      See Note [Avoid re-simplifying coercions]
    
    169
    -                                --      in GHC.Core.Opt.Simplify.Iteration
    
    170 167
           , sc_cont :: SimplCont }
    
    171 168
     
    
    172 169
       | ApplyToVal         -- (ApplyToVal arg K)[e] = K[ e arg ]
    

  • compiler/GHC/Core/SimpleOpt.hs
    ... ... @@ -30,7 +30,6 @@ import GHC.Core.Unfold.Make
    30 30
     import GHC.Core.Make ( FloatBind(..), mkWildValBinder )
    
    31 31
     import GHC.Core.Opt.OccurAnal( occurAnalyseExpr, occurAnalysePgm, zapLambdaBndrs )
    
    32 32
     import GHC.Core.DataCon
    
    33
    -import GHC.Core.Coercion.Opt ( optCoercion, OptCoercionOpts (..) )
    
    34 33
     import GHC.Core.Type hiding ( substTy, extendTvSubst, extendCvSubst, extendTvSubstList
    
    35 34
                                 , isInScope, substTyVarBndr, cloneTyVarBndr )
    
    36 35
     import GHC.Core.Predicate( isCoVarType )
    
    ... ... @@ -113,7 +112,6 @@ See ticket #25790
    113 112
     -- | Simple optimiser options
    
    114 113
     data SimpleOpts = SimpleOpts
    
    115 114
        { so_uf_opts :: !UnfoldingOpts   -- ^ Unfolding options
    
    116
    -   , so_co_opts :: !OptCoercionOpts -- ^ Coercion optimiser options
    
    117 115
        , so_eta_red :: !Bool            -- ^ Eta reduction on?
    
    118 116
        , so_inline :: !Bool             -- ^ False <=> do no inlining whatsoever,
    
    119 117
                                         --    even for trivial or used-once things
    
    ... ... @@ -123,7 +121,6 @@ data SimpleOpts = SimpleOpts
    123 121
     defaultSimpleOpts :: SimpleOpts
    
    124 122
     defaultSimpleOpts = SimpleOpts
    
    125 123
        { so_uf_opts = defaultUnfoldingOpts
    
    126
    -   , so_co_opts = OptCoercionOpts { optCoercionEnabled = False }
    
    127 124
        , so_eta_red = False
    
    128 125
        , so_inline  = True
    
    129 126
        }
    
    ... ... @@ -288,7 +285,7 @@ simple_opt_expr env expr = go expr
    288 285
         go e@(Lam {})  = simple_app env e []
    
    289 286
     
    
    290 287
         go (Type ty)        = Type     (substTyUnchecked subst ty)
    
    291
    -    go (Coercion co)    = Coercion (go_co co)
    
    288
    +    go (Coercion co)    = Coercion (simple_opt_co env co)
    
    292 289
         go (Lit lit)        = Lit lit
    
    293 290
         go (Tick tickish e) = mkTick (substTickish subst tickish) (go e)
    
    294 291
         go (Let bind body)  = case simple_opt_bind env bind NotTopLevel of
    
    ... ... @@ -323,15 +320,15 @@ simple_opt_expr env expr = go expr
    323 320
             e' = go e
    
    324 321
             (env', b') = subst_opt_bndr env b
    
    325 322
     
    
    326
    -    ----------------------
    
    327
    -    go_co co = optCoercion (so_co_opts (soe_opts env)) subst co
    
    328
    -
    
    329 323
         ----------------------
    
    330 324
         go_alt env (Alt con bndrs rhs)
    
    331 325
           = Alt con bndrs' (simple_opt_expr env' rhs)
    
    332 326
           where
    
    333 327
             (env', bndrs') = subst_opt_bndrs env bndrs
    
    334 328
     
    
    329
    +simple_opt_co :: SimpleOptEnv -> InCoercion -> OutCoercion
    
    330
    +simple_opt_co env co = substCo (soe_subst env) co
    
    331
    +
    
    335 332
     mk_cast :: CoreExpr -> CoercionR -> CoreExpr
    
    336 333
     -- Like GHC.Core.Utils.mkCast, but does a full reflexivity check.
    
    337 334
     -- mkCast doesn't do that because the Simplifier does (in simplCast)
    
    ... ... @@ -471,7 +468,7 @@ add_cast env co1 as
    471 468
           CastIt co2:rest -> CastIt (co1' `mkTransCo` co2):rest
    
    472 469
           _               -> CastIt co1':as
    
    473 470
       where
    
    474
    -    co1' = optCoercion (so_co_opts (soe_opts env)) (soe_subst env) co1
    
    471
    +    co1' = simple_opt_co env co1
    
    475 472
     
    
    476 473
     rebuild_app :: HasDebugCallStack
    
    477 474
                 => SimpleOptEnv -> OutExpr -> [SimpleContItem] -> OutExpr
    
    ... ... @@ -606,7 +603,7 @@ simple_bind_pair env@(SOE { soe_inl = inl_env, soe_subst = subst, soe_opts = opt
    606 603
         (env { soe_subst = extendTvSubst subst in_bndr out_ty }, Nothing)
    
    607 604
     
    
    608 605
       | Coercion co <- in_rhs
    
    609
    -  , let out_co = optCoercion (so_co_opts (soe_opts env)) (soe_subst rhs_env) co
    
    606
    +  , let out_co = simple_opt_co rhs_env co
    
    610 607
       = assert (isCoVar in_bndr)
    
    611 608
         (env { soe_subst = extendCvSubst subst in_bndr out_co }, Nothing)
    
    612 609
     
    

  • compiler/GHC/Driver/Config.hs
    ... ... @@ -24,7 +24,6 @@ initOptCoercionOpts dflags = OptCoercionOpts
    24 24
     initSimpleOpts :: DynFlags -> SimpleOpts
    
    25 25
     initSimpleOpts dflags = SimpleOpts
    
    26 26
        { so_uf_opts = unfoldingOpts dflags
    
    27
    -   , so_co_opts = initOptCoercionOpts dflags
    
    28 27
        , so_eta_red = gopt Opt_DoEtaReduction dflags
    
    29 28
        , so_inline  = True
    
    30 29
        }
    

  • compiler/GHC/Driver/DynFlags.hs
    ... ... @@ -1236,7 +1236,7 @@ optLevelFlags -- see Note [Documenting optimisation flags]
    1236 1236
         , ([1,2],   Opt_DoCleverArgEtaExpansion) -- See Note [Eta expansion of arguments in CorePrep]
    
    1237 1237
         , ([0,1,2], Opt_DoEtaReduction)          -- See Note [Eta-reduction in -O0]
    
    1238 1238
         , ([0,1,2], Opt_ProfManualCcs )
    
    1239
    -    , ([0,1,2], Opt_OptCoercion )
    
    1239
    +    , ([0,1,2], Opt_OptCoercion )    -- See Note [Coercion optimisation] in GHC.Core.Coercion.Opt
    
    1240 1240
         , ([2], Opt_DictsStrict)
    
    1241 1241
     
    
    1242 1242
         , ([0],     Opt_IgnoreInterfacePragmas)