From 4b5e2f4454f323dbd46042c2b4e9b70ed0ae0585 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Wed, 30 Oct 2019 00:19:56 -0700 Subject: [PATCH] Write some more about runetime --- code/compiler/07/runtime.c | 77 ++++++++++++++++++++++++++++- content/blog/07_compiler_runtime.md | 39 ++++++++++++++- 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/code/compiler/07/runtime.c b/code/compiler/07/runtime.c index e125338..27f3848 100644 --- a/code/compiler/07/runtime.c +++ b/code/compiler/07/runtime.c @@ -29,6 +29,7 @@ struct node_num { struct node_global { struct node_base base; + int32_t arity; void (*function)(struct stack*); }; @@ -44,7 +45,81 @@ struct node_data { }; struct node_base* alloc_node() { - node_base* new_node = malloc(sizeof(struct node_app)); + struct node_base* new_node = malloc(sizeof(struct node_app)); assert(new_node != NULL); return new_node; } + +struct stack { + size_t size; + size_t count; + struct node_base** data; +}; + +void stack_init(struct stack* s) { + s->size = 0; + s->count = 0; + s->data = malloc(sizeof(*s->data) * s->size); + assert(s->data != NULL); +} + +void stack_free(struct stack* s) { + free(s->data); +} + +void stack_push(struct stack* s, struct node_base* n) { + while(s->count >= s->size) { + s->data = realloc(s->data, sizeof(*s->data) * (s->size *= 2)); + assert(s->data != NULL); + } + s->data[s->count++] = n; +} + +struct node_base* stack_pop(struct stack* s) { + assert(s->count > 0); + s->count--; +} + +struct node_base* stack_peek(struct stack* s, size_t o) { + assert(s->count > o); + return s->data[s->count - o - 1]; +} + +void stack_popn(struct stack* s, size_t n) { + assert(s->count >= n); + s->count -= n; +} + +void stack_slide(struct stack* s, size_t n) { + assert(s->count > n); + s->data[s->count - n - 1] = s->data[s->count - 1]; + s->count -= n; +} + +void stack_update(struct stack* s, size_t o) { + assert(s->count > o + 1); + struct node_ind* ind = (struct node_ind*) s->data[s->count - o - 2]; + ind->base.tag = NODE_IND; + ind->next = s->data[s->count -= 1]; +} + +void stack_alloc(struct stack* s, size_t o) { + while(o--) { + struct node_ind* new_node = (struct node_ind*) alloc_node(); + new_node->base.tag = NODE_IND; + new_node->next = NULL; + stack_push(s, (struct node_base*) new_node); + } +} + +void eval(struct node_base* n); + +extern void f_main(struct stack* s); + +int main(int argc, char** argv) { + struct node_global* first_node = (struct node_global*) alloc_node(); + first_node->base.tag = NODE_GLOBAL; + first_node->arity = 0; + first_node->function = f_main; + eval((struct node_base*) first_node); +} diff --git a/content/blog/07_compiler_runtime.md b/content/blog/07_compiler_runtime.md index 20fd70c..840f918 100644 --- a/content/blog/07_compiler_runtime.md +++ b/content/blog/07_compiler_runtime.md @@ -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 46 >}} +{{< codelines "C++" "compiler/07/runtime.c" 5 51 >}} 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 @@ -66,3 +66,40 @@ to be any node. We do this because we sometimes mutate nodes (replacing 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: + +* `stack_init` and `stack_free` - one allocates memory for the stack, +the other releases it. +* `stack_push`, `stack_pop` and `stack_peek` - the classic stack operations. +We have `_peek` to take an offset, so we can peek relative to the top of the stack. +* `stack_popn` - pop off some number of nodes instead of one. +* `stack_slide` - the slide we specified in the semantics. Keeps the top, deletes the +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. + +Here's the implementation: +{{< codelines "C++" "compiler/07/runtime.c" 53 113 >}} + +Let's not 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. + +When we finally generate machine code for each of the functions +we have in our program, it will be made up of sequences of G-machine +operations expressed using assembly instructions. These instructions will still +have to manipulate the G-machine stack (they still represent G-machine operations!), +and thus, the resulting assembly subroutine will take as parameter a stack. It will +then construct the function's graph on that stack, as we've already seen. Thus, +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 +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 >}}