Simon Peyton Jones pushed to branch wip/spj-try-opt-coercion at Glasgow Haskell Compiler / GHC
Commits:
-
88657af0
by Simon Peyton Jones at 2025-12-16T09:11:10+00:00
-
395e1128
by Simon Peyton Jones at 2025-12-16T09:11:28+00:00
-
f4ca153a
by Simon Peyton Jones at 2025-12-16T09:37:31+00:00
6 changed files:
- compiler/GHC/Core/Coercion.hs
- compiler/GHC/Core/Opt/OccurAnal.hs
- compiler/GHC/Core/Opt/Simplify/Iteration.hs
- compiler/GHC/Core/Opt/Simplify/Utils.hs
- compiler/GHC/Core/Opt/SpecConstr.hs
- compiler/GHC/Core/Utils.hs
Changes:
| ... | ... | @@ -709,7 +709,10 @@ isReflCo_maybe _ = Nothing |
| 709 | 709 | -- | Slowly checks if the coercion is reflexive. Don't call this in a loop,
|
| 710 | 710 | -- as it walks over the entire coercion.
|
| 711 | 711 | isReflexiveCo :: Coercion -> Bool
|
| 712 | -isReflexiveCo = isJust . isReflexiveCo_maybe
|
|
| 712 | +isReflexiveCo (Refl {}) = True
|
|
| 713 | +isReflexiveCo (GRefl _ _ mco) = isReflKindMCo mco
|
|
| 714 | +isReflexiveCo (SymCo co) = isReflexiveCo co
|
|
| 715 | +isReflexiveCo co = coercionLKind co `eqType` coercionRKind co
|
|
| 713 | 716 | |
| 714 | 717 | isReflexiveMCo :: MCoercion -> Bool
|
| 715 | 718 | isReflexiveMCo MRefl = True
|
| ... | ... | @@ -28,7 +28,7 @@ core expression with (hopefully) improved usage information. |
| 28 | 28 | module GHC.Core.Opt.OccurAnal (
|
| 29 | 29 | occurAnalysePgm,
|
| 30 | 30 | occurAnalyseExpr,
|
| 31 | - zapLambdaBndrs, BinderSwapDecision(..), scrutOkForBinderSwap
|
|
| 31 | + zapLambdaBndrs
|
|
| 32 | 32 | ) where
|
| 33 | 33 | |
| 34 | 34 | import GHC.Prelude hiding ( head, init, last, tail )
|
| ... | ... | @@ -36,7 +36,7 @@ import GHC.Prelude hiding ( head, init, last, tail ) |
| 36 | 36 | import GHC.Core
|
| 37 | 37 | import GHC.Core.FVs
|
| 38 | 38 | import GHC.Core.Utils ( exprIsTrivial, isDefaultAlt, isExpandableApp,
|
| 39 | - mkCastMCo, mkTicks )
|
|
| 39 | + mkCastMCo, mkTicks, BinderSwapDecision(..), scrutOkForBinderSwap )
|
|
| 40 | 40 | import GHC.Core.Opt.Arity ( joinRhsArity, isOneShotBndr )
|
| 41 | 41 | import GHC.Core.Coercion
|
| 42 | 42 | import GHC.Core.Type
|
| ... | ... | @@ -3535,6 +3535,7 @@ doesn't use it. So this is only to satisfy the perhaps-over-picky Lint. |
| 3535 | 3535 | -}
|
| 3536 | 3536 | |
| 3537 | 3537 | addBndrSwap :: OutExpr -> Id -> OccEnv -> OccEnv
|
| 3538 | +-- See Note [Binder swap]
|
|
| 3538 | 3539 | -- See Note [The binder-swap substitution]
|
| 3539 | 3540 | addBndrSwap scrut case_bndr
|
| 3540 | 3541 | env@(OccEnv { occ_bs_env = swap_env, occ_bs_rng = rng_vars })
|
| ... | ... | @@ -3542,7 +3543,7 @@ addBndrSwap scrut case_bndr |
| 3542 | 3543 | , scrut_var /= case_bndr
|
| 3543 | 3544 | -- Consider: case x of x { ... }
|
| 3544 | 3545 | -- Do not add [x :-> x] to occ_bs_env, else lookupBndrSwap will loop
|
| 3545 | - = env { occ_bs_env = extendVarEnv swap_env scrut_var (case_bndr', mco)
|
|
| 3546 | + = env { occ_bs_env = extendVarEnv swap_env scrut_var (case_bndr', mkSymMCo mco)
|
|
| 3546 | 3547 | , occ_bs_rng = rng_vars `extendVarSet` case_bndr'
|
| 3547 | 3548 | `unionVarSet` tyCoVarsOfMCo mco }
|
| 3548 | 3549 | |
| ... | ... | @@ -3552,27 +3553,6 @@ addBndrSwap scrut case_bndr |
| 3552 | 3553 | case_bndr' = zapIdOccInfo case_bndr
|
| 3553 | 3554 | -- See Note [Zap case binders in proxy bindings]
|
| 3554 | 3555 | |
| 3555 | --- | See bBinderSwaOk.
|
|
| 3556 | -data BinderSwapDecision
|
|
| 3557 | - = NoBinderSwap
|
|
| 3558 | - | DoBinderSwap OutVar MCoercion
|
|
| 3559 | - |
|
| 3560 | -scrutOkForBinderSwap :: OutExpr -> BinderSwapDecision
|
|
| 3561 | --- If (scrutOkForBinderSwap e = DoBinderSwap v mco, then
|
|
| 3562 | --- v = e |> mco
|
|
| 3563 | --- See Note [Case of cast]
|
|
| 3564 | --- See Historical Note [Care with binder-swap on dictionaries]
|
|
| 3565 | ---
|
|
| 3566 | --- We use this same function in SpecConstr, and Simplify.Iteration,
|
|
| 3567 | --- when something binder-swap-like is happening
|
|
| 3568 | -scrutOkForBinderSwap e
|
|
| 3569 | - = case e of
|
|
| 3570 | - Tick _ e -> scrutOkForBinderSwap e -- Drop ticks
|
|
| 3571 | - Var v -> DoBinderSwap v MRefl
|
|
| 3572 | - Cast (Var v) co -> DoBinderSwap v (MCo (mkSymCo co))
|
|
| 3573 | - -- Cast: see Note [Case of cast]
|
|
| 3574 | - _ -> NoBinderSwap
|
|
| 3575 | - |
|
| 3576 | 3556 | lookupBndrSwap :: OccEnv -> Id -> (CoreExpr, Id)
|
| 3577 | 3557 | -- See Note [The binder-swap substitution]
|
| 3578 | 3558 | -- Returns an expression of the same type as Id
|
| ... | ... | @@ -22,7 +22,7 @@ import GHC.Core.TyCo.Compare( eqType ) |
| 22 | 22 | import GHC.Core.Opt.Simplify.Env
|
| 23 | 23 | import GHC.Core.Opt.Simplify.Inline
|
| 24 | 24 | import GHC.Core.Opt.Simplify.Utils
|
| 25 | -import GHC.Core.Opt.OccurAnal ( occurAnalyseExpr, zapLambdaBndrs, scrutOkForBinderSwap, BinderSwapDecision (..) )
|
|
| 25 | +import GHC.Core.Opt.OccurAnal ( occurAnalyseExpr, zapLambdaBndrs )
|
|
| 26 | 26 | import GHC.Core.Make ( FloatBind, mkImpossibleExpr, castBottomExpr )
|
| 27 | 27 | import qualified GHC.Core.Make
|
| 28 | 28 | import GHC.Core.Coercion hiding ( substCo, substCoVar )
|
| ... | ... | @@ -1531,7 +1531,8 @@ rebuild_go env expr cont |
| 1531 | 1531 | Stop {} -> return (emptyFloats env, expr)
|
| 1532 | 1532 | TickIt t cont -> rebuild_go env (mkTick t expr) cont
|
| 1533 | 1533 | CastIt { sc_co = co, sc_opt = opt, sc_cont = cont }
|
| 1534 | - -> rebuild_go env (mkCast expr co') cont
|
|
| 1534 | + | isReflexiveCo co -> rebuild_go env expr cont
|
|
| 1535 | + | otherwise -> rebuild_go env (mkCast expr co') cont
|
|
| 1535 | 1536 | -- NB: mkCast implements the (Coercion co |> g) optimisation
|
| 1536 | 1537 | where
|
| 1537 | 1538 | co' = optOutCoercion env co opt
|
| ... | ... | @@ -3593,11 +3594,13 @@ addAltUnfoldings env case_bndr bndr_swap con_app |
| 3593 | 3594 | env1 = addBinderUnfolding env case_bndr con_app_unf
|
| 3594 | 3595 | |
| 3595 | 3596 | -- See Note [Add unfolding for scrutinee]
|
| 3597 | + -- e.g. case (x |> co) of K a b -> blah
|
|
| 3598 | + -- We add to `x` the unfolding (K a b |> sym co)
|
|
| 3596 | 3599 | env2 | DoBinderSwap v mco <- bndr_swap
|
| 3597 | 3600 | = addBinderUnfolding env1 v $
|
| 3598 | 3601 | if isReflMCo mco -- isReflMCo: avoid calling mk_simple_unf
|
| 3599 | 3602 | then con_app_unf -- twice in the common case
|
| 3600 | - else mk_simple_unf (mkCastMCo con_app mco)
|
|
| 3603 | + else mk_simple_unf (mkCastMCo con_app (mkSymMCo mco))
|
|
| 3601 | 3604 | |
| 3602 | 3605 | | otherwise = env1
|
| 3603 | 3606 |
| ... | ... | @@ -2693,7 +2693,7 @@ mkCase, mkCase1, mkCase2, mkCase3 |
| 2693 | 2693 | |
| 2694 | 2694 | mkCase mode scrut outer_bndr alts_ty alts
|
| 2695 | 2695 | | sm_case_merge mode
|
| 2696 | - , Just (joins, alts') <- mergeCaseAlts outer_bndr alts
|
|
| 2696 | + , Just (joins, alts') <- mergeCaseAlts scrut outer_bndr alts
|
|
| 2697 | 2697 | = do { tick (CaseMerge outer_bndr)
|
| 2698 | 2698 | ; case_expr <- mkCase1 mode scrut outer_bndr alts_ty alts'
|
| 2699 | 2699 | ; return (mkLets joins case_expr) }
|
| ... | ... | @@ -30,7 +30,6 @@ import GHC.Core.Opt.Simplify.Inline |
| 30 | 30 | import GHC.Core.FVs ( exprsFreeVarsList, exprFreeVars )
|
| 31 | 31 | import GHC.Core.Opt.Monad
|
| 32 | 32 | import GHC.Core.Opt.WorkWrap.Utils
|
| 33 | -import GHC.Core.Opt.OccurAnal( BinderSwapDecision(..), scrutOkForBinderSwap )
|
|
| 34 | 33 | import GHC.Core.DataCon
|
| 35 | 34 | import GHC.Core.Class( classTyVars )
|
| 36 | 35 | import GHC.Core.Coercion hiding( substCo )
|
| ... | ... | @@ -2025,9 +2024,9 @@ spec_one env fn arg_bndrs body (call_pat, rule_number) |
| 2025 | 2024 | -- , text "spec_call_args" <+> ppr spec_call_args
|
| 2026 | 2025 | -- , text "rule_rhs" <+> ppr rule_rhs
|
| 2027 | 2026 | -- , text "adds_void_worker_arg" <+> ppr add_void_arg
|
| 2028 | ----- , text "body" <+> ppr body
|
|
| 2029 | ----- , text "spec_rhs" <+> ppr spec_rhs
|
|
| 2030 | ----- , text "how_bound" <+> ppr (sc_how_bound env) ]
|
|
| 2027 | +-- , text "body" <+> ppr body
|
|
| 2028 | +-- , text "spec_rhs" <+> ppr spec_rhs
|
|
| 2029 | +-- , text "how_bound" <+> ppr (sc_how_bound env) ]
|
|
| 2031 | 2030 | -- ]
|
| 2032 | 2031 | ; return (spec_usg, OS { os_pat = call_pat, os_rule = rule
|
| 2033 | 2032 | , os_id = spec_id
|
| ... | ... | @@ -19,6 +19,7 @@ module GHC.Core.Utils ( |
| 19 | 19 | mergeAlts, mergeCaseAlts, trimConArgs,
|
| 20 | 20 | filterAlts, combineIdenticalAlts, refineDefaultAlt,
|
| 21 | 21 | scaleAltsBy,
|
| 22 | + BinderSwapDecision(..), scrutOkForBinderSwap,
|
|
| 22 | 23 | |
| 23 | 24 | -- * Properties of expressions
|
| 24 | 25 | exprType, coreAltType, coreAltsType,
|
| ... | ... | @@ -116,7 +117,6 @@ import Data.ByteString ( ByteString ) |
| 116 | 117 | import Data.Function ( on )
|
| 117 | 118 | import Data.List ( sort, sortBy, partition, zipWith4, mapAccumL )
|
| 118 | 119 | import Data.Ord ( comparing )
|
| 119 | -import Control.Monad ( guard )
|
|
| 120 | 120 | import qualified Data.Set as Set
|
| 121 | 121 | |
| 122 | 122 | {-
|
| ... | ... | @@ -590,6 +590,28 @@ The default alternative must be first, if it exists at all. |
| 590 | 590 | This makes it easy to find, though it makes matching marginally harder.
|
| 591 | 591 | -}
|
| 592 | 592 | |
| 593 | +data BinderSwapDecision
|
|
| 594 | + = NoBinderSwap
|
|
| 595 | + | DoBinderSwap OutVar MCoercion
|
|
| 596 | + |
|
| 597 | +scrutOkForBinderSwap :: OutExpr -> BinderSwapDecision
|
|
| 598 | +-- If (scrutOkForBinderSwap e = DoBinderSwap v mco, then
|
|
| 599 | +-- e = v |> mco
|
|
| 600 | +-- See Note [Case of cast]
|
|
| 601 | +-- See Historical Note [Care with binder-swap on dictionaries]
|
|
| 602 | +--
|
|
| 603 | +-- We use this same function in SpecConstr, and Simplify.Iteration,
|
|
| 604 | +-- when something binder-swap-like is happening
|
|
| 605 | +--
|
|
| 606 | +-- See Note [Binder swap] in GHC.Core.Opt.OccurAnal
|
|
| 607 | +scrutOkForBinderSwap e
|
|
| 608 | + = case e of
|
|
| 609 | + Tick _ e -> scrutOkForBinderSwap e -- Drop ticks
|
|
| 610 | + Var v -> DoBinderSwap v MRefl
|
|
| 611 | + Cast (Var v) co -> DoBinderSwap v (MCo co)
|
|
| 612 | + -- Cast: see Note [Case of cast]
|
|
| 613 | + _ -> NoBinderSwap
|
|
| 614 | + |
|
| 593 | 615 | -- | Extract the default case alternative
|
| 594 | 616 | findDefault :: [Alt b] -> ([Alt b], Maybe (Expr b))
|
| 595 | 617 | findDefault (Alt DEFAULT args rhs : alts) = assert (null args) (alts, Just rhs)
|
| ... | ... | @@ -651,9 +673,9 @@ filters down the matching alternatives in GHC.Core.Opt.Simplify.rebuildCase. |
| 651 | 673 | -}
|
| 652 | 674 | |
| 653 | 675 | ---------------------------------
|
| 654 | -mergeCaseAlts :: Id -> [CoreAlt] -> Maybe ([CoreBind], [CoreAlt])
|
|
| 676 | +mergeCaseAlts :: CoreExpr -> Id -> [CoreAlt] -> Maybe ([CoreBind], [CoreAlt])
|
|
| 655 | 677 | -- See Note [Merge Nested Cases]
|
| 656 | -mergeCaseAlts outer_bndr (Alt DEFAULT _ deflt_rhs : outer_alts)
|
|
| 678 | +mergeCaseAlts scrut outer_bndr (Alt DEFAULT _ deflt_rhs : outer_alts)
|
|
| 657 | 679 | | Just (joins, inner_alts) <- go deflt_rhs
|
| 658 | 680 | = Just (joins, mergeAlts outer_alts inner_alts)
|
| 659 | 681 | -- NB: mergeAlts gives priority to the left
|
| ... | ... | @@ -665,6 +687,9 @@ mergeCaseAlts outer_bndr (Alt DEFAULT _ deflt_rhs : outer_alts) |
| 665 | 687 | -- When we merge, we must ensure that e1 takes
|
| 666 | 688 | -- precedence over e2 as the value for A!
|
| 667 | 689 | where
|
| 690 | + bndr_swap :: BinderSwapDecision
|
|
| 691 | + bndr_swap = scrutOkForBinderSwap scrut
|
|
| 692 | + |
|
| 668 | 693 | go :: CoreExpr -> Maybe ([CoreBind], [CoreAlt])
|
| 669 | 694 | |
| 670 | 695 | -- Whizzo: we can merge!
|
| ... | ... | @@ -702,11 +727,10 @@ mergeCaseAlts outer_bndr (Alt DEFAULT _ deflt_rhs : outer_alts) |
| 702 | 727 | = do { (joins, alts) <- go body
|
| 703 | 728 | |
| 704 | 729 | -- Check for capture; but only if we could otherwise do a merge
|
| 705 | - ; let capture = outer_bndr `elem` bindersOf bind
|
|
| 706 | - || outer_bndr `elemVarSet` bindFreeVars bind
|
|
| 707 | - ; guard (not capture)
|
|
| 730 | + -- (i.e. the recursive `go` succeeds)
|
|
| 731 | + ; fix_up_binds <- okToFloatJoin bndr_swap outer_bndr bind
|
|
| 708 | 732 | |
| 709 | - ; return (bind:joins, alts ) }
|
|
| 733 | + ; return (fix_up_binds ++ (bind : joins), alts ) }
|
|
| 710 | 734 | | otherwise
|
| 711 | 735 | = Nothing
|
| 712 | 736 | |
| ... | ... | @@ -718,7 +742,25 @@ mergeCaseAlts outer_bndr (Alt DEFAULT _ deflt_rhs : outer_alts) |
| 718 | 742 | |
| 719 | 743 | go _ = Nothing
|
| 720 | 744 | |
| 721 | -mergeCaseAlts _ _ = Nothing
|
|
| 745 | +mergeCaseAlts _ _ _ = Nothing
|
|
| 746 | + |
|
| 747 | +okToFloatJoin :: BinderSwapDecision -> Id -> CoreBind -> Maybe [CoreBind]
|
|
| 748 | +-- Check a join-point binding to see if it can be floated out of
|
|
| 749 | +-- the DEFAULT branch of a `case`. A Just result means "yes",
|
|
| 750 | +-- and the [CoreBInd] are the extra fix-up bindings to add.
|
|
| 751 | +-- See Note [Floating join points out of DEFAULT alternatives]
|
|
| 752 | +okToFloatJoin bndr_swap outer_bndr bind
|
|
| 753 | + | outer_bndr `elem` bindersOf bind -- (a)
|
|
| 754 | + = Nothing
|
|
| 755 | + | outer_bndr `elemVarSet` bindFreeVars bind -- (b)
|
|
| 756 | + = case bndr_swap of
|
|
| 757 | + DoBinderSwap scrut_var mco
|
|
| 758 | + | scrut_var /= outer_bndr
|
|
| 759 | + -> Just [ NonRec outer_bndr (mkCastMCo (Var scrut_var) mco) ]
|
|
| 760 | + _ -> Nothing
|
|
| 761 | + | otherwise
|
|
| 762 | + = Just []
|
|
| 763 | + |
|
| 722 | 764 | |
| 723 | 765 | ---------------------------------
|
| 724 | 766 | mergeAlts :: [Alt a] -> [Alt a] -> [Alt a]
|
| ... | ... | @@ -927,10 +969,43 @@ Wrinkles |
| 927 | 969 | non-join-points unless the /outer/ case has just one alternative; doing
|
| 928 | 970 | so would risk more allocation
|
| 929 | 971 | |
| 972 | + Floating out join points isn't entirely straightforward.
|
|
| 973 | + See Note [Floating join points out of DEFAULT alternatives]
|
|
| 974 | + |
|
| 930 | 975 | (MC5) See Note [Cascading case merge]
|
| 931 | 976 | |
| 932 | 977 | See also Note [Example of case-merging and caseRules] in GHC.Core.Opt.Simplify.Utils
|
| 933 | 978 | |
| 979 | +Note [Floating join points out of DEFAULT alternatives]
|
|
| 980 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 981 | +Consider this, from (MC4) of Note [Merge Nested Cases]
|
|
| 982 | + case x of r
|
|
| 983 | + DEFAULT -> join j = rhs in case r of ...
|
|
| 984 | + alts
|
|
| 985 | + |
|
| 986 | +We want to float that join point out to give this
|
|
| 987 | + join j = rhs
|
|
| 988 | + case x of r
|
|
| 989 | + DEFAULT -> case r of ...
|
|
| 990 | + alts
|
|
| 991 | + |
|
| 992 | +But doing so is flat-out wrong if the scoping gets messed up:
|
|
| 993 | + (a) case x of r { DEFAULT -> join r = ... in ...r... }
|
|
| 994 | + (b) case x of r { DEFAULT -> join j = ...r.. in ... }
|
|
| 995 | +In both cases we can't float the join point out because r changes its meaning.
|
|
| 996 | + |
|
| 997 | +BUT we can fix up case (b) by adding an extra binding, like this
|
|
| 998 | + let r = x in
|
|
| 999 | + join j = rhs[r]
|
|
| 1000 | + case x of r
|
|
| 1001 | + DEFAULT -> ...r...
|
|
| 1002 | + ...other alts...
|
|
| 1003 | + |
|
| 1004 | +This extra binding is figured out by `okToFloatJoin`.
|
|
| 1005 | + |
|
| 1006 | +Note that the cases that still don't work (e.g. (a)) will probably work fine the
|
|
| 1007 | +next iteration of the Simplifier, because they involve shadowing, and the Simplifier
|
|
| 1008 | +generally eliminates shadowing.
|
|
| 934 | 1009 | |
| 935 | 1010 | Note [Cascading case merge]
|
| 936 | 1011 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~
|