Finish initial draft of runtime posts.
This commit is contained in:
parent
4b5e2f4454
commit
a834fd578e
31
code/compiler/07/examples/runtime1.c
Normal file
31
code/compiler/07/examples/runtime1.c
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#include "../runtime.h"
|
||||||
|
|
||||||
|
void f_add(struct stack* s) {
|
||||||
|
struct node_num* left = (struct node_num*) eval(stack_peek(s, 0));
|
||||||
|
struct node_num* right = (struct node_num*) eval(stack_peek(s, 1));
|
||||||
|
stack_push(s, (struct node_base*) alloc_num(left->value + right->value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void f_main(struct stack* s) {
|
||||||
|
// PushInt 320
|
||||||
|
stack_push(s, (struct node_base*) alloc_num(320));
|
||||||
|
|
||||||
|
// PushInt 6
|
||||||
|
stack_push(s, (struct node_base*) alloc_num(6));
|
||||||
|
|
||||||
|
// PushGlobal f_add (the function for +)
|
||||||
|
stack_push(s, (struct node_base*) alloc_global(f_add, 2));
|
||||||
|
|
||||||
|
struct node_base* left;
|
||||||
|
struct node_base* right;
|
||||||
|
|
||||||
|
// MkApp
|
||||||
|
left = stack_pop(s);
|
||||||
|
right = stack_pop(s);
|
||||||
|
stack_push(s, (struct node_base*) alloc_app(left, right));
|
||||||
|
|
||||||
|
// MkApp
|
||||||
|
left = stack_pop(s);
|
||||||
|
right = stack_pop(s);
|
||||||
|
stack_push(s, (struct node_base*) alloc_app(left, right));
|
||||||
|
}
|
|
@ -1,48 +1,7 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <memory.h>
|
||||||
struct stack;
|
#include "runtime.h"
|
||||||
|
|
||||||
enum node_tag {
|
|
||||||
NODE_APP,
|
|
||||||
NODE_NUM,
|
|
||||||
NODE_GLOBAL,
|
|
||||||
NODE_IND,
|
|
||||||
NODE_PACK
|
|
||||||
};
|
|
||||||
|
|
||||||
struct node_base {
|
|
||||||
enum node_tag tag;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct node_app {
|
|
||||||
struct node_base base;
|
|
||||||
struct node_base* left;
|
|
||||||
struct node_base* right;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct node_num {
|
|
||||||
struct node_base base;
|
|
||||||
int32_t value;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct node_global {
|
|
||||||
struct node_base base;
|
|
||||||
int32_t arity;
|
|
||||||
void (*function)(struct stack*);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct node_ind {
|
|
||||||
struct node_base base;
|
|
||||||
struct node_base* next;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct node_data {
|
|
||||||
struct node_base base;
|
|
||||||
int8_t tag;
|
|
||||||
struct node_base** array;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct node_base* alloc_node() {
|
struct node_base* alloc_node() {
|
||||||
struct node_base* new_node = malloc(sizeof(struct node_app));
|
struct node_base* new_node = malloc(sizeof(struct node_app));
|
||||||
|
@ -50,14 +9,38 @@ struct node_base* alloc_node() {
|
||||||
return new_node;
|
return new_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct stack {
|
struct node_app* alloc_app(struct node_base* l, struct node_base* r) {
|
||||||
size_t size;
|
struct node_app* node = (struct node_app*) alloc_node();
|
||||||
size_t count;
|
node->base.tag = NODE_APP;
|
||||||
struct node_base** data;
|
node->left = l;
|
||||||
};
|
node->right = r;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct node_num* alloc_num(int32_t n) {
|
||||||
|
struct node_num* node = (struct node_num*) alloc_node();
|
||||||
|
node->base.tag = NODE_NUM;
|
||||||
|
node->value = n;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct node_global* alloc_global(void (*f)(struct stack*), int32_t a) {
|
||||||
|
struct node_global* node = (struct node_global*) alloc_node();
|
||||||
|
node->base.tag = NODE_GLOBAL;
|
||||||
|
node->arity = a;
|
||||||
|
node->function = f;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct node_ind* alloc_ind(struct node_base* n) {
|
||||||
|
struct node_ind* node = (struct node_ind*) alloc_node();
|
||||||
|
node->base.tag = NODE_IND;
|
||||||
|
node->next = n;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
void stack_init(struct stack* s) {
|
void stack_init(struct stack* s) {
|
||||||
s->size = 0;
|
s->size = 4;
|
||||||
s->count = 0;
|
s->count = 0;
|
||||||
s->data = malloc(sizeof(*s->data) * s->size);
|
s->data = malloc(sizeof(*s->data) * s->size);
|
||||||
assert(s->data != NULL);
|
assert(s->data != NULL);
|
||||||
|
@ -77,7 +60,7 @@ void stack_push(struct stack* s, struct node_base* n) {
|
||||||
|
|
||||||
struct node_base* stack_pop(struct stack* s) {
|
struct node_base* stack_pop(struct stack* s) {
|
||||||
assert(s->count > 0);
|
assert(s->count > 0);
|
||||||
s->count--;
|
return s->data[--s->count];
|
||||||
}
|
}
|
||||||
|
|
||||||
struct node_base* stack_peek(struct stack* s, size_t o) {
|
struct node_base* stack_peek(struct stack* s, size_t o) {
|
||||||
|
@ -105,21 +88,73 @@ void stack_update(struct stack* s, size_t o) {
|
||||||
|
|
||||||
void stack_alloc(struct stack* s, size_t o) {
|
void stack_alloc(struct stack* s, size_t o) {
|
||||||
while(o--) {
|
while(o--) {
|
||||||
struct node_ind* new_node = (struct node_ind*) alloc_node();
|
stack_push(s, (struct node_base*) alloc_ind(NULL));
|
||||||
new_node->base.tag = NODE_IND;
|
|
||||||
new_node->next = NULL;
|
|
||||||
stack_push(s, (struct node_base*) new_node);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void eval(struct node_base* n);
|
void stack_pack(struct stack* s, size_t n, int8_t t) {
|
||||||
|
assert(s->count >= n);
|
||||||
|
|
||||||
|
struct node_base** data = malloc(sizeof(*data) * n);
|
||||||
|
assert(data != NULL);
|
||||||
|
memcpy(data, &s->data[s->count - 1 - n], n * sizeof(*data));
|
||||||
|
|
||||||
|
struct node_data* new_node = (struct node_data*) alloc_node();
|
||||||
|
new_node->array = data;
|
||||||
|
new_node->base.tag = NODE_DATA;
|
||||||
|
new_node->tag = t;
|
||||||
|
|
||||||
|
stack_popn(s, n);
|
||||||
|
stack_push(s, (struct node_base*) new_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stack_split(struct stack* s, size_t n) {
|
||||||
|
struct node_data* node = (struct node_data*) stack_pop(s);
|
||||||
|
for(size_t i = 0; i < n; i++) {
|
||||||
|
stack_push(s, node->array[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void unwind(struct stack* s) {
|
||||||
|
while(1) {
|
||||||
|
struct node_base* peek = stack_peek(s, 0);
|
||||||
|
if(peek->tag == NODE_APP) {
|
||||||
|
struct node_app* n = (struct node_app*) peek;
|
||||||
|
stack_push(s, n->left);
|
||||||
|
} else if(peek->tag == NODE_GLOBAL) {
|
||||||
|
struct node_global* n = (struct node_global*) peek;
|
||||||
|
assert(s->count > n->arity);
|
||||||
|
|
||||||
|
for(size_t i = 1; i <= n->arity; i++) {
|
||||||
|
s->data[s->count - i]
|
||||||
|
= ((struct node_app*) s->data[s->count - i - 1])->right;
|
||||||
|
}
|
||||||
|
|
||||||
|
n->function(s);
|
||||||
|
} else if(peek->tag == NODE_IND) {
|
||||||
|
struct node_ind* n = (struct node_ind*) peek;
|
||||||
|
stack_pop(s);
|
||||||
|
stack_push(s, n->next);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct node_base* eval(struct node_base* n) {
|
||||||
|
struct stack program_stack;
|
||||||
|
stack_init(&program_stack);
|
||||||
|
stack_push(&program_stack, n);
|
||||||
|
unwind(&program_stack);
|
||||||
|
struct node_base* result = stack_pop(&program_stack);
|
||||||
|
stack_free(&program_stack);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
extern void f_main(struct stack* s);
|
extern void f_main(struct stack* s);
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
struct node_global* first_node = (struct node_global*) alloc_node();
|
struct node_global* first_node = alloc_global(f_main, 0);
|
||||||
first_node->base.tag = NODE_GLOBAL;
|
struct node_base* result = eval((struct node_base*) first_node);
|
||||||
first_node->arity = 0;
|
printf("%d\n", ((struct node_num*) result)->value);
|
||||||
first_node->function = f_main;
|
|
||||||
eval((struct node_base*) first_node);
|
|
||||||
}
|
}
|
||||||
|
|
69
code/compiler/07/runtime.h
Normal file
69
code/compiler/07/runtime.h
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
#pragma once
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
struct stack;
|
||||||
|
|
||||||
|
enum node_tag {
|
||||||
|
NODE_APP,
|
||||||
|
NODE_NUM,
|
||||||
|
NODE_GLOBAL,
|
||||||
|
NODE_IND,
|
||||||
|
NODE_DATA
|
||||||
|
};
|
||||||
|
|
||||||
|
struct node_base {
|
||||||
|
enum node_tag tag;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct node_app {
|
||||||
|
struct node_base base;
|
||||||
|
struct node_base* left;
|
||||||
|
struct node_base* right;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct node_num {
|
||||||
|
struct node_base base;
|
||||||
|
int32_t value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct node_global {
|
||||||
|
struct node_base base;
|
||||||
|
int32_t arity;
|
||||||
|
void (*function)(struct stack*);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct node_ind {
|
||||||
|
struct node_base base;
|
||||||
|
struct node_base* next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct node_data {
|
||||||
|
struct node_base base;
|
||||||
|
int8_t tag;
|
||||||
|
struct node_base** array;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct node_base* alloc_node();
|
||||||
|
struct node_app* alloc_app(struct node_base* l, struct node_base* r);
|
||||||
|
struct node_num* alloc_num(int32_t n);
|
||||||
|
struct node_global* alloc_global(void (*f)(struct stack*), int32_t a);
|
||||||
|
struct node_ind* alloc_ind(struct node_base* n);
|
||||||
|
|
||||||
|
struct stack {
|
||||||
|
size_t size;
|
||||||
|
size_t count;
|
||||||
|
struct node_base** data;
|
||||||
|
};
|
||||||
|
|
||||||
|
void stack_free(struct stack* s);
|
||||||
|
void stack_push(struct stack* s, struct node_base* n);
|
||||||
|
struct node_base* stack_pop(struct stack* s);
|
||||||
|
struct node_base* stack_peek(struct stack* s, size_t o);
|
||||||
|
void stack_popn(struct stack* s, size_t n);
|
||||||
|
void stack_slide(struct stack* s, size_t n);
|
||||||
|
void stack_update(struct stack* s, size_t o);
|
||||||
|
void stack_alloc(struct stack* s, size_t o);
|
||||||
|
void stack_pack(struct stack* s, size_t n, int8_t t);
|
||||||
|
void stack_split(struct stack* s, size_t n);
|
||||||
|
|
||||||
|
struct node_base* eval(struct node_base* n);
|
|
@ -9,12 +9,12 @@ Wikipedia has the following definition for a __runtime__:
|
||||||
> A [runtime] primarily implements portions of an execution model.
|
> 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
|
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
|
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,
|
functional language will rely on such manipulation (it's how they run!). Furthermore,
|
||||||
most G-machine instructions are also above hardware level (especially unwind!).
|
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
|
Most computers aren't stack machines. We'll have to implement
|
||||||
our own stack, and whenever a graph-building function will want to modify
|
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:
|
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,
|
We can start working on an implementation of the runtime right now,
|
||||||
beginning with the nodes:
|
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
|
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
|
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.
|
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.
|
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,
|
* `stack_init` and `stack_free` - one allocates memory for the stack,
|
||||||
the other releases it.
|
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,
|
* `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).
|
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_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:
|
We declare these in a header:
|
||||||
{{< codelines "C++" "compiler/07/runtime.c" 53 113 >}}
|
{{< 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.
|
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
|
It has a member `function`, which is a __function pointer__ to a function
|
||||||
that takes a stack and returns void.
|
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.
|
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
|
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.
|
to produce the initial graph, and then let the unwind loop take care of the evaluation.
|
||||||
|
|
||||||
Thus, our program will initially look like this:
|
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.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user