summaryrefslogtreecommitdiff
path: root/compiler/GHC/Core/Opt/WorkWrap/Utils.hs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/GHC/Core/Opt/WorkWrap/Utils.hs')
-rw-r--r--compiler/GHC/Core/Opt/WorkWrap/Utils.hs172
1 files changed, 112 insertions, 60 deletions
diff --git a/compiler/GHC/Core/Opt/WorkWrap/Utils.hs b/compiler/GHC/Core/Opt/WorkWrap/Utils.hs
index 5f450b9316..c62ba572de 100644
--- a/compiler/GHC/Core/Opt/WorkWrap/Utils.hs
+++ b/compiler/GHC/Core/Opt/WorkWrap/Utils.hs
@@ -8,7 +8,8 @@ A library for the ``worker\/wrapper'' back-end to the strictness analyser
{-# LANGUAGE ViewPatterns #-}
module GHC.Core.Opt.WorkWrap.Utils
- ( WwOpts(..), initWwOpts, mkWwBodies, mkWWstr, mkWWstr_one, mkWorkerArgs
+ ( WwOpts(..), initWwOpts, mkWwBodies, mkWWstr, mkWWstr_one
+ , needsVoidWorkerArg, addVoidWorkerArg
, DataConPatContext(..)
, UnboxingDecision(..), wantToUnboxArg
, findTypeShape, IsRecDataConResult(..), isRecDataCon
@@ -141,7 +142,6 @@ data WwOpts
{ wo_fam_envs :: !FamInstEnvs
, wo_simple_opts :: !SimpleOpts
, wo_cpr_anal :: !Bool
- , wo_fun_to_thunk :: !Bool
-- Used for absent argument error message
, wo_module :: !Module
@@ -155,7 +155,6 @@ initWwOpts this_mod dflags fam_envs = MkWwOpts
{ wo_fam_envs = fam_envs
, wo_simple_opts = initSimpleOpts dflags
, wo_cpr_anal = gopt Opt_CprAnal dflags
- , wo_fun_to_thunk = gopt Opt_FunToThunk dflags
, wo_module = this_mod
, wo_unlift_strict = gopt Opt_WorkerWrapperUnlift dflags
}
@@ -240,11 +239,14 @@ mkWwBodies opts fun_id arg_vars res_ty demands res_cpr
; let (work_args, work_marks) = unzip work_args_cbv
-- Do CPR w/w. See Note [Always do CPR w/w]
- ; (useful2, wrap_fn_cpr, work_fn_cpr, cpr_res_ty)
+ ; (useful2, wrap_fn_cpr, work_fn_cpr)
<- mkWWcpr_entry opts res_ty' res_cpr
- ; let (work_lam_args, work_call_args, work_call_cbv) = mkWorkerArgs fun_id (wo_fun_to_thunk opts)
- work_args work_marks cpr_res_ty
+ ; let (work_lam_args, work_call_args, work_call_cbv)
+ | needsVoidWorkerArg fun_id arg_vars work_args
+ = addVoidWorkerArg work_args work_marks
+ | otherwise
+ = (work_args, work_args, work_marks)
call_work work_fn = mkVarApps (Var work_fn) work_call_args
call_rhs fn_rhs = mkAppsBeta fn_rhs fn_args
@@ -347,9 +349,19 @@ function for the worker:
of the fun body in the next run of the Simplifier, but CoreLint will complain
in the meantime, so zap it.
-We zap in mkWwBodies because we need the zapped variables both when binding them
-in mkWWstr (mkAbsentFiller, specifically) and in mkWorkerArgs, where we produce
-the call to the fun body.
+We zap in mkWwBodies because we need the zapped variables when binding them in
+mkWWstr (mkAbsentFiller, specifically).
+
+Note [Do not split void functions]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Consider this rather common form of binding:
+ $j = \x:Void# -> ...no use of x...
+
+Since x is not used it'll be marked as absent. But there is no point
+in w/w-ing because we'll simply add (\y:Void#), see addVoidWorkerArg.
+
+If x has a more interesting type (eg Int, or Int#), there *is* a point
+in w/w so that we don't pass the argument at all.
************************************************************************
* *
@@ -369,44 +381,29 @@ add a void argument. E.g.
We use the state-token type which generates no code.
-}
--- | Prevent a function from becoming a thunk by adding a void argument if
--- required.
-mkWorkerArgs :: Id -- The wrapper Id
- -> Bool -- Allow fun->thunk conversion.
- -> [Var]
- -> [CbvMark]
- -> Type -- Type of body
- -> ([Var], -- Lambda bound args
- [Var], -- Args at call site
- [CbvMark] -- cbv semantics for the worker args.
- )
-mkWorkerArgs wrap_id fun_to_thunk args cbv_marks res_ty
- | not (isJoinId wrap_id) -- Join Ids never need an extra arg
- , not (any isId args) -- No existing value lambdas
- , needs_a_value_lambda -- and we need to add one
- = (args ++ [voidArgId], args ++ [voidPrimId], cbv_marks ++ [NotMarkedCbv])
-
- | otherwise
- = (args, args, cbv_marks)
- where
- -- If fun_to_thunk is False we always keep at least one value
- -- argument: see Note [Protecting the last value argument]
- -- If it is True, we only need to keep a value argument if
- -- the result type is (or might be) unlifted, in which case
- -- dropping the last arg would mean we wrongly used call-by-value
- needs_a_value_lambda
- = not fun_to_thunk
- || might_be_unlifted
-
- -- Might the result be lifted?
- -- False => definitely lifted
- -- True => might be unlifted
- -- We may encounter a representation-polymorphic result, in which case we
- -- conservatively assume that we have laziness that needs
- -- preservation. See #15186.
- might_be_unlifted = case isLiftedType_maybe res_ty of
- Just lifted -> not lifted
- Nothing -> True
+-- | Whether the worker needs an additional `Void#` arg as per
+-- Note [Protecting the last value argument] or
+-- Note [Preserving float barriers].
+needsVoidWorkerArg :: Id -> [Var] -> [Var] -> Bool
+needsVoidWorkerArg fn_id wrap_args work_args
+ = not (isJoinId fn_id) && no_value_arg -- See Note [Protecting the last value argument]
+ || needs_float_barrier -- See Note [Preserving float barriers]
+ where
+ no_value_arg = all (not . isId) work_args
+ is_float_barrier v = isId v && hasNoOneShotInfo (idOneShotInfo v)
+ wrap_had_barrier = any is_float_barrier wrap_args
+ work_has_barrier = any is_float_barrier work_args
+ needs_float_barrier = wrap_had_barrier && not work_has_barrier
+
+-- | Inserts a `Void#` arg before the first value argument (but after leading type args).
+addVoidWorkerArg :: [Var] -> [CbvMark]
+ -> ([Var], -- Lambda bound args
+ [Var], -- Args at call site
+ [CbvMark]) -- cbv semantics for the worker args.
+addVoidWorkerArg work_args cbv_marks
+ = (ty_args ++ voidArgId:rest, ty_args ++ voidPrimId:rest, NotMarkedCbv:cbv_marks)
+ where
+ (ty_args, rest) = break isId work_args
{-
Note [Protecting the last value argument]
@@ -414,13 +411,71 @@ Note [Protecting the last value argument]
If the user writes (\_ -> E), they might be intentionally disallowing
the sharing of E. Since absence analysis and worker-wrapper are keen
to remove such unused arguments, we add in a void argument to prevent
-the function from becoming a thunk.
+the function from becoming a thunk. Three reasons why turning a function
+into a thunk might be bad:
+
+1) It can create a space leak. e.g.
+ f x = let y () = [1..x]
+ in (sum (y ()) + length (y ()))
+ As written it'll calculate [1..x] twice, and avoid keeping a big
+ list around. (Of course let-floating may introduce the leak; but
+ at least w/w doesn't.)
-The user can avoid adding the void argument with the -ffun-to-thunk
-flag. However, this can create sharing, which may be bad in two ways. 1) It can
-create a space leak. 2) It can prevent inlining *under a lambda*. If w/w
-removes the last argument from a function f, then f now looks like a thunk, and
-so f can't be inlined *under a lambda*.
+2) It can prevent inlining *under a lambda*. e.g.
+ g = \y. [1..100]
+ f = \t. g ()
+ Here we can inline g under the \t. But we won't if we remove the \y.
+
+3) It can create an unlifted binding. E.g.
+ g :: Int -> Int#
+ g = \x. 30#
+ Removing the \x would leave an unlifted binding.
+
+NB: none of these apply to a join point.
+
+Note [Preserving float barriers]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Consider
+```
+let
+ t = sum [0..x]
+ f a{os} b[Dmd=A] c{os} = ... t ...
+in f 1 2 3 + f 4 5 6
+```
+Here, we would like to drop the argument `b` because it's absent. But doing so
+leaves behind only one-shot lambdas, `$wf a{os} c{os} = ...`, and then the
+Simplifier will inline `t` into `$wf`, because `$wf` says "I'm only called
+once". That's bad, because we lost sharing of `t`! Similarly, FloatIn would
+happily float `t` into `$wf`, see Note [Floating in past a lambda group].
+
+Why does floating happen after dropping `b` but not before? Because `b` was the
+only non-one-shot value lambda left, acting as our "float barrier".
+
+Definition: A float barrier is a non-one-shot value lambda.
+Key insight: If `f` had a float barrier, `$wf` has to have one, too.
+
+To this end, in `needsVoidWorkerArg`, we check whether the wrapper had a float
+barrier and if the worker has none so far. If that is the case, we add a `Void#`
+argument at the end as an artificial float barrier.
+
+The issue is tracked in #21150. It came up when compiling GHC itself, in
+GHC.Tc.Gen.Bind.mkEdges. There the key_map thunk was inlined after WW dropped a
+leading absent non-one-shot arg. Here are some example wrapper arguments of
+which some are absent or one-shot and the resulting worker arguments:
+
+ * \a{Abs}.\b{os}.\c{os}... ==> \b{os}.\c{os}.\(_::Void#)...
+ Wrapper arg `a` was the only float barrier and had been dropped. Hence Void#
+ * \a{Abs,os}.\b{os}.\c... ==> \b{os}.\c...
+ Worker arg `c` is a float barrier.
+ * \a.\b{Abs}.\c{os}... ==> \a.\c{os}...
+ Worker arg `a` is a float barrier.
+ * \a{os}.\b{Abs,os}.\c{os}... ==> \a{os}.\c{os}...
+ Wrapper didn't have a float barrier, no need for Void#.
+ * \a{Abs,os}.... ==> ... (no value lambda left)
+ This examples simply demonstrates that preserving float barriers is not
+ enough to subsume Note [Protecting the last value argument].
+
+Executable examples in T21150.
Note [Join points and beta-redexes]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1301,12 +1356,10 @@ mkWWcpr_entry
-> Cpr -- CPR analysis results
-> UniqSM (Bool, -- Is w/w'ing useful?
CoreExpr -> CoreExpr, -- New wrapper. 'nop_fn' if not useful
- CoreExpr -> CoreExpr, -- New worker. 'nop_fn' if not useful
- Type) -- Type of worker's body.
- -- Just the input body_ty if not useful
+ CoreExpr -> CoreExpr) -- New worker. 'nop_fn' if not useful
-- ^ Entrypoint to CPR W/W. See Note [Worker/wrapper for CPR] for an overview.
mkWWcpr_entry opts body_ty body_cpr
- | not (wo_cpr_anal opts) = return (badWorker, nop_fn, nop_fn, body_ty)
+ | not (wo_cpr_anal opts) = return (badWorker, nop_fn, nop_fn)
| otherwise = do
-- Part (1)
res_bndr <- mk_res_bndr body_ty
@@ -1322,10 +1375,9 @@ mkWWcpr_entry opts body_ty body_cpr
-- Stacking unboxer (work_fn) and builder (wrap_fn) together
let wrap_fn = unbox_transit_tup rebuilt_result -- 3 2
work_fn body = bind_res_bndr body (work_unpack_res transit_tup) -- 1 2 3
- work_body_ty = exprType transit_tup
return $ if not useful
- then (badWorker, nop_fn, nop_fn, body_ty)
- else (goodWorker, wrap_fn, work_fn, work_body_ty)
+ then (badWorker, nop_fn, nop_fn)
+ else (goodWorker, wrap_fn, work_fn)
-- | Part (1) of Note [Worker/wrapper for CPR].
mk_res_bndr :: Type -> UniqSM Id