The G-Machine

Motivation

Our initial model, the Template Instantiator (TI) was a very straightforward solution to compilation, but its core design has a major Achilles’ heel, being that Compilation is interleaved with evaluation – The heap nodes for supercombinators hold uninstantiated expressions, i.e. raw ASTs straight from the parser. When a supercombinator is found on the stack during evaluation, the template expression is instantiated (compiled) on the spot.

\transrule
{ a_0 : a_1 : \ldots : a_n : s
& d
& h
\begin{bmatrix}
     a_0 : \mathtt{NSupercomb} \; [x_1,\ldots,x_n] \; e
\end{bmatrix}
& g
}
{ a_n : s
& d
& h'
& g
\\
& \SetCell[c=3]{c}
\text{where } h' = \mathtt{instantiateU} \; e \; a_n \; h \; g
}

The process of instantiating a supercombinator goes something like this

  1. Augment the environment with bindings to the arguments.

  2. Using the local augmented environment, instantiate the supercombinator body on the heap.

  3. Remove the nodes applying the supercombinator to its arguments from the stack.

  4. Push the address to the newly instantiated body onto the stack.

src/TI.hs
scStep :: Name -> [Name] -> Expr -> TiState -> TiState
scStep n as e (TiState s d h g sts) =
    TiState s' d h' g sts
    where
        s' = rootAddr : drop (length as + 1) s  -- 3., 4.
        h' = instantiateU e rootAddr h env      -- 2.
        rootAddr = s !! length as

        env = argBinds ++ g                     -- 1.
        argBinds = as `zip` argAddrs
        argAddrs = getArgs h s

Instantiating the supercombinator’s body in this way is the root of our Achilles’ heel. Traversing a tree structure is a very non-linear task unfit for an assembly target. The goal of our new G-Machine is to compile a linear sequence of instructions which instantiate the expression at execution.

Trees and Vines, in Theory

WIP.

Evaluation: Slurping Vines

WIP.

Laziness

WIP.

  • Instead of Slide (n+1); Unwind, do Update n; Pop n; Unwind

Compilation: Squashing Trees

WIP.

Notice that we do not keep a (local) environment at run-time. The environment only exists at compile-time to map local names to stack indices. When compiling a supercombinator, the arguments are enumerated from zero (the top of the stack), and passed to compileR as an environment.

src/GM.hs
-- type CompiledSC = (Name, Int, Code)

compileSc :: ScDef -> CompiledSC
compileSc (ScDef n as b) = (n, d, compileR env b)
    where
        env = as `zip` [0..]
        d = length as

Of course, variables being indexed relative to the top of the stack means that they will become inaccurate the moment we push or pop the stack a single time. The way around this is quite simple: simply offset the stack when w

src/GM.hs
compileC g (App f x) = compileC g x
                    <> compileC (argOffset 1 g) f
                    <> [MkAp]