Chapter 9
STG and CorePrep
Where laziness stops being an abstraction. Every thunk, every closure and every allocation becomes something you can point at in the syntax.
Where this lives in the tree
-
GHC/CoreToStg/Prep.hsCorePrep, which normalises Core before the translation -
GHC/CoreToStg.hsthe translation itself -
GHC/Stg/Syntax.hsthe STG language -
GHC/Stg/Unarise.hsunarisation, which flattens unboxed tuples and sums
Core is a lambda calculus: it says what a program means, not how it runs. let
in Core creates a binding; whether that costs an allocation is left open.
STG (the Spineless Tagless G-machine) closes that gap. It is still recognisably
functional, but every construct now corresponds to something the runtime does.
A let in STG is an allocation. An application is an entry into a closure.
Reading STG tells you what your program will actually do.
CorePrep first
Core cannot be translated directly, because it permits shapes the code generator
has no answer for: arbitrary expressions in argument position, unsaturated
constructor applications, complex nested lets. CorePrep normalises these away,
producing Core in A-normal form: every argument is a variable or literal, every
constructor and primop saturated.
It also does a few things that are genuinely semantic rather than cosmetic:
Since call-by-value is much cheaper than call-by-need, we case-bind arguments
that are either
1. Strictly evaluated anyway, according to the DmdSig of the callee, or
2. ok-for-spec, according to 'exprOkForSpeculation'.
This includes DFuns `$fEqList a`, for example.
(Could identify more in the future; see reference to !1866 below.)
While (1) is a no-brainer and always beneficial, (2) is a bit
more subtle, as the careful haddock for 'exprOkForSpeculation'
points out. Still, by case-binding the argument we don't need
to allocate a thunk for it, whose closure must be retained as
long as the callee might evaluate it. And if it is evaluated on
most code paths anyway, we get to turn the unknown eval in the
callee into a known call at the call site.
Very Nasty Wrinkle
We must be very careful not to speculate recursive calls! Doing so
might well change termination behavior.
That comes up in practice for DFuns, which are considered ok-for-spec,
because they always immediately return a constructor.
See Note [NON-BOTTOM-DICTS invariant] in GHC.Core. Show the rest of this Note (59 more lines)
But not so if you speculate the recursive call, as #20836 shows: class Foo m => Foo m where runFoo :: m a -> m a newtype Trans m a = Trans { runTrans :: m a } instance Monad m => Foo (Trans m) where runFoo = id (NB: class Foo m => Foo m` looks weird and needs -XUndecidableSuperClasses. The example in #20836 is more compelling, but boils down to the same thing.) This program compiles to the following DFun for the `Trans` instance: Rec { $fFooTrans = \ @m $dMonad -> C:Foo ($fFooTrans $dMonad) (\ @a -> id) end Rec } Note that the DFun immediately terminates and produces a dictionary, just like DFuns ought to, but it calls itself recursively to produce the `Foo m` dictionary. But alas, if we treat `$fFooTrans` as always-terminating, so that we can speculate its calls, and hence use call-by-value, we get: $fFooTrans = \ @m $dMonad -> case ($fFooTrans $dMonad) of sc -> C:Foo sc (\ @a -> id) and that's an infinite loop! Note that this bad-ness only happens in `$fFooTrans`'s own RHS. In the *body* of the letrec, it's absolutely fine to use call-by-value on `foo ($fFooTrans d)`. Our solution is this: we track in cpe_rec_ids the set of enclosing recursively-bound Ids, the RHSs of which we are currently transforming and then in 'exprOkForSpecEval' (a special entry point to 'exprOkForSpeculation', basically) we'll say that any binder in this set is not ok-for-spec. Note if we have a letrec group `Rec { f1 = rhs1; ...; fn = rhsn }`, and we prep up `rhs1`, we have to include not only `f1`, but all binders of the group `f1..fn` in this set, otherwise our fix is not robust wrt. mutual recursive DFuns. NB: If at some point we decide to have a termination analysis for general functions (#8655, !1866), we need to take similar precautions for (guarded) recursive functions: repeat x = x : repeat x Same problem here: As written, repeat evaluates rapidly to WHNF. So `repeat x` is a cheap call that we are willing to speculate, but *not* in repeat's RHS. Fortunately, pce_rec_ids already has all the information we need in that case. The problem is very similar to Note [Eta reduction in recursive RHSs]. Here as well as there it is *unsound* to change the termination properties of the very function whose termination properties we are exploiting. It is also similar to Note [Do not strictify a DFun's parameter dictionaries], where marking recursive DFuns (of undecidable *instances*) strict in dictionary *parameters* leads to quite the same change in termination as above.
Speculative evaluation is a small, real optimisation: if a thunk is cheap and certain to be demanded, build the value now rather than a thunk that will be forced immediately. Saving a thunk saves an allocation, a write, and a later indirection.
What STG makes explicit
Three things become visible that Core left implicit.
Closures and their free variables. An STG closure records exactly which variables it captures. The size of the heap object is right there.
Update flags. A thunk that is updated after evaluation, so it is not recomputed, is distinguished from one that is not. This is memoisation made syntactic.
Constructor saturation. Every constructor application has all its arguments, so the code generator knows the size of the heap object without any analysis.
Unarisation
Unboxed tuples and unboxed sums do not exist at runtime: an unboxed tuple is not a heap object, it is several values in registers. Unarisation flattens them away, rewriting binders and applications so that one binder of an unboxed tuple type becomes several binders of component types.
The invariants afterwards are strict, because everything downstream assumes them:
STG programs after unarisation have these invariants:
1. No unboxed sums at all.
2. No unboxed tuple binders. Tuples only appear in return position.
3. Binders and literals always have zero (for void arguments) or one PrimRep.
(i.e. typePrimRep1 won't crash; see Note [VoidRep] in GHC.Types.RepType.)
4. DataCon applications (StgRhsCon and StgConApp) don't have void arguments.
This means that it's safe to wrap `StgArg`s of DataCon applications with
`GHC.StgToCmm.Env.NonVoid`, for example.
5. Alt binders (binders in patterns) are always non-void. Pointer tagging
The “tagless” in Spineless Tagless G-machine is historical; modern GHC very much
does tag pointers. The low bits of a heap pointer, which are always zero from
alignment, carry which constructor the object is, so a case on an already-
evaluated value can branch on the pointer itself rather than dereferencing it and
reading an info table.
Getting this right requires knowing which pointers are guaranteed already evaluated and tagged, which is a property STG must track and preserve:
The goal of EnforceEPT pass is to mark as many binders as possible as EPT (see Note [Evaluated and Properly Tagged]). To find more EPT binders, it establishes the following EPT INVARIANT: > Any binder of > * a strict field (see Note [Strict fields in Core]), or > * a CBV argument (see Note [CBV Function Ids]) > is EPT. (Note that prior to EPT enforcement, this invariant may *not* always be upheld. An example can be found at the end of this Note.) This is all to optimise code such as the following: data SPair a b = SP !a !b case p :: SP Bool Bool of SP x y -> case x of True -> ... False -> ... We can infer that the strict field x is EPT and hence may safely omit the code to enter x and the check for the presence of a tag that goes along with it. However we still branch on the tag as usual to jump to the True or
Show the rest of this Note (108 more lines)
False case.
Note that for every example involving strict fields we could find a similar
example using CBV functions, e.g.
$wf x[EPT] y =
case x of
True -> ...
False -> ...
is the above example translated to use a CBV function $wf.
Note that /any/ strict function can in principle be chosen as a CBV function;
however, we presently only promote worker functions such as $wf to CBV because
we see all its call sites and can use the proper by-value calling convention.
More precisely, with -O0, we guarantee that no CBV functions are visible in
the interface file, so that naïve clients do not need to know how to call CBV
functions. See Note [CBV Function Ids] for more details.
Specification
EPT enforcement works like implicit type conversions in C, such as from int to
float, only much simpler (no overloaded operations such as +).
For EPT enforcement, the "type system" in question is whether a binder is
statically EPT. We differentiate "EPT binder" from "non-EPT binder", where the
latter means "might be EPT, but we could not prove it so".
In this sense, EPT binders form a subtype of non-EPT binders.
We differentiate two conversion directions:
* Downcast: EPT binders can be converted into non-EPT binders for free.
* Upcast: non-EPT binders can be converted into EPT binders by inserting an eval.
The EPT invariant expresses type signatures. In particular, these type
signatures entail two things:
* A _precondition_: Any binder that is passed as a CBV arg/strict field
must be EPT (i.e. must have type "EPT binder").
* A _postcondition_: Any binder of a CBV arg/strict field is EPT.
EPT enforcement is then simply a matter of figuring out where to insert
Upcasts (remember that Downcasts are free).
Since Upcasts (evals!) are not free, it is desirable to insert as few as possible.
To this end, we run a static *EPT analysis*, the purpose of which is to identify
as many EPT binders as possible.
Beyond discovering case binders and value bindings, EPT analysis exploits the
type signatures provided by the EPT invariant, looks inside returned tuples and
does some limited amount of fixpointing.
Afterwards, the *EPT rewriter* inserts the actual evals realising Upcasts.
Implementation
* EPT analysis is implemented in GHC.Stg.EnforceEpt.inferTags.
It attaches its result to /binders/, not occurrence sites.
* The EPT rewriter establishes the EPT invariant by inserting evals. That is, if
(a) a binder x is used to
* construct a strict field (`SP x y`), or
* passed as a CBV argument (`$wf x`),
and
(b) x was not inferred EPT,
then the EPT rewriter inserts an eval prior to the call, e.g.
case x of x' { __ DEFAULT -> SP x' y }.
case x of x' { __ DEFAULT -> $wf x' }.
(Recall that the case binder x' is always EPT.)
This is implemented in GHC.Stg.EnforceEpt.Rewrite.rewriteTopBinds.
This pass also propagates the EPTness from binders to occurrences.
It is sound to insert evals on strict fields (Note [Strict fields in Core]),
and on CBV arguments as well (Note [CBV Function Ids]).
* We also export the EPTness of top level bindings to allow this optimisation
to work across module boundaries.
NB: The EPT Invariant *must* be upheld, regardless of the optimisation level;
hence EPTness is practically part of the internal ABI of a strict data
constructor or CBV function. Note [CBV Function Ids] contains the details.
* Finally, code generation skips the thunk check when branching on binders that
are EPT. This is done by `cgExpr`/`cgCase` in the backend.
Evaluation
EPT enforcement can have large impact on spine-strict tree data structure
performance. For containers the reduction in runtimes with this optimization
was as follows:
intmap-benchmarks: 89.30%
intset-benchmarks: 90.87%
map-benchmarks: 88.00%
sequence-benchmarks: 99.84%
set-benchmarks: 85.00%
set-operations-intmap:88.64%
set-operations-map: 74.23%
set-operations-set: 76.50%
lookupge-intmap: 89.57%
lookupge-map: 70.95%
With nofib being ~0.3% faster as well.
Note that EPT enforcement may cause regressions in rare cases.
For example consider this code:
foo x = ...
let c = StrictJust x
in ...
When x cannot be inferred EPT, the rewriter transforms to
foo x = ...
let c = case x of x' -> StrictJust x'
in ...
which allocates an additional thunk for `c` that returns the constructor. Boo! Seeing it happen
What you wrote.
{-# LANGUAGE BangPatterns #-}
-- | What demand analysis and worker/wrapper are for.
--
-- `sumStrict` is a strict accumulator loop. Demand analysis proves the
-- accumulator is always forced, and worker/wrapper splits the function into a
-- wrapper with the original boxed type and a worker taking an unboxed `Int#`.
-- Look for `$wsumStrict` in the optimised Core, and note the loop allocates
-- nothing.
--
-- `sumLazy` is the same fold without the bang. Compare the two in STG: the lazy
-- version builds a thunk per iteration, and every `let` in STG is an allocation.
module Strict where
sumStrict :: [Int] -> Int
sumStrict = go 0
where
go !acc [] = acc
go !acc (x : xs) = go (acc + x) xs
sumLazy :: [Int] -> Int
sumLazy = go 0
where
go acc [] = acc
go acc (x : xs) = go (acc + x) xs
-- A strict data type: the bangs let GHC unpack the fields, so a Point is two
-- unboxed Ints in one heap object rather than two pointers to two boxes.
data Point = Point !Int !Int
shift :: Int -> Point -> Point
shift d (Point x y) = Point (x + d) (y + d)
-- Constructed product result: `minMax` returns a pair, and CPR analysis lets the
-- caller receive the components directly rather than allocating the tuple.
minMax :: [Int] -> (Int, Int)
minMax [] = (0, 0)
minMax (z : zs) = go z z zs
where
go !lo !hi [] = (lo, hi)
go !lo !hi (w : ws) = go (min lo w) (max hi w) wsThe same program after the simplifier has run.
Result size of Tidy Core
= {terms: 180, types: 132, coercions: 0, joins: 2/2}
-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
$trModule4 :: Addr#
$trModule4 = "main"#
-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
$trModule3 :: TrName
$trModule3 = TrNameS $trModule4
-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
$trModule2 :: Addr#
$trModule2 = "Strict"#
-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
$trModule1 :: TrName
$trModule1 = TrNameS $trModule2
-- RHS size: {terms: 3, types: 0, coercions: 0, joins: 0/0}
$trModule :: Module
$trModule = Module $trModule3 $trModule1
-- RHS size: {terms: 3, types: 1, coercions: 0, joins: 0/0}
$krep :: KindRep
$krep = KindRepTyConApp $tcInt []
-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
$tcPoint2 :: Addr#
$tcPoint2 = "Point"#
-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
$tcPoint1 :: TrName
$tcPoint1 = TrNameS $tcPoint2
-- RHS size: {terms: 7, types: 0, coercions: 0, joins: 0/0}
$tcPoint :: TyCon
$tcPoint
= TyCon
15270500999396957839#Word64
1310266649928625514#Word64
$trModule
$tcPoint1
0#
krep$*
-- RHS size: {terms: 3, types: 1, coercions: 0, joins: 0/0}
$krep1 :: KindRep
$krep1 = KindRepTyConApp $tcPoint []
-- RHS size: {terms: 3, types: 0, coercions: 0, joins: 0/0}
$krep2 :: KindRep
$krep2 = KindRepFun $krep $krep1
-- RHS size: {terms: 3, types: 0, coercions: 0, joins: 0/0}
$tc'Point1 :: KindRep
$tc'Point1 = KindRepFun $krep $krep2
-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
$tc'Point3 :: Addr#
$tc'Point3 = "'Point"#
-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
$tc'Point2 :: TrName
$tc'Point2 = TrNameS $tc'Point3
-- RHS size: {terms: 7, types: 0, coercions: 0, joins: 0/0}
$tc'Point :: TyCon
$tc'Point
= TyCon
209391528505640325#Word64
16890207377952019736#Word64
$trModule
$tc'Point2
0#
$tc'Point1
-- RHS size: {terms: 15, types: 7, coercions: 0, joins: 0/0}
shift :: Int -> Point -> Point
shift
= \ (d :: Int) (ds :: Point) ->
case ds of { Point bx bx1 ->
case d of { I# y -> Point (+# bx y) (+# bx1 y) }
}
Rec {
-- RHS size: {terms: 15, types: 10, coercions: 0, joins: 0/0}
$wgo1 :: Int# -> [Int] -> Int#
$wgo1
= \ (ww :: Int#) (ds :: [Int]) ->
case ds of {
[] -> ww;
: x xs -> case x of { I# y -> $wgo1 (+# ww y) xs }
}
end Rec }
-- RHS size: {terms: 8, types: 3, coercions: 0, joins: 0/0}
sumLazy :: [Int] -> Int
sumLazy
= \ (ds :: [Int]) -> case $wgo1 0# ds of ww { __DEFAULT -> I# ww }
-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
sumStrict :: [Int] -> Int
sumStrict = sumLazy
-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
minMax2 :: Int
minMax2 = I# 0#
-- RHS size: {terms: 3, types: 2, coercions: 0, joins: 0/0}
minMax1 :: (Int, Int)
minMax1 = (minMax2, minMax2)
Rec {
-- RHS size: {terms: 41, types: 31, coercions: 0, joins: 2/2}
$wgo :: Int# -> Int# -> [Int] -> (# Int#, Int# #)
$wgo
= \ (ww :: Int#) (ww1 :: Int#) (ds :: [Int]) ->
case ds of {
[] -> (# ww, ww1 #);
: w ws ->
case w of { I# y1 ->
join {
$j :: Int# -> (# Int#, Int# #)
$j (ww2 :: Int#)
= join {
$j1 :: Int# -> (# Int#, Int# #)
$j1 (ww3 :: Int#) = $wgo ww2 ww3 ws } in
case <=# ww1 y1 of {
__DEFAULT -> jump $j1 ww1;
1# -> jump $j1 y1
} } in
case <=# ww y1 of {
__DEFAULT -> jump $j y1;
1# -> jump $j ww
}
}
}
end Rec }
-- RHS size: {terms: 20, types: 17, coercions: 0, joins: 0/0}
minMax_go :: Int -> Int -> [Int] -> (Int, Int)
minMax_go
= \ (lo :: Int) (hi :: Int) (ds :: [Int]) ->
case lo of { I# ww ->
case hi of { I# ww1 ->
case $wgo ww ww1 ds of { (# ww2, ww3 #) -> (I# ww2, I# ww3) }
}
}
-- RHS size: {terms: 10, types: 7, coercions: 0, joins: 0/0}
minMax :: [Int] -> (Int, Int)
minMax
= \ (ds :: [Int]) ->
case ds of {
[] -> minMax1;
: z zs -> minMax_go z z zs
}Result size of Tidy Core
= {terms: 180, types: 132, coercions: 0, joins: 2/2}
-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
Strict.$trModule4 :: GHC.Internal.Prim.Addr#
[GblId,
Unf=Unf{Src=<vanilla>, TopLvl=True,
Value=True, ConLike=True, WorkFree=True, Expandable=True,
Guidance=IF_ARGS [] 20 0}]
Strict.$trModule4 = "main"#
-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
Strict.$trModule3 :: GHC.Internal.Types.TrName
[GblId,
Unf=Unf{Src=<vanilla>, TopLvl=True,
Value=True, ConLike=True, WorkFree=True, Expandable=True,
Guidance=IF_ARGS [] 10 10}]
Strict.$trModule3 = GHC.Internal.Types.TrNameS Strict.$trModule4
-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
Strict.$trModule2 :: GHC.Internal.Prim.Addr#
[GblId,
Unf=Unf{Src=<vanilla>, TopLvl=True,
Value=True, ConLike=True, WorkFree=True, Expandable=True,
Guidance=IF_ARGS [] 30 0}]
Strict.$trModule2 = "Strict"#
-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
Strict.$trModule1 :: GHC.Internal.Types.TrName
[GblId,
Unf=Unf{Src=<vanilla>, TopLvl=True,
Value=True, ConLike=True, WorkFree=True, Expandable=True,
Guidance=IF_ARGS [] 10 10}]
Strict.$trModule1 = GHC.Internal.Types.TrNameS Strict.$trModule2
-- RHS size: {terms: 3, types: 0, coercions: 0, joins: 0/0}
Strict.$trModule :: GHC.Internal.Types.Module
[GblId,
Unf=Unf{Src=<vanilla>, TopLvl=True,
Value=True, ConLike=True, WorkFree=True, Expandable=True,
Guidance=IF_ARGS [] 10 10}]
Strict.$trModule
= GHC.Internal.Types.Module Strict.$trModule3 Strict.$trModule1
-- RHS size: {terms: 3, types: 1, coercions: 0, joins: 0/0}
$krep :: GHC.Internal.Types.KindRep
[GblId, Unf=OtherCon []]
$krep
= GHC.Internal.Types.KindRepTyConApp
GHC.Internal.Types.$tcInt
(GHC.Internal.Types.[] @GHC.Internal.Types.KindRep)
-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
Strict.$tcPoint2 :: GHC.Internal.Prim.Addr#
[GblId,
Unf=Unf{Src=<vanilla>, TopLvl=True,
Value=True, ConLike=True, WorkFree=True, Expandable=True,
Guidance=IF_ARGS [] 30 0}]
Strict.$tcPoint2 = "Point"#
-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
Strict.$tcPoint1 :: GHC.Internal.Types.TrName
[GblId,
Unf=Unf{Src=<vanilla>, TopLvl=True,
Value=True, ConLike=True, WorkFree=True, Expandable=True,
Guidance=IF_ARGS [] 10 10}]
Strict.$tcPoint1 = GHC.Internal.Types.TrNameS Strict.$tcPoint2
-- RHS size: {terms: 7, types: 0, coercions: 0, joins: 0/0}
Strict.$tcPoint :: GHC.Internal.Types.TyCon
[GblId,
Unf=Unf{Src=<vanilla>, TopLvl=True,
Value=True, ConLike=True, WorkFree=True, Expandable=True,
Guidance=IF_ARGS [] 10 10}]
Strict.$tcPoint
= GHC.Internal.Types.TyCon
15270500999396957839#Word64
1310266649928625514#Word64
Strict.$trModule
Strict.$tcPoint1
0#
GHC.Internal.Types.krep$*
-- RHS size: {terms: 3, types: 1, coercions: 0, joins: 0/0}
$krep1 :: GHC.Internal.Types.KindRep
[GblId, Unf=OtherCon []]
$krep1
= GHC.Internal.Types.KindRepTyConApp
Strict.$tcPoint (GHC.Internal.Types.[] @GHC.Internal.Types.KindRep)
-- RHS size: {terms: 3, types: 0, coercions: 0, joins: 0/0}
$krep2 :: GHC.Internal.Types.KindRep
[GblId, Unf=OtherCon []]
$krep2 = GHC.Internal.Types.KindRepFun $krep $krep1
-- RHS size: {terms: 3, types: 0, coercions: 0, joins: 0/0}
Strict.$tc'Point1 [InlPrag=[~]] :: GHC.Internal.Types.KindRep
[GblId, Unf=OtherCon []]
Strict.$tc'Point1 = GHC.Internal.Types.KindRepFun $krep $krep2
-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
Strict.$tc'Point3 :: GHC.Internal.Prim.Addr#
[GblId,
Unf=Unf{Src=<vanilla>, TopLvl=True,
Value=True, ConLike=True, WorkFree=True, Expandable=True,
Guidance=IF_ARGS [] 30 0}]
Strict.$tc'Point3 = "'Point"#
-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
Strict.$tc'Point2 :: GHC.Internal.Types.TrName
[GblId,
Unf=Unf{Src=<vanilla>, TopLvl=True,
Value=True, ConLike=True, WorkFree=True, Expandable=True,
Guidance=IF_ARGS [] 10 10}]
Strict.$tc'Point2 = GHC.Internal.Types.TrNameS Strict.$tc'Point3
-- RHS size: {terms: 7, types: 0, coercions: 0, joins: 0/0}
Strict.$tc'Point :: GHC.Internal.Types.TyCon
[GblId,
Unf=Unf{Src=<vanilla>, TopLvl=True,
Value=True, ConLike=True, WorkFree=True, Expandable=True,
Guidance=IF_ARGS [] 10 10}]
Strict.$tc'Point
= GHC.Internal.Types.TyCon
209391528505640325#Word64
16890207377952019736#Word64
Strict.$trModule
Strict.$tc'Point2
0#
Strict.$tc'Point1
-- RHS size: {terms: 15, types: 7, coercions: 0, joins: 0/0}
shift :: Int -> Point -> Point
[GblId,
Arity=2,
Str=<1!P(L)><1!P(L,L)>,
Cpr=1,
Unf=Unf{Src=StableSystem, TopLvl=True,
Value=True, ConLike=True, WorkFree=True, Expandable=True,
Guidance=ALWAYS_IF(arity=2,unsat_ok=True,boring_ok=False)
Tmpl= \ (d [Occ=Once1!] :: Int) (ds [Occ=Once1!] :: Point) ->
case ds of { Point bx [Occ=Once1] bx1 [Occ=Once1] ->
case d of { GHC.Internal.Types.I# y ->
Strict.Point
(GHC.Internal.Prim.+# bx y) (GHC.Internal.Prim.+# bx1 y)
}
}}]
shift
= \ (d :: Int) (ds :: Point) ->
case ds of { Point bx bx1 ->
case d of { GHC.Internal.Types.I# y ->
Strict.Point
(GHC.Internal.Prim.+# bx y) (GHC.Internal.Prim.+# bx1 y)
}
}
Rec {
-- RHS size: {terms: 15, types: 10, coercions: 0, joins: 0/0}
Strict.$wgo1 [InlPrag=[2], Occ=LoopBreaker]
:: GHC.Internal.Prim.Int# -> [Int] -> GHC.Internal.Prim.Int#
[GblId[StrictWorker([~, !])],
Arity=2,
Str=<L><1L>,
Unf=OtherCon []]
Strict.$wgo1
= \ (ww :: GHC.Internal.Prim.Int#) (ds :: [Int]) ->
case ds of {
[] -> ww;
: x xs ->
case x of { GHC.Internal.Types.I# y ->
Strict.$wgo1 (GHC.Internal.Prim.+# ww y) xs
}
}
end Rec }
-- RHS size: {terms: 8, types: 3, coercions: 0, joins: 0/0}
sumLazy :: [Int] -> Int
[GblId,
Arity=1,
Str=<1L>,
Cpr=1,
Unf=Unf{Src=<vanilla>, TopLvl=True,
Value=True, ConLike=True, WorkFree=True, Expandable=True,
Guidance=IF_ARGS [0] 50 10}]
sumLazy
= \ (ds :: [Int]) ->
case Strict.$wgo1 0# ds of ww { __DEFAULT ->
GHC.Internal.Types.I# ww
}
-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
sumStrict :: [Int] -> Int
[GblId,
Arity=1,
Str=<1L>,
Cpr=1,
Unf=Unf{Src=<vanilla>, TopLvl=True,
Value=True, ConLike=True, WorkFree=True, Expandable=True,
Guidance=ALWAYS_IF(arity=0,unsat_ok=True,boring_ok=True)}]
sumStrict = sumLazy
-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
Strict.minMax2 :: Int
[GblId,
Unf=Unf{Src=<vanilla>, TopLvl=True,
Value=True, ConLike=True, WorkFree=True, Expandable=True,
Guidance=IF_ARGS [] 10 10}]
Strict.minMax2 = GHC.Internal.Types.I# 0#
-- RHS size: {terms: 3, types: 2, coercions: 0, joins: 0/0}
Strict.minMax1 :: (Int, Int)
[GblId,
Unf=Unf{Src=<vanilla>, TopLvl=True,
Value=True, ConLike=True, WorkFree=True, Expandable=True,
Guidance=IF_ARGS [] 10 10}]
Strict.minMax1 = (Strict.minMax2, Strict.minMax2)
Rec {
-- RHS size: {terms: 41, types: 31, coercions: 0, joins: 2/2}
Strict.$wgo [InlPrag=[2], Occ=LoopBreaker]
:: GHC.Internal.Prim.Int#
-> GHC.Internal.Prim.Int#
-> [Int]
-> (# GHC.Internal.Prim.Int#, GHC.Internal.Prim.Int# #)
[GblId[StrictWorker([~, ~, !])],
Arity=3,
Str=<L><L><1L>,
Unf=OtherCon []]
Strict.$wgo
= \ (ww :: GHC.Internal.Prim.Int#)
(ww1 :: GHC.Internal.Prim.Int#)
(ds :: [Int]) ->
case ds of {
[] -> (# ww, ww1 #);
: w ws ->
case w of { GHC.Internal.Types.I# y1 ->
join {
$j [Dmd=1C(1,!P(L,L))]
:: GHC.Internal.Prim.Int#
-> (# GHC.Internal.Prim.Int#, GHC.Internal.Prim.Int# #)
[LclId[JoinId(1)(Nothing)], Arity=1, Str=<L>, Unf=OtherCon []]
$j (ww2 [OS=OneShot] :: GHC.Internal.Prim.Int#)
= join {
$j1 [Dmd=1C(1,!P(L,L))]
:: GHC.Internal.Prim.Int#
-> (# GHC.Internal.Prim.Int#, GHC.Internal.Prim.Int# #)
[LclId[JoinId(1)(Nothing)], Arity=1, Str=<L>, Unf=OtherCon []]
$j1 (ww3 [OS=OneShot] :: GHC.Internal.Prim.Int#)
= Strict.$wgo ww2 ww3 ws } in
case GHC.Internal.Prim.<=# ww1 y1 of {
__DEFAULT -> jump $j1 ww1;
1# -> jump $j1 y1
} } in
case GHC.Internal.Prim.<=# ww y1 of {
__DEFAULT -> jump $j y1;
1# -> jump $j ww
}
}
}
end Rec }
-- RHS size: {terms: 20, types: 17, coercions: 0, joins: 0/0}
Strict.minMax_go [InlPrag=[2]] :: Int -> Int -> [Int] -> (Int, Int)
[GblId[StrictWorker([!, !, !])],
Arity=3,
Str=<1!P(L)><1!P(L)><1L>,
Cpr=1(1, 1),
Unf=Unf{Src=StableSystem, TopLvl=True,
Value=True, ConLike=True, WorkFree=True, Expandable=True,
Guidance=ALWAYS_IF(arity=3,unsat_ok=True,boring_ok=False)
Tmpl= \ (lo [Occ=Once1!] :: Int)
(hi [Occ=Once1!] :: Int)
(ds [Occ=Once1] :: [Int]) ->
case lo of { GHC.Internal.Types.I# ww [Occ=Once1] ->
case hi of { GHC.Internal.Types.I# ww1 [Occ=Once1] ->
case Strict.$wgo ww ww1 ds of
{ (# ww2 [Occ=Once1], ww3 [Occ=Once1] #) ->
(GHC.Internal.Types.I# ww2, GHC.Internal.Types.I# ww3)
}
}
}}]
Strict.minMax_go
= \ (lo :: Int)
(hi [OS=OneShot] :: Int)
(ds [OS=OneShot] :: [Int]) ->
case lo of { GHC.Internal.Types.I# ww ->
case hi of { GHC.Internal.Types.I# ww1 ->
case Strict.$wgo ww ww1 ds of { (# ww2, ww3 #) ->
(GHC.Internal.Types.I# ww2, GHC.Internal.Types.I# ww3)
}
}
}
-- RHS size: {terms: 10, types: 7, coercions: 0, joins: 0/0}
minMax :: [Int] -> (Int, Int)
[GblId,
Arity=1,
Str=<1L>,
Cpr=1(1, 1),
Unf=Unf{Src=StableSystem, TopLvl=True,
Value=True, ConLike=True, WorkFree=True, Expandable=True,
Guidance=ALWAYS_IF(arity=1,unsat_ok=True,boring_ok=False)
Tmpl= \ (ds [Occ=Once1!] :: [Int]) ->
case ds of {
[] -> Strict.minMax1;
: z zs [Occ=Once1] -> Strict.minMax_go z z zs
}}]
minMax
= \ (ds :: [Int]) ->
case ds of {
[] -> Strict.minMax1;
: z zs -> Strict.minMax_go z z zs
}Allocation and evaluation made explicit, ready for code generation.
$tc'Point3 :: Addr# = "'Point"#;
$tcPoint2 :: Addr# = "Point"#;
$trModule2 :: Addr# = "Strict"#;
$trModule4 :: Addr# = "main"#;
$WPoint :: Int %1 -> Int %1 -> Point =
{} \r [conrep conrep]
case conrep of conrep {
I# unbx -> case conrep of conrep { I# unbx -> Point [unbx unbx]; };
};
Point :: Int# %1 -> Int# %1 -> Point =
{} \r [eta eta] Point [eta eta];
$trModule3 :: TrName = TrNameS! [$trModule4];
$trModule1 :: TrName = TrNameS! [$trModule2];
$trModule :: Module = Module! [$trModule3 $trModule1];
$krep :: KindRep = KindRepTyConApp! [$tcInt []];
$tcPoint1 :: TrName = TrNameS! [$tcPoint2];
$tcPoint :: TyCon =
TyCon! [15270500999396957839#Word64
1310266649928625514#Word64
$trModule
$tcPoint1
0#
krep$*];
$krep1 :: KindRep = KindRepTyConApp! [$tcPoint []];
$krep2 :: KindRep = KindRepFun! [$krep $krep1];
$tc'Point1 :: KindRep = KindRepFun! [$krep $krep2];
$tc'Point2 :: TrName = TrNameS! [$tc'Point3];
$tc'Point :: TyCon =
TyCon! [209391528505640325#Word64
16890207377952019736#Word64
$trModule
$tc'Point2
0#
$tc'Point1];
shift :: Int -> Point -> Point =
{} \r [d ds]
case ds of wild {
Point bx bx1 ->
case d of wild1 {
I# y ->
case +# [bx1 y] of shift_sat {
__DEFAULT ->
case +# [bx y] of shift_sat {
__DEFAULT -> Point [shift_sat shift_sat];
};
};
};
};
Rec {
$wgo1 :: Int# -> [Int] -> Int# =
{} \r [ww ds]
case ds<TagProper> of wild {
[] -> ww<TagProper>;
: x xs ->
case x of wild1 {
I# y ->
case +# [ww y] of $wgo1_sat {
__DEFAULT -> case xs of xs { __DEFAULT -> $wgo1 $wgo1_sat xs; };
};
};
};
end Rec }
sumLazy :: [Int] -> Int =
{} \r [ds]
case case ds of ds { __DEFAULT -> $wgo1 0# ds; } of ww {
__DEFAULT -> I# [ww];
};
sumStrict :: [Int] -> Int = {} \r [eta] sumLazy eta;
minMax2 :: Int = I#! [0#];
minMax1 :: (Int, Int) = (,)! [minMax2 minMax2];
Rec {
$wgo :: Int# -> Int# -> [Int] -> (# Int#, Int# #) =
{} \r [ww ww1 ds]
case ds<TagProper> of wild {
[] -> (#,#) [ww ww1];
: w ws ->
case w of wild1 {
I# y1 ->
let-no-escape {
$j :: Int# -> (# Int#, Int# #) =
{y1, ww1, ws} \j [ww2]
let-no-escape {
$j1 :: Int# -> (# Int#, Int# #) =
{ww2, ws} \j [ww3] case ws of ws { __DEFAULT -> $wgo ww2 ww3 ws; };
} in
case <=# [ww1 y1] of lwild {
__DEFAULT -> $j1 ww1;
1# -> $j1 y1;
};
} in
case <=# [ww y1] of lwild {
__DEFAULT -> $j y1;
1# -> $j ww;
};
};
};
end Rec }
minMax_go :: Int -> Int -> [Int] -> (Int, Int) =
{} \r [lo hi ds]
case lo<TagProper> of wild {
I# ww ->
case hi<TagProper> of wild1 {
I# ww1 ->
case $wgo ww ww1 ds of wild2 {
(#,#) ww2 ww3 ->
let { minMax_go_sat :: Int = I#! [ww3]; } in
let { minMax_go_sat :: Int = I#! [ww2];
} in (,) [minMax_go_sat minMax_go_sat];
};
};
};
minMax :: [Int] -> (Int, Int) =
{} \r [ds]
case ds of wild {
[] -> minMax1<TagProper>;
: z zs ->
case z of z {
__DEFAULT ->
case z of z {
__DEFAULT -> case zs of zs { __DEFAULT -> minMax_go z z zs; };
};
};
};Strict.$tc'Point3 :: GHC.Internal.Prim.Addr#
[GblId, Unf=OtherCon []] =
"'Point"#;
Strict.$tcPoint2 :: GHC.Internal.Prim.Addr#
[GblId, Unf=OtherCon []] =
"Point"#;
Strict.$trModule2 :: GHC.Internal.Prim.Addr#
[GblId, Unf=OtherCon []] =
"Strict"#;
Strict.$trModule4 :: GHC.Internal.Prim.Addr#
[GblId, Unf=OtherCon []] =
"main"#;
Strict.$WPoint [InlPrag=INLINE[final] CONLIKE]
:: GHC.Internal.Types.Int
%1 -> GHC.Internal.Types.Int %1 -> Strict.Point
[GblId[DataConWrapper],
Arity=2,
Caf=NoCafRefs,
Str=<SL><SL>,
Unf=OtherCon []] =
{} \r [conrep conrep]
case conrep of conrep {
GHC.Internal.Types.I# unbx [Occ=Once1] ->
case conrep of conrep {
GHC.Internal.Types.I# unbx [Occ=Once1] -> Strict.Point [unbx unbx];
};
};
Strict.Point [InlPrag=CONLIKE]
:: GHC.Internal.Prim.Int#
%1 -> GHC.Internal.Prim.Int# %1 -> Strict.Point
[GblId[DataCon],
Arity=2,
Caf=NoCafRefs,
Str=<L><L>,
Unf=OtherCon []] =
{} \r [eta eta] Strict.Point [eta eta];
Strict.$trModule3 :: GHC.Internal.Types.TrName
[GblId, Unf=OtherCon []] =
GHC.Internal.Types.TrNameS! [Strict.$trModule4];
Strict.$trModule1 :: GHC.Internal.Types.TrName
[GblId, Unf=OtherCon []] =
GHC.Internal.Types.TrNameS! [Strict.$trModule2];
Strict.$trModule :: GHC.Internal.Types.Module
[GblId, Unf=OtherCon []] =
GHC.Internal.Types.Module! [Strict.$trModule3 Strict.$trModule1];
$krep :: GHC.Internal.Types.KindRep
[GblId, Unf=OtherCon []] =
GHC.Internal.Types.KindRepTyConApp! [GHC.Internal.Types.$tcInt
GHC.Internal.Types.[]];
Strict.$tcPoint1 :: GHC.Internal.Types.TrName
[GblId, Unf=OtherCon []] =
GHC.Internal.Types.TrNameS! [Strict.$tcPoint2];
Strict.$tcPoint :: GHC.Internal.Types.TyCon
[GblId, Unf=OtherCon []] =
GHC.Internal.Types.TyCon! [15270500999396957839#Word64
1310266649928625514#Word64
Strict.$trModule
Strict.$tcPoint1
0#
GHC.Internal.Types.krep$*];
$krep1 :: GHC.Internal.Types.KindRep
[GblId, Unf=OtherCon []] =
GHC.Internal.Types.KindRepTyConApp! [Strict.$tcPoint
GHC.Internal.Types.[]];
$krep2 :: GHC.Internal.Types.KindRep
[GblId, Unf=OtherCon []] =
GHC.Internal.Types.KindRepFun! [$krep $krep1];
Strict.$tc'Point1 [InlPrag=[~]] :: GHC.Internal.Types.KindRep
[GblId, Unf=OtherCon []] =
GHC.Internal.Types.KindRepFun! [$krep $krep2];
Strict.$tc'Point2 :: GHC.Internal.Types.TrName
[GblId, Unf=OtherCon []] =
GHC.Internal.Types.TrNameS! [Strict.$tc'Point3];
Strict.$tc'Point :: GHC.Internal.Types.TyCon
[GblId, Unf=OtherCon []] =
GHC.Internal.Types.TyCon! [209391528505640325#Word64
16890207377952019736#Word64
Strict.$trModule
Strict.$tc'Point2
0#
Strict.$tc'Point1];
Strict.shift
:: GHC.Internal.Types.Int -> Strict.Point -> Strict.Point
[GblId, Arity=2, Str=<1!P(L)><1!P(L,L)>, Cpr=1, Unf=OtherCon []] =
{} \r [d ds]
case ds of wild {
Strict.Point bx [Occ=Once1] bx1 [Occ=Once1] ->
case d of wild1 {
GHC.Internal.Types.I# y ->
case +# [bx1 y] of shift_sat {
__DEFAULT ->
case +# [bx y] of shift_sat {
__DEFAULT -> Strict.Point [shift_sat shift_sat];
};
};
};
};
Rec {
Strict.$wgo1 [InlPrag=[2], Occ=LoopBreaker]
:: GHC.Internal.Prim.Int#
-> [GHC.Internal.Types.Int] -> GHC.Internal.Prim.Int#
[GblId[StrictWorker([~, !])],
Arity=2,
Str=<L><1L>,
Unf=OtherCon []] =
{} \r [ww ds]
case ds<TagProper> of wild {
[] -> ww<TagProper>;
: x [Occ=Once1!] xs [Occ=Once1] ->
case x of wild1 {
GHC.Internal.Types.I# y [Occ=Once1] ->
case +# [ww y] of $wgo1_sat {
__DEFAULT ->
case xs of xs { __DEFAULT -> Strict.$wgo1 $wgo1_sat xs; };
};
};
};
end Rec }
Strict.sumLazy
:: [GHC.Internal.Types.Int] -> GHC.Internal.Types.Int
[GblId, Arity=1, Str=<1L>, Cpr=1, Unf=OtherCon []] =
{} \r [ds]
case case ds of ds { __DEFAULT -> Strict.$wgo1 0# ds; } of ww {
__DEFAULT -> GHC.Internal.Types.I# [ww];
};
Strict.sumStrict
:: [GHC.Internal.Types.Int] -> GHC.Internal.Types.Int
[GblId, Arity=1, Str=<1L>, Cpr=1, Unf=OtherCon []] =
{} \r [eta] Strict.sumLazy eta;
Strict.minMax2 :: GHC.Internal.Types.Int
[GblId, Unf=OtherCon []] =
GHC.Internal.Types.I#! [0#];
Strict.minMax1 :: (GHC.Internal.Types.Int, GHC.Internal.Types.Int)
[GblId, Unf=OtherCon []] =
(,)! [Strict.minMax2 Strict.minMax2];
Rec {
Strict.$wgo [InlPrag=[2], Occ=LoopBreaker]
:: GHC.Internal.Prim.Int#
-> GHC.Internal.Prim.Int#
-> [GHC.Internal.Types.Int]
-> (# GHC.Internal.Prim.Int#, GHC.Internal.Prim.Int# #)
[GblId[StrictWorker([~, ~, !])],
Arity=3,
Str=<L><L><1L>,
Unf=OtherCon []] =
{} \r [ww ww1 ds]
case ds<TagProper> of wild {
[] -> (#,#) [ww ww1];
: w [Occ=Once1!] ws [Occ=Once1] ->
case w of wild1 {
GHC.Internal.Types.I# y1 ->
let-no-escape {
$j [Occ=Once2!T[1], Dmd=1C(1,!P(L,L))]
:: GHC.Internal.Prim.Int#
-> (# GHC.Internal.Prim.Int#, GHC.Internal.Prim.Int# #)
[LclId[JoinId(1)(Nothing)], Arity=1, Str=<L>, Unf=OtherCon []] =
{y1, ww1, ws} \j [ww2]
let-no-escape {
$j1 [Occ=Once2!T[1], Dmd=1C(1,!P(L,L))]
:: GHC.Internal.Prim.Int#
-> (# GHC.Internal.Prim.Int#, GHC.Internal.Prim.Int# #)
[LclId[JoinId(1)(Nothing)], Arity=1, Str=<L>, Unf=OtherCon []] =
{ww2, ws} \j [ww3]
case ws of ws { __DEFAULT -> Strict.$wgo ww2 ww3 ws; };
} in
case <=# [ww1 y1] of lwild {
__DEFAULT -> $j1 ww1;
1# -> $j1 y1;
};
} in
case <=# [ww y1] of lwild {
__DEFAULT -> $j y1;
1# -> $j ww;
};
};
};
end Rec }
Strict.minMax_go [InlPrag=[2]]
:: GHC.Internal.Types.Int
-> GHC.Internal.Types.Int
-> [GHC.Internal.Types.Int]
-> (GHC.Internal.Types.Int, GHC.Internal.Types.Int)
[GblId[StrictWorker([!, !, !])],
Arity=3,
Str=<1!P(L)><1!P(L)><1L>,
Cpr=1(1, 1),
Unf=OtherCon []] =
{} \r [lo hi ds]
case lo<TagProper> of wild {
GHC.Internal.Types.I# ww [Occ=Once1] ->
case hi<TagProper> of wild1 {
GHC.Internal.Types.I# ww1 [Occ=Once1] ->
case Strict.$wgo ww ww1 ds of wild2 {
(#,#) ww2 [Occ=Once1] ww3 [Occ=Once1] ->
let {
minMax_go_sat [Occ=Once1] :: GHC.Internal.Types.Int
[LclId, Unf=OtherCon []] =
GHC.Internal.Types.I#! [ww3]; } in
let {
minMax_go_sat [Occ=Once1] :: GHC.Internal.Types.Int
[LclId, Unf=OtherCon []] =
GHC.Internal.Types.I#! [ww2];
} in (,) [minMax_go_sat minMax_go_sat];
};
};
};
Strict.minMax
:: [GHC.Internal.Types.Int]
-> (GHC.Internal.Types.Int, GHC.Internal.Types.Int)
[GblId, Arity=1, Str=<1L>, Cpr=1(1, 1), Unf=OtherCon []] =
{} \r [ds]
case ds of wild {
[] -> Strict.minMax1<TagProper>;
: z zs [Occ=Once1] ->
case z of z {
__DEFAULT ->
case z of z {
__DEFAULT ->
case zs of zs { __DEFAULT -> Strict.minMax_go z z zs; };
};
};
};This is the most practical skill the dump offers. let in STG means allocate a
closure, so counting lets inside a loop tells you what it costs per
iteration, no profiling required.
Compare sumStrict with sumLazy here. The strict version’s loop runs on
unboxed values with nothing allocated per iteration. The lazy one accumulates a
chain of thunks, and each one is a let you can see.
Point, with its strict fields, shows the other half: the unpacked
representation means shift builds one heap object rather than one plus two
boxed Ints.
Reading the source yourself
GHC/Stg/Syntax.hs: small, and the closest thing to a description of GHC’s execution model in the compiler itself.GHC/CoreToStg/Prep.hsfor the normalisation, which is where most of the fiddly cases live.GHC/Stg/Unarise.hswhen you first meet an unboxed tuple in anger.- The STG paper (Simon Peyton Jones, Implementing lazy functional
languages on stock hardware) remains the best explanation of why the
machine is shaped this way.
docs/stg-spec/in the tree has the formal version.
-ddump-stg-final shows the result. It is the single most useful dump for
answering “why is this allocating?”, because in STG the allocations are exactly
the lets.