pocketlang/src/vm.c

1051 lines
28 KiB
C
Raw Normal View History

2021-02-07 15:40:00 +08:00
/*
* Copyright (c) 2021 Thakee Nathees
* Licensed under: MIT License
*/
#include "vm.h"
2021-02-11 01:23:48 +08:00
#include "core.h"
2021-05-16 15:05:54 +08:00
//#include "debug.h" //< Wrap around debug macro.
2021-02-11 01:23:48 +08:00
#include "utils.h"
#define HAS_ERROR() (vm->fiber->error != NULL)
2021-02-11 01:23:48 +08:00
2021-02-12 01:35:43 +08:00
// Initially allocated call frame capacity. Will grow dynamically.
#define INITIAL_CALL_FRAMES 4
// Minimum size of the stack.
2021-02-15 20:49:19 +08:00
#define MIN_STACK_SIZE 128
2021-02-12 01:35:43 +08:00
// The allocated size the'll trigger the first GC. (~10MB).
#define INITIAL_GC_SIZE (1024 * 1024 * 10)
// The heap size might shrink if the remaining allocated bytes after a GC
// is less than the one before the last GC. So we need a minimum size.
#define MIN_HEAP_SIZE (1024 * 1024)
// The heap size for the next GC will be calculated as the bytes we have
// allocated so far plus the fill factor of it.
#define HEAP_FILL_PERCENT 50
2021-04-25 23:19:39 +08:00
static void* defaultRealloc(void* memory, size_t new_size, void* user_data) {
if (new_size == 0) {
free(memory);
return NULL;
}
return realloc(memory, new_size);
}
2021-05-09 18:28:00 +08:00
void* vmRealloc(PKVM* self, void* memory, size_t old_size, size_t new_size) {
2021-02-07 15:40:00 +08:00
2021-04-25 23:19:39 +08:00
// TODO: Debug trace allocations here.
2021-02-12 01:35:43 +08:00
// Track the total allocated memory of the VM to trigger the GC.
2021-05-04 18:24:26 +08:00
// if vmRealloc is called for freeing, the old_size would be 0 since
2021-02-12 01:35:43 +08:00
// deallocated bytes are traced by garbage collector.
self->bytes_allocated += new_size - old_size;
2021-02-07 15:40:00 +08:00
2021-04-25 23:19:39 +08:00
if (new_size > 0 && self->bytes_allocated > self->next_gc) {
vmCollectGarbage(self);
}
2021-02-07 15:40:00 +08:00
2021-04-25 23:19:39 +08:00
return self->config.realloc_fn(memory, new_size, self->config.user_data);
2021-02-07 15:40:00 +08:00
}
2021-05-09 18:28:00 +08:00
void pkInitConfiguration(pkConfiguration* config) {
2021-04-26 17:34:30 +08:00
config->realloc_fn = defaultRealloc;
config->error_fn = NULL;
config->write_fn = NULL;
config->load_script_fn = NULL;
2021-05-07 17:41:19 +08:00
config->resolve_path_fn = NULL;
2021-04-26 17:34:30 +08:00
config->user_data = NULL;
}
2021-05-09 18:28:00 +08:00
PKVM* pkNewVM(pkConfiguration* config) {
2021-05-06 22:19:30 +08:00
2021-05-08 18:54:07 +08:00
// TODO: If the [config] is NULL, initialize a default one.
2021-05-09 18:28:00 +08:00
pkReallocFn realloc_fn = defaultRealloc;
2021-05-06 22:19:30 +08:00
void* user_data = NULL;
if (config != NULL) {
realloc_fn = config->realloc_fn;
user_data = config->user_data;
}
2021-05-09 18:28:00 +08:00
PKVM* vm = (PKVM*)realloc_fn(NULL, sizeof(PKVM), user_data);
memset(vm, 0, sizeof(PKVM));
2021-05-06 22:19:30 +08:00
vm->config = *config;
vm->gray_list_count = 0;
vm->gray_list_capacity = MIN_CAPACITY;
vm->gray_list = (Object**)vm->config.realloc_fn(
NULL, sizeof(Object*) * vm->gray_list_capacity, NULL);
vm->next_gc = INITIAL_GC_SIZE;
vm->min_heap_size = MIN_HEAP_SIZE;
vm->heap_fill_percent = HEAP_FILL_PERCENT;
2021-05-06 22:19:30 +08:00
vm->scripts = newMap(vm);
vm->core_libs = newMap(vm);
vm->builtins_count = 0;
2021-05-06 22:19:30 +08:00
initializeCore(vm);
2021-04-26 17:34:30 +08:00
return vm;
}
2021-05-09 18:28:00 +08:00
void pkFreeVM(PKVM* self) {
2021-04-26 17:34:30 +08:00
Object* obj = self->first;
while (obj != NULL) {
Object* next = obj->next;
freeObject(self, obj);
obj = next;
}
2021-05-01 18:13:39 +08:00
self->gray_list = (Object**)self->config.realloc_fn(
self->gray_list, 0, self->config.user_data);
2021-02-12 01:35:43 +08:00
2021-05-06 22:19:30 +08:00
DEALLOCATE(self, self);
2021-02-11 01:23:48 +08:00
}
2021-05-09 18:28:00 +08:00
void vmPushTempRef(PKVM* self, Object* obj) {
2021-02-12 01:35:43 +08:00
ASSERT(obj != NULL, "Cannot reference to NULL.");
2021-04-25 23:19:39 +08:00
ASSERT(self->temp_reference_count < MAX_TEMP_REFERENCE,
"Too many temp references");
2021-02-12 01:35:43 +08:00
self->temp_reference[self->temp_reference_count++] = obj;
2021-02-07 15:40:00 +08:00
}
2021-05-09 18:28:00 +08:00
void vmPopTempRef(PKVM* self) {
2021-02-12 01:35:43 +08:00
ASSERT(self->temp_reference_count > 0, "Temporary reference is empty to pop.");
self->temp_reference_count--;
2021-02-07 15:40:00 +08:00
}
2021-05-09 18:28:00 +08:00
void vmCollectGarbage(PKVM* self) {
2021-04-25 23:19:39 +08:00
// Reset VM's bytes_allocated value and count it again so that we don't
// required to know the size of each object that'll be freeing.
self->bytes_allocated = 0;
// Mark the core libs and builtin functions.
grayObject(&self->core_libs->_super, self);
for (int i = 0; i < self->builtins_count; i++) {
grayObject(&self->builtins[i].fn->_super, self);
}
2021-04-26 17:34:30 +08:00
2021-05-06 22:19:30 +08:00
// Mark the scripts cache.
grayObject(&self->scripts->_super, self);
2021-04-26 17:34:30 +08:00
// Mark temp references.
for (int i = 0; i < self->temp_reference_count; i++) {
2021-05-01 18:13:39 +08:00
grayObject(self->temp_reference[i], self);
2021-04-26 17:34:30 +08:00
}
// Garbage collection triggered at the middle of a compilation.
if (self->compiler != NULL) {
compilerMarkObjects(self->compiler, self);
}
// Garbage collection triggered at the middle of runtime.
if (self->script != NULL) {
2021-05-01 18:13:39 +08:00
grayObject(&self->script->_super, self);
2021-04-26 17:34:30 +08:00
}
if (self->fiber != NULL) {
2021-05-01 18:13:39 +08:00
grayObject(&self->fiber->_super, self);
2021-04-26 17:34:30 +08:00
}
2021-05-01 18:13:39 +08:00
blackenObjects(self);
// Now sweep all the un-marked objects in then link list and remove them
// from the chain.
// [ptr] is an Object* reference that should be equal to the next
// non-garbage Object*.
Object** ptr = &self->first;
while (*ptr != NULL) {
// If the object the pointer points to wasn't marked it's unreachable.
// Clean it. And update the pointer points to the next object.
if (!(*ptr)->is_marked) {
Object* garbage = *ptr;
*ptr = garbage->next;
freeObject(self, garbage);
} else {
// Unmark the object for the next garbage collection.
(*ptr)->is_marked = false;
ptr = &(*ptr)->next;
}
}
// Next GC heap size will be change depends on the byte we've left with now,
// and the [heap_fill_percent].
self->next_gc = self->bytes_allocated + (
(self->bytes_allocated * self->heap_fill_percent) / 100);
if (self->next_gc < self->min_heap_size) self->next_gc = self->min_heap_size;
2021-04-25 23:19:39 +08:00
}
2021-05-09 18:28:00 +08:00
void* pkGetUserData(PKVM* vm) {
2021-02-17 02:28:03 +08:00
return vm->config.user_data;
}
2021-05-09 18:28:00 +08:00
void pkSetUserData(PKVM* vm, void* user_data) {
2021-02-17 02:28:03 +08:00
vm->config.user_data = user_data;
}
2021-05-09 18:28:00 +08:00
static Script* getScript(PKVM* vm, String* name) {
2021-05-07 15:21:34 +08:00
Var scr = mapGet(vm->scripts, VAR_OBJ(&name->_super));
if (IS_UNDEF(scr)) return NULL;
ASSERT(AS_OBJ(scr)->type == OBJ_SCRIPT, OOPS);
return (Script*)AS_OBJ(scr);
}
2021-02-11 01:23:48 +08:00
/******************************************************************************
* RUNTIME *
*****************************************************************************/
// If failed to resolve it'll return false. Parameter [result] should be points
// to the string which is the path that has to be resolved and once it resolved
// the provided result's string's on_done() will be called and, it's string will
// be updated with the new resolved path string.
static bool resolveScriptPath(PKVM* vm, pkStringPtr* path_string) {
2021-05-07 17:41:19 +08:00
if (vm->config.resolve_path_fn == NULL) return true;
const char* path = path_string->string;
pkStringPtr resolved;
2021-05-07 15:21:34 +08:00
Fiber* fiber = vm->fiber;
2021-05-07 17:41:19 +08:00
if (fiber == NULL || fiber->frame_count <= 0) {
// fiber == NULL => vm haven't started yet and it's a root script.
resolved = vm->config.resolve_path_fn(vm, NULL, path);
2021-05-07 17:41:19 +08:00
} else {
Function* fn = fiber->frames[fiber->frame_count - 1].fn;
resolved = vm->config.resolve_path_fn(vm, fn->owner->path->data, path);
2021-05-07 17:41:19 +08:00
}
2021-05-07 15:21:34 +08:00
// Done with the last string and update it with the new string.
if (path_string->on_done != NULL) path_string->on_done(vm, *path_string);
*path_string = resolved;
2021-05-07 15:21:34 +08:00
return path_string->string != NULL;
2021-05-06 22:19:30 +08:00
}
2021-05-07 15:21:34 +08:00
// Import and return Script object as Var. If the script is imported and
// compiled here it'll set [is_new_script] to true oterwise (using the cached
// script) set to false.
static Var importScript(PKVM* vm, String* path_name, bool is_core,
2021-05-09 20:31:36 +08:00
bool* is_new_script) {
if (is_core) {
ASSERT(is_new_script == NULL, OOPS);
Script* core_lib = getCoreLib(vm, path_name);
2021-05-09 20:31:36 +08:00
if (core_lib != NULL) {
return VAR_OBJ(&core_lib->_super);
}
vm->fiber->error = stringFormat(vm, "Failed to import core library '@'",
path_name);
2021-05-09 20:31:36 +08:00
return VAR_NULL;
2021-05-07 17:41:19 +08:00
}
2021-05-09 20:31:36 +08:00
// Relative path import.
pkStringPtr resolved;
resolved.string = path_name->data;
resolved.on_done = NULL; // Don't have to clean the String.
2021-05-09 20:31:36 +08:00
ASSERT(is_new_script != NULL, OOPS);
if (!resolveScriptPath(vm, &resolved)) {
vm->fiber->error = stringFormat(vm, "Failed to resolve script '@' from '@'",
path_name, vm->fiber->func->owner->path);
2021-05-07 15:21:34 +08:00
return VAR_NULL;
}
2021-05-09 20:31:36 +08:00
// If the returned string is not the same as (pointer wise) provided one,
// allocate a new string. And clean the old string result.
if (resolved.string != path_name->data) {
path_name = newString(vm, resolved.string);
if (resolved.on_done != NULL) resolved.on_done(vm, resolved);
}
// Push the path_name string. Incase if it's allocated above.
vmPushTempRef(vm, &path_name->_super);
2021-05-06 22:19:30 +08:00
// Check if the script is already cached (then use it).
Var scr = mapGet(vm->scripts, VAR_OBJ(&path_name->_super));
2021-05-06 22:19:30 +08:00
if (!IS_UNDEF(scr)) {
ASSERT(AS_OBJ(scr)->type == OBJ_SCRIPT, OOPS);
*is_new_script = false;
return scr;
}
*is_new_script = true;
pkStringPtr source = { false, NULL, NULL };
if (vm->config.load_script_fn != NULL) {
source = vm->config.load_script_fn(vm, path_name->data);
}
2021-05-06 22:19:30 +08:00
if (source.string == NULL) {
vmPopTempRef(vm); // path_name
2021-05-06 22:19:30 +08:00
String* err_msg = stringFormat(vm, "Cannot import script '@'", path_name);
2021-05-06 22:19:30 +08:00
vm->fiber->error = err_msg; //< Set the error msg.
return VAR_NULL;
}
Script* script = newScript(vm, path_name);
2021-05-07 15:21:34 +08:00
vmPushTempRef(vm, &script->_super);
mapSet(vm->scripts, vm, VAR_OBJ(&path_name->_super), VAR_OBJ(&script->_super));
2021-05-07 15:21:34 +08:00
vmPopTempRef(vm);
2021-05-06 22:19:30 +08:00
vmPopTempRef(vm); // path_name
2021-05-07 17:41:19 +08:00
bool success = compile(vm, script, source.string);
if (source.on_done) source.on_done(vm, source);
2021-05-07 15:21:34 +08:00
if (!success) {
vm->fiber->error = stringFormat(vm, "Compilation of script '@' failed.",
path_name);
2021-05-07 15:21:34 +08:00
return VAR_NULL;
}
return VAR_OBJ(&script->_super);
2021-05-06 22:19:30 +08:00
}
2021-05-09 18:28:00 +08:00
static void ensureStackSize(PKVM* vm, int size) {
Fiber* fiber = vm->fiber;
if (fiber->stack_size > size) return;
int new_size = utilPowerOf2Ceil(size);
Var* old_rbp = fiber->stack; //< Old stack base pointer.
fiber->stack = (Var*)vmRealloc(vm, fiber->stack,
sizeof(Var) * fiber->stack_size,
sizeof(Var) * new_size);
fiber->stack_size = new_size;
// If the old stack base pointer is the same as the current, that means the
// stack hasn't been moved by the reallocation. In that case we're done.
if (old_rbp == fiber->stack) return;
// If we reached here that means the stack is moved by the reallocation and
// we have to update all the pointers that pointing to the old stack slots.
/*
' '
' ' ' '
' ' | | <new_rsp
old_rsp> | | | |
| | .----> | value | <new_ptr
| | | | |
old_ptr> | value | ------' |________| <new_rbp
| | ^ new stack
old_rbp> |________| | height
old stack
new_ptr = new_rbp + height
= fiber->stack + ( old_ptr - old_rbp ) */
#define MAP_PTR(old_ptr) (fiber->stack + ((old_ptr) - old_rbp))
// Update the stack top pointer.
fiber->sp = MAP_PTR(fiber->sp);
// Update the stack base pointer of the call frames.
for (int i = 0; i < fiber->frame_capacity; i++) {
CallFrame* frame = fiber->frames + i;
frame->rbp = MAP_PTR(frame->rbp);
}
2021-02-12 01:35:43 +08:00
}
2021-05-09 18:28:00 +08:00
static inline void pushCallFrame(PKVM* vm, Function* fn) {
2021-02-12 01:35:43 +08:00
ASSERT(!fn->is_native, "Native function shouldn't use call frames.");
// Grow the stack frame if needed.
if (vm->fiber->frame_count + 1 > vm->fiber->frame_capacity) {
int new_capacity = vm->fiber->frame_capacity * 2;
vm->fiber->frames = (CallFrame*)vmRealloc(vm, vm->fiber->frames,
sizeof(CallFrame) * vm->fiber->frame_capacity,
2021-02-12 01:35:43 +08:00
sizeof(CallFrame) * new_capacity);
vm->fiber->frame_capacity = new_capacity;
2021-02-12 01:35:43 +08:00
}
// Grow the stack if needed.
int stack_size = (int)(vm->fiber->sp - vm->fiber->stack);
2021-02-12 01:35:43 +08:00
int needed = stack_size + fn->fn->stack_size;
// TODO: set stack overflow maybe?.
2021-02-12 01:35:43 +08:00
ensureStackSize(vm, needed);
CallFrame* frame = &vm->fiber->frames[vm->fiber->frame_count++];
frame->rbp = vm->fiber->ret;
2021-02-12 01:35:43 +08:00
frame->fn = fn;
frame->ip = fn->fn->opcodes.data;
}
void pkSetRuntimeError(PKVM* vm, const char* message) {
vm->fiber->error = newString(vm, message);
2021-02-11 01:23:48 +08:00
}
2021-05-09 18:28:00 +08:00
void vmReportError(PKVM* vm) {
2021-02-12 01:35:43 +08:00
ASSERT(HAS_ERROR(), "runtimeError() should be called after an error.");
// TODO: pass the error to the caller of the fiber.
2021-05-16 15:05:54 +08:00
// Print the Error message and stack trace.
if (vm->config.error_fn == NULL) return;
Fiber* fiber = vm->fiber;
vm->config.error_fn(vm, PK_ERROR_RUNTIME, NULL, -1, fiber->error->data);
for (int i = fiber->frame_count - 1; i >= 0; i--) {
CallFrame* frame = &fiber->frames[i];
Function* fn = frame->fn;
ASSERT(!fn->is_native, OOPS);
int line = fn->fn->oplines.data[frame->ip - fn->fn->opcodes.data - 1];
vm->config.error_fn(vm, PK_ERROR_STACKTRACE, fn->owner->path->data, line,
fn->name);
2021-05-16 15:05:54 +08:00
}
2021-02-12 01:35:43 +08:00
}
// This function is responsible to call on_done function if it's done with the
// provided string pointers.
static PKInterpretResult interpretSource(PKVM* vm, pkStringPtr source,
pkStringPtr path) {
String* path_name = newString(vm, path.string);
if (path.on_done) path.on_done(vm, path);
vmPushTempRef(vm, &path_name->_super); // path_name.
2021-05-12 18:57:35 +08:00
// Load a new script to the vm's scripts cache.
Script* scr = getScript(vm, path_name);
if (scr == NULL) {
scr = newScript(vm, path_name);
vmPushTempRef(vm, &scr->_super); // scr.
mapSet(vm->scripts, vm, VAR_OBJ(&path_name->_super),
VAR_OBJ(&scr->_super));
vmPopTempRef(vm); // scr.
}
vmPopTempRef(vm); // path_name.
2021-05-12 18:57:35 +08:00
// Compile the source.
bool success = compile(vm, scr, source.string);
if (source.on_done) source.on_done(vm, source);
2021-05-12 18:57:35 +08:00
if (!success) return PK_RESULT_COMPILE_ERROR;
vm->script = scr;
return vmRunScript(vm, scr);
}
PKInterpretResult pkInterpretSource(PKVM* vm, const char* source,
const char* path) {
// Call the internal interpretSource implementation.
pkStringPtr source_ptr = { source, NULL, NULL };
pkStringPtr path_ptr = { path, NULL, NULL };
return interpretSource(vm, source_ptr, path_ptr);
}
2021-02-12 01:35:43 +08:00
PKInterpretResult pkInterpret(PKVM* vm, const char* path) {
2021-05-07 17:41:19 +08:00
pkStringPtr resolved;
resolved.string = path;
resolved.on_done = NULL;
// The provided path should already be resolved.
//if (!resolveScriptPath(vm, &resolved)) {
// if (vm->config.error_fn != NULL) {
// vm->config.error_fn(vm, PK_ERROR_COMPILE, NULL, -1,
// stringFormat(vm, "Failed to resolve path '$'.", path)->data);
// }
// return PK_RESULT_COMPILE_ERROR;
//}
2021-05-07 15:21:34 +08:00
// Load the script source.
pkStringPtr source = vm->config.load_script_fn(vm, resolved.string);
if (source.string == NULL) {
if (vm->config.error_fn != NULL) {
vm->config.error_fn(vm, PK_ERROR_COMPILE, NULL, -1,
stringFormat(vm, "Failed to load script '$'.", resolved.string)->data);
}
2021-05-07 15:21:34 +08:00
return PK_RESULT_COMPILE_ERROR;
2021-05-07 15:21:34 +08:00
}
return interpretSource(vm, source, resolved);
2021-02-11 01:23:48 +08:00
}
2021-05-09 18:28:00 +08:00
PKInterpretResult vmRunScript(PKVM* vm, Script* _script) {
2021-02-11 01:23:48 +08:00
// Reference to the instruction pointer in the call frame.
register uint8_t** ip;
#define IP (*ip) // Convinent macro to the instruction pointer.
2021-02-12 01:35:43 +08:00
register Var* rbp; //< Stack base pointer register.
register CallFrame* frame; //< Current call frame.
2021-02-13 01:40:19 +08:00
register Script* script; //< Currently executing script.
2021-02-11 01:23:48 +08:00
// TODO: implement fiber from script body.
vm->fiber = newFiber(vm);
vm->fiber->func = _script->body;
// Allocate stack.
int stack_size = utilPowerOf2Ceil(vm->fiber->func->fn->stack_size + 1);
if (stack_size < MIN_STACK_SIZE) stack_size = MIN_STACK_SIZE;
vm->fiber->stack_size = stack_size;
vm->fiber->stack = ALLOCATE_ARRAY(vm, Var, vm->fiber->stack_size);
vm->fiber->sp = vm->fiber->stack;
vm->fiber->ret = vm->fiber->stack;
// Allocate call frames.
vm->fiber->frame_capacity = INITIAL_CALL_FRAMES;
vm->fiber->frames = ALLOCATE_ARRAY(vm, CallFrame, vm->fiber->frame_capacity);
vm->fiber->frame_count = 1;
// Initialize VM's first frame.
vm->fiber->frames[0].ip = _script->body->fn->opcodes.data;
vm->fiber->frames[0].fn = _script->body;
vm->fiber->frames[0].rbp = vm->fiber->stack;
#define PUSH(value) (*vm->fiber->sp++ = (value))
#define POP() (*(--vm->fiber->sp))
#define DROP() (--vm->fiber->sp)
#define PEEK() (*(vm->fiber->sp - 1))
#define READ_BYTE() (*IP++)
#define READ_SHORT() (IP+=2, (uint16_t)((IP[-2] << 8) | IP[-1]))
2021-02-11 01:23:48 +08:00
// Check if any runtime error exists and if so returns RESULT_RUNTIME_ERROR.
2021-05-09 18:28:00 +08:00
#define CHECK_ERROR() \
do { \
if (HAS_ERROR()) { \
vmReportError(vm); \
return PK_RESULT_RUNTIME_ERROR; \
} \
2021-02-12 01:35:43 +08:00
} while (false)
// [err_msg] must be of type String.
#define RUNTIME_ERROR(err_msg) \
2021-02-17 02:28:03 +08:00
do { \
vm->fiber->error = err_msg; \
2021-02-17 02:28:03 +08:00
vmReportError(vm); \
2021-05-09 18:28:00 +08:00
return PK_RESULT_RUNTIME_ERROR; \
2021-02-12 01:35:43 +08:00
} while (false)
// Load the last call frame to vm's execution variables to resume/run the
// function.
#define LOAD_FRAME() \
do { \
frame = &vm->fiber->frames[vm->fiber->frame_count-1]; \
ip = &(frame->ip); \
rbp = frame->rbp; \
script = frame->fn->owner; \
2021-02-12 01:35:43 +08:00
} while (false)
2021-02-11 01:23:48 +08:00
#ifdef OPCODE
#error "OPCODE" should not be deifined here.
#endif
2021-02-12 01:35:43 +08:00
#define SWITCH(code) \
L_vm_main_loop: \
switch (code = (Opcode)READ_BYTE())
2021-02-11 01:23:48 +08:00
#define OPCODE(code) case OP_##code
#define DISPATCH() goto L_vm_main_loop
2021-02-12 01:35:43 +08:00
PUSH(VAR_NULL); // Return value of the script body.
LOAD_FRAME();
Opcode instruction;
SWITCH(instruction) {
2021-05-16 15:05:54 +08:00
OPCODE(PUSH_CONSTANT):
{
2021-05-05 12:55:27 +08:00
uint16_t index = READ_SHORT();
ASSERT_INDEX(index, script->literals.count);
PUSH(script->literals.data[index]);
2021-02-12 01:35:43 +08:00
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(PUSH_NULL):
PUSH(VAR_NULL);
DISPATCH();
OPCODE(PUSH_TRUE):
PUSH(VAR_TRUE);
DISPATCH();
OPCODE(PUSH_FALSE):
PUSH(VAR_FALSE);
DISPATCH();
2021-05-16 15:05:54 +08:00
OPCODE(SWAP):
{
Var top1 = *(vm->fiber->sp - 1);
Var top2 = *(vm->fiber->sp - 2);
*(vm->fiber->sp - 1) = top2;
*(vm->fiber->sp - 2) = top1;
DISPATCH();
}
2021-02-13 01:40:19 +08:00
OPCODE(PUSH_LIST):
{
List* list = newList(vm, (uint32_t)READ_SHORT());
PUSH(VAR_OBJ(&list->_super));
DISPATCH();
}
OPCODE(PUSH_MAP):
{
Map* map = newMap(vm);
PUSH(VAR_OBJ(&map->_super));
DISPATCH();
}
2021-02-13 01:40:19 +08:00
OPCODE(LIST_APPEND):
{
Var elem = POP();
Var list = PEEK();
2021-02-13 01:40:19 +08:00
ASSERT(IS_OBJ(list) && AS_OBJ(list)->type == OBJ_LIST, OOPS);
varBufferWrite(&((List*)AS_OBJ(list))->elements, vm, elem);
DISPATCH();
}
OPCODE(MAP_INSERT):
{
Var value = POP();
Var key = POP();
Var on = PEEK();
ASSERT(IS_OBJ(on) && AS_OBJ(on)->type == OBJ_MAP, OOPS);
if (IS_OBJ(key) && !isObjectHashable(AS_OBJ(key)->type)) {
RUNTIME_ERROR(stringFormat(vm, "$ type is not hashable.", varTypeName(key)));
} else {
mapSet((Map*)AS_OBJ(on), vm, key, value);
}
varsetSubscript(vm, on, key, value);
CHECK_ERROR();
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(PUSH_LOCAL_0):
OPCODE(PUSH_LOCAL_1):
OPCODE(PUSH_LOCAL_2):
OPCODE(PUSH_LOCAL_3):
OPCODE(PUSH_LOCAL_4):
OPCODE(PUSH_LOCAL_5):
OPCODE(PUSH_LOCAL_6):
OPCODE(PUSH_LOCAL_7):
OPCODE(PUSH_LOCAL_8):
{
int index = (int)(instruction - OP_PUSH_LOCAL_0);
PUSH(rbp[index + 1]); // +1: rbp[0] is return value.
2021-02-12 01:35:43 +08:00
DISPATCH();
}
OPCODE(PUSH_LOCAL_N):
{
2021-05-05 12:55:27 +08:00
uint16_t index = READ_SHORT();
PUSH(rbp[index + 1]); // +1: rbp[0] is return value.
2021-02-12 01:35:43 +08:00
DISPATCH();
}
OPCODE(STORE_LOCAL_0):
OPCODE(STORE_LOCAL_1):
OPCODE(STORE_LOCAL_2):
OPCODE(STORE_LOCAL_3):
OPCODE(STORE_LOCAL_4):
OPCODE(STORE_LOCAL_5):
OPCODE(STORE_LOCAL_6):
OPCODE(STORE_LOCAL_7):
OPCODE(STORE_LOCAL_8):
{
int index = (int)(instruction - OP_STORE_LOCAL_0);
rbp[index + 1] = PEEK(); // +1: rbp[0] is return value.
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(STORE_LOCAL_N):
{
2021-05-05 12:55:27 +08:00
uint16_t index = READ_SHORT();
rbp[index + 1] = PEEK(); // +1: rbp[0] is return value.
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(PUSH_GLOBAL):
{
2021-05-05 12:55:27 +08:00
uint16_t index = READ_SHORT();
2021-02-13 01:40:19 +08:00
ASSERT(index < script->globals.count, OOPS);
2021-02-12 01:35:43 +08:00
PUSH(script->globals.data[index]);
DISPATCH();
}
OPCODE(STORE_GLOBAL):
{
2021-05-05 12:55:27 +08:00
uint16_t index = READ_SHORT();
2021-02-13 01:40:19 +08:00
ASSERT(index < script->globals.count, OOPS);
2021-02-13 21:57:59 +08:00
script->globals.data[index] = PEEK();
2021-02-12 01:35:43 +08:00
DISPATCH();
}
OPCODE(PUSH_FN):
{
2021-05-05 12:55:27 +08:00
uint16_t index = READ_SHORT();
2021-02-13 01:40:19 +08:00
ASSERT(index < script->functions.count, OOPS);
2021-02-12 01:35:43 +08:00
Function* fn = script->functions.data[index];
PUSH(VAR_OBJ(&fn->_super));
DISPATCH();
}
OPCODE(PUSH_BUILTIN_FN):
{
Function* fn = getBuiltinFunction(vm, READ_SHORT());
2021-02-12 01:35:43 +08:00
PUSH(VAR_OBJ(&fn->_super));
DISPATCH();
}
OPCODE(POP):
DROP();
DISPATCH();
OPCODE(IMPORT):
2021-05-06 22:19:30 +08:00
{
2021-05-09 20:31:36 +08:00
String* name = script->names.data[READ_SHORT()];
PUSH(importScript(vm, name, true, NULL));
2021-05-06 22:19:30 +08:00
CHECK_ERROR();
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(CALL):
{
2021-05-05 12:55:27 +08:00
uint16_t argc = READ_SHORT();
Var* callable = vm->fiber->sp - argc - 1;
2021-02-12 01:35:43 +08:00
if (IS_OBJ(*callable) && AS_OBJ(*callable)->type == OBJ_FUNC) {
Function* fn = (Function*)AS_OBJ(*callable);
2021-02-13 01:40:19 +08:00
// -1 argument means multiple number of args.
if (fn->arity != -1 && fn->arity != argc) {
String* arg_str = toString(vm, VAR_NUM(fn->arity), false);
vmPushTempRef(vm, &arg_str->_super);
String* msg = stringFormat(vm, "Expected excatly @ argument(s).",
arg_str);
vmPopTempRef(vm); // arg_str.
RUNTIME_ERROR(msg);
2021-02-12 01:35:43 +08:00
}
// Now the function will never needed in the stack and it'll
// initialized with VAR_NULL as return value.
*callable = VAR_NULL;
// Next call frame starts here. (including return value).
vm->fiber->ret = callable;
2021-02-12 01:35:43 +08:00
if (fn->is_native) {
if (fn->native == NULL) {
RUNTIME_ERROR(stringFormat(vm,
"Native function pointer of $ was NULL.", fn->name));
}
2021-02-12 01:35:43 +08:00
fn->native(vm);
// Pop function arguments except for the return value.
vm->fiber->sp = vm->fiber->ret + 1;
2021-02-12 01:35:43 +08:00
CHECK_ERROR();
} else {
pushCallFrame(vm, fn);
LOAD_FRAME(); //< Load the top frame to vm's execution variables.
2021-02-12 01:35:43 +08:00
}
} else {
RUNTIME_ERROR(newString(vm, "Expected a function in call."));
2021-02-12 01:35:43 +08:00
}
DISPATCH();
}
OPCODE(ITER):
2021-02-13 01:40:19 +08:00
{
Var* iter_value = (vm->fiber->sp - 1);
Var* iterator = (vm->fiber->sp - 2);
Var* container = (vm->fiber->sp - 3);
2021-05-05 12:55:27 +08:00
uint16_t jump_offset = READ_SHORT();
bool iterated = varIterate(vm, *container, iterator, iter_value);
CHECK_ERROR();
if (!iterated) {
IP += jump_offset;
2021-02-13 01:40:19 +08:00
}
DISPATCH();
}
2021-04-26 17:34:30 +08:00
OPCODE(JUMP):
{
2021-05-05 12:55:27 +08:00
uint16_t offset = READ_SHORT();
IP += offset;
2021-02-13 01:40:19 +08:00
DISPATCH();
}
2021-04-26 17:34:30 +08:00
OPCODE(LOOP):
{
2021-05-05 12:55:27 +08:00
uint16_t offset = READ_SHORT();
IP -= offset;
2021-02-13 01:40:19 +08:00
DISPATCH();
}
OPCODE(JUMP_IF):
2021-02-15 20:49:19 +08:00
{
Var cond = POP();
2021-05-05 12:55:27 +08:00
uint16_t offset = READ_SHORT();
2021-02-15 20:49:19 +08:00
if (toBool(cond)) {
IP += offset;
2021-02-15 20:49:19 +08:00
}
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(JUMP_IF_NOT):
2021-02-15 20:49:19 +08:00
{
Var cond = POP();
2021-05-05 12:55:27 +08:00
uint16_t offset = READ_SHORT();
2021-02-15 20:49:19 +08:00
if (!toBool(cond)) {
IP += offset;
2021-02-15 20:49:19 +08:00
}
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(RETURN):
{
// TODO: handle caller fiber.
Var ret = POP();
// Pop the last frame.
vm->fiber->frame_count--;
// If no more call frames. We're done.
if (vm->fiber->frame_count == 0) {
vm->fiber->sp = vm->fiber->stack;
PUSH(ret);
2021-05-09 18:28:00 +08:00
return PK_RESULT_SUCCESS;
}
// Set the return value.
*(frame->rbp) = ret;
// Pop the locals and update stack pointer.
vm->fiber->sp = frame->rbp + 1; // +1: rbp is returned value.
LOAD_FRAME();
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(GET_ATTRIB):
2021-02-16 02:51:00 +08:00
{
Var on = POP();
String* name = script->names.data[READ_SHORT()];
PUSH(varGetAttrib(vm, on, name));
CHECK_ERROR();
2021-02-16 02:51:00 +08:00
DISPATCH();
}
OPCODE(GET_ATTRIB_KEEP):
2021-02-16 02:51:00 +08:00
{
Var on = PEEK();
2021-02-16 02:51:00 +08:00
String* name = script->names.data[READ_SHORT()];
PUSH(varGetAttrib(vm, on, name));
CHECK_ERROR();
2021-02-16 02:51:00 +08:00
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(SET_ATTRIB):
{
Var value = POP();
Var on = POP();
String* name = script->names.data[READ_SHORT()];
varSetAttrib(vm, on, name, value);
PUSH(value);
CHECK_ERROR();
DISPATCH();
}
2021-02-16 02:51:00 +08:00
2021-02-12 01:35:43 +08:00
OPCODE(GET_SUBSCRIPT):
2021-02-16 02:51:00 +08:00
{
Var key = POP();
Var on = POP();
PUSH(varGetSubscript(vm, on, key));
CHECK_ERROR();
DISPATCH();
}
OPCODE(GET_SUBSCRIPT_KEEP):
2021-02-16 02:51:00 +08:00
{
Var key = *(vm->fiber->sp - 1);
Var on = *(vm->fiber->sp - 2);
2021-02-16 02:51:00 +08:00
PUSH(varGetSubscript(vm, on, key));
CHECK_ERROR();
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(SET_SUBSCRIPT):
2021-02-16 02:51:00 +08:00
{
Var value = POP();
Var key = POP();
Var on = POP();
varsetSubscript(vm, on, key, value);
CHECK_ERROR();
PUSH(value);
DISPATCH();
}
2021-02-13 01:40:19 +08:00
2021-02-12 01:35:43 +08:00
OPCODE(NEGATIVE):
2021-02-13 01:40:19 +08:00
{
Var num = POP();
if (!IS_NUM(num)) {
RUNTIME_ERROR(newString(vm, "Cannot negate a non numeric value."));
2021-02-13 01:40:19 +08:00
}
PUSH(VAR_NUM(-AS_NUM(num)));
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(NOT):
2021-02-15 20:49:19 +08:00
{
Var val = POP();
PUSH(VAR_BOOL(!toBool(val)));
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(BIT_NOT):
TODO;
2021-05-12 18:57:35 +08:00
// Do not ever use PUSH(binaryOp(vm, POP(), POP()));
// Function parameters are not evaluated in a defined order in C.
2021-02-12 01:35:43 +08:00
OPCODE(ADD):
2021-05-12 18:57:35 +08:00
{
Var r = POP(), l = POP();
PUSH(varAdd(vm, l, r));
2021-02-13 01:40:19 +08:00
CHECK_ERROR();
2021-02-12 01:35:43 +08:00
DISPATCH();
2021-05-12 18:57:35 +08:00
}
2021-02-12 01:35:43 +08:00
OPCODE(SUBTRACT):
2021-05-12 18:57:35 +08:00
{
Var r = POP(), l = POP();
PUSH(varSubtract(vm, l, r));
2021-02-13 01:40:19 +08:00
CHECK_ERROR();
2021-02-12 01:35:43 +08:00
DISPATCH();
2021-05-12 18:57:35 +08:00
}
2021-02-12 01:35:43 +08:00
OPCODE(MULTIPLY):
2021-05-12 18:57:35 +08:00
{
Var r = POP(), l = POP();
PUSH(varMultiply(vm, l, r));
2021-02-13 01:40:19 +08:00
CHECK_ERROR();
2021-02-12 01:35:43 +08:00
DISPATCH();
2021-05-12 18:57:35 +08:00
}
2021-02-12 01:35:43 +08:00
OPCODE(DIVIDE):
2021-05-12 18:57:35 +08:00
{
Var r = POP(), l = POP();
PUSH(varDivide(vm, l, r));
2021-02-13 01:40:19 +08:00
CHECK_ERROR();
2021-02-12 01:35:43 +08:00
DISPATCH();
2021-05-12 18:57:35 +08:00
}
2021-02-12 01:35:43 +08:00
OPCODE(MOD):
2021-05-12 18:57:35 +08:00
{
Var r = POP(), l = POP();
PUSH(varModulo(vm, l, r));
CHECK_ERROR();
DISPATCH();
2021-05-12 18:57:35 +08:00
}
2021-02-12 01:35:43 +08:00
OPCODE(BIT_AND):
OPCODE(BIT_OR):
OPCODE(BIT_XOR):
OPCODE(BIT_LSHIFT):
OPCODE(BIT_RSHIFT):
2021-05-12 18:57:35 +08:00
OPCODE(EQEQ) :
{
Var r = POP(), l = POP();
PUSH(VAR_BOOL(isValuesEqual(l, r)));
DISPATCH();
2021-05-12 18:57:35 +08:00
}
2021-02-12 01:35:43 +08:00
OPCODE(NOTEQ):
2021-05-12 18:57:35 +08:00
{
Var r = POP(), l = POP();
PUSH(VAR_BOOL(!isValuesEqual(l, r)));
DISPATCH();
2021-05-12 18:57:35 +08:00
}
2021-02-15 20:49:19 +08:00
2021-02-12 01:35:43 +08:00
OPCODE(LT):
2021-02-15 20:49:19 +08:00
{
2021-05-12 18:57:35 +08:00
Var r = POP(), l = POP();
PUSH(VAR_BOOL(varLesser(vm, l, r)));
2021-02-16 02:51:00 +08:00
CHECK_ERROR();
2021-02-15 20:49:19 +08:00
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(LTEQ):
{
2021-05-12 18:57:35 +08:00
Var r = POP(), l = POP();
bool lteq = varLesser(vm, l, r);
CHECK_ERROR();
if (!lteq) {
lteq = isValuesEqual(l, r);
CHECK_ERROR();
}
PUSH(VAR_BOOL(lteq));
DISPATCH();
}
2021-02-15 20:49:19 +08:00
2021-02-12 01:35:43 +08:00
OPCODE(GT):
2021-02-15 20:49:19 +08:00
{
2021-05-12 18:57:35 +08:00
Var r = POP(), l = POP();
PUSH(VAR_BOOL(varGreater(vm, l, r)));
2021-02-16 02:51:00 +08:00
CHECK_ERROR();
2021-02-15 20:49:19 +08:00
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(GTEQ):
{
2021-05-12 18:57:35 +08:00
Var r = POP(), l = POP();
bool gteq = varGreater(vm, l, r);
CHECK_ERROR();
if (!gteq) {
gteq = isValuesEqual(l, r);
CHECK_ERROR();
}
PUSH(VAR_BOOL(gteq));
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(RANGE):
{
Var to = POP();
Var from = POP();
if (!IS_NUM(from) || !IS_NUM(to)) {
RUNTIME_ERROR(newString(vm, "Range arguments must be number."));
}
PUSH(VAR_OBJ(newRange(vm, AS_NUM(from), AS_NUM(to))));
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(IN):
// TODO: Implement bool varContaines(vm, on, value);
TODO;
2021-02-12 01:35:43 +08:00
OPCODE(END):
TODO;
2021-02-12 01:35:43 +08:00
break;
default:
UNREACHABLE();
}
2021-05-09 18:28:00 +08:00
return PK_RESULT_SUCCESS;
2021-02-11 01:23:48 +08:00
}