Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
#include <assert.h>
|
|
|
|
|
#include <stdbool.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
#include "apfl.h"
|
|
|
|
|
#include "compile.h"
|
|
|
|
|
#include "bytecode.h"
|
|
|
|
|
#include "resizable.h"
|
2022-04-23 20:46:27 +00:00
|
|
|
#include "strings.h"
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
#define DEBUG_COMPILING 0
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
|
|
|
|
#if DEBUG_COMPILING
|
|
|
|
|
# define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__);
|
|
|
|
|
#else
|
|
|
|
|
# define DEBUG_LOG(...)
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
struct compiler {
|
|
|
|
|
struct gc *gc;
|
|
|
|
|
struct apfl_error error;
|
|
|
|
|
size_t line;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define TRY(b) do { if (!(b)) { return false; } } while(0)
|
|
|
|
|
#define MALLOC_FAIL_IF_NULL(compiler, x) \
|
|
|
|
|
do { \
|
|
|
|
|
if ((x) == NULL) { \
|
|
|
|
|
compiler->error = apfl_error_simple(APFL_ERR_MALLOC_FAILED); \
|
|
|
|
|
return false; \
|
|
|
|
|
} \
|
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
|
|
static bool compile_expr(struct compiler *, struct apfl_expr *, struct instruction_list *);
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
malloc_failure_on_false(struct compiler *compiler, bool b)
|
|
|
|
|
{
|
|
|
|
|
if (!b) {
|
|
|
|
|
compiler->error = apfl_error_simple(APFL_ERR_MALLOC_FAILED);
|
|
|
|
|
}
|
|
|
|
|
return b;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
ilist_ensure_cap(struct compiler *compiler, struct instruction_list *ilist, size_t n)
|
|
|
|
|
{
|
|
|
|
|
return malloc_failure_on_false(compiler, apfl_resizable_ensure_cap_for_more_elements(
|
|
|
|
|
compiler->gc->allocator,
|
|
|
|
|
sizeof(union instruction_or_arg),
|
|
|
|
|
(void **)&ilist->instructions,
|
|
|
|
|
ilist->len,
|
|
|
|
|
&ilist->cap,
|
|
|
|
|
n
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-17 18:22:05 +00:00
|
|
|
#define ILIST_PUT(ilist, member, data, dbgfmt, ...) \
|
|
|
|
|
assert(ilist->cap > 0); \
|
|
|
|
|
assert(ilist->len < ilist->cap); \
|
|
|
|
|
\
|
|
|
|
|
ilist->instructions[ilist->len] = (union instruction_or_arg) { \
|
|
|
|
|
.member = data, \
|
|
|
|
|
}; \
|
|
|
|
|
ilist->len++; \
|
|
|
|
|
\
|
|
|
|
|
DEBUG_LOG("ilist %p " dbgfmt "\n", (void *)ilist, __VA_ARGS__);
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
static void
|
|
|
|
|
ilist_put_insn(struct instruction_list *ilist, enum instruction instruction)
|
|
|
|
|
{
|
2022-06-17 18:22:05 +00:00
|
|
|
ILIST_PUT(ilist, instruction, instruction, "put_insn: %s", apfl_instruction_to_string(instruction))
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
ilist_put_number(struct instruction_list *ilist, apfl_number number)
|
|
|
|
|
{
|
2022-06-17 18:22:05 +00:00
|
|
|
ILIST_PUT(ilist, number, number, "put_number: %lf", number)
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
ilist_put_count(struct instruction_list *ilist, size_t count)
|
|
|
|
|
{
|
2022-06-17 18:22:05 +00:00
|
|
|
ILIST_PUT(ilist, count, count, "put_count: %d", (int)count)
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
ilist_put_string(struct instruction_list *ilist, struct apfl_string *string)
|
|
|
|
|
{
|
2022-06-17 18:22:05 +00:00
|
|
|
ILIST_PUT(ilist, string, string, "put_string: " APFL_STR_FMT, APFL_STR_FMT_ARGS(*string))
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
static void
|
|
|
|
|
ilist_put_body(struct instruction_list *ilist, struct instruction_list *body)
|
|
|
|
|
{
|
|
|
|
|
ILIST_PUT(ilist, body, body, "put_body: %p", (void *)body)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
ilist_put_index(struct instruction_list *ilist, size_t index)
|
|
|
|
|
{
|
|
|
|
|
ILIST_PUT(ilist, index, index, "put_index: %d", (int)index)
|
|
|
|
|
}
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
static bool
|
|
|
|
|
string_move_into_new(struct compiler *compiler, struct apfl_string **out, struct apfl_string *in)
|
|
|
|
|
{
|
2022-04-23 20:46:27 +00:00
|
|
|
struct apfl_string *str = apfl_string_move_into_new_gc_string(compiler->gc, in);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
if (str == NULL) {
|
|
|
|
|
compiler->error = apfl_error_simple(APFL_ERR_MALLOC_FAILED);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*out = str;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
compile_constant(struct compiler *compiler, struct apfl_expr_const *constant, struct instruction_list *ilist)
|
|
|
|
|
{
|
|
|
|
|
switch (constant->type) {
|
|
|
|
|
case APFL_EXPR_CONST_NIL:
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 1));
|
|
|
|
|
ilist_put_insn(ilist, INSN_NIL);
|
|
|
|
|
return true;
|
|
|
|
|
case APFL_EXPR_CONST_BOOLEAN:
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 1));
|
|
|
|
|
ilist_put_insn(ilist, constant->boolean ? INSN_TRUE : INSN_FALSE);
|
|
|
|
|
return true;
|
|
|
|
|
case APFL_EXPR_CONST_NUMBER:
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
|
|
|
ilist_put_insn(ilist, INSN_NUMBER);
|
|
|
|
|
ilist_put_number(ilist, constant->number);
|
|
|
|
|
return true;
|
|
|
|
|
case APFL_EXPR_CONST_STRING:
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
|
|
|
|
|
|
|
|
struct apfl_string *str;
|
|
|
|
|
TRY(string_move_into_new(compiler, &str, &constant->string));
|
|
|
|
|
|
|
|
|
|
ilist_put_insn(ilist, INSN_STRING);
|
|
|
|
|
ilist_put_string(ilist, str);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(false);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
compile_list(struct compiler *compiler, struct apfl_expr_list *list, struct instruction_list *ilist)
|
|
|
|
|
{
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
|
|
|
ilist_put_insn(ilist, INSN_LIST);
|
|
|
|
|
ilist_put_count(ilist, list->len);
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < list->len; i++) {
|
|
|
|
|
struct apfl_expr_list_item *item = &list->items[i];
|
|
|
|
|
|
|
|
|
|
TRY(compile_expr(compiler, item->expr, ilist));
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 1));
|
|
|
|
|
ilist_put_insn(ilist, item->expand ? INSN_LIST_EXPAND_INTO : INSN_LIST_APPEND);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
compile_dict(struct compiler *compiler, struct apfl_expr_dict *dict, struct instruction_list *ilist)
|
|
|
|
|
{
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 1));
|
|
|
|
|
ilist_put_insn(ilist, INSN_DICT);
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < dict->len; i++) {
|
|
|
|
|
struct apfl_expr_dict_pair *pair = &dict->items[i];
|
|
|
|
|
|
|
|
|
|
TRY(compile_expr(compiler, pair->k, ilist));
|
|
|
|
|
TRY(compile_expr(compiler, pair->v, ilist));
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 1));
|
|
|
|
|
ilist_put_insn(ilist, INSN_DICT_APPEND_KVPAIR);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
compile_dot(struct compiler *compiler, struct apfl_expr_dot *dot, struct instruction_list *ilist)
|
|
|
|
|
{
|
|
|
|
|
TRY(compile_expr(compiler, dot->lhs, ilist));
|
|
|
|
|
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 3));
|
|
|
|
|
|
|
|
|
|
struct apfl_string *str;
|
|
|
|
|
TRY(string_move_into_new(compiler, &str, &dot->rhs));
|
|
|
|
|
|
|
|
|
|
ilist_put_insn(ilist, INSN_STRING);
|
|
|
|
|
ilist_put_string(ilist, str);
|
|
|
|
|
ilist_put_insn(ilist, INSN_GET_MEMBER);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
compile_at(struct compiler *compiler, struct apfl_expr_at *at, struct instruction_list *ilist)
|
|
|
|
|
{
|
|
|
|
|
TRY(compile_expr(compiler, at->lhs, ilist));
|
|
|
|
|
TRY(compile_expr(compiler, at->rhs, ilist));
|
|
|
|
|
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 1));
|
|
|
|
|
ilist_put_insn(ilist, INSN_GET_MEMBER);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
compile_var(struct compiler *compiler, struct apfl_string *var, struct instruction_list *ilist)
|
|
|
|
|
{
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
|
|
|
|
|
|
|
|
struct apfl_string *str;
|
|
|
|
|
TRY(string_move_into_new(compiler, &str, var));
|
|
|
|
|
|
|
|
|
|
ilist_put_insn(ilist, INSN_VAR_GET);
|
|
|
|
|
ilist_put_string(ilist, str);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
compile_simple_assignment(
|
|
|
|
|
struct compiler *compiler,
|
|
|
|
|
struct apfl_string *var,
|
|
|
|
|
struct apfl_expr *rhs,
|
2022-07-11 19:41:05 +00:00
|
|
|
struct instruction_list *ilist,
|
|
|
|
|
enum instruction new_insn,
|
|
|
|
|
enum instruction set_insn
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
) {
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
|
|
|
|
|
|
|
|
struct apfl_string *str;
|
|
|
|
|
TRY(string_move_into_new(compiler, &str, var));
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
ilist_put_insn(ilist, new_insn);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
ilist_put_string(ilist, str);
|
|
|
|
|
|
|
|
|
|
TRY(compile_expr(compiler, rhs, ilist));
|
|
|
|
|
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
2022-07-11 19:41:05 +00:00
|
|
|
ilist_put_insn(ilist, set_insn);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
ilist_put_string(ilist, str);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
static struct instruction_list *
|
|
|
|
|
tmp_ilist(struct compiler *compiler, int line)
|
|
|
|
|
{
|
|
|
|
|
struct instruction_list *ilist;
|
|
|
|
|
if (
|
|
|
|
|
(ilist = apfl_instructions_new(compiler->gc, line)) == NULL
|
|
|
|
|
|| !apfl_gc_tmproot_add(
|
|
|
|
|
compiler->gc,
|
|
|
|
|
GC_OBJECT_FROM(ilist, GC_TYPE_INSTRUCTIONS)
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ilist;
|
|
|
|
|
}
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
static bool
|
|
|
|
|
compile_assignment(
|
|
|
|
|
struct compiler *compiler,
|
|
|
|
|
struct apfl_expr_assignment *assignment,
|
|
|
|
|
struct instruction_list *ilist
|
|
|
|
|
) {
|
|
|
|
|
if (
|
|
|
|
|
assignment->lhs.type == APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER
|
|
|
|
|
&& assignment->lhs.var_or_member.type == APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_VAR
|
|
|
|
|
) {
|
|
|
|
|
return compile_simple_assignment(
|
|
|
|
|
compiler,
|
|
|
|
|
&assignment->lhs.var_or_member.var,
|
|
|
|
|
assignment->rhs,
|
2022-07-11 19:41:05 +00:00
|
|
|
ilist,
|
|
|
|
|
assignment->local ? INSN_VAR_NEW_LOCAL : INSN_VAR_NEW,
|
|
|
|
|
assignment->local ? INSN_VAR_SET_LOCAL : INSN_VAR_SET
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Implement other assignables
|
|
|
|
|
compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
static bool
|
|
|
|
|
compile_call(struct compiler *compiler, struct apfl_expr_call *call, struct instruction_list *ilist)
|
|
|
|
|
{
|
|
|
|
|
TRY(compile_expr(compiler, call->callee, ilist));
|
|
|
|
|
TRY(compile_list(compiler, &call->arguments, ilist));
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 1));
|
|
|
|
|
ilist_put_insn(ilist, INSN_CALL);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
compile_body(struct compiler *compiler, struct apfl_expr_body *body, struct instruction_list *ilist)
|
|
|
|
|
{
|
|
|
|
|
for (size_t i = 0; i < body->len; i++) {
|
|
|
|
|
TRY(compile_expr(compiler, &body->items[i], ilist));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
compile_simple_func_inner(struct compiler *compiler, struct apfl_expr_body *func, struct instruction_list *ilist, size_t line)
|
|
|
|
|
{
|
|
|
|
|
struct instruction_list *body_ilist = NULL;
|
|
|
|
|
MALLOC_FAIL_IF_NULL(compiler, (body_ilist = tmp_ilist(compiler, line)));
|
|
|
|
|
|
|
|
|
|
// Drop the argument list, we ignore it in simple functions
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, body_ilist, 1));
|
|
|
|
|
ilist_put_insn(body_ilist, INSN_DROP);
|
|
|
|
|
|
|
|
|
|
TRY(compile_body(compiler, func, body_ilist));
|
|
|
|
|
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
|
|
|
ilist_put_insn(ilist, INSN_FUNC);
|
|
|
|
|
ilist_put_body(ilist, body_ilist);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
compile_simple_func(struct compiler *compiler, struct apfl_expr_body *func, struct instruction_list *ilist, size_t line)
|
|
|
|
|
{
|
|
|
|
|
size_t tmproots = apfl_gc_tmproots_begin(compiler->gc);
|
|
|
|
|
bool ok = compile_simple_func_inner(compiler, func, ilist, line);
|
|
|
|
|
apfl_gc_tmproots_restore(compiler->gc, tmproots);
|
|
|
|
|
return ok;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
compile_complex_func_inner(struct compiler *compiler, struct apfl_expr_complex_func *func, struct instruction_list *ilist, size_t line)
|
|
|
|
|
{
|
|
|
|
|
if (func->len != 1) {
|
|
|
|
|
compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct apfl_expr_subfunc *subfunc = &func->subfuncs[0];
|
|
|
|
|
|
|
|
|
|
struct instruction_list *body_ilist = NULL;
|
|
|
|
|
MALLOC_FAIL_IF_NULL(compiler, (body_ilist = tmp_ilist(compiler, line)));
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < subfunc->params.len; i++) {
|
|
|
|
|
struct apfl_expr_params_item *param = &subfunc->params.params[i];
|
|
|
|
|
if (param->expand) {
|
|
|
|
|
compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (param->param.type) {
|
|
|
|
|
case APFL_EXPR_PARAM_VAR:
|
|
|
|
|
{
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, body_ilist, 4));
|
|
|
|
|
|
|
|
|
|
struct apfl_string *str;
|
|
|
|
|
TRY(string_move_into_new(compiler, &str, ¶m->param.var));
|
|
|
|
|
|
|
|
|
|
ilist_put_insn(body_ilist, INSN_GET_BY_INDEX_KEEP);
|
|
|
|
|
ilist_put_index(body_ilist, i);
|
|
|
|
|
ilist_put_insn(body_ilist, INSN_MOVE_TO_LOCAL_VAR);
|
|
|
|
|
ilist_put_string(body_ilist, str);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case APFL_EXPR_PARAM_CONSTANT:
|
|
|
|
|
case APFL_EXPR_PARAM_PREDICATE:
|
|
|
|
|
case APFL_EXPR_PARAM_LIST:
|
|
|
|
|
compiler->error = apfl_error_simple(APFL_ERR_NOT_IMPLEMENTED);
|
|
|
|
|
return false;
|
|
|
|
|
case APFL_EXPR_PARAM_BLANK:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, body_ilist, 1));
|
|
|
|
|
ilist_put_insn(body_ilist, INSN_DROP); // Drop the argument list
|
|
|
|
|
|
|
|
|
|
TRY(compile_body(compiler, &subfunc->body, body_ilist));
|
|
|
|
|
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
|
|
|
ilist_put_insn(ilist, INSN_FUNC);
|
|
|
|
|
ilist_put_body(ilist, body_ilist);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
compile_complex_func(struct compiler *compiler, struct apfl_expr_complex_func *func, struct instruction_list *ilist, size_t line)
|
|
|
|
|
{
|
|
|
|
|
size_t tmproots = apfl_gc_tmproots_begin(compiler->gc);
|
|
|
|
|
bool ok = compile_complex_func_inner(compiler, func, ilist, line);
|
|
|
|
|
apfl_gc_tmproots_restore(compiler->gc, tmproots);
|
|
|
|
|
return ok;
|
|
|
|
|
}
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
static bool
|
|
|
|
|
compile_expr(struct compiler *compiler, struct apfl_expr *expr, struct instruction_list *ilist)
|
|
|
|
|
{
|
|
|
|
|
size_t new_line = (size_t)expr->position.line;
|
|
|
|
|
if (new_line != compiler->line) {
|
|
|
|
|
if (new_line == compiler->line + 1) {
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 1));
|
|
|
|
|
ilist_put_insn(ilist, INSN_NEXT_LINE);
|
|
|
|
|
} else {
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 2));
|
|
|
|
|
ilist_put_insn(ilist, INSN_SET_LINE);
|
|
|
|
|
ilist_put_count(ilist, new_line);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
compiler->line = new_line;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (expr->type) {
|
|
|
|
|
case APFL_EXPR_CALL:
|
2022-07-11 19:41:05 +00:00
|
|
|
return compile_call(compiler, &expr->call, ilist);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
case APFL_EXPR_SIMPLE_FUNC:
|
2022-07-11 19:41:05 +00:00
|
|
|
return compile_simple_func(compiler, &expr->simple_func, ilist, new_line);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
case APFL_EXPR_COMPLEX_FUNC:
|
2022-07-11 19:41:05 +00:00
|
|
|
return compile_complex_func(compiler, &expr->complex_func, ilist, new_line);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
case APFL_EXPR_CONSTANT:
|
|
|
|
|
return compile_constant(compiler, &expr->constant, ilist);
|
|
|
|
|
case APFL_EXPR_BLANK:
|
|
|
|
|
TRY(ilist_ensure_cap(compiler, ilist, 1));
|
|
|
|
|
ilist_put_insn(ilist, INSN_NIL);
|
|
|
|
|
return true;
|
|
|
|
|
case APFL_EXPR_LIST:
|
|
|
|
|
return compile_list(compiler, &expr->list, ilist);
|
|
|
|
|
case APFL_EXPR_DICT:
|
|
|
|
|
return compile_dict(compiler, &expr->dict, ilist);
|
|
|
|
|
case APFL_EXPR_DOT:
|
|
|
|
|
return compile_dot(compiler, &expr->dot, ilist);
|
|
|
|
|
case APFL_EXPR_AT:
|
|
|
|
|
return compile_at(compiler, &expr->at, ilist);
|
|
|
|
|
case APFL_EXPR_VAR:
|
|
|
|
|
return compile_var(compiler, &expr->var, ilist);
|
|
|
|
|
case APFL_EXPR_ASSIGNMENT:
|
|
|
|
|
return compile_assignment(compiler, &expr->assignment, ilist);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(false);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
apfl_compile(struct gc *gc, struct apfl_expr expr, struct apfl_error *error_out, struct instruction_list *out)
|
|
|
|
|
{
|
|
|
|
|
struct compiler compiler = {
|
|
|
|
|
.gc = gc,
|
|
|
|
|
.line = out->line,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
bool ok = compile_expr(&compiler, &expr, out);
|
|
|
|
|
apfl_expr_deinit(gc->allocator, &expr);
|
|
|
|
|
|
|
|
|
|
if (!ok) {
|
|
|
|
|
*error_out = compiler.error;
|
|
|
|
|
}
|
|
|
|
|
return ok;
|
|
|
|
|
}
|