From addeefc054b64286dfc231d394885bfdecfd261d Mon Sep 17 00:00:00 2001
From: Simon Peyton Jones <simonpj@microsoft.com>
Date: Thu, 28 Jul 2022 14:55:16 +0100
Subject: [PATCH] Refactor UnfoldingSource and IfaceUnfolding

I finally got tired of the way that IfaceUnfolding reflected
a previous structure of unfoldings, not the current one. This
MR refactors UnfoldingSource and IfaceUnfolding to be simpler
and more consistent.

It's largely just a refactor, but in UnfoldingSource (which moves
to GHC.Types.Basic, since it is now used in IfaceSyn too), I
distinguish between /user-specified/ and /system-generated/ stable
unfoldings.

    data UnfoldingSource
      = VanillaSrc
      | StableUserSrc   -- From a user-specified pragma
      | StableSystemSrc -- From a system-generated unfolding
      | CompulsorySrc

This has a minor effect in CSE (see the use of isisStableUserUnfolding
in GHC.Core.Opt.CSE), which I tripped over when working on
specialisation, but it seems like a Good Thing to know anyway.
---
 compiler/GHC/Core.hs                          |  67 +++-------
 compiler/GHC/Core/Opt/CSE.hs                  |  69 +++++-----
 compiler/GHC/Core/Opt/Simplify/Iteration.hs   |   8 +-
 compiler/GHC/Core/Opt/Simplify/Utils.hs       |   2 +-
 compiler/GHC/Core/Opt/WorkWrap.hs             |  10 +-
 compiler/GHC/Core/Ppr.hs                      |   5 -
 compiler/GHC/Core/SimpleOpt.hs                |   2 +-
 compiler/GHC/Core/Tidy.hs                     |   2 +-
 compiler/GHC/Core/Unfold.hs                   |  24 +---
 compiler/GHC/Core/Unfold/Make.hs              | 118 +++++++++---------
 compiler/GHC/CoreToIface.hs                   |  17 ++-
 compiler/GHC/HsToCore.hs                      |   2 +-
 compiler/GHC/HsToCore/Binds.hs                |  15 +--
 compiler/GHC/HsToCore/Foreign/C.hs            |   5 +-
 compiler/GHC/Iface/Rename.hs                  |   8 +-
 compiler/GHC/Iface/Syntax.hs                  |  94 ++++++--------
 compiler/GHC/Iface/Tidy.hs                    |  12 +-
 compiler/GHC/IfaceToCore.hs                   |  31 ++---
 compiler/GHC/Tc/TyCl/Instance.hs              |   4 +-
 compiler/GHC/Types/Basic.hs                   |  61 ++++++++-
 compiler/GHC/Types/Id/Make.hs                 |  28 ++---
 .../deSugar/should_compile/T19969.stderr      |   4 +-
 .../tests/deSugar/should_compile/T2431.stderr |  10 +-
 .../tests/numeric/should_compile/T7116.stdout |   4 +-
 .../should_compile/OpaqueNoRebox.stderr       |  11 +-
 .../simplCore/should_compile/T13143.stderr    |   6 +-
 .../simplCore/should_compile/T18013.stderr    |   2 +-
 .../simplCore/should_compile/T18355.stderr    |  19 +--
 .../tests/simplCore/should_compile/T21261.hs  |   6 +
 .../simplCore/should_compile/T21261.stderr    |  49 +++-----
 .../simplCore/should_compile/T3717.stderr     |   2 +-
 .../simplCore/should_compile/T3772.stdout     |   4 +-
 .../simplCore/should_compile/T4201.stdout     |   6 +-
 .../simplCore/should_compile/T4908.stderr     |   2 +-
 .../simplCore/should_compile/T4930.stderr     |   2 +-
 .../simplCore/should_compile/T7360.stderr     |   8 +-
 .../simplCore/should_compile/T7865.stdout     |   2 +-
 .../should_compile/spec-inline.stderr         |   4 +-
 38 files changed, 359 insertions(+), 366 deletions(-)

diff --git a/compiler/GHC/Core.hs b/compiler/GHC/Core.hs
index 664e8cac437c..c1ed8d741da5 100644
--- a/compiler/GHC/Core.hs
+++ b/compiler/GHC/Core.hs
@@ -65,7 +65,8 @@ module GHC.Core (
         maybeUnfoldingTemplate, otherCons,
         isValueUnfolding, isEvaldUnfolding, isCheapUnfolding,
         isExpandableUnfolding, isConLikeUnfolding, isCompulsoryUnfolding,
-        isStableUnfolding, isInlineUnfolding, isBootUnfolding,
+        isStableUnfolding, isStableUserUnfolding, isStableSystemUnfolding,
+        isInlineUnfolding, isBootUnfolding,
         hasCoreUnfolding, hasSomeUnfolding,
         canUnfold, neverUnfoldGuidance, isStableSource,
 
@@ -1338,36 +1339,6 @@ data Unfolding
 
 
 ------------------------------------------------
-data UnfoldingSource
-  = -- See also Note [Historical note: unfoldings for wrappers]
-
-    InlineRhs          -- The current rhs of the function
-                       -- Replace uf_tmpl each time around
-
-  | InlineStable       -- From an INLINE or INLINABLE pragma
-                       --   INLINE     if guidance is UnfWhen
-                       --   INLINABLE  if guidance is UnfIfGoodArgs/UnfoldNever
-                       -- (well, technically an INLINABLE might be made
-                       -- UnfWhen if it was small enough, and then
-                       -- it will behave like INLINE outside the current
-                       -- module, but that is the way automatic unfoldings
-                       -- work so it is consistent with the intended
-                       -- meaning of INLINABLE).
-                       --
-                       -- uf_tmpl may change, but only as a result of
-                       -- gentle simplification, it doesn't get updated
-                       -- to the current RHS during compilation as with
-                       -- InlineRhs.
-                       --
-                       -- See Note [InlineStable]
-
-  | InlineCompulsory   -- Something that *has* no binding, so you *must* inline it
-                       -- Only a few primop-like things have this property
-                       -- (see "GHC.Types.Id.Make", calls to mkCompulsoryUnfolding).
-                       -- Inline absolutely always, however boring the context.
-
-
-
 -- | 'UnfoldingGuidance' says when unfolding should take place
 data UnfoldingGuidance
   = UnfWhen {   -- Inline without thinking about the *size* of the uf_tmpl
@@ -1472,12 +1443,6 @@ bootUnfolding = BootUnfolding
 mkOtherCon :: [AltCon] -> Unfolding
 mkOtherCon = OtherCon
 
-isStableSource :: UnfoldingSource -> Bool
--- Keep the unfolding template
-isStableSource InlineCompulsory   = True
-isStableSource InlineStable       = True
-isStableSource InlineRhs          = False
-
 -- | Retrieves the template of an unfolding: panics if none is known
 unfoldingTemplate :: Unfolding -> CoreExpr
 unfoldingTemplate = uf_tmpl
@@ -1542,8 +1507,8 @@ expandUnfolding_maybe (CoreUnfolding { uf_expandable = True, uf_tmpl = rhs }) =
 expandUnfolding_maybe _                                                       = Nothing
 
 isCompulsoryUnfolding :: Unfolding -> Bool
-isCompulsoryUnfolding (CoreUnfolding { uf_src = InlineCompulsory }) = True
-isCompulsoryUnfolding _                                             = False
+isCompulsoryUnfolding (CoreUnfolding { uf_src = src }) = isCompulsorySource src
+isCompulsoryUnfolding _                                = False
 
 isStableUnfolding :: Unfolding -> Bool
 -- True of unfoldings that should not be overwritten
@@ -1552,6 +1517,16 @@ isStableUnfolding (CoreUnfolding { uf_src = src }) = isStableSource src
 isStableUnfolding (DFunUnfolding {})               = True
 isStableUnfolding _                                = False
 
+isStableUserUnfolding :: Unfolding -> Bool
+-- True of unfoldings that arise from an INLINE or INLINEABLE pragma
+isStableUserUnfolding (CoreUnfolding { uf_src = src }) = isStableUserSource src
+isStableUserUnfolding _                                = False
+
+isStableSystemUnfolding :: Unfolding -> Bool
+-- True of unfoldings that arise from an INLINE or INLINEABLE pragma
+isStableSystemUnfolding (CoreUnfolding { uf_src = src }) = isStableSystemSource src
+isStableSystemUnfolding _                                = False
+
 isInlineUnfolding :: Unfolding -> Bool
 -- ^ True of a /stable/ unfolding that is
 --   (a) always inlined; that is, with an `UnfWhen` guidance, or
@@ -1608,8 +1583,8 @@ ones are
 
 We consider even a StableUnfolding as fragile, because it needs substitution.
 
-Note [InlineStable]
-~~~~~~~~~~~~~~~~~
+Note [Stable unfoldings]
+~~~~~~~~~~~~~~~~~~~~~~~~
 When you say
       {-# INLINE f #-}
       f x = <rhs>
@@ -1619,10 +1594,11 @@ with it.  Meanwhile, we can optimise <rhs> to our heart's content,
 leaving the original unfolding intact in Unfolding of 'f'. For example
         all xs = foldr (&&) True xs
         any p = all . map p  {-# INLINE any #-}
-We optimise any's RHS fully, but leave the InlineRule saying "all . map p",
-which deforests well at the call site.
+We optimise any's RHS fully, but leave the stable unfolding for `any`
+saying "all . map p", which deforests well at the call site.
 
-So INLINE pragma gives rise to an InlineRule, which captures the original RHS.
+So INLINE pragma gives rise to a stable unfolding, which captures the
+original RHS.
 
 Moreover, it's only used when 'f' is applied to the
 specified number of arguments; that is, the number of argument on
@@ -1636,9 +1612,6 @@ on the left, thus
 it'd only inline when applied to three arguments.  This slightly-experimental
 change was requested by Roman, but it seems to make sense.
 
-See also Note [Inlining an InlineRule] in GHC.Core.Unfold.
-
-
 Note [OccInfo in unfoldings and rules]
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 In unfoldings and rules, we guarantee that the template is occ-analysed,
diff --git a/compiler/GHC/Core/Opt/CSE.hs b/compiler/GHC/Core/Opt/CSE.hs
index ff1bd3782e41..64f845cc54aa 100644
--- a/compiler/GHC/Core/Opt/CSE.hs
+++ b/compiler/GHC/Core/Opt/CSE.hs
@@ -19,7 +19,7 @@ import GHC.Types.Var.Env ( mkInScopeSet )
 import GHC.Types.Id     ( Id, idType, idHasRules, zapStableUnfolding
                         , idInlineActivation, setInlineActivation
                         , zapIdOccInfo, zapIdUsageInfo, idInlinePragma
-                        , isJoinId, isJoinId_maybe )
+                        , isJoinId, isJoinId_maybe, idUnfolding )
 import GHC.Core.Utils   ( mkAltExpr
                         , exprIsTickedString
                         , stripTicksE, stripTicksT, mkTicks )
@@ -228,7 +228,7 @@ is small).  The conclusion here is this:
   might replace <rhs> by 'bar', and then later be unable to see that it
   really was <rhs>.
 
-An except to the rule is when the INLINE pragma is not from the user, e.g. from
+An exception to the rule is when the INLINE pragma is not from the user, e.g. from
 WorkWrap (see Note [Wrapper activation]). We can tell because noUserInlineSpec
 is then true.
 
@@ -262,27 +262,31 @@ There could conceivably be merit in rewriting the RHS of bar:
 but now bar's inlining behaviour will change, and importing
 modules might see that.  So it seems dodgy and we don't do it.
 
-Stable unfoldings are also created during worker/wrapper when we decide
-that a function's definition is so small that it should always inline.
-In this case we still want to do CSE (#13340). Hence the use of
-isAnyInlinePragma rather than isStableUnfolding.
-
-Now consider
-   foo = <expr>
-   bar {-# Unf = Stable ... #-}
-      = <expr>
-
-where the unfolding was added by strictness analysis, say.  Then
-CSE goes ahead, so we get
-   bar = foo
-and probably use SUBSTITUTE that will make 'bar' dead.  But just
-possibly not -- see Note [Dealing with ticks].  In that case we might
-be left with
-   bar = tick t1 (tick t2 foo)
-in which case we would really like to get rid of the stable unfolding
-(generated by the strictness analyser, say).  Hence the zapStableUnfolding
-in cse_bind.  Not a big deal, and only makes a difference when ticks
-get into the picture.
+Wrinkles
+
+* Stable unfoldings are also created during worker/wrapper when we
+  decide that a function's definition is so small that it should
+  always inline, or indeed for the wrapper function itself.  In this
+  case we still want to do CSE (#13340). Hence the use of
+  isStableUserUnfolding/isStableSystemUnfolding rather than
+  isStableUnfolding.
+
+* Consider
+     foo = <expr>
+     bar {-# Unf = Stable ... #-}
+        = <expr>
+  where the unfolding was added by strictness analysis, say.  Then
+  CSE goes ahead, so we get
+     bar = foo
+  and probably use SUBSTITUTE that will make 'bar' dead.  But just
+  possibly not -- see Note [Dealing with ticks].  In that case we might
+  be left with
+     bar = tick t1 (tick t2 foo)
+  in which case we would really like to get rid of the stable unfolding
+  (generated by the strictness analyser, say).
+
+  Hence the zapStableUnfolding in cse_bind.  Not a big deal, and only
+  makes a difference when ticks get into the picture.
 
 Note [Corner case for case expressions]
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -508,14 +512,17 @@ extendCSEnvWithBinding env in_id out_id rhs' cse_done
 -- | Given a binder `let x = e`, this function
 -- determines whether we should add `e -> x` to the cs_map
 noCSE :: InId -> Bool
-noCSE id =  not (isAlwaysActive (idInlineActivation id)) &&
-            not (noUserInlineSpec (inlinePragmaSpec (idInlinePragma id)))
-             -- See Note [CSE for INLINE and NOINLINE]
-         || isAnyInlinePragma (idInlinePragma id)
-             -- See Note [CSE for stable unfoldings]
-         || isJoinId id
-             -- See Note [CSE for join points?]
-
+noCSE id
+  | isJoinId id                = no_cse  -- See Note [CSE for join points?]
+  | isStableUserUnfolding  unf = no_cse  -- See Note [CSE for stable unfoldings]
+  | user_activation_control    = no_cse  -- See Note [CSE for INLINE and NOINLINE]
+  | otherwise = yes_cse
+   where
+     unf = idUnfolding id
+     user_activation_control = not (isAlwaysActive (idInlineActivation id))
+                            && not (noUserInlineSpec (inlinePragmaSpec (idInlinePragma id)))
+     yes_cse = False
+     no_cse  = True
 
 {- Note [Take care with literal strings]
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/compiler/GHC/Core/Opt/Simplify/Iteration.hs b/compiler/GHC/Core/Opt/Simplify/Iteration.hs
index afee252a40de..d2bdace3e233 100644
--- a/compiler/GHC/Core/Opt/Simplify/Iteration.hs
+++ b/compiler/GHC/Core/Opt/Simplify/Iteration.hs
@@ -628,7 +628,7 @@ tryCastWorkerWrapper env bind_cxt old_bndr occ_info bndr (Cast rhs co)
                            , extendIdSubst (setInScopeFromF env floats) old_bndr $
                              DoneEx triv_rhs Nothing ) }
 
-          else do { wrap_unf <- mkLetUnfolding uf_opts top_lvl InlineRhs bndr triv_rhs
+          else do { wrap_unf <- mkLetUnfolding uf_opts top_lvl VanillaSrc bndr triv_rhs
                   ; let bndr' = bndr `setInlinePragma` mkCastWrapperInlinePrag (idInlinePragma bndr)
                                 `setIdUnfolding`  wrap_unf
                         floats' = floats `extendFloats` NonRec bndr' triv_rhs
@@ -659,7 +659,7 @@ tryCastWorkerWrapper env bind_cxt old_bndr occ_info bndr (Cast rhs co)
       = case realUnfoldingInfo info of -- NB: the real one, even for loop-breakers
            unf@(CoreUnfolding { uf_tmpl = unf_rhs, uf_src = src })
              | isStableSource src -> return (unf { uf_tmpl = mkCast unf_rhs (mkSymCo co) })
-           _ -> mkLetUnfolding uf_opts top_lvl InlineRhs work_id work_rhs
+           _ -> mkLetUnfolding uf_opts top_lvl VanillaSrc work_id work_rhs
 
 tryCastWorkerWrapper env _ _ _ bndr rhs  -- All other bindings
   = do { traceSmpl "tcww:no" (vcat [ text "bndr:" <+> ppr bndr
@@ -841,7 +841,7 @@ makeTrivial env top_lvl dmd occ_fs expr
           -- the 'floats' from prepareRHS; but they are all fresh, so there is
           -- no danger of introducing name shadowig in eta expansion
 
-        ; unf <- mkLetUnfolding uf_opts top_lvl InlineRhs var expr2
+        ; unf <- mkLetUnfolding uf_opts top_lvl VanillaSrc var expr2
 
         ; let final_id = addLetBndrInfo var arity_type unf
               bind     = NonRec final_id expr2
@@ -4110,7 +4110,7 @@ simplLetUnfolding env bind_cxt id new_rhs rhs_ty arity unf
   | otherwise
   = -- Otherwise, we end up retaining all the SimpleEnv
     let !opts = seUnfoldingOpts env
-    in mkLetUnfolding opts (bindContextLevel bind_cxt) InlineRhs id new_rhs
+    in mkLetUnfolding opts (bindContextLevel bind_cxt) VanillaSrc id new_rhs
 
 -------------------
 mkLetUnfolding :: UnfoldingOpts -> TopLevelFlag -> UnfoldingSource
diff --git a/compiler/GHC/Core/Opt/Simplify/Utils.hs b/compiler/GHC/Core/Opt/Simplify/Utils.hs
index 86ad7df93d45..6a143c8be8bc 100644
--- a/compiler/GHC/Core/Opt/Simplify/Utils.hs
+++ b/compiler/GHC/Core/Opt/Simplify/Utils.hs
@@ -2077,7 +2077,7 @@ abstractFloats uf_opts top_lvl main_tvs floats body
       = (poly_id `setIdUnfolding` unf, poly_rhs)
       where
         poly_rhs = mkLams tvs_here rhs
-        unf = mkUnfolding uf_opts InlineRhs is_top_lvl False poly_rhs
+        unf = mkUnfolding uf_opts VanillaSrc is_top_lvl False poly_rhs
 
         -- We want the unfolding.  Consider
         --      let
diff --git a/compiler/GHC/Core/Opt/WorkWrap.hs b/compiler/GHC/Core/Opt/WorkWrap.hs
index fc1d9e278569..711ce6dbd8c9 100644
--- a/compiler/GHC/Core/Opt/WorkWrap.hs
+++ b/compiler/GHC/Core/Opt/WorkWrap.hs
@@ -210,7 +210,7 @@ Solution:
 
   It's important that both get this, because the specialiser uses
   the existence of a /user-specified/ INLINE/INLINABLE pragma to
-  drive specialiation of imported functions.  See  GHC.Core.Opt.Specialise
+  drive specialisation of imported functions.  See  GHC.Core.Opt.Specialise
   Note [Specialising imported functions]
 
 * Remember, the subsequent inlining behaviour of the wrapper is expressed by
@@ -892,9 +892,13 @@ mkStrWrapperInlinePrag (InlinePragma { inl_inline = fn_inl
                                      , inl_rule   = rule_info }) rules
   = InlinePragma { inl_src    = SourceText "{-# INLINE"
                  , inl_sat    = Nothing
-                 , inl_inline = fn_inl -- See Note [Worker/wrapper for INLINABLE functions]
+
+                 , inl_inline = fn_inl
+                      -- See Note [Worker/wrapper for INLINABLE functions]
+
                  , inl_act    = activeAfter wrapper_phase
-                                -- See Note [Wrapper activation]
+                      -- See Note [Wrapper activation]
+
                  , inl_rule   = rule_info }  -- RuleMatchInfo is (and must be) unaffected
   where
     -- See Note [Wrapper activation]
diff --git a/compiler/GHC/Core/Ppr.hs b/compiler/GHC/Core/Ppr.hs
index d4b2cbeb9356..e24dc20fb982 100644
--- a/compiler/GHC/Core/Ppr.hs
+++ b/compiler/GHC/Core/Ppr.hs
@@ -618,11 +618,6 @@ instance Outputable UnfoldingGuidance where
                int size,
                int discount ]
 
-instance Outputable UnfoldingSource where
-  ppr InlineCompulsory  = text "Compulsory"
-  ppr InlineStable      = text "InlineStable"
-  ppr InlineRhs         = text "<vanilla>"
-
 instance Outputable Unfolding where
   ppr NoUnfolding                = text "No unfolding"
   ppr BootUnfolding              = text "No unfolding (from boot)"
diff --git a/compiler/GHC/Core/SimpleOpt.hs b/compiler/GHC/Core/SimpleOpt.hs
index 75a5ed27a0f7..d8f2b4b5bd80 100644
--- a/compiler/GHC/Core/SimpleOpt.hs
+++ b/compiler/GHC/Core/SimpleOpt.hs
@@ -756,7 +756,7 @@ add_info env old_bndr top_level new_rhs new_bndr
                  | otherwise
                  = unfolding_from_rhs
 
-   unfolding_from_rhs = mkUnfolding uf_opts InlineRhs
+   unfolding_from_rhs = mkUnfolding uf_opts VanillaSrc
                                     (isTopLevel top_level)
                                     False -- may be bottom or not
                                     new_rhs
diff --git a/compiler/GHC/Core/Tidy.hs b/compiler/GHC/Core/Tidy.hs
index 3a73ce7dd518..af48f42f23a6 100644
--- a/compiler/GHC/Core/Tidy.hs
+++ b/compiler/GHC/Core/Tidy.hs
@@ -400,7 +400,7 @@ two reasons:
 
   (a) To make printing tidy core nicer
 
-  (b) Because we tidy RULES and InlineRules, which may then propagate
+  (b) Because we tidy RULES and unfoldings, which may then propagate
       via --make into the compilation of the next module, and we want
       the benefit of that occurrence analysis when we use the rule or
       or inline the function.  In particular, it's vital not to lose
diff --git a/compiler/GHC/Core/Unfold.hs b/compiler/GHC/Core/Unfold.hs
index 0cf19d81f8e3..49ef7ca02cd0 100644
--- a/compiler/GHC/Core/Unfold.hs
+++ b/compiler/GHC/Core/Unfold.hs
@@ -1307,20 +1307,6 @@ Note [Things to watch]
     Make sure that x does not inline unconditionally!
     Lest we get extra allocation.
 
-Note [Inlining an InlineRule]
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-An InlineRules is used for
-  (a) programmer INLINE pragmas
-  (b) inlinings from worker/wrapper
-
-For (a) the RHS may be large, and our contract is that we *only* inline
-when the function is applied to all the arguments on the LHS of the
-source-code defn.  (The uf_arity in the rule.)
-
-However for worker/wrapper it may be worth inlining even if the
-arity is not satisfied (as we do in the CoreUnfolding case) so we don't
-require saturation.
-
 Note [Nested functions]
 ~~~~~~~~~~~~~~~~~~~~~~~
 At one time we treated a call of a non-top-level function as
@@ -1399,8 +1385,8 @@ RULE) so there's no gain.
 However, watch out:
 
  * Consider this:
-        foo = _inline_ (\n. [n])
-        bar = _inline_ (foo 20)
+        foo = \n. [n])  {-# INLINE foo #-}
+        bar = foo 20    {-# INLINE bar #-}
         baz = \n. case bar of { (m:_) -> m + n }
    Here we really want to inline 'bar' so that we can inline 'foo'
    and the whole thing unravels as it should obviously do.  This is
@@ -1408,9 +1394,9 @@ However, watch out:
    structure rather than a list.
 
    So the non-inlining of lone_variables should only apply if the
-   unfolding is regarded as cheap; because that is when exprIsConApp_maybe
-   looks through the unfolding.  Hence the "&& is_wf" in the
-   InlineRule branch.
+   unfolding is regarded as expandable; because that is when
+   exprIsConApp_maybe looks through the unfolding.  Hence the "&&
+   is_exp" in the CaseCtxt branch of interesting_call
 
  * Even a type application or coercion isn't a lone variable.
    Consider
diff --git a/compiler/GHC/Core/Unfold/Make.hs b/compiler/GHC/Core/Unfold/Make.hs
index 538af3db3d74..e545f4a9f32f 100644
--- a/compiler/GHC/Core/Unfold/Make.hs
+++ b/compiler/GHC/Core/Unfold/Make.hs
@@ -8,13 +8,12 @@ module GHC.Core.Unfold.Make
    , mkFinalUnfolding
    , mkSimpleUnfolding
    , mkWorkerUnfolding
-   , mkInlineUnfolding
-   , mkInlineUnfoldingWithArity
+   , mkInlineUnfoldingWithArity, mkInlineUnfoldingNoArity
    , mkInlinableUnfolding
    , mkWrapperUnfolding
-   , mkCompulsoryUnfolding
-   , mkCompulsoryUnfolding'
+   , mkCompulsoryUnfolding, mkCompulsoryUnfolding'
    , mkDFunUnfolding
+   , mkDataConUnfolding
    , specUnfolding
    , certainlyWillInline
    )
@@ -50,15 +49,14 @@ mkFinalUnfolding opts src strict_sig expr
                 (isDeadEndSig strict_sig)
                 expr
 
+-- | Same as 'mkCompulsoryUnfolding' but simplifies the unfolding first
+mkCompulsoryUnfolding' :: SimpleOpts -> CoreExpr -> Unfolding
+mkCompulsoryUnfolding' opts expr = mkCompulsoryUnfolding (simpleOptExpr opts expr)
+
 -- | Used for things that absolutely must be unfolded
-mkCompulsoryUnfolding :: SimpleOpts -> CoreExpr -> Unfolding
-mkCompulsoryUnfolding opts expr = mkCompulsoryUnfolding' (simpleOptExpr opts expr)
-
--- | Same as 'mkCompulsoryUnfolding' but no simple optimiser pass is performed
--- on the unfolding.
-mkCompulsoryUnfolding' :: CoreExpr -> Unfolding
-mkCompulsoryUnfolding' expr
-  = mkCoreUnfolding InlineCompulsory True
+mkCompulsoryUnfolding :: CoreExpr -> Unfolding
+mkCompulsoryUnfolding expr
+  = mkCoreUnfolding CompulsorySrc True
                     expr
                     (UnfWhen { ug_arity = 0    -- Arity of unfolding doesn't matter
                              , ug_unsat_ok = unSaturatedOk, ug_boring_ok = boringCxtOk })
@@ -71,7 +69,7 @@ mkCompulsoryUnfolding' expr
 
 mkSimpleUnfolding :: UnfoldingOpts -> CoreExpr -> Unfolding
 mkSimpleUnfolding !opts rhs
-  = mkUnfolding opts InlineRhs False False rhs
+  = mkUnfolding opts VanillaSrc False False rhs
 
 mkDFunUnfolding :: [Var] -> DataCon -> [CoreExpr] -> Unfolding
 mkDFunUnfolding bndrs con ops
@@ -80,11 +78,21 @@ mkDFunUnfolding bndrs con ops
                   , df_args = map occurAnalyseExpr ops }
                   -- See Note [Occurrence analysis of unfoldings]
 
+mkDataConUnfolding :: CoreExpr -> Unfolding
+-- Used for non-newtype data constructors with non-trivial wrappers
+mkDataConUnfolding expr
+  = mkCoreUnfolding StableSystemSrc True expr guide
+    -- No need to simplify the expression
+  where
+    guide = UnfWhen { ug_arity     = manifestArity expr
+                    , ug_unsat_ok  = unSaturatedOk
+                    , ug_boring_ok = False }
+
 mkWrapperUnfolding :: SimpleOpts -> CoreExpr -> Arity -> Unfolding
 -- Make the unfolding for the wrapper in a worker/wrapper split
 -- after demand/CPR analysis
 mkWrapperUnfolding opts expr arity
-  = mkCoreUnfolding InlineStable True
+  = mkCoreUnfolding StableSystemSrc True
                     (simpleOptExpr opts expr)
                     (UnfWhen { ug_arity     = arity
                              , ug_unsat_ok  = unSaturatedOk
@@ -103,13 +111,13 @@ mkWorkerUnfolding opts work_fn
 
 mkWorkerUnfolding _ _ _ = noUnfolding
 
--- | Make an unfolding that may be used unsaturated
+-- | Make an INLINE unfolding that may be used unsaturated
 -- (ug_unsat_ok = unSaturatedOk) and that is reported as having its
 -- manifest arity (the number of outer lambdas applications will
 -- resolve before doing any work).
-mkInlineUnfolding :: SimpleOpts -> CoreExpr -> Unfolding
-mkInlineUnfolding opts expr
-  = mkCoreUnfolding InlineStable
+mkInlineUnfoldingNoArity :: SimpleOpts -> UnfoldingSource -> CoreExpr -> Unfolding
+mkInlineUnfoldingNoArity opts src expr
+  = mkCoreUnfolding src
                     True         -- Note [Top-level flag on inline rules]
                     expr' guide
   where
@@ -119,11 +127,11 @@ mkInlineUnfolding opts expr
                     , ug_boring_ok = boring_ok }
     boring_ok = inlineBoringOk expr'
 
--- | Make an unfolding that will be used once the RHS has been saturated
+-- | Make an INLINE unfolding that will be used once the RHS has been saturated
 -- to the given arity.
-mkInlineUnfoldingWithArity :: Arity -> SimpleOpts -> CoreExpr -> Unfolding
-mkInlineUnfoldingWithArity arity opts expr
-  = mkCoreUnfolding InlineStable
+mkInlineUnfoldingWithArity :: SimpleOpts -> UnfoldingSource -> Arity -> CoreExpr -> Unfolding
+mkInlineUnfoldingWithArity opts src arity expr
+  = mkCoreUnfolding src
                     True         -- Note [Top-level flag on inline rules]
                     expr' guide
   where
@@ -136,9 +144,9 @@ mkInlineUnfoldingWithArity arity opts expr
     boring_ok | arity == 0 = True
               | otherwise  = inlineBoringOk expr'
 
-mkInlinableUnfolding :: SimpleOpts -> CoreExpr -> Unfolding
-mkInlinableUnfolding opts expr
-  = mkUnfolding (so_uf_opts opts) InlineStable False False expr'
+mkInlinableUnfolding :: SimpleOpts -> UnfoldingSource -> CoreExpr -> Unfolding
+mkInlinableUnfolding opts src expr
+  = mkUnfolding (so_uf_opts opts) src False False expr'
   where
     expr' = simpleOptExpr opts expr
 
@@ -316,29 +324,29 @@ mkCoreUnfolding :: UnfoldingSource -> Bool -> CoreExpr
                 -> UnfoldingGuidance -> Unfolding
 -- Occurrence-analyses the expression before capturing it
 mkCoreUnfolding src top_lvl expr guidance
-  =
-
-  let is_value = exprIsHNF expr
-      is_conlike = exprIsConLike expr
-      is_work_free = exprIsWorkFree expr
-      is_expandable = exprIsExpandable expr
-  in
-  -- See #20905 for what is going on here. We are careful to make sure we only
-  -- have one copy of an unfolding around at once.
-  -- Note [Thoughtful forcing in mkCoreUnfolding]
-  CoreUnfolding { uf_tmpl         = is_value `seq`
-                                    is_conlike `seq`
-                                    is_work_free `seq`
-                                    is_expandable `seq`
-                                      occurAnalyseExpr expr,
-                      -- See Note [Occurrence analysis of unfoldings]
-                    uf_src          = src,
-                    uf_is_top       = top_lvl,
-                    uf_is_value     = is_value,
-                    uf_is_conlike   = is_conlike,
-                    uf_is_work_free = is_work_free,
-                    uf_expandable   = is_expandable,
-                    uf_guidance     = guidance }
+  = CoreUnfolding { uf_tmpl = is_value `seq`
+                              is_conlike `seq`
+                              is_work_free `seq`
+                              is_expandable `seq`
+                              occurAnalyseExpr expr
+      -- occAnalyseExpr: see Note [Occurrence analysis of unfoldings]
+      -- See #20905 for what a discussion of these 'seq's
+      -- We are careful to make sure we only
+      -- have one copy of an unfolding around at once.
+      -- Note [Thoughtful forcing in mkCoreUnfolding]
+
+                  , uf_src          = src
+                  , uf_is_top       = top_lvl
+                  , uf_is_value     = is_value
+                  , uf_is_conlike   = is_conlike
+                  , uf_is_work_free = is_work_free
+                  , uf_expandable   = is_expandable
+                  , uf_guidance     = guidance }
+  where
+    is_value      = exprIsHNF expr
+    is_conlike    = exprIsConLike expr
+    is_work_free  = exprIsWorkFree expr
+    is_expandable = exprIsExpandable expr
 
 ----------------
 certainlyWillInline :: UnfoldingOpts -> IdInfo -> CoreExpr -> Maybe Unfolding
@@ -358,14 +366,12 @@ certainlyWillInline opts fn_info rhs'
              UnfIfGoodArgs { ug_size = size, ug_args = args }
                         -> do_cunf size args src' tmpl'
         where
-          src' = -- Do not change InlineCompulsory!
-                 case src of
-                   InlineCompulsory -> InlineCompulsory
-                   _                -> InlineStable
-          tmpl' = -- Do not overwrite stable unfoldings!
-                  case src of
-                    InlineRhs -> occurAnalyseExpr rhs'
-                    _         -> uf_tmpl fn_unf
+          src' | isCompulsorySource src = src  -- Do not change InlineCompulsory!
+               | otherwise              = StableSystemSrc
+
+          tmpl' | isStableSource src = uf_tmpl fn_unf
+                | otherwise          = occurAnalyseExpr rhs'
+                -- Do not overwrite stable unfoldings!
 
       DFunUnfolding {} -> Just fn_unf  -- Don't w/w DFuns; it never makes sense
                                        -- to do so, and even if it is currently a
diff --git a/compiler/GHC/CoreToIface.hs b/compiler/GHC/CoreToIface.hs
index 2564320eaafa..0060d82f26c3 100644
--- a/compiler/GHC/CoreToIface.hs
+++ b/compiler/GHC/CoreToIface.hs
@@ -501,20 +501,11 @@ toIfUnfolding lb (CoreUnfolding { uf_tmpl = rhs
                                 , uf_src = src
                                 , uf_guidance = guidance })
   = Just $ HsUnfold lb $
-    case src of
-        InlineStable
-          -> case guidance of
-               UnfWhen {ug_arity = arity, ug_unsat_ok = unsat_ok, ug_boring_ok =  boring_ok }
-                      -> IfInlineRule arity unsat_ok boring_ok if_rhs
-               _other -> IfCoreUnfold True if_rhs
-        InlineCompulsory -> IfCompulsory if_rhs
-        InlineRhs        -> IfCoreUnfold False if_rhs
+    IfCoreUnfold src (toIfGuidance src guidance) (toIfaceExpr rhs)
         -- Yes, even if guidance is UnfNever, expose the unfolding
         -- If we didn't want to expose the unfolding, GHC.Iface.Tidy would
         -- have stuck in NoUnfolding.  For supercompilation we want
         -- to see that unfolding!
-  where
-    if_rhs = toIfaceExpr rhs
 
 toIfUnfolding lb (DFunUnfolding { df_bndrs = bndrs, df_args = args })
   = Just (HsUnfold lb (IfDFunUnfold (map toIfaceBndr bndrs) (map toIfaceExpr args)))
@@ -531,6 +522,12 @@ toIfUnfolding _ BootUnfolding = Nothing
 
 toIfUnfolding _ NoUnfolding = Nothing
 
+toIfGuidance :: UnfoldingSource -> UnfoldingGuidance -> IfGuidance
+toIfGuidance src guidance
+  | UnfWhen arity unsat_ok boring_ok <- guidance
+  , isStableSource src = IfWhen arity unsat_ok boring_ok
+  | otherwise          = IfNoGuidance
+
 {-
 ************************************************************************
 *                                                                      *
diff --git a/compiler/GHC/HsToCore.hs b/compiler/GHC/HsToCore.hs
index 983f3086b549..6da39a27bc03 100644
--- a/compiler/GHC/HsToCore.hs
+++ b/compiler/GHC/HsToCore.hs
@@ -787,7 +787,7 @@ mkUnsafeCoercePrimPair _old_id old_expr
 
 
              info = noCafIdInfo `setInlinePragInfo` alwaysInlinePragma
-                                `setUnfoldingInfo` mkCompulsoryUnfolding' rhs
+                                `setUnfoldingInfo` mkCompulsoryUnfolding rhs
                                 `setArityInfo`     arity
 
              ty = mkSpecForAllTys [ runtimeRep1TyVar, runtimeRep2TyVar
diff --git a/compiler/GHC/HsToCore/Binds.hs b/compiler/GHC/HsToCore/Binds.hs
index 053c9959a2bc..b5e31de53260 100644
--- a/compiler/GHC/HsToCore/Binds.hs
+++ b/compiler/GHC/HsToCore/Binds.hs
@@ -390,7 +390,7 @@ makeCorePair :: DynFlags -> Id -> Bool -> Arity -> CoreExpr
 makeCorePair dflags gbl_id is_default_method dict_arity rhs
   | is_default_method    -- Default methods are *always* inlined
                          -- See Note [INLINE and default methods] in GHC.Tc.TyCl.Instance
-  = (gbl_id `setIdUnfolding` mkCompulsoryUnfolding simpl_opts rhs, rhs)
+  = (gbl_id `setIdUnfolding` mkCompulsoryUnfolding' simpl_opts rhs, rhs)
 
   | otherwise
   = case inlinePragmaSpec inline_prag of
@@ -402,19 +402,20 @@ makeCorePair dflags gbl_id is_default_method dict_arity rhs
   where
     simpl_opts    = initSimpleOpts dflags
     inline_prag   = idInlinePragma gbl_id
-    inlinable_unf = mkInlinableUnfolding simpl_opts rhs
+    inlinable_unf = mkInlinableUnfolding simpl_opts StableUserSrc rhs
     inline_pair
        | Just arity <- inlinePragmaSat inline_prag
         -- Add an Unfolding for an INLINE (but not for NOINLINE)
         -- And eta-expand the RHS; see Note [Eta-expanding INLINE things]
        , let real_arity = dict_arity + arity
-        -- NB: The arity in the InlineRule takes account of the dictionaries
-       = ( gbl_id `setIdUnfolding` mkInlineUnfoldingWithArity real_arity simpl_opts rhs
+        -- NB: The arity passed to mkInlineUnfoldingWithArity
+        --     must take account of the dictionaries
+       = ( gbl_id `setIdUnfolding` mkInlineUnfoldingWithArity simpl_opts StableUserSrc real_arity rhs
          , etaExpand real_arity rhs)
 
        | otherwise
        = pprTrace "makeCorePair: arity missing" (ppr gbl_id) $
-         (gbl_id `setIdUnfolding` mkInlineUnfolding simpl_opts rhs, rhs)
+         (gbl_id `setIdUnfolding` mkInlineUnfoldingNoArity simpl_opts StableUserSrc rhs, rhs)
 
 dictArity :: [Var] -> Arity
 -- Don't count coercion variables in arity
@@ -542,7 +543,7 @@ this:
         fromT :: T Bool -> Bool
         { fromT_1 ((TBool b)) = not b } } }
 
-Note the nested AbsBind.  The arity for the InlineRule on $cfromT should be
+Note the nested AbsBind.  The arity for the unfolding on $cfromT should be
 gotten from the binding for fromT_1.
 
 It might be better to have just one level of AbsBinds, but that requires more
@@ -976,7 +977,7 @@ And from that we want the rule
 
 But be careful!  That dInt might be GHC.Base.$fOrdInt, which is an External
 Name, and you can't bind them in a lambda or forall without getting things
-confused.   Likewise it might have an InlineRule or something, which would be
+confused.   Likewise it might have a stable unfolding or something, which would be
 utterly bogus. So we really make a fresh Id, with the same unique and type
 as the old one, but with an Internal name and no IdInfo.
 
diff --git a/compiler/GHC/HsToCore/Foreign/C.hs b/compiler/GHC/HsToCore/Foreign/C.hs
index a35e3feca655..13ba3123f4c4 100644
--- a/compiler/GHC/HsToCore/Foreign/C.hs
+++ b/compiler/GHC/HsToCore/Foreign/C.hs
@@ -324,9 +324,8 @@ dsFCall fn_id co fcall mDeclHeader = do
         wrap_rhs     = mkLams (tvs ++ args) wrapper_body
         wrap_rhs'    = Cast wrap_rhs co
         simpl_opts   = initSimpleOpts dflags
-        fn_id_w_inl  = fn_id `setIdUnfolding` mkInlineUnfoldingWithArity
-                                                (length args)
-                                                simpl_opts
+        fn_id_w_inl  = fn_id `setIdUnfolding` mkInlineUnfoldingWithArity simpl_opts
+                                                StableSystemSrc (length args)
                                                 wrap_rhs'
 
     return ([(work_id, work_rhs), (fn_id_w_inl, wrap_rhs')], mempty, CStub cDoc [] [])
diff --git a/compiler/GHC/Iface/Rename.hs b/compiler/GHC/Iface/Rename.hs
index fdbe0dd55a84..1a7acea25fbb 100644
--- a/compiler/GHC/Iface/Rename.hs
+++ b/compiler/GHC/Iface/Rename.hs
@@ -600,12 +600,8 @@ rnIfaceInfoItem i
     = pure i
 
 rnIfaceUnfolding :: Rename IfaceUnfolding
-rnIfaceUnfolding (IfCoreUnfold stable if_expr)
-    = IfCoreUnfold stable <$> rnIfaceExpr if_expr
-rnIfaceUnfolding (IfCompulsory if_expr)
-    = IfCompulsory <$> rnIfaceExpr if_expr
-rnIfaceUnfolding (IfInlineRule arity unsat_ok boring_ok if_expr)
-    = IfInlineRule arity unsat_ok boring_ok <$> rnIfaceExpr if_expr
+rnIfaceUnfolding (IfCoreUnfold src guide if_expr)
+    = IfCoreUnfold src guide <$> rnIfaceExpr if_expr
 rnIfaceUnfolding (IfDFunUnfold bs ops)
     = IfDFunUnfold <$> rnIfaceBndrs bs <*> mapM rnIfaceExpr ops
 
diff --git a/compiler/GHC/Iface/Syntax.hs b/compiler/GHC/Iface/Syntax.hs
index 1affa46b42c7..7e7a1aa0c80d 100644
--- a/compiler/GHC/Iface/Syntax.hs
+++ b/compiler/GHC/Iface/Syntax.hs
@@ -13,7 +13,7 @@ module GHC.Iface.Syntax (
         IfaceConDecl(..), IfaceConDecls(..), IfaceEqSpec,
         IfaceExpr(..), IfaceAlt(..), IfaceLetBndr(..), IfaceJoinInfo(..),
         IfaceBinding(..), IfaceConAlt(..),
-        IfaceIdInfo, IfaceIdDetails(..), IfaceUnfolding(..),
+        IfaceIdInfo, IfaceIdDetails(..), IfaceUnfolding(..), IfGuidance(..),
         IfaceInfoItem(..), IfaceRule(..), IfaceAnnotation(..), IfaceAnnTarget,
         IfaceClsInst(..), IfaceFamInst(..), IfaceTickish(..),
         IfaceClassBody(..),
@@ -360,21 +360,12 @@ data IfaceInfoItem
 -- only later attached to the Id.  Partial reason: some are orphans.
 
 data IfaceUnfolding
-  = IfCoreUnfold Bool IfaceExpr -- True <=> INLINABLE, False <=> regular unfolding
-                                -- Possibly could eliminate the Bool here, the information
-                                -- is also in the InlinePragma.
-
-  | IfCompulsory IfaceExpr      -- default methods and unsafeCoerce#
-                                -- for more about unsafeCoerce#, see
-                                -- Note [Wiring in unsafeCoerce#] in "GHC.HsToCore"
-
-  | IfInlineRule Arity          -- INLINE pragmas
-                 Bool           -- OK to inline even if *un*-saturated
-                 Bool           -- OK to inline even if context is boring
-                 IfaceExpr
-
+  = IfCoreUnfold UnfoldingSource IfGuidance IfaceExpr
   | IfDFunUnfold [IfaceBndr] [IfaceExpr]
 
+data IfGuidance
+  = IfNoGuidance            -- Compute it from the IfaceExpr
+  | IfWhen Arity Bool Bool  -- Just like UnfWhen in Core.UnfoldingGuidance
 
 -- We only serialise the IdDetails of top-level Ids, and even then
 -- we only need a very limited selection.  Notably, none of the
@@ -1488,17 +1479,15 @@ instance Outputable IfaceJoinInfo where
   ppr (IfaceJoinPoint ar) = angleBrackets (text "join" <+> ppr ar)
 
 instance Outputable IfaceUnfolding where
-  ppr (IfCompulsory e)     = text "<compulsory>" <+> parens (ppr e)
-  ppr (IfCoreUnfold s e)   = (if s
-                                then text "<stable>"
-                                else Outputable.empty)
-                              <+> parens (ppr e)
-  ppr (IfInlineRule a uok bok e) = sep [text "InlineRule"
-                                            <+> ppr (a,uok,bok),
-                                        pprParendIfaceExpr e]
+  ppr (IfCoreUnfold src guide e)
+    = sep [ text "Core:" <+> ppr src <+> ppr guide, ppr e ]
   ppr (IfDFunUnfold bs es) = hang (text "DFun:" <+> sep (map ppr bs) <> dot)
                                 2 (sep (map pprParendIfaceExpr es))
 
+instance Outputable IfGuidance where
+  ppr IfNoGuidance   = empty
+  ppr (IfWhen a u b) = angleBrackets (ppr a <> comma <> ppr u <> ppr b)
+
 {-
 ************************************************************************
 *                                                                      *
@@ -1742,9 +1731,7 @@ freeNamesItem (HsLFInfo (IfLFCon n)) = unitNameSet n
 freeNamesItem _                      = emptyNameSet
 
 freeNamesIfUnfold :: IfaceUnfolding -> NameSet
-freeNamesIfUnfold (IfCoreUnfold _ e)     = freeNamesIfExpr e
-freeNamesIfUnfold (IfCompulsory e)       = freeNamesIfExpr e
-freeNamesIfUnfold (IfInlineRule _ _ _ e) = freeNamesIfExpr e
+freeNamesIfUnfold (IfCoreUnfold _ _ e)   = freeNamesIfExpr e
 freeNamesIfUnfold (IfDFunUnfold bs es)   = freeNamesIfBndrs bs &&& fnList freeNamesIfExpr es
 
 freeNamesIfExpr :: IfaceExpr -> NameSet
@@ -2264,39 +2251,41 @@ instance Binary IfaceInfoItem where
             _ -> HsTagSig <$> get bh
 
 instance Binary IfaceUnfolding where
-    put_ bh (IfCoreUnfold s e) = do
+    put_ bh (IfCoreUnfold s g e) = do
         putByte bh 0
         put_ bh s
+        put_ bh g
         put_ bh e
-    put_ bh (IfInlineRule a b c d) = do
-        putByte bh 1
-        put_ bh a
-        put_ bh b
-        put_ bh c
-        put_ bh d
     put_ bh (IfDFunUnfold as bs) = do
-        putByte bh 2
+        putByte bh 1
         put_ bh as
         put_ bh bs
-    put_ bh (IfCompulsory e) = do
-        putByte bh 3
-        put_ bh e
     get bh = do
         h <- getByte bh
         case h of
             0 -> do s <- get bh
+                    g <- get bh
                     e <- get bh
-                    return (IfCoreUnfold s e)
-            1 -> do a <- get bh
-                    b <- get bh
-                    c <- get bh
-                    d <- get bh
-                    return (IfInlineRule a b c d)
-            2 -> do as <- get bh
+                    return (IfCoreUnfold s g e)
+            _ -> do as <- get bh
                     bs <- get bh
                     return (IfDFunUnfold as bs)
-            _ -> do e <- get bh
-                    return (IfCompulsory e)
+
+instance Binary IfGuidance where
+    put_ bh IfNoGuidance = putByte bh 0
+    put_ bh (IfWhen a b c ) = do
+        putByte bh 1
+        put_ bh a
+        put_ bh b
+        put_ bh c
+    get bh = do
+        h <- getByte bh
+        case h of
+            0 -> return IfNoGuidance
+            _ -> do a <- get bh
+                    b <- get bh
+                    c <- get bh
+                    return (IfWhen a b c)
 
 instance Binary IfaceAlt where
     put_ bh (IfaceAlt a b c) = do
@@ -2610,16 +2599,15 @@ instance NFData IfaceInfoItem where
     HsLFInfo lf_info -> lf_info `seq` () -- TODO: seq further?
     HsTagSig sig -> sig `seq` ()
 
+instance NFData IfGuidance where
+  rnf = \case
+    IfNoGuidance -> ()
+    IfWhen a b c -> a `seq` b `seq` c `seq` ()
+
 instance NFData IfaceUnfolding where
   rnf = \case
-    IfCoreUnfold inlinable expr ->
-      rnf inlinable `seq` rnf expr
-    IfCompulsory expr ->
-      rnf expr
-    IfInlineRule arity b1 b2 e ->
-      rnf arity `seq` rnf b1 `seq` rnf b2 `seq` rnf e
-    IfDFunUnfold bndrs exprs ->
-      rnf bndrs `seq` rnf exprs
+    IfCoreUnfold src guidance expr -> src `seq` rnf guidance `seq` rnf expr
+    IfDFunUnfold bndrs exprs       -> rnf bndrs `seq` rnf exprs
 
 instance NFData IfaceExpr where
   rnf = \case
diff --git a/compiler/GHC/Iface/Tidy.hs b/compiler/GHC/Iface/Tidy.hs
index 515402abc5a6..68733b36712a 100644
--- a/compiler/GHC/Iface/Tidy.hs
+++ b/compiler/GHC/Iface/Tidy.hs
@@ -875,9 +875,9 @@ dffvBind(x,r)
 dffvLetBndr :: Bool -> Id -> DFFV ()
 -- Gather the free vars of the RULES and unfolding of a binder
 -- We always get the free vars of a *stable* unfolding, but
--- for a *vanilla* one (InlineRhs), the flag controls what happens:
+-- for a *vanilla* one (VanillaSrc), the flag controls what happens:
 --   True <=> get fvs of even a *vanilla* unfolding
---   False <=> ignore an InlineRhs
+--   False <=> ignore a VanillaSrc
 -- For nested bindings (call from dffvBind) we always say "False" because
 --       we are taking the fvs of the RHS anyway
 -- For top-level bindings (call from addExternal, via bndrFvsInOrder)
@@ -889,10 +889,9 @@ dffvLetBndr vanilla_unfold id
     idinfo = idInfo id
 
     go_unf (CoreUnfolding { uf_tmpl = rhs, uf_src = src })
-       = case src of
-           InlineRhs | vanilla_unfold -> dffvExpr rhs
-                     | otherwise      -> return ()
-           _                          -> dffvExpr rhs
+       | isStableSource src = dffvExpr rhs
+       | vanilla_unfold     = dffvExpr rhs
+       | otherwise          = return ()
 
     go_unf (DFunUnfolding { df_bndrs = bndrs, df_args = args })
              = extendScopeList bndrs $ mapM_ dffvExpr args
@@ -1292,7 +1291,6 @@ tidyTopIdInfo rhs_tidy_env name rhs_ty orig_rhs tidy_rhs idinfo show_unfold
                  = tidyTopUnfolding rhs_tidy_env tidy_rhs unf_info
                  | otherwise
                  = minimal_unfold_info
---    unf_from_rhs = mkFinalUnfolding uf_opts InlineRhs final_sig orig_rhs
      -- NB: use `orig_rhs` not `tidy_rhs` in this call to mkFinalUnfolding
      -- else you get a black hole (#22122). Reason: mkFinalUnfolding
      -- looks at IdInfo, and that is knot-tied in tidyTopBind (the Rec case)
diff --git a/compiler/GHC/IfaceToCore.hs b/compiler/GHC/IfaceToCore.hs
index a7c3162930ab..4ef629593c84 100644
--- a/compiler/GHC/IfaceToCore.hs
+++ b/compiler/GHC/IfaceToCore.hs
@@ -57,6 +57,7 @@ import GHC.Core.FamInstEnv
 import GHC.Core
 import GHC.Core.Unify( RoughMatchTc(..) )
 import GHC.Core.Utils
+import GHC.Core.Unfold( calcUnfoldingGuidance )
 import GHC.Core.Unfold.Make
 import GHC.Core.Lint
 import GHC.Core.Make
@@ -97,6 +98,7 @@ import GHC.Types.Unique.FM
 import GHC.Types.Unique.DSet ( mkUniqDSet )
 import GHC.Types.Unique.Set ( nonDetEltsUniqSet )
 import GHC.Types.Unique.Supply
+import GHC.Types.Demand( isDeadEndSig )
 import GHC.Types.Literal
 import GHC.Types.Var as Var
 import GHC.Types.Var.Set
@@ -1655,8 +1657,8 @@ tcIdInfo ignore_prags toplvl name ty info = do
     need_prag :: IfaceInfoItem -> Bool
       -- Always read in compulsory unfoldings
       -- See Note [Always expose compulsory unfoldings] in GHC.Iface.Tidy
-    need_prag (HsUnfold _ (IfCompulsory {})) = True
-    need_prag _                              = False
+    need_prag (HsUnfold _ (IfCoreUnfold src _ _)) = isCompulsorySource src
+    need_prag _ = False
 
     tcPrag :: IdInfo -> IfaceInfoItem -> IfL IdInfo
     tcPrag info HsNoCafRefs        = return (info `setCafInfo`   NoCafRefs)
@@ -1716,25 +1718,16 @@ tcLFInfo lfi = case lfi of
 
 tcUnfolding :: TopLevelFlag -> Name -> Type -> IdInfo -> IfaceUnfolding -> IfL Unfolding
 -- See Note [Lazily checking Unfoldings]
-tcUnfolding toplvl name _ info (IfCoreUnfold stable if_expr)
+tcUnfolding toplvl name _ info (IfCoreUnfold src if_guidance if_expr)
   = do  { uf_opts <- unfoldingOpts <$> getDynFlags
-        ; expr <- tcUnfoldingRhs False toplvl name if_expr
-        ; let unf_src | stable    = InlineStable
-                      | otherwise = InlineRhs
-        ; return $ mkFinalUnfolding uf_opts unf_src strict_sig expr }
+        ; expr <- tcUnfoldingRhs (isCompulsorySource src) toplvl name if_expr
+        ; let guidance = case if_guidance of
+                 IfWhen arity unsat_ok boring_ok -> UnfWhen arity unsat_ok boring_ok
+                 IfNoGuidance -> calcUnfoldingGuidance uf_opts is_top_bottoming expr
+        ; return $ mkCoreUnfolding src True expr guidance }
   where
     -- Strictness should occur before unfolding!
-    strict_sig = dmdSigInfo info
-
-tcUnfolding toplvl name _ _ (IfCompulsory if_expr)
-  = do  { expr <- tcUnfoldingRhs True toplvl name if_expr
-        ; return $ mkCompulsoryUnfolding' expr }
-
-tcUnfolding toplvl name _ _ (IfInlineRule arity unsat_ok boring_ok if_expr)
-  = do  { expr <- tcUnfoldingRhs False toplvl name if_expr
-        ; return $ mkCoreUnfolding InlineStable True expr guidance }
-  where
-    guidance = UnfWhen { ug_arity = arity, ug_unsat_ok = unsat_ok, ug_boring_ok = boring_ok }
+    is_top_bottoming = isTopLevel toplvl && isDeadEndSig (dmdSigInfo info)
 
 tcUnfolding _toplvl name dfun_ty _ (IfDFunUnfold bs ops)
   = bindIfaceBndrs bs $ \ bs' ->
@@ -1765,7 +1758,7 @@ in the middle of checking (so looking at it would cause a loop).
 
 Conclusion: `tcUnfolding` must return an `Unfolding` whose `uf_src` field is readable without
 forcing the `uf_tmpl` field. In particular, all the functions used at the end of
-`tcUnfolding` (such as `mkFinalUnfolding`, `mkCompulsoryUnfolding'`, `mkCoreUnfolding`) must be
+`tcUnfolding` (such as `mkFinalUnfolding`, `mkCoreUnfolding`) must be
 lazy in `expr`.
 
 Ticket #21139
diff --git a/compiler/GHC/Tc/TyCl/Instance.hs b/compiler/GHC/Tc/TyCl/Instance.hs
index e1b7fc0f0fb7..8b3c34aa83c6 100644
--- a/compiler/GHC/Tc/TyCl/Instance.hs
+++ b/compiler/GHC/Tc/TyCl/Instance.hs
@@ -175,7 +175,7 @@ Note [Instances and loop breakers]
   inline df_i in it, and that in turn means that (since it'll be a
   loop-breaker because df_i isn't), op1_i will ironically never be
   inlined.  But this is OK: the recursion breaking happens by way of
-  a RULE (the magic ClassOp rule above), and RULES work inside InlineRule
+  a RULE (the magic ClassOp rule above), and RULES work inside stable
   unfoldings. See Note [RULEs enabled in InitialPhase] in GHC.Core.Opt.Simplify.Utils
 
 Note [ClassOp/DFun selection]
@@ -1349,7 +1349,7 @@ addDFunPrags :: DFunId -> [Id] -> DFunId
 -- is messing with.
 addDFunPrags dfun_id sc_meth_ids
  | is_newtype
-  = dfun_id `setIdUnfolding`  mkInlineUnfoldingWithArity 0 defaultSimpleOpts con_app
+  = dfun_id `setIdUnfolding`  mkInlineUnfoldingWithArity defaultSimpleOpts StableSystemSrc 0 con_app
             `setInlinePragma` alwaysInlinePragma { inl_sat = Just 0 }
  | otherwise
  = dfun_id `setIdUnfolding`  mkDFunUnfolding dfun_bndrs dict_con dict_args
diff --git a/compiler/GHC/Types/Basic.hs b/compiler/GHC/Types/Basic.hs
index 027fe63bad77..bb8dcde29f7c 100644
--- a/compiler/GHC/Types/Basic.hs
+++ b/compiler/GHC/Types/Basic.hs
@@ -98,6 +98,9 @@ module GHC.Types.Basic (
         setInlinePragmaActivation, setInlinePragmaRuleMatchInfo,
         pprInline, pprInlineDebug,
 
+        UnfoldingSource(..), isStableSource, isStableUserSource,
+        isStableSystemSource, isCompulsorySource,
+
         SuccessFlag(..), succeeded, failed, successIf,
 
         IntWithInf, infinity, treatZeroAsInf, subWithInf, mkIntWithInf, intGtLimit,
@@ -1433,7 +1436,7 @@ If you write nothing at all, you get defaultInlinePragma:
 It's not possible to get that combination by *writing* something, so
 if an Id has defaultInlinePragma it means the user didn't specify anything.
 
-If inl_inline = Inline or Inlineable, then the Id should have an InlineRule unfolding.
+If inl_inline = Inline or Inlineable, then the Id should have a stable unfolding.
 
 If you want to know where InlinePragmas take effect: Look in GHC.HsToCore.Binds.makeCorePair
 
@@ -1778,6 +1781,62 @@ pprInline' emptyInline (InlinePragma
               | otherwise      = ppr info
 
 
+{- *********************************************************************
+*                                                                      *
+                 UnfoldingSource
+*                                                                      *
+********************************************************************* -}
+
+data UnfoldingSource
+  = -- See also Note [Historical note: unfoldings for wrappers]
+    VanillaSrc         -- The current rhs of the function
+                       -- Replace uf_tmpl each time around
+
+  -- See Note [Stable unfoldings] in GHC.Core
+  | StableUserSrc   -- From a user-specified INLINE or INLINABLE pragma
+  | StableSystemSrc -- From a wrapper, or system-generated unfolding
+
+  | CompulsorySrc   -- Something that *has* no binding, so you *must* inline it
+                    -- Only a few primop-like things have this property
+                    -- (see "GHC.Types.Id.Make", calls to mkCompulsoryUnfolding).
+                    -- Inline absolutely always, however boring the context.
+
+isStableUserSource :: UnfoldingSource -> Bool
+isStableUserSource StableUserSrc = True
+isStableUserSource _             = False
+
+isStableSystemSource :: UnfoldingSource -> Bool
+isStableSystemSource StableSystemSrc = True
+isStableSystemSource _               = False
+
+isCompulsorySource :: UnfoldingSource -> Bool
+isCompulsorySource CompulsorySrc = True
+isCompulsorySource _             = False
+
+isStableSource :: UnfoldingSource -> Bool
+isStableSource CompulsorySrc   = True
+isStableSource StableSystemSrc = True
+isStableSource StableUserSrc   = True
+isStableSource VanillaSrc      = False
+
+instance Binary UnfoldingSource where
+    put_ bh CompulsorySrc   = putByte bh 0
+    put_ bh StableUserSrc   = putByte bh 1
+    put_ bh StableSystemSrc = putByte bh 2
+    put_ bh VanillaSrc      = putByte bh 3
+    get bh = do
+        h <- getByte bh
+        case h of
+            0 -> return CompulsorySrc
+            1 -> return StableUserSrc
+            2 -> return StableSystemSrc
+            _ -> return VanillaSrc
+
+instance Outputable UnfoldingSource where
+  ppr CompulsorySrc     = text "Compulsory"
+  ppr StableUserSrc     = text "StableUser"
+  ppr StableSystemSrc   = text "StableSystem"
+  ppr VanillaSrc        = text "<vanilla>"
 
 {-
 ************************************************************************
diff --git a/compiler/GHC/Types/Id/Make.hs b/compiler/GHC/Types/Id/Make.hs
index 59452d2912a0..7b0e15df9107 100644
--- a/compiler/GHC/Types/Id/Make.hs
+++ b/compiler/GHC/Types/Id/Make.hs
@@ -483,8 +483,8 @@ mkDictSelId name clas
 
     info | new_tycon
          = base_info `setInlinePragInfo` alwaysInlinePragma
-                     `setUnfoldingInfo`  mkInlineUnfoldingWithArity 1
-                                           defaultSimpleOpts
+                     `setUnfoldingInfo`  mkInlineUnfoldingWithArity defaultSimpleOpts
+                                           StableSystemSrc 1
                                            (mkDictSelRhs clas val_index)
                    -- See Note [Single-method classes] in GHC.Tc.TyCl.Instance
                    -- for why alwaysInlinePragma
@@ -492,8 +492,8 @@ mkDictSelId name clas
          | otherwise
          = base_info `setRuleInfo` mkRuleInfo [rule]
                      `setInlinePragInfo` neverInlinePragma
-                     `setUnfoldingInfo`  mkInlineUnfoldingWithArity 1
-                                           defaultSimpleOpts
+                     `setUnfoldingInfo`  mkInlineUnfoldingWithArity defaultSimpleOpts
+                                           StableSystemSrc 1
                                            (mkDictSelRhs clas val_index)
                    -- Add a magic BuiltinRule, but no unfolding
                    -- so that the rule is always available to fire.
@@ -600,7 +600,7 @@ mkDataConWorkId wkr_name data_con
     newtype_unf  = assertPpr (isVanillaDataCon data_con && isSingleton arg_tys)
                              (ppr data_con) $
                               -- Note [Newtype datacons]
-                   mkCompulsoryUnfolding defaultSimpleOpts $
+                   mkCompulsoryUnfolding $
                    mkLams univ_tvs $ Lam id_arg1 $
                    wrapNewTypeBody tycon res_ty_args (Var id_arg1)
 
@@ -719,9 +719,9 @@ mkDataConRep dc_bang_opts fam_envs wrap_name data_con
              -- See Note [Inline partially-applied constructor wrappers]
              -- Passing Nothing here allows the wrapper to inline when
              -- unsaturated.
-             wrap_unf | isNewTyCon tycon = mkCompulsoryUnfolding defaultSimpleOpts wrap_rhs
+             wrap_unf | isNewTyCon tycon = mkCompulsoryUnfolding wrap_rhs
                         -- See Note [Compulsory newtype unfolding]
-                      | otherwise        = mkInlineUnfolding defaultSimpleOpts wrap_rhs
+                      | otherwise        = mkDataConUnfolding wrap_rhs
              wrap_rhs = mkLams wrap_tvs $
                         mkLams wrap_args $
                         wrapFamInstBody tycon res_ty_args $
@@ -1431,14 +1431,14 @@ nullAddrId :: Id
 nullAddrId = pcMiscPrelId nullAddrName addrPrimTy info
   where
     info = noCafIdInfo `setInlinePragInfo` alwaysInlinePragma
-                       `setUnfoldingInfo`  mkCompulsoryUnfolding defaultSimpleOpts (Lit nullAddrLit)
+                       `setUnfoldingInfo`  mkCompulsoryUnfolding (Lit nullAddrLit)
 
 ------------------------------------------------
 seqId :: Id     -- See Note [seqId magic]
 seqId = pcMiscPrelId seqName ty info
   where
     info = noCafIdInfo `setInlinePragInfo` inline_prag
-                       `setUnfoldingInfo`  mkCompulsoryUnfolding defaultSimpleOpts rhs
+                       `setUnfoldingInfo`  mkCompulsoryUnfolding rhs
                        `setArityInfo`      arity
 
     inline_prag
@@ -1484,7 +1484,7 @@ oneShotId :: Id -- See Note [The oneShot function]
 oneShotId = pcMiscPrelId oneShotName ty info
   where
     info = noCafIdInfo `setInlinePragInfo` alwaysInlinePragma
-                       `setUnfoldingInfo`  mkCompulsoryUnfolding defaultSimpleOpts rhs
+                       `setUnfoldingInfo`  mkCompulsoryUnfolding rhs
                        `setArityInfo`      arity
     ty  = mkInfForAllTys  [ runtimeRep1TyVar, runtimeRep2TyVar ] $
           mkSpecForAllTys [ openAlphaTyVar, openBetaTyVar ]      $
@@ -1525,7 +1525,7 @@ leftSectionId :: Id
 leftSectionId = pcMiscPrelId leftSectionName ty info
   where
     info = noCafIdInfo `setInlinePragInfo` alwaysInlinePragma
-                       `setUnfoldingInfo`  mkCompulsoryUnfolding defaultSimpleOpts rhs
+                       `setUnfoldingInfo`  mkCompulsoryUnfolding rhs
                        `setArityInfo`      arity
     ty  = mkInfForAllTys  [runtimeRep1TyVar,runtimeRep2TyVar, multiplicityTyVar1] $
           mkSpecForAllTys [openAlphaTyVar,  openBetaTyVar]    $
@@ -1550,7 +1550,7 @@ rightSectionId :: Id
 rightSectionId = pcMiscPrelId rightSectionName ty info
   where
     info = noCafIdInfo `setInlinePragInfo` alwaysInlinePragma
-                       `setUnfoldingInfo`  mkCompulsoryUnfolding defaultSimpleOpts rhs
+                       `setUnfoldingInfo`  mkCompulsoryUnfolding rhs
                        `setArityInfo`      arity
     ty  = mkInfForAllTys  [runtimeRep1TyVar,runtimeRep2TyVar,runtimeRep3TyVar
                           , multiplicityTyVar1, multiplicityTyVar2 ] $
@@ -1576,7 +1576,7 @@ coerceId :: Id
 coerceId = pcMiscPrelId coerceName ty info
   where
     info = noCafIdInfo `setInlinePragInfo` alwaysInlinePragma
-                       `setUnfoldingInfo`  mkCompulsoryUnfolding defaultSimpleOpts rhs
+                       `setUnfoldingInfo`  mkCompulsoryUnfolding rhs
                        `setArityInfo`      2
     eqRTy     = mkTyConApp coercibleTyCon  [ tYPE_r,         a, b ]
     eqRPrimTy = mkTyConApp eqReprPrimTyCon [ tYPE_r, tYPE_r, a, b ]
@@ -1813,7 +1813,7 @@ voidPrimId :: Id     -- Global constant :: Void#
                      -- We cannot define it in normal Haskell, since it's
                      -- a top-level unlifted value.
 voidPrimId  = pcMiscPrelId voidPrimIdName unboxedUnitTy
-                (noCafIdInfo `setUnfoldingInfo` mkCompulsoryUnfolding defaultSimpleOpts unboxedUnitExpr)
+                (noCafIdInfo `setUnfoldingInfo` mkCompulsoryUnfolding unboxedUnitExpr)
 
 unboxedUnitExpr :: CoreExpr
 unboxedUnitExpr = Var (dataConWorkId unboxedUnitDataCon)
diff --git a/testsuite/tests/deSugar/should_compile/T19969.stderr b/testsuite/tests/deSugar/should_compile/T19969.stderr
index 3ded6f27a498..555a333349a6 100644
--- a/testsuite/tests/deSugar/should_compile/T19969.stderr
+++ b/testsuite/tests/deSugar/should_compile/T19969.stderr
@@ -16,7 +16,7 @@ g [InlPrag=INLINE (sat-args=1)] :: [Int] -> [Int]
  Arity=1,
  Str=<B>b,
  Cpr=b,
- Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ Unf=Unf{Src=StableUser, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True,
          Guidance=ALWAYS_IF(arity=1,unsat_ok=False,boring_ok=True)
          Tmpl= f}]
@@ -28,7 +28,7 @@ h [InlPrag=INLINE (sat-args=1)] :: [Int] -> [Int]
  Arity=1,
  Str=<B>b,
  Cpr=b,
- Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ Unf=Unf{Src=StableUser, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True,
          Guidance=ALWAYS_IF(arity=1,unsat_ok=False,boring_ok=True)
          Tmpl= f}]
diff --git a/testsuite/tests/deSugar/should_compile/T2431.stderr b/testsuite/tests/deSugar/should_compile/T2431.stderr
index 9a1f79839d0e..3ff19d51ea60 100644
--- a/testsuite/tests/deSugar/should_compile/T2431.stderr
+++ b/testsuite/tests/deSugar/should_compile/T2431.stderr
@@ -7,7 +7,7 @@ Result size of Tidy Core
 T2431.$WRefl [InlPrag=INLINE[final] CONLIKE] :: forall a. a :~: a
 [GblId[DataConWrapper],
  Caf=NoCafRefs,
- Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ Unf=Unf{Src=StableSystem, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True,
          Guidance=ALWAYS_IF(arity=0,unsat_ok=True,boring_ok=False)
          Tmpl= \ (@a) -> T2431.Refl @a @a @~(<a>_N :: a GHC.Prim.~# a)}]
@@ -64,8 +64,8 @@ T2431.$tc:~: :: GHC.Types.TyCon
 [GblId, Unf=OtherCon []]
 T2431.$tc:~:
   = GHC.Types.TyCon
-      4608886815921030019##
-      6030312177285011233##
+      4608886815921030019##64
+      6030312177285011233##64
       T2431.$trModule
       $tc:~:2
       0#
@@ -103,8 +103,8 @@ T2431.$tc'Refl :: GHC.Types.TyCon
 [GblId, Unf=OtherCon []]
 T2431.$tc'Refl
   = GHC.Types.TyCon
-      2478588351447975921##
-      2684375695874497811##
+      2478588351447975921##64
+      2684375695874497811##64
       T2431.$trModule
       $tc'Refl2
       1#
diff --git a/testsuite/tests/numeric/should_compile/T7116.stdout b/testsuite/tests/numeric/should_compile/T7116.stdout
index 90aeda659de3..407a057855c7 100644
--- a/testsuite/tests/numeric/should_compile/T7116.stdout
+++ b/testsuite/tests/numeric/should_compile/T7116.stdout
@@ -45,7 +45,7 @@ dr :: Double -> Double
  Arity=1,
  Str=<1!P(L)>,
  Cpr=1,
- Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ Unf=Unf{Src=StableSystem, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True,
          Guidance=ALWAYS_IF(arity=1,unsat_ok=True,boring_ok=False)
          Tmpl= \ (x [Occ=Once1!] :: Double) ->
@@ -73,7 +73,7 @@ fr :: Float -> Float
  Arity=1,
  Str=<1!P(L)>,
  Cpr=1,
- Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ Unf=Unf{Src=StableSystem, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True,
          Guidance=ALWAYS_IF(arity=1,unsat_ok=True,boring_ok=False)
          Tmpl= \ (x [Occ=Once1!] :: Float) ->
diff --git a/testsuite/tests/simplCore/should_compile/OpaqueNoRebox.stderr b/testsuite/tests/simplCore/should_compile/OpaqueNoRebox.stderr
index ad82c9e16c02..2be1c412dfdb 100644
--- a/testsuite/tests/simplCore/should_compile/OpaqueNoRebox.stderr
+++ b/testsuite/tests/simplCore/should_compile/OpaqueNoRebox.stderr
@@ -45,7 +45,7 @@ OpaqueNoRebox.$trModule
 -- RHS size: {terms: 7, types: 8, coercions: 0, joins: 0/0}
 f [InlPrag=OPAQUE] :: (Int, Int) -> Int
 [GblId, Arity=1, Str=<1P(1L,1L)>, Unf=OtherCon []]
-f = / (ds :: (Int, Int)) ->
+f = \ (ds :: (Int, Int)) ->
       case ds of { (x, y) -> GHC.Num.$fNumInt_$c+ x y }
 
 -- RHS size: {terms: 19, types: 14, coercions: 0, joins: 0/0}
@@ -54,10 +54,10 @@ g [InlPrag=[2]] :: (Int, Int) -> Int
  Arity=1,
  Str=<1P(SL,SL)>,
  Cpr=1,
- Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ Unf=Unf{Src=StableSystem, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True,
          Guidance=ALWAYS_IF(arity=1,unsat_ok=True,boring_ok=False)
-         Tmpl= / (p [Occ=Once1!] :: (Int, Int)) ->
+         Tmpl= \ (p [Occ=Once1!] :: (Int, Int)) ->
                  case p of wild { (x [Occ=Once1!], _ [Occ=Dead]) ->
                  case x of { GHC.Types.I# x1 [Occ=Once1] ->
                  case f (f wild, f wild) of { GHC.Types.I# y [Occ=Once1] ->
@@ -65,7 +65,7 @@ g [InlPrag=[2]] :: (Int, Int) -> Int
                  }
                  }
                  }}]
-g = / (p :: (Int, Int)) ->
+g = \ (p :: (Int, Int)) ->
       case p of wild { (x, ds1) ->
       case x of { GHC.Types.I# x1 ->
       case f (f wild, f wild) of { GHC.Types.I# y ->
@@ -73,3 +73,6 @@ g = / (p :: (Int, Int)) ->
       }
       }
       }
+
+
+
diff --git a/testsuite/tests/simplCore/should_compile/T13143.stderr b/testsuite/tests/simplCore/should_compile/T13143.stderr
index 16189c6daaac..1d4b3dd9fa52 100644
--- a/testsuite/tests/simplCore/should_compile/T13143.stderr
+++ b/testsuite/tests/simplCore/should_compile/T13143.stderr
@@ -12,12 +12,12 @@ T13143.$wf = \ _ [Occ=Dead] (@a) -> T13143.$wf GHC.Prim.(##) @a
 end Rec }
 
 -- RHS size: {terms: 4, types: 3, coercions: 0, joins: 0/0}
-f [InlPrag=[final]] :: forall a. Int -> a
+f [InlPrag=NOINLINE[final]] :: forall a. Int -> a
 [GblId,
  Arity=1,
  Str=<B>b,
  Cpr=b,
- Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ Unf=Unf{Src=StableSystem, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True,
          Guidance=ALWAYS_IF(arity=1,unsat_ok=True,boring_ok=True)
          Tmpl= \ (@a) _ [Occ=Dead] -> T13143.$wf GHC.Prim.(##) @a}]
@@ -94,7 +94,7 @@ g [InlPrag=[2]] :: Bool -> Bool -> Int -> Int
  Arity=3,
  Str=<1L><1L><1!P(L)>,
  Cpr=1,
- Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ Unf=Unf{Src=StableSystem, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True,
          Guidance=ALWAYS_IF(arity=3,unsat_ok=True,boring_ok=False)
          Tmpl= \ (ds [Occ=Once1] :: Bool)
diff --git a/testsuite/tests/simplCore/should_compile/T18013.stderr b/testsuite/tests/simplCore/should_compile/T18013.stderr
index b94cec212b4e..719f70df19d7 100644
--- a/testsuite/tests/simplCore/should_compile/T18013.stderr
+++ b/testsuite/tests/simplCore/should_compile/T18013.stderr
@@ -139,7 +139,7 @@ mapMaybeRule [InlPrag=[2]]
 [GblId,
  Arity=1,
  Str=<1!P(L,LCS(C1(C1(P(L,1L)))))>,
- Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ Unf=Unf{Src=StableSystem, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True,
          Guidance=ALWAYS_IF(arity=1,unsat_ok=True,boring_ok=False)
          Tmpl= \ (@a) (@b) (f [Occ=Once1!] :: Rule IO a b) ->
diff --git a/testsuite/tests/simplCore/should_compile/T18355.stderr b/testsuite/tests/simplCore/should_compile/T18355.stderr
index 6b7372c5afd7..a21a492b6d85 100644
--- a/testsuite/tests/simplCore/should_compile/T18355.stderr
+++ b/testsuite/tests/simplCore/should_compile/T18355.stderr
@@ -1,25 +1,16 @@
 
 ==================== Tidy Core ====================
 Result size of Tidy Core
-  = {terms: 32, types: 23, coercions: 0, joins: 0/0}
+  = {terms: 32, types: 21, coercions: 0, joins: 0/0}
 
--- RHS size: {terms: 17, types: 10, coercions: 0, joins: 0/0}
+-- RHS size: {terms: 17, types: 9, coercions: 0, joins: 0/0}
 f :: forall {a}. Num a => a -> Bool -> a -> a
 [GblId,
  Arity=4,
- Str=<S,1*U(1*C1(C1(U)),1*C1(C1(U)),A,A,A,A,A)><L,U><S,1*U><L,U>,
- Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ Str=<1P(MC1(C1(L)),MC1(C1(L)),A,A,A,A,A)><L><1L><L>,
+ Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True,
-         Guidance=ALWAYS_IF(arity=4,unsat_ok=True,boring_ok=False)
-         Tmpl= \ (@a)
-                 ($dNum [Occ=Once2] :: Num a)
-                 (x [Occ=Once2] :: a)
-                 (b [Occ=Once1!] :: Bool)
-                 (eta [Occ=Once2, OS=OneShot] :: a) ->
-                 case b of {
-                   False -> - @a $dNum x eta;
-                   True -> + @a $dNum x eta
-                 }}]
+         Guidance=IF_ARGS [60 0 70 0] 100 0}]
 f = \ (@a)
       ($dNum :: Num a)
       (x :: a)
diff --git a/testsuite/tests/simplCore/should_compile/T21261.hs b/testsuite/tests/simplCore/should_compile/T21261.hs
index 167d3f0f865b..888c2fed1321 100644
--- a/testsuite/tests/simplCore/should_compile/T21261.hs
+++ b/testsuite/tests/simplCore/should_compile/T21261.hs
@@ -1,3 +1,9 @@
+{-# OPTIONS_GHC -fno-worker-wrapper #-}
+
+-- The -fno-worker-wrapper stops f1, f2 etc from worker/wrappering
+-- via CPR analysis, after which they inline ane confuse the
+-- detection of eta-expansion or otherwise
+
 module T21261 where
 
 -- README: The convention here is that bindings starting with 'yes' should be
diff --git a/testsuite/tests/simplCore/should_compile/T21261.stderr b/testsuite/tests/simplCore/should_compile/T21261.stderr
index fadd73c2190e..6ed7bb993302 100644
--- a/testsuite/tests/simplCore/should_compile/T21261.stderr
+++ b/testsuite/tests/simplCore/should_compile/T21261.stderr
@@ -1,7 +1,7 @@
 
 ==================== Tidy Core ====================
 Result size of Tidy Core
-  = {terms: 139, types: 130, coercions: 0, joins: 0/0}
+  = {terms: 127, types: 120, coercions: 0, joins: 0/0}
 
 lvl = I# 3#
 
@@ -13,58 +13,49 @@ f2 = \ c -> case c lvl2 of { __DEFAULT -> c lvl lvl1 }
 
 yes1or2 = f2
 
-lvl3 = I# 2#
+lvl3 = I# 42#
 
-$wf4
+lvl4 = I# 2#
+
+f4
   = \ c ->
-      case c lvl2 lvl3 of { __DEFAULT ->
-      case c lvl lvl1 of { __DEFAULT -> 42# }
+      case c lvl2 lvl4 of { __DEFAULT ->
+      case c lvl lvl1 of { __DEFAULT -> lvl3 }
       }
 
-f4 = \ c -> case $wf4 c of ww { __DEFAULT -> I# ww }
-
-no3
-  = \ c ->
-      case $wf4 (\ x y z -> c x y z) of ww { __DEFAULT -> I# ww }
+no3 = \ c -> f4 (\ x y z -> c x y z)
 
-f6 = \ c -> case c lvl2 of { __DEFAULT -> c lvl3 lvl }
+f6 = \ c -> case c lvl2 of { __DEFAULT -> c lvl4 lvl }
 
 no_tricky = \ c -> f6 (\ x y -> c x y)
 
-$wf7 = \ c -> (# case c lvl2 of { __DEFAULT -> c lvl lvl1 } #)
-
-f7 = \ c -> case $wf7 c of { (# ww #) -> Just ww }
+f7 = \ c -> Just (case c lvl2 of { __DEFAULT -> c lvl lvl1 })
 
 no_tricky_lazy = \ c -> f7 (\ x y -> c x y)
 
-$wf5
+f5
   = \ c ->
-      (# case c lvl2 lvl3 of { I# x ->
+      Just
+        (case c lvl2 lvl4 of { I# x ->
          case c lvl lvl1 of { I# y -> I# (+# x y) }
-         } #)
-
-f5 = \ c -> case $wf5 c of { (# ww #) -> Just ww }
+         })
 
 yes2_lazy = f5
 
-$wf3
+f3
   = \ c ->
-      case c lvl2 lvl3 of { I# x ->
-      case c lvl lvl1 of { I# y -> +# x y }
+      case c lvl2 lvl4 of { I# x ->
+      case c lvl lvl1 of { I# y -> I# (+# x y) }
       }
 
-f3 = \ c -> case $wf3 c of ww { __DEFAULT -> I# ww }
-
 yes2 = f3
 
-$wf1
+f1
   = \ c ->
-      case c lvl2 lvl3 of { I# x ->
-      case c lvl lvl1 of { I# y -> +# x y }
+      case c lvl2 lvl4 of { I# x ->
+      case c lvl lvl1 of { I# y -> I# (+# x y) }
       }
 
-f1 = \ c -> case $wf1 c of ww { __DEFAULT -> I# ww }
-
 yes1 = f1
 
 
diff --git a/testsuite/tests/simplCore/should_compile/T3717.stderr b/testsuite/tests/simplCore/should_compile/T3717.stderr
index f13121413269..5da8a9f3023f 100644
--- a/testsuite/tests/simplCore/should_compile/T3717.stderr
+++ b/testsuite/tests/simplCore/should_compile/T3717.stderr
@@ -58,7 +58,7 @@ foo [InlPrag=[2]] :: Int -> Int
  Arity=1,
  Str=<1!P(1L)>,
  Cpr=1,
- Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ Unf=Unf{Src=StableSystem, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True,
          Guidance=ALWAYS_IF(arity=1,unsat_ok=True,boring_ok=False)
          Tmpl= \ (ds [Occ=Once1!] :: Int) ->
diff --git a/testsuite/tests/simplCore/should_compile/T3772.stdout b/testsuite/tests/simplCore/should_compile/T3772.stdout
index 4a67fd841331..6faaab181a95 100644
--- a/testsuite/tests/simplCore/should_compile/T3772.stdout
+++ b/testsuite/tests/simplCore/should_compile/T3772.stdout
@@ -62,12 +62,12 @@ T3772.$wfoo
       }
 
 -- RHS size: {terms: 9, types: 4, coercions: 0, joins: 0/0}
-foo [InlPrag=[final]] :: Int -> ()
+foo [InlPrag=NOINLINE[final]] :: Int -> ()
 [GblId,
  Arity=1,
  Str=<1!P(L)>,
  Cpr=1,
- Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ Unf=Unf{Src=StableSystem, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True,
          Guidance=ALWAYS_IF(arity=1,unsat_ok=True,boring_ok=False)
          Tmpl= \ (n [Occ=Once1!] :: Int) ->
diff --git a/testsuite/tests/simplCore/should_compile/T4201.stdout b/testsuite/tests/simplCore/should_compile/T4201.stdout
index 920ae576628d..68d0bc48fd67 100644
--- a/testsuite/tests/simplCore/should_compile/T4201.stdout
+++ b/testsuite/tests/simplCore/should_compile/T4201.stdout
@@ -1,4 +1,4 @@
+  [HasNoCafRefs, TagSig: <TagProper>, LambdaFormInfo: LFReEntrant 1,
    Arity: 1, Strictness: <1!A>, CPR: 1,
-   Unfolding: (bof
-                 `cast`
-               (Sym (N:Foo[0]) %<'GHC.Types.Many>_N ->_R <T>_R))]
+   Unfolding: Core: <vanilla>
+              bof `cast` (Sym (N:Foo[0]) %<'GHC.Types.Many>_N ->_R <T>_R)]
diff --git a/testsuite/tests/simplCore/should_compile/T4908.stderr b/testsuite/tests/simplCore/should_compile/T4908.stderr
index 307c9fb7280c..a306a5a5e726 100644
--- a/testsuite/tests/simplCore/should_compile/T4908.stderr
+++ b/testsuite/tests/simplCore/should_compile/T4908.stderr
@@ -61,7 +61,7 @@ f [InlPrag=[2]] :: Int -> (Int, Int) -> Bool
  Arity=2,
  Str=<1!P(1L)><MP(A,1P(1L))>,
  Cpr=2,
- Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ Unf=Unf{Src=StableSystem, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True,
          Guidance=ALWAYS_IF(arity=2,unsat_ok=True,boring_ok=False)
          Tmpl= \ (ds [Occ=Once1!] :: Int) (x [Occ=Once1!] :: (Int, Int)) ->
diff --git a/testsuite/tests/simplCore/should_compile/T4930.stderr b/testsuite/tests/simplCore/should_compile/T4930.stderr
index 413f8929424d..bc6bacdb4061 100644
--- a/testsuite/tests/simplCore/should_compile/T4930.stderr
+++ b/testsuite/tests/simplCore/should_compile/T4930.stderr
@@ -58,7 +58,7 @@ foo [InlPrag=[2]] :: Int -> Int
  Arity=1,
  Str=<1!P(L)>,
  Cpr=1,
- Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ Unf=Unf{Src=StableSystem, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True,
          Guidance=ALWAYS_IF(arity=1,unsat_ok=True,boring_ok=False)
          Tmpl= \ (n [Occ=Once1!] :: Int) ->
diff --git a/testsuite/tests/simplCore/should_compile/T7360.stderr b/testsuite/tests/simplCore/should_compile/T7360.stderr
index 345efa5a18af..17eb1b593420 100644
--- a/testsuite/tests/simplCore/should_compile/T7360.stderr
+++ b/testsuite/tests/simplCore/should_compile/T7360.stderr
@@ -9,7 +9,7 @@ T7360.$WFoo3 [InlPrag=INLINE[final] CONLIKE] :: Int %1 -> Foo
  Arity=1,
  Caf=NoCafRefs,
  Str=<SL>,
- Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ Unf=Unf{Src=StableSystem, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True,
          Guidance=ALWAYS_IF(arity=1,unsat_ok=True,boring_ok=False)
          Tmpl= \ (conrep [Occ=Once1!] :: Int) ->
@@ -27,12 +27,12 @@ T7360.$wfun1
   = \ (x :: Foo) -> case x of { __DEFAULT -> GHC.Prim.(##) }
 
 -- RHS size: {terms: 6, types: 2, coercions: 0, joins: 0/0}
-fun1 [InlPrag=[final]] :: Foo -> ()
+fun1 [InlPrag=NOINLINE[final]] :: Foo -> ()
 [GblId,
  Arity=1,
  Str=<1A>,
  Cpr=1,
- Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ Unf=Unf{Src=StableSystem, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True,
          Guidance=ALWAYS_IF(arity=1,unsat_ok=True,boring_ok=False)
          Tmpl= \ (x [Occ=Once1] :: Foo) ->
@@ -54,7 +54,7 @@ fun2 :: forall {a}. [a] -> ((), Int)
  Arity=1,
  Str=<ML>,
  Cpr=1,
- Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ Unf=Unf{Src=StableSystem, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True,
          Guidance=ALWAYS_IF(arity=1,unsat_ok=True,boring_ok=False)
          Tmpl= \ (@a) (x [Occ=Once1] :: [a]) ->
diff --git a/testsuite/tests/simplCore/should_compile/T7865.stdout b/testsuite/tests/simplCore/should_compile/T7865.stdout
index 1dd2c25893f1..c8758d3af1de 100644
--- a/testsuite/tests/simplCore/should_compile/T7865.stdout
+++ b/testsuite/tests/simplCore/should_compile/T7865.stdout
@@ -1,6 +1,6 @@
 T7865.$wexpensive [InlPrag=NOINLINE]
 T7865.$wexpensive
-expensive [InlPrag=[final]] :: Int -> Int
+expensive [InlPrag=NOINLINE[final]] :: Int -> Int
                  case T7865.$wexpensive ww of ww1 [Occ=Once1] { __DEFAULT ->
 expensive
       case T7865.$wexpensive ww of ww1 { __DEFAULT -> GHC.Types.I# ww1 }
diff --git a/testsuite/tests/simplCore/should_compile/spec-inline.stderr b/testsuite/tests/simplCore/should_compile/spec-inline.stderr
index e0b2ad49620e..8705eeacea01 100644
--- a/testsuite/tests/simplCore/should_compile/spec-inline.stderr
+++ b/testsuite/tests/simplCore/should_compile/spec-inline.stderr
@@ -113,7 +113,7 @@ Roman.foo_go [InlPrag=[2]] :: Maybe Int -> Maybe Int -> Int
  Arity=2,
  Str=<1L><1L>,
  Cpr=1,
- Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ Unf=Unf{Src=StableSystem, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True,
          Guidance=ALWAYS_IF(arity=2,unsat_ok=True,boring_ok=False)
          Tmpl= \ (u [Occ=Once1] :: Maybe Int)
@@ -145,7 +145,7 @@ foo :: Int -> Int
  Arity=1,
  Str=<1!P(L)>,
  Cpr=1,
- Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
+ Unf=Unf{Src=StableSystem, TopLvl=True, Value=True, ConLike=True,
          WorkFree=True, Expandable=True,
          Guidance=ALWAYS_IF(arity=1,unsat_ok=True,boring_ok=False)
          Tmpl= \ (n [Occ=Once1!] :: Int) ->
-- 
GitLab