diff options
Diffstat (limited to 'compiler/GHC/Core/Opt/WorkWrap/Utils.hs')
-rw-r--r-- | compiler/GHC/Core/Opt/WorkWrap/Utils.hs | 207 |
1 files changed, 105 insertions, 102 deletions
diff --git a/compiler/GHC/Core/Opt/WorkWrap/Utils.hs b/compiler/GHC/Core/Opt/WorkWrap/Utils.hs index 1f5cd29a26..f23384d134 100644 --- a/compiler/GHC/Core/Opt/WorkWrap/Utils.hs +++ b/compiler/GHC/Core/Opt/WorkWrap/Utils.hs @@ -11,7 +11,7 @@ module GHC.Core.Opt.WorkWrap.Utils ( WwOpts(..), mkWwBodies, mkWWstr, mkWWstr_one , needsVoidWorkerArg, addVoidWorkerArg , DataConPatContext(..) - , UnboxingDecision(..), wantToUnboxArg + , UnboxingDecision(..), canUnboxArg , findTypeShape, IsRecDataConResult(..), isRecDataCon , mkAbsentFiller , isWorkerSmallEnough, dubiousDataConInstArgTys @@ -221,18 +221,18 @@ mkWwBodies opts fun_id arg_vars res_ty demands res_cpr zapped_arg_vars = map zap_var arg_vars (subst, cloned_arg_vars) = cloneBndrs empty_subst uniq_supply zapped_arg_vars res_ty' = GHC.Core.Subst.substTy subst res_ty - init_cbv_marks = map (const NotMarkedStrict) cloned_arg_vars + init_str_marks = map (const NotMarkedStrict) cloned_arg_vars - ; (useful1, work_args_cbv, wrap_fn_str, fn_args) - <- mkWWstr opts cloned_arg_vars init_cbv_marks + ; (useful1, work_args_str, wrap_fn_str, fn_args) + <- mkWWstr opts cloned_arg_vars init_str_marks - ; let (work_args, work_marks) = unzip work_args_cbv + ; let (work_args, work_marks) = unzip work_args_str -- Do CPR w/w. See Note [Always do CPR w/w] ; (useful2, wrap_fn_cpr, work_fn_cpr) <- mkWWcpr_entry opts res_ty' res_cpr - ; let (work_lam_args, work_call_args, work_call_cbv) + ; let (work_lam_args, work_call_args, work_call_str) | needsVoidWorkerArg fun_id arg_vars work_args = addVoidWorkerArg work_args work_marks | otherwise @@ -243,9 +243,9 @@ mkWwBodies opts fun_id arg_vars res_ty demands res_cpr -- See Note [Join points and beta-redexes] wrapper_body = mkLams cloned_arg_vars . wrap_fn_cpr . wrap_fn_str . call_work -- See Note [Call-by-value for worker args] - work_seq_str_flds = mkStrictFieldSeqs (zip work_lam_args work_call_cbv) + work_seq_str_flds = mkStrictFieldSeqs (zip work_lam_args work_call_str) worker_body = mkLams work_lam_args . work_seq_str_flds . work_fn_cpr . call_rhs - worker_args_dmds= [(idDemandInfo v) | v <- work_call_args, isId v] + worker_args_dmds= [ idDemandInfo v | v <- work_call_args, isId v] ; if ((useful1 && not only_one_void_argument) || useful2) then return (Just (worker_args_dmds, length work_call_args, @@ -394,9 +394,9 @@ needsVoidWorkerArg fn_id wrap_args work_args addVoidWorkerArg :: [Var] -> [StrictnessMark] -> ([Var], -- Lambda bound args [Var], -- Args at call site - [StrictnessMark]) -- cbv semantics for the worker args. -addVoidWorkerArg work_args cbv_marks - = (voidArgId : work_args, voidPrimId:work_args, NotMarkedStrict:cbv_marks) + [StrictnessMark]) -- str semantics for the worker args. +addVoidWorkerArg work_args str_marks + = (voidArgId : work_args, voidPrimId:work_args, NotMarkedStrict:str_marks) {- Note [Protecting the last value argument] @@ -554,29 +554,26 @@ see #17478. -- -- * @dc @exs flds :: T tys@ -- * @co :: T tys ~ ty@ -data DataConPatContext +-- +-- 's' will be 'Demand' or 'Cpr'. +data DataConPatContext s = DataConPatContext { dcpc_dc :: !DataCon , dcpc_tc_args :: ![Type] , dcpc_co :: !Coercion + , dcpc_args :: ![s] } -- | Describes the outer shape of an argument to be unboxed or left as-is -- Depending on how @s@ is instantiated (e.g., 'Demand' or 'Cpr'). -data UnboxingDecision s +data UnboxingDecision unboxing_info = StopUnboxing -- ^ We ran out of strictness info. Leave untouched. | DropAbsent -- ^ The argument/field was absent. Drop it. - | Unbox !DataConPatContext [s] - -- ^ The argument is used strictly or the returned product was constructed, so - -- unbox it. - -- The 'DataConPatContext' carries the bits necessary for - -- instantiation with 'dataConRepInstPat'. - -- The @[s]@ carries the bits of information with which we can continue - -- unboxing, e.g. @s@ will be 'Demand' or 'Cpr'. - | Unlift - -- ^ The argument can't be unboxed, but we want it to be passed evaluated to the worker. + | Unbox !unboxing_info + -- ^ The argument is used strictly or the returned product + -- was constructed, so unbox it. -- Do we want to create workers just for unlifting? wwForUnlifting :: WwOpts -> Bool @@ -599,41 +596,34 @@ isGoodWorker = id -- | Unwraps the 'Boxity' decision encoded in the given 'SubDemand' and returns -- a 'DataConPatContext' as well the nested demands on fields of the 'DataCon' -- to unbox. -wantToUnboxArg - :: Bool -- ^ Consider unlifting - -> FamInstEnvs - -> Type -- ^ Type of the argument - -> Demand -- ^ How the arg was used - -> UnboxingDecision Demand +canUnboxArg + :: FamInstEnvs + -> Type -- ^ Type of the argument + -> Demand -- ^ How the arg was used + -> UnboxingDecision (DataConPatContext Demand) -- See Note [Which types are unboxed?] -wantToUnboxArg do_unlifting fam_envs ty dmd@(n :* sd) +canUnboxArg fam_envs ty (n :* sd) | isAbs n = DropAbsent + -- From here we are strict and not absent | Just (tc, tc_args, co) <- normSplitTyConApp_maybe fam_envs ty , Just dc <- tyConSingleAlgDataCon_maybe tc , let arity = dataConRepArity dc - , Just (Unboxed, ds) <- viewProd arity sd -- See Note [Boxity analysis] - -- NB: No strictness or evaluatedness checks for unboxing here. - -- That is done by 'finaliseArgBoxities'! - = Unbox (DataConPatContext dc tc_args co) ds - - -- See Note [CBV Function Ids] - | do_unlifting - , isStrUsedDmd dmd - , not (isFunTy ty) - , not (isUnliftedType ty) -- Already unlifted! - -- NB: function arguments have a fixed RuntimeRep, so it's OK to call isUnliftedType here - = Unlift + , Just (Unboxed, dmds) <- viewProd arity sd -- See Note [Boxity analysis] + , dmds `lengthIs` dataConRepArity dc + = Unbox (DataConPatContext { dcpc_dc = dc, dcpc_tc_args = tc_args + , dcpc_co = co, dcpc_args = dmds }) | otherwise = StopUnboxing -- | Unboxing strategy for constructed results. -wantToUnboxResult :: FamInstEnvs -> Type -> Cpr -> UnboxingDecision Cpr +canUnboxResult :: FamInstEnvs -> Type -> Cpr + -> UnboxingDecision (DataConPatContext Cpr) -- See Note [Which types are unboxed?] -wantToUnboxResult fam_envs ty cpr +canUnboxResult fam_envs ty cpr | Just (con_tag, arg_cprs) <- asConCpr cpr , Just (tc, tc_args, co) <- normSplitTyConApp_maybe fam_envs ty , Just dcs <- tyConAlgDataCons_maybe tc <|> open_body_ty_warning @@ -648,14 +638,15 @@ wantToUnboxResult fam_envs ty cpr -- Deactivates CPR worker/wrapper splits on constructors with non-linear -- arguments, for the moment, because they require unboxed tuple with variable -- multiplicity fields. - = Unbox (DataConPatContext dc tc_args co) arg_cprs + = Unbox (DataConPatContext { dcpc_dc = dc, dcpc_tc_args = tc_args + , dcpc_co = co, dcpc_args = arg_cprs }) | otherwise = StopUnboxing where -- See Note [non-algebraic or open body type warning] - open_body_ty_warning = warnPprTrace True "wantToUnboxResult: non-algebraic or open body type" (ppr ty) Nothing + open_body_ty_warning = warnPprTrace True "canUnboxResult: non-algebraic or open body type" (ppr ty) Nothing isLinear :: Scaled a -> Bool isLinear (Scaled w _ ) = @@ -697,8 +688,8 @@ Worker/wrapper will unbox to > $wf x y = let ... in (# @ex, (a :: ..ex..), (b :: ..ex..) #) -The respective tests are in 'wantToUnboxArg' and -'wantToUnboxResult', respectively. +The respective tests are in 'canUnboxArg' and +'canUnboxResult', respectively. Note that the data constructor /can/ have evidence arguments: equality constraints, type classes etc. So it can be GADT. These evidence @@ -756,14 +747,17 @@ Performing W/W might not always be a win. In particular it's easy to break See #20364 for a more detailed explaination. Hence we have the following strategies with different trade-offs: + A) Never do W/W *just* for unlifting of arguments. + Very conservative - doesn't break any rules - Lot's of performance left on the table + B) Do W/W on just about anything where it might be beneficial. + Exploits pretty much every oppertunity for unlifting. - A bit of compile time/code size cost for all the wrappers. - Can break rules which would otherwise fire. See #20364. + C) Unlift *any* (non-boot exported) functions arguments if they are strict. That is instead of creating a Worker with the new calling convention we change the calling convention of the binding itself. @@ -784,11 +778,13 @@ C) Unlift *any* (non-boot exported) functions arguments if they are strict. Currently we use the first approach A) by default, with a flag that allows users to fall back to the more aggressive approach B). + I also tried the third approach C) using eta-expansion at call sites to avoid modifying the PAP-handling code which wasn't fruitful. See https://gitlab.haskell.org/ghc/ghc/-/merge_requests/5614#note_389903. We could still try to do C) in the future by having PAP calls which will evaluate the required arguments before calling the partially applied function. But this would be neither a small nor simple change so we stick with A) and a flag for B) for now. + See also Note [Tag Inference] and Note [CBV Function Ids] -} @@ -803,7 +799,7 @@ See also Note [Tag Inference] and Note [CBV Function Ids] mkWWstr :: WwOpts -> [Var] -- Wrapper args; have their demand info on them -- *Includes type variables* - -> [StrictnessMark] -- cbv info for arguments + -> [StrictnessMark] -- Strictness-mark info for arguments -> UniqSM (Bool, -- Will this result in a useful worker [(Var,StrictnessMark)], -- Worker args/their call-by-value semantics. CoreExpr -> CoreExpr, -- Wrapper body, lacking the worker call @@ -812,20 +808,18 @@ mkWWstr :: WwOpts [CoreExpr]) -- Reboxed args for the call to the -- original RHS. Corresponds one-to-one -- with the wrapper arg vars -mkWWstr opts args cbv_info - = go args cbv_info +mkWWstr opts args str_marks + = go args str_marks where - go_one arg cbv = mkWWstr_one opts arg cbv - - go [] _ = return (badWorker, [], nop_fn, []) - go (arg : args) (cbv:cbvs) - = do { (useful1, args1, wrap_fn1, wrap_arg) <- go_one arg cbv - ; (useful2, args2, wrap_fn2, wrap_args) <- go args cbvs - ; return ( useful1 || useful2 - , args1 ++ args2 - , wrap_fn1 . wrap_fn2 - , wrap_arg:wrap_args ) } - go _ _ = panic "mkWWstr: Impossible - cbv/arg length missmatch" + go [] _ = return (badWorker, [], nop_fn, []) + go (arg : args) (str:strs) + = do { (useful1, args1, wrap_fn1, wrap_arg) <- mkWWstr_one opts arg str + ; (useful2, args2, wrap_fn2, wrap_args) <- go args strs + ; return ( useful1 || useful2 + , args1 ++ args2 + , wrap_fn1 . wrap_fn2 + , wrap_arg:wrap_args ) } + go _ _ = panic "mkWWstr: Impossible - str/arg length missmatch" ---------------------- -- mkWWstr_one wrap_var = (useful, work_args, wrap_fn, wrap_arg) @@ -838,24 +832,28 @@ mkWWstr_one :: WwOpts -> Var -> StrictnessMark -> UniqSM (Bool, [(Var,StrictnessMark)], CoreExpr -> CoreExpr, CoreExpr) -mkWWstr_one opts arg banged = - case wantToUnboxArg True fam_envs arg_ty arg_dmd of +mkWWstr_one opts arg str_mark = + case canUnboxArg fam_envs arg_ty arg_dmd of _ | isTyVar arg -> do_nothing DropAbsent - | Just absent_filler <- mkAbsentFiller opts arg banged + | Just absent_filler <- mkAbsentFiller opts arg str_mark -- Absent case. Dropt the argument from the worker. -- We can't always handle absence for arbitrary -- unlifted types, so we need to choose just the cases we can -- (that's what mkAbsentFiller does) -> return (goodWorker, [], nop_fn, absent_filler) - Unbox dcpc ds -> unbox_one_arg opts arg ds dcpc banged + Unbox dcpc -> unbox_one_arg opts arg dcpc - Unlift -> return ( wwForUnlifting opts - , [(arg, MarkedStrict)] - , nop_fn - , varToCoreExpr arg) + _ | isStrictDmd arg_dmd || isMarkedStrict str_mark + , wwForUnlifting opts + , not (isFunTy arg_ty) + , not (isUnliftedType arg_ty) -- Already unlifted! + -- NB: function arguments have a fixed RuntimeRep, + -- so it's OK to call isUnliftedType here + -> -- See Note [CBV Function Ids] + return (goodWorker, [(arg, MarkedStrict)], nop_fn, varToCoreExpr arg ) _ -> do_nothing -- Other cases, like StopUnboxing @@ -863,37 +861,42 @@ mkWWstr_one opts arg banged = fam_envs = wo_fam_envs opts arg_ty = idType arg arg_dmd = idDemandInfo arg - -- Type args don't get cbv marks - arg_cbv = if isTyVar arg then NotMarkedStrict else banged - - do_nothing = return (badWorker, [(arg,arg_cbv)], nop_fn, varToCoreExpr arg) + arg_str | isTyVar arg = NotMarkedStrict -- Type args don't get stricness marks + | otherwise = str_mark + do_nothing = return (badWorker, [(arg,arg_str)], nop_fn, varToCoreExpr arg) unbox_one_arg :: WwOpts - -> Var - -> [Demand] - -> DataConPatContext - -> StrictnessMark - -> UniqSM (Bool, [(Var,StrictnessMark)], CoreExpr -> CoreExpr, CoreExpr) -unbox_one_arg opts arg_var ds - DataConPatContext { dcpc_dc = dc, dcpc_tc_args = tc_args - , dcpc_co = co } - _marked_cbv + -> Var-> DataConPatContext Demand + -> UniqSM (Bool, [(Var,StrictnessMark)], CoreExpr -> CoreExpr, CoreExpr) +unbox_one_arg opts arg_var + DataConPatContext { dcpc_dc = dc, dcpc_tc_args = tc_args + , dcpc_co = co, dcpc_args = ds } = do { pat_bndrs_uniqs <- getUniquesM ; let ex_name_fss = map getOccFS $ dataConExTyCoVars dc + -- Create new arguments we get when unboxing dc - (ex_tvs', arg_ids) = - dataConRepFSInstPat (ex_name_fss ++ repeat ww_prefix) pat_bndrs_uniqs (idMult arg_var) dc tc_args + (ex_tvs', arg_ids) = dataConRepFSInstPat (ex_name_fss ++ repeat ww_prefix) + pat_bndrs_uniqs (idMult arg_var) dc tc_args con_str_marks = dataConRepStrictness dc - -- Apply str info to new args. Also remove OtherCon unfoldings so they don't end up in lambda binders - -- of the worker. See Note [Never put `OtherCon` unfoldings on lambda binders] - arg_ids' = map zapIdUnfolding $ zipWithEqual "unbox_one_arg" setIdDemandInfo arg_ids ds + + -- Apply str info to new args. Also remove OtherCon unfoldings so they + -- don't end up in lambda binders of the worker. + -- See Note [Never put `OtherCon` unfoldings on lambda binders] + arg_ids' = map zapIdUnfolding $ + zipWithEqual "unbox_one_arg" setIdDemandInfo arg_ids ds + unbox_fn = mkUnpackCase (Var arg_var) co (idMult arg_var) dc (ex_tvs' ++ arg_ids') - -- Mark arguments coming out of strict fields so we can make the worker strict on those - -- argumnets later. seq them later. See Note [Call-by-value for worker args] - strict_marks = (map (const NotMarkedStrict) ex_tvs') ++ con_str_marks - ; (_sub_args_quality, worker_args, wrap_fn, wrap_args) <- mkWWstr opts (ex_tvs' ++ arg_ids') strict_marks + + -- Mark arguments coming out of strict fields so we can seq them in the worker + -- See Note [Call-by-value for worker args] + all_str_marks = (map (const NotMarkedStrict) ex_tvs') ++ con_str_marks + + ; (_sub_args_quality, worker_args, wrap_fn, wrap_args) + <- mkWWstr opts (ex_tvs' ++ arg_ids') all_str_marks + ; let wrap_arg = mkConApp dc (map Type tc_args ++ wrap_args) `mkCast` mkSymCo co + ; return (goodWorker, worker_args, unbox_fn . wrap_fn, wrap_arg) } -- Don't pass the arg, rebox instead @@ -937,7 +940,7 @@ mkAbsentFiller opts arg str {- Note [Worker/wrapper for Strictness and Absence] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The worker/wrapper transformation, mkWWstr_one, takes concrete action -based on the 'UnboxingDescision' returned by 'wantToUnboxArg'. +based on the 'UnboxingDecision' returned by 'canUnboxArg'. The latter takes into account several possibilities to decide if the function is worthy for splitting: @@ -951,7 +954,7 @@ function is worthy for splitting: 2. If the argument is evaluated strictly (or known to be eval'd), we can take a view into the product demand ('viewProd'). In accordance - with Note [Boxity analysis], 'wantToUnboxArg' will say 'Unbox'. + with Note [Boxity analysis], 'canUnboxArg' will say 'Unbox'. 'mkWWstr_one' then follows suit it and recurses into the fields of the product demand. For example @@ -973,7 +976,7 @@ function is worthy for splitting: $gw c a b = if c then a else b 2a But do /not/ unbox if Boxity Analysis said "Boxed". - In this case, 'wantToUnboxArg' returns 'StopUnboxing'. + In this case, 'canUnboxArg' returns 'StopUnboxing'. Otherwise we risk decomposing and reboxing a massive tuple which is barely used. Example: @@ -994,7 +997,7 @@ function is worthy for splitting: 3. In all other cases (e.g., lazy, used demand and not eval'd), 'finaliseArgBoxities' will have cleared the Boxity flag to 'Boxed' (see Note [Finalising boxity for demand signatures] in GHC.Core.Opt.DmdAnal) - and 'wantToUnboxArg' returns 'StopUnboxing' so that 'mkWWstr_one' + and 'canUnboxArg' returns 'StopUnboxing' so that 'mkWWstr_one' stops unboxing. Note [Worker/wrapper for bottoming functions] @@ -1374,7 +1377,7 @@ mkWWcpr _opts vars [] = -- hence stop WW. return (badWorker, toOL vars, map varToCoreExpr vars, nop_fn) mkWWcpr opts vars cprs = do - -- No existentials in 'vars'. 'wantToUnboxResult' should have checked that. + -- No existentials in 'vars'. 'canUnboxResult' should have checked that. massertPpr (not (any isTyVar vars)) (ppr vars $$ ppr cprs) massertPpr (equalLength vars cprs) (ppr vars $$ ppr cprs) (usefuls, varss, rebuilt_results, work_unpack_ress) <- @@ -1388,17 +1391,17 @@ mkWWcpr_one :: WwOpts -> Id -> Cpr -> UniqSM CprWwResultOne -- ^ See if we want to unbox the result and hand off to 'unbox_one_result'. mkWWcpr_one opts res_bndr cpr | assert (not (isTyVar res_bndr) ) True - , Unbox dcpc arg_cprs <- wantToUnboxResult (wo_fam_envs opts) (idType res_bndr) cpr - = unbox_one_result opts res_bndr arg_cprs dcpc + , Unbox dcpc <- canUnboxResult (wo_fam_envs opts) (idType res_bndr) cpr + = unbox_one_result opts res_bndr dcpc | otherwise = return (badWorker, unitOL res_bndr, varToCoreExpr res_bndr, nop_fn) unbox_one_result - :: WwOpts -> Id -> [Cpr] -> DataConPatContext -> UniqSM CprWwResultOne + :: WwOpts -> Id -> DataConPatContext Cpr -> UniqSM CprWwResultOne -- ^ Implements the main bits of part (2) of Note [Worker/wrapper for CPR] -unbox_one_result opts res_bndr arg_cprs +unbox_one_result opts res_bndr DataConPatContext { dcpc_dc = dc, dcpc_tc_args = tc_args - , dcpc_co = co } = do + , dcpc_co = co, dcpc_args = arg_cprs } = do -- unboxer (free in `res_bndr`): | builder (where <i> builds what was -- ( case res_bndr of (i, j) -> ) | bound to i) -- ( case i of I# a -> ) | @@ -1407,7 +1410,7 @@ unbox_one_result opts res_bndr arg_cprs pat_bndrs_uniqs <- getUniquesM let (_exs, arg_ids) = dataConRepFSInstPat (repeat ww_prefix) pat_bndrs_uniqs cprCaseBndrMult dc tc_args - massert (null _exs) -- Should have been caught by wantToUnboxResult + massert (null _exs) -- Should have been caught by canUnboxResult (nested_useful, transit_vars, con_args, work_unbox_res) <- mkWWcpr opts arg_ids arg_cprs |