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
|
|
|
#ifndef APFL_CONTEXT_H
|
|
|
|
|
#define APFL_CONTEXT_H
|
|
|
|
|
|
|
|
|
|
#ifdef __cplusplus
|
|
|
|
|
extern "C" {
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#include <stdint.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 "bytecode.h"
|
|
|
|
|
#include "hashmap.h"
|
|
|
|
|
#include "gc.h"
|
|
|
|
|
#include "value.h"
|
2022-04-15 12:41:22 +00:00
|
|
|
#include "scope.h"
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
#define RESULT_OFF_FOR_LONGJMP 1
|
|
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
struct scopes {
|
|
|
|
|
// Both scope and closure_scope can be null
|
|
|
|
|
struct scope *local;
|
|
|
|
|
struct scope *closure;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct matcher_capture_transfer {
|
|
|
|
|
struct apfl_string *var;
|
|
|
|
|
size_t path_start;
|
|
|
|
|
size_t path_len;
|
|
|
|
|
bool local;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct matcher {
|
|
|
|
|
struct matcher_instruction_list *instructions;
|
|
|
|
|
size_t value_count;
|
|
|
|
|
struct apfl_value *values;
|
|
|
|
|
};
|
|
|
|
|
|
Implement mark&sweep garbage collection and bytecode compilation
Instead of the previous refcount base garbage collection, we're now using
a basic tri-color mark&sweep collector. This is done to support cyclical
value relationships in the future (functions can form cycles, all values
implemented up to this point can not).
The collector maintains a set of roots and a set of objects (grouped into
blocks). The GC enabled objects are no longer allocated manually, but will
be allocated by the GC. The GC also wraps an allocator, this way the GC
knows, if we ran out of memory and will try to get out of this situation by
performing a full collection cycle.
The tri-color abstraction was chosen for two reasons:
- We don't have to maintain a list of objects that need to be marked, we
can simply grab the next grey one.
- It should allow us to later implement incremental collection (right now
we only do a stop-the-world collection).
This also switches to a bytecode based evaluation of the code: We no longer
directly evaluate the AST, but first compile it into a series of
instructions, that are evaluated in a separate step. This was done in
preparation for inplementing functions: We only need to turn a function
body into instructions instead of evaluating the node again with each call
of the function. Also, since an instruction list is implemented as a GC
object, this then removes manual memory management of the function body and
it's child nodes. Since the GC and the bytecode go hand in hand, this was
done in one (giant) commit.
As a downside, we've now lost the ability do do list matching on
assignments. I've already started to work on implementing this in the new
architecture, but left it out of this commit, as it's already quite a large
commit :)
2022-04-11 20:24:22 +00:00
|
|
|
struct stack {
|
|
|
|
|
struct apfl_value *items;
|
|
|
|
|
size_t len;
|
|
|
|
|
size_t cap;
|
|
|
|
|
};
|
|
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
struct matcher_stack {
|
|
|
|
|
struct matcher **items;
|
|
|
|
|
size_t len;
|
|
|
|
|
size_t cap;
|
|
|
|
|
};
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
struct func_call_stack_entry {
|
|
|
|
|
size_t pc;
|
|
|
|
|
struct instruction_list *instructions;
|
|
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
// scopes.scope will be created lazily
|
|
|
|
|
struct scopes scopes;
|
2022-07-11 19:41:05 +00:00
|
|
|
|
|
|
|
|
int execution_line;
|
2022-07-28 18:46:32 +00:00
|
|
|
|
2023-01-26 20:22:34 +00:00
|
|
|
struct function *function; // Can be NULL, in that case it's the toplevel
|
|
|
|
|
size_t subfunction_index; // Not set, if function == NULL
|
|
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
struct matcher_stack matcher_stack;
|
2022-07-28 18:46:32 +00:00
|
|
|
bool returning_from_matcher;
|
2022-11-19 21:06:23 +00:00
|
|
|
bool matcher_result;
|
2022-07-11 19:41:05 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct cfunc_call_stack_entry {
|
|
|
|
|
struct cfunction *func;
|
|
|
|
|
};
|
|
|
|
|
|
2022-07-28 18:46:32 +00:00
|
|
|
enum matcher_mode {
|
|
|
|
|
MATCHER_MODE_VALUE,
|
|
|
|
|
MATCHER_MODE_STOP,
|
|
|
|
|
MATCHER_MODE_LIST_START,
|
|
|
|
|
MATCHER_MODE_LIST_END,
|
|
|
|
|
MATCHER_MODE_LIST_REMAINING,
|
|
|
|
|
MATCHER_MODE_LIST_UNDERFLOW,
|
|
|
|
|
};
|
|
|
|
|
|
2022-11-06 16:12:03 +00:00
|
|
|
struct matcher_state {
|
2022-07-28 18:46:32 +00:00
|
|
|
enum matcher_mode mode;
|
|
|
|
|
|
|
|
|
|
size_t lower;
|
|
|
|
|
size_t upper;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct matcher_call_stack_entry {
|
|
|
|
|
size_t pc;
|
2022-11-19 22:20:22 +00:00
|
|
|
bool from_predicate;
|
2022-07-28 18:46:32 +00:00
|
|
|
struct matcher *matcher;
|
2022-11-19 21:06:23 +00:00
|
|
|
struct scopes scopes;
|
|
|
|
|
|
|
|
|
|
size_t capture_index;
|
|
|
|
|
size_t capture_count;
|
|
|
|
|
struct apfl_value *captures;
|
|
|
|
|
struct matcher_capture_transfer *transfers;
|
2022-07-28 18:46:32 +00:00
|
|
|
|
2022-11-06 16:12:03 +00:00
|
|
|
struct matcher_state *matcher_state_stack;
|
|
|
|
|
size_t matcher_state_stack_len;
|
|
|
|
|
size_t matcher_state_stack_cap;
|
2022-07-28 18:46:32 +00:00
|
|
|
};
|
|
|
|
|
|
2022-08-12 22:50:26 +00:00
|
|
|
struct func_dispatch_call_stack_entry {
|
|
|
|
|
size_t subfunc;
|
2022-11-19 21:06:23 +00:00
|
|
|
struct scopes scopes;
|
2022-08-12 22:50:26 +00:00
|
|
|
bool returning_from_matcher;
|
2022-11-19 21:06:23 +00:00
|
|
|
bool matcher_result;
|
2022-08-12 22:50:26 +00:00
|
|
|
struct function *function;
|
|
|
|
|
};
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
struct call_stack_entry {
|
2023-01-26 20:22:34 +00:00
|
|
|
enum apfl_call_stack_entry_type type;
|
2022-07-11 19:41:05 +00:00
|
|
|
|
|
|
|
|
struct stack stack;
|
|
|
|
|
|
|
|
|
|
union {
|
|
|
|
|
struct func_call_stack_entry func;
|
|
|
|
|
struct cfunc_call_stack_entry cfunc;
|
2022-07-28 18:46:32 +00:00
|
|
|
struct matcher_call_stack_entry matcher;
|
2022-08-12 22:50:26 +00:00
|
|
|
struct func_dispatch_call_stack_entry func_dispatch;
|
2022-07-11 19:41:05 +00:00
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct call_stack {
|
|
|
|
|
struct call_stack_entry *items;
|
|
|
|
|
size_t len;
|
|
|
|
|
size_t cap;
|
|
|
|
|
};
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
struct error_handler {
|
|
|
|
|
jmp_buf jump;
|
|
|
|
|
};
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
struct iterative_runners_list {
|
|
|
|
|
apfl_iterative_runner *items;
|
|
|
|
|
size_t len;
|
|
|
|
|
size_t 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
|
|
|
struct apfl_ctx_data {
|
|
|
|
|
struct gc gc;
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
apfl_panic_callback panic_callback;
|
|
|
|
|
void *panic_callback_data;
|
2022-07-11 19:41:05 +00:00
|
|
|
|
|
|
|
|
struct stack toplevel_stack;
|
|
|
|
|
|
|
|
|
|
struct scope *globals;
|
|
|
|
|
struct call_stack call_stack;
|
|
|
|
|
|
2022-06-24 21:13:44 +00:00
|
|
|
struct error_handler *error_handler;
|
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 iterative_runners_list iterative_runners;
|
2022-10-28 19:32:17 +00:00
|
|
|
|
2023-02-10 20:38:54 +00:00
|
|
|
struct apfl_io_writer output_writer;
|
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-11-19 21:06:23 +00:00
|
|
|
void apfl_matcher_call_stack_entry_deinit(struct apfl_allocator, struct matcher_call_stack_entry *);
|
2022-07-11 19:41:05 +00:00
|
|
|
void apfl_call_stack_entry_deinit(struct apfl_allocator, struct call_stack_entry *);
|
|
|
|
|
|
|
|
|
|
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
|
|
|
bool apfl_stack_push(apfl_ctx, struct apfl_value);
|
2022-06-24 21:13:44 +00:00
|
|
|
void apfl_stack_must_push(apfl_ctx ctx, struct apfl_value 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
|
|
|
bool apfl_stack_check_index(apfl_ctx, apfl_stackidx *);
|
2022-07-11 19:41:05 +00:00
|
|
|
bool apfl_stack_has_index(apfl_ctx, apfl_stackidx);
|
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_pop(apfl_ctx, struct apfl_value *value, apfl_stackidx);
|
2022-07-11 19:41:05 +00:00
|
|
|
struct apfl_value apfl_stack_must_pop(apfl_ctx, apfl_stackidx);
|
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, struct apfl_value *value, apfl_stackidx);
|
2022-07-11 19:41:05 +00:00
|
|
|
struct apfl_value apfl_stack_must_get(apfl_ctx, apfl_stackidx);
|
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, apfl_stackidx);
|
2022-07-11 19:41:05 +00:00
|
|
|
bool apfl_stack_drop_multi(apfl_ctx ctx, size_t count, apfl_stackidx *indices);
|
2022-04-21 19:15:20 +00:00
|
|
|
void apfl_stack_clear(apfl_ctx);
|
2022-04-21 20:40:27 +00:00
|
|
|
struct apfl_value *apfl_stack_push_placeholder(apfl_ctx);
|
2022-06-24 21:13:44 +00:00
|
|
|
bool apfl_move_string_onto_stack(apfl_ctx, struct apfl_string);
|
|
|
|
|
|
2023-01-30 21:50:01 +00:00
|
|
|
// Like apfl_tostring, but ensures it's a dynamically allocated string and returns the underlying string.
|
|
|
|
|
struct apfl_string *apfl_to_dynamic_string(apfl_ctx ctx, apfl_stackidx index);
|
|
|
|
|
|
2023-01-24 20:22:22 +00:00
|
|
|
/* Raise an error with a message formatted according to fmt.
|
|
|
|
|
*
|
|
|
|
|
* fmt allows placeholders in the form of `{<param>:<type>}`, where `<param>:`
|
|
|
|
|
* is optional. Each of these placeholders then takes a vararg. The following
|
|
|
|
|
* placeholders are implemented:
|
|
|
|
|
*
|
|
|
|
|
* - string: Expects a `struct apfl_string_view` parameter and replaces the
|
|
|
|
|
* placeholder with that string. With the param `c`, a C `char *` string is
|
|
|
|
|
* expected instead.
|
|
|
|
|
* - type: Expects an `enum apfl_type` and will replace the placeholder with the
|
|
|
|
|
* name of that type. With the param `value` a `struct apfl_value` is expected
|
|
|
|
|
* instead and the type of that value is used. With the param `stack` a
|
|
|
|
|
* `apfl_stackidx` is expected instead and the type of the value at the given
|
|
|
|
|
* stack index is used instead.
|
|
|
|
|
*/
|
2023-02-16 19:52:27 +00:00
|
|
|
noreturn void apfl_raise_errorfmt(apfl_ctx ctx, const char *fmt, ...);
|
2023-01-24 20:22:22 +00:00
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
struct call_stack_entry *apfl_call_stack_cur_entry(apfl_ctx);
|
|
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
struct scope *apfl_closure_scope_for_func(apfl_ctx, struct scopes);
|
2022-07-11 19:41:05 +00:00
|
|
|
|
2023-02-10 20:38:54 +00:00
|
|
|
struct apfl_io_writer apfl_get_output_writer(apfl_ctx);
|
2022-10-28 19:32:17 +00:00
|
|
|
|
2022-11-20 20:42:46 +00:00
|
|
|
enum apfl_result apfl_do_protected(
|
|
|
|
|
apfl_ctx ctx,
|
|
|
|
|
void (*callback)(apfl_ctx, void *),
|
|
|
|
|
void *opaque,
|
2023-01-28 20:41:48 +00:00
|
|
|
void (*errcallback)(apfl_ctx, void *)
|
2022-11-20 20:42:46 +00:00
|
|
|
);
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
bool apfl_ctx_register_iterative_runner(apfl_ctx, apfl_iterative_runner);
|
|
|
|
|
void apfl_ctx_unregister_iterative_runner(apfl_ctx, apfl_iterative_runner);
|
|
|
|
|
|
2022-11-19 21:06:23 +00:00
|
|
|
struct matcher *apfl_matcher_new(struct gc *, struct matcher_instruction_list *);
|
|
|
|
|
void apfl_matcher_deinit(struct apfl_allocator, struct matcher *);
|
|
|
|
|
void apfl_gc_matcher_traverse(struct matcher *, gc_visitor, void *);
|
|
|
|
|
|
2022-07-11 19:41:05 +00:00
|
|
|
void apfl_iterative_runner_visit_gc_objects(apfl_iterative_runner, gc_visitor, 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
|
|
|
|
|
|
|
|
#ifdef __cplusplus
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#endif
|