diff options
author | Simon Peyton Jones <simonpj@microsoft.com> | 2020-03-20 08:48:47 +0000 |
---|---|---|
committer | Marge Bot <ben+marge-bot@smart-cactus.org> | 2020-03-29 17:29:30 -0400 |
commit | 54250f2d8de910b094070c1b48f086030df634b1 (patch) | |
tree | e062c160912d97eefbdd21d2ce239fd824089e43 /compiler | |
parent | f1a6c73d01912b389e012a0af81a5c2002e82636 (diff) | |
download | haskell-54250f2d8de910b094070c1b48f086030df634b1.tar.gz |
Demand analysis: simplify the demand for a RHS
Ticket #17932 showed that we were using a stupid demand for the RHS
of a let-binding, when the result is a product. This was the result
of a "fix" in 2013, which (happily) turns out to no longer be
necessary.
So I just deleted the code, which simplifies the demand analyser,
and fixes #17932. That in turn uncovered that the anticipation
of worker/wrapper in CPR analysis was inaccurate, hence the logic
that decides whether to unbox an argument in WW was extracted into
a function `wantToUnbox`, now consulted by CPR analysis.
I tried nofib, and got 0.0% perf changes.
All this came up when messing about with !2873 (ticket #17917),
but is idependent of it.
Unfortunately, this patch regresses #4267 and realised that it is now
blocked on #16335.
Diffstat (limited to 'compiler')
-rw-r--r-- | compiler/GHC/Core/Op/CprAnal.hs | 86 | ||||
-rw-r--r-- | compiler/GHC/Core/Op/DmdAnal.hs | 27 | ||||
-rw-r--r-- | compiler/GHC/Core/Op/WorkWrap/Lib.hs | 100 |
3 files changed, 117 insertions, 96 deletions
diff --git a/compiler/GHC/Core/Op/CprAnal.hs b/compiler/GHC/Core/Op/CprAnal.hs index 8016c2c13d..022ce0b7f1 100644 --- a/compiler/GHC/Core/Op/CprAnal.hs +++ b/compiler/GHC/Core/Op/CprAnal.hs @@ -13,7 +13,6 @@ module GHC.Core.Op.CprAnal ( cprAnalProgram ) where import GhcPrelude -import GHC.Core.Op.WorkWrap.Lib ( deepSplitProductType_maybe ) import GHC.Driver.Session import GHC.Types.Demand import GHC.Types.Cpr @@ -30,6 +29,7 @@ import GHC.Core.Utils ( exprIsHNF, dumpIdInfoOfProgram ) import GHC.Core.TyCon import GHC.Core.Type import GHC.Core.FamInstEnv +import GHC.Core.Op.WorkWrap.Lib import Util import ErrUtils ( dumpIfSet_dyn, DumpFormat (..) ) import Maybes ( isJust, isNothing ) @@ -88,7 +88,8 @@ Ideally, we would want the following pipeline: 4. worker/wrapper (for CPR) Currently, we omit 2. and anticipate the results of worker/wrapper. -See Note [CPR in a DataAlt case alternative] and Note [CPR for strict binders]. +See Note [CPR in a DataAlt case alternative] +and Note [CPR for binders that will be unboxed]. An additional w/w pass would simplify things, but probably add slight overhead. So currently we have @@ -175,7 +176,7 @@ cprAnal' env (Lam var body) | otherwise = (lam_ty, Lam var body') where - env' = extendSigsWithLam env var + env' = extendAnalEnvForDemand env var (idDemandInfo var) (body_ty, body') = cprAnal env' body lam_ty = abstractCprTy body_ty @@ -392,15 +393,25 @@ lookupSigEnv env id = lookupVarEnv (ae_sigs env) id nonVirgin :: AnalEnv -> AnalEnv nonVirgin env = env { ae_virgin = False } -extendSigsWithLam :: AnalEnv -> Id -> AnalEnv --- Extend the AnalEnv when we meet a lambda binder -extendSigsWithLam env id +-- | A version of 'extendAnalEnv' for a binder of which we don't see the RHS +-- needed to compute a 'CprSig' (e.g. lambdas and DataAlt field binders). +-- In this case, we can still look at their demand to attach CPR signatures +-- anticipating the unboxing done by worker/wrapper. +-- See Note [CPR for binders that will be unboxed]. +extendAnalEnvForDemand :: AnalEnv -> Id -> Demand -> AnalEnv +extendAnalEnvForDemand env id dmd | isId id - , isStrictDmd (idDemandInfo id) -- See Note [CPR for strict binders] - , Just (dc,_,_,_) <- deepSplitProductType_maybe (ae_fam_envs env) $ idType id + , Just (_, DataConAppContext { dcac_dc = dc }) + <- wantToUnbox (ae_fam_envs env) has_inlineable_prag (idType id) dmd = extendAnalEnv env id (CprSig (conCprType (dataConTag dc))) | otherwise = env + where + -- Rather than maintaining in AnalEnv whether we are in an INLINEABLE + -- function, we just assume that we aren't. That flag is only relevant + -- to Note [Do not unpack class dictionaries], the few unboxing + -- opportunities on dicts it prohibits are probably irrelevant to CPR. + has_inlineable_prag = False extendEnvForDataAlt :: AnalEnv -> CoreExpr -> Id -> DataCon -> [Var] -> AnalEnv -- See Note [CPR in a DataAlt case alternative] @@ -425,18 +436,16 @@ extendEnvForDataAlt env scrut case_bndr dc bndrs -- propagate available unboxed things from the scrutinee, getting rid of -- the is_var_scrut heuristic. See Note [CPR in a DataAlt case alternative]. -- Giving strict binders the CPR property only makes sense for products, as - -- the arguments in Note [CPR for strict binders] don't apply to sums (yet); - -- we lack WW for strict binders of sum type. + -- the arguments in Note [CPR for binders that will be unboxed] don't apply + -- to sums (yet); we lack WW for strict binders of sum type. do_con_arg env (id, str) - | let is_strict = isStrictDmd (idDemandInfo id) || isMarkedStrict str - , is_var_scrut && is_strict - , let fam_envs = ae_fam_envs env - , Just (dc,_,_,_) <- deepSplitProductType_maybe fam_envs $ idType id - = extendAnalEnv env id (CprSig (conCprType (dataConTag dc))) + | is_var scrut + -- See Note [Add demands for strict constructors] in WorkWrap.Lib + , let dmd = applyWhen (isMarkedStrict str) strictifyDmd (idDemandInfo id) + = extendAnalEnvForDemand env id dmd | otherwise = env - is_var_scrut = is_var scrut is_var (Cast e _) = is_var e is_var (Var v) = isLocalId v is_var _ = False @@ -472,7 +481,8 @@ Specifically box. If the wrapper doesn't cancel with its caller, we'll end up re-boxing something that we did have available in boxed form. - * Any strict binders with product type, can use Note [CPR for strict binders] + * Any strict binders with product type, can use + Note [CPR for binders that will be unboxed] to anticipate worker/wrappering for strictness info. But we can go a little further. Consider @@ -499,11 +509,11 @@ Specifically sub-component thereof. But it's simple, and nothing terrible happens if we get it wrong. e.g. Trac #10694. -Note [CPR for strict binders] -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If a lambda-bound variable is marked demanded with a strict demand, then give it -a CPR signature, anticipating the results of worker/wrapper. Here's a concrete -example ('f1' in test T10482a), assuming h is strict: +Note [CPR for binders that will be unboxed] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +If a lambda-bound variable will be unboxed by worker/wrapper (so it must be +demanded strictly), then give it a CPR signature. Here's a concrete example +('f1' in test T10482a), assuming h is strict: f1 :: Int -> Int f1 x = case h x of @@ -527,6 +537,9 @@ Note that has product type, else we may get over-optimistic CPR results (e.g. from \x -> x!). + * This also (approximately) applies to DataAlt field binders; + See Note [CPR in a DataAlt case alternative]. + * See Note [CPR examples] Note [CPR for sum types] @@ -628,21 +641,6 @@ point: all of these functions can have the CPR property. True -> x False -> f1 (x-1) - - ------- f2 ----------- - -- x is a strict field of MkT2, so we'll pass it unboxed - -- to $wf2, so it's available unboxed. This depends on - -- the case expression analysing (a subcomponent of) one - -- of the original arguments to the function, so it's - -- a bit more delicate. - - data T2 = MkT2 !Int Int - - f2 :: T2 -> Int - f2 (MkT2 x y) | y>0 = f2 (MkT2 x (y-1)) - | otherwise = x - - ------- f3 ----------- -- h is strict in x, so x will be unboxed before it -- is rerturned in the otherwise case. @@ -652,18 +650,4 @@ point: all of these functions can have the CPR property. f1 :: T3 -> Int f1 (MkT3 x y) | h x y = f3 (MkT3 x (y-1)) | otherwise = x - - - ------- f4 ----------- - -- Just like f2, but MkT4 can't unbox its strict - -- argument automatically, as f2 can - - data family Foo a - newtype instance Foo Int = Foo Int - - data T4 a = MkT4 !(Foo a) Int - - f4 :: T4 Int -> Int - f4 (MkT4 x@(Foo v) y) | y>0 = f4 (MkT4 x (y-1)) - | otherwise = v -} diff --git a/compiler/GHC/Core/Op/DmdAnal.hs b/compiler/GHC/Core/Op/DmdAnal.hs index 88e96773ac..08d244a36a 100644 --- a/compiler/GHC/Core/Op/DmdAnal.hs +++ b/compiler/GHC/Core/Op/DmdAnal.hs @@ -617,16 +617,11 @@ dmdAnalRhsLetDown rec_flag env let_dmd id rhs is_thunk = not (exprIsHNF rhs) && not (isJoinId id) -- | @mkRhsDmd env rhs_arity rhs@ creates a 'CleanDemand' for --- unleashing on the given function's @rhs@, by creating a call demand of --- @rhs_arity@ with a body demand appropriate for possible product types. --- See Note [Product demands for function body]. --- For example, a call of the form @mkRhsDmd _ 2 (\x y -> (x, y))@ returns a --- clean usage demand of @C1(C1(U(U,U)))@. +-- unleashing on the given function's @rhs@, by creating +-- a call demand of @rhs_arity@ +-- See Historical Note [Product demands for function body] mkRhsDmd :: AnalEnv -> Arity -> CoreExpr -> CleanDemand -mkRhsDmd env rhs_arity rhs = - case peelTsFuns rhs_arity (findTypeShape (ae_fam_envs env) (exprType rhs)) of - Just (TsProd tss) -> mkCallDmds rhs_arity (cleanEvalProdDmd (length tss)) - _ -> mkCallDmds rhs_arity cleanEvalDmd +mkRhsDmd _env rhs_arity _rhs = mkCallDmds rhs_arity cleanEvalDmd -- | If given the let-bound 'Id', 'useLetUp' determines whether we should -- process the binding up (body before rhs) or down (rhs before body). @@ -857,9 +852,9 @@ forward plusInt's demand signature, and all is well (see Note [Newtype arity] in GHC.Core.Arity)! A small example is the test case NewtypeArity. -Note [Product demands for function body] -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This example comes from shootout/binary_trees: +Historical Note [Product demands for function body] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +In 2013 I spotted this example, in shootout/binary_trees: Main.check' = \ b z ds. case z of z' { I# ip -> case ds_d13s of @@ -878,8 +873,12 @@ Here we *really* want to unbox z, even though it appears to be used boxed in the Nil case. Partly the Nil case is not a hot path. But more specifically, the whole function gets the CPR property if we do. -So for the demand on the body of a RHS we use a product demand if it's -a product type. +That motivated using a demand of C(C(C(S(L,L)))) for the RHS, where +(solely because the result was a product) we used a product demand +(albeit with lazy components) for the body. But that gives very silly +behaviour -- see #17932. Happily it turns out now to be entirely +unnecessary: we get good results with C(C(C(S))). So I simply +deleted the special case. ************************************************************************ * * diff --git a/compiler/GHC/Core/Op/WorkWrap/Lib.hs b/compiler/GHC/Core/Op/WorkWrap/Lib.hs index 6245bb9099..684c807d07 100644 --- a/compiler/GHC/Core/Op/WorkWrap/Lib.hs +++ b/compiler/GHC/Core/Op/WorkWrap/Lib.hs @@ -8,7 +8,8 @@ A library for the ``worker\/wrapper'' back-end to the strictness analyser module GHC.Core.Op.WorkWrap.Lib ( mkWwBodies, mkWWstr, mkWorkerArgs - , deepSplitProductType_maybe, findTypeShape + , DataConAppContext(..), deepSplitProductType_maybe, wantToUnbox + , findTypeShape , isWorkerSmallEnough ) where @@ -588,21 +589,8 @@ mkWWstr_one dflags fam_envs has_inlineable_prag arg -- (that's what mk_absent_let does) = return (True, [], nop_fn, work_fn) - | isStrictDmd dmd - , Just cs <- splitProdDmd_maybe dmd - -- See Note [Unpacking arguments with product and polymorphic demands] - , not (has_inlineable_prag && isClassPred arg_ty) - -- See Note [Do not unpack class dictionaries] - , Just stuff@(_, _, inst_con_arg_tys, _) <- deepSplitProductType_maybe fam_envs arg_ty - , cs `equalLength` inst_con_arg_tys - -- See Note [mkWWstr and unsafeCoerce] - = unbox_one dflags fam_envs arg cs stuff - - | isSeqDmd dmd -- For seqDmd, splitProdDmd_maybe will return Nothing, but - -- it should behave like <S, U(AAAA)>, for some suitable arity - , Just stuff@(_, _, inst_con_arg_tys, _) <- deepSplitProductType_maybe fam_envs arg_ty - , let abs_dmds = map (const absDmd) inst_con_arg_tys - = unbox_one dflags fam_envs arg abs_dmds stuff + | Just (cs, acdc) <- wantToUnbox fam_envs has_inlineable_prag arg_ty dmd + = unbox_one dflags fam_envs arg cs acdc | otherwise -- Other cases = return (False, [arg], nop_fn, nop_fn) @@ -611,12 +599,36 @@ mkWWstr_one dflags fam_envs has_inlineable_prag arg arg_ty = idType arg dmd = idDemandInfo arg +wantToUnbox :: FamInstEnvs -> Bool -> Type -> Demand -> Maybe ([Demand], DataConAppContext) +wantToUnbox fam_envs has_inlineable_prag ty dmd = + case deepSplitProductType_maybe fam_envs ty of + Just dcac@DataConAppContext{ dcac_arg_tys = con_arg_tys } + | isStrictDmd dmd + -- See Note [Unpacking arguments with product and polymorphic demands] + , Just cs <- split_prod_dmd_arity dmd (length con_arg_tys) + -- See Note [Do not unpack class dictionaries] + , not (has_inlineable_prag && isClassPred ty) + -- See Note [mkWWstr and unsafeCoerce] + , cs `equalLength` con_arg_tys + -> Just (cs, dcac) + _ -> Nothing + where + split_prod_dmd_arity dmd arty + -- For seqDmd, splitProdDmd_maybe will return Nothing (because how would + -- it know the arity?), but it should behave like <S, U(AAAA)>, for some + -- suitable arity + | isSeqDmd dmd = Just (replicate arty absDmd) + -- Otherwise splitProdDmd_maybe does the job + | otherwise = splitProdDmd_maybe dmd + unbox_one :: DynFlags -> FamInstEnvs -> Var -> [Demand] - -> (DataCon, [Type], [(Type, StrictnessMark)], Coercion) + -> DataConAppContext -> UniqSM (Bool, [Var], CoreExpr -> CoreExpr, CoreExpr -> CoreExpr) unbox_one dflags fam_envs arg cs - (data_con, inst_tys, inst_con_arg_tys, co) + DataConAppContext { dcac_dc = data_con, dcac_tys = inst_tys + , dcac_arg_tys = inst_con_arg_tys + , dcac_co = co } = do { (uniq1:uniqs) <- getUniquesM ; let -- See Note [Add demands for strict constructors] cs' = addDataConStrictness data_con cs @@ -898,8 +910,8 @@ If we have f :: Ord a => [a] -> Int -> a {-# INLINABLE f #-} and we worker/wrapper f, we'll get a worker with an INLINABLE pragma -(see Note [Worker-wrapper for INLINABLE functions] in GHC.Core.Op.WorkWrap), which -can still be specialised by the type-class specialiser, something like +(see Note [Worker-wrapper for INLINABLE functions] in GHC.Core.Op.WorkWrap), +which can still be specialised by the type-class specialiser, something like fw :: Ord a => [a] -> Int# -> a BUT if f is strict in the Ord dictionary, we might unpack it, to get @@ -915,9 +927,29 @@ Historical note: #14955 describes how I got this fix wrong the first time. -} -deepSplitProductType_maybe - :: FamInstEnvs -> Type - -> Maybe (DataCon, [Type], [(Type, StrictnessMark)], Coercion) +-- | Context for a 'DataCon' application with a hole for every field, including +-- surrounding coercions. +-- The result of 'deepSplitProductType_maybe' and 'deepSplitCprType_maybe'. +-- +-- Example: +-- +-- > DataConAppContext Just [Int] [(Lazy, Int)] (co :: Maybe Int ~ First Int) +-- +-- represents +-- +-- > Just @Int (_1 :: Int) |> co :: First Int +-- +-- where _1 is a hole for the first argument. The number of arguments is +-- determined by the length of @arg_tys@. +data DataConAppContext + = DataConAppContext + { dcac_dc :: !DataCon + , dcac_tys :: ![Type] + , dcac_arg_tys :: ![(Type, StrictnessMark)] + , dcac_co :: !Coercion + } + +deepSplitProductType_maybe :: FamInstEnvs -> Type -> Maybe DataConAppContext -- If deepSplitProductType_maybe ty = Just (dc, tys, arg_tys, co) -- then dc @ tys (args::arg_tys) :: rep_ty -- co :: ty ~ rep_ty @@ -930,12 +962,14 @@ deepSplitProductType_maybe fam_envs ty , Just con <- isDataProductTyCon_maybe tc , let arg_tys = dataConInstArgTys con tc_args strict_marks = dataConRepStrictness con - = Just (con, tc_args, zipEqual "dspt" arg_tys strict_marks, co) + = Just DataConAppContext { dcac_dc = con + , dcac_tys = tc_args + , dcac_arg_tys = zipEqual "dspt" arg_tys strict_marks + , dcac_co = co } deepSplitProductType_maybe _ _ = Nothing deepSplitCprType_maybe - :: FamInstEnvs -> ConTag -> Type - -> Maybe (DataCon, [Type], [(Type, StrictnessMark)], Coercion) + :: FamInstEnvs -> ConTag -> Type -> Maybe DataConAppContext -- If deepSplitCprType_maybe n ty = Just (dc, tys, arg_tys, co) -- then dc @ tys (args::arg_tys) :: rep_ty -- co :: ty ~ rep_ty @@ -952,7 +986,10 @@ deepSplitCprType_maybe fam_envs con_tag ty , let con = cons `getNth` (con_tag - fIRST_TAG) arg_tys = dataConInstArgTys con tc_args strict_marks = dataConRepStrictness con - = Just (con, tc_args, zipEqual "dsct" arg_tys strict_marks, co) + = Just DataConAppContext { dcac_dc = con + , dcac_tys = tc_args + , dcac_arg_tys = zipEqual "dspt" arg_tys strict_marks + , dcac_co = co } deepSplitCprType_maybe _ _ _ = Nothing findTypeShape :: FamInstEnvs -> Type -> TypeShape @@ -1009,17 +1046,18 @@ mkWWcpr opt_CprAnal fam_envs body_ty cpr | otherwise = case asConCpr cpr of Nothing -> return (False, id, id, body_ty) -- No CPR info - Just con_tag | Just stuff <- deepSplitCprType_maybe fam_envs con_tag body_ty - -> mkWWcpr_help stuff + Just con_tag | Just dcac <- deepSplitCprType_maybe fam_envs con_tag body_ty + -> mkWWcpr_help dcac | otherwise -- See Note [non-algebraic or open body type warning] -> WARN( True, text "mkWWcpr: non-algebraic or open body type" <+> ppr body_ty ) return (False, id, id, body_ty) -mkWWcpr_help :: (DataCon, [Type], [(Type,StrictnessMark)], Coercion) +mkWWcpr_help :: DataConAppContext -> UniqSM (Bool, CoreExpr -> CoreExpr, CoreExpr -> CoreExpr, Type) -mkWWcpr_help (data_con, inst_tys, arg_tys, co) +mkWWcpr_help (DataConAppContext { dcac_dc = data_con, dcac_tys = inst_tys + , dcac_arg_tys = arg_tys, dcac_co = co }) | [arg1@(arg_ty1, _)] <- arg_tys , isUnliftedType arg_ty1 -- Special case when there is a single result of unlifted type |