diff options
-rw-r--r-- | compiler/GHC/StgToCmm.hs | 4 | ||||
-rw-r--r-- | compiler/GHC/StgToCmm/Closure.hs | 8 | ||||
-rw-r--r-- | compiler/GHC/StgToCmm/DataCon.hs | 110 |
3 files changed, 114 insertions, 8 deletions
diff --git a/compiler/GHC/StgToCmm.hs b/compiler/GHC/StgToCmm.hs index e33844aa67..6536b261dd 100644 --- a/compiler/GHC/StgToCmm.hs +++ b/compiler/GHC/StgToCmm.hs @@ -239,9 +239,9 @@ cgEnumerationTyCon tycon | con <- tyConDataCons tycon] +-- | Generate the entry code and associated info table for a constructor. +-- Where are generating the static closure at all? cgDataCon :: ConInfoTableLocation -> DataCon -> FCode () --- Generate the entry code, info tables, and (for niladic constructor) --- the static closure, for a constructor. cgDataCon mn data_con = do { massert (not (isUnboxedTupleDataCon data_con || isUnboxedSumDataCon data_con)) ; profile <- getProfile diff --git a/compiler/GHC/StgToCmm/Closure.hs b/compiler/GHC/StgToCmm/Closure.hs index fc76664d94..56cd91596e 100644 --- a/compiler/GHC/StgToCmm/Closure.hs +++ b/compiler/GHC/StgToCmm/Closure.hs @@ -22,7 +22,7 @@ module GHC.StgToCmm.Closure ( argPrimRep, NonVoid(..), fromNonVoid, nonVoidIds, nonVoidStgArgs, - assertNonVoidIds, assertNonVoidStgArgs, + assertNonVoidIds, assertNonVoidStgArgs, hasNoNonZeroWidthArgs, -- * LambdaFormInfo LambdaFormInfo, -- Abstract @@ -170,6 +170,12 @@ assertNonVoidStgArgs :: [StgArg] -> [NonVoid StgArg] assertNonVoidStgArgs args = assert (not (any (isZeroBitTy . stgArgType) args)) $ coerce args +-- | Returns whether there are any arguments with a non-zero-width runtime +-- representation. +-- +-- Returns True if the datacon has no or /just/ zero-width arguments. +hasNoNonZeroWidthArgs :: DataCon -> Bool +hasNoNonZeroWidthArgs = all (isZeroBitTy . scaledThing) . dataConRepArgTys ----------------------------------------------------------------------------- -- Representations diff --git a/compiler/GHC/StgToCmm/DataCon.hs b/compiler/GHC/StgToCmm/DataCon.hs index f9402efd37..8ada996495 100644 --- a/compiler/GHC/StgToCmm/DataCon.hs +++ b/compiler/GHC/StgToCmm/DataCon.hs @@ -41,6 +41,7 @@ import GHC.Data.FastString import GHC.Types.Id import GHC.Types.Id.Info( CafInfo( NoCafRefs ) ) import GHC.Types.Name (isInternalName) +import GHC.Types.Var (varName) import GHC.Types.RepType (countConRepArgs) import GHC.Types.Literal import GHC.Builtin.Utils @@ -246,7 +247,8 @@ But also at runtime where the GC does the same (but only for INT/CHAR closures). `precomputedStaticConInfo_maybe` checks if a given constructor application -can be replaced with a reference to a existing static closure. +can be replaced with a reference to a existing static closure, according +to the Note [Precomputed static closures of nullary constructors] If so the code will reference the existing closure when accessing the binding. @@ -317,6 +319,103 @@ This holds for both local and top level bindings. We don't support this optimization when compiling into Windows DLLs yet because they don't support cross package data references well. + +Note [Precomputed static closures of nullary constructors] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +We can easily create a precomputed static closure for all data constructors +that don't take runtime-relevant arguments since their closure is always just +the constructor info. + +Instead of allocating a closure with just the constructor info every time it is +used, we can instead use the precomputed static closure! + +For example, to return from a function the constructor `Nothing`, instead of +allocating on the heap a word for `Nothing_con_info` and returning the pointer +to it tagged `+1`, we can simply return `Nothing_closure+1` + +We must consider three distinct situations of saturated applications of +constructors that take no runtime-relevant arguments in which we can use a +precomputed static closure: + +(1) For a data con /worker/ `TCon1` application to no arguments whatsoever we +can trivially use the static closure of the worker, `TCon1_closure`. + Recall that for a worker such as `TCon1`, `TCon1_closure` is just the + `TCon1_con_info`: + section ""data" . M.TCon1_closure" { + M.TCon1_closure: + const M.TCon1_con_info; + } + Invariant: These workers don't have wrappers. + +(2) For a data con /wrapper/ `$WTCon2` that takes no arguments whatsoever, we +can also trivially return the static closure of the wrapper, `$WTCon2_closure`. +It might be surprising to see a nullary data con /wrapper/ -- they come into +existence when the worker only takes zero-width arguments. See the example below. + As in (1), `$WTCon2_closure` simply points to a `TCon2_con_info`. + section ""data" . M.$WTCon2_closure" { + M.$WTCon2_closure: + const M.TCon2_con_info; + } + +(3) For a data con /worker/ `TCon2` that takes zero-width arguments only (and +whose wrapper is `$WTCon2`): because the arguments aren't relevant at runtime, +closures for it still only have the constructor info -- we can use a +precomputed static closure instead of allocating them on the heap, nonetheless. + However, unlike the worker in (1), `TCon2`, in taking arguments (regardless +of runtime representation), is unambiguously a function! Therefore, its +`TCon2_closure` actually contains the info of the function (`TCon2_info`) that returns the +constructor when called -- and as so it must remain -- if `TCon2` is ever used as +a function instead of in a saturated data con application, it better be one. + To generate in place of a saturated data con application of `TCon2`, we would + need something close to: + section ""data" . M.TCon2_some_sort_of_closure" { + M.TCon2_some_sort_of_closure: + const M.TCon2_con_info; -- Must be TCon2_con_info rather than TCon2_info which we have in TCon2_closure + } + But this turns out to be exactly the definition of this worker's wrapper's + static closure (see `$WTCon2_closure`). So, for the kind of worker in (3), + the precomputed static closure is the same as the one for the wrapper. + Invariant: These workers always have a wrapper of type (2) + +The solution that handles all of these cases turns out to be surprisingly +simple: A data con applied to an empty list of non-void arguments has a +precomputed static closure which is the tagged closure label of the var name of +the `dataConWrapId`, both for workers and wrappers. + For (1), `dataConWrapId` will return the Id of the worker because the wrapper + doesn't exist (i.e. `Wrk_closure+tag`). + For (2), `dataConWrapId` will return the Id of the wrapper for the wrapper (i.e. `$Wrp_closure+tag`). + For (3), `dataConWrapId` will return the Id of the wrapper, which must exist (i.e. `$Wrp_closure+tag`). + +As an example, since (2) and (3) might be hard to visualise, consider the datatype: + + data TCon2 a where + TCon2 :: TCon2 () + +and its STG representation post-unarisation: + + G.$WTCon2 :: G.TCon2 () + = G.TCon2! []; + + G.TCon2 :: forall {a}. (a GHC.Prim.~# ()) => G.TCon2 a + = {} \r [void_0E] G.TCon2 []; + +and the C--: + + section ""data" . G.$WTCon2_closure" { + G.$WTCon2_closure: + const G.TCon2_con_info; -- Static constructor info + } + + section ""data" . G.TCon2_closure" { + G.TCon2_closure: + const G.TCon2_info; -- Static function info + } + +The precomputed static closure for `$WTCon2` is `$WTCon2_closure+1`, and the +precomputed static closure for `TCon2` is also `$WTCon2_closure+1`; that is, +all saturated data con applications of `TCon2` and `$WTCon2` are compiled to +`$WTCon2_closure+1` instead of an allocation on the heap and +tagging of its pointer. -} -- (precomputedStaticConInfo_maybe cfg id con args) @@ -326,10 +425,11 @@ because they don't support cross package data references well. -- See Note [Precomputed static closures] precomputedStaticConInfo_maybe :: StgToCmmConfig -> Id -> DataCon -> [NonVoid StgArg] -> Maybe CgIdInfo precomputedStaticConInfo_maybe cfg binder con [] --- Nullary constructors - | isNullaryRepDataCon con - = Just $ litIdInfo (stgToCmmPlatform cfg) binder (mkConLFInfo con) - (CmmLabel (mkClosureLabel (dataConName con) NoCafRefs)) + -- Nullary constructors (list of nonvoid args is null) + -- See Note [Precomputed static closures of nullary constructors] + = assert (hasNoNonZeroWidthArgs con) $ + Just $ litIdInfo (stgToCmmPlatform cfg) binder (mkConLFInfo con) + (CmmLabel (mkClosureLabel (varName $ dataConWrapId con) NoCafRefs)) precomputedStaticConInfo_maybe cfg binder con [arg] -- Int/Char values with existing closures in the RTS | intClosure || charClosure |