apfl/src/context.c

1304 lines
32 KiB
C
Raw Normal View History

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>
#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"
#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"
static bool try_push_const_string(apfl_ctx ctx, const char *string);
static bool current_stack_move_to_top(apfl_ctx, apfl_stackidx);
static struct apfl_string *new_copied_string(struct gc *, struct apfl_string_view);
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
apfl_call_protected(apfl_ctx ctx, apfl_stackidx func, apfl_stackidx args, bool *with_error_on_stack)
{
struct error_handler *prev_handler = ctx->error_handler;
size_t callstack_len = ctx->call_stack.len;
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) {
apfl_call(ctx, func, args);
} 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);
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
)
);
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;
}
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
apfl_raise_error_with_type(apfl_ctx ctx, apfl_stackidx idx, enum apfl_result type)
{
bool ok = current_stack_move_to_top(ctx, idx);
raise_error(ctx, ok, type);
}
APFL_NORETURN void
apfl_raise_const_error(apfl_ctx ctx, enum apfl_result type, const char *message)
{
raise_error(ctx, try_push_const_string(ctx, message), type);
}
APFL_NORETURN void
apfl_raise_alloc_error(apfl_ctx ctx)
{
apfl_raise_const_error(ctx, APFL_RESULT_ERR_FATAL, apfl_messages.could_not_alloc_mem);
}
APFL_NORETURN void
apfl_raise_invalid_stackidx(apfl_ctx ctx)
{
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.invalid_stack_index);
}
APFL_NORETURN void
apfl_raise_error_object(apfl_ctx ctx, struct apfl_error error)
{
enum apfl_result errtype = APFL_RESULT_ERR;
if (apfl_error_is_fatal_type(error.type)) {
errtype = APFL_RESULT_ERR_FATAL;
}
const char *const_str = apfl_error_as_const_string(error);
if (const_str != NULL) {
apfl_raise_const_error(ctx, errtype, const_str);
}
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);
}
apfl_raise_error_with_type(ctx, -1, errtype);
}
void
apfl_ctx_set_panic_callback(apfl_ctx ctx, apfl_panic_callback cb, void *opaque)
{
ctx->panic_callback = cb;
ctx->panic_callback_data = opaque;
}
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
{
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,
};
}
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;
}
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)
{
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),
(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
);
}
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) {
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;
}
*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;
}
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;
}
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;
}
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
{
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++) {
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),
(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)
{
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;
}
*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),
(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;
}
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)
{
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;
}
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
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
{
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;
}
*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;
}
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)
{
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
}
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);
}
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);
}
void
apfl_drop(apfl_ctx ctx, apfl_stackidx index)
{
if (!apfl_stack_drop(ctx, index)) {
apfl_raise_invalid_stackidx(ctx);
}
}
static bool
current_stack_move_to_top(apfl_ctx ctx, apfl_stackidx index)
{
struct stack *stack = current_value_stack(ctx);
struct apfl_value val;
if (!stack_get_and_adjust_index(stack, &val, &index)) {
return false;
}
size_t absindex = (size_t)index;
// If we're here, index is an absolute address and is guaranteed to be < len
assert(stack->len >= absindex+1);
memmove(
&stack->items[absindex + 1],
&stack->items[absindex],
stack->len - absindex - 1
);
stack->items[stack->len-1] = val;
return true;
}
2022-04-21 19:15:20 +00:00
void
apfl_stack_clear(apfl_ctx ctx)
{
struct stack *stack = current_value_stack(ctx);
stack->len = 0;
}
static void
stack_traverse(struct stack stack, gc_visitor visitor, void *opaque)
{
for (size_t i = 0; i < stack.len; i++) {
struct gc_object *object = apfl_value_get_gc_object(stack.items[i]);
if (object != NULL) {
visitor(opaque, object);
}
}
2022-04-21 19:15:20 +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
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:
visitor(
opaque,
GC_OBJECT_FROM(cse.func.instructions, GC_TYPE_INSTRUCTIONS)
);
visit_nullable_scope(cse.func.scope, visitor, opaque);
visit_nullable_scope(cse.func.closure_scope, visitor, opaque);
if (cse.func.matcher != NULL) {
visitor(
opaque,
GC_OBJECT_FROM(cse.func.matcher, GC_TYPE_MATCHER)
);
}
break;
case CSE_CFUNCTION:
visitor(
opaque,
GC_OBJECT_FROM(cse.cfunc.func, GC_TYPE_CFUNC)
);
break;
case CSE_MATCHER:
visitor(
opaque,
GC_OBJECT_FROM(cse.matcher.matcher, GC_TYPE_MATCHER)
);
break;
case CSE_FUNCTION_DISPATCH:
visitor(
opaque,
GC_OBJECT_FROM(cse.func_dispatch.function, GC_TYPE_FUNC)
);
break;
}
}
static void
get_roots(void *own_opaque, gc_visitor visitor, void *visitor_opaque)
{
apfl_ctx ctx = own_opaque;
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);
}
}
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();
}
void
apfl_call_stack_entry_deinit(struct apfl_allocator allocator, struct call_stack_entry *entry)
{
deinit_stack(allocator, &entry->stack);
switch (entry->type) {
case CSE_FUNCTION:
case CSE_CFUNCTION:
case CSE_FUNCTION_DISPATCH:
break;
case CSE_MATCHER:
FREE_LIST(allocator, entry->matcher.matcher_stack, entry->matcher.matcher_stack_cap);
break;
}
}
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,
};
}
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
apfl_ctx_new(struct apfl_allocator base_allocator)
{
apfl_ctx ctx = ALLOC_OBJ(base_allocator, struct apfl_ctx_data);
if (ctx == NULL) {
return NULL;
}
// 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;
ctx->toplevel_stack = apfl_stack_new();
ctx->call_stack = call_stack_new();
ctx->globals = NULL;
ctx->iterative_runners = iterative_runners_list_new();
apfl_gc_init(&ctx->gc, base_allocator, get_roots, ctx);
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;
}
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;
}
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);
}
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;
}
struct iterative_runner_tmproot_data {
struct gc *gc;
bool ok;
};
static void
ctx_register_iterative_runner_tmproot(void *opaque, struct gc_object *object)
{
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;
}
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
);
}
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;
}
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
)
);
}
#define CREATE_GC_OBJECT_VALUE_ON_STACK(ctx, TYPE, MEMB, NEW) \
struct apfl_value *value = apfl_stack_push_placeholder(ctx); \
if (value == NULL) { \
apfl_raise_alloc_error(ctx); \
} \
\
struct apfl_value new_value = {.type = TYPE}; \
if ((new_value.MEMB = NEW) == NULL) { \
assert(apfl_stack_drop(ctx, -1)); \
apfl_raise_alloc_error(ctx); \
} \
\
*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
void
apfl_push_nil(apfl_ctx ctx)
{
apfl_stack_must_push(ctx, (struct apfl_value) {
.type = VALUE_NIL,
});
}
void
apfl_push_bool(apfl_ctx ctx, bool b)
{
apfl_stack_must_push(ctx, (struct apfl_value) {
.type = VALUE_BOOLEAN,
.boolean = b,
});
}
void
apfl_push_number(apfl_ctx ctx, apfl_number num)
{
apfl_stack_must_push(ctx, (struct apfl_value) {
.type = VALUE_NUMBER,
.number = num,
});
}
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;
}
void
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)
)
}
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-04-23 20:14:42 +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)
)
}
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) {
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) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, 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
}
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
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
)) {
assert(apfl_stack_drop(ctx, value_index));
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));
}
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) {
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) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, 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
}
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
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));
apfl_raise_const_error(ctx, APFL_RESULT_ERR, 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
}
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
)) {
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));
}
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)
)
}
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
) {
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}));
apfl_raise_const_error(ctx, APFL_RESULT_ERR, 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}));
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}));
}
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_get_member(
apfl_ctx ctx,
apfl_stackidx container_index,
apfl_stackidx k_index
) {
struct apfl_value container;
struct apfl_value k;
if (
!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
) {
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) {
assert(apfl_stack_drop(ctx, -1));
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}));
switch (result) {
case GET_ITEM_OK:
break;
case GET_ITEM_KEY_DOESNT_EXIST:
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.key_doesnt_exist);
break;
case GET_ITEM_NOT_A_CONTAINER:
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.value_is_not_a_container);
break;
case GET_ITEM_WRONG_KEY_TYPE:
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.wrong_key_type);
break;
}
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
}
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) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_list);
}
if (index >= list.list->len) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.key_doesnt_exist);
}
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]);
return;
}
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:
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.wrong_type);
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;
}
struct apfl_string_view
apfl_get_string(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:
case VALUE_LIST:
case VALUE_DICT:
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.wrong_type);
goto error;
case VALUE_STRING:
return apfl_string_view_from(*value.string);
case VALUE_CONST_STRING:
return value.const_string;
}
error:
assert(false);
return (struct apfl_string_view) {.bytes = NULL, .len = 0};
}
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);
}
bool
apfl_is_truthy(apfl_ctx ctx, apfl_stackidx index)
{
struct apfl_value value = apfl_stack_must_get(ctx, index);
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;
}
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.wrong_type);
}
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;
}
static struct scope *
closure_scope_for_func_inner(apfl_ctx ctx)
{
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;
}
struct call_stack_entry *entry = apfl_call_stack_cur_entry(ctx);
if (entry == NULL || entry->type != CSE_FUNCTION) {
return out;
}
// The order is important here: by merging entry->scope last, we make sure a
// variable from the current scope shadows a variable from the closure scope
if (entry->func.closure_scope != NULL) {
if (!apfl_scope_merge_into(out, entry->func.closure_scope)) {
return NULL;
}
}
if (entry->func.scope != NULL) {
if (!apfl_scope_merge_into(out, entry->func.scope)) {
return NULL;
}
}
return out;
}
struct scope *
apfl_closure_scope_for_func(apfl_ctx ctx)
{
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
struct scope *out = closure_scope_for_func_inner(ctx);
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)
{
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_c_function);
}
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) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.invalid_slotidx);
}
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) {
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.wrong_type);
}
return value.userdata;
}