pocketlang/src/pk_vm.c

1672 lines
47 KiB
C
Raw Normal View History

2021-02-07 15:40:00 +08:00
/*
* Copyright (c) 2020-2022 Thakee Nathees
* Copyright (c) 2021-2022 Pocketlang Contributors
2021-06-09 18:42:26 +08:00
* Distributed Under The MIT License
2021-02-07 15:40:00 +08:00
*/
2021-06-09 18:42:26 +08:00
#include "pk_vm.h"
2021-02-07 15:40:00 +08:00
2021-05-24 06:17:52 +08:00
#include <math.h>
2021-06-09 18:42:26 +08:00
#include "pk_core.h"
#include "pk_utils.h"
#include "pk_debug.h"
2021-05-19 02:59:09 +08:00
2021-05-28 03:27:29 +08:00
/*****************************************************************************/
/* VM PUBLIC API */
/*****************************************************************************/
2021-05-24 06:17:52 +08:00
// The default allocator that will be used to initialize the vm's configuration
// if the host doesn't provided any allocators for us.
2021-05-28 03:27:29 +08:00
static void* defaultRealloc(void* memory, size_t new_size, void* user_data);
2021-02-07 15:40:00 +08:00
2021-06-07 13:54:06 +08:00
// Runs the [fiber] if it's at yielded state, this will resume the execution
// till the next yield or return statement, and return result.
static PkResult runFiber(PKVM* vm, Fiber* fiber);
2021-06-08 00:56:56 +08:00
PkConfiguration pkNewConfiguration(void) {
2021-06-04 22:55:06 +08:00
PkConfiguration config;
2021-05-19 02:59:09 +08:00
config.realloc_fn = defaultRealloc;
2021-04-26 17:34:30 +08:00
2021-05-19 02:59:09 +08:00
config.error_fn = NULL;
config.write_fn = NULL;
2021-06-07 13:54:06 +08:00
config.read_fn = NULL;
2021-04-26 17:34:30 +08:00
2021-05-19 02:59:09 +08:00
config.load_script_fn = NULL;
config.resolve_path_fn = NULL;
config.user_data = NULL;
return config;
2021-04-26 17:34:30 +08:00
}
2021-06-08 00:56:56 +08:00
PkCompileOptions pkNewCompilerOptions(void) {
2021-06-07 13:54:06 +08:00
PkCompileOptions options;
options.debug = false;
options.repl_mode = false;
return options;
}
2021-06-04 22:55:06 +08:00
PKVM* pkNewVM(PkConfiguration* config) {
2021-05-06 22:19:30 +08:00
2021-06-04 22:55:06 +08:00
PkConfiguration default_config = pkNewConfiguration();
2021-05-08 18:54:07 +08:00
2021-05-19 02:59:09 +08:00
if (config == NULL) config = &default_config;
PKVM* vm = (PKVM*)config->realloc_fn(NULL, sizeof(PKVM), config->user_data);
2021-05-09 18:28:00 +08:00
memset(vm, 0, sizeof(PKVM));
2021-05-06 22:19:30 +08:00
vm->config = *config;
vm->working_set_count = 0;
vm->working_set_capacity = MIN_CAPACITY;
vm->working_set = (Object**)vm->config.realloc_fn(
NULL, sizeof(Object*) * vm->working_set_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->modules = newMap(vm);
vm->builtins_count = 0;
2021-05-06 22:19:30 +08:00
// This is necessary to prevent garbage collection skip the entry in this
// array while we're building it.
2022-04-20 12:10:54 +08:00
for (int i = 0; i < PK_INSTANCE; i++) {
vm->builtin_classes[i] = NULL;
}
2021-05-06 22:19:30 +08:00
initializeCore(vm);
2021-04-26 17:34:30 +08:00
return vm;
}
2021-06-05 22:47:59 +08:00
void pkFreeVM(PKVM* vm) {
2021-04-26 17:34:30 +08:00
2021-06-05 22:47:59 +08:00
Object* obj = vm->first;
2021-04-26 17:34:30 +08:00
while (obj != NULL) {
Object* next = obj->next;
2021-06-05 22:47:59 +08:00
freeObject(vm, obj);
2021-04-26 17:34:30 +08:00
obj = next;
}
vm->working_set = (Object**)vm->config.realloc_fn(
vm->working_set, 0, vm->config.user_data);
2021-02-12 01:35:43 +08:00
2021-05-23 04:59:32 +08:00
// Tell the host application that it forget to release all of it's handles
// before freeing the VM.
2021-06-05 22:47:59 +08:00
__ASSERT(vm->handles == NULL, "Not all handles were released.");
2021-05-23 04:59:32 +08:00
2021-06-05 22:47:59 +08:00
DEALLOCATE(vm, vm);
2021-02-11 01:23:48 +08:00
}
void* pkGetUserData(const PKVM* vm) {
2021-05-28 03:27:29 +08:00
return vm->config.user_data;
}
void pkSetUserData(PKVM* vm, void* user_data) {
vm->config.user_data = user_data;
}
2021-05-23 04:59:32 +08:00
PkHandle* pkNewHandle(PKVM* vm, PkVar value) {
return vmNewHandle(vm, *((Var*)value));
}
PkVar pkGetHandleValue(const PkHandle* handle) {
2021-05-23 04:59:32 +08:00
return (PkVar)&handle->value;
}
void pkReleaseHandle(PKVM* vm, PkHandle* handle) {
__ASSERT(handle != NULL, "Given handle was NULL.");
// If the handle is the head of the vm's handle chain set it to the next one.
if (handle == vm->handles) {
vm->handles = handle->next;
}
// Remove the handle from the chain by connecting the both ends together.
if (handle->next) handle->next->prev = handle->prev;
if (handle->prev) handle->prev->next = handle->next;
// Free the handle.
DEALLOCATE(vm, handle);
}
2021-06-11 15:46:55 +08:00
// This function is responsible to call on_done function if it's done with the
2021-06-07 13:54:06 +08:00
// provided string pointers.
PkResult pkInterpretSource(PKVM* vm, PkStringPtr source, PkStringPtr path,
const PkCompileOptions* options) {
String* path_ = newString(vm, path.string);
2021-06-07 13:54:06 +08:00
if (path.on_done) path.on_done(vm, path);
vmPushTempRef(vm, &path_->_super); // path_
2021-06-07 13:54:06 +08:00
// FIXME:
// Should I clean the module if it already exists before compiling it?
2021-06-07 13:54:06 +08:00
// Load a new module to the vm's modules cache.
Module* module = vmGetModule(vm, path_);
if (module == NULL) {
module = newModule(vm);
module->path = path_;
vmPushTempRef(vm, &module->_super); // module.
vmRegisterModule(vm, module, path_);
vmPopTempRef(vm); // module.
2021-06-07 13:54:06 +08:00
}
vmPopTempRef(vm); // path_
2021-06-07 13:54:06 +08:00
// Compile the source.
PkResult result = compile(vm, module, source.string, options);
2021-06-07 13:54:06 +08:00
if (source.on_done) source.on_done(vm, source);
2021-06-09 18:42:26 +08:00
if (result != PK_RESULT_SUCCESS) return result;
2021-06-07 13:54:06 +08:00
// Set module initialized to true before the execution ends to prevent cyclic
2021-06-07 13:54:06 +08:00
// inclusion cause a crash.
module->initialized = true;
2021-06-07 13:54:06 +08:00
return runFiber(vm, newFiber(vm, module->body));
2021-06-07 13:54:06 +08:00
}
2021-06-08 00:56:56 +08:00
PkResult pkRunFiber(PKVM* vm, PkHandle* fiber,
int argc, PkHandle** argv) {
__ASSERT(fiber != NULL, "Handle fiber was NULL.");
Var fb = fiber->value;
__ASSERT(IS_OBJ_TYPE(fb, OBJ_FIBER), "Given handle is not a fiber.");
Fiber* _fiber = (Fiber*)AS_OBJ(fb);
Var* args[MAX_ARGC];
for (int i = 0; i < argc; i++) {
args[i] = &(argv[i]->value);
}
if (!vmPrepareFiber(vm, _fiber, argc, args)) {
return PK_RESULT_RUNTIME_ERROR;
}
ASSERT(_fiber->frame_count == 1, OOPS);
return runFiber(vm, _fiber);
}
PkResult pkResumeFiber(PKVM* vm, PkHandle* fiber, PkVar value) {
__ASSERT(fiber != NULL, "Handle fiber was NULL.");
Var fb = fiber->value;
__ASSERT(IS_OBJ_TYPE(fb, OBJ_FIBER), "Given handle is not a fiber.");
Fiber* _fiber = (Fiber*)AS_OBJ(fb);
if (!vmSwitchFiber(vm, _fiber, (Var*)value)) {
return PK_RESULT_RUNTIME_ERROR;
}
return runFiber(vm, _fiber);
}
2021-06-07 13:54:06 +08:00
void pkSetRuntimeError(PKVM* vm, const char* message) {
__ASSERT(vm->fiber != NULL, "This function can only be called at runtime.");
2021-06-11 15:46:55 +08:00
VM_SET_ERROR(vm, newString(vm, message));
2021-06-07 13:54:06 +08:00
}
/*****************************************************************************/
/* SHARED FUNCTIONS */
/*****************************************************************************/
2021-06-05 22:47:59 +08:00
PkHandle* vmNewHandle(PKVM* vm, Var value) {
PkHandle* handle = (PkHandle*)ALLOCATE(vm, PkHandle);
2021-05-23 04:59:32 +08:00
handle->value = value;
handle->prev = NULL;
2021-06-05 22:47:59 +08:00
handle->next = vm->handles;
2021-05-23 04:59:32 +08:00
if (handle->next != NULL) handle->next->prev = handle;
2021-06-05 22:47:59 +08:00
vm->handles = handle;
2021-05-23 04:59:32 +08:00
return handle;
}
2021-06-05 22:47:59 +08:00
void* vmRealloc(PKVM* vm, void* memory, size_t old_size, size_t new_size) {
2021-05-28 03:27:29 +08:00
// TODO: Debug trace allocations here.
// Track the total allocated memory of the VM to trigger the GC.
2021-06-11 15:46:55 +08:00
// if vmRealloc is called for freeing, the old_size would be 0 since
2021-05-28 03:27:29 +08:00
// deallocated bytes are traced by garbage collector.
2021-06-05 22:47:59 +08:00
vm->bytes_allocated += new_size - old_size;
2021-05-28 03:27:29 +08:00
2021-06-05 22:47:59 +08:00
if (new_size > 0 && vm->bytes_allocated > vm->next_gc) {
vmCollectGarbage(vm);
2021-05-28 03:27:29 +08:00
}
2021-06-05 22:47:59 +08:00
return vm->config.realloc_fn(memory, new_size, vm->config.user_data);
2021-05-28 03:27:29 +08:00
}
2021-06-05 22:47:59 +08:00
void vmPushTempRef(PKVM* vm, Object* obj) {
2021-05-28 03:27:29 +08:00
ASSERT(obj != NULL, "Cannot reference to NULL.");
2021-06-05 22:47:59 +08:00
ASSERT(vm->temp_reference_count < MAX_TEMP_REFERENCE,
2021-05-28 03:27:29 +08:00
"Too many temp references");
2021-06-05 22:47:59 +08:00
vm->temp_reference[vm->temp_reference_count++] = obj;
2021-05-28 03:27:29 +08:00
}
2021-06-05 22:47:59 +08:00
void vmPopTempRef(PKVM* vm) {
ASSERT(vm->temp_reference_count > 0,
"Temporary reference is empty to pop.");
2021-06-05 22:47:59 +08:00
vm->temp_reference_count--;
2021-05-28 03:27:29 +08:00
}
void vmRegisterModule(PKVM* vm, Module* module, String* key) {
ASSERT((((module->name != NULL) && IS_STR_EQ(module->name, key)) ||
IS_STR_EQ(module->path, key)), OOPS);
// FIXME:
// Not sure what to do, if a module the the same key already exists. Should
// I override or assert.
mapSet(vm, vm->modules, VAR_OBJ(key), VAR_OBJ(module));
}
Module* vmGetModule(PKVM* vm, String* key) {
Var module = mapGet(vm->modules, VAR_OBJ(key));
if (IS_UNDEF(module)) return NULL;
ASSERT(AS_OBJ(module)->type == OBJ_MODULE, OOPS);
return (Module*)AS_OBJ(module);
2021-06-07 13:54:06 +08:00
}
2021-06-05 22:47:59 +08:00
void vmCollectGarbage(PKVM* vm) {
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.
2021-06-05 22:47:59 +08:00
vm->bytes_allocated = 0;
2021-04-25 23:19:39 +08:00
// Mark builtin functions.
for (int i = 0; i < vm->builtins_count; i++) {
2022-04-20 12:10:54 +08:00
markObject(vm, &vm->builtins_funcs[i]->_super);
}
2021-04-26 17:34:30 +08:00
// Mark primitive types' classes.
2022-04-20 12:10:54 +08:00
for (int i = 0; i < PK_INSTANCE; i++) {
// It's possible that a garbage collection could be triggered while we're
// building the primitives and the class could be NULL.
2022-04-20 12:10:54 +08:00
if (vm->builtin_classes[i] == NULL) continue;
2022-04-20 12:10:54 +08:00
markObject(vm, &vm->builtin_classes[i]->_super);
}
// Mark the modules.
markObject(vm, &vm->modules->_super);
2021-04-26 17:34:30 +08:00
// Mark temp references.
2021-06-05 22:47:59 +08:00
for (int i = 0; i < vm->temp_reference_count; i++) {
markObject(vm, vm->temp_reference[i]);
2021-04-26 17:34:30 +08:00
}
2021-05-23 04:59:32 +08:00
// Mark the handles.
2021-06-05 22:47:59 +08:00
for (PkHandle* h = vm->handles; h != NULL; h = h->next) {
markValue(vm, h->value);
2021-05-23 04:59:32 +08:00
}
2021-04-26 17:34:30 +08:00
// Garbage collection triggered at the middle of a compilation.
2021-06-05 22:47:59 +08:00
if (vm->compiler != NULL) {
compilerMarkObjects(vm, vm->compiler);
2021-04-26 17:34:30 +08:00
}
2021-06-05 22:47:59 +08:00
if (vm->fiber != NULL) {
markObject(vm, &vm->fiber->_super);
2021-04-26 17:34:30 +08:00
}
2021-05-28 03:27:29 +08:00
// Pop the marked objects from the working set and push all of it's
// referenced objects. This will repeat till no more objects left in the
// working set.
popMarkedObjects(vm);
2021-05-01 18:13:39 +08:00
// 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*.
2021-06-05 22:47:59 +08:00
Object** ptr = &vm->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;
2021-06-05 22:47:59 +08:00
freeObject(vm, 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].
2021-06-05 22:47:59 +08:00
vm->next_gc = vm->bytes_allocated + (
(vm->bytes_allocated * vm->heap_fill_percent) / 100);
if (vm->next_gc < vm->min_heap_size) vm->next_gc = vm->min_heap_size;
2021-04-25 23:19:39 +08:00
}
2021-06-08 00:56:56 +08:00
#define _ERR_FAIL(msg) \
do { \
2021-06-11 15:46:55 +08:00
if (vm->fiber != NULL) VM_SET_ERROR(vm, msg); \
2021-06-08 00:56:56 +08:00
return false; \
} while (false)
bool vmPrepareFiber(PKVM* vm, Fiber* fiber, int argc, Var** argv) {
ASSERT(fiber->closure->fn->arity >= -1,
OOPS " (Forget to initialize arity.)");
2021-06-08 00:56:56 +08:00
if (argc != fiber->closure->fn->arity) {
char buff[STR_INT_BUFF_SIZE];
sprintf(buff, "%d", fiber->closure->fn->arity);
_ERR_FAIL(stringFormat(vm, "Expected exactly $ argument(s).", buff));
2021-06-08 00:56:56 +08:00
}
if (fiber->state != FIBER_NEW) {
switch (fiber->state) {
case FIBER_NEW: UNREACHABLE();
case FIBER_RUNNING:
_ERR_FAIL(newString(vm, "The fiber has already been running."));
case FIBER_YIELDED:
_ERR_FAIL(newString(vm, "Cannot run a fiber which is yielded, use "
"fiber_resume() instead."));
case FIBER_DONE:
_ERR_FAIL(newString(vm, "The fiber has done running."));
}
UNREACHABLE();
}
ASSERT(fiber->stack != NULL && fiber->sp == fiber->stack + 1, OOPS);
ASSERT(fiber->ret + 1 == fiber->sp, OOPS);
// Pass the function arguments.
// Assert we have the first frame (to push the arguments). And assert we have
// enough stack space for parameters.
2021-06-08 00:56:56 +08:00
ASSERT(fiber->frame_count == 1, OOPS);
ASSERT(fiber->frames[0].rbp == fiber->ret, OOPS);
ASSERT((fiber->stack + fiber->stack_size) - fiber->sp >= argc, OOPS);
// ARG1 is fiber, function arguments are ARG(2), ARG(3), ... ARG(argc).
// And ret[0] is the return value, parameters starts at ret[1], ...
for (int i = 0; i < argc; i++) {
fiber->ret[1 + i] = *argv[i]; // +1: ret[0] is return value.
}
fiber->sp += argc; // Parameters.
// Set the new fiber as the vm's fiber.
fiber->caller = vm->fiber;
vm->fiber = fiber;
// On success return true.
return true;
}
bool vmSwitchFiber(PKVM* vm, Fiber* fiber, Var* value) {
if (fiber->state != FIBER_YIELDED) {
switch (fiber->state) {
case FIBER_NEW:
_ERR_FAIL(newString(vm, "The fiber hasn't started. call fiber_run() "
"to start."));
case FIBER_RUNNING:
_ERR_FAIL(newString(vm, "The fiber has already been running."));
case FIBER_YIELDED: UNREACHABLE();
case FIBER_DONE:
_ERR_FAIL(newString(vm, "The fiber has done running."));
}
UNREACHABLE();
}
// Pass the resume argument if it has any.
// Assert if we have a call frame and the stack size enough for the return
// value and the resumed value.
ASSERT(fiber->frame_count != 0, OOPS);
ASSERT((fiber->stack + fiber->stack_size) - fiber->sp >= 2, OOPS);
// fb->ret will points to the return value of the 'yield()' call.
if (value == NULL) *fiber->ret = VAR_NULL;
else *fiber->ret = *value;
// Switch fiber.
fiber->caller = vm->fiber;
vm->fiber = fiber;
// On success return true.
return true;
}
#undef _ERR_FAIL
2021-06-07 13:54:06 +08:00
void vmYieldFiber(PKVM* vm, Var* value) {
Fiber* caller = vm->fiber->caller;
// Return the yield value to the caller fiber.
if (caller != NULL) {
if (value == NULL) *caller->ret = VAR_NULL;
else *caller->ret = *value;
}
// Can be resumed by another caller fiber.
vm->fiber->caller = NULL;
vm->fiber->state = FIBER_YIELDED;
vm->fiber = caller;
}
2021-05-28 03:27:29 +08:00
/*****************************************************************************/
/* VM INTERNALS */
/*****************************************************************************/
2021-02-17 02:28:03 +08:00
2021-05-28 03:27:29 +08:00
// The default allocator that will be used to initialize the vm's configuration
// if the host doesn't provided any allocators for us.
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-02-17 02:28:03 +08:00
}
// FIXME:
// We're assuming that the module should be available at the VM's modules cache
// which is added by the compilation pahse, but we cannot rely on the
// compilation phase here as it could be a seperate system from the runtime and
// should throw a runtime error if the module is not present in the modules
// cache (or try to load).
// Example: If we may support to store the compiled script as a separate file
// (like python's ".pyc" or java's ".class" the runtime cannot ensure that the
// module it import is already cached.
//
// Import and return the Module object with the [name] (if it's a scirpt
// doesn't have a module name, the name would be it's resolved path).
static inline Var importModule(PKVM* vm, String* key) {
2021-05-06 22:19:30 +08:00
Var entry = mapGet(vm->modules, VAR_OBJ(key));
2021-05-19 02:59:09 +08:00
if (!IS_UNDEF(entry)) {
ASSERT(AS_OBJ(entry)->type == OBJ_MODULE, OOPS);
2021-05-19 02:59:09 +08:00
return entry;
}
2021-05-06 22:19:30 +08:00
// FIXME: Should be a runtime error.
// Imported modules were resolved at compile time.
2021-05-19 02:59:09 +08:00
UNREACHABLE();
2021-05-07 17:41:19 +08:00
2021-05-19 02:59:09 +08:00
return VAR_NULL;
2021-05-06 22:19:30 +08:00
}
2021-05-28 03:27:29 +08:00
static inline void growStack(PKVM* vm, int size) {
Fiber* fiber = vm->fiber;
2021-05-24 06:17:52 +08:00
ASSERT(fiber->stack_size <= size, OOPS);
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;
2021-06-11 15:46:55 +08:00
// 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))
2021-05-24 06:17:52 +08:00
// Update the stack top pointer and the return pointer.
fiber->sp = MAP_PTR(fiber->sp);
2021-05-24 06:17:52 +08:00
fiber->ret = MAP_PTR(fiber->ret);
// Update the stack base pointer of the call frames.
2021-05-24 06:17:52 +08:00
for (int i = 0; i < fiber->frame_count; i++) {
CallFrame* frame = fiber->frames + i;
frame->rbp = MAP_PTR(frame->rbp);
}
2021-02-12 01:35:43 +08:00
}
static inline void pushCallFrame(PKVM* vm, const Closure* closure, Var* rbp) {
ASSERT(!closure->fn->is_native, OOPS);
2021-06-20 18:23:21 +08:00
// Grow the stack frame if needed.
if (vm->fiber->frame_count + 1 > vm->fiber->frame_capacity) {
int new_capacity = vm->fiber->frame_capacity << 1;
vm->fiber->frames = (CallFrame*)vmRealloc(vm, vm->fiber->frames,
sizeof(CallFrame) * vm->fiber->frame_capacity,
sizeof(CallFrame) * new_capacity);
vm->fiber->frame_capacity = new_capacity;
}
2021-02-12 01:35:43 +08:00
2021-06-20 18:23:21 +08:00
// Grow the stack if needed.
int needed = (closure->fn->fn->stack_size +
(int)(vm->fiber->sp - vm->fiber->stack));
2021-06-20 18:23:21 +08:00
if (vm->fiber->stack_size <= needed) growStack(vm, needed);
2021-02-12 01:35:43 +08:00
2021-06-20 18:23:21 +08:00
CallFrame* frame = vm->fiber->frames + vm->fiber->frame_count++;
frame->rbp = rbp;
frame->closure = closure;
frame->ip = closure->fn->fn->opcodes.data;
frame->self = VAR_UNDEFINED;
2021-02-12 01:35:43 +08:00
}
static inline void reuseCallFrame(PKVM* vm, const Closure* closure) {
2021-06-13 04:17:44 +08:00
ASSERT(!closure->fn->is_native, OOPS);
ASSERT(closure->fn->arity >= 0, OOPS);
2021-06-13 04:17:44 +08:00
ASSERT(vm->fiber->frame_count > 0, OOPS);
Fiber* fb = vm->fiber;
2021-06-20 18:23:21 +08:00
CallFrame* frame = fb->frames + fb->frame_count - 1;
frame->closure = closure;
frame->ip = closure->fn->fn->opcodes.data;
2021-06-20 18:23:21 +08:00
ASSERT(*frame->rbp == VAR_NULL, OOPS);
2021-06-13 04:17:44 +08:00
// Move all the argument(s) to the base of the current frame.
Var* arg = fb->sp - closure->fn->arity;
2021-06-20 18:23:21 +08:00
Var* target = frame->rbp + 1;
2021-06-13 04:17:44 +08:00
for (; arg < fb->sp; arg++, target++) {
*target = *arg;
}
// At this point target points to the stack pointer of the next call.
fb->sp = target;
// Grow the stack if needed (least probably).
int needed = (closure->fn->fn->stack_size +
(int)(vm->fiber->sp - vm->fiber->stack));
2021-06-13 04:17:44 +08:00
if (vm->fiber->stack_size <= needed) growStack(vm, needed);
}
2022-04-13 17:01:57 +08:00
// Capture the [local] into an upvalue and return it. If the upvalue already
// exists on the fiber, it'll return it.
static Upvalue* captureUpvalue(PKVM* vm, Fiber* fiber, Var* local) {
// If the fiber doesn't have any upvalues yet, create new one and add it.
if (fiber->open_upvalues == NULL) {
Upvalue* upvalue = newUpvalue(vm, local);
fiber->open_upvalues = upvalue;
return upvalue;
}
// In the bellow diagram 'u0' is the head of the open upvalues of the fiber.
// We'll walk through the upvalues to see if any of it's value is similar
// to the [local] we want to capture.
//
// This can be optimized with binary search since the upvalues are sorted
// but it's not a frequent task neither the number of upvalues would be very
// few and the local mostly located at the stack top.
//
// 1. If say 'l3' is what we want to capture, that local already has an
// upavlue 'u1' return it.
// 2. If say 'l4' is what we want to capture, It doesn't have an upvalue yet.
// Create a new upvalue and insert to the link list (ie. u1.next = u3,
// u3.next = u2) and return it.
//
// | |
// | l1 | <-- u0 (u1.value = l3)
// | l2 | |
// | l3 | <-- u1 (u1.value = l3)
// | l4 | |
// | l5 | <-- u2 (u2.value = l5)
// '------' |
// stack NULL
// Edge case: if the local is located higher than all the open upvalues, we
// cannot walk the chain, it's going to be the new head of the open upvalues.
if (fiber->open_upvalues->ptr < local) {
Upvalue* head = newUpvalue(vm, local);
head->next = fiber->open_upvalues;
fiber->open_upvalues = head;
return head;
}
// Now we walk the chain of open upvalues and if we find an upvalue for the
// local return it, otherwise insert it in the chain.
Upvalue* last = NULL;
Upvalue* current = fiber->open_upvalues;
while (current->ptr > local) {
last = current;
current = current->next;
// If the current is NULL, we've walked all the way to the end of the open
// upvalues, and there isn't one upvalue for the local.
if (current == NULL) {
last->next = newUpvalue(vm, local);
return last->next;
}
}
// If [current] is the upvalue that captured [local] then return it.
if (current->ptr == local) return current;
ASSERT(last != NULL, OOPS);
// If we've reached here, the upvalue isn't found, create a new one and
// insert it to the chain.
Upvalue* upvalue = newUpvalue(vm, local);
last->next = upvalue;
upvalue->next = current;
return upvalue;
}
// Close all the upvalues for the locals including [top] and higher in the
// stack.
static void closeUpvalues(Fiber* fiber, Var* top) {
while (fiber->open_upvalues != NULL && fiber->open_upvalues->ptr >= top) {
Upvalue* upvalue = fiber->open_upvalues;
upvalue->closed = *upvalue->ptr;
upvalue->ptr = &upvalue->closed;
fiber->open_upvalues = upvalue->next;
}
}
2021-06-07 13:54:06 +08:00
static void reportError(PKVM* vm) {
2021-06-11 15:46:55 +08:00
ASSERT(VM_HAS_ERROR(vm), "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];
const Function* fn = frame->closure->fn;
2021-05-16 15:05:54 +08:00
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
}
2021-05-28 03:27:29 +08:00
/******************************************************************************
* RUNTIME *
*****************************************************************************/
static PkResult runFiber(PKVM* vm, Fiber* fiber_) {
2021-02-11 01:23:48 +08:00
2021-06-05 22:47:59 +08:00
// Set the fiber as the vm's current fiber (another root object) to prevent
// it from garbage collection and get the reference from native functions.
vm->fiber = fiber_;
2021-06-05 22:47:59 +08:00
ASSERT(fiber_->state == FIBER_NEW || fiber_->state == FIBER_YIELDED, OOPS);
fiber_->state = FIBER_RUNNING;
2021-05-29 02:53:46 +08:00
2021-06-04 22:55:06 +08:00
// The instruction pointer.
register const uint8_t* ip;
2021-02-12 01:35:43 +08:00
register Var* rbp; //< Stack base pointer register.
register Var* self; //< Points to the self in the current call frame.
2021-02-12 01:35:43 +08:00
register CallFrame* frame; //< Current call frame.
register Module* module; //< Currently executing module.
register Fiber* fiber = fiber_;
2021-02-11 01:23:48 +08:00
#if DEBUG
#define PUSH(value) \
do { \
ASSERT(fiber->sp < (fiber->stack + (fiber->stack_size - 1)), \
OOPS); \
(*fiber->sp++ = (value)); \
} while (false)
#else
#define PUSH(value) (*fiber->sp++ = (value))
#endif
#define POP() (*(--fiber->sp))
#define DROP() (--fiber->sp)
#define PEEK(off) (*(fiber->sp + (off)))
2021-06-04 22:55:06 +08:00
#define READ_BYTE() (*ip++)
#define READ_SHORT() (ip+=2, (uint16_t)((ip[-2] << 8) | ip[-1]))
2021-02-11 01:23:48 +08:00
2021-06-08 00:56:56 +08:00
// Switch back to the caller of the current fiber, will be called when we're
// done with the fiber or aborting it for runtime errors.
#define FIBER_SWITCH_BACK() \
do { \
Fiber* caller = fiber->caller; \
2021-06-08 00:56:56 +08:00
ASSERT(caller == NULL || caller->state == FIBER_RUNNING, OOPS); \
fiber->state = FIBER_DONE; \
fiber->caller = NULL; \
fiber = caller; \
vm->fiber = fiber; \
2021-06-08 00:56:56 +08:00
} while (false)
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 { \
2021-06-11 15:46:55 +08:00
if (VM_HAS_ERROR(vm)) { \
2021-06-04 22:55:06 +08:00
UPDATE_FRAME(); \
2021-06-07 13:54:06 +08:00
reportError(vm); \
2021-06-08 00:56:56 +08:00
FIBER_SWITCH_BACK(); \
2021-05-09 18:28:00 +08:00
return PK_RESULT_RUNTIME_ERROR; \
} \
2021-02-12 01:35:43 +08:00
} while (false)
// [err_msg] must be of type String.
2021-06-04 22:55:06 +08:00
#define RUNTIME_ERROR(err_msg) \
do { \
2021-06-11 15:46:55 +08:00
VM_SET_ERROR(vm, err_msg); \
2021-06-04 22:55:06 +08:00
UPDATE_FRAME(); \
2021-06-07 13:54:06 +08:00
reportError(vm); \
2021-06-08 00:56:56 +08:00
FIBER_SWITCH_BACK(); \
2021-06-04 22:55:06 +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 = &fiber->frames[fiber->frame_count-1]; \
ip = frame->ip; \
rbp = frame->rbp; \
self = &frame->self; \
module = frame->closure->fn->owner; \
2021-02-12 01:35:43 +08:00
} while (false)
2021-02-11 01:23:48 +08:00
2021-06-04 22:55:06 +08:00
// Update the frame's execution variables before pushing another call frame.
2021-06-05 22:47:59 +08:00
#define UPDATE_FRAME() frame->ip = ip
2021-06-04 22:55:06 +08:00
2021-02-11 01:23:48 +08:00
#ifdef OPCODE
#error "OPCODE" should not be deifined here.
#endif
2021-05-24 06:17:52 +08:00
#define SWITCH() Opcode instruction; switch (instruction = (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-06-08 00:56:56 +08:00
// Load the fiber's top call frame to the vm's execution variables.
2021-02-12 01:35:43 +08:00
LOAD_FRAME();
L_vm_main_loop:
// This NO_OP is required since Labels can only be followed by statements
// and, declarations are not statements, If the macro DUMP_STACK isn't
// defined, the next line become a declaration (Opcode instruction;).
NO_OP;
#if DUMP_STACK
system("cls"); // FIXME:
dumpGlobalValues(vm);
dumpStackFrame(vm);
DEBUG_BREAK();
#endif
2021-05-24 06:17:52 +08:00
SWITCH() {
2021-02-12 01:35:43 +08:00
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, module->constants.count);
PUSH(module->constants.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();
2021-05-24 06:17:52 +08:00
OPCODE(PUSH_0):
PUSH(VAR_NUM(0));
DISPATCH();
2021-02-12 01:35:43 +08:00
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 tmp = *(fiber->sp - 1);
*(fiber->sp - 1) = *(fiber->sp - 2);
*(fiber->sp - 2) = tmp;
2021-05-16 15:05:54 +08:00
DISPATCH();
}
2021-02-13 01:40:19 +08:00
OPCODE(PUSH_LIST):
{
List* list = newList(vm, (uint32_t)READ_SHORT());
PUSH(VAR_OBJ(list));
2021-02-13 01:40:19 +08:00
DISPATCH();
}
OPCODE(PUSH_MAP):
{
Map* map = newMap(vm);
PUSH(VAR_OBJ(map));
DISPATCH();
}
OPCODE(PUSH_SELF):
{
PUSH(*self);
DISPATCH();
}
2021-02-13 01:40:19 +08:00
OPCODE(LIST_APPEND):
{
Var elem = PEEK(-1); // Don't pop yet, we need the reference for gc.
Var list = PEEK(-2);
ASSERT(IS_OBJ_TYPE(list, OBJ_LIST), OOPS);
2021-06-09 18:42:26 +08:00
pkVarBufferWrite(&((List*)AS_OBJ(list))->elements, vm, elem);
DROP(); // elem
2021-02-13 01:40:19 +08:00
DISPATCH();
}
OPCODE(MAP_INSERT):
{
Var value = PEEK(-1); // Don't pop yet, we need the reference for gc.
2021-06-11 15:46:55 +08:00
Var key = PEEK(-2); // Don't pop yet, we need the reference for gc.
Var on = PEEK(-3);
ASSERT(IS_OBJ_TYPE(on, OBJ_MAP), OOPS);
if (IS_OBJ(key) && !isObjectHashable(AS_OBJ(key)->type)) {
RUNTIME_ERROR(stringFormat(vm, "$ type is not hashable.",
varTypeName(key)));
}
2021-05-24 06:17:52 +08:00
mapSet(vm, (Map*)AS_OBJ(on), key, value);
DROP(); // value
DROP(); // key
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-06-04 22:55:06 +08:00
uint8_t index = READ_BYTE();
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); // +1: rbp[0] is return value.
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(STORE_LOCAL_N):
{
2021-06-04 22:55:06 +08:00
uint8_t index = READ_BYTE();
rbp[index + 1] = PEEK(-1); // +1: rbp[0] is return value.
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(PUSH_GLOBAL):
{
2021-06-04 22:55:06 +08:00
uint8_t index = READ_BYTE();
ASSERT_INDEX(index, module->globals.count);
PUSH(module->globals.data[index]);
2021-02-12 01:35:43 +08:00
DISPATCH();
}
OPCODE(STORE_GLOBAL):
{
2021-06-04 22:55:06 +08:00
uint8_t index = READ_BYTE();
ASSERT_INDEX(index, module->globals.count);
module->globals.data[index] = PEEK(-1);
2021-02-12 01:35:43 +08:00
DISPATCH();
}
OPCODE(PUSH_BUILTIN_FN):
{
2021-06-04 22:55:06 +08:00
uint8_t index = READ_BYTE();
2021-05-24 06:17:52 +08:00
ASSERT_INDEX(index, vm->builtins_count);
2022-04-20 12:10:54 +08:00
Closure* closure = vm->builtins_funcs[index];
PUSH(VAR_OBJ(closure));
DISPATCH();
}
2022-04-13 17:01:57 +08:00
OPCODE(PUSH_UPVALUE):
{
uint8_t index = READ_BYTE();
PUSH(*(frame->closure->upvalues[index]->ptr));
DISPATCH();
}
OPCODE(STORE_UPVALUE):
{
uint8_t index = READ_BYTE();
*(frame->closure->upvalues[index]->ptr) = PEEK(-1);
DISPATCH();
}
OPCODE(PUSH_CLOSURE):
{
uint16_t index = READ_SHORT();
ASSERT_INDEX(index, module->constants.count);
ASSERT(IS_OBJ_TYPE(module->constants.data[index], OBJ_FUNC), OOPS);
Function* fn = (Function*)AS_OBJ(module->constants.data[index]);
2022-04-13 17:01:57 +08:00
Closure* closure = newClosure(vm, fn);
// Capture the vaupes.
for (int i = 0; i < fn->upvalue_count; i++) {
uint8_t is_immediate = READ_BYTE();
uint8_t index = READ_BYTE();
if (is_immediate) {
// rbp[0] is the return value, rbp + 1 is the first local and so on.
closure->upvalues[i] = captureUpvalue(vm, fiber, (rbp + 1 + index));
2022-04-13 17:01:57 +08:00
} else {
// The upvalue is already captured by the current function, reuse it.
closure->upvalues[i] = frame->closure->upvalues[index];
}
}
PUSH(VAR_OBJ(closure));
DISPATCH();
}
OPCODE(CLOSE_UPVALUE):
{
closeUpvalues(fiber, fiber->sp - 1);
2022-04-13 17:01:57 +08:00
DROP();
2021-02-12 01:35:43 +08:00
DISPATCH();
}
OPCODE(POP):
DROP();
DISPATCH();
OPCODE(IMPORT):
2021-05-06 22:19:30 +08:00
{
uint16_t index = READ_SHORT();
String* name = moduleGetStringAt(module, (int)index);
ASSERT(name != NULL, OOPS);
2021-05-29 02:53:46 +08:00
Var _imported = importModule(vm, name);
ASSERT(IS_OBJ_TYPE(_imported, OBJ_MODULE), OOPS);
PUSH(_imported);
// TODO: If the body doesn't have any statements (just the functions).
// This initialization call is un-necessary.
Module* imported = (Module*)AS_OBJ(_imported);
if (!imported->initialized) {
imported->initialized = true;
ASSERT(imported->body != NULL, OOPS);
// Note that we're setting the main function's return address to the
// module itself (for every other function we'll push a null at the rbp
// before calling them and it'll be returned without modified if the
// function doesn't returned anything). Also We can't return from the
// body of the module, so the main function will return what's at the
// rbp without modifying it. So at the end of the main function the
// stack top would be the module itself.
Var* module_ret = fiber->sp - 1;
UPDATE_FRAME(); //< Update the current frame's ip.
pushCallFrame(vm, imported->body, module_ret);
LOAD_FRAME(); //< Load the top frame to vm's execution variables.
}
2021-05-06 22:19:30 +08:00
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(CALL):
2021-06-13 04:17:44 +08:00
OPCODE(TAIL_CALL):
2021-02-12 01:35:43 +08:00
{
2021-06-04 22:55:06 +08:00
const uint8_t argc = READ_BYTE();
Var* callable = fiber->sp - argc - 1;
2021-02-12 01:35:43 +08:00
const Closure* closure = NULL;
// Raw functions cannot be on the stack, since they're not first class
// citizens.
ASSERT(!IS_OBJ_TYPE(*callable, OBJ_FUNC), OOPS);
2021-06-20 23:28:31 +08:00
if (IS_OBJ_TYPE(*callable, OBJ_CLOSURE)) {
closure = (const Closure*)AS_OBJ(*callable);
2021-02-12 01:35:43 +08:00
2021-06-20 23:28:31 +08:00
} else if (IS_OBJ_TYPE(*callable, OBJ_CLASS)) {
closure = (const Closure*)((Class*)AS_OBJ(*callable))->ctor;
2021-06-05 22:47:59 +08:00
2021-06-20 23:28:31 +08:00
} else {
RUNTIME_ERROR(stringFormat(vm, "$ $(@).", "Expected a callable to "
2021-06-20 23:28:31 +08:00
"call, instead got",
varTypeName(*callable), toString(vm, *callable)));
DISPATCH();
}
2021-06-05 22:47:59 +08:00
2021-06-20 23:28:31 +08:00
// If we reached here it's a valid callable.
2021-06-05 22:47:59 +08:00
2021-06-20 23:28:31 +08:00
// -1 argument means multiple number of args.
if (closure->fn->arity != -1 && closure->fn->arity != argc) {
char buff[STR_INT_BUFF_SIZE]; sprintf(buff, "%d", closure->fn->arity);
2021-06-20 23:28:31 +08:00
String* msg = stringFormat(vm, "Expected exactly $ argument(s).",
buff);
RUNTIME_ERROR(msg);
}
2021-06-20 18:23:21 +08:00
// Next call frame starts here. (including return value).
fiber->ret = callable;
*(fiber->ret) = VAR_NULL; //< Set the return value to null.
if (closure->fn->is_native) {
2021-06-05 22:47:59 +08:00
if (closure->fn->native == NULL) {
2021-06-20 23:28:31 +08:00
RUNTIME_ERROR(stringFormat(vm,
"Native function pointer of $ was NULL.", closure->fn->name));
2021-06-20 23:28:31 +08:00
}
2021-06-05 22:47:59 +08:00
2021-06-20 23:28:31 +08:00
// Update the current frame's ip.
UPDATE_FRAME();
2021-06-05 22:47:59 +08:00
closure->fn->native(vm); //< Call the native function.
2021-06-13 04:17:44 +08:00
2021-06-20 23:28:31 +08:00
// Calling yield() will change vm->fiber to it's caller fiber, which
// would be null if we're not running the function with a fiber.
if (vm->fiber == NULL) return PK_RESULT_SUCCESS;
2021-06-13 04:17:44 +08:00
2021-06-20 23:28:31 +08:00
// Pop function arguments except for the return value.
// Note that calling fiber_new() and yield() would change the
// vm->fiber so we're using fiber.
fiber->sp = fiber->ret + 1;
// If the fiber has changed, Load the top frame to vm's execution
// variables.
if (vm->fiber != fiber) {
fiber = vm->fiber;
LOAD_FRAME();
}
2021-06-20 23:28:31 +08:00
CHECK_ERROR();
2021-02-12 01:35:43 +08:00
} else {
2021-06-20 23:28:31 +08:00
if (instruction == OP_CALL) {
UPDATE_FRAME(); //< Update the current frame's ip.
pushCallFrame(vm, closure, callable);
2021-06-20 23:28:31 +08:00
LOAD_FRAME(); //< Load the top frame to vm's execution variables.
} else {
ASSERT(instruction == OP_TAIL_CALL, OOPS);
reuseCallFrame(vm, closure);
2021-06-20 23:28:31 +08:00
LOAD_FRAME(); //< Re-load the frame to vm's execution variables.
}
2021-02-12 01:35:43 +08:00
}
2021-05-24 06:17:52 +08:00
DISPATCH();
}
OPCODE(ITER_TEST):
{
Var seq = PEEK(-3);
// Primitive types are not iterable.
if (!IS_OBJ(seq)) {
if (IS_NULL(seq)) {
RUNTIME_ERROR(newString(vm, "Null is not iterable."));
} else if (IS_BOOL(seq)) {
RUNTIME_ERROR(newString(vm, "Boolenan is not iterable."));
} else if (IS_NUM(seq)) {
RUNTIME_ERROR(newString(vm, "Number is not iterable."));
} else {
UNREACHABLE();
}
}
2021-02-12 01:35:43 +08:00
DISPATCH();
}
// TODO: move this to a function in pk_core.c.
OPCODE(ITER):
2021-02-13 01:40:19 +08:00
{
Var* value = (fiber->sp - 1);
Var* iterator = (fiber->sp - 2);
2021-05-24 06:17:52 +08:00
Var seq = PEEK(-3);
2021-05-05 12:55:27 +08:00
uint16_t jump_offset = READ_SHORT();
2021-05-24 06:17:52 +08:00
#define JUMP_ITER_EXIT() \
do { \
2021-06-04 22:55:06 +08:00
ip += jump_offset; \
2021-05-24 06:17:52 +08:00
DISPATCH(); \
} while (false)
ASSERT(IS_NUM(*iterator), OOPS);
double it = AS_NUM(*iterator); //< Nth iteration.
ASSERT(AS_NUM(*iterator) == (int32_t)trunc(it), OOPS);
Object* obj = AS_OBJ(seq);
switch (obj->type) {
case OBJ_STRING: {
uint32_t iter = (int32_t)trunc(it);
// TODO: // Need to consider utf8.
String* str = ((String*)obj);
if (iter >= str->length) JUMP_ITER_EXIT();
//TODO: vm's char (and reusable) strings.
*value = VAR_OBJ(newStringLength(vm, str->data + iter, 1));
*iterator = VAR_NUM((double)iter + 1);
} DISPATCH();
case OBJ_LIST: {
uint32_t iter = (int32_t)trunc(it);
2021-06-09 18:42:26 +08:00
pkVarBuffer* elems = &((List*)obj)->elements;
2021-05-24 06:17:52 +08:00
if (iter >= elems->count) JUMP_ITER_EXIT();
*value = elems->data[iter];
*iterator = VAR_NUM((double)iter + 1);
2021-06-11 15:46:55 +08:00
2021-05-24 06:17:52 +08:00
} DISPATCH();
case OBJ_MAP: {
uint32_t iter = (int32_t)trunc(it);
Map* map = (Map*)obj;
if (map->entries == NULL) JUMP_ITER_EXIT();
MapEntry* e = map->entries + iter;
for (; iter < map->capacity; iter++, e++) {
2021-06-12 14:56:09 +08:00
if (!IS_UNDEF(e->key)) break;
2021-05-24 06:17:52 +08:00
}
if (iter >= map->capacity) JUMP_ITER_EXIT();
*value = map->entries[iter].key;
*iterator = VAR_NUM((double)iter + 1);
2021-06-11 15:46:55 +08:00
2021-05-24 06:17:52 +08:00
} DISPATCH();
case OBJ_RANGE: {
double from = ((Range*)obj)->from;
double to = ((Range*)obj)->to;
if (from == to) JUMP_ITER_EXIT();
double current;
if (from <= to) { //< Straight range.
current = from + it;
} else { //< Reversed range.
current = from - it;
}
if (current == to) JUMP_ITER_EXIT();
*value = VAR_NUM(current);
*iterator = VAR_NUM(it + 1);
2021-06-11 15:46:55 +08:00
2021-05-24 06:17:52 +08:00
} DISPATCH();
case OBJ_MODULE:
2021-05-24 06:17:52 +08:00
case OBJ_FUNC:
case OBJ_CLOSURE:
case OBJ_UPVALUE:
2021-05-24 06:17:52 +08:00
case OBJ_FIBER:
2021-06-20 23:28:31 +08:00
case OBJ_CLASS:
case OBJ_INST:
2021-05-24 06:17:52 +08:00
TODO; break;
default:
UNREACHABLE();
2021-02-13 01:40:19 +08:00
}
2021-05-24 06:17:52 +08:00
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();
2021-06-04 22:55:06 +08:00
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();
2021-06-04 22:55:06 +08:00
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)) {
2021-06-04 22:55:06 +08:00
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)) {
2021-06-04 22:55:06 +08:00
ip += offset;
2021-02-15 20:49:19 +08:00
}
DISPATCH();
}
OPCODE(OR):
{
Var cond = PEEK(-1);
uint16_t offset = READ_SHORT();
if (toBool(cond)) {
ip += offset;
} else {
DROP();
}
DISPATCH();
}
OPCODE(AND):
{
Var cond = PEEK(-1);
uint16_t offset = READ_SHORT();
if (!toBool(cond)) {
ip += offset;
} else {
DROP();
}
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(RETURN):
{
2022-04-13 17:01:57 +08:00
// Close all the locals of the current frame.
closeUpvalues(fiber, rbp + 1);
2022-04-13 17:01:57 +08:00
2021-05-24 06:17:52 +08:00
// Set the return value.
2021-06-05 22:47:59 +08:00
Var ret_value = POP();
2021-06-05 22:47:59 +08:00
// Pop the last frame, and if no more call frames, we're done with the
// current fiber.
if (--fiber->frame_count == 0) {
// TODO: if we're evaluating an expression we need to set it's
2021-06-08 00:56:56 +08:00
// value on the stack.
//fiber->sp = fiber->stack; ??
2021-06-08 00:56:56 +08:00
FIBER_SWITCH_BACK();
2021-06-05 22:47:59 +08:00
if (fiber == NULL) {
2021-06-05 22:47:59 +08:00
return PK_RESULT_SUCCESS;
} else {
*fiber->ret = ret_value;
2021-06-05 22:47:59 +08:00
}
} else {
*rbp = ret_value;
// Pop the params (locals should have popped at this point) and update
// stack pointer.
fiber->sp = rbp + 1; // +1: rbp is returned value.
2021-06-05 22:47:59 +08:00
}
LOAD_FRAME();
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(GET_ATTRIB):
2021-02-16 02:51:00 +08:00
{
2021-06-11 15:46:55 +08:00
Var on = PEEK(-1); // Don't pop yet, we need the reference for gc.
String* name = moduleGetStringAt(module, READ_SHORT());
ASSERT(name != NULL, OOPS);
Var value = varGetAttrib(vm, on, name);
DROP(); // on
PUSH(value);
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(-1);
String* name = moduleGetStringAt(module, READ_SHORT());
ASSERT(name != NULL, OOPS);
2021-02-16 02:51:00 +08:00
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):
{
2021-06-11 15:46:55 +08:00
Var value = PEEK(-1); // Don't pop yet, we need the reference for gc.
Var on = PEEK(-2); // Don't pop yet, we need the reference for gc.
String* name = moduleGetStringAt(module, READ_SHORT());
ASSERT(name != NULL, OOPS);
varSetAttrib(vm, on, name, value);
DROP(); // value
DROP(); // on
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 = PEEK(-1); // Don't pop yet, we need the reference for gc.
Var on = PEEK(-2); // Don't pop yet, we need the reference for gc.
Var value = varGetSubscript(vm, on, key);
DROP(); // key
DROP(); // on
PUSH(value);
2021-02-16 02:51:00 +08:00
CHECK_ERROR();
DISPATCH();
}
OPCODE(GET_SUBSCRIPT_KEEP):
2021-02-16 02:51:00 +08:00
{
Var key = PEEK(-1);
Var on = PEEK(-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 = PEEK(-1); // Don't pop yet, we need the reference for gc.
Var key = PEEK(-2); // Don't pop yet, we need the reference for gc.
Var on = PEEK(-3); // Don't pop yet, we need the reference for gc.
2021-02-16 02:51:00 +08:00
varsetSubscript(vm, on, key, value);
DROP(); // value
DROP(); // key
DROP(); // on
2021-02-16 02:51:00 +08:00
PUSH(value);
CHECK_ERROR();
2021-02-16 02:51:00 +08:00
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, "Can not 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):
{
// Don't pop yet, we need the reference for gc.
2021-06-17 03:54:07 +08:00
Var val = PEEK(-1);
Var result = varBitNot(vm, val);
DROP(); // val
PUSH(result);
CHECK_ERROR();
DISPATCH();
}
2021-02-12 01:35:43 +08:00
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
{
// Don't pop yet, we need the reference for gc.
Var r = PEEK(-1), l = PEEK(-2);
Var result = varAdd(vm, l, r);
DROP(); DROP(); // r, l
PUSH(result);
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
{
// Don't pop yet, we need the reference for gc.
Var r = PEEK(-1), l = PEEK(-2);
Var result = varSubtract(vm, l, r);
DROP(); DROP(); // r, l
PUSH(result);
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
{
// Don't pop yet, we need the reference for gc.
Var r = PEEK(-1), l = PEEK(-2);
Var result = varMultiply(vm, l, r);
DROP(); DROP(); // r, l
PUSH(result);
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
{
// Don't pop yet, we need the reference for gc.
Var r = PEEK(-1), l = PEEK(-2);
Var result = varDivide(vm, l, r);
DROP(); DROP(); // r, l
PUSH(result);
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
{
// Don't pop yet, we need the reference for gc.
Var r = PEEK(-1), l = PEEK(-2);
Var result = varModulo(vm, l, r);
DROP(); DROP(); // r, l
PUSH(result);
CHECK_ERROR();
DISPATCH();
2021-05-12 18:57:35 +08:00
}
2021-06-11 15:46:55 +08:00
OPCODE(BIT_AND) :
{
// Don't pop yet, we need the reference for gc.
Var r = PEEK(-1), l = PEEK(-2);
Var result = varBitAnd(vm, l, r);
2021-06-11 15:46:55 +08:00
DROP(); DROP(); // r, l
PUSH(result);
2021-06-11 15:46:55 +08:00
CHECK_ERROR();
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(BIT_OR):
{
// Don't pop yet, we need the reference for gc.
Var r = PEEK(-1), l = PEEK(-2);
Var result = varBitOr(vm, l, r);
DROP(); DROP(); // r, l
PUSH(result);
CHECK_ERROR();
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(BIT_XOR):
{
// Don't pop yet, we need the reference for gc.
Var r = PEEK(-1), l = PEEK(-2);
Var result = varBitXor(vm, l, r);
DROP(); DROP(); // r, l
PUSH(result);
CHECK_ERROR();
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(BIT_LSHIFT):
{
// Don't pop yet, we need the reference for gc.
Var r = PEEK(-1), l = PEEK(-2);
Var result = varBitLshift(vm, l, r);
DROP(); DROP(); // r, l
PUSH(result);
CHECK_ERROR();
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(BIT_RSHIFT):
{
// Don't pop yet, we need the reference for gc.
Var r = PEEK(-1), l = PEEK(-2);
Var result = varBitRshift(vm, l, r);
DROP(); DROP(); // r, l
PUSH(result);
CHECK_ERROR();
DISPATCH();
}
2021-05-24 06:17:52 +08:00
OPCODE(EQEQ):
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-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(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(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(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(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 = PEEK(-1); // Don't pop yet, we need the reference for gc.
Var from = PEEK(-2); // Don't pop yet, we need the reference for gc.
if (!IS_NUM(from) || !IS_NUM(to)) {
RUNTIME_ERROR(newString(vm, "Range arguments must be number."));
}
DROP(); // to
DROP(); // from
PUSH(VAR_OBJ(newRange(vm, AS_NUM(from), AS_NUM(to))));
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(IN):
{
// Don't pop yet, we need the reference for gc.
Var container = PEEK(-1), elem = PEEK(-2);
bool contains = varContains(vm, elem, container);
DROP(); DROP(); // container, elem
PUSH(VAR_BOOL(contains));
CHECK_ERROR();
DISPATCH();
}
2021-06-07 13:54:06 +08:00
OPCODE(REPL_PRINT):
{
if (vm->config.write_fn != NULL) {
Var tmp = PEEK(-1);
2021-06-08 00:56:56 +08:00
if (!IS_NULL(tmp)) {
vm->config.write_fn(vm, toRepr(vm, tmp)->data);
vm->config.write_fn(vm, "\n");
}
2021-06-07 13:54:06 +08:00
}
DISPATCH();
}
2021-02-12 01:35:43 +08:00
OPCODE(END):
2021-06-20 23:28:31 +08:00
UNREACHABLE();
2021-02-12 01:35:43 +08:00
break;
default:
UNREACHABLE();
}
UNREACHABLE();
return PK_RESULT_RUNTIME_ERROR;
2021-02-11 01:23:48 +08:00
}