Finish initial draft of runtime posts.
This commit is contained in:
@@ -9,12 +9,12 @@ Wikipedia has the following definition for a __runtime__:
|
||||
> A [runtime] primarily implements portions of an execution model.
|
||||
|
||||
We know what our execution model is! We talked about it in Part 5 - it's the
|
||||
lazy graph reduction we've been talking about. Creating and manipulating
|
||||
lazy graph reduction we've specified. Creating and manipulating
|
||||
graph nodes is slightly above hardware level, and all programs in our
|
||||
functional language will rely on such manipulation (it's how they run!). Furthermore,
|
||||
most G-machine instructions are also above hardware level (especially unwind!).
|
||||
|
||||
Push and Slide and other instructions are pretty complex instructions.
|
||||
Push and Slide and other instructions are pretty complex.
|
||||
Most computers aren't stack machines. We'll have to implement
|
||||
our own stack, and whenever a graph-building function will want to modify
|
||||
the stack, it will have to call library routines for our stack implementation:
|
||||
@@ -48,7 +48,7 @@ implementation of the G-machine compilation.
|
||||
We can start working on an implementation of the runtime right now,
|
||||
beginning with the nodes:
|
||||
|
||||
{{< codelines "C++" "compiler/07/runtime.c" 5 51 >}}
|
||||
{{< codelines "C++" "compiler/07/runtime.h" 4 50 >}}
|
||||
|
||||
We have a variety of different nodes that can be on the stack, but without
|
||||
the magic of C++'s `vtable` and RTTI, we have to take care of the bookkeeping
|
||||
@@ -67,7 +67,16 @@ expressions with the results of their evaluation), changing their type.
|
||||
We then want to be able to change a node without reallocating memory.
|
||||
Since the biggest node we have is `node_app`, that's the one we choose.
|
||||
|
||||
We now move on to implement some stack operations. Let's list them off:
|
||||
Finally, to make it easier to create nodes from our generated code,
|
||||
we add helper functions like `alloc_num`, which allocate a given
|
||||
node type, and set its tag and member fields appropriately. We
|
||||
don't include such a function for `node_data`, since this
|
||||
node will be created only in one possible way.
|
||||
|
||||
Here's the implementation:
|
||||
{{< codelines "C" "compiler/07/runtime.c" 6 40 >}}
|
||||
|
||||
We now move on to implement some stack operations. Let's list them:
|
||||
|
||||
* `stack_init` and `stack_free` - one allocates memory for the stack,
|
||||
the other releases it.
|
||||
@@ -79,11 +88,15 @@ next several nodes.
|
||||
* `stack_update` - turns the node at the offset into an indirection to the result,
|
||||
which we will use for lazy evaluation (modifying expressions with their reduced forms).
|
||||
* `stack_alloc` - allocate indirection nodes on the stack. We will use this later.
|
||||
* `stack_pack` and `stack_split` - Wrap and unwrap constructors on the stack.
|
||||
|
||||
Here's the implementation:
|
||||
{{< codelines "C++" "compiler/07/runtime.c" 53 113 >}}
|
||||
We declare these in a header:
|
||||
{{< codelines "C" "compiler/07/runtime.h" 52 67 >}}
|
||||
|
||||
Let's not talk about how this will connect to the code we generate. To get
|
||||
And implement them as follows:
|
||||
{{< codelines "C" "compiler/07/runtime.c" 42 116 >}}
|
||||
|
||||
Let's now talk about how this will connect to the code we generate. To get
|
||||
a quick example, consider the `node_global` struct that we have declared above.
|
||||
It has a member `function`, which is a __function pointer__ to a function
|
||||
that takes a stack and returns void.
|
||||
@@ -98,8 +111,51 @@ we express a compiled top-level function as a subroutine that takes a stack,
|
||||
and returns void. A global node holds in it the pointer to the function that it will call.
|
||||
|
||||
When our program will start, it will assume that there exists a top-level
|
||||
function `main` that takes 0 parameters. It will take that function, call it
|
||||
function `f_main` that takes 0 parameters. It will take that function, call it
|
||||
to produce the initial graph, and then let the unwind loop take care of the evaluation.
|
||||
|
||||
Thus, our program will initially look like this:
|
||||
{{< codelines "C++" "compiler/07/runtime.c" 117 125 >}}
|
||||
{{< codelines "C" "compiler/07/runtime.c" 154 159 >}}
|
||||
|
||||
As we said, we expect an externally-declared subroutine `f_main`. We construct
|
||||
a global node for `f_main` with arity 0, and then start the execution using a function `eval`.
|
||||
What's `eval`, though? It's the function that will take care of creating
|
||||
a new stack, and evaluating the node that is passed to it using
|
||||
our unwind loop. `eval` itself is pretty terse:
|
||||
|
||||
{{< codelines "C" "compiler/07/runtime.c" 144 152 >}}
|
||||
|
||||
We create a fresh program stack, start it off with whatever node
|
||||
we want to evaluate, and have `unwind` take care of the rest.
|
||||
|
||||
`unwind` is a direct implementation of the rules from Part 5:
|
||||
|
||||
{{< codelines "C" "compiler/07/runtime.c" 118 142 >}}
|
||||
|
||||
We can now come up with some simple programs. Let's try
|
||||
writing out, by hand, `main = { 320 + 6 }`. We end up with:
|
||||
|
||||
{{< codeblock "C" "compiler/07/examples/runtime1.c" >}}
|
||||
|
||||
If we add to the bottom of our `main` the following code:
|
||||
```C
|
||||
printf("%d\n", ((struct node_num*) result)->value);
|
||||
```
|
||||
|
||||
And compile and run our code:
|
||||
```
|
||||
gcc runtime.c examples/runtime1.c
|
||||
./a.out
|
||||
```
|
||||
|
||||
We get the output `326`, which is exactly correct!
|
||||
|
||||
We now have a common set of functions and declarations
|
||||
that serve to support the code we generate from our compiler.
|
||||
Although this time, we wrote out `f_main` by hand, we will soon
|
||||
use LLVM to generate code for `f_main` and more. Once we get
|
||||
that going, we be able to compile our code!
|
||||
|
||||
Next time, we will start work on converting our G-machine instructions
|
||||
into machine code. We will set up LLVM and get our very first
|
||||
fully functional compiled programs in Part 8 - LLVM.
|
||||
|
||||
Reference in New Issue
Block a user