Make constructor wrappers inline only during the final phase
authorArnaud Spiwack <arnaud.spiwack@tweag.io>
Thu, 15 Nov 2018 16:14:31 +0000 (17:14 +0100)
committerMarge Bot <ben+marge-bot@smart-cactus.org>
Tue, 19 Feb 2019 11:14:04 +0000 (06:14 -0500)
For case-of-known constructor to continue triggering early,
exprIsConApp_maybe is now capable of looking through lets and cases.

See #15840

14 files changed:
compiler/basicTypes/Id.hs
compiler/basicTypes/MkId.hs
compiler/coreSyn/CoreOpt.hs
compiler/coreSyn/MkCore.hs
compiler/prelude/PrelRules.hs
compiler/simplCore/FloatIn.hs
compiler/simplCore/Simplify.hs
testsuite/tests/deSugar/should_compile/T2431.stderr
testsuite/tests/simplCore/should_compile/T7360.stderr
testsuite/tests/simplCore/should_run/T15840.hs [new file with mode: 0644]
testsuite/tests/simplCore/should_run/T15840.stdout [new file with mode: 0644]
testsuite/tests/simplCore/should_run/T15840a.hs [new file with mode: 0644]
testsuite/tests/simplCore/should_run/T15840a.stdout [new file with mode: 0644]
testsuite/tests/simplCore/should_run/all.T

index 5e91d26..01b648e 100644 (file)
@@ -66,7 +66,8 @@ module Id (
         isClassOpId_maybe, isDFunId,
         isPrimOpId, isPrimOpId_maybe,
         isFCallId, isFCallId_maybe,
-        isDataConWorkId, isDataConWorkId_maybe, isDataConId_maybe, idDataCon,
+        isDataConWorkId, isDataConWorkId_maybe, isDataConWrapId, isDataConId_maybe,
+        idDataCon,
         isConLikeId, isBottomingId, idIsFrom,
         hasNoBinding,
 
@@ -419,6 +420,7 @@ isDataConRecordSelector  :: Id -> Bool
 isPrimOpId              :: Id -> Bool
 isFCallId               :: Id -> Bool
 isDataConWorkId         :: Id -> Bool
+isDataConWrapId         :: Id -> Bool
 isDFunId                :: Id -> Bool
 
 isClassOpId_maybe       :: Id -> Maybe Class
@@ -474,6 +476,10 @@ isDataConWorkId_maybe id = case Var.idDetails id of
                         DataConWorkId con -> Just con
                         _                 -> Nothing
 
+isDataConWrapId id = case Var.idDetails id of
+                       DataConWrapId _ -> True
+                       _               -> False
+
 isDataConId_maybe :: Id -> Maybe DataCon
 isDataConId_maybe id = case Var.idDetails id of
                          DataConWorkId con -> Just con
index 616454f..98ff0b0 100644 (file)
@@ -409,8 +409,8 @@ dictSelRule :: Int -> Arity -> RuleFun
 --
 dictSelRule val_index n_ty_args _ id_unf _ args
   | (dict_arg : _) <- drop n_ty_args args
-  , Just (_, _, con_args) <- exprIsConApp_maybe id_unf dict_arg
-  = Just (getNth con_args val_index)
+  , Just (floats, _, _, con_args) <- exprIsConApp_maybe id_unf dict_arg
+  = Just (wrapFloats floats $ getNth con_args val_index)
   | otherwise
   = Nothing
 
@@ -596,7 +596,7 @@ mkDataConRep dflags fam_envs wrap_name mb_bangs data_con
                         | otherwise           = topDmd
 
              wrap_prag = alwaysInlinePragma `setInlinePragmaActivation`
-                         activeAfterInitial
+                         activeDuringFinal
                          -- See Note [Activation for data constructor wrappers]
 
              -- The wrapper will usually be inlined (see wrap_unf), so its
@@ -706,16 +706,24 @@ mkDataConRep dflags fam_envs wrap_name mb_bangs data_con
 
 {- Note [Activation for data constructor wrappers]
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-The Activation on a data constructor wrapper allows it to inline in
-Phase 2 and later (1, 0).  But not in the InitialPhase.  That gives
-rewrite rules a chance to fire (in the InitialPhase) if they mention
-a data constructor on the left
+The Activation on a data constructor wrapper allows it to inline only in Phase
+0. This way rules have a chance to fire if they mention a data constructor on
+the left
    RULE "foo"  f (K a b) = ...
 Since the LHS of rules are simplified with InitialPhase, we won't
 inline the wrapper on the LHS either.
 
-People have asked for this before, but now that even the InitialPhase
-does some inlining, it has become important.
+On the other hand, this means that exprIsConApp_maybe must be able to deal
+with wrappers so that case-of-constructor is not delayed; see
+Note [exprIsConApp_maybe on data constructors with wrappers] for details.
+
+It used to activate in phases 2 (afterInitial) and later, but it makes it
+awkward to write a RULE[1] with a constructor on the left: it would work if a
+constructor has no wrapper, but whether a constructor has a wrapper depends, for
+instance, on the order of type argument of that constructors. Therefore changing
+the order of type argument could make previously working RULEs fail.
+
+See also https://ghc.haskell.org/trac/ghc/ticket/15840 .
 
 
 Note [Bangs on imported data constructors]
index ca82d9a..dc74acf 100644 (file)
@@ -28,6 +28,7 @@ import CoreSyn
 import CoreSubst
 import CoreUtils
 import CoreFVs
+import MkCore ( FloatBind(..) )
 import PprCore  ( pprCoreBindings, pprRules )
 import OccurAnal( occurAnalyseExpr, occurAnalysePgm )
 import Literal  ( Literal(LitString) )
@@ -231,7 +232,8 @@ simple_opt_expr env expr
     go (Case e b ty as)
        -- See Note [Getting the map/coerce RULE to work]
       | isDeadBinder b
-      , Just (con, _tys, es) <- exprIsConApp_maybe in_scope_env e'
+      , Just ([], con, _tys, es) <- exprIsConApp_maybe in_scope_env e'
+        -- We don't need to be concerned about floats when looking for coerce.
       , Just (altcon, bs, rhs) <- findAlt (DataAlt con) as
       = case altcon of
           DEFAULT -> go rhs
@@ -756,52 +758,153 @@ To get this to come out we need to simplify on the fly
    ((/\a b. K e1 e2) |> g) @t1 @t2
 
 Hence the use of pushCoArgs.
+
+Note [exprIsConApp_maybe on data constructors with wrappers]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Problem:
+- some data constructors have wrappers
+- these wrappers inline late (see MkId Note [Activation for data constructor wrappers])
+- but we still want case-of-known-constructor to fire early.
+
+Example:
+   data T = MkT !Int
+   $WMkT n = case n of n' -> MkT n'   -- Wrapper for MkT
+   foo x = case $WMkT e of MkT y -> blah
+
+Here we want the case-of-known-constructor transformation to fire, giving
+   foo x = case e of x' -> let y = x' in blah
+
+Here's how exprIsConApp_maybe achieves this:
+
+0.  Start with scrutinee = $WMkT e
+
+1.  Inline $WMkT on-the-fly.  That's why data-constructor wrappers are marked
+    as expandable. (See CoreUtils.isExpandableApp.) Now we have
+      scrutinee = (\n. case n of n' -> MkT n') e
+
+2.  Beta-reduce the application, generating a floated 'let'.
+    See Note [beta-reduction in exprIsConApp_maybe] below.  Now we have
+      scrutinee = case n of n' -> MkT n'
+      with floats {Let n = e}
+
+3.  Float the "case x of x' ->" binding out.  Now we have
+      scrutinee = MkT n'
+      with floats {Let n = e; case n of n' ->}
+
+And now we have a known-constructor MkT that we can return.
+
+Notice that both (2) and (3) require exprIsConApp_maybe to gather and return
+a bunch of floats, both let and case bindings.
+
+Note [beta-reduction in exprIsConApp_maybe]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The unfolding a definition (_e.g._ a let-bound variable or a datacon wrapper) is
+typically a function. For instance, take the wrapper for MkT in Note
+[exprIsConApp_maybe on data constructors with wrappers]:
+
+    $WMkT n = case n of { n' -> T n' }
+
+If `exprIsConApp_maybe` is trying to analyse `$MkT arg`, upon unfolding of $MkT,
+it will see
+
+   (\n -> case n of { n' -> T n' }) arg
+
+In order to go progress, `exprIsConApp_maybe` must perform a beta-reduction.
+
+We don't want to blindly substitute `arg` in the body of the function, because
+it duplicates work. We can (and, in fact, used to) substitute `arg` in the body,
+but only when `arg` is a variable (or something equally work-free).
+
+But, because of Note [exprIsConApp_maybe on data constructors with wrappers],
+'exprIsConApp_maybe' now returns floats. So, instead, we can beta-reduce
+_always_:
+
+    (\x -> body) arg
+
+Is transformed into
+
+   let x = arg in body
+
+Which, effectively, means emitting a float `let x = arg` and recursively
+analysing the body.
+
 -}
 
 data ConCont = CC [CoreExpr] Coercion
                   -- Substitution already applied
 
--- | Returns @Just (dc, [t1..tk], [x1..xn])@ if the argument expression is
--- a *saturated* constructor application of the form @dc t1..tk x1 .. xn@,
--- where t1..tk are the *universally-quantified* type args of 'dc'
-exprIsConApp_maybe :: InScopeEnv -> CoreExpr -> Maybe (DataCon, [Type], [CoreExpr])
+-- | Returns @Just ([b1..bp], dc, [t1..tk], [x1..xn])@ if the argument
+-- expression is a *saturated* constructor application of the form @let b1 in
+-- .. let bp in dc t1..tk x1 .. xn@, where t1..tk are the
+-- *universally-quantified* type args of 'dc'. Floats can also be (and most
+-- likely are) single-alternative case expressions. Why does
+-- 'exprIsConApp_maybe' return floats? We may have to look through lets and
+-- cases to detect that we are in the presence of a data constructor wrapper. In
+-- this case, we need to return the lets and cases that we traversed. See Note
+-- [exprIsConApp_maybe on data constructors with wrappers]. Data constructor wrappers
+-- are unfolded late, but we really want to trigger case-of-known-constructor as
+-- early as possible. See also Note [Activation for data constructor wrappers]
+-- in MkId.
+exprIsConApp_maybe :: InScopeEnv -> CoreExpr -> Maybe ([FloatBind], DataCon, [Type], [CoreExpr])
 exprIsConApp_maybe (in_scope, id_unf) expr
-  = go (Left in_scope) expr (CC [] (mkRepReflCo (exprType expr)))
+  = do
+    (floats, con, ty, args) <- go (Left in_scope) [] expr (CC [] (mkRepReflCo (exprType expr)))
+    return $ (reverse floats, con, ty, args)
   where
     go :: Either InScopeSet Subst
              -- Left in-scope  means "empty substitution"
              -- Right subst    means "apply this substitution to the CoreExpr"
-       -> CoreExpr -> ConCont
-       -> Maybe (DataCon, [Type], [CoreExpr])
-    go subst (Tick t expr) cont
-       | not (tickishIsCode t) = go subst expr cont
-    go subst (Cast expr co1) (CC args co2)
+       -> [FloatBind] -> CoreExpr -> ConCont
+             -- Notice that the floats here are in reverse order
+       -> Maybe ([FloatBind], DataCon, [Type], [CoreExpr])
+    go subst floats (Tick t expr) cont
+       | not (tickishIsCode t) = go subst floats expr cont
+    go subst floats (Cast expr co1) (CC args co2)
        | Just (args', m_co1') <- pushCoArgs (subst_co subst co1) args
             -- See Note [Push coercions in exprIsConApp_maybe]
        = case m_co1' of
-           MCo co1' -> go subst expr (CC args' (co1' `mkTransCo` co2))
-           MRefl    -> go subst expr (CC args' co2)
-    go subst (App fun arg) (CC args co)
-       = go subst fun (CC (subst_arg subst arg : args) co)
-    go subst (Lam var body) (CC (arg:args) co)
+           MCo co1' -> go subst floats expr (CC args' (co1' `mkTransCo` co2))
+           MRefl    -> go subst floats expr (CC args' co2)
+    go subst floats (App fun arg) (CC args co)
+       = go subst floats fun (CC (subst_arg subst arg : args) co)
+    go subst floats (Lam var body) (CC (arg:args) co)
        | exprIsTrivial arg          -- Don't duplicate stuff!
-       = go (extend subst var arg) body (CC args co)
-    go (Right sub) (Var v) cont
+       = go (extend subst var arg) floats body (CC args co)
+    go subst floats (Let bndr@(NonRec b _) expr) cont
+       = let (subst', bndr') = subst_bind subst bndr in
+           go subst' (FloatLet bndr' : floats) expr cont
+    go subst floats (Case scrut b _ [(con, vars, expr)]) cont
+       = let
+          (subst', b') = subst_bndr subst b
+          (subst'', vars') = subst_bndrs subst' vars
+         in
+           go subst'' (FloatCase (subst_arg subst scrut) b' con vars' : floats) expr cont
+    go (Right sub) floats (Var v) cont
        = go (Left (substInScope sub))
+            floats
             (lookupIdSubst (text "exprIsConApp" <+> ppr expr) sub v)
             cont
 
-    go (Left in_scope) (Var fun) cont@(CC args co)
+    go (Left in_scope) floats (Var fun) cont@(CC args co)
 
         | Just con <- isDataConWorkId_maybe fun
         , count isValArg args == idArity fun
-        = pushCoDataCon con args co
+        = pushFloats floats $ pushCoDataCon con args co
+
+        -- Look through data constructor wrappers: they inline late (See Note
+        -- [Activation for data constructor wrappers]) but we want to do
+        -- case-of-known-constructor optimisation eagerly.
+        | isDataConWrapId fun
+        , let rhs = uf_tmpl (realIdUnfolding fun)
+        = go (Left in_scope) floats rhs cont
 
         -- Look through dictionary functions; see Note [Unfolding DFuns]
         | DFunUnfolding { df_bndrs = bndrs, df_con = con, df_args = dfun_args } <- unfolding
         , bndrs `equalLength` args    -- See Note [DFun arity check]
         , let subst = mkOpenSubst in_scope (bndrs `zip` args)
-        = pushCoDataCon con (map (substExpr (text "exprIsConApp1") subst) dfun_args) co
+        = pushFloats floats $
+          pushCoDataCon con (map (substExpr (text "exprIsConApp1") subst) dfun_args) co
 
         -- Look through unfoldings, but only arity-zero one;
         -- if arity > 0 we are effectively inlining a function call,
@@ -811,18 +914,23 @@ exprIsConApp_maybe (in_scope, id_unf) expr
         | idArity fun == 0
         , Just rhs <- expandUnfolding_maybe unfolding
         , let in_scope' = extendInScopeSetSet in_scope (exprFreeVars rhs)
-        = go (Left in_scope') rhs cont
+        = go (Left in_scope') floats rhs cont
 
         -- See Note [exprIsConApp_maybe on literal strings]
         | (fun `hasKey` unpackCStringIdKey) ||
           (fun `hasKey` unpackCStringUtf8IdKey)
-        , [arg]                <- args
+        , [arg]              <- args
         , Just (LitString str) <- exprIsLiteral_maybe (in_scope, id_unf) arg
-        = dealWithStringLiteral fun str co
+        = pushFloats floats $ dealWithStringLiteral fun str co
         where
           unfolding = id_unf fun
 
-    go _ _ _ = Nothing
+    go _ _ _ _ = Nothing
+
+    pushFloats :: [FloatBind] -> Maybe (DataCon, [Type], [CoreExpr]) -> Maybe ([FloatBind], DataCon, [Type], [CoreExpr])
+    pushFloats floats x = do
+      (c, tys, args) <- x
+      return (floats, c, tys, args)
 
     ----------------------------
     -- Operations on the (Either InScopeSet CoreSubst)
@@ -833,6 +941,22 @@ exprIsConApp_maybe (in_scope, id_unf) expr
     subst_arg (Left {}) e = e
     subst_arg (Right s) e = substExpr (text "exprIsConApp2") s e
 
+    subst_bind (Left in_scope) bndr@(NonRec b _) =
+      (Left (extendInScopeSet in_scope b), bndr)
+    subst_bind (Left _) _ =
+      error "CoreOpt.exprIsConApp_maybe: recursive float."
+    subst_bind (Right subst) bndr =
+      let (subst', bndr') = substBind subst bndr in
+      (Right subst', bndr')
+
+    subst_bndr (Left in_scope) b =
+      (Left (extendInScopeSet in_scope b), b)
+    subst_bndr (Right subst) b =
+      let (subst', b') = substBndr subst b in
+      (Right subst', b')
+
+    subst_bndrs subst bs = mapAccumL subst_bndr subst bs
+
     extend (Left in_scope) v e = Right (extendSubst (mkEmptySubst in_scope) v e)
     extend (Right s)       v e = Right (extendSubst s v e)
 
index 8de684b..1583c59 100644 (file)
@@ -17,7 +17,7 @@ module MkCore (
         mkCharExpr, mkStringExpr, mkStringExprFS, mkStringExprFSWith,
 
         -- * Floats
-        FloatBind(..), wrapFloat,
+        FloatBind(..), wrapFloat, wrapFloats, floatBindings,
 
         -- * Constructing small tuples
         mkCoreVarTup, mkCoreVarTupTy, mkCoreTup, mkCoreUbxTup,
@@ -560,6 +560,19 @@ wrapFloat :: FloatBind -> CoreExpr -> CoreExpr
 wrapFloat (FloatLet defns)       body = Let defns body
 wrapFloat (FloatCase e b con bs) body = Case e b (exprType body) [(con, bs, body)]
 
+-- | Applies the floats from right to left. That is @wrapFloats [b1, b2, …, bn]
+-- u = let b1 in let b2 in … in let bn in u@
+wrapFloats :: [FloatBind] -> CoreExpr -> CoreExpr
+wrapFloats floats expr = foldr wrapFloat expr floats
+
+bindBindings :: CoreBind -> [Var]
+bindBindings (NonRec b _) = [b]
+bindBindings (Rec bnds) = map fst bnds
+
+floatBindings :: FloatBind -> [Var]
+floatBindings (FloatLet bnd) = bindBindings bnd
+floatBindings (FloatCase _ b _ bs) = b:bs
+
 {-
 ************************************************************************
 *                                                                      *
index 7111c7b..a6d7bcc 100644 (file)
@@ -1039,9 +1039,9 @@ dataToTagRule = a `mplus` b
       dflags <- getDynFlags
       [_, val_arg] <- getArgs
       in_scope <- getInScopeEnv
-      (dc,_,_) <- liftMaybe $ exprIsConApp_maybe in_scope val_arg
+      (floats, dc,_,_) <- liftMaybe $ exprIsConApp_maybe in_scope val_arg
       ASSERT( not (isNewTyCon (dataConTyCon dc)) ) return ()
-      return $ mkIntVal dflags (toInteger (dataConTagZ dc))
+      return $ wrapFloats floats (mkIntVal dflags (toInteger (dataConTagZ dc)))
 
 {- Note [dataToTag# magic]
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
index e8c7ef2..07f0549 100644 (file)
@@ -22,7 +22,7 @@ module FloatIn ( floatInwards ) where
 import GhcPrelude
 
 import CoreSyn
-import MkCore
+import MkCore hiding    ( wrapFloats )
 import HscTypes         ( ModGuts(..) )
 import CoreUtils
 import CoreFVs
index 8418ce1..2bb177d 100644 (file)
@@ -22,7 +22,8 @@ import FamInstEnv       ( FamInstEnv )
 import Literal          ( litIsLifted ) --, mkLitInt ) -- temporalily commented out. See #8326
 import Id
 import MkId             ( seqId )
-import MkCore           ( mkImpossibleExpr, castBottomExpr )
+import MkCore           ( FloatBind, mkImpossibleExpr, castBottomExpr )
+import qualified MkCore as MkCore
 import IdInfo
 import Name             ( mkSystemVarName, isExternalName, getOccFS )
 import Coercion hiding  ( substCo, substCoVar )
@@ -2354,6 +2355,26 @@ Why don't we drop the case?  Because it's strict in v.  It's technically
 wrong to drop even unnecessary evaluations, and in practice they
 may be a result of 'seq' so we *definitely* don't want to drop those.
 I don't really know how to improve this situation.
+
+
+Note [FloatBinds from constructor wrappers]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+If we have FloatBinds coming from the constructor wrapper
+(as in Note [exprIsConApp_maybe on data constructors with wrappers]),
+ew cannot float past them. We'd need to float the FloatBind
+together with the simplify floats, unfortunately the
+simplifier doesn't have case-floats. The simplest thing we can
+do is to wrap all the floats here. The next iteration of the
+simplifier will take care of all these cases and lets.
+
+Given data T = MkT !Bool, this allows us to simplify
+case $WMkT b of { MkT x -> f x }
+to
+case b of { b' -> f b' }.
+
+We could try and be more clever (like maybe wfloats only contain
+let binders, so we could float them). But the need for the
+extra complication is not clear.
 -}
 
 ---------------------------------------------------------
@@ -2378,25 +2399,36 @@ rebuildCase env scrut case_bndr alts cont
   = do  { tick (KnownBranch case_bndr)
         ; case findAlt (LitAlt lit) alts of
             Nothing           -> missingAlt env case_bndr alts cont
-            Just (_, bs, rhs) -> simple_rhs bs rhs }
+            Just (_, bs, rhs) -> simple_rhs [] scrut bs rhs }
 
-  | Just (con, ty_args, other_args) <- exprIsConApp_maybe (getUnfoldingInRuleMatch env) scrut
+  | Just (wfloats, con, ty_args, other_args) <- exprIsConApp_maybe (getUnfoldingInRuleMatch env) scrut
         -- Works when the scrutinee is a variable with a known unfolding
         -- as well as when it's an explicit constructor application
   = do  { tick (KnownBranch case_bndr)
         ; case findAlt (DataAlt con) alts of
             Nothing  -> missingAlt env case_bndr alts cont
-            Just (DEFAULT, bs, rhs) -> simple_rhs bs rhs
-            Just (_, bs, rhs)       -> knownCon env scrut con ty_args other_args
+            Just (DEFAULT, bs, rhs) -> let con_app = Var (dataConWorkId con)
+                                                 `mkTyApps` ty_args
+                                                 `mkApps`   other_args
+                                       in simple_rhs wfloats con_app bs rhs
+            Just (_, bs, rhs)       -> knownCon env scrut wfloats con ty_args other_args
                                                 case_bndr bs rhs cont
         }
   where
-    simple_rhs bs rhs = ASSERT( null bs )
-                        do { (floats1, env') <- simplNonRecX env case_bndr scrut
-                               -- scrut is a constructor application,
-                               -- hence satisfies let/app invariant
-                           ; (floats2, expr') <- simplExprF env' rhs cont
-                           ; return (floats1 `addFloats` floats2, expr') }
+    simple_rhs wfloats scrut' bs rhs =
+      ASSERT( null bs )
+      do { let env0 = addNewInScopeIds env (concatMap MkCore.floatBindings wfloats)
+         ; (floats1, env') <- simplNonRecX env0 case_bndr scrut'
+             -- scrut is a constructor application,
+             -- hence satisfies let/app invariant
+         ; (floats2, expr') <- simplExprF env' rhs cont
+         ; case wfloats of
+             [] -> return (floats1 `addFloats` floats2, expr')
+             _ -> return
+               -- See Note [FloatBinds from constructor wrappers]
+                   ( emptyFloats env,
+                     MkCore.wrapFloats wfloats $
+                     wrapFloats (floats1 `addFloats` floats2) expr' )}
 
 
 --------------------------------------------------
@@ -2824,17 +2856,25 @@ All this should happen in one sweep.
 -}
 
 knownCon :: SimplEnv
-         -> OutExpr                             -- The scrutinee
-         -> DataCon -> [OutType] -> [OutExpr]   -- The scrutinee (in pieces)
-         -> InId -> [InBndr] -> InExpr          -- The alternative
+         -> OutExpr                                           -- The scrutinee
+         -> [FloatBind] -> DataCon -> [OutType] -> [OutExpr]  -- The scrutinee (in pieces)
+         -> InId -> [InBndr] -> InExpr                        -- The alternative
          -> SimplCont
          -> SimplM (SimplFloats, OutExpr)
 
-knownCon env scrut dc dc_ty_args dc_args bndr bs rhs cont
-  = do  { (floats1, env1)  <- bind_args env bs dc_args
+knownCon env scrut dc_floats dc dc_ty_args dc_args bndr bs rhs cont
+  = do  { let env0 = addNewInScopeIds env (concatMap MkCore.floatBindings dc_floats)
+        ; (floats1, env1)  <- bind_args env0 bs dc_args
         ; (floats2, env2) <- bind_case_bndr env1
         ; (floats3, expr') <- simplExprF env2 rhs cont
-        ; return (floats1 `addFloats` floats2 `addFloats` floats3, expr') }
+        ; case dc_floats of
+            [] ->
+              return (floats1 `addFloats` floats2 `addFloats` floats3, expr')
+            _ ->
+              return ( emptyFloats env
+               -- See Note [FloatBinds from constructor wrappers]
+                     , MkCore.wrapFloats dc_floats $
+                       wrapFloats (floats1 `addFloats` floats2 `addFloats` floats3) expr') }
   where
     zap_occ = zapBndrOccInfo (isDeadBinder bndr)    -- bndr is an InId
 
index 54308c6..30b5f8c 100644 (file)
@@ -4,7 +4,7 @@ Result size of Tidy Core
   = {terms: 63, types: 43, coercions: 1, joins: 0/0}
 
 -- RHS size: {terms: 2, types: 4, coercions: 1, joins: 0/0}
-T2431.$WRefl [InlPrag=INLINE[2]] :: forall a. a :~: a
+T2431.$WRefl [InlPrag=INLINE[0]] :: forall a. a :~: a
 [GblId[DataConWrapper],
  Caf=NoCafRefs,
  Str=m,
@@ -110,6 +110,3 @@ T2431.$tc'Refl
       $tc'Refl2
       1#
       $krep3
-
-
-
index 5332a3e..41f67dc 100644 (file)
@@ -4,7 +4,7 @@ Result size of Tidy Core
   = {terms: 114, types: 53, coercions: 0, joins: 0/0}
 
 -- RHS size: {terms: 6, types: 3, coercions: 0, joins: 0/0}
-T7360.$WFoo3 [InlPrag=INLINE[2]] :: Int -> Foo
+T7360.$WFoo3 [InlPrag=INLINE[0]] :: Int -> Foo
 [GblId[DataConWrapper],
  Arity=1,
  Caf=NoCafRefs,
diff --git a/testsuite/tests/simplCore/should_run/T15840.hs b/testsuite/tests/simplCore/should_run/T15840.hs
new file mode 100644 (file)
index 0000000..e844f9d
--- /dev/null
@@ -0,0 +1,14 @@
+module Main (main) where
+
+data T = MkT !Bool
+
+f :: T -> Bool
+f _ = False
+{-# NOINLINE f #-}
+
+{-# RULES "non-det" [1] forall x. f (MkT x) = x #-}
+
+main :: IO ()
+main = print (f (MkT True))
+-- Prints `True` if the rule fires, or `False` is the wrapper for `MkT` inlines
+-- in phase 2, preventing the rule from being triggered in phase 1.
diff --git a/testsuite/tests/simplCore/should_run/T15840.stdout b/testsuite/tests/simplCore/should_run/T15840.stdout
new file mode 100644 (file)
index 0000000..0ca9514
--- /dev/null
@@ -0,0 +1 @@
+True
diff --git a/testsuite/tests/simplCore/should_run/T15840a.hs b/testsuite/tests/simplCore/should_run/T15840a.hs
new file mode 100644 (file)
index 0000000..ade75b6
--- /dev/null
@@ -0,0 +1,22 @@
+module Main (main) where
+
+data T = MkT !Bool
+
+f :: Bool -> IO ()
+f _ = putStrLn "The rule triggered before case-of-known-constructor could take effect (bad!)"
+{-# NOINLINE f #-}
+
+g :: IO ()
+g = putStrLn "Case-of-known-constructor triggered (good!)"
+
+{-# RULES "non-det" [~0] f True = g #-}
+
+main :: IO ()
+main =
+  case MkT True of
+    MkT x -> f x
+-- What we want to see is case-of-known-constructor triggering before phase 0
+-- (when the wrapper for MkT is allowed to be inlined). If it is, then the rule
+-- will see `f True` and trigger, and `g` will be run. If it isn't then `f True`
+-- will only appear at phase 0, when the rule cannot trigger, hence `f` will be
+-- run.
diff --git a/testsuite/tests/simplCore/should_run/T15840a.stdout b/testsuite/tests/simplCore/should_run/T15840a.stdout
new file mode 100644 (file)
index 0000000..54601ba
--- /dev/null
@@ -0,0 +1 @@
+Case-of-known-constructor triggered (good!)
index 0a74c62..f808943 100644 (file)
@@ -87,3 +87,5 @@ test('T14894', [when((arch('powerpc64') or arch('powerpc64le')), expect_broken(1
 test('T14965', normal, compile_and_run, [''])
 test('T15114', only_ways('optasm'), compile_and_run, [''])
 test('T15436', normal, compile_and_run, [''])
+test('T15840', normal, compile_and_run, [''])
+test('T15840a', normal, compile_and_run, [''])