summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Graf <sebastian.graf@kit.edu>2020-11-23 11:42:04 +0100
committerMarge Bot <ben+marge-bot@smart-cactus.org>2020-12-12 04:45:09 -0500
commit5bd71bfd3a410ff2edcd29306a9824d60857f9fd (patch)
tree978d1366447bc4c97d2df573c548f533aa99775d
parent4af6126d1758d5e365cadf032e34c99489f13dee (diff)
downloadhaskell-5bd71bfd3a410ff2edcd29306a9824d60857f9fd.tar.gz
DmdAnal: Annotate top-level function bindings with demands (#18894)
It's useful to annotate a non-exported top-level function like `g` in ```hs module Lib (h) where g :: Int -> Int -> (Int,Int) g m 1 = (m, 0) g m n = (2 * m, 2 `div` n) {-# NOINLINE g #-} h :: Int -> Int h 1 = 0 h m | odd m = snd (g m 2) | otherwise = uncurry (+) (g 2 m) ``` with its demand `UCU(CS(P(1P(U),SP(U))`, which tells us that whenever `g` was called, the second component of the returned pair was evaluated strictly. Since #18903 we do so for local functions, where we can see all calls. For top-level functions, we can assume that all *exported* functions are demanded according to `topDmd` and thus get sound demands for non-exported top-level functions. The demand on `g` is crucial information for Nested CPR, which may the go on and unbox `g` for the second pair component. That is true even if that pair component may diverge, as is the case for the call site `g 13 0`, which throws a div-by-zero exception. In `T18894b`, you can even see the new demand annotation enabling us to eta-expand a function that we wouldn't be able to eta-expand without Call Arity. We only track bindings of function type in order not to risk huge compile-time regressions, see `isInterestingTopLevelFn`. There was a CoreLint check that rejected strict demand annotations on recursive or top-level bindings, which seems completely unjustified. All the cases I investigated were fine, so I removed it. Fixes #18894.
-rw-r--r--compiler/GHC/Core/FVs.hs7
-rw-r--r--compiler/GHC/Core/Lint.hs15
-rw-r--r--compiler/GHC/Core/Opt/DmdAnal.hs374
-rw-r--r--compiler/GHC/Core/Opt/Pipeline.hs18
-rw-r--r--compiler/GHC/Core/Opt/Simplify/Env.hs5
-rw-r--r--compiler/GHC/Core/Opt/WorkWrap.hs6
-rw-r--r--compiler/GHC/Core/Opt/WorkWrap/Utils.hs8
-rw-r--r--compiler/GHC/Core/Tidy.hs4
-rw-r--r--compiler/GHC/Types/Demand.hs10
-rw-r--r--compiler/GHC/Types/Id/Info.hs2
-rw-r--r--testsuite/tests/stranal/should_compile/T18894.hs28
-rw-r--r--testsuite/tests/stranal/should_compile/T18894.stderr404
-rw-r--r--testsuite/tests/stranal/should_compile/T18894b.hs20
-rw-r--r--testsuite/tests/stranal/should_compile/T18894b.stderr187
-rw-r--r--testsuite/tests/stranal/should_compile/all.T4
15 files changed, 937 insertions, 155 deletions
diff --git a/compiler/GHC/Core/FVs.hs b/compiler/GHC/Core/FVs.hs
index 0cbf81d528..7fc5c6994c 100644
--- a/compiler/GHC/Core/FVs.hs
+++ b/compiler/GHC/Core/FVs.hs
@@ -525,12 +525,11 @@ ruleLhsFVIds (Rule { ru_bndrs = bndrs, ru_args = args })
= filterFV isLocalId $ addBndrs bndrs (exprs_fvs args)
ruleRhsFreeIds :: CoreRule -> VarSet
--- ^ This finds all locally-defined free Ids on the left hand side of a rule
+-- ^ This finds all locally-defined free Ids on the right hand side of a rule
-- and returns them as a non-deterministic set
ruleRhsFreeIds (BuiltinRule {}) = emptyVarSet
-ruleRhsFreeIds (Rule { ru_bndrs = bndrs, ru_args = args })
- = fvVarSet $ filterFV isLocalId $
- addBndrs bndrs $ exprs_fvs args
+ruleRhsFreeIds (Rule { ru_bndrs = bndrs, ru_rhs = rhs })
+ = fvVarSet $ filterFV isLocalId $ addBndrs bndrs $ expr_fvs rhs
{-
Note [Rule free var hack] (Not a hack any more)
diff --git a/compiler/GHC/Core/Lint.hs b/compiler/GHC/Core/Lint.hs
index a1eae78a60..6dc84b91ab 100644
--- a/compiler/GHC/Core/Lint.hs
+++ b/compiler/GHC/Core/Lint.hs
@@ -624,14 +624,6 @@ lintLetBind top_lvl rec_flag binder rhs rhs_ty
|| exprIsTickedString rhs)
(badBndrTyMsg binder (text "unlifted"))
- -- Check that if the binder is top-level or recursive, it's not
- -- demanded. Primitive string literals are exempt as there is no
- -- computation to perform, see Note [Core top-level string literals].
- ; checkL (not (isStrictId binder)
- || (isNonRec rec_flag && not (isTopLevel top_lvl))
- || exprIsTickedString rhs)
- (mkStrictMsg binder)
-
-- Check that if the binder is at the top level and has type Addr#,
-- that it is a string literal, see
-- Note [Core top-level string literals].
@@ -3120,13 +3112,6 @@ badBndrTyMsg binder what
= vcat [ text "The type of this binder is" <+> what <> colon <+> ppr binder
, text "Binder's type:" <+> ppr (idType binder) ]
-mkStrictMsg :: Id -> MsgDoc
-mkStrictMsg binder
- = vcat [hsep [text "Recursive or top-level binder has strict demand info:",
- ppr binder],
- hsep [text "Binder's demand info:", ppr (idDemandInfo binder)]
- ]
-
mkNonTopExportedMsg :: Id -> MsgDoc
mkNonTopExportedMsg binder
= hsep [text "Non-top-level binder is marked as exported:", ppr binder]
diff --git a/compiler/GHC/Core/Opt/DmdAnal.hs b/compiler/GHC/Core/Opt/DmdAnal.hs
index 4869fb1fa9..6eb3c895e2 100644
--- a/compiler/GHC/Core/Opt/DmdAnal.hs
+++ b/compiler/GHC/Core/Opt/DmdAnal.hs
@@ -37,6 +37,7 @@ import GHC.Core.Type
import GHC.Core.FVs ( exprFreeIds, ruleRhsFreeIds )
import GHC.Core.Coercion ( Coercion, coVarsOfCo )
import GHC.Core.FamInstEnv
+import GHC.Core.Opt.Arity ( typeArity )
import GHC.Utils.Misc
import GHC.Utils.Panic
import GHC.Data.Maybe ( isJust )
@@ -64,28 +65,55 @@ data DmdAnalOpts = DmdAnalOpts
--
-- Note: use `seqBinds` on the result to avoid leaks due to lazyness (cf Note
-- [Stamp out space leaks in demand analysis])
-dmdAnalProgram :: DmdAnalOpts -> FamInstEnvs -> CoreProgram -> CoreProgram
-dmdAnalProgram opts fam_envs binds = binds_plus_dmds
- where
- env = emptyAnalEnv opts fam_envs
- binds_plus_dmds = snd $ mapAccumL dmdAnalTopBind env binds
-
--- Analyse a (group of) top-level binding(s)
-dmdAnalTopBind :: AnalEnv
- -> CoreBind
- -> (AnalEnv, CoreBind)
-dmdAnalTopBind env (NonRec id rhs)
- = ( extendAnalEnv TopLevel env id sig
- , NonRec (setIdStrictness id sig) rhs')
+dmdAnalProgram :: DmdAnalOpts -> FamInstEnvs -> [CoreRule] -> CoreProgram -> CoreProgram
+dmdAnalProgram opts fam_envs rules binds
+ = snd $ go (emptyAnalEnv opts fam_envs) binds
where
- ( _, sig, rhs') = dmdAnalRhsLetDown Nothing env topSubDmd id rhs
+ -- See Note [Analysing top-level bindings]
+ -- and Note [Why care for top-level demand annotations?]
+ go _ [] = (nopDmdType, [])
+ go env (b:bs) = cons_up $ dmdAnalBind TopLevel env topSubDmd b anal_body
+ where
+ anal_body env'
+ | (body_ty, bs') <- go env' bs
+ = (add_exported_uses env' body_ty (bindersOf b), bs')
+
+ cons_up :: (a, b, [b]) -> (a, [b])
+ cons_up (dmd_ty, b', bs') = (dmd_ty, b':bs')
+
+ add_exported_uses :: AnalEnv -> DmdType -> [Id] -> DmdType
+ add_exported_uses env = foldl' (add_exported_use env)
+
+ -- | If @e@ is denoted by @dmd_ty@, then @add_exported_use _ dmd_ty id@
+ -- corresponds to the demand type of @(id, e)@, but is a lot more direct.
+ -- See Note [Analysing top-level bindings].
+ add_exported_use :: AnalEnv -> DmdType -> Id -> DmdType
+ add_exported_use env dmd_ty id
+ | isExportedId id || elemVarSet id rule_fvs
+ -- See Note [Absence analysis for stable unfoldings and RULES]
+ = dmd_ty `plusDmdType` fst (dmdAnalStar env topDmd (Var id))
+ | otherwise
+ = dmd_ty
-dmdAnalTopBind env (Rec pairs)
- = (env', Rec pairs')
- where
- (env', _, pairs') = dmdFix TopLevel env topSubDmd pairs
- -- We get two iterations automatically
- -- c.f. the NonRec case above
+ rule_fvs :: IdSet
+ rule_fvs = foldr (unionVarSet . ruleRhsFreeIds) emptyVarSet rules
+
+-- | We attach useful (e.g. not 'topDmd') 'idDemandInfo' to top-level bindings
+-- that satisfy this function.
+--
+-- Basically, we want to know how top-level *functions* are *used*
+-- (e.g. called). The information will always be lazy.
+-- Any other top-level bindings are boring.
+--
+-- See also Note [Why care for top-level demand annotations?].
+isInterestingTopLevelFn :: Id -> Bool
+-- SG tried to set this to True and got a +2% ghc/alloc regression in T5642
+-- (which is dominated by the Simplifier) at no gain in analysis precision.
+-- If there was a gain, that regression might be acceptable.
+-- Plus, we could use LetUp for thunks and share some code with local let
+-- bindings.
+isInterestingTopLevelFn id =
+ typeArity (idType id) `lengthExceeds` 0
{- Note [Stamp out space leaks in demand analysis]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -105,8 +133,80 @@ generation would hold on to an extra copy of the Core program, via
unforced thunks in demand or strictness information; and it is the
most memory-intensive part of the compilation process, so this added
seqBinds makes a big difference in peak memory usage.
--}
+Note [Analysing top-level bindings]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Consider a CoreProgram like
+ e1 = ...
+ n1 = ...
+ e2 = \a b -> ... fst (n1 a b) ...
+ n2 = \c d -> ... snd (e2 c d) ...
+ ...
+where e* are exported, but n* are not.
+Intuitively, we can see that @n1@ is only ever called with two arguments
+and in every call site, the first component of the result of the call
+is evaluated. Thus, we'd like it to have idDemandInfo @UCU(C1(P(SU,A))@.
+NB: We may *not* give e2 a similar annotation, because it is exported and
+external callers might use it in arbitrary ways, expressed by 'topDmd'.
+This can then be exploited by Nested CPR and eta-expansion,
+see Note [Why care for top-level demand annotations?].
+
+How do we get this result? Answer: By analysing the program as if it was a let
+expression of this form:
+ let e1 = ... in
+ let n1 = ... in
+ let e2 = ... in
+ let n2 = ... in
+ (e1,e2, ...)
+E.g. putting all bindings in nested lets and returning all exported binders in a tuple.
+Of course, we will not actually build that CoreExpr! Instead we faithfully
+simulate analysis of said expression by adding the free variable 'DmdEnv'
+of @e*@'s strictness signatures to the 'DmdType' we get from analysing the
+nested bindings.
+
+And even then the above form blows up analysis performance in T10370:
+If @e1@ uses many free variables, we'll unnecessarily carry their demands around
+with us from the moment we analyse the pair to the moment we bubble back up to
+the binding for @e1@. So instead we analyse as if we had
+ let e1 = ... in
+ (e1, let n1 = ... in
+ ( let e2 = ... in
+ (e2, let n2 = ... in
+ ( ...))))
+That is, a series of right-nested pairs, where the @fst@ are the exported
+binders of the last enclosing let binding and @snd@ continues the nested
+lets.
+
+Variables occuring free in RULE RHSs are to be handled the same as exported Ids.
+See also Note [Absence analysis for stable unfoldings and RULES].
+
+Note [Why care for top-level demand annotations?]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Reading Note [Analysing top-level bindings], you might think that we go through
+quite some trouble to get useful demands for top-level bindings. They can never
+be strict, for example, so why bother?
+
+First, we get to eta-expand top-level bindings that we weren't able to
+eta-expand before without Call Arity. From T18894b:
+ module T18894b (f) where
+ eta :: Int -> Int -> Int
+ eta x = if fst (expensive x) == 13 then \y -> ... else \y -> ...
+ f m = ... eta m 2 ... eta 2 m ...
+Since only @f@ is exported, we see all call sites of @eta@ and can eta-expand to
+arity 2.
+
+The call demands we get for some top-level bindings will also allow Nested CPR
+to unbox deeper. From T18894:
+ module T18894 (h) where
+ g m n = (2 * m, 2 `div` n)
+ {-# NOINLINE g #-}
+ h :: Int -> Int
+ h m = ... snd (g m 2) ... uncurry (+) (g 2 m) ...
+Only @h@ is exported, hence we see that @g@ is always called in contexts were we
+also force the division in the second component of the pair returned by @g@.
+This allows Nested CPR to evalute the division eagerly and return an I# in its
+position.
+-}
{-
************************************************************************
@@ -114,7 +214,103 @@ seqBinds makes a big difference in peak memory usage.
\subsection{The analyser itself}
* *
************************************************************************
+-}
+
+-- | Analyse a binding group and its \"body\", e.g. where it is in scope.
+--
+-- It calls a function that knows how to analyse this \"body\" given
+-- an 'AnalEnv' with updated demand signatures for the binding group
+-- (reflecting their 'idStrictnessInfo') and expects to receive a
+-- 'DmdType' in return, which it uses to annotate the binding group with their
+-- 'idDemandInfo'.
+dmdAnalBind
+ :: TopLevelFlag
+ -> AnalEnv
+ -> SubDemand -- ^ Demand put on the "body"
+ -- (important for join points)
+ -> CoreBind
+ -> (AnalEnv -> (DmdType, a)) -- ^ How to analyse the "body", e.g.
+ -- where the binding is in scope
+ -> (DmdType, CoreBind, a)
+dmdAnalBind top_lvl env dmd bind anal_body = case bind of
+ NonRec id rhs
+ | useLetUp top_lvl id
+ -> dmdAnalBindLetUp top_lvl env id rhs anal_body
+ _ -> dmdAnalBindLetDown top_lvl env dmd bind anal_body
+
+-- | Annotates uninteresting top level functions ('isInterestingTopLevelFn')
+-- with 'topDmd', the rest with the given demand.
+setBindIdDemandInfo :: TopLevelFlag -> Id -> Demand -> Id
+setBindIdDemandInfo top_lvl id dmd = setIdDemandInfo id $ case top_lvl of
+ TopLevel | not (isInterestingTopLevelFn id) -> topDmd
+ _ -> dmd
+
+-- | Let bindings can be processed in two ways:
+-- Down (RHS before body) or Up (body before RHS).
+-- This function handles the up variant.
+--
+-- It is very simple. For let x = rhs in body
+-- * Demand-analyse 'body' in the current environment
+-- * Find the demand, 'rhs_dmd' placed on 'x' by 'body'
+-- * Demand-analyse 'rhs' in 'rhs_dmd'
+--
+-- This is used for a non-recursive local let without manifest lambdas (see
+-- 'useLetUp').
+--
+-- This is the LetUp rule in the paper “Higher-Order Cardinality Analysis”.
+dmdAnalBindLetUp :: TopLevelFlag -> AnalEnv -> Id -> CoreExpr -> (AnalEnv -> (DmdType, a)) -> (DmdType, CoreBind, a)
+dmdAnalBindLetUp top_lvl env id rhs anal_body = (final_ty, NonRec id' rhs', body')
+ where
+ (body_ty, body') = anal_body env
+ (body_ty', id_dmd) = findBndrDmd env notArgOfDfun body_ty id
+ id' = setBindIdDemandInfo top_lvl id id_dmd
+ (rhs_ty, rhs') = dmdAnalStar env (dmdTransformThunkDmd rhs id_dmd) rhs
+ final_ty = body_ty' `plusDmdType` rhs_ty
+
+-- | Let bindings can be processed in two ways:
+-- Down (RHS before body) or Up (body before RHS).
+-- This function handles the down variant.
+--
+-- It computes a demand signature (by means of 'dmdAnalRhsSig') and uses
+-- that at call sites in the body.
+--
+-- It is used for toplevel definitions, recursive definitions and local
+-- non-recursive definitions that have manifest lambdas (cf. 'useLetUp').
+-- Local non-recursive definitions without a lambda are handled with LetUp.
+--
+-- This is the LetDown rule in the paper “Higher-Order Cardinality Analysis”.
+dmdAnalBindLetDown :: TopLevelFlag -> AnalEnv -> SubDemand -> CoreBind -> (AnalEnv -> (DmdType, a)) -> (DmdType, CoreBind, a)
+dmdAnalBindLetDown top_lvl env dmd bind anal_body = case bind of
+ NonRec id rhs
+ | (env', lazy_fv, id1, rhs1) <-
+ dmdAnalRhsSig top_lvl NonRecursive env dmd id rhs
+ -> do_rest env' lazy_fv [(id1, rhs1)] (uncurry NonRec . only)
+ Rec pairs
+ | (env', lazy_fv, pairs') <- dmdFix top_lvl env dmd pairs
+ -> do_rest env' lazy_fv pairs' Rec
+ where
+ do_rest env' lazy_fv pairs1 build_bind = (final_ty, build_bind pairs2, body')
+ where
+ (body_ty, body') = anal_body env'
+ -- see Note [Lazy and unleashable free variables]
+ dmd_ty = addLazyFVs body_ty lazy_fv
+ (!final_ty, id_dmds) = findBndrsDmds env' dmd_ty (map fst pairs1)
+ pairs2 = zipWith do_one pairs1 id_dmds
+ do_one (id', rhs') dmd = (setBindIdDemandInfo top_lvl id' dmd, rhs')
+ -- If the actual demand is better than the vanilla call
+ -- demand, you might think that we might do better to re-analyse
+ -- the RHS with the stronger demand.
+ -- But (a) That seldom happens, because it means that *every* path in
+ -- the body of the let has to use that stronger demand
+ -- (b) It often happens temporarily in when fixpointing, because
+ -- the recursive function at first seems to place a massive demand.
+ -- But we don't want to go to extra work when the function will
+ -- probably iterate to something less demanding.
+ -- In practice, all the times the actual demand on id2 is more than
+ -- the vanilla call demand seem to be due to (b). So we don't
+ -- bother to re-analyse the RHS.
+{-
Note [Ensure demand is strict]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It's important not to analyse e with a lazy demand because
@@ -295,60 +491,11 @@ dmdAnal' env dmd (Case scrut case_bndr ty alts)
-- , text "res_ty" <+> ppr res_ty ]) $
(res_ty, Case scrut' case_bndr' ty alts')
--- Let bindings can be processed in two ways:
--- Down (RHS before body) or Up (body before RHS).
--- The following case handle the up variant.
---
--- It is very simple. For let x = rhs in body
--- * Demand-analyse 'body' in the current environment
--- * Find the demand, 'rhs_dmd' placed on 'x' by 'body'
--- * Demand-analyse 'rhs' in 'rhs_dmd'
---
--- This is used for a non-recursive local let without manifest lambdas.
--- This is the LetUp rule in the paper “Higher-Order Cardinality Analysis”.
-dmdAnal' env dmd (Let (NonRec id rhs) body)
- | useLetUp id
- = (final_ty, Let (NonRec id' rhs') body')
- where
- (body_ty, body') = dmdAnal env dmd body
- (body_ty', id_dmd) = findBndrDmd env notArgOfDfun body_ty id
- id' = setIdDemandInfo id id_dmd
-
- (rhs_ty, rhs') = dmdAnalStar env (dmdTransformThunkDmd rhs id_dmd) rhs
- final_ty = body_ty' `plusDmdType` rhs_ty
-
-dmdAnal' env dmd (Let (NonRec id rhs) body)
- = (body_ty2, Let (NonRec id2 rhs') body')
+dmdAnal' env dmd (Let bind body)
+ = (final_ty, Let bind' body')
where
- (lazy_fv, sig, rhs') = dmdAnalRhsLetDown Nothing env dmd id rhs
- id1 = setIdStrictness id sig
- env1 = extendAnalEnv NotTopLevel env id sig
- (body_ty, body') = dmdAnal env1 dmd body
- (body_ty1, id2) = annotateBndr env body_ty id1
- body_ty2 = addLazyFVs body_ty1 lazy_fv -- see Note [Lazy and unleashable free variables]
-
- -- If the actual demand is better than the vanilla call
- -- demand, you might think that we might do better to re-analyse
- -- the RHS with the stronger demand.
- -- But (a) That seldom happens, because it means that *every* path in
- -- the body of the let has to use that stronger demand
- -- (b) It often happens temporarily in when fixpointing, because
- -- the recursive function at first seems to place a massive demand.
- -- But we don't want to go to extra work when the function will
- -- probably iterate to something less demanding.
- -- In practice, all the times the actual demand on id2 is more than
- -- the vanilla call demand seem to be due to (b). So we don't
- -- bother to re-analyse the RHS.
-
-dmdAnal' env dmd (Let (Rec pairs) body)
- = let
- (env', lazy_fv, pairs') = dmdFix NotTopLevel env dmd pairs
- (body_ty, body') = dmdAnal env' dmd body
- body_ty1 = deleteFVs body_ty (map fst pairs)
- body_ty2 = addLazyFVs body_ty1 lazy_fv -- see Note [Lazy and unleashable free variables]
- in
- body_ty2 `seq`
- (body_ty2, Let (Rec pairs') body')
+ (final_ty, bind', body') = dmdAnalBind NotTopLevel env dmd bind go'
+ go' env' = dmdAnal env' dmd body
-- | A simple, syntactic analysis of whether an expression MAY throw a precise
-- exception when evaluated. It's always sound to return 'True'.
@@ -582,9 +729,17 @@ dmdTransform env var dmd
| Just (sig, top_lvl) <- lookupSigEnv env var
, let fn_ty = dmdTransformSig sig dmd
= -- pprTrace "dmdTransform:LetDown" (vcat [ppr var, ppr sig, ppr dmd, ppr fn_ty]) $
- if isTopLevel top_lvl
- then fn_ty -- Don't record demand on top-level things
- else addVarDmd fn_ty var (C_11 :* dmd)
+ case top_lvl of
+ NotTopLevel -> addVarDmd fn_ty var (C_11 :* dmd)
+ TopLevel
+ | isInterestingTopLevelFn var
+ -- Top-level things will be used multiple times or not at
+ -- all anyway, hence the multDmd below: It means we don't
+ -- have to track whether @var@ is used strictly or at most
+ -- once, because ultimately it never will.
+ -> addVarDmd fn_ty var (C_0N `multDmd` (C_11 :* dmd)) -- discard strictness
+ | otherwise
+ -> fn_ty -- don't bother tracking; just annotate with 'topDmd' later
-- Everything else:
-- * Local let binders for which we use LetUp (cf. 'useLetUp')
-- * Lambda binders
@@ -599,46 +754,46 @@ dmdTransform env var dmd
* *
********************************************************************* -}
--- Let bindings can be processed in two ways:
--- Down (RHS before body) or Up (body before RHS).
--- dmdAnalRhsLetDown implements the Down variant:
--- * assuming a demand of <L,U>
+-- | @dmdAnalRhsSig@ analyses the given RHS to compute a demand signature
+-- for the LetDown rule. It works as follows:
+--
+-- * assuming a demand of <U>
-- * looking at the definition
-- * determining a strictness signature
--
--- It is used for toplevel definition, recursive definitions and local
--- non-recursive definitions that have manifest lambdas.
--- Local non-recursive definitions without a lambda are handled with LetUp.
---
--- This is the LetDown rule in the paper “Higher-Order Cardinality Analysis”.
-dmdAnalRhsLetDown
- :: Maybe [Id] -- Just bs <=> recursive, Nothing <=> non-recursive
+-- Since it assumed a demand of <U>, the resulting signature is applicable at
+-- any call site.
+dmdAnalRhsSig
+ :: TopLevelFlag
+ -> RecFlag
-> AnalEnv -> SubDemand
-> Id -> CoreExpr
- -> (DmdEnv, StrictSig, CoreExpr)
+ -> (AnalEnv, DmdEnv, Id, CoreExpr)
-- Process the RHS of the binding, add the strictness signature
-- to the Id, and augment the environment with the signature as well.
-- See Note [NOINLINE and strictness]
-dmdAnalRhsLetDown rec_flag env let_dmd id rhs
- = -- pprTrace "dmdAnalRhsLetDown" (ppr id $$ ppr let_dmd $$ ppr sig $$ ppr lazy_fv) $
- (lazy_fv, sig, rhs')
+dmdAnalRhsSig top_lvl rec_flag env let_dmd id rhs
+ = -- pprTrace "dmdAnalRhsSig" (ppr id $$ ppr let_dmd $$ ppr sig $$ ppr lazy_fv) $
+ (env', lazy_fv, id', rhs')
where
rhs_arity = idArity id
+ -- See Note [Demand signatures are computed for a threshold demand based on idArity]
rhs_dmd -- See Note [Demand analysis for join points]
-- See Note [Invariants on join points] invariant 2b, in GHC.Core
-- rhs_arity matches the join arity of the join point
| isJoinId id
= mkCallDmds rhs_arity let_dmd
| otherwise
- -- NB: rhs_arity
- -- See Note [Demand signatures are computed for a threshold demand based on idArity]
- = mkRhsDmd env rhs_arity rhs
+ = mkCallDmds rhs_arity topSubDmd
(rhs_dmd_ty, rhs') = dmdAnal env rhs_dmd rhs
DmdType rhs_fv rhs_dmds rhs_div = rhs_dmd_ty
sig = mkStrictSigForArity rhs_arity (DmdType sig_fv rhs_dmds rhs_div)
+ id' = id `setIdStrictness` sig
+ env' = extendAnalEnv top_lvl env id' sig
+
-- See Note [Aggregated demand for cardinality]
-- FIXME: That Note doesn't explain the following lines at all. The reason
-- is really much different: When we have a recursive function, we'd
@@ -651,8 +806,8 @@ dmdAnalRhsLetDown rec_flag env let_dmd id rhs
-- we'd have to do an additional iteration. reuseEnv makes sure that
-- we never get used-once info for FVs of recursive functions.
rhs_fv1 = case rec_flag of
- Just bs -> reuseEnv (delVarEnvList rhs_fv bs)
- Nothing -> rhs_fv
+ Recursive -> reuseEnv rhs_fv
+ NonRecursive -> rhs_fv
rhs_fv2 = rhs_fv1 `keepAliveDmdEnv` extra_fvs
-- Find the RHS free vars of the unfoldings and RULES
@@ -669,13 +824,6 @@ dmdAnalRhsLetDown rec_flag env let_dmd id rhs
= exprFreeIds unf_body
| otherwise = emptyVarSet
--- | @mkRhsDmd env rhs_arity rhs@ creates a 'SubDemand' for
--- unleashing on the given function's @rhs@, by creating
--- a call demand of @rhs_arity@
--- See Historical Note [Product demands for function body]
-mkRhsDmd :: AnalEnv -> Arity -> CoreExpr -> SubDemand
-mkRhsDmd _env rhs_arity _rhs = mkCallDmds rhs_arity topSubDmd
-
-- | If given the (local, non-recursive) let-bound 'Id', 'useLetUp' determines
-- whether we should process the binding up (body before rhs) or down (rhs
-- before body).
@@ -720,8 +868,8 @@ mkRhsDmd _env rhs_arity _rhs = mkCallDmds rhs_arity topSubDmd
-- * For a more convincing example with join points, see Note [Demand analysis
-- for join points].
--
-useLetUp :: Var -> Bool
-useLetUp f = idArity f == 0 && not (isJoinId f)
+useLetUp :: TopLevelFlag -> Var -> Bool
+useLetUp top_lvl f = isNotTopLevel top_lvl && idArity f == 0 && not (isJoinId f)
{- Note [Demand analysis for join points]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -939,8 +1087,6 @@ dmdFix :: TopLevelFlag
dmdFix top_lvl env let_dmd orig_pairs
= loop 1 initial_pairs
where
- bndrs = map fst orig_pairs
-
-- See Note [Initialising strictness]
initial_pairs | ae_virgin env = [(setIdStrictness id botSig, rhs) | (id, rhs) <- orig_pairs ]
| otherwise = orig_pairs
@@ -990,10 +1136,8 @@ dmdFix top_lvl env let_dmd orig_pairs
= -- pprTrace "my_downRhs" (ppr id $$ ppr (idStrictness id) $$ ppr sig) $
((env', lazy_fv'), (id', rhs'))
where
- (lazy_fv1, sig, rhs') = dmdAnalRhsLetDown (Just bndrs) env let_dmd id rhs
- lazy_fv' = plusVarEnv_C plusDmd lazy_fv lazy_fv1
- env' = extendAnalEnv top_lvl env id sig
- id' = setIdStrictness id sig
+ (env', lazy_fv1, id', rhs') = dmdAnalRhsSig top_lvl Recursive env let_dmd id rhs
+ lazy_fv' = plusVarEnv_C plusDmd lazy_fv lazy_fv1
zapIdStrictness :: [(Id, CoreExpr)] -> [(Id, CoreExpr)]
zapIdStrictness pairs = [(setIdStrictness id nopSig, rhs) | (id, rhs) <- pairs ]
@@ -1090,7 +1234,7 @@ addLazyFVs dmd_ty lazy_fvs
-- demand with the bottom coming up from 'error'
--
-- I got a loop in the fixpointer without this, due to an interaction
- -- with the lazy_fv filtering in dmdAnalRhsLetDown. Roughly, it was
+ -- with the lazy_fv filtering in dmdAnalRhsSig. Roughly, it was
-- letrec f n x
-- = letrec g y = x `fatbar`
-- letrec h z = z + ...g...
@@ -1156,10 +1300,6 @@ annotateLamIdBndr env arg_of_dfun dmd_ty id
main_ty = addDemand dmd dmd_ty'
(dmd_ty', dmd) = findBndrDmd env arg_of_dfun dmd_ty id
-deleteFVs :: DmdType -> [Var] -> DmdType
-deleteFVs (DmdType fvs dmds res) bndrs
- = DmdType (delVarEnvList fvs bndrs) dmds res
-
{-
Note [NOINLINE and strictness]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/compiler/GHC/Core/Opt/Pipeline.hs b/compiler/GHC/Core/Opt/Pipeline.hs
index abddab3e45..872edca65a 100644
--- a/compiler/GHC/Core/Opt/Pipeline.hs
+++ b/compiler/GHC/Core/Opt/Pipeline.hs
@@ -66,6 +66,7 @@ import GHC.Types.SrcLoc
import GHC.Types.Id
import GHC.Types.Id.Info
import GHC.Types.Basic
+import GHC.Types.Demand ( zapDmdEnvSig )
import GHC.Types.Var.Set
import GHC.Types.Var.Env
import GHC.Types.Unique.Supply ( UniqSupply, mkSplitUniqSupply, splitUniqSupply )
@@ -499,7 +500,7 @@ doCorePass CoreDoExitify = {-# SCC "Exitify" #-}
doPass exitifyProgram
doCorePass CoreDoDemand = {-# SCC "DmdAnal" #-}
- doPassDFM dmdAnal
+ doPassDFRM dmdAnal
doCorePass CoreDoCpr = {-# SCC "CprAnal" #-}
doPassDFM cprAnalProgram
@@ -582,6 +583,13 @@ doPassDFM do_pass guts = do
let fam_envs = (p_fam_env, mg_fam_inst_env guts)
doPassM (liftIO . do_pass dflags fam_envs) guts
+doPassDFRM :: (DynFlags -> FamInstEnvs -> [CoreRule] -> CoreProgram -> IO CoreProgram) -> ModGuts -> CoreM ModGuts
+doPassDFRM do_pass guts = do
+ dflags <- getDynFlags
+ p_fam_env <- getPackageFamInstEnv
+ let fam_envs = (p_fam_env, mg_fam_inst_env guts)
+ doPassM (liftIO . do_pass dflags fam_envs (mg_rules guts)) guts
+
doPassDFU :: (DynFlags -> FamInstEnvs -> UniqSupply -> CoreProgram -> CoreProgram) -> ModGuts -> CoreM ModGuts
doPassDFU do_pass guts = do
dflags <- getDynFlags
@@ -1095,13 +1103,13 @@ transferIdInfo exported_id local_id
-dmdAnal :: DynFlags -> FamInstEnvs -> CoreProgram -> IO CoreProgram
-dmdAnal dflags fam_envs binds = do
+dmdAnal :: DynFlags -> FamInstEnvs -> [CoreRule] -> CoreProgram -> IO CoreProgram
+dmdAnal dflags fam_envs rules binds = do
let opts = DmdAnalOpts
{ dmd_strict_dicts = gopt Opt_DictsStrict dflags
}
- binds_plus_dmds = dmdAnalProgram opts fam_envs binds
+ binds_plus_dmds = dmdAnalProgram opts fam_envs rules binds
Err.dumpIfSet_dyn dflags Opt_D_dump_str_signatures "Strictness signatures" FormatText $
- dumpIdInfoOfProgram (ppr . strictnessInfo) binds_plus_dmds
+ dumpIdInfoOfProgram (ppr . zapDmdEnvSig . strictnessInfo) binds_plus_dmds
-- See Note [Stamp out space leaks in demand analysis] in GHC.Core.Opt.DmdAnal
seqBinds binds_plus_dmds `seq` return binds_plus_dmds
diff --git a/compiler/GHC/Core/Opt/Simplify/Env.hs b/compiler/GHC/Core/Opt/Simplify/Env.hs
index 29f0dc58b2..8ef66a6a9d 100644
--- a/compiler/GHC/Core/Opt/Simplify/Env.hs
+++ b/compiler/GHC/Core/Opt/Simplify/Env.hs
@@ -598,11 +598,10 @@ addJoinFlts = appOL
mkRecFloats :: SimplFloats -> SimplFloats
-- Flattens the floats into a single Rec group,
-- They must either all be lifted LetFloats or all JoinFloats
-mkRecFloats floats@(SimplFloats { sfLetFloats = LetFloats bs ff
+mkRecFloats floats@(SimplFloats { sfLetFloats = LetFloats bs _ff
, sfJoinFloats = jbs
, sfInScope = in_scope })
- = ASSERT2( case ff of { FltLifted -> True; _ -> False }, ppr (fromOL bs) )
- ASSERT2( isNilOL bs || isNilOL jbs, ppr floats )
+ = ASSERT2( isNilOL bs || isNilOL jbs, ppr floats )
SimplFloats { sfLetFloats = floats'
, sfJoinFloats = jfloats'
, sfInScope = in_scope }
diff --git a/compiler/GHC/Core/Opt/WorkWrap.hs b/compiler/GHC/Core/Opt/WorkWrap.hs
index 842a4981d1..68d3e314a3 100644
--- a/compiler/GHC/Core/Opt/WorkWrap.hs
+++ b/compiler/GHC/Core/Opt/WorkWrap.hs
@@ -484,7 +484,7 @@ tryWW dflags fam_envs is_rec fn_id rhs
| is_fun && is_eta_exp
= splitFun dflags fam_envs new_fn_id fn_info wrap_dmds div cpr rhs
- | is_thunk -- See Note [Thunk splitting]
+ | isNonRec is_rec, is_thunk -- See Note [Thunk splitting]
= splitThunk dflags fam_envs is_rec new_fn_id rhs
| otherwise
@@ -777,6 +777,10 @@ Notice that x certainly has the CPR property now!
In fact, splitThunk uses the function argument w/w splitting
function, so that if x's demand is deeper (say U(U(L,L),L))
then the splitting will go deeper too.
+
+NB: For recursive thunks, the Simplifier is unable to float `x-rhs` out of
+`x*`'s RHS, because `x*` occurs freely in `x-rhs`, and will just change it
+back to the original definition, so we just split non-recursive thunks.
-}
-- See Note [Thunk splitting]
diff --git a/compiler/GHC/Core/Opt/WorkWrap/Utils.hs b/compiler/GHC/Core/Opt/WorkWrap/Utils.hs
index 70c99485de..3551cd7d78 100644
--- a/compiler/GHC/Core/Opt/WorkWrap/Utils.hs
+++ b/compiler/GHC/Core/Opt/WorkWrap/Utils.hs
@@ -1275,10 +1275,14 @@ mk_absent_let dflags fam_envs arg
abs_rhs = mkAbsentErrorApp arg_ty msg
msg = showSDoc (gopt_set dflags Opt_SuppressUniques)
- (ppr arg <+> ppr (idType arg) <+> file_msg)
+ (vcat
+ [ text "Arg:" <+> ppr arg
+ , text "Type:" <+> ppr arg_ty
+ , file_msg
+ ])
file_msg = case outputFile dflags of
Nothing -> empty
- Just f -> text "in output file " <+> quotes (text f)
+ Just f -> text "In output file " <+> quotes (text f)
-- We need to suppress uniques here because otherwise they'd
-- end up in the generated code as strings. This is bad for
-- determinism, because with different uniques the strings
diff --git a/compiler/GHC/Core/Tidy.hs b/compiler/GHC/Core/Tidy.hs
index 948b1e3673..e5637d6fef 100644
--- a/compiler/GHC/Core/Tidy.hs
+++ b/compiler/GHC/Core/Tidy.hs
@@ -21,7 +21,7 @@ import GHC.Core
import GHC.Core.Seq ( seqUnfolding )
import GHC.Types.Id
import GHC.Types.Id.Info
-import GHC.Types.Demand ( zapUsageEnvSig )
+import GHC.Types.Demand ( zapDmdEnvSig )
import GHC.Core.Type ( tidyType, tidyVarBndr )
import GHC.Core.Coercion ( tidyCo )
import GHC.Types.Var
@@ -206,7 +206,7 @@ tidyLetBndr rec_tidy_env env@(tidy_env, var_env) id
new_info = vanillaIdInfo
`setOccInfo` occInfo old_info
`setArityInfo` arityInfo old_info
- `setStrictnessInfo` zapUsageEnvSig (strictnessInfo old_info)
+ `setStrictnessInfo` zapDmdEnvSig (strictnessInfo old_info)
`setDemandInfo` demandInfo old_info
`setInlinePragInfo` inlinePragInfo old_info
`setUnfoldingInfo` new_unf
diff --git a/compiler/GHC/Types/Demand.hs b/compiler/GHC/Types/Demand.hs
index 2ebc2222b4..46502fe126 100644
--- a/compiler/GHC/Types/Demand.hs
+++ b/compiler/GHC/Types/Demand.hs
@@ -73,7 +73,7 @@ module GHC.Types.Demand (
seqDemand, seqDemandList, seqDmdType, seqStrictSig,
-- * Zapping usage information
- zapUsageDemand, zapUsageEnvSig, zapUsedOnceDemand, zapUsedOnceSig
+ zapUsageDemand, zapDmdEnvSig, zapUsedOnceDemand, zapUsedOnceSig
) where
#include "HsVersions.h"
@@ -367,7 +367,7 @@ lubSubDmd _ _ = topSubDmd
-- | Denotes '∪' on 'Demand'.
lubDmd :: Demand -> Demand -> Demand
-lubDmd (n1 :* sd1) (n2 :* sd2) = lubCard n1 n2 :* lubSubDmd sd1 sd2
+lubDmd (n1 :* sd1) (n2 :* sd2) = lubCard n1 n2 :* lubSubDmd sd1 sd2
-- | Denotes '+' on 'SubDemand'.
plusSubDmd :: SubDemand -> SubDemand -> SubDemand
@@ -1571,9 +1571,9 @@ This is weird, so I'm not worried about whether this optimises brilliantly; but
it should not fall over.
-}
-zapUsageEnvSig :: StrictSig -> StrictSig
--- Remove the usage environment from the demand
-zapUsageEnvSig (StrictSig (DmdType _ ds r)) = mkClosedStrictSig ds r
+-- | Remove the demand environment from the signature.
+zapDmdEnvSig :: StrictSig -> StrictSig
+zapDmdEnvSig (StrictSig (DmdType _ ds r)) = mkClosedStrictSig ds r
zapUsageDemand :: Demand -> Demand
-- Remove the usage info, but not the strictness info, from the demand
diff --git a/compiler/GHC/Types/Id/Info.hs b/compiler/GHC/Types/Id/Info.hs
index 6620e23cad..0ece12cefa 100644
--- a/compiler/GHC/Types/Id/Info.hs
+++ b/compiler/GHC/Types/Id/Info.hs
@@ -650,7 +650,7 @@ zapUsageInfo info = Just (info {demandInfo = zapUsageDemand (demandInfo info)})
zapUsageEnvInfo :: IdInfo -> Maybe IdInfo
zapUsageEnvInfo info
| hasDemandEnvSig (strictnessInfo info)
- = Just (info {strictnessInfo = zapUsageEnvSig (strictnessInfo info)})
+ = Just (info {strictnessInfo = zapDmdEnvSig (strictnessInfo info)})
| otherwise
= Nothing
diff --git a/testsuite/tests/stranal/should_compile/T18894.hs b/testsuite/tests/stranal/should_compile/T18894.hs
new file mode 100644
index 0000000000..6b91d0e3b5
--- /dev/null
+++ b/testsuite/tests/stranal/should_compile/T18894.hs
@@ -0,0 +1,28 @@
+{-# OPTIONS_GHC -O2 -fforce-recomp #-}
+
+-- | The point of this test is that @g*@ get's a demand that says
+-- "whenever @g*@ is called, the second component of the pair is evaluated strictly".
+module T18894 (h1, h2) where
+
+g1 :: Int -> (Int,Int)
+g1 1 = (15, 0)
+g1 n = (2 * n, 2 `div` n)
+{-# NOINLINE g1 #-}
+
+h1 :: Int -> Int
+h1 1 = 0
+-- Sadly, the @g1 2@ subexpression will be floated to top-level, where we
+-- don't see the specific demand placed on it by @snd@. Tracked in #19001.
+h1 2 = snd (g1 2)
+h1 m = uncurry (+) (g1 m)
+
+g2 :: Int -> Int -> (Int,Int)
+g2 m 1 = (m, 0)
+g2 m n = (2 * m, 2 `div` n)
+{-# NOINLINE g2 #-}
+
+h2 :: Int -> Int
+h2 1 = 0
+h2 m
+ | odd m = snd (g2 m 2)
+ | otherwise = uncurry (+) (g2 2 m)
diff --git a/testsuite/tests/stranal/should_compile/T18894.stderr b/testsuite/tests/stranal/should_compile/T18894.stderr
new file mode 100644
index 0000000000..e0efbe9272
--- /dev/null
+++ b/testsuite/tests/stranal/should_compile/T18894.stderr
@@ -0,0 +1,404 @@
+
+==================== Demand analysis ====================
+Result size of Demand analysis
+ = {terms: 177, types: 97, coercions: 0, joins: 0/0}
+
+-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
+$trModule :: GHC.Prim.Addr#
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 20 0}]
+$trModule = "main"#
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+$trModule :: GHC.Types.TrName
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+$trModule = GHC.Types.TrNameS $trModule
+
+-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
+$trModule :: GHC.Prim.Addr#
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 30 0}]
+$trModule = "T18894"#
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+$trModule :: GHC.Types.TrName
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+$trModule = GHC.Types.TrNameS $trModule
+
+-- RHS size: {terms: 3, types: 0, coercions: 0, joins: 0/0}
+T18894.$trModule :: GHC.Types.Module
+[LclIdX,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+T18894.$trModule = GHC.Types.Module $trModule $trModule
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+lvl = GHC.Types.I# 0#
+
+-- RHS size: {terms: 36, types: 16, coercions: 0, joins: 0/0}
+g2 [InlPrag=NOINLINE, Dmd=UCU(CS(P(1P(U),SP(U))))]
+ :: Int -> Int -> (Int, Int)
+[LclId,
+ Arity=2,
+ Str=<UP(U)><SP(SU)>,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [20 20] 161 20}]
+g2
+ = \ (m [Dmd=UP(U)] :: Int) (ds [Dmd=SP(SU)] :: Int) ->
+ case ds of { GHC.Types.I# ds [Dmd=SU] ->
+ case ds of ds [Dmd=1U] {
+ __DEFAULT ->
+ (case m of { GHC.Types.I# y -> GHC.Types.I# (GHC.Prim.*# 2# y) },
+ case ds of wild {
+ __DEFAULT ->
+ case GHC.Classes.divInt# 2# wild of ww4 { __DEFAULT ->
+ GHC.Types.I# ww4
+ };
+ -1# -> GHC.Types.I# -2#;
+ 0# -> case GHC.Real.divZeroError of wild [Dmd=B] { }
+ });
+ 1# -> (m, lvl)
+ }
+ }
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+lvl = GHC.Types.I# 2#
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+lvl = GHC.Types.I# 2#
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+lvl = GHC.Types.I# 0#
+
+-- RHS size: {terms: 36, types: 19, coercions: 0, joins: 0/0}
+h2 :: Int -> Int
+[LclIdX,
+ Arity=1,
+ Str=<SP(MU)>,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [20] 162 10}]
+h2
+ = \ (ds [Dmd=SP(MU)] :: Int) ->
+ case ds of wild [Dmd=UP(U)] { GHC.Types.I# ds [Dmd=MU] ->
+ case ds of ds {
+ __DEFAULT ->
+ case GHC.Prim.remInt# ds 2# of {
+ __DEFAULT ->
+ case g2 wild lvl of { (ds1 [Dmd=A], y [Dmd=SU]) -> y };
+ 0# ->
+ case g2 lvl wild of { (x [Dmd=SP(U)], ds [Dmd=SP(U)]) ->
+ case x of { GHC.Types.I# x ->
+ case ds of { GHC.Types.I# y -> GHC.Types.I# (GHC.Prim.+# x y) }
+ }
+ }
+ };
+ 1# -> lvl
+ }
+ }
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+lvl = GHC.Types.I# 15#
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+lvl = GHC.Types.I# 0#
+
+-- RHS size: {terms: 3, types: 2, coercions: 0, joins: 0/0}
+lvl :: (Int, Int)
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+lvl = (lvl, lvl)
+
+-- RHS size: {terms: 30, types: 11, coercions: 0, joins: 0/0}
+g1 [InlPrag=NOINLINE, Dmd=UCU(P(UP(U),UP(U)))] :: Int -> (Int, Int)
+[LclId,
+ Arity=1,
+ Str=<SP(SU)>,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [20] 141 10}]
+g1
+ = \ (ds [Dmd=SP(SU)] :: Int) ->
+ case ds of { GHC.Types.I# ds [Dmd=SU] ->
+ case ds of ds {
+ __DEFAULT ->
+ (GHC.Types.I# (GHC.Prim.*# 2# ds),
+ case ds of wild {
+ __DEFAULT ->
+ case GHC.Classes.divInt# 2# wild of ww4 { __DEFAULT ->
+ GHC.Types.I# ww4
+ };
+ -1# -> GHC.Types.I# -2#;
+ 0# -> case GHC.Real.divZeroError of wild [Dmd=B] { }
+ });
+ 1# -> lvl
+ }
+ }
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+lvl = GHC.Types.I# 0#
+
+-- RHS size: {terms: 3, types: 0, coercions: 0, joins: 0/0}
+lvl :: (Int, Int)
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=False, ConLike=False,
+ WorkFree=False, Expandable=False, Guidance=IF_ARGS [] 30 0}]
+lvl = g1 (GHC.Types.I# 2#)
+
+-- RHS size: {terms: 28, types: 18, coercions: 0, joins: 0/0}
+h1 :: Int -> Int
+[LclIdX,
+ Arity=1,
+ Str=<SP(MU)>,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [20] 111 10}]
+h1
+ = \ (ds [Dmd=SP(MU)] :: Int) ->
+ case ds of wild [Dmd=1P(1U)] { GHC.Types.I# ds [Dmd=MU] ->
+ case ds of {
+ __DEFAULT ->
+ case g1 wild of { (x [Dmd=SP(U)], ds [Dmd=SP(U)]) ->
+ case x of { GHC.Types.I# x ->
+ case ds of { GHC.Types.I# y -> GHC.Types.I# (GHC.Prim.+# x y) }
+ }
+ };
+ 1# -> lvl;
+ 2# -> case lvl of { (ds1 [Dmd=A], y [Dmd=SU]) -> y }
+ }
+ }
+
+
+
+
+==================== Demand analysis ====================
+Result size of Demand analysis
+ = {terms: 171, types: 120, coercions: 0, joins: 0/0}
+
+-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
+$trModule :: GHC.Prim.Addr#
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 20 0}]
+$trModule = "main"#
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+$trModule :: GHC.Types.TrName
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+$trModule = GHC.Types.TrNameS $trModule
+
+-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
+$trModule :: GHC.Prim.Addr#
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 30 0}]
+$trModule = "T18894"#
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+$trModule :: GHC.Types.TrName
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+$trModule = GHC.Types.TrNameS $trModule
+
+-- RHS size: {terms: 3, types: 0, coercions: 0, joins: 0/0}
+T18894.$trModule :: GHC.Types.Module
+[LclIdX,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+T18894.$trModule = GHC.Types.Module $trModule $trModule
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+lvl = GHC.Types.I# 0#
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+lvl = GHC.Types.I# -2#
+
+-- RHS size: {terms: 32, types: 18, coercions: 0, joins: 0/0}
+$wg2 [InlPrag=NOINLINE, Dmd=UCU(CS(P(1P(U),SP(U))))]
+ :: Int -> GHC.Prim.Int# -> (# Int, Int #)
+[LclId,
+ Arity=2,
+ Str=<UP(U)><SU>,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [20 30] 121 20}]
+$wg2
+ = \ (w [Dmd=UP(U)] :: Int) (ww [Dmd=SU] :: GHC.Prim.Int#) ->
+ case ww of ds {
+ __DEFAULT ->
+ (# case w of { GHC.Types.I# y -> GHC.Types.I# (GHC.Prim.*# 2# y) },
+ case ds of {
+ __DEFAULT ->
+ case GHC.Classes.divInt# 2# ds of ww4 { __DEFAULT ->
+ GHC.Types.I# ww4
+ };
+ -1# -> lvl;
+ 0# -> case GHC.Real.divZeroError of wild [Dmd=B] { }
+ } #);
+ 1# -> (# w, lvl #)
+ }
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+lvl = GHC.Types.I# 2#
+
+-- RHS size: {terms: 34, types: 21, coercions: 0, joins: 0/0}
+$wh2 [InlPrag=[2], Dmd=UCU(U)] :: GHC.Prim.Int# -> Int
+[LclId,
+ Arity=1,
+ Str=<SU>,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [30] 162 10}]
+$wh2
+ = \ (ww [Dmd=SU] :: GHC.Prim.Int#) ->
+ case ww of ds {
+ __DEFAULT ->
+ case GHC.Prim.remInt# ds 2# of {
+ __DEFAULT ->
+ case $wg2 (GHC.Types.I# ds) 2# of
+ { (# ww [Dmd=A], ww [Dmd=SU] #) ->
+ ww
+ };
+ 0# ->
+ case $wg2 lvl ds of { (# ww [Dmd=SP(U)], ww [Dmd=SP(U)] #) ->
+ case ww of { GHC.Types.I# x ->
+ case ww of { GHC.Types.I# y -> GHC.Types.I# (GHC.Prim.+# x y) }
+ }
+ }
+ };
+ 1# -> lvl
+ }
+
+-- RHS size: {terms: 6, types: 3, coercions: 0, joins: 0/0}
+h2 [InlPrag=[2]] :: Int -> Int
+[LclIdX,
+ Arity=1,
+ Str=<SP(SU)>,
+ Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True,
+ Guidance=ALWAYS_IF(arity=1,unsat_ok=True,boring_ok=False)
+ Tmpl= \ (w [Occ=Once1!] :: Int) ->
+ case w of { GHC.Types.I# ww [Occ=Once1, Dmd=MU] -> $wh2 ww }}]
+h2
+ = \ (w [Dmd=SP(SU)] :: Int) ->
+ case w of { GHC.Types.I# ww [Dmd=SU] -> $wh2 ww }
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+lvl = GHC.Types.I# 15#
+
+-- RHS size: {terms: 28, types: 15, coercions: 0, joins: 0/0}
+$wg1 [InlPrag=NOINLINE, Dmd=UCU(P(UP(U),UP(U)))]
+ :: GHC.Prim.Int# -> (# Int, Int #)
+[LclId,
+ Arity=1,
+ Str=<SU>,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [30] 111 20}]
+$wg1
+ = \ (ww [Dmd=SU] :: GHC.Prim.Int#) ->
+ case ww of ds {
+ __DEFAULT ->
+ (# GHC.Types.I# (GHC.Prim.*# 2# ds),
+ case ds of {
+ __DEFAULT ->
+ case GHC.Classes.divInt# 2# ds of ww4 { __DEFAULT ->
+ GHC.Types.I# ww4
+ };
+ -1# -> lvl;
+ 0# -> case GHC.Real.divZeroError of wild [Dmd=B] { }
+ } #);
+ 1# -> (# lvl, lvl #)
+ }
+
+-- RHS size: {terms: 7, types: 9, coercions: 0, joins: 0/0}
+lvl :: (Int, Int)
+[LclId,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=False, ConLike=False,
+ WorkFree=False, Expandable=False, Guidance=IF_ARGS [] 40 10}]
+lvl = case $wg1 2# of { (# ww, ww #) -> (ww, ww) }
+
+-- RHS size: {terms: 25, types: 18, coercions: 0, joins: 0/0}
+$wh1 [InlPrag=[2], Dmd=UCU(U)] :: GHC.Prim.Int# -> Int
+[LclId,
+ Arity=1,
+ Str=<SU>,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True, Guidance=IF_ARGS [50] 101 10}]
+$wh1
+ = \ (ww [Dmd=SU] :: GHC.Prim.Int#) ->
+ case ww of ds [Dmd=1U] {
+ __DEFAULT ->
+ case $wg1 ds of { (# ww [Dmd=SP(U)], ww [Dmd=SP(U)] #) ->
+ case ww of { GHC.Types.I# x ->
+ case ww of { GHC.Types.I# y -> GHC.Types.I# (GHC.Prim.+# x y) }
+ }
+ };
+ 1# -> lvl;
+ 2# -> case lvl of { (ds1 [Dmd=A], y [Dmd=SU]) -> y }
+ }
+
+-- RHS size: {terms: 6, types: 3, coercions: 0, joins: 0/0}
+h1 [InlPrag=[2]] :: Int -> Int
+[LclIdX,
+ Arity=1,
+ Str=<SP(SU)>,
+ Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ WorkFree=True, Expandable=True,
+ Guidance=ALWAYS_IF(arity=1,unsat_ok=True,boring_ok=False)
+ Tmpl= \ (w [Occ=Once1!] :: Int) ->
+ case w of { GHC.Types.I# ww [Occ=Once1, Dmd=MU] -> $wh1 ww }}]
+h1
+ = \ (w [Dmd=SP(SU)] :: Int) ->
+ case w of { GHC.Types.I# ww [Dmd=SU] -> $wh1 ww }
+
+
+
diff --git a/testsuite/tests/stranal/should_compile/T18894b.hs b/testsuite/tests/stranal/should_compile/T18894b.hs
new file mode 100644
index 0000000000..e90f34e3fd
--- /dev/null
+++ b/testsuite/tests/stranal/should_compile/T18894b.hs
@@ -0,0 +1,20 @@
+{-# OPTIONS_GHC -O -fno-call-arity -fforce-recomp #-}
+
+module T18894 (f) where
+
+expensive :: Int -> (Int, Int)
+expensive n = (n+1, n+2)
+{-# NOINLINE expensive #-}
+
+-- arity 1 by itself, but not exported, thus can be eta-expanded based on usage
+eta :: Int -> Int -> Int
+eta x = if fst (expensive x) == 13
+ then \y -> x + y
+ else \y -> x * y
+{-# NOINLINE eta #-}
+
+f :: Int -> Int
+f 1 = 0
+f m
+ | odd m = eta m 2
+ | otherwise = eta 2 m
diff --git a/testsuite/tests/stranal/should_compile/T18894b.stderr b/testsuite/tests/stranal/should_compile/T18894b.stderr
new file mode 100644
index 0000000000..d9d950769b
--- /dev/null
+++ b/testsuite/tests/stranal/should_compile/T18894b.stderr
@@ -0,0 +1,187 @@
+
+==================== Demand analysis ====================
+Result size of Demand analysis = {terms: 83, types: 40, coercions: 0, joins: 0/0}
+
+-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
+$trModule :: GHC.Prim.Addr#
+[LclId, Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 20 0}]
+$trModule = "main"#
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+$trModule :: GHC.Types.TrName
+[LclId, Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+$trModule = GHC.Types.TrNameS $trModule
+
+-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
+$trModule :: GHC.Prim.Addr#
+[LclId, Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 30 0}]
+$trModule = "T18894"#
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+$trModule :: GHC.Types.TrName
+[LclId, Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+$trModule = GHC.Types.TrNameS $trModule
+
+-- RHS size: {terms: 3, types: 0, coercions: 0, joins: 0/0}
+T18894.$trModule :: GHC.Types.Module
+[LclIdX, Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+T18894.$trModule = GHC.Types.Module $trModule $trModule
+
+-- RHS size: {terms: 16, types: 7, coercions: 0, joins: 0/0}
+expensive [InlPrag=NOINLINE, Dmd=UCU(P(SP(SU),A))] :: Int -> (Int, Int)
+[LclId,
+ Arity=1,
+ Str=<UP(U)>,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [40] 52 10}]
+expensive
+ = \ (n [Dmd=UP(U)] :: Int) ->
+ (case n of { GHC.Types.I# x -> GHC.Types.I# (GHC.Prim.+# x 1#) }, case n of { GHC.Types.I# x -> GHC.Types.I# (GHC.Prim.+# x 2#) })
+
+-- RHS size: {terms: 20, types: 11, coercions: 0, joins: 0/0}
+eta [InlPrag=NOINLINE, Dmd=UCU(CS(U))] :: Int -> Int -> Int
+[LclId,
+ Arity=1,
+ Str=<UP(U)>,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [0] 140 120}]
+eta
+ = \ (x [Dmd=UP(U)] :: Int) ->
+ case expensive x of { (x [Dmd=SP(SU)], ds1 [Dmd=A]) ->
+ case x of { GHC.Types.I# x [Dmd=SU] ->
+ case x of {
+ __DEFAULT -> \ (y [Dmd=SP(U)] :: Int) -> GHC.Num.$fNumInt_$c* x y;
+ 13# -> \ (y [Dmd=SP(U)] :: Int) -> GHC.Num.$fNumInt_$c+ x y
+ }
+ }
+ }
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+[LclId, Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+lvl = GHC.Types.I# 2#
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+[LclId, Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+lvl = GHC.Types.I# 2#
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+[LclId, Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+lvl = GHC.Types.I# 0#
+
+-- RHS size: {terms: 21, types: 5, coercions: 0, joins: 0/0}
+f :: Int -> Int
+[LclIdX,
+ Arity=1,
+ Str=<SP(MU)>,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [20] 111 0}]
+f = \ (ds [Dmd=SP(MU)] :: Int) ->
+ case ds of wild [Dmd=UP(U)] { GHC.Types.I# ds [Dmd=MU] ->
+ case ds of ds {
+ __DEFAULT ->
+ case GHC.Prim.remInt# ds 2# of {
+ __DEFAULT -> eta wild lvl;
+ 0# -> eta lvl wild
+ };
+ 1# -> lvl
+ }
+ }
+
+
+
+
+==================== Demand analysis ====================
+Result size of Demand analysis = {terms: 85, types: 47, coercions: 0, joins: 0/0}
+
+-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
+$trModule :: GHC.Prim.Addr#
+[LclId, Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 20 0}]
+$trModule = "main"#
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+$trModule :: GHC.Types.TrName
+[LclId, Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+$trModule = GHC.Types.TrNameS $trModule
+
+-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
+$trModule :: GHC.Prim.Addr#
+[LclId, Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 30 0}]
+$trModule = "T18894"#
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+$trModule :: GHC.Types.TrName
+[LclId, Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+$trModule = GHC.Types.TrNameS $trModule
+
+-- RHS size: {terms: 3, types: 0, coercions: 0, joins: 0/0}
+T18894.$trModule :: GHC.Types.Module
+[LclIdX, Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+T18894.$trModule = GHC.Types.Module $trModule $trModule
+
+-- RHS size: {terms: 16, types: 9, coercions: 0, joins: 0/0}
+$wexpensive [InlPrag=NOINLINE, Dmd=UCU(P(SP(SU),A))] :: Int -> (# Int, Int #)
+[LclId,
+ Arity=1,
+ Str=<UP(U)>,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [40] 42 10}]
+$wexpensive
+ = \ (w [Dmd=UP(U)] :: Int) ->
+ (# case w of { GHC.Types.I# x -> GHC.Types.I# (GHC.Prim.+# x 1#) },
+ case w of { GHC.Types.I# x -> GHC.Types.I# (GHC.Prim.+# x 2#) } #)
+
+-- RHS size: {terms: 19, types: 12, coercions: 0, joins: 0/0}
+eta [InlPrag=NOINLINE, Dmd=UCU(CS(U))] :: Int -> Int -> Int
+[LclId,
+ Arity=2,
+ Str=<MP(U)><SP(U)>,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [0 0] 120 0}]
+eta
+ = \ (x [Dmd=MP(U)] :: Int) (eta [Dmd=SP(U), OS=OneShot] :: Int) ->
+ case $wexpensive x of { (# ww [Dmd=SP(SU)], ww [Dmd=A] #) ->
+ case ww of { GHC.Types.I# x [Dmd=SU] ->
+ case x of {
+ __DEFAULT -> GHC.Num.$fNumInt_$c* x eta;
+ 13# -> GHC.Num.$fNumInt_$c+ x eta
+ }
+ }
+ }
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+[LclId, Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+lvl = GHC.Types.I# 2#
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+[LclId, Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
+lvl = GHC.Types.I# 0#
+
+-- RHS size: {terms: 20, types: 3, coercions: 0, joins: 0/0}
+$wf [InlPrag=[2]] :: GHC.Prim.Int# -> Int
+[LclId,
+ Arity=1,
+ Str=<SU>,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True, Guidance=IF_ARGS [30] 121 0}]
+$wf
+ = \ (ww [Dmd=SU] :: GHC.Prim.Int#) ->
+ case ww of ds {
+ __DEFAULT ->
+ case GHC.Prim.remInt# ds 2# of {
+ __DEFAULT -> eta (GHC.Types.I# ds) lvl;
+ 0# -> eta lvl (GHC.Types.I# ds)
+ };
+ 1# -> lvl
+ }
+
+-- RHS size: {terms: 6, types: 3, coercions: 0, joins: 0/0}
+f [InlPrag=[2]] :: Int -> Int
+[LclIdX,
+ Arity=1,
+ Str=<SP(SU)>,
+ Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True, WorkFree=True, Expandable=True,
+ Guidance=ALWAYS_IF(arity=1,unsat_ok=True,boring_ok=False)
+ Tmpl= \ (w [Occ=Once1!] :: Int) -> case w of { GHC.Types.I# ww [Occ=Once1, Dmd=MU] -> $wf ww }}]
+f = \ (w [Dmd=SP(SU)] :: Int) -> case w of { GHC.Types.I# ww [Dmd=SU] -> $wf ww }
+
+
+
diff --git a/testsuite/tests/stranal/should_compile/all.T b/testsuite/tests/stranal/should_compile/all.T
index 1262ad426e..c00d61b8c2 100644
--- a/testsuite/tests/stranal/should_compile/all.T
+++ b/testsuite/tests/stranal/should_compile/all.T
@@ -58,3 +58,7 @@ test('T18122', [ grep_errmsg(r'wfoo =') ], compile, ['-ddump-simpl'])
# We care about the call demand on $wg
test('T18903', [ grep_errmsg(r'Dmd=\S+C\S+') ], compile, ['-ddump-simpl -dsuppress-uniques'])
+# We care about the call demand on $wg1 and $wg2
+test('T18894', [ grep_errmsg(r'Dmd=\S+C\S+') ], compile, ['-ddump-stranal -dsuppress-uniques'])
+# We care about the Arity 2 on eta, as a result of the annotated Dmd
+test('T18894b', [ grep_errmsg(r'Arity=2') ], compile, ['-ddump-stranal -dsuppress-uniques -fno-call-arity -dppr-cols=200'])