Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
#include <assert.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
2022-06-24 21:13:44 +00:00
|
|
|
#include <setjmp.h>
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
|
|
|
|
#include "apfl.h"
|
|
|
|
|
#include "alloc.h"
|
|
|
|
|
#include "context.h"
|
|
|
|
|
#include "gc.h"
|
2022-07-12 20:13:07 +00:00
|
|
|
#include "globals.h"
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
#include "hashmap.h"
|
|
|
|
|
#include "resizable.h"
|
2022-04-22 21:17:28 +00:00
|
|
|
#include "strings.h"
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
#include "value.h"
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
static bool try_push_const_string(apfl_ctx ctx, const char *string);
|
2022-07-11 19:41:05 +00:00
|
|
|
static bool current_stack_move_to_top(apfl_ctx, apfl_stackidx);
|
2022-07-12 20:13:07 +00:00
|
|
|
static struct apfl_string *new_copied_string(struct gc *, struct apfl_string_view);
|
2022-06-24 21:13:44 +00:00
|
|
|
|
|
|
|
|
APFL_NORETURN static void
|
|
|
|
|
panic(apfl_ctx ctx, bool with_error_on_stack, enum apfl_result result)
|
|
|
|
|
{
|
|
|
|
|
(void)ctx;
|
|
|
|
|
(void)result;
|
|
|
|
|
(void)with_error_on_stack;
|
|
|
|
|
fprintf(stderr, "panic!\n");
|
|
|
|
|
// TODO: more details
|
|
|
|
|
abort();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum apfl_result
|
2022-07-11 19:41:05 +00:00
|
|
|
apfl_call_protected(apfl_ctx ctx, apfl_stackidx func, apfl_stackidx args, bool *with_error_on_stack)
|
2022-06-24 21:13:44 +00:00
|
|
|
{
|
|
|
|
|
struct error_handler *prev_handler = ctx->error_handler;
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
size_t callstack_len = ctx->call_stack.len;
|
2022-06-24 21:13:44 +00:00
|
|
|
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
|
|
|
|
|
|
|
|
|
|
struct error_handler handler;
|
|
|
|
|
ctx->error_handler = &handler;
|
|
|
|
|
|
|
|
|
|
enum apfl_result result = APFL_RESULT_OK;
|
|
|
|
|
int rv = setjmp(handler.jump);
|
|
|
|
|
if (rv == 0) {
|
2022-07-11 19:41:05 +00:00
|
|
|
apfl_call(ctx, func, args);
|
2022-06-24 21:13:44 +00:00
|
|
|
} else {
|
|
|
|
|
result = (enum apfl_result)(rv - RESULT_OFF_FOR_LONGJMP);
|
|
|
|
|
assert(result != APFL_RESULT_OK);
|
|
|
|
|
|
|
|
|
|
struct apfl_value err;
|
|
|
|
|
if ((*with_error_on_stack = handler.with_error_on_stack)) {
|
|
|
|
|
if (!apfl_stack_pop(ctx, &err, -1)) {
|
|
|
|
|
*with_error_on_stack = false;
|
|
|
|
|
// TODO: Indicate error during error handling
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
|
2022-07-11 19:41:05 +00:00
|
|
|
assert(callstack_len <= ctx->call_stack.cap);
|
|
|
|
|
|
|
|
|
|
for (size_t i = callstack_len; i < ctx->call_stack.len; i++) {
|
|
|
|
|
apfl_call_stack_entry_deinit(ctx->gc.allocator, &ctx->call_stack.items[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(
|
|
|
|
|
// Shrinking should not fail
|
|
|
|
|
apfl_resizable_resize(
|
|
|
|
|
ctx->gc.allocator,
|
|
|
|
|
sizeof(struct call_stack_entry),
|
|
|
|
|
(void **)&ctx->call_stack.items,
|
|
|
|
|
&ctx->call_stack.len,
|
|
|
|
|
&ctx->call_stack.cap,
|
|
|
|
|
callstack_len
|
|
|
|
|
)
|
|
|
|
|
);
|
2022-06-24 21:13:44 +00:00
|
|
|
|
|
|
|
|
if (*with_error_on_stack) {
|
|
|
|
|
if (!apfl_stack_push(ctx, err)) {
|
|
|
|
|
*with_error_on_stack = false;
|
|
|
|
|
// TODO: Indicate error during error handling
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx->error_handler = prev_handler;
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
bool ok = current_stack_move_to_top(ctx, idx);
|
2022-06-24 21:13:44 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
struct stack
|
|
|
|
|
apfl_stack_new(void)
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
{
|
2022-07-01 20:40:43 +00:00
|
|
|
return (struct stack) {
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
.items = NULL,
|
|
|
|
|
.len = 0,
|
|
|
|
|
.cap = 0,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
static struct stack *
|
|
|
|
|
current_value_stack(apfl_ctx ctx)
|
|
|
|
|
{
|
|
|
|
|
struct call_stack_entry *csentry = apfl_call_stack_cur_entry(ctx);
|
|
|
|
|
return csentry != NULL
|
|
|
|
|
? &csentry->stack
|
|
|
|
|
: &ctx->toplevel_stack;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
|
|
|
|
apfl_stack_must_push(apfl_ctx ctx, struct apfl_value value)
|
|
|
|
|
{
|
|
|
|
|
if (!apfl_stack_push(ctx, value)) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
bool
|
|
|
|
|
apfl_stack_push(apfl_ctx ctx, struct apfl_value value)
|
|
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
struct stack *stack = current_value_stack(ctx);
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
return apfl_resizable_append(
|
|
|
|
|
ctx->gc.allocator,
|
|
|
|
|
sizeof(struct apfl_value),
|
2022-07-11 19:41:05 +00:00
|
|
|
(void **)&stack->items,
|
|
|
|
|
&stack->len,
|
|
|
|
|
&stack->cap,
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
&value,
|
|
|
|
|
1
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
static bool
|
|
|
|
|
stack_check_index(struct stack *stack, apfl_stackidx *index)
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
{
|
|
|
|
|
if (*index < 0) {
|
2022-07-11 19:41:05 +00:00
|
|
|
if ((size_t)-*index > stack->len) {
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
2022-07-11 19:41:05 +00:00
|
|
|
*index = stack->len + *index;
|
|
|
|
|
} else if ((size_t)*index >= stack->len) {
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
assert(0 <= *index && (size_t)*index < stack->len);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
bool
|
|
|
|
|
apfl_stack_check_index(apfl_ctx ctx, apfl_stackidx *index)
|
|
|
|
|
{
|
|
|
|
|
return stack_check_index(current_value_stack(ctx), index);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
apfl_stack_has_index(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
return apfl_stack_check_index(ctx, &index);
|
|
|
|
|
}
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
static int
|
|
|
|
|
cmp_stackidx(const void *_a, const void *_b)
|
|
|
|
|
{
|
|
|
|
|
const apfl_stackidx *a = _a;
|
|
|
|
|
const apfl_stackidx *b = _b;
|
|
|
|
|
return *a - *b;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
bool
|
|
|
|
|
apfl_stack_drop_multi(apfl_ctx ctx, size_t count, apfl_stackidx *indices)
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
struct stack *stack = current_value_stack(ctx);
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
for (size_t i = 0; i < count; i++) {
|
2022-07-11 19:41:05 +00:00
|
|
|
if (!stack_check_index(stack, &indices[i])) {
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qsort(indices, count, sizeof(apfl_stackidx), cmp_stackidx);
|
|
|
|
|
|
|
|
|
|
for (size_t i = count; i-- > 0; ) {
|
|
|
|
|
// Will not fail, as we've already checked the indices
|
|
|
|
|
assert(apfl_resizable_cut_without_resize(
|
|
|
|
|
sizeof(struct apfl_value),
|
2022-07-11 19:41:05 +00:00
|
|
|
(void **)&stack->items,
|
|
|
|
|
&stack->len,
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
indices[i],
|
|
|
|
|
1
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Shrink stack
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
apfl_stack_pop(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index)
|
|
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
struct stack *stack = current_value_stack(ctx);
|
|
|
|
|
|
|
|
|
|
if (!stack_check_index(stack, &index)) {
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
*value = stack->items[index];
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
|
|
|
|
assert(apfl_resizable_splice(
|
|
|
|
|
ctx->gc.allocator,
|
|
|
|
|
sizeof(struct apfl_value),
|
2022-07-11 19:41:05 +00:00
|
|
|
(void **)&stack->items,
|
|
|
|
|
&stack->len,
|
|
|
|
|
&stack->cap,
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
index,
|
|
|
|
|
1,
|
|
|
|
|
NULL,
|
|
|
|
|
0
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
struct apfl_value
|
|
|
|
|
apfl_stack_must_pop(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value value;
|
|
|
|
|
if (!apfl_stack_pop(ctx, &value, index)) {
|
|
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
|
|
|
}
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
static struct apfl_value *
|
|
|
|
|
stack_get_pointer(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
struct stack *stack = current_value_stack(ctx);
|
|
|
|
|
|
|
|
|
|
if (!stack_check_index(stack, &index)) {
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
return &stack->items[index];
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
2022-07-11 19:41:05 +00:00
|
|
|
stack_get_and_adjust_index(struct stack *stack, struct apfl_value *value, apfl_stackidx *index)
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
if (!stack_check_index(stack, index)) {
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
*value = stack->items[*index];
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
static bool
|
|
|
|
|
current_stack_get_and_adjust_index(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx *index)
|
|
|
|
|
{
|
|
|
|
|
return stack_get_and_adjust_index(current_value_stack(ctx), value, index);
|
|
|
|
|
}
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
bool
|
|
|
|
|
apfl_stack_get(apfl_ctx ctx, struct apfl_value *value, apfl_stackidx index)
|
|
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
return current_stack_get_and_adjust_index(ctx, value, &index);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct apfl_value
|
|
|
|
|
apfl_stack_must_get(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value value;
|
|
|
|
|
if (!apfl_stack_get(ctx, &value, index)) {
|
|
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
|
|
|
}
|
|
|
|
|
return value;
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
2022-04-21 20:40:27 +00:00
|
|
|
struct apfl_value *
|
|
|
|
|
apfl_stack_push_placeholder(apfl_ctx ctx)
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
{
|
|
|
|
|
if (!apfl_stack_push(ctx, (struct apfl_value) {.type = VALUE_NIL})) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return stack_get_pointer(ctx, -1);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
bool
|
|
|
|
|
apfl_move_string_onto_stack(apfl_ctx ctx, struct apfl_string string)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value *value = apfl_stack_push_placeholder(ctx);
|
|
|
|
|
if (value == NULL) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((value->string = apfl_string_move_into_new_gc_string(&ctx->gc, &string)) == NULL) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
value->type = VALUE_STRING;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
bool
|
|
|
|
|
apfl_stack_drop(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value value;
|
|
|
|
|
return apfl_stack_pop(ctx, &value, index);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-12 20:13:07 +00:00
|
|
|
void
|
|
|
|
|
apfl_drop(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
if (!apfl_stack_drop(ctx, index)) {
|
|
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
static bool
|
|
|
|
|
current_stack_move_to_top(apfl_ctx ctx, apfl_stackidx index)
|
2022-06-24 21:13:44 +00:00
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
struct stack *stack = current_value_stack(ctx);
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
struct apfl_value val;
|
2022-07-11 19:41:05 +00:00
|
|
|
if (!stack_get_and_adjust_index(stack, &val, &index)) {
|
2022-06-24 21:13:44 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t absindex = (size_t)index;
|
|
|
|
|
|
|
|
|
|
// If we're here, index is an absolute address and is guaranteed to be < len
|
2022-07-11 19:41:05 +00:00
|
|
|
assert(stack->len >= absindex+1);
|
2022-06-24 21:13:44 +00:00
|
|
|
|
|
|
|
|
memmove(
|
2022-07-11 19:41:05 +00:00
|
|
|
&stack->items[absindex + 1],
|
|
|
|
|
&stack->items[absindex],
|
|
|
|
|
stack->len - absindex - 1
|
2022-06-24 21:13:44 +00:00
|
|
|
);
|
2022-07-11 19:41:05 +00:00
|
|
|
stack->items[stack->len-1] = val;
|
2022-06-24 21:13:44 +00:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-21 19:15:20 +00:00
|
|
|
void
|
|
|
|
|
apfl_stack_clear(apfl_ctx ctx)
|
|
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
struct stack *stack = current_value_stack(ctx);
|
|
|
|
|
stack->len = 0;
|
2022-07-01 20:40:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2022-07-11 19:41:05 +00:00
|
|
|
stack_traverse(struct stack stack, gc_visitor visitor, void *opaque)
|
2022-07-01 20:40:43 +00:00
|
|
|
{
|
2022-07-11 19:41:05 +00:00
|
|
|
for (size_t i = 0; i < stack.len; i++) {
|
|
|
|
|
struct gc_object *object = apfl_value_get_gc_object(stack.items[i]);
|
2022-07-01 20:40:43 +00:00
|
|
|
if (object != NULL) {
|
|
|
|
|
visitor(opaque, object);
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-04-21 19:15:20 +00:00
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
static void
|
|
|
|
|
visit_nullable_scope(struct scope *scope, gc_visitor visitor, void *opaque)
|
|
|
|
|
{
|
|
|
|
|
if (scope != NULL) {
|
|
|
|
|
visitor(opaque, GC_OBJECT_FROM(scope, GC_TYPE_SCOPE));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
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);
|
|
|
|
|
break;
|
|
|
|
|
case CSE_CFUNCTION:
|
|
|
|
|
visitor(
|
|
|
|
|
opaque,
|
|
|
|
|
GC_OBJECT_FROM(cse.cfunc.func, GC_TYPE_CFUNC)
|
|
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-01 20:00:58 +00:00
|
|
|
static void
|
|
|
|
|
get_roots(void *own_opaque, gc_visitor visitor, void *visitor_opaque)
|
|
|
|
|
{
|
|
|
|
|
apfl_ctx ctx = own_opaque;
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
if (ctx->globals != NULL) {
|
|
|
|
|
visitor(visitor_opaque, GC_OBJECT_FROM(ctx->globals, GC_TYPE_SCOPE));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stack_traverse(ctx->toplevel_stack, visitor, visitor_opaque);
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < ctx->call_stack.len; i++) {
|
|
|
|
|
gc_traverse_call_stack_entry(ctx->call_stack.items[i], visitor, visitor_opaque);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < ctx->iterative_runners.len; i++) {
|
|
|
|
|
apfl_iterative_runner_visit_gc_objects(ctx->iterative_runners.items[i], visitor, visitor_opaque);
|
2022-07-01 20:00:58 +00:00
|
|
|
}
|
2022-07-11 19:41:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct call_stack
|
|
|
|
|
call_stack_new(void)
|
|
|
|
|
{
|
|
|
|
|
return (struct call_stack) {
|
|
|
|
|
.items = NULL,
|
|
|
|
|
.len = 0,
|
|
|
|
|
.cap = 0,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
deinit_stack(struct apfl_allocator allocator, struct stack *stack)
|
|
|
|
|
{
|
|
|
|
|
FREE_LIST(allocator, stack->items, stack->cap);
|
|
|
|
|
*stack = apfl_stack_new();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_call_stack_entry_deinit(struct apfl_allocator allocator, struct call_stack_entry *entry)
|
|
|
|
|
{
|
|
|
|
|
deinit_stack(allocator, &entry->stack);
|
|
|
|
|
}
|
2022-07-01 20:00:58 +00:00
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
struct call_stack_entry *
|
|
|
|
|
apfl_call_stack_cur_entry(apfl_ctx ctx)
|
|
|
|
|
{
|
|
|
|
|
return ctx->call_stack.len == 0
|
|
|
|
|
? NULL
|
|
|
|
|
: &ctx->call_stack.items[ctx->call_stack.len - 1];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct iterative_runners_list
|
|
|
|
|
iterative_runners_list_new(void)
|
|
|
|
|
{
|
|
|
|
|
return (struct iterative_runners_list) {
|
|
|
|
|
.items = NULL,
|
|
|
|
|
.len = 0,
|
|
|
|
|
.cap = 0,
|
|
|
|
|
};
|
2022-07-01 20:00:58 +00:00
|
|
|
}
|
|
|
|
|
|
2022-07-12 20:13:07 +00:00
|
|
|
static bool
|
|
|
|
|
init_globals(apfl_ctx ctx)
|
|
|
|
|
{
|
|
|
|
|
struct gc *gc = &ctx->gc;
|
|
|
|
|
|
|
|
|
|
size_t tmproots = apfl_gc_tmproots_begin(gc);
|
|
|
|
|
|
|
|
|
|
for (
|
|
|
|
|
const struct global_def *global = apfl_globals();
|
|
|
|
|
global->name != NULL && global->func != NULL;
|
|
|
|
|
global++
|
|
|
|
|
) {
|
|
|
|
|
struct cfunction *cfunc = apfl_cfunc_new(gc, global->func, 0);
|
|
|
|
|
if (cfunc == NULL) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!apfl_gc_tmproot_add(gc, GC_OBJECT_FROM(cfunc, GC_TYPE_CFUNC))) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: It's silly that we need to copy the name into a new gc string.
|
|
|
|
|
// It should be possible for the scope to also have a const string
|
|
|
|
|
// as a name.
|
|
|
|
|
struct apfl_string *name = new_copied_string(gc, apfl_string_view_from(global->name));
|
|
|
|
|
if (name == NULL) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!apfl_gc_tmproot_add(gc, GC_OBJECT_FROM(name, GC_TYPE_STRING))) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!apfl_scope_set(gc, ctx->globals, name, (struct apfl_value) {
|
|
|
|
|
.type = VALUE_CFUNC,
|
|
|
|
|
.cfunc = cfunc,
|
|
|
|
|
})) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apfl_gc_tmproots_restore(gc, tmproots);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
error:
|
|
|
|
|
apfl_gc_tmproots_restore(gc, tmproots);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
apfl_ctx
|
|
|
|
|
apfl_ctx_new(struct apfl_allocator base_allocator)
|
|
|
|
|
{
|
|
|
|
|
apfl_ctx ctx = ALLOC_OBJ(base_allocator, struct apfl_ctx_data);
|
|
|
|
|
if (ctx == NULL) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-01 20:00:58 +00:00
|
|
|
apfl_gc_init(&ctx->gc, base_allocator, get_roots, 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
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
ctx->toplevel_stack = apfl_stack_new();
|
|
|
|
|
ctx->call_stack = call_stack_new();
|
|
|
|
|
|
|
|
|
|
if ((ctx->globals = apfl_scope_new(&ctx->gc)) == NULL) {
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
ctx->error_handler = NULL;
|
|
|
|
|
ctx->panic_callback = NULL;
|
|
|
|
|
ctx->panic_callback_data = NULL;
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
ctx->iterative_runners = iterative_runners_list_new();
|
|
|
|
|
|
2022-07-12 20:13:07 +00:00
|
|
|
if (!init_globals(ctx)) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
return ctx;
|
|
|
|
|
|
|
|
|
|
error:
|
|
|
|
|
apfl_ctx_destroy(ctx);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_ctx_destroy(apfl_ctx ctx)
|
|
|
|
|
{
|
|
|
|
|
if (ctx == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
deinit_stack(ctx->gc.allocator, &ctx->toplevel_stack);
|
|
|
|
|
ctx->toplevel_stack = apfl_stack_new();
|
|
|
|
|
|
|
|
|
|
DEINIT_CAP_LIST(
|
|
|
|
|
ctx->gc.allocator,
|
|
|
|
|
ctx->call_stack.items,
|
|
|
|
|
ctx->call_stack.len,
|
|
|
|
|
ctx->call_stack.cap,
|
|
|
|
|
apfl_call_stack_entry_deinit
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
ctx->call_stack = call_stack_new();
|
|
|
|
|
|
|
|
|
|
FREE_LIST(ctx->gc.allocator, ctx->iterative_runners.items, ctx->iterative_runners.cap);
|
|
|
|
|
ctx->iterative_runners = iterative_runners_list_new();
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
struct apfl_allocator base_allocator = ctx->gc.base_allocator;
|
|
|
|
|
|
|
|
|
|
apfl_gc_full(&ctx->gc);
|
|
|
|
|
apfl_gc_deinit(&ctx->gc);
|
|
|
|
|
|
|
|
|
|
FREE_OBJ(base_allocator, ctx);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
static bool
|
|
|
|
|
find_iterative_runner(apfl_ctx ctx, apfl_iterative_runner runner, size_t *index)
|
|
|
|
|
{
|
|
|
|
|
// It should be very uncommon that there are a lot of iterative runners
|
|
|
|
|
// existing on the same apfl_ctx at the same time, so a linear scan should
|
|
|
|
|
// be good enough :)
|
|
|
|
|
for (size_t i = 0; i < ctx->iterative_runners.len; i++) {
|
|
|
|
|
if (ctx->iterative_runners.items[i] == runner) {
|
|
|
|
|
if (index != NULL) {
|
|
|
|
|
*index = i;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
|
apfl_ctx_register_iterative_runner(apfl_ctx ctx, apfl_iterative_runner runner)
|
|
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_ctx_unregister_iterative_runner(apfl_ctx ctx, apfl_iterative_runner runner)
|
|
|
|
|
{
|
|
|
|
|
size_t i;
|
|
|
|
|
if (!find_iterative_runner(ctx, runner, &i)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(
|
|
|
|
|
// We're only removing elements, the buffer should not grow,
|
|
|
|
|
// therefore there should be no allocation errors
|
|
|
|
|
apfl_resizable_splice(
|
|
|
|
|
ctx->gc.allocator,
|
|
|
|
|
sizeof(apfl_iterative_runner),
|
|
|
|
|
(void **)&ctx->iterative_runners.items,
|
|
|
|
|
&ctx->iterative_runners.len,
|
|
|
|
|
&ctx->iterative_runners.cap,
|
|
|
|
|
i,
|
|
|
|
|
1,
|
|
|
|
|
NULL,
|
|
|
|
|
0
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-21 20:40:27 +00:00
|
|
|
#define CREATE_GC_OBJECT_VALUE_ON_STACK(ctx, TYPE, MEMB, NEW) \
|
|
|
|
|
struct apfl_value *value = apfl_stack_push_placeholder(ctx); \
|
|
|
|
|
if (value == NULL) { \
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_alloc_error(ctx); \
|
2022-04-21 20:40:27 +00:00
|
|
|
} \
|
|
|
|
|
\
|
|
|
|
|
struct apfl_value new_value = {.type = TYPE}; \
|
|
|
|
|
if ((new_value.MEMB = NEW) == NULL) { \
|
|
|
|
|
assert(apfl_stack_drop(ctx, -1)); \
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_alloc_error(ctx); \
|
2022-04-21 20:40:27 +00:00
|
|
|
} \
|
|
|
|
|
\
|
2022-06-24 21:13:44 +00:00
|
|
|
*value = new_value;
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
2022-04-22 19:39:03 +00:00
|
|
|
apfl_push_nil(apfl_ctx ctx)
|
|
|
|
|
{
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_stack_must_push(ctx, (struct apfl_value) {
|
2022-04-22 19:39:03 +00:00
|
|
|
.type = VALUE_NIL,
|
2022-06-24 21:13:44 +00:00
|
|
|
});
|
2022-04-22 19:39:03 +00:00
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
2022-04-22 19:39:03 +00:00
|
|
|
apfl_push_bool(apfl_ctx ctx, bool b)
|
|
|
|
|
{
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_stack_must_push(ctx, (struct apfl_value) {
|
2022-04-22 19:39:03 +00:00
|
|
|
.type = VALUE_BOOLEAN,
|
|
|
|
|
.boolean = b,
|
2022-06-24 21:13:44 +00:00
|
|
|
});
|
2022-04-22 19:39:03 +00:00
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
2022-04-22 19:39:03 +00:00
|
|
|
apfl_push_number(apfl_ctx ctx, apfl_number num)
|
|
|
|
|
{
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_stack_must_push(ctx, (struct apfl_value) {
|
2022-04-22 19:39:03 +00:00
|
|
|
.type = VALUE_NUMBER,
|
|
|
|
|
.number = num,
|
2022-06-24 21:13:44 +00:00
|
|
|
});
|
2022-04-22 19:39:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct apfl_string *
|
|
|
|
|
new_copied_string(struct gc *gc, struct apfl_string_view sv)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_string s = apfl_string_blank();
|
|
|
|
|
if (!apfl_string_copy(gc->allocator, &s, sv)) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct apfl_string *out = apfl_string_move_into_new_gc_string(gc, &s);
|
|
|
|
|
if (out == NULL) {
|
|
|
|
|
apfl_string_deinit(gc->allocator, &s);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
2022-04-22 19:39:03 +00:00
|
|
|
apfl_push_string_view_copy(apfl_ctx ctx, struct apfl_string_view sv)
|
|
|
|
|
{
|
|
|
|
|
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
|
|
|
|
ctx,
|
|
|
|
|
VALUE_STRING,
|
|
|
|
|
string,
|
|
|
|
|
new_copied_string(&ctx->gc, sv)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
static bool
|
|
|
|
|
try_push_const_string(apfl_ctx ctx, const char *string)
|
2022-04-23 20:14:42 +00:00
|
|
|
{
|
|
|
|
|
return apfl_stack_push(ctx, (struct apfl_value) {
|
|
|
|
|
.type = VALUE_CONST_STRING,
|
|
|
|
|
.const_string = apfl_string_view_from(string),
|
2022-06-24 21:13:44 +00:00
|
|
|
});
|
2022-04-23 20:14:42 +00:00
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
|
|
|
|
apfl_push_const_string(apfl_ctx ctx, const char *string)
|
|
|
|
|
{
|
|
|
|
|
if (!try_push_const_string(ctx, string)) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
apfl_list_create(apfl_ctx ctx, size_t initial_cap)
|
|
|
|
|
{
|
|
|
|
|
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
|
|
|
|
ctx,
|
|
|
|
|
VALUE_LIST,
|
|
|
|
|
list,
|
|
|
|
|
apfl_list_new(&ctx->gc, initial_cap)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
apfl_list_append(apfl_ctx ctx, apfl_stackidx list_index, apfl_stackidx value_index)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value *list_val = stack_get_pointer(ctx, list_index);
|
|
|
|
|
if (list_val == NULL) {
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (list_val->type != VALUE_LIST) {
|
2022-06-24 21:13:44 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
struct apfl_value value = apfl_stack_must_get(ctx, value_index);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
if (!apfl_list_splice(
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
&ctx->gc,
|
|
|
|
|
&list_val->list,
|
|
|
|
|
list_val->list->len,
|
|
|
|
|
0,
|
|
|
|
|
&value,
|
|
|
|
|
1
|
2022-06-24 21:13:44 +00:00
|
|
|
)) {
|
2022-07-11 19:41:05 +00:00
|
|
|
assert(apfl_stack_drop(ctx, value_index));
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
|
|
|
|
assert(apfl_stack_drop(ctx, value_index));
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
apfl_list_append_list(apfl_ctx ctx, apfl_stackidx dst_index, apfl_stackidx src_index)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value *dst_val = stack_get_pointer(ctx, dst_index);
|
|
|
|
|
if (dst_val == NULL) {
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dst_val->type != VALUE_LIST) {
|
2022-06-24 21:13:44 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
struct apfl_value src_val = apfl_stack_must_get(ctx, src_index);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
2022-04-24 14:45:25 +00:00
|
|
|
if (src_val.type != VALUE_LIST) {
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
assert(apfl_stack_drop(ctx, src_index));
|
2022-06-24 21:13:44 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
if (!apfl_list_splice(
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
&ctx->gc,
|
|
|
|
|
&dst_val->list,
|
|
|
|
|
dst_val->list->len,
|
|
|
|
|
0,
|
|
|
|
|
src_val.list->items,
|
|
|
|
|
src_val.list->len
|
2022-06-24 21:13:44 +00:00
|
|
|
)) {
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
|
|
|
|
assert(apfl_stack_drop(ctx, src_index));
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
apfl_dict_create(apfl_ctx ctx)
|
|
|
|
|
{
|
|
|
|
|
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
|
|
|
|
ctx,
|
|
|
|
|
VALUE_DICT,
|
|
|
|
|
dict,
|
|
|
|
|
apfl_dict_new(&ctx->gc)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
apfl_dict_set(
|
|
|
|
|
apfl_ctx ctx,
|
|
|
|
|
apfl_stackidx dict_index,
|
|
|
|
|
apfl_stackidx k_index,
|
|
|
|
|
apfl_stackidx v_index
|
|
|
|
|
) {
|
|
|
|
|
struct apfl_value k;
|
|
|
|
|
struct apfl_value v;
|
|
|
|
|
struct apfl_value *dict_value;
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
!apfl_stack_get(ctx, &k, k_index)
|
|
|
|
|
|| !apfl_stack_get(ctx, &v, v_index)
|
|
|
|
|
|| (dict_value = stack_get_pointer(ctx, dict_index)) == NULL
|
|
|
|
|
) {
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dict_value->type != VALUE_DICT) {
|
|
|
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}));
|
2022-06-24 21:13:44 +00:00
|
|
|
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}));
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_alloc_error(ctx);
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}));
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
void
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
apfl_get_member(
|
|
|
|
|
apfl_ctx ctx,
|
|
|
|
|
apfl_stackidx container_index,
|
|
|
|
|
apfl_stackidx k_index
|
|
|
|
|
) {
|
|
|
|
|
struct apfl_value container;
|
|
|
|
|
struct apfl_value k;
|
|
|
|
|
if (
|
2022-07-11 19:41:05 +00:00
|
|
|
!current_stack_get_and_adjust_index(ctx, &container, &container_index)
|
|
|
|
|
|| !current_stack_get_and_adjust_index(ctx, &k, &k_index)
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
) {
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_raise_invalid_stackidx(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct apfl_value *value = apfl_stack_push_placeholder(ctx);
|
|
|
|
|
if (value == NULL) {
|
|
|
|
|
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, container_index}));
|
|
|
|
|
apfl_raise_alloc_error(ctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum get_item_result result = apfl_value_get_item(container, k, value);
|
|
|
|
|
if (result != GET_ITEM_OK) {
|
|
|
|
|
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}));
|
2022-06-24 21:13:44 +00:00
|
|
|
|
|
|
|
|
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
|
|
|
}
|
2022-07-11 19:41:05 +00:00
|
|
|
|
|
|
|
|
void
|
|
|
|
|
apfl_get_list_member_by_index(
|
|
|
|
|
apfl_ctx ctx,
|
|
|
|
|
apfl_stackidx list_index,
|
|
|
|
|
size_t index
|
|
|
|
|
) {
|
|
|
|
|
struct apfl_value list = apfl_stack_must_get(ctx, list_index);
|
|
|
|
|
|
|
|
|
|
if (list.type != VALUE_LIST) {
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-12 20:13:07 +00:00
|
|
|
size_t
|
|
|
|
|
apfl_len(apfl_ctx ctx, apfl_stackidx index)
|
|
|
|
|
{
|
|
|
|
|
struct apfl_value value = apfl_stack_must_get(ctx, index);
|
|
|
|
|
switch (value.type) {
|
|
|
|
|
case VALUE_NIL:
|
|
|
|
|
case VALUE_BOOLEAN:
|
|
|
|
|
case VALUE_NUMBER:
|
|
|
|
|
case VALUE_FUNC:
|
|
|
|
|
case VALUE_CFUNC:
|
|
|
|
|
case VALUE_USERDATA:
|
|
|
|
|
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};
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
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;
|
|
|
|
|
}
|