diff options
author | Simon Peyton Jones <simonpj@microsoft.com> | 2020-06-11 15:45:53 +0100 |
---|---|---|
committer | Ben Gamari <ben@smart-cactus.org> | 2020-07-11 12:18:08 -0400 |
commit | f250606761b8120799a276eafc858bfb94743df9 (patch) | |
tree | b1f1f527c6ca9fc8012402af45537f12befb208a /compiler | |
parent | b9f0d5832afe2605c6b49be806f78223007f4f0a (diff) | |
download | haskell-wip/simon-perf.tar.gz |
This patch addresses the exponential blow-up in the simplifier.wip/simon-perf
Specifically:
#13253 exponential inlining
#10421 ditto
#18140 strict constructors
#18282 another nested-function call case
This patch makes two significant changes:
1. For Ids that are used at most once in each branch of a case,
make the occurrence analyser record the total number of
syntactic occurrences. Then in postInlineUnconditionally
use that info to avoid inling something many many times.
Actual changes:
* See the occ_n_br field of OneOcc.
* postInlineUnconditionally
See Note [Suppress exponential blowup] in GHC.Core.Opt.Simplify.Utils
2. Change the way that mkDupableCont handles StrictArg.
The details are explained in GHC.Core.Opt.Simplify
Note [Duplicating StrictArg]
Current nofib run
Program Size Allocs Runtime Elapsed TotalMem
--------------------------------------------------------------------------------
VS -0.3% +115.9% +12.1% +11.2% 0.0%
boyer2 -0.3% +10.0% +3.5% +4.0% 0.0%
cryptarithm2 -0.3% +39.0% +16.6% +16.1% 0.0%
gamteb -0.3% +4.1% -0.0% +0.4% 0.0%
last-piece -0.3% +1.4% -1.1% -0.4% 0.0%
mate -0.4% -11.1% -8.5% -9.0% 0.0%
multiplier -0.3% -2.2% -1.5% -1.5% 0.0%
transform -0.3% +3.4% +0.5% +0.8% 0.0%
--------------------------------------------------------------------------------
Min -0.8% -11.1% -8.5% -9.0% 0.0%
Max -0.3% +115.9% +30.1% +26.4% 0.0%
Geometric Mean -0.3% +1.0% +1.0% +1.0% -0.0%
Should investigate these numbers.
But the tickets are indeed cured, I think.
Diffstat (limited to 'compiler')
-rw-r--r-- | compiler/GHC/Core/Opt/OccurAnal.hs | 18 | ||||
-rw-r--r-- | compiler/GHC/Core/Opt/SetLevels.hs | 12 | ||||
-rw-r--r-- | compiler/GHC/Core/Opt/Simplify.hs | 193 | ||||
-rw-r--r-- | compiler/GHC/Core/Opt/Simplify/Utils.hs | 47 | ||||
-rw-r--r-- | compiler/GHC/Core/SimpleOpt.hs | 2 | ||||
-rw-r--r-- | compiler/GHC/Types/Basic.hs | 25 | ||||
-rw-r--r-- | compiler/GHC/Types/Id/Info.hs | 2 |
7 files changed, 183 insertions, 116 deletions
diff --git a/compiler/GHC/Core/Opt/OccurAnal.hs b/compiler/GHC/Core/Opt/OccurAnal.hs index 9cc0953efd..8a48766627 100644 --- a/compiler/GHC/Core/Opt/OccurAnal.hs +++ b/compiler/GHC/Core/Opt/OccurAnal.hs @@ -832,7 +832,7 @@ occAnalNonRecBind env lvl imp_rule_edges bndr rhs body_usage certainly_inline -- See Note [Cascading inlines] = case occ of - OneOcc { occ_in_lam = NotInsideLam, occ_one_br = InOneBranch } + OneOcc { occ_in_lam = NotInsideLam, occ_n_br = 1 } -> active && not_stable _ -> False @@ -2563,7 +2563,7 @@ mkOneOcc id int_cxt arity = emptyDetails where occ_info = OneOcc { occ_in_lam = NotInsideLam - , occ_one_br = InOneBranch + , occ_n_br = oneBranch , occ_int_cxt = int_cxt , occ_tail = AlwaysTailCalled arity } @@ -2967,11 +2967,15 @@ addOccInfo a1 a2 = ASSERT( not (isDeadOcc a1 || isDeadOcc a2) ) -- (orOccInfo orig new) is used -- when combining occurrence info from branches of a case -orOccInfo (OneOcc { occ_in_lam = in_lam1, occ_int_cxt = int_cxt1 - , occ_tail = tail1 }) - (OneOcc { occ_in_lam = in_lam2, occ_int_cxt = int_cxt2 - , occ_tail = tail2 }) - = OneOcc { occ_one_br = MultipleBranches -- because it occurs in both branches +orOccInfo (OneOcc { occ_in_lam = in_lam1 + , occ_n_br = nbr1 + , occ_int_cxt = int_cxt1 + , occ_tail = tail1 }) + (OneOcc { occ_in_lam = in_lam2 + , occ_n_br = nbr2 + , occ_int_cxt = int_cxt2 + , occ_tail = tail2 }) + = OneOcc { occ_n_br = nbr1 + nbr2 , occ_in_lam = in_lam1 `mappend` in_lam2 , occ_int_cxt = int_cxt1 `mappend` int_cxt2 , occ_tail = tail1 `andTailCallInfo` tail2 } diff --git a/compiler/GHC/Core/Opt/SetLevels.hs b/compiler/GHC/Core/Opt/SetLevels.hs index 91e9f6ec34..efcf96e6df 100644 --- a/compiler/GHC/Core/Opt/SetLevels.hs +++ b/compiler/GHC/Core/Opt/SetLevels.hs @@ -658,8 +658,8 @@ lvlMFE env strict_ctxt e@(_, AnnCase {}) lvlMFE env strict_ctxt ann_expr | floatTopLvlOnly env && not (isTopLvl dest_lvl) -- Only floating to the top level is allowed. - || anyDVarSet isJoinId fvs -- If there is a free join, don't float - -- See Note [Free join points] + || hasFreeJoin env fvs -- If there is a free join, don't float + -- See Note [Free join points] || isExprLevPoly expr -- We can't let-bind levity polymorphic expressions -- See Note [Levity polymorphism invariants] in GHC.Core @@ -755,6 +755,14 @@ lvlMFE env strict_ctxt ann_expr && floatConsts env && (not strict_ctxt || is_bot || exprIsHNF expr) +hasFreeJoin :: LevelEnv -> DVarSet -> Bool +-- Has a free join point which is not being floated to top level. +-- (In the latter case it won't be a join point any more.) +-- Not treating top-level ones specially had a massive effect +-- on nofib/minimax/Prog.prog +hasFreeJoin env fvs + = not (maxFvLevel isJoinId env fvs == tOP_LEVEL) + isBottomThunk :: Maybe (Arity, s) -> Bool -- See Note [Bottoming floats] (2) isBottomThunk (Just (0, _)) = True -- Zero arity diff --git a/compiler/GHC/Core/Opt/Simplify.hs b/compiler/GHC/Core/Opt/Simplify.hs index 355dd256c1..9c558f2053 100644 --- a/compiler/GHC/Core/Opt/Simplify.hs +++ b/compiler/GHC/Core/Opt/Simplify.hs @@ -664,13 +664,6 @@ prepareRhs mode top_lvl occ rhs0 go _ other = return (False, emptyLetFloats, other) -makeTrivialArg :: SimplMode -> ArgSpec -> SimplM (LetFloats, ArgSpec) -makeTrivialArg mode arg@(ValArg { as_arg = e }) - = do { (floats, e') <- makeTrivial mode NotTopLevel (fsLit "arg") e - ; return (floats, arg { as_arg = e' }) } -makeTrivialArg _ arg - = return (emptyLetFloats, arg) -- CastBy, TyArg - makeTrivial :: SimplMode -> TopLevelFlag -> FastString -- ^ A "friendly name" to build the new binder from -> OutExpr -- ^ This expression satisfies the let/app invariant @@ -3325,9 +3318,11 @@ mkDupableCont env (TickIt t cont) mkDupableCont env (StrictBind { sc_bndr = bndr, sc_bndrs = bndrs , sc_body = body, sc_env = se, sc_cont = cont}) - -- See Note [Duplicating StrictBind] +-- See Note [Duplicating StrictBind] +-- K[ let x = <> in b ] --> join j x = K[ b ] +-- j <> = do { let sb_env = se `setInScopeFromE` env - ; (sb_env1, bndr') <- simplBinder sb_env bndr + ; (sb_env1, bndr') <- simplBinder sb_env bndr ; (floats1, join_inner) <- simplLam sb_env1 bndrs body cont -- No need to use mkDupableCont before simplLam; we -- use cont once here, and then share the result if necessary @@ -3335,37 +3330,21 @@ mkDupableCont env (StrictBind { sc_bndr = bndr, sc_bndrs = bndrs ; let join_body = wrapFloats floats1 join_inner res_ty = contResultType cont - ; (floats2, body2) - <- if exprIsDupable (targetPlatform (seDynFlags env)) join_body - then return (emptyFloats env, join_body) - else do { join_bndr <- newJoinId [bndr'] res_ty - ; let join_call = App (Var join_bndr) (Var bndr') - join_rhs = Lam (setOneShotLambda bndr') join_body - join_bind = NonRec join_bndr join_rhs - floats = emptyFloats env `extendFloats` join_bind - ; return (floats, join_call) } - ; return ( floats2 - , StrictBind { sc_bndr = bndr', sc_bndrs = [] - , sc_body = body2 - , sc_env = zapSubstEnv se `setInScopeFromF` floats2 - -- See Note [StaticEnv invariant] in GHC.Core.Opt.Simplify.Utils - , sc_dup = OkToDup - , sc_cont = mkBoringStop res_ty } ) } - -mkDupableCont env (StrictArg { sc_fun = info, sc_cci = cci - , sc_cont = cont, sc_fun_ty = fun_ty, sc_mult = m }) - -- See Note [Duplicating StrictArg] - -- NB: sc_dup /= OkToDup; that is caught earlier by contIsDupable - = do { (floats1, cont') <- mkDupableCont env cont - ; (floats_s, args') <- mapAndUnzipM (makeTrivialArg (getMode env)) - (ai_args info) - ; return ( foldl' addLetFloats floats1 floats_s - , StrictArg { sc_fun = info { ai_args = args' } - , sc_cont = cont' - , sc_cci = cci - , sc_fun_ty = fun_ty - , sc_mult = m - , sc_dup = OkToDup} ) } + ; mkDupableStrictBind env RhsCtxt bndr' join_body res_ty } + +mkDupableCont env (StrictArg { sc_fun = fun, sc_cci = cci + , sc_cont = cont, sc_fun_ty = fun_ty + , sc_mult = m }) +-- See Note [Duplicating StrictArg] +-- NB: sc_dup /= OkToDup; that is caught earlier by contIsDupable +-- K[ f a b <> ] --> join j x = K[ f a b x ] +-- j <> + = do { let arg_ty = funArgTy fun_ty + rhs_ty = contResultType cont + ; arg_bndr <- newId (fsLit "arg") m arg_ty -- ToDo: check this linearity argument + ; let env' = env `addNewInScopeIds` [arg_bndr] + ; (floats, join_rhs) <- rebuildCall env' (addValArgTo fun (m, Var arg_bndr) fun_ty) cont + ; mkDupableStrictBind env' cci arg_bndr (wrapFloats floats join_rhs) rhs_ty } mkDupableCont env (ApplyToTy { sc_cont = cont , sc_arg_ty = arg_ty, sc_hole_ty = hole_ty }) @@ -3439,6 +3418,34 @@ mkDupableCont env (Select { sc_bndr = case_bndr, sc_alts = alts -- See Note [StaticEnv invariant] in GHC.Core.Opt.Simplify.Utils , sc_cont = mkBoringStop (contResultType cont) } ) } +mkDupableStrictBind :: SimplEnv -> CallCtxt -> OutId -> OutExpr -> OutType + -> SimplM (SimplFloats, SimplCont) +mkDupableStrictBind env cci arg_bndr join_rhs res_ty + | exprIsDupable (targetPlatform (seDynFlags env)) join_rhs + = return (emptyFloats env + , StrictBind { sc_bndr = arg_bndr, sc_bndrs = [] + , sc_body = join_rhs + , sc_env = zapSubstEnv env + -- See Note [StaticEnv invariant] in GHC.Core.Opt.Simplify.Utils + , sc_dup = OkToDup + , sc_cont = mkBoringStop res_ty } ) + | otherwise + = do { join_bndr <- newJoinId [arg_bndr] res_ty + ; let arg_info = ArgInfo { ai_fun = join_bndr + , ai_rules = Nothing, ai_args = [] + , ai_encl = False, ai_strs = repeat False + , ai_discs = repeat 0 } + ; return ( addJoinFloats (emptyFloats env) $ + unitJoinFloat $ + NonRec join_bndr $ + Lam (setOneShotLambda arg_bndr) join_rhs + , StrictArg { sc_dup = OkToDup + , sc_fun = arg_info + , sc_fun_ty = idType join_bndr + , sc_cont = mkBoringStop res_ty + , sc_mult = Many -- ToDo: check this! + , sc_cci = cci } ) } + mkDupableAlt :: Platform -> OutId -> JoinFloats -> OutAlt -> SimplM (JoinFloats, OutAlt) @@ -3612,56 +3619,75 @@ type variables as well as term variables. Note [Duplicating StrictArg] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -We make a StrictArg duplicable simply by making all its -stored-up arguments (in sc_fun) trivial, by let-binding -them. Thus: - f E [..hole..] - ==> let a = E - in f a [..hole..] -Now if the thing in the hole is a case expression (which is when -we'll call mkDupableCont), we'll push the function call into the -branches, which is what we want. Now RULES for f may fire, and -call-pattern specialisation. Here's an example from #3116 +What code do we want for this? + + f (case x1 of { T -> F; F -> T }) + (case x2 of { T -> F; F -> T }) + ...etc... + +when f is strict in all its arguments. (It might, for example, be a +strict data constructor whose wrapper has not yet been inlined.) + +Morally, we want to evaluate each argument in turn, and then call f. +Eavluating each argument has a case-split, so we'll get a diamond +pattern of join points, like this, assuming we evaluate the args +left-to-right: + + join { + j1 a1 = join { + j2 a2 = ..... + } in case x2 of { T -> j2 F; j2 T } + } in case x1 of { T -> j1 F; F -> j1 T } + +So when we want to duplicate a StrictArg continuation, we +want to use this transformation + K[ f a b <> ] --> join j x = K[ f a b x ] + in j <> + +-- Downsides -- + +This plan has some downsides, because now the call to 'f' can't +"see" the actual argument 'x' which might be important for RULES +or call-pattern specialisation. Here's an example from #3116 + go (n+1) (case l of 1 -> bs' _ -> Chunk p fpc (o+1) (l-1) bs') -If we can push the call for 'go' inside the case, we get + +If we pushed the entire call for 'go' inside the case, we get call-pattern specialisation for 'go', which is *crucial* for this program. -Here is the (&&) example: - && E (case x of { T -> F; F -> T }) - ==> let a = E in - case x of { T -> && a F; F -> && a T } -Much better! - -Notice that - * Arguments to f *after* the strict one are handled by - the ApplyToVal case of mkDupableCont. Eg - f [..hole..] E - - * We can only do the let-binding of E because the function - part of a StrictArg continuation is an explicit syntax - tree. In earlier versions we represented it as a function - (CoreExpr -> CoreEpxr) which we couldn't take apart. - -Historical aide: previously we did this (where E is a -big argument: - f E [..hole..] - ==> let $j = \a -> f E a - in $j [..hole..] - -But this is terrible! Here's an example: +Here is another example. With our current approach we see && E (case x of { T -> F; F -> T }) -Now, && is strict so we end up simplifying the case with -an ArgOf continuation. If we let-bind it, we get - let $j = \v -> && E v - in simplExpr (case x of { T -> F; F -> T }) - (ArgOf (\r -> $j r) -And after simplifying more we get + ==> let $j = \v -> && E v in case x of { T -> $j F; F -> $j T } -Which is a Very Bad Thing + +But we'd prefer to get + let a = E + in case x of { T -> && a F; F -> && a T } + +Pushing the whole call inwards in this way is precisely the change +that was made in #3116, but /un-done/ by my fix to #13253. Why? +Because pushing the whole call inwards works very badly in some cases. + + f (case x1 of { T->F; F->T }) (case x2..) ... + +==> GHC 8.10 duplicate StrictArg + (case x1 of { T -> f F, F -> f T }) + (case x2 ...) + (case x3 ...) +==> duplicate ApplyToVal + let a2 = case x2 of ... + a3 = case x3 of ... + in case x1 of { T -> f F a2 a3 ... ; F -> f T a2 a3 ... } + +Now there is an Awful Danger than we'll postInlineUnconditionally a2 +and a3, and repeat the whole exercise, leading to exponential code +size. Moreover, if we don't, those ai lets are really strict; so not +or later they will be dealt with via Note [Duplicating StrictBind]. +StrictArg and StrictBind should be handled the same. Note [Duplicating StrictBind] @@ -3671,9 +3697,10 @@ that for case expressions. After all, let x* = e in b is similar to case e of x -> b So we potentially make a join-point for the body, thus: - let x = [] in b ==> join j x = b - in let x = [] in j x + let x = <> in b ==> join j x = b + in j <> +Just like StrictArg in fact -- and indeed they share code. Note [Join point abstraction] Historical note ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/compiler/GHC/Core/Opt/Simplify/Utils.hs b/compiler/GHC/Core/Opt/Simplify/Utils.hs index e9ee16157f..cf56ac9c94 100644 --- a/compiler/GHC/Core/Opt/Simplify/Utils.hs +++ b/compiler/GHC/Core/Opt/Simplify/Utils.hs @@ -1201,9 +1201,9 @@ preInlineUnconditionally env top_lvl bndr rhs rhs_env extend_subst_with inl_rhs = extendIdSubst env bndr (mkContEx rhs_env inl_rhs) one_occ IAmDead = True -- Happens in ((\x.1) v) - one_occ OneOcc{ occ_one_br = InOneBranch + one_occ OneOcc{ occ_n_br = 1 , occ_in_lam = NotInsideLam } = isNotTopLevel top_lvl || early_phase - one_occ OneOcc{ occ_one_br = InOneBranch + one_occ OneOcc{ occ_n_br = 1 , occ_in_lam = IsInsideLam , occ_int_cxt = IsInteresting } = canInlineInLam rhs one_occ _ = False @@ -1329,12 +1329,17 @@ postInlineUnconditionally env top_lvl bndr occ_info rhs -- False -> case x of ... -- This is very important in practice; e.g. wheel-seive1 doubles -- in allocation if you miss this out - OneOcc { occ_in_lam = in_lam, occ_int_cxt = int_cxt } - -- OneOcc => no code-duplication issue - -> smallEnoughToInline dflags unfolding -- Small enough to dup + + OneOcc { occ_in_lam = in_lam, occ_int_cxt = int_cxt, occ_n_br = n_br } + -> -- See Note [Suppress exponential blowup] + n_br < (case int_cxt of + IsInteresting -> 16 + NotInteresting -> 4) + + && smallEnoughToInline dflags unfolding -- Small enough to dup -- ToDo: consider discount on smallEnoughToInline if int_cxt is true -- - -- NB: Do NOT inline arbitrarily big things, even if one_br is True + -- NB: Do NOT inline arbitrarily big things, even if occ_n_br=1 -- Reason: doing so risks exponential behaviour. We simplify a big -- expression, inline it, and simplify it again. But if the -- very same thing happens in the big expression, we get @@ -1381,7 +1386,35 @@ postInlineUnconditionally env top_lvl bndr occ_info rhs active = isActive (sm_phase (getMode env)) (idInlineActivation bndr) -- See Note [pre/postInlineUnconditionally in gentle mode] -{- +{- Note [Suppress exponential blowup] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +In #13253, and a raft of related tickets, we got an exponential blowup +in code size from postInlineUnconditionally. The trouble comes when +we have + let j1a = case f y of { True -> p; False -> q } + j1b = case f y of { True -> q; False -> p } + j2a = case f (y+1) of { True -> j1a; False -> j1b } + j2b = case f (y+1) of { True -> j1b; False -> j1a } + ... + in case f (y+10) of { True -> j10a; False -> j10b } + +when there are many branches. In pass 1, postInlineUnconditionally +inlines j10a and j10b (they are both small). Now we have two calls +to j9a and two to j9b. In pass 2, postInlineUnconditionally inlines +all four of these calls, leaving four calls to j8a and j8b. Etc. +Yikes! This is exponential! + +Moreover, this structure can and does arise easily, as the +tickets show: it's just a sequence of diamond control flow blocks. + +Solution: stop doing postInlineUnconditionally for some fixed, +smallish number of branches, say 4. + +This still leaves the nasty possiblity that /ordinary/ inlining (not +postInlineUnconditionally) might inline these join points, each of +which is individually quiet small. I'm still not sure what to do +about this (see #15488). But let's kill off one problem anyway. + Note [Top level and postInlineUnconditionally] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We don't do postInlineUnconditionally for top-level things (even for diff --git a/compiler/GHC/Core/SimpleOpt.hs b/compiler/GHC/Core/SimpleOpt.hs index 8525fb292f..3735cded34 100644 --- a/compiler/GHC/Core/SimpleOpt.hs +++ b/compiler/GHC/Core/SimpleOpt.hs @@ -433,7 +433,7 @@ simple_bind_pair env@(SOE { soe_inl = inl_env, soe_subst = subst }) safe_to_inline IAmALoopBreaker{} = False safe_to_inline IAmDead = True safe_to_inline OneOcc{ occ_in_lam = NotInsideLam - , occ_one_br = InOneBranch } = True + , occ_n_br = 1 } = True safe_to_inline OneOcc{} = False safe_to_inline ManyOccs{} = False diff --git a/compiler/GHC/Types/Basic.hs b/compiler/GHC/Types/Basic.hs index cf373f76d5..4559a4c913 100644 --- a/compiler/GHC/Types/Basic.hs +++ b/compiler/GHC/Types/Basic.hs @@ -68,7 +68,7 @@ module GHC.Types.Basic ( isNoOccInfo, strongLoopBreaker, weakLoopBreaker, InsideLam(..), - OneBranch(..), + BranchCount, oneBranch, InterestingCxt(..), TailCallInfo(..), tailCallInfo, zapOccTailCallInfo, isAlwaysTailCalled, @@ -978,7 +978,7 @@ data OccInfo -- lambda and case-bound variables. | OneOcc { occ_in_lam :: !InsideLam - , occ_one_br :: !OneBranch + , occ_n_br :: {-# UNPACK #-} !BranchCount , occ_int_cxt :: !InterestingCxt , occ_tail :: !TailCallInfo } -- ^ Occurs exactly once (per branch), not inside a rule @@ -992,6 +992,11 @@ data OccInfo type RulesOnly = Bool +type BranchCount = Int -- For OneOcc, says how many syntactic occurrences there are + +oneBranch :: BranchCount +oneBranch = 1 + {- Note [LoopBreaker OccInfo] ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1058,14 +1063,6 @@ instance Monoid InsideLam where mappend = (Semi.<>) ----------------- -data OneBranch - = InOneBranch - -- ^ One syntactic occurrence: Occurs in only one case branch - -- so no code-duplication issue to worry about - | MultipleBranches - deriving (Eq) - ------------------ data TailCallInfo = AlwaysTailCalled JoinArity -- See Note [TailCallInfo] | NoTailCallInfo deriving (Eq) @@ -1124,12 +1121,10 @@ instance Outputable OccInfo where pp_ro | rule_only = char '!' | otherwise = empty ppr (OneOcc inside_lam one_branch int_cxt tail_info) - = text "Once" <> pp_lam inside_lam <> pp_br one_branch <> pp_args int_cxt <> pp_tail + = text "Once" <> pp_lam inside_lam <> ppr one_branch <> pp_args int_cxt <> pp_tail where pp_lam IsInsideLam = char 'L' pp_lam NotInsideLam = empty - pp_br MultipleBranches = char '*' - pp_br InOneBranch = empty pp_args IsInteresting = char '!' pp_args NotInteresting = empty pp_tail = pprShortTailCallInfo tail_info @@ -1156,7 +1151,7 @@ AlwaysTailCalled. Note that there is a 'TailCallInfo' on a 'ManyOccs' value. One might expect that being tail-called would mean that the variable could only appear once per branch -(thus getting a `OneOcc { occ_one_br = True }` occurrence info), but a join +(thus getting a `OneOcc { }` occurrence info), but a join point can also be invoked from other join points, not just from case branches: let j1 x = ... @@ -1167,7 +1162,7 @@ point can also be invoked from other join points, not just from case branches: C -> j2 q Here both 'j1' and 'j2' will get marked AlwaysTailCalled, but j1 will get -ManyOccs and j2 will get `OneOcc { occ_one_br = True }`. +ManyOccs and j2 will get `OneOcc { occ_n_br = 2 }`. ************************************************************************ * * diff --git a/compiler/GHC/Types/Id/Info.hs b/compiler/GHC/Types/Id/Info.hs index dfd6ef96ab..f67f581b74 100644 --- a/compiler/GHC/Types/Id/Info.hs +++ b/compiler/GHC/Types/Id/Info.hs @@ -58,7 +58,7 @@ module GHC.Types.Id.Info ( isDeadOcc, isStrongLoopBreaker, isWeakLoopBreaker, occInfo, setOccInfo, - InsideLam(..), OneBranch(..), + InsideLam(..), BranchCount, TailCallInfo(..), tailCallInfo, isAlwaysTailCalled, |