Chapter 10
Cmm and code generation
The last target-independent form, and the three different back ends that take it the rest of the way, plus the garbage collector's stake in the layout of every stack frame.
Where this lives in the tree
-
GHC/StgToCmm.hsSTG to Cmm, where the execution model becomes concrete -
GHC/Cmm/Info/Build.hsinfo tables and SRTs -
GHC/CmmToAsm.hsthe native code generator -
GHC/CmmToLlvm.hsthe LLVM back end
Cmm is C-like: basic blocks, assignments, conditional jumps, calls, explicit loads and stores. It is where GHC stops being a functional-language compiler and becomes an ordinary one.
It is also the last form that does not know which machine it is for. Below Cmm the paths diverge (native code generator, LLVM, or bytecode for GHCi) and above it everything is shared.
What StgToCmm decides
The translation from STG makes the execution model concrete. Heap objects get layouts. Closures get entry code. Function calls get a calling convention:
On certain architectures, some registers are utilized for parameter passing in the C calling convention. For example, in x86-64 Linux convention, rdi, rsi, rdx and rcx (as well as r8 and r9) may be used for argument passing. These are registers R3-R6, which our generated code may also be using; as a result, it's necessary to save these values before doing a foreign call. This is done during initial code generation in callerSaveVolatileRegs in GHC.StgToCmm.Utils. However, one result of doing this is that the contents of these registers may mysteriously change if referenced inside the arguments. This is dangerous, so you'll need to disable inlining much in the same way is done in GHC.Cmm.Sink currently. We should fix this!
Stack and heap checks appear here too. Every function that allocates must first check there is room, and jump to the garbage collector if not. The checks are inserted at Cmm level, hoisted so one check covers a whole block’s worth of allocation.
Info tables
Every heap object and every stack frame is preceded by an info table: a static description of the object’s layout, saying which fields are pointers, what the entry code is, and what constructor or closure type it is.
The garbage collector needs this. Faced with an arbitrary heap object it must determine, precisely, which words are pointers to follow and which are raw data. An imprecise answer is not an option: GHC’s collector is exact, not conservative.
The same requirement applies to the stack, which is where SRTs come in:
Static Reference Tables (SRTs) are the mechanism by which the garbage collector can determine the live CAFs in the program. An SRT is a static table associated with a CAFfy closure which record which CAFfy objects are reachable from the closure's code. Representation ^^^^^^^^^^^^^^ +------+ | info | | | +-----+---+---+---+ | -------->|SRT_2| | | | | 0 | |------| +-----+-|-+-|-+---+ | | | | | code | | | | | v v An SRT is simply an object in the program's data segment. It has the same representation as a static constructor. There are 16 pre-compiled SRT info tables: stg_SRT_1_info, .. stg_SRT_16_info, representing SRT objects with 1-16 pointers, respectively. The entries of an SRT object point to static closures, which are either - FUN_STATIC, THUNK_STATIC or CONSTR - Another SRT (actually just a CONSTR) The final field of the SRT is the static link field, used by the garbage collector to chain together static closures that it visits and
Show the rest of this Note (320 more lines)
to determine whether a static closure has been visited or not. (see Note [STATIC_LINK fields]) By traversing the transitive closure of an SRT, the GC will reach all of the CAFs that are reachable from the code associated with this SRT. If we need to create an SRT with more than 16 entries, we build a chain of SRT objects with all but the last having 16 entries. +-----+---+- -+---+---+ |SRT16| | | | | | 0 | +-----+-|-+- -+-|-+---+ | | v v +----+---+---+---+ |SRT2| | | | | 0 | +----+-|-+-|-+---+ | | | | v v Referring to an SRT from the info table ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The following things have SRTs: - Static functions (FUN) - Static thunks (THUNK), ie. CAFs - Continuations (RET_SMALL, etc.) In each case, the info table points to the SRT, if there is one. - info->srt is 0 if there's no SRT - otherwise, there are three ways which we may encode the location of the SRT in the info table, described below. USE_SRT_POINTER Most general implementation. Can always be used, but other ways are more efficient. - info->srt is a pointer We encode an **absolute pointer** to the SRT in info->srt. e.g. for a FUN with an SRT: StgInfoTable +------+ info->layout.ptrs | ... | info->layout.nptrs | ... | info->srt | ------------> pointer to SRT object info->type | ... | |------| USE_SRT_OFFSET Requires: - tables-next-to-code enabled In this case we use the info->srt to encode whether or not there is an SRT and if so encode the offset to its location in info->f.srt_offset: - info->srt is a half-word - info->f.srt_offset is a 32-bit int - info->srt is 0 if there's no SRT, otherwise, - info->srt == 1 and info->f.srt_offset is a offset to the SRT, relative to the field address itself e.g. for a FUN with an SRT: StgFunInfoTable +------+ info->f.srt_offset | ------------> offset to SRT object StgInfoTable +------+ info->layout.ptrs | ... | info->layout.nptrs | ... | info->srt | 1 | info->type | ... | |------| USE_INLINE_SRT_FIELD Requires: - tables-next-to-code enabled - 64-bit architecture - small memory model We optimise the info table representation further. The offset to the SRT can be stored in 32 bits (all code lives within a 2GB region in x86_64's small memory model), so we can save a word in the info table by storing the srt_offset in the srt field, which is half a word. - info->srt is a half-word - info->srt is 0 if there's no SRT, otherwise: - info->srt is an offset from the info pointer to the SRT object StgInfoTable +------+ info->layout.ptrs | | info->layout.nptrs | | info->srt | ------------> offset to SRT object |------| EXAMPLE ^^^^^^^ f = \x. ... g ... where g = \y. ... h ... c1 ... h = \z. ... c2 ... c1 & c2 are CAFs g and h are local functions, but they have no static closures. When we generate code for f, we start with a CmmGroup of four CmmDecls: [ f_closure, f_entry, g_entry, h_entry ] we process each CmmDecl separately in cpsTop, giving us a list of CmmDecls. e.g. for f_entry, we might end up with [ f_entry, f1_ret, f2_proc ] where f1_ret is a return point, and f2_proc is a proc-point. We have a CAFSet for each of these CmmDecls, let's suppose they are [ f_entry{g_info}, f1_ret{g_info}, f2_proc{} ] [ g_entry{h_info, c1_closure} ] [ h_entry{c2_closure} ] Next, we make an SRT for each of these functions: f_srt : [g_info] g_srt : [h_info, c1_closure] h_srt : [c2_closure] Now, for g_info and h_info, we want to refer to the SRTs for g and h respectively, which we'll label g_srt and h_srt: f_srt : [g_srt] g_srt : [h_srt, c1_closure] h_srt : [c2_closure] Now, when an SRT has a single entry, we don't actually generate an SRT closure for it, instead we just replace references to it with its single element. So, since h_srt == c2_closure, we have f_srt : [g_srt] g_srt : [c2_closure, c1_closure] h_srt : [c2_closure] and the only SRT closure we generate is g_srt = SRT_2 [c2_closure, c1_closure] Algorithm ^^^^^^^^^ 0. let srtMap :: Map CAFfyLabel (Maybe SRTEntry) = {} Maps closures to their SRT entries (i.e. how they appear in a SRT payload) 1. Start with decls :: [CmmDecl]. This corresponds to an SCC of bindings in STG after code-generation. 2. CPS-convert each CmmDecl (GHC.Cmm.Pipeline.cpsTop), resulting in a list [CmmDecl]. There might be multiple CmmDecls in the result, due to proc-point splitting. 3. In cpsTop, *before* proc-point splitting, when we still have a single CmmDecl, we do cafAnal for procs: * cafAnal performs a backwards analysis on the code blocks * For each labelled block, the analysis produces a CAFSet (= Set CAFfyLabel), representing all the CAFfyLabels reachable from this label. * A label is added to the set if it refers to a FUN, THUNK, or RET, and its CafInfo /= NoCafRefs. (NB. all CafInfo for Ids in the current module should be initialised to MayHaveCafRefs) * The result is CAFEnv = LabelMap CAFSet (Why *before* proc-point splitting? Because the analysis needs to propagate information across branches, and proc-point splitting turns branches into CmmCalls to top-level CmmDecls. The analysis would fail to find all the references to CAFFY labels if we did it after proc-point splitting.) For static data, cafAnalData simply returns set of all labels that refer to a FUN, THUNK, and RET whose CafInfos /= NoCafRefs. 4. The result of cpsTop is (CAFEnv, [CmmDecl]) for procs and (CAFSet, CmmDecl) for static data. So after `mapM cpsTop decls` we have [Either (CAFEnv, [CmmDecl]) (CAFSet, CmmDecl)] 5. For procs concat the decls and union the CAFEnvs to get (CAFEnv, [CmmDecl]) 6. For static data generate a Map CLabel CAFSet (maps static data to their CAFSets) 7. Dependency-analyse the decls using CAFEnv and CAFSets, giving us SCC CAFfyLabel 8. For each SCC in dependency order - Let lbls :: [CAFfyLabel] be the non-recursive labels in this SCC - Apply CAFEnv to each label and concat the result :: [CAFfyLabel] - For each CAFfyLabel in the set apply srtMap (and ignore Nothing) to get srt :: [SRTEntry] - Make a label for this SRT, call it l - If the SRT is not empty (i.e. the group is CAFFY) add FUN_STATICs in the group to the SRT (see Note [Invalid optimisation: shortcutting]) - Add to srtMap: lbls -> if null srt then Nothing else Just l 9. At the end, update the IdInfo for every top-level binding x: if srtMap x == Nothing, then the binding is non-CAFFY, otherwise it is CAFFY. Optimisations ^^^^^^^^^^^^^ To reduce the code size overhead and the cost of traversing SRTs in the GC, we want to simplify SRTs where possible. We therefore apply the following optimisations. Each has a [keyword]; search for the keyword in the code below to see where the optimisation is implemented. 1. [Inline] we never create an SRT with a single entry, instead we point to the single entry directly from the info table. i.e. instead of +------+ | info | | | +-----+---+---+ | -------->|SRT_1| | | 0 | |------| +-----+-|-+---+ | | | | code | | | | v C we can point directly to the closure: +------+ | info | | | | -------->C |------| | | | code | | | Furthermore, the SRT for any code that refers to this info table can point directly to C. The exception to this is when we're doing dynamic linking. In that case, if the closure is not locally defined then we can't point to it directly from the info table, because this is the text section which cannot contain runtime relocations. In this case we skip this optimisation and generate the singleton SRT, because SRTs are in the data section and *can* have relocatable references. 2. [FUN] A static function closure can also be an SRT, we simply put the SRT entries as fields in the static closure. This makes a lot of sense: the static references are just like the free variables of the FUN closure. i.e. instead of f_closure: +-----+---+ | | | 0 | +- |--+---+ | +------+ | | info | f_srt: | | | +-----+---+---+---+ | | -------->|SRT_2| | | | + 0 | `----------->|------| +-----+-|-+-|-+---+ | | | | | code | | | | | v v We can generate: f_closure: +-----+---+---+---+ | | | | | | | 0 | +- |--+-|-+-|-+---+ | | | +------+ | v v | info | | | | | | 0 | `----------->|------| | | | code | | | (note: we can't do this for THUNKs, because the thunk gets overwritten when it is entered, so we wouldn't be able to share this SRT with other info tables that want to refer to it (see [Common] below). FUNs are immutable so don't have this problem.) 3. [Common] Identical SRTs can be commoned up. 4. [Filter] If an SRT A refers to an SRT B and a closure C, and B also refers to C (perhaps transitively), then we can omit the reference to C from A. Note that there are many other optimisations that we could do, but aren't implemented. In general, we could omit any reference from an SRT if everything reachable from it is also reachable from the other fields in the SRT. Our [Filter] optimisation is a special case of this. Another opportunity we don't exploit is this: A = {X,Y,Z} B = {Y,Z} C = {X,B} Here we could use C = {A} and therefore [Inline] C = A.
A Static Reference Table records which static (CAF) closures a piece of code can reach. Without them the collector could not know whether a top-level thunk is still live, and CAFs would either leak or be collected while reachable. Building SRTs is one of the more intricate things the Cmm pipeline does, because it is a reachability problem over the whole module.
Cmm-to-Cmm optimisation
Between generation and code emission there is a modest optimisation pipeline: control-flow simplification, common block elimination, sinking, and a proc-point analysis that decides which blocks need to be separately entered.
These are ordinary compiler optimisations. The interesting ones already happened in Core; by this point the wins are small and local.
Three back ends
The native code generator (GHC/CmmToAsm/) emits assembly directly, with
register allocation, either a fast linear-scan allocator or a graph-colouring
one. It is the default because it compiles quickly.
The LLVM back end (GHC/CmmToLlvm/) emits LLVM IR. It compiles more slowly
but can produce better code, particularly for numeric work, since LLVM’s
optimiser knows things about instruction selection that GHC’s does not.
The bytecode generator (GHC/StgToByteCode.hs) takes a different route
entirely, going from STG to bytecode for GHCi. It skips Cmm, because the
interpreter needs neither register allocation nor machine code.
Determinism matters across all of them. The same source must produce the same object file, or builds are not reproducible:
Object determinism means that GHC, for the same exact input, produces, deterministically, byte-for-byte identical objects (.o files, executables, libraries...) on separate multi-threaded runs. Deterministic objects are critical, for instance, for reproducible software packaging and distribution, or build systems with content-sensitive recompilation avoidance. The main cause of non-determinism in objects comes from the non-deterministic uniques leaking into the generated code. Apart from uniques previously affecting determinism both directly by showing up in symbol labels and indirectly, e.g. in the CLabel Ord instance, GHC already did a lot deterministically (modulo bugs) by the time we set out to achieve full object determinism: * The Simplifier is deterministic in the optimisations it applies (c.f. #25170) * Interface files are deterministic (which depends on the previous bullet) * The Cmm/NCG pipeline processes sections in a deterministic order, so the final object sections, closures, data, etc., are already always outputted in the same order for the same module.
Show the rest of this Note (24 more lines)
Beyond fixing small bugs in the above bullets and other smaller non-determinism leaks like the Ord instance of CLabels, we must ensure that/do the following to make GHC produce fully deterministic objects: * In STG -> Cmm, deterministically /rename/ all non-external uniques in the Cmm chunk, deterministically, before yielding. See Note [Renaming uniques deterministically] in GHC.Cmm.UniqueRenamer. This pass is necessary for object determinism but is currently guarded by -fobject-determinism. * Multiple Cmm passes work with non-deterministic @LabelMap@s -- that doesn't change since they are both important for performance and do not affect the determinism of the end result. As after the renaming pass the uniques are all produced deterministically, the orderings observable by the map are also going to be deterministic. In the brief period before a CmmGroup has been renamed, a list instead of LabelMap is used to preserve the ordering. See Note [DCmmGroup vs CmmGroup or: Deterministic Info Tables] in GHC.Cmm. * In the code generation pipeline from Cmm onwards, when new uniques need to be created for a given pass, use @UniqDSM@ instead of the previously used @UniqSM@. @UniqDSM@ supplies uniques iteratively, guaranteeing uniques produced by the backend are deterministic accross runs. See Note [Deterministic Uniques in the CG] in GHC.Types.Unique.DSM. Also, c.f. Note [Unique Determinism]
That is harder than it sounds, because the compiler is full of Uniques and maps
keyed by them, whose iteration order can depend on allocation history rather than
on the program.
Reading the source yourself
GHC/Cmm/Syntax-adjacent modules:GHC/Cmm.hsandGHC/Cmm/Node.hsfor the language itself.GHC/StgToCmm/, starting with the closure layout modules, since object layout is the thing that makes the rest legible.GHC/Cmm/Info/Build.hsfor info tables and SRTs. Read the SRT Note first.GHC/CmmToAsm/Reg/only if register allocation is what you came for; it is self-contained and does not require the rest.
-ddump-cmm and -ddump-asm show the output. For a first look, compile a
three-line module: the runtime’s calling convention means even trivial functions
produce more Cmm than you expect.