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 <stdlib.h>
|
|
|
|
|
#include <string.h>
|
2022-06-24 21:13:44 +00:00
|
|
|
#include <setjmp.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
|
|
|
|
|
|
|
|
#include "apfl.h"
|
|
|
|
|
#include "alloc.h"
|
|
|
|
|
#include "context.h"
|
|
|
|
|
#include "gc.h"
|
2022-07-12 20:13:07 +00:00
|
|
|
#include "globals.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
|
|
|
#include "hashmap.h"
|
|
|
|
|
#include "resizable.h"
|
2022-04-22 21:17:28 +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
|
|
|
#include "value.h"
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
static bool try_push_const_string(apfl_ctx ctx, const char *string);
|
2022-07-11 19:41:05 +00:00
|
|
|
static bool current_stack_move_to_top(apfl_ctx, apfl_stackidx);
|
2022-07-12 20:13:07 +00:00
|
|
|
static struct apfl_string *new_copied_string(struct gc *, struct apfl_string_view);
|
2022-06-24 21:13:44 +00:00
|
|
|
|
|
|
|
|
APFL_NORETURN static void
|
|
|
|
|
panic(apfl_ctx ctx, bool with_error_on_stack, enum apfl_result result)
|
|
|
|
|
{
|
|
|
|
|
(void)ctx;
|
|
|
|
|
(void)result;
|
|
|
|
|
(void)with_error_on_stack;
|
|
|
|
|
fprintf(stderr, "panic!\n");
|
|
|
|
|
// TODO: more details
|
|
|
|
|
abort();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum apfl_result
|
2022-11-20 20:42:46 +00:00
|
|
|
apfl_do_protected(
|
|
|
|
|
apfl_ctx ctx,
|
|
|
|
|
void (*callback)(apfl_ctx, void *),
|
|
|
|
|
void *opaque,
|
|
|
|
|
bool *with_error_on_stack
|
|
|
|
|
) {
|
2022-06-24 21:13:44 +00:00
|
|
|
struct error_handler *prev_handler = ctx->error_handler;
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
size_t callstack_len = ctx->call_stack.len;
|
2022-06-24 21:13:44 +00:00
|
|
|
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
|
|
|
|
|
|
|
|
|
|
struct error_handler handler;
|
|
|
|
|
ctx->error_handler = &handler;
|
|
|
|
|
|
|
|
|
|
enum apfl_result result = APFL_RESULT_OK;
|
|
|
|
|
int rv = setjmp(handler.jump);
|
|
|
|
|
if (rv == 0) {
|
2022-11-20 20:42:46 +00:00
|
|
|
callback(ctx, opaque);
|
2022-06-24 21:13:44 +00:00
|
|
|
} else {
|
|
|
|
|
result = (enum apfl_result)(rv - RESULT_OFF_FOR_LONGJMP);
|
|
|
|
|
assert(result != APFL_RESULT_OK);
|
|
|
|
|
|
|
|
|
|
struct apfl_value err;
|
|
|
|
|
if ((*with_error_on_stack = handler.with_error_on_stack)) {
|
|
|
|
|
if (!apfl_stack_pop(ctx, &err, -1)) {
|
|
|
|
|
*with_error_on_stack = false;
|
|
|
|
|
// TODO: Indicate error during error handling
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
|
2022-07-11 19:41:05 +00:00
|
|
|
assert(callstack_len <= ctx->call_stack.cap);
|
|
|
|
|
|
|
|
|
|
for (size_t i = callstack_len; i < ctx->call_stack.len; i++) {
|
|
|
|
|
apfl_call_stack_entry_deinit(ctx->gc.allocator, &ctx->call_stack.items[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(
|
|
|
|
|
// Shrinking should not fail
|
|
|
|
|
apfl_resizable_resize(
|
|
|
|
|
ctx->gc.allocator,
|
|
|
|
|
sizeof(struct call_stack_entry),
|
|
|
|
|
(void **)&ctx->call_stack.items,
|
|
|
|
|
&ctx->call_stack.len,
|
|
|
|
|
&ctx->call_stack.cap,
|
|
|
|
|
callstack_len
|
|
|
|
|
)
|
|
|
|
|
);
|
2022-06-24 21:13:44 +00:00
|
|
|
|
|
|
|
|
if (*with_error_on_stack) {
|
|
|
|
|
if (!apfl_stack_push(ctx, err)) {
|
|
|
|
|
*with_error_on_stack = false;
|
|
|
|
|
// TODO: Indicate error during error handling
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx->error_handler = prev_handler;
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-20 20:42:46 +00:00
|
|
|
struct call_protected_data {
|
|
|
|
|
apfl_stackidx func;
|
|
|
|
|
apfl_stackidx args;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
call_protected_cb(apfl_ctx ctx, void *opaque)
|
|
|
|
|
{
|
|
|
|
|
struct call_protected_data *data = opaque;
|
|
|
|
|
apfl_call(ctx, data->func, data->args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum apfl_result
|
|
|
|
|
apfl_call_protected(apfl_ctx ctx, apfl_stackidx func, apfl_stackidx args, bool *with_error_on_stack)
|
|
|
|
|
{
|
|
|
|
|
struct call_protected_data data = {
|
|
|
|
|
.func = func,
|
|
|
|
|
.args = args,
|
|
|
|
|
};
|
|
|
|
|
return apfl_do_protected(ctx, call_protected_cb, &data, with_error_on_stack);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
APFL_NORETURN static void
|
|
|
|
|
raise_error(apfl_ctx ctx, bool with_error_on_stack, enum apfl_result type)
|
|
|
|
|
{
|
|
|
|
|
assert(type != APFL_RESULT_OK);
|
|
|
|
|
|
|
|
|
|
if (ctx->error_handler != NULL) {
|
|
|
|
|
ctx->error_handler->with_error_on_stack = with_error_on_stack;
|
|
|
|
|
longjmp(ctx->error_handler->jump, (int)type + RESULT_OFF_FOR_LONGJMP);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ctx->panic_callback != NULL) {
|
|
|
|
|
ctx->panic_callback(ctx, with_error_on_stack, ctx->panic_callback_data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
panic(ctx, with_error_on_stack, type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
APFL_NORETURN void
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_error(apfl_ctx ctx, apfl_stackidx idx)
|
2022-06-24 21:13:44 +00:00
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
bool ok = current_stack_move_to_top(ctx, idx);
|
2022-12-09 20:22:50 +00:00
|
|
|
raise_error(ctx, ok, APFL_RESULT_ERR);
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
APFL_NORETURN void
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(apfl_ctx ctx, const char *message)
|
2022-06-24 21:13:44 +00:00
|
|
|
{
|
2022-12-09 20:22:50 +00:00
|
|
|
raise_error(ctx, try_push_const_string(ctx, message), APFL_RESULT_ERR);
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
APFL_NORETURN void
|
|
|
|
|
apfl_raise_alloc_error(apfl_ctx ctx)
|
|
|
|
|
{
|
2022-12-09 20:22:50 +00:00
|
|
|
raise_error(ctx, false, APFL_RESULT_ERR_ALLOC);
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
APFL_NORETURN void
|
|
|
|
|
apfl_raise_invalid_stackidx(apfl_ctx ctx)
|
|
|
|
|
{
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.invalid_stack_index);
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
APFL_NORETURN void
|
|
|
|
|
apfl_raise_error_object(apfl_ctx ctx, struct apfl_error error)
|
|
|
|
|
{
|
2022-12-09 20:22:50 +00:00
|
|
|
if (error.type == APFL_ERR_MALLOC_FAILED) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char *const_str = apfl_error_as_const_string(error);
|
|
|
|
|
if (const_str != NULL) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, const_str);
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct apfl_string string;
|
|
|
|
|
if (!apfl_error_as_string(error, ctx->gc.allocator, &string)) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!apfl_move_string_onto_stack(ctx, string)) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_error(ctx, -1);
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_ctx_set_panic_callback(apfl_ctx ctx, apfl_panic_callback cb, void *opaque)
|
|
|
|
|
{
|
|
|
|
|
ctx->panic_callback = cb;
|
|
|
|
|
ctx->panic_callback_data = opaque;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
struct stack
|
|
|
|
|
apfl_stack_new(void)
|
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-01 20:40:43 +00:00
|
|
|
return (struct stack) {
|
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
|
|
|
.items = NULL,
|
|
|
|
|
.len = 0,
|
|
|
|
|
.cap = 0,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
static struct stack *
|
|
|
|
|
current_value_stack(apfl_ctx ctx)
|
|
|
|
|
{
|
|
|
|
|
struct call_stack_entry *csentry = apfl_call_stack_cur_entry(ctx);
|
|
|
|
|
return csentry != NULL
|
|
|
|
|
? &csentry->stack
|
|
|
|
|
: &ctx->toplevel_stack;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
|
|
|
|
apfl_stack_must_push(apfl_ctx ctx, struct apfl_value value)
|
|
|
|
|
{
|
|
|
|
|
if (!apfl_stack_push(ctx, value)) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
bool
|
|
|
|
|
apfl_stack_push(apfl_ctx ctx, struct apfl_value value)
|
|
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
struct stack *stack = current_value_stack(ctx);
|
|
|
|
|
|
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
|
|
|
return apfl_resizable_append(
|
|
|
|
|
ctx->gc.allocator,
|
|
|
|
|
sizeof(struct apfl_value),
|
2022-07-11 19:41:05 +00:00
|
|
|
(void **)&stack->items,
|
|
|
|
|
&stack->len,
|
|
|
|
|
&stack->cap,
|
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
|
|
|
&value,
|
|
|
|
|
1
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
static bool
|
|
|
|
|
stack_check_index(struct stack *stack, apfl_stackidx *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
|
|
|
{
|
|
|
|
|
if (*index < 0) {
|
2022-07-11 19:41:05 +00:00
|
|
|
if ((size_t)-*index > stack->len) {
|
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
|
|
|
return false;
|
|
|
|
|
}
|
2022-07-11 19:41:05 +00:00
|
|
|
*index = stack->len + *index;
|
|
|
|
|
} else if ((size_t)*index >= stack->len) {
|
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
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
assert(0 <= *index && (size_t)*index < stack->len);
|
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
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
bool
|
|
|
|
|
apfl_stack_check_index(apfl_ctx ctx, apfl_stackidx *index)
|
|
|
|
|
{
|
|
|
|
|
return stack_check_index(current_value_stack(ctx), index);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
apfl_stack_has_index(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
return apfl_stack_check_index(ctx, &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 int
|
|
|
|
|
cmp_stackidx(const void *_a, const void *_b)
|
|
|
|
|
{
|
|
|
|
|
const apfl_stackidx *a = _a;
|
|
|
|
|
const apfl_stackidx *b = _b;
|
|
|
|
|
return *a - *b;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
bool
|
|
|
|
|
apfl_stack_drop_multi(apfl_ctx ctx, size_t count, apfl_stackidx *indices)
|
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
|
|
|
struct stack *stack = current_value_stack(ctx);
|
|
|
|
|
|
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
|
|
|
for (size_t i = 0; i < count; i++) {
|
2022-07-11 19:41:05 +00:00
|
|
|
if (!stack_check_index(stack, &indices[i])) {
|
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
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qsort(indices, count, sizeof(apfl_stackidx), cmp_stackidx);
|
|
|
|
|
|
|
|
|
|
for (size_t i = count; i-- > 0; ) {
|
|
|
|
|
// Will not fail, as we've already checked the indices
|
|
|
|
|
assert(apfl_resizable_cut_without_resize(
|
|
|
|
|
sizeof(struct apfl_value),
|
2022-07-11 19:41:05 +00:00
|
|
|
(void **)&stack->items,
|
|
|
|
|
&stack->len,
|
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
|
|
|
indices[i],
|
|
|
|
|
1
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Shrink stack
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
apfl_stack_pop(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index)
|
|
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
struct stack *stack = current_value_stack(ctx);
|
|
|
|
|
|
|
|
|
|
if (!stack_check_index(stack, &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
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
*value = stack->items[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
|
|
|
|
|
|
|
|
assert(apfl_resizable_splice(
|
|
|
|
|
ctx->gc.allocator,
|
|
|
|
|
sizeof(struct apfl_value),
|
2022-07-11 19:41:05 +00:00
|
|
|
(void **)&stack->items,
|
|
|
|
|
&stack->len,
|
|
|
|
|
&stack->cap,
|
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
|
|
|
index,
|
|
|
|
|
1,
|
|
|
|
|
NULL,
|
|
|
|
|
0
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
struct apfl_value
|
|
|
|
|
apfl_stack_must_pop(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value value;
|
|
|
|
|
if (!apfl_stack_pop(ctx, &value, index)) {
|
|
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
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 struct apfl_value *
|
|
|
|
|
stack_get_pointer(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
struct stack *stack = current_value_stack(ctx);
|
|
|
|
|
|
|
|
|
|
if (!stack_check_index(stack, &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
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
return &stack->items[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
|
2022-07-11 19:41:05 +00:00
|
|
|
stack_get_and_adjust_index(struct stack *stack, struct apfl_value *value, apfl_stackidx *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
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
if (!stack_check_index(stack, 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
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
*value = stack->items[*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
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
static bool
|
|
|
|
|
current_stack_get_and_adjust_index(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx *index)
|
|
|
|
|
{
|
|
|
|
|
return stack_get_and_adjust_index(current_value_stack(ctx), value, 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
|
|
|
bool
|
|
|
|
|
apfl_stack_get(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index)
|
|
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
return current_stack_get_and_adjust_index(ctx, value, &index);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct apfl_value
|
|
|
|
|
apfl_stack_must_get(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value value;
|
|
|
|
|
if (!apfl_stack_get(ctx, &value, index)) {
|
|
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
|
|
|
}
|
|
|
|
|
return value;
|
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-04-21 20:40:27 +00:00
|
|
|
struct apfl_value *
|
|
|
|
|
apfl_stack_push_placeholder(apfl_ctx ctx)
|
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 (!apfl_stack_push(ctx, (struct apfl_value) {.type = VALUE_NIL})) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return stack_get_pointer(ctx, -1);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
bool
|
|
|
|
|
apfl_move_string_onto_stack(apfl_ctx ctx, struct apfl_string string)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value *value = apfl_stack_push_placeholder(ctx);
|
|
|
|
|
if (value == NULL) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((value->string = apfl_string_move_into_new_gc_string(&ctx->gc, &string)) == NULL) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
value->type = VALUE_STRING;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
bool
|
|
|
|
|
apfl_stack_drop(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value value;
|
|
|
|
|
return apfl_stack_pop(ctx, &value, index);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-12 20:13:07 +00:00
|
|
|
void
|
|
|
|
|
apfl_drop(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
if (!apfl_stack_drop(ctx, index)) {
|
|
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-26 22:06:55 +00:00
|
|
|
bool
|
|
|
|
|
current_stack_multi_move_to_top(apfl_ctx ctx, size_t count, apfl_stackidx *indices)
|
2022-06-24 21:13:44 +00:00
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
struct stack *stack = current_value_stack(ctx);
|
|
|
|
|
|
2022-11-26 22:06:55 +00:00
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
|
|
|
if (!stack_check_index(stack, &indices[i])) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2022-06-24 21:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
2022-11-26 22:06:55 +00:00
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
|
|
|
// If we're here, index is an absolute address and is guaranteed to be < len
|
|
|
|
|
assert(indices[i] >= 0);
|
|
|
|
|
size_t absindex = (size_t)indices[i];
|
|
|
|
|
assert(stack->len >= absindex+1);
|
|
|
|
|
|
|
|
|
|
struct apfl_value val = stack->items[absindex];
|
2022-06-24 21:13:44 +00:00
|
|
|
|
2022-11-26 22:06:55 +00:00
|
|
|
memmove(
|
|
|
|
|
&stack->items[absindex],
|
|
|
|
|
&stack->items[absindex + 1],
|
|
|
|
|
sizeof(struct apfl_value) * (stack->len - absindex - 1)
|
|
|
|
|
);
|
|
|
|
|
stack->items[stack->len-1] = val;
|
2022-06-24 21:13:44 +00:00
|
|
|
|
2022-11-26 22:06:55 +00:00
|
|
|
for (size_t j = i + 1; j < count; j++) {
|
|
|
|
|
if (indices[j] >= indices[i]) {
|
|
|
|
|
indices[j]--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-06-24 21:13:44 +00:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-26 22:06:55 +00:00
|
|
|
static bool
|
|
|
|
|
current_stack_move_to_top(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
return current_stack_multi_move_to_top(ctx, 1, &index);
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-30 21:44:34 +00:00
|
|
|
void
|
|
|
|
|
apfl_move_to_top_of_stack(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
if (!current_stack_move_to_top(ctx, index)) {
|
|
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-26 22:06:55 +00:00
|
|
|
void
|
|
|
|
|
apfl_multi_move_to_top_of_stack(apfl_ctx ctx, size_t count, apfl_stackidx *indices)
|
|
|
|
|
{
|
|
|
|
|
if (!current_stack_multi_move_to_top(ctx, count, indices)) {
|
|
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-31 14:51:56 +00:00
|
|
|
void
|
|
|
|
|
apfl_copy(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value value = apfl_stack_must_get(ctx, index);
|
|
|
|
|
apfl_stack_must_push(ctx, apfl_value_set_cow_flag(value));
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-21 19:15:20 +00:00
|
|
|
void
|
|
|
|
|
apfl_stack_clear(apfl_ctx ctx)
|
|
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
struct stack *stack = current_value_stack(ctx);
|
|
|
|
|
stack->len = 0;
|
2022-07-01 20:40:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2022-07-11 19:41:05 +00:00
|
|
|
stack_traverse(struct stack stack, gc_visitor visitor, void *opaque)
|
2022-07-01 20:40:43 +00:00
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
for (size_t i = 0; i < stack.len; i++) {
|
2022-11-19 21:06:23 +00:00
|
|
|
apfl_value_visit_gc_object(stack.items[i], visitor, opaque);
|
2022-07-01 20:40:43 +00:00
|
|
|
}
|
2022-04-21 19:15:20 +00:00
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
static void
|
|
|
|
|
visit_nullable_scope(struct scope *scope, gc_visitor visitor, void *opaque)
|
|
|
|
|
{
|
|
|
|
|
if (scope != NULL) {
|
|
|
|
|
visitor(opaque, GC_OBJECT_FROM(scope, GC_TYPE_SCOPE));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2022-11-19 21:06:23 +00:00
|
|
|
visit_scopes(struct scopes scopes, gc_visitor visitor, void *opaque)
|
2022-07-11 19:41:05 +00:00
|
|
|
{
|
2022-11-19 21:06:23 +00:00
|
|
|
visit_nullable_scope(scopes.local, visitor, opaque);
|
|
|
|
|
visit_nullable_scope(scopes.closure, visitor, opaque);
|
|
|
|
|
}
|
2022-07-11 19:41:05 +00:00
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
static void
|
|
|
|
|
visit_matcher_stack(struct matcher_stack matcher_stack, gc_visitor visitor, void *opaque)
|
|
|
|
|
{
|
|
|
|
|
for (size_t i = 0; i < matcher_stack.len; i++) {
|
2022-07-11 19:41:05 +00:00
|
|
|
visitor(
|
|
|
|
|
opaque,
|
2022-11-19 21:06:23 +00:00
|
|
|
GC_OBJECT_FROM(matcher_stack.items[i], GC_TYPE_MATCHER)
|
2022-07-11 19:41:05 +00:00
|
|
|
);
|
2022-11-19 21:06:23 +00:00
|
|
|
}
|
|
|
|
|
}
|
2022-07-11 19:41:05 +00:00
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
static void
|
|
|
|
|
visit_func_cse(struct func_call_stack_entry cse, gc_visitor visitor, void *opaque)
|
|
|
|
|
{
|
|
|
|
|
visitor(
|
|
|
|
|
opaque,
|
|
|
|
|
GC_OBJECT_FROM(cse.instructions, GC_TYPE_INSTRUCTIONS)
|
|
|
|
|
);
|
2022-08-04 20:23:21 +00:00
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
visit_scopes(cse.scopes, visitor, opaque);
|
|
|
|
|
visit_matcher_stack(cse.matcher_stack, visitor, opaque);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
visit_cfunc_cse(struct cfunc_call_stack_entry cse, gc_visitor visitor, void *opaque)
|
|
|
|
|
{
|
|
|
|
|
visitor(
|
|
|
|
|
opaque,
|
|
|
|
|
GC_OBJECT_FROM(cse.func, GC_TYPE_CFUNC)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
visit_matcher_cse(struct matcher_call_stack_entry cse, gc_visitor visitor, void* opaque)
|
|
|
|
|
{
|
|
|
|
|
visitor(
|
|
|
|
|
opaque,
|
|
|
|
|
GC_OBJECT_FROM(cse.matcher, GC_TYPE_MATCHER)
|
|
|
|
|
);
|
|
|
|
|
visit_scopes(cse.scopes, visitor, opaque);
|
|
|
|
|
for (size_t i = 0; i < cse.capture_count; i++) {
|
|
|
|
|
apfl_value_visit_gc_object(cse.captures[i], visitor, opaque);
|
2022-07-11 19:41:05 +00:00
|
|
|
visitor(
|
|
|
|
|
opaque,
|
2022-11-19 21:06:23 +00:00
|
|
|
GC_OBJECT_FROM(cse.transfers[i].var, GC_TYPE_STRING)
|
2022-07-11 19:41:05 +00:00
|
|
|
);
|
2022-11-19 21:06:23 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
visit_func_dispatch_cse(struct func_dispatch_call_stack_entry cse, gc_visitor visitor, void *opaque)
|
|
|
|
|
{
|
|
|
|
|
visitor(
|
|
|
|
|
opaque,
|
|
|
|
|
GC_OBJECT_FROM(cse.function, GC_TYPE_FUNC)
|
|
|
|
|
);
|
|
|
|
|
visit_scopes(cse.scopes, visitor, opaque);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
gc_traverse_call_stack_entry(struct call_stack_entry cse, gc_visitor visitor, void *opaque)
|
|
|
|
|
{
|
|
|
|
|
stack_traverse(cse.stack, visitor, opaque);
|
|
|
|
|
|
|
|
|
|
switch (cse.type) {
|
|
|
|
|
case CSE_FUNCTION:
|
|
|
|
|
visit_func_cse(cse.func, visitor, opaque);
|
|
|
|
|
break;
|
|
|
|
|
case CSE_CFUNCTION:
|
|
|
|
|
visit_cfunc_cse(cse.cfunc, visitor, opaque);
|
2022-07-11 19:41:05 +00:00
|
|
|
break;
|
2022-07-28 18:46:32 +00:00
|
|
|
case CSE_MATCHER:
|
2022-11-19 21:06:23 +00:00
|
|
|
visit_matcher_cse(cse.matcher, visitor, opaque);
|
2022-07-28 18:46:32 +00:00
|
|
|
break;
|
2022-08-12 22:50:26 +00:00
|
|
|
case CSE_FUNCTION_DISPATCH:
|
2022-11-19 21:06:23 +00:00
|
|
|
visit_func_dispatch_cse(cse.func_dispatch, visitor, opaque);
|
2022-08-12 22:50:26 +00:00
|
|
|
break;
|
2022-07-11 19:41:05 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-01 20:00:58 +00:00
|
|
|
static void
|
|
|
|
|
get_roots(void *own_opaque, gc_visitor visitor, void *visitor_opaque)
|
|
|
|
|
{
|
|
|
|
|
apfl_ctx ctx = own_opaque;
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
if (ctx->globals != NULL) {
|
|
|
|
|
visitor(visitor_opaque, GC_OBJECT_FROM(ctx->globals, GC_TYPE_SCOPE));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stack_traverse(ctx->toplevel_stack, visitor, visitor_opaque);
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < ctx->call_stack.len; i++) {
|
|
|
|
|
gc_traverse_call_stack_entry(ctx->call_stack.items[i], visitor, visitor_opaque);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < ctx->iterative_runners.len; i++) {
|
|
|
|
|
apfl_iterative_runner_visit_gc_objects(ctx->iterative_runners.items[i], visitor, visitor_opaque);
|
2022-07-01 20:00:58 +00:00
|
|
|
}
|
2022-07-11 19:41:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct call_stack
|
|
|
|
|
call_stack_new(void)
|
|
|
|
|
{
|
|
|
|
|
return (struct call_stack) {
|
|
|
|
|
.items = NULL,
|
|
|
|
|
.len = 0,
|
|
|
|
|
.cap = 0,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
deinit_stack(struct apfl_allocator allocator, struct stack *stack)
|
|
|
|
|
{
|
|
|
|
|
FREE_LIST(allocator, stack->items, stack->cap);
|
|
|
|
|
*stack = apfl_stack_new();
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
static void
|
|
|
|
|
func_call_stack_entry_deinit(struct apfl_allocator allocator, struct func_call_stack_entry *cse)
|
|
|
|
|
{
|
|
|
|
|
FREE_LIST(allocator, cse->matcher_stack.items, cse->matcher_stack.cap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_matcher_call_stack_entry_deinit(struct apfl_allocator allocator, struct matcher_call_stack_entry *cse)
|
|
|
|
|
{
|
|
|
|
|
FREE_LIST(allocator, cse->captures, cse->capture_count);
|
|
|
|
|
cse->captures = NULL;
|
|
|
|
|
FREE_LIST(allocator, cse->transfers, cse->capture_count);
|
|
|
|
|
cse->transfers = NULL;
|
|
|
|
|
FREE_LIST(allocator, cse->matcher_state_stack, cse->matcher_state_stack_cap);
|
|
|
|
|
cse->matcher_state_stack = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
void
|
|
|
|
|
apfl_call_stack_entry_deinit(struct apfl_allocator allocator, struct call_stack_entry *entry)
|
|
|
|
|
{
|
|
|
|
|
deinit_stack(allocator, &entry->stack);
|
2022-07-28 18:46:32 +00:00
|
|
|
|
|
|
|
|
switch (entry->type) {
|
|
|
|
|
case CSE_FUNCTION:
|
2022-11-19 21:06:23 +00:00
|
|
|
func_call_stack_entry_deinit(allocator, &entry->func);
|
|
|
|
|
break;
|
2022-07-28 18:46:32 +00:00
|
|
|
case CSE_CFUNCTION:
|
2022-08-12 22:50:26 +00:00
|
|
|
case CSE_FUNCTION_DISPATCH:
|
2022-07-28 18:46:32 +00:00
|
|
|
break;
|
|
|
|
|
case CSE_MATCHER:
|
2022-11-19 21:06:23 +00:00
|
|
|
apfl_matcher_call_stack_entry_deinit(allocator, &entry->matcher);
|
2022-07-28 18:46:32 +00:00
|
|
|
break;
|
|
|
|
|
}
|
2022-07-11 19:41:05 +00:00
|
|
|
}
|
2022-07-01 20:00:58 +00:00
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
struct call_stack_entry *
|
|
|
|
|
apfl_call_stack_cur_entry(apfl_ctx ctx)
|
|
|
|
|
{
|
|
|
|
|
return ctx->call_stack.len == 0
|
|
|
|
|
? NULL
|
|
|
|
|
: &ctx->call_stack.items[ctx->call_stack.len - 1];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct iterative_runners_list
|
|
|
|
|
iterative_runners_list_new(void)
|
|
|
|
|
{
|
|
|
|
|
return (struct iterative_runners_list) {
|
|
|
|
|
.items = NULL,
|
|
|
|
|
.len = 0,
|
|
|
|
|
.cap = 0,
|
|
|
|
|
};
|
2022-07-01 20:00:58 +00:00
|
|
|
}
|
|
|
|
|
|
2022-07-12 20:13:07 +00:00
|
|
|
static bool
|
|
|
|
|
init_globals(apfl_ctx ctx)
|
|
|
|
|
{
|
|
|
|
|
struct gc *gc = &ctx->gc;
|
|
|
|
|
|
|
|
|
|
size_t tmproots = apfl_gc_tmproots_begin(gc);
|
|
|
|
|
|
|
|
|
|
for (
|
|
|
|
|
const struct global_def *global = apfl_globals();
|
|
|
|
|
global->name != NULL && global->func != NULL;
|
|
|
|
|
global++
|
|
|
|
|
) {
|
|
|
|
|
struct cfunction *cfunc = apfl_cfunc_new(gc, global->func, 0);
|
|
|
|
|
if (cfunc == NULL) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!apfl_gc_tmproot_add(gc, GC_OBJECT_FROM(cfunc, GC_TYPE_CFUNC))) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: It's silly that we need to copy the name into a new gc string.
|
|
|
|
|
// It should be possible for the scope to also have a const string
|
|
|
|
|
// as a name.
|
|
|
|
|
struct apfl_string *name = new_copied_string(gc, apfl_string_view_from(global->name));
|
|
|
|
|
if (name == NULL) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!apfl_gc_tmproot_add(gc, GC_OBJECT_FROM(name, GC_TYPE_STRING))) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!apfl_scope_set(gc, ctx->globals, name, (struct apfl_value) {
|
|
|
|
|
.type = VALUE_CFUNC,
|
|
|
|
|
.cfunc = cfunc,
|
|
|
|
|
})) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apfl_gc_tmproots_restore(gc, tmproots);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
error:
|
|
|
|
|
apfl_gc_tmproots_restore(gc, tmproots);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
apfl_ctx
|
2022-10-28 19:32:17 +00:00
|
|
|
apfl_ctx_new(struct apfl_config config)
|
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-10-28 19:32:17 +00:00
|
|
|
apfl_ctx ctx = ALLOC_OBJ(config.allocator, struct apfl_ctx_data);
|
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 (ctx == NULL) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-14 19:13:34 +00:00
|
|
|
// It's important that we initialize all these before initializing the
|
|
|
|
|
// garbage collector, otherwise it might try to follow invalid pointers.
|
|
|
|
|
ctx->error_handler = NULL;
|
|
|
|
|
ctx->panic_callback = NULL;
|
|
|
|
|
ctx->panic_callback_data = NULL;
|
2022-07-11 19:41:05 +00:00
|
|
|
ctx->toplevel_stack = apfl_stack_new();
|
|
|
|
|
ctx->call_stack = call_stack_new();
|
2022-07-14 19:13:34 +00:00
|
|
|
ctx->globals = NULL;
|
|
|
|
|
ctx->iterative_runners = iterative_runners_list_new();
|
|
|
|
|
|
2022-10-28 19:32:17 +00:00
|
|
|
apfl_gc_init(&ctx->gc, config.allocator, get_roots, ctx);
|
2022-07-11 19:41:05 +00:00
|
|
|
|
|
|
|
|
if ((ctx->globals = apfl_scope_new(&ctx->gc)) == NULL) {
|
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
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-28 19:32:17 +00:00
|
|
|
ctx->output_writer = config.output_writer;
|
|
|
|
|
|
2022-07-12 20:13:07 +00:00
|
|
|
if (!init_globals(ctx)) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
return ctx;
|
|
|
|
|
|
|
|
|
|
error:
|
|
|
|
|
apfl_ctx_destroy(ctx);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_ctx_destroy(apfl_ctx ctx)
|
|
|
|
|
{
|
|
|
|
|
if (ctx == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
deinit_stack(ctx->gc.allocator, &ctx->toplevel_stack);
|
|
|
|
|
ctx->toplevel_stack = apfl_stack_new();
|
|
|
|
|
|
|
|
|
|
DEINIT_CAP_LIST(
|
|
|
|
|
ctx->gc.allocator,
|
|
|
|
|
ctx->call_stack.items,
|
|
|
|
|
ctx->call_stack.len,
|
|
|
|
|
ctx->call_stack.cap,
|
|
|
|
|
apfl_call_stack_entry_deinit
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
ctx->call_stack = call_stack_new();
|
|
|
|
|
|
|
|
|
|
FREE_LIST(ctx->gc.allocator, ctx->iterative_runners.items, ctx->iterative_runners.cap);
|
|
|
|
|
ctx->iterative_runners = iterative_runners_list_new();
|
|
|
|
|
|
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
|
|
|
struct apfl_allocator base_allocator = ctx->gc.base_allocator;
|
|
|
|
|
|
|
|
|
|
apfl_gc_full(&ctx->gc);
|
|
|
|
|
apfl_gc_deinit(&ctx->gc);
|
|
|
|
|
|
|
|
|
|
FREE_OBJ(base_allocator, ctx);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
static bool
|
|
|
|
|
find_iterative_runner(apfl_ctx ctx, apfl_iterative_runner runner, size_t *index)
|
|
|
|
|
{
|
|
|
|
|
// It should be very uncommon that there are a lot of iterative runners
|
|
|
|
|
// existing on the same apfl_ctx at the same time, so a linear scan should
|
|
|
|
|
// be good enough :)
|
|
|
|
|
for (size_t i = 0; i < ctx->iterative_runners.len; i++) {
|
|
|
|
|
if (ctx->iterative_runners.items[i] == runner) {
|
|
|
|
|
if (index != NULL) {
|
|
|
|
|
*index = i;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-14 19:13:34 +00:00
|
|
|
struct iterative_runner_tmproot_data {
|
|
|
|
|
struct gc *gc;
|
|
|
|
|
bool ok;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
ctx_register_iterative_runner_tmproot(void *opaque, struct gc_object *object)
|
2022-07-11 19:41:05 +00:00
|
|
|
{
|
2022-07-14 19:13:34 +00:00
|
|
|
struct iterative_runner_tmproot_data *data = opaque;
|
|
|
|
|
if (!data->ok) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
data->ok = apfl_gc_tmproot_add(data->gc, object);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
ctx_register_iterative_runner_inner(apfl_ctx ctx, apfl_iterative_runner runner)
|
|
|
|
|
{
|
|
|
|
|
struct iterative_runner_tmproot_data data = {
|
|
|
|
|
.gc = &ctx->gc,
|
|
|
|
|
.ok = true
|
|
|
|
|
};
|
|
|
|
|
apfl_iterative_runner_visit_gc_objects(runner, ctx_register_iterative_runner_tmproot, &data);
|
|
|
|
|
|
|
|
|
|
if (!data.ok) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
if (find_iterative_runner(ctx, runner, NULL)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return apfl_resizable_append(
|
|
|
|
|
ctx->gc.allocator,
|
|
|
|
|
sizeof(apfl_iterative_runner),
|
|
|
|
|
(void **)&ctx->iterative_runners.items,
|
|
|
|
|
&ctx->iterative_runners.len,
|
|
|
|
|
&ctx->iterative_runners.cap,
|
|
|
|
|
&runner,
|
|
|
|
|
1
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-14 19:13:34 +00:00
|
|
|
bool
|
|
|
|
|
apfl_ctx_register_iterative_runner(apfl_ctx ctx, apfl_iterative_runner runner)
|
|
|
|
|
{
|
|
|
|
|
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
|
|
|
|
|
bool out = ctx_register_iterative_runner_inner(ctx, runner);
|
|
|
|
|
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
void
|
|
|
|
|
apfl_ctx_unregister_iterative_runner(apfl_ctx ctx, apfl_iterative_runner runner)
|
|
|
|
|
{
|
|
|
|
|
size_t i;
|
|
|
|
|
if (!find_iterative_runner(ctx, runner, &i)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(
|
|
|
|
|
// We're only removing elements, the buffer should not grow,
|
|
|
|
|
// therefore there should be no allocation errors
|
|
|
|
|
apfl_resizable_splice(
|
|
|
|
|
ctx->gc.allocator,
|
|
|
|
|
sizeof(apfl_iterative_runner),
|
|
|
|
|
(void **)&ctx->iterative_runners.items,
|
|
|
|
|
&ctx->iterative_runners.len,
|
|
|
|
|
&ctx->iterative_runners.cap,
|
|
|
|
|
i,
|
|
|
|
|
1,
|
|
|
|
|
NULL,
|
|
|
|
|
0
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-21 20:40:27 +00:00
|
|
|
#define CREATE_GC_OBJECT_VALUE_ON_STACK(ctx, TYPE, MEMB, NEW) \
|
|
|
|
|
struct apfl_value *value = apfl_stack_push_placeholder(ctx); \
|
|
|
|
|
if (value == NULL) { \
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_alloc_error(ctx); \
|
2022-04-21 20:40:27 +00:00
|
|
|
} \
|
|
|
|
|
\
|
|
|
|
|
struct apfl_value new_value = {.type = TYPE}; \
|
|
|
|
|
if ((new_value.MEMB = NEW) == NULL) { \
|
|
|
|
|
assert(apfl_stack_drop(ctx, -1)); \
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_alloc_error(ctx); \
|
2022-04-21 20:40:27 +00:00
|
|
|
} \
|
|
|
|
|
\
|
2022-06-24 21:13:44 +00:00
|
|
|
*value = new_value;
|
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-06-24 21:13:44 +00:00
|
|
|
void
|
2022-04-22 19:39:03 +00:00
|
|
|
apfl_push_nil(apfl_ctx ctx)
|
|
|
|
|
{
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_stack_must_push(ctx, (struct apfl_value) {
|
2022-04-22 19:39:03 +00:00
|
|
|
.type = VALUE_NIL,
|
2022-06-24 21:13:44 +00:00
|
|
|
});
|
2022-04-22 19:39:03 +00:00
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
2022-04-22 19:39:03 +00:00
|
|
|
apfl_push_bool(apfl_ctx ctx, bool b)
|
|
|
|
|
{
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_stack_must_push(ctx, (struct apfl_value) {
|
2022-04-22 19:39:03 +00:00
|
|
|
.type = VALUE_BOOLEAN,
|
|
|
|
|
.boolean = b,
|
2022-06-24 21:13:44 +00:00
|
|
|
});
|
2022-04-22 19:39:03 +00:00
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
2022-04-22 19:39:03 +00:00
|
|
|
apfl_push_number(apfl_ctx ctx, apfl_number num)
|
|
|
|
|
{
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_stack_must_push(ctx, (struct apfl_value) {
|
2022-04-22 19:39:03 +00:00
|
|
|
.type = VALUE_NUMBER,
|
|
|
|
|
.number = num,
|
2022-06-24 21:13:44 +00:00
|
|
|
});
|
2022-04-22 19:39:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct apfl_string *
|
|
|
|
|
new_copied_string(struct gc *gc, struct apfl_string_view sv)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_string s = apfl_string_blank();
|
|
|
|
|
if (!apfl_string_copy(gc->allocator, &s, sv)) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct apfl_string *out = apfl_string_move_into_new_gc_string(gc, &s);
|
|
|
|
|
if (out == NULL) {
|
|
|
|
|
apfl_string_deinit(gc->allocator, &s);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
2022-04-22 19:39:03 +00:00
|
|
|
apfl_push_string_view_copy(apfl_ctx ctx, struct apfl_string_view sv)
|
|
|
|
|
{
|
|
|
|
|
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
|
|
|
|
ctx,
|
|
|
|
|
VALUE_STRING,
|
|
|
|
|
string,
|
|
|
|
|
new_copied_string(&ctx->gc, sv)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
static bool
|
|
|
|
|
try_push_const_string(apfl_ctx ctx, const char *string)
|
2022-04-23 20:14:42 +00:00
|
|
|
{
|
|
|
|
|
return apfl_stack_push(ctx, (struct apfl_value) {
|
|
|
|
|
.type = VALUE_CONST_STRING,
|
|
|
|
|
.const_string = apfl_string_view_from(string),
|
2022-06-24 21:13:44 +00:00
|
|
|
});
|
2022-04-23 20:14:42 +00:00
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
|
|
|
|
apfl_push_const_string(apfl_ctx ctx, const char *string)
|
|
|
|
|
{
|
|
|
|
|
if (!try_push_const_string(ctx, string)) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
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
|
|
|
apfl_list_create(apfl_ctx ctx, size_t initial_cap)
|
|
|
|
|
{
|
|
|
|
|
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
|
|
|
|
ctx,
|
|
|
|
|
VALUE_LIST,
|
|
|
|
|
list,
|
|
|
|
|
apfl_list_new(&ctx->gc, initial_cap)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
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
|
|
|
apfl_list_append(apfl_ctx ctx, apfl_stackidx list_index, apfl_stackidx value_index)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value *list_val = stack_get_pointer(ctx, list_index);
|
|
|
|
|
if (list_val == NULL) {
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
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 (list_val->type != VALUE_LIST) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.not_a_list);
|
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
|
|
|
struct apfl_value value = apfl_stack_must_get(ctx, value_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
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
if (!apfl_list_splice(
|
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
|
|
|
&ctx->gc,
|
|
|
|
|
&list_val->list,
|
|
|
|
|
list_val->list->len,
|
|
|
|
|
0,
|
|
|
|
|
&value,
|
|
|
|
|
1
|
2022-06-24 21:13:44 +00:00
|
|
|
)) {
|
2022-07-11 19:41:05 +00:00
|
|
|
assert(apfl_stack_drop(ctx, value_index));
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
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
|
|
|
|
|
|
|
|
assert(apfl_stack_drop(ctx, value_index));
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
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
|
|
|
apfl_list_append_list(apfl_ctx ctx, apfl_stackidx dst_index, apfl_stackidx src_index)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value *dst_val = stack_get_pointer(ctx, dst_index);
|
|
|
|
|
if (dst_val == NULL) {
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
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 (dst_val->type != VALUE_LIST) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.not_a_list);
|
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
|
|
|
struct apfl_value src_val = apfl_stack_must_get(ctx, src_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
|
|
|
|
2022-04-24 14:45:25 +00:00
|
|
|
if (src_val.type != VALUE_LIST) {
|
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
|
|
|
assert(apfl_stack_drop(ctx, src_index));
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.not_a_list);
|
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-06-24 21:13:44 +00:00
|
|
|
if (!apfl_list_splice(
|
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
|
|
|
&ctx->gc,
|
|
|
|
|
&dst_val->list,
|
|
|
|
|
dst_val->list->len,
|
|
|
|
|
0,
|
|
|
|
|
src_val.list->items,
|
|
|
|
|
src_val.list->len
|
2022-06-24 21:13:44 +00:00
|
|
|
)) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
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
|
|
|
|
|
|
|
|
assert(apfl_stack_drop(ctx, src_index));
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
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
|
|
|
apfl_dict_create(apfl_ctx ctx)
|
|
|
|
|
{
|
|
|
|
|
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
|
|
|
|
ctx,
|
|
|
|
|
VALUE_DICT,
|
|
|
|
|
dict,
|
|
|
|
|
apfl_dict_new(&ctx->gc)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
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
|
|
|
apfl_dict_set(
|
|
|
|
|
apfl_ctx ctx,
|
|
|
|
|
apfl_stackidx dict_index,
|
|
|
|
|
apfl_stackidx k_index,
|
|
|
|
|
apfl_stackidx v_index
|
|
|
|
|
) {
|
|
|
|
|
struct apfl_value k;
|
|
|
|
|
struct apfl_value v;
|
|
|
|
|
struct apfl_value *dict_value;
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
!apfl_stack_get(ctx, &k, k_index)
|
|
|
|
|
|| !apfl_stack_get(ctx, &v, v_index)
|
|
|
|
|
|| (dict_value = stack_get_pointer(ctx, dict_index)) == NULL
|
|
|
|
|
) {
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
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 (dict_value->type != VALUE_DICT) {
|
|
|
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}));
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.not_a_dict);
|
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 (!apfl_dict_set_raw(&ctx->gc, &dict_value->dict, k, v)) {
|
|
|
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}));
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_alloc_error(ctx);
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}));
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
bool
|
|
|
|
|
apfl_get_member_if_exists(
|
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
|
|
|
apfl_ctx ctx,
|
|
|
|
|
apfl_stackidx container_index,
|
|
|
|
|
apfl_stackidx k_index
|
|
|
|
|
) {
|
|
|
|
|
struct apfl_value container;
|
|
|
|
|
struct apfl_value k;
|
|
|
|
|
if (
|
2022-07-11 19:41:05 +00:00
|
|
|
!current_stack_get_and_adjust_index(ctx, &container, &container_index)
|
|
|
|
|
|| !current_stack_get_and_adjust_index(ctx, &k, &k_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
|
|
|
) {
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct apfl_value *value = apfl_stack_push_placeholder(ctx);
|
|
|
|
|
if (value == NULL) {
|
|
|
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, container_index}));
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum get_item_result result = apfl_value_get_item(container, k, value);
|
|
|
|
|
if (result != GET_ITEM_OK) {
|
2022-11-19 21:06:23 +00:00
|
|
|
assert(apfl_stack_drop(ctx, -1)); // Drop the placeholder
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, container_index}));
|
2022-06-24 21:13:44 +00:00
|
|
|
|
|
|
|
|
switch (result) {
|
|
|
|
|
case GET_ITEM_OK:
|
2022-11-19 21:06:23 +00:00
|
|
|
return true;
|
2022-06-24 21:13:44 +00:00
|
|
|
case GET_ITEM_KEY_DOESNT_EXIST:
|
2022-11-19 21:06:23 +00:00
|
|
|
return false;
|
2022-06-24 21:13:44 +00:00
|
|
|
case GET_ITEM_NOT_A_CONTAINER:
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.value_is_not_a_container);
|
2022-06-24 21:13:44 +00:00
|
|
|
break;
|
|
|
|
|
case GET_ITEM_WRONG_KEY_TYPE:
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.wrong_key_type);
|
2022-06-24 21:13:44 +00:00
|
|
|
break;
|
|
|
|
|
}
|
2022-11-19 21:06:23 +00:00
|
|
|
|
|
|
|
|
assert(false);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_get_member(
|
|
|
|
|
apfl_ctx ctx,
|
|
|
|
|
apfl_stackidx container_index,
|
|
|
|
|
apfl_stackidx k_index
|
|
|
|
|
) {
|
|
|
|
|
if (!apfl_get_member_if_exists(ctx, container_index, k_index)) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.key_doesnt_exist);
|
2022-11-19 21:06:23 +00:00
|
|
|
}
|
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
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_get_list_member_by_index(
|
|
|
|
|
apfl_ctx ctx,
|
|
|
|
|
apfl_stackidx list_index,
|
|
|
|
|
size_t index
|
|
|
|
|
) {
|
|
|
|
|
struct apfl_value list = apfl_stack_must_get(ctx, list_index);
|
|
|
|
|
|
|
|
|
|
if (list.type != VALUE_LIST) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.not_a_list);
|
2022-07-11 19:41:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (index >= list.list->len) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.key_doesnt_exist);
|
2022-07-11 19:41:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct apfl_value *value = apfl_stack_push_placeholder(ctx);
|
|
|
|
|
if (value == NULL) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*value = apfl_value_set_cow_flag(list.list->items[index]);
|
2022-11-29 22:13:10 +00:00
|
|
|
}
|
2022-07-11 19:41:05 +00:00
|
|
|
|
2022-11-29 22:13:10 +00:00
|
|
|
void
|
|
|
|
|
apfl_set_list_member_by_index(
|
|
|
|
|
apfl_ctx ctx,
|
|
|
|
|
apfl_stackidx list_index,
|
|
|
|
|
size_t index_in_list,
|
|
|
|
|
apfl_stackidx value_index
|
|
|
|
|
) {
|
|
|
|
|
struct apfl_value *list = stack_get_pointer(ctx, list_index);
|
|
|
|
|
if (list == NULL) {
|
|
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (list->type != VALUE_LIST) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.not_a_list);
|
2022-11-29 22:13:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (index_in_list >= list->list->len) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.key_doesnt_exist);
|
2022-11-29 22:13:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct apfl_value value = apfl_stack_must_get(ctx, value_index);
|
|
|
|
|
|
|
|
|
|
if (!apfl_list_splice(
|
|
|
|
|
&ctx->gc,
|
|
|
|
|
&list->list,
|
|
|
|
|
index_in_list,
|
|
|
|
|
1,
|
|
|
|
|
&value,
|
|
|
|
|
1
|
|
|
|
|
)) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apfl_drop(ctx, value_index);
|
2022-07-11 19:41:05 +00:00
|
|
|
}
|
|
|
|
|
|
2022-07-12 20:13:07 +00:00
|
|
|
size_t
|
|
|
|
|
apfl_len(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value value = apfl_stack_must_get(ctx, index);
|
|
|
|
|
switch (value.type) {
|
|
|
|
|
case VALUE_NIL:
|
|
|
|
|
case VALUE_BOOLEAN:
|
|
|
|
|
case VALUE_NUMBER:
|
|
|
|
|
case VALUE_FUNC:
|
|
|
|
|
case VALUE_CFUNC:
|
|
|
|
|
case VALUE_USERDATA:
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.wrong_type);
|
2022-07-12 20:13:07 +00:00
|
|
|
return 0;
|
|
|
|
|
case VALUE_STRING:
|
|
|
|
|
return value.string->len;
|
|
|
|
|
case VALUE_CONST_STRING:
|
|
|
|
|
return value.const_string.len;
|
|
|
|
|
case VALUE_LIST:
|
|
|
|
|
return apfl_list_len(value.list);
|
|
|
|
|
case VALUE_DICT:
|
|
|
|
|
return apfl_dict_len(value.dict);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(false);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-20 15:26:38 +00:00
|
|
|
static bool
|
|
|
|
|
get_string_view_of_value(struct apfl_string_view *sv, struct apfl_value value)
|
2022-07-12 20:13:07 +00:00
|
|
|
{
|
|
|
|
|
switch (value.type) {
|
|
|
|
|
case VALUE_NIL:
|
|
|
|
|
case VALUE_BOOLEAN:
|
|
|
|
|
case VALUE_NUMBER:
|
|
|
|
|
case VALUE_FUNC:
|
|
|
|
|
case VALUE_CFUNC:
|
|
|
|
|
case VALUE_USERDATA:
|
|
|
|
|
case VALUE_LIST:
|
|
|
|
|
case VALUE_DICT:
|
2022-11-20 15:26:38 +00:00
|
|
|
return false;
|
2022-07-12 20:13:07 +00:00
|
|
|
case VALUE_STRING:
|
2022-11-20 15:26:38 +00:00
|
|
|
*sv = apfl_string_view_from(*value.string);
|
|
|
|
|
return true;
|
2022-07-12 20:13:07 +00:00
|
|
|
case VALUE_CONST_STRING:
|
2022-11-20 15:26:38 +00:00
|
|
|
*sv = value.const_string;
|
|
|
|
|
return true;
|
2022-07-12 20:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(false);
|
2022-11-20 15:26:38 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct apfl_string_view
|
|
|
|
|
apfl_get_string(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_string_view sv;
|
|
|
|
|
if (!get_string_view_of_value(&sv, apfl_stack_must_get(ctx, index))) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.wrong_type);
|
2022-11-20 15:26:38 +00:00
|
|
|
}
|
|
|
|
|
return sv;
|
2022-07-12 20:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
2022-07-12 21:24:19 +00:00
|
|
|
enum apfl_value_type
|
|
|
|
|
apfl_get_type(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value value = apfl_stack_must_get(ctx, index);
|
|
|
|
|
return apfl_value_type_to_abstract_type(value.type);
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-30 21:44:34 +00:00
|
|
|
void
|
|
|
|
|
apfl_tostring(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
apfl_move_to_top_of_stack(ctx, index);
|
|
|
|
|
struct apfl_value value = apfl_stack_must_get(ctx, -1);
|
|
|
|
|
if (apfl_value_type_to_abstract_type(value.type) == APFL_VALUE_STRING) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct apfl_string_builder sb = apfl_string_builder_init(ctx->gc.allocator);
|
|
|
|
|
if (!apfl_value_format(value, apfl_format_string_writer(&sb))) {
|
|
|
|
|
apfl_string_builder_deinit(&sb);
|
|
|
|
|
apfl_stack_drop(ctx, -1);
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct apfl_string str = apfl_string_builder_move_string(&sb);
|
|
|
|
|
apfl_string_builder_deinit(&sb);
|
|
|
|
|
if (!apfl_move_string_onto_stack(ctx, str)) {
|
|
|
|
|
apfl_string_deinit(ctx->gc.allocator, &str);
|
|
|
|
|
apfl_stack_drop(ctx, -1);
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apfl_stack_drop(ctx, -2); // Drop original value
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-26 22:06:55 +00:00
|
|
|
void
|
|
|
|
|
apfl_join_strings(apfl_ctx ctx, apfl_stackidx glue, apfl_stackidx parts)
|
2022-11-20 15:26:38 +00:00
|
|
|
{
|
2022-11-26 22:06:55 +00:00
|
|
|
apfl_multi_move_to_top_of_stack(ctx, 2, (apfl_stackidx[]){parts, glue});
|
|
|
|
|
parts = -2;
|
|
|
|
|
glue = -1;
|
2022-11-20 15:26:38 +00:00
|
|
|
|
2022-11-29 22:13:10 +00:00
|
|
|
if (apfl_get_type(ctx, parts) != APFL_VALUE_LIST) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.not_a_list);
|
2022-11-20 15:26:38 +00:00
|
|
|
}
|
2022-11-29 22:13:10 +00:00
|
|
|
size_t parts_len = apfl_len(ctx, parts);
|
2022-11-20 15:26:38 +00:00
|
|
|
|
2022-11-26 22:06:55 +00:00
|
|
|
apfl_tostring(ctx, glue);
|
|
|
|
|
struct apfl_string_view glue_sv = apfl_get_string(ctx, glue);
|
2022-11-20 15:26:38 +00:00
|
|
|
|
|
|
|
|
size_t total_length = 0;
|
|
|
|
|
|
2022-11-29 22:13:10 +00:00
|
|
|
for (size_t i = 0; i < parts_len; i++) {
|
2022-11-26 22:06:55 +00:00
|
|
|
// Convert values to strings, if necessary
|
2022-11-29 22:13:10 +00:00
|
|
|
apfl_get_list_member_by_index(ctx, parts, i);
|
|
|
|
|
if (apfl_get_type(ctx, -1) == APFL_VALUE_STRING) {
|
|
|
|
|
total_length += apfl_get_string(ctx, -1).len;
|
|
|
|
|
apfl_drop(ctx, -1);
|
|
|
|
|
} else {
|
2022-11-26 22:06:55 +00:00
|
|
|
apfl_tostring(ctx, -1);
|
2022-11-29 22:13:10 +00:00
|
|
|
total_length += apfl_get_string(ctx, -1).len;
|
|
|
|
|
apfl_set_list_member_by_index(ctx, -3, i, -1);
|
2022-11-26 22:06:55 +00:00
|
|
|
}
|
2022-11-20 15:26:38 +00:00
|
|
|
|
|
|
|
|
if (i > 0) {
|
|
|
|
|
total_length += glue_sv.len;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct apfl_string_builder sb = apfl_string_builder_init(ctx->gc.allocator);
|
|
|
|
|
if (!apfl_string_builder_prealloc(&sb, total_length)) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-29 22:13:10 +00:00
|
|
|
for (size_t i = 0; i < parts_len; i++) {
|
2022-11-20 15:26:38 +00:00
|
|
|
if (i > 0) {
|
|
|
|
|
if (!apfl_string_builder_append(&sb, glue_sv)) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-29 22:13:10 +00:00
|
|
|
apfl_get_list_member_by_index(ctx, parts, i);
|
2022-11-20 15:26:38 +00:00
|
|
|
|
2022-11-29 22:13:10 +00:00
|
|
|
if (!apfl_string_builder_append(&sb, apfl_get_string(ctx, -1))) {
|
2022-11-20 15:26:38 +00:00
|
|
|
goto error;
|
|
|
|
|
}
|
2022-11-29 22:13:10 +00:00
|
|
|
|
|
|
|
|
apfl_drop(ctx, -1);
|
2022-11-20 15:26:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct apfl_string str = apfl_string_builder_move_string(&sb);
|
|
|
|
|
apfl_string_builder_deinit(&sb);
|
|
|
|
|
if (!apfl_move_string_onto_stack(ctx, str)) {
|
|
|
|
|
apfl_string_deinit(ctx->gc.allocator, &str);
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-26 22:06:55 +00:00
|
|
|
// Drop the glue and list from the stack
|
|
|
|
|
apfl_drop(ctx, -2);
|
|
|
|
|
apfl_drop(ctx, -2);
|
2022-11-20 15:26:38 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
error:
|
2022-11-26 22:06:55 +00:00
|
|
|
// Drop the glue and list from the stack
|
|
|
|
|
apfl_drop(ctx, -1);
|
|
|
|
|
apfl_drop(ctx, -1);
|
2022-11-20 15:26:38 +00:00
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-12 21:24:19 +00:00
|
|
|
bool
|
|
|
|
|
apfl_is_truthy(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
2022-10-31 14:51:28 +00:00
|
|
|
struct apfl_value value = apfl_stack_must_pop(ctx, index);
|
2022-07-12 21:24:19 +00:00
|
|
|
switch (value.type) {
|
|
|
|
|
case VALUE_NIL:
|
|
|
|
|
return false;
|
|
|
|
|
case VALUE_BOOLEAN:
|
|
|
|
|
return value.boolean;
|
|
|
|
|
default:
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apfl_number
|
|
|
|
|
apfl_get_number(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value value = apfl_stack_must_pop(ctx, index);
|
|
|
|
|
if (value.type == VALUE_NUMBER) {
|
|
|
|
|
return value.number;
|
|
|
|
|
}
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.wrong_type);
|
2022-07-12 21:24:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
apfl_eq(apfl_ctx ctx, apfl_stackidx _a, apfl_stackidx _b)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value a = apfl_stack_must_get(ctx, _a);
|
|
|
|
|
struct apfl_value b = apfl_stack_must_get(ctx, _b);
|
|
|
|
|
|
|
|
|
|
bool eq = apfl_value_eq(a, b);
|
|
|
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){_a, _b}));
|
|
|
|
|
return eq;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-20 12:47:38 +00:00
|
|
|
int
|
|
|
|
|
apfl_cmp(apfl_ctx ctx, apfl_stackidx _a, apfl_stackidx _b)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value a = apfl_stack_must_get(ctx, _a);
|
|
|
|
|
struct apfl_value b = apfl_stack_must_get(ctx, _b);
|
|
|
|
|
|
|
|
|
|
enum comparison_result result = apfl_value_cmp(a, b);
|
|
|
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){_a, _b}));
|
|
|
|
|
|
|
|
|
|
switch (result) {
|
|
|
|
|
case CMP_LT:
|
|
|
|
|
return -1;
|
|
|
|
|
case CMP_EQ:
|
|
|
|
|
return 0;
|
|
|
|
|
case CMP_GT:
|
|
|
|
|
return 1;
|
|
|
|
|
case CMP_UNCOMPARABLE:
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.uncomparable);
|
2022-11-20 12:47:38 +00:00
|
|
|
case CMP_INCOMPATIBLE_TYPES:
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.incompatible_types);
|
2022-11-20 12:47:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(false);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
static struct scope *
|
2022-11-19 21:06:23 +00:00
|
|
|
closure_scope_for_func_inner(apfl_ctx ctx, struct scopes scopes)
|
2022-07-11 19:41:05 +00:00
|
|
|
{
|
|
|
|
|
struct scope *out = apfl_scope_new(&ctx->gc);
|
|
|
|
|
if (out == NULL) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(out, GC_TYPE_SCOPE))) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
// The order is important here: by merging the local scope last, we make
|
|
|
|
|
// sure a variable from the current scope shadows a variable from the
|
|
|
|
|
// closure scope
|
2022-07-11 19:41:05 +00:00
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
if (scopes.closure != NULL) {
|
|
|
|
|
if (!apfl_scope_merge_into(out, scopes.closure)) {
|
2022-07-11 19:41:05 +00:00
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
if (scopes.local != NULL) {
|
|
|
|
|
if (!apfl_scope_merge_into(out, scopes.local)) {
|
2022-07-11 19:41:05 +00:00
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct scope *
|
2022-11-19 21:06:23 +00:00
|
|
|
apfl_closure_scope_for_func(apfl_ctx ctx, struct scopes scopes)
|
2022-07-11 19:41:05 +00:00
|
|
|
{
|
|
|
|
|
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
|
2022-11-19 21:06:23 +00:00
|
|
|
struct scope *out = closure_scope_for_func_inner(ctx, scopes);
|
2022-07-11 19:41:05 +00:00
|
|
|
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_push_cfunc(apfl_ctx ctx, apfl_cfunc cfunc, size_t nslots)
|
|
|
|
|
{
|
|
|
|
|
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
|
|
|
|
ctx,
|
|
|
|
|
VALUE_CFUNC,
|
|
|
|
|
cfunc,
|
|
|
|
|
apfl_cfunc_new(&ctx->gc, cfunc, nslots)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static APFL_NORETURN void
|
|
|
|
|
raise_no_cfunc(apfl_ctx ctx)
|
|
|
|
|
{
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.not_a_c_function);
|
2022-07-11 19:41:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct cfunction *
|
|
|
|
|
must_get_cfunc(apfl_ctx ctx, apfl_stackidx idx)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value value = apfl_stack_must_get(ctx, idx);
|
|
|
|
|
if (value.type != VALUE_CFUNC) {
|
|
|
|
|
raise_no_cfunc(ctx);
|
|
|
|
|
}
|
|
|
|
|
return value.cfunc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct cfunction *
|
|
|
|
|
must_get_cfunc_self(apfl_ctx ctx)
|
|
|
|
|
{
|
|
|
|
|
struct call_stack_entry *entry = apfl_call_stack_cur_entry(ctx);
|
|
|
|
|
if (entry == NULL) {
|
|
|
|
|
raise_no_cfunc(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (entry->type != CSE_CFUNCTION) {
|
|
|
|
|
raise_no_cfunc(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return entry->cfunc.func;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct apfl_value **
|
|
|
|
|
cfunc_getslotvar(apfl_ctx ctx, struct cfunction *cfunction, apfl_slotidx slot)
|
|
|
|
|
{
|
|
|
|
|
if (slot >= cfunction->slots_len) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.invalid_slotidx);
|
2022-07-11 19:41:05 +00:00
|
|
|
}
|
|
|
|
|
return &cfunction->slots[slot];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
cfunc_getslot(apfl_ctx ctx, struct cfunction *cfunction, apfl_slotidx slot)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value **var = cfunc_getslotvar(ctx, cfunction, slot);
|
|
|
|
|
if (*var == NULL) {
|
|
|
|
|
apfl_push_nil(ctx);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The value is now in the slot and on the stack. We need to set the COW
|
|
|
|
|
// flag so a mutation of one copy doesn't affect the other one.
|
|
|
|
|
apfl_stack_must_push(ctx, apfl_value_set_cow_flag(**var));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_cfunc_getslot(apfl_ctx ctx, apfl_stackidx cfunc, apfl_slotidx slot)
|
|
|
|
|
{
|
|
|
|
|
cfunc_getslot(ctx, must_get_cfunc(ctx, cfunc), slot);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_cfunc_self_getslot(apfl_ctx ctx, apfl_slotidx slot)
|
|
|
|
|
{
|
|
|
|
|
cfunc_getslot(ctx, must_get_cfunc_self(ctx), slot);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
cfunc_setslot(apfl_ctx ctx, struct cfunction *cfunction, apfl_slotidx slot, apfl_stackidx value)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value **var = cfunc_getslotvar(ctx, cfunction, slot);
|
|
|
|
|
if (*var == NULL) {
|
|
|
|
|
*var = apfl_gc_new_var(&ctx->gc);
|
|
|
|
|
if (*var == NULL) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
**var = (struct apfl_value) { .type = VALUE_NIL };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
**var = apfl_stack_must_pop(ctx, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_cfunc_setslot(apfl_ctx ctx, apfl_stackidx cfunc, apfl_slotidx slot, apfl_stackidx value)
|
|
|
|
|
{
|
|
|
|
|
cfunc_setslot(ctx, must_get_cfunc(ctx, cfunc), slot, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_cfunc_self_setslot(apfl_ctx ctx, apfl_slotidx slot, apfl_stackidx value)
|
|
|
|
|
{
|
|
|
|
|
cfunc_setslot(ctx, must_get_cfunc_self(ctx), slot, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_push_userdata(apfl_ctx ctx, void *userdata)
|
|
|
|
|
{
|
|
|
|
|
apfl_stack_must_push(ctx, (struct apfl_value) {
|
|
|
|
|
.type = VALUE_USERDATA,
|
|
|
|
|
.userdata = userdata,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void *apfl_get_userdata(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value value = apfl_stack_must_pop(ctx, index);
|
|
|
|
|
if (value.type != VALUE_USERDATA) {
|
2022-12-09 20:22:50 +00:00
|
|
|
apfl_raise_const_error(ctx, apfl_messages.wrong_type);
|
2022-07-11 19:41:05 +00:00
|
|
|
}
|
|
|
|
|
return value.userdata;
|
|
|
|
|
}
|
2022-10-28 19:32:17 +00:00
|
|
|
|
|
|
|
|
struct apfl_format_writer apfl_get_output_writer(apfl_ctx ctx)
|
|
|
|
|
{
|
|
|
|
|
return ctx->output_writer;
|
|
|
|
|
}
|
2022-11-19 21:06:23 +00:00
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
init_values_list(struct apfl_allocator allocator, struct apfl_value **list, size_t len)
|
|
|
|
|
{
|
|
|
|
|
if (len == 0) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((*list = ALLOC_LIST(allocator, struct apfl_value, len)) == NULL) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < len; i++) {
|
|
|
|
|
(*list)[i] = (struct apfl_value) { .type = VALUE_NIL };
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct matcher *
|
|
|
|
|
apfl_matcher_new(struct gc *gc, struct matcher_instruction_list *milist)
|
|
|
|
|
{
|
|
|
|
|
struct matcher matcher = {
|
|
|
|
|
.instructions = milist,
|
|
|
|
|
.value_count = 0,
|
|
|
|
|
.values = NULL,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (!init_values_list(gc->allocator, &matcher.values, milist->value_count)) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
matcher.value_count = milist->value_count;
|
|
|
|
|
|
|
|
|
|
struct matcher *gc_matcher = apfl_gc_new_matcher(gc);
|
|
|
|
|
if (gc_matcher == NULL) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*gc_matcher = matcher;
|
|
|
|
|
|
|
|
|
|
return gc_matcher;
|
|
|
|
|
|
|
|
|
|
error:
|
|
|
|
|
apfl_matcher_deinit(gc->allocator, &matcher);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_matcher_deinit(struct apfl_allocator allocator, struct matcher *matcher)
|
|
|
|
|
{
|
|
|
|
|
FREE_LIST(allocator, matcher->values, matcher->value_count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_gc_matcher_traverse(struct matcher *matcher, gc_visitor visitor, void *opaque)
|
|
|
|
|
{
|
|
|
|
|
visitor(opaque, GC_OBJECT_FROM(matcher->instructions, GC_TYPE_MATCHER_INSTRUCTIONS));
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < matcher->instructions->value_count; i++) {
|
|
|
|
|
apfl_value_visit_gc_object(matcher->values[i], visitor, opaque);
|
|
|
|
|
}
|
|
|
|
|
}
|