summaryrefslogtreecommitdiff
path: root/compiler/GHC/Core
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/GHC/Core')
-rw-r--r--compiler/GHC/Core/Lint.hs2
-rw-r--r--compiler/GHC/Core/Opt/SpecConstr.hs27
-rw-r--r--compiler/GHC/Core/Opt/Specialise.hs2
-rw-r--r--compiler/GHC/Core/Opt/WorkWrap.hs12
-rw-r--r--compiler/GHC/Core/Opt/WorkWrap/Utils.hs172
5 files changed, 130 insertions, 85 deletions
diff --git a/compiler/GHC/Core/Lint.hs b/compiler/GHC/Core/Lint.hs
index c0cc8b0cfd..adf8124b12 100644
--- a/compiler/GHC/Core/Lint.hs
+++ b/compiler/GHC/Core/Lint.hs
@@ -174,7 +174,7 @@ In the desugarer, it's very very convenient to be able to say (in effect)
let a = Type Bool in
let x::a = True in <body>
That is, use a type let. See Note [Core type and coercion invariant] in "GHC.Core".
-One place it is used is in mkWorkerArgs; see Note [Join points and beta-redexes]
+One place it is used is in mkWwBodies; see Note [Join points and beta-redexes]
in GHC.Core.Opt.WorkWrap.Utils. (Maybe there are other "clients" of this feature; I'm not sure).
* Hence when linting <body> we need to remember that a=Int, else we
diff --git a/compiler/GHC/Core/Opt/SpecConstr.hs b/compiler/GHC/Core/Opt/SpecConstr.hs
index 14fe9bec00..d973c75570 100644
--- a/compiler/GHC/Core/Opt/SpecConstr.hs
+++ b/compiler/GHC/Core/Opt/SpecConstr.hs
@@ -30,7 +30,7 @@ import GHC.Core.Utils
import GHC.Core.Unfold
import GHC.Core.FVs ( exprsFreeVarsList )
import GHC.Core.Opt.Monad
-import GHC.Core.Opt.WorkWrap.Utils ( isWorkerSmallEnough, mkWorkerArgs )
+import GHC.Core.Opt.WorkWrap.Utils
import GHC.Core.DataCon
import GHC.Core.Coercion hiding( substCo )
import GHC.Core.Rules
@@ -1771,20 +1771,25 @@ spec_one env fn arg_bndrs body (call_pat, rule_number)
-- And build the results
; let spec_body_ty = exprType spec_body
- (spec_lam_args1, spec_sig, spec_arity, spec_join_arity)
+ (spec_lam_args1, spec_sig, spec_arity1, spec_join_arity1)
= calcSpecInfo fn call_pat extra_bndrs
-- Annotate the variables with the strictness information from
-- the function (see Note [Strictness information in worker binders])
- (spec_lam_args, spec_call_args,_) = mkWorkerArgs fn False
- spec_lam_args1 []
- spec_body_ty
- -- mkWorkerArgs: usual w/w hack to avoid generating
- -- a spec_rhs of unlifted type and no args.
- -- Unlike W/W we don't turn functions into strict workers
- -- immediately here instead letting tidy handle this.
- -- For this reason we can ignore the cbv marks.
- -- See Note [Strict Worker Ids]. See Note [Tag Inference].
+ (spec_lam_args, spec_call_args, spec_arity, spec_join_arity)
+ | needsVoidWorkerArg fn arg_bndrs spec_lam_args1
+ , (spec_lam_args, spec_call_args, _) <- addVoidWorkerArg spec_lam_args1 []
+ -- needsVoidWorkerArg: usual w/w hack to avoid generating
+ -- a spec_rhs of unlifted type and no args.
+ -- Unlike W/W we don't turn functions into strict workers
+ -- immediately here instead letting tidy handle this.
+ -- For this reason we can ignore the cbv marks.
+ -- See Note [Strict Worker Ids]. See Note [Tag Inference].
+ , !spec_arity <- spec_arity1 + 1
+ , !spec_join_arity <- fmap (+ 1) spec_join_arity1
+ = (spec_lam_args, spec_call_args, spec_arity, spec_join_arity)
+ | otherwise
+ = (spec_lam_args1, spec_lam_args1, spec_arity1, spec_join_arity1)
spec_id = mkLocalId spec_name Many
(mkLamTypes spec_lam_args spec_body_ty)
diff --git a/compiler/GHC/Core/Opt/Specialise.hs b/compiler/GHC/Core/Opt/Specialise.hs
index cbf3a4e10e..d80e78f685 100644
--- a/compiler/GHC/Core/Opt/Specialise.hs
+++ b/compiler/GHC/Core/Opt/Specialise.hs
@@ -1519,7 +1519,7 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
-- Maybe add a void arg to the specialised function,
-- to avoid unlifted bindings
-- See Note [Specialisations Must Be Lifted]
- -- C.f. GHC.Core.Opt.WorkWrap.Utils.mkWorkerArgs
+ -- C.f. GHC.Core.Opt.WorkWrap.Utils.needsVoidWorkerArg
add_void_arg = isUnliftedType spec_fn_ty1 && not (isJoinId fn)
(spec_bndrs, spec_rhs, spec_fn_ty)
| add_void_arg = ( voidPrimId : spec_bndrs1
diff --git a/compiler/GHC/Core/Opt/WorkWrap.hs b/compiler/GHC/Core/Opt/WorkWrap.hs
index f07e8dde37..092fdbb7a7 100644
--- a/compiler/GHC/Core/Opt/WorkWrap.hs
+++ b/compiler/GHC/Core/Opt/WorkWrap.hs
@@ -900,18 +900,6 @@ the original function.
The demand on the worker is then calculated using mkWorkerDemand, and always of
the form [Demand=<L,1*(C1(...(C1(U))))>]
-
-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 GHC.Core.Opt.WorkWrap.Utils.mkWorerArgs.
-
-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.
-
Note [Thunk splitting]
~~~~~~~~~~~~~~~~~~~~~~
Suppose x is used strictly; never mind whether it has the CPR
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