runFunction() abstraction to hide fiber creation

the function will create a fiber and execute the function, this is
a better abstracion for the host applicaion (createFiber, runFiber
are removed) and this will come in handy when it comes to execute
pocket functions inside a native function (it's required to
implement operator overloading).
This commit is contained in:
Thakee Nathees 2022-04-27 08:46:00 +05:30
parent 590e76b19f
commit 87fe3b01d6
8 changed files with 107 additions and 114 deletions

View File

@ -134,12 +134,9 @@ int repl(PKVM* vm, const PkCompileOptions* options) {
// Compiled source would be the "main" function of the module. Run it.
PkHandle* _main = pkModuleGetMainFunction(vm, module);
PkHandle* fiber = pkNewFiber(vm, _main);
ASSERT((_main != NULL) && (fiber != NULL), OOPS);
result = pkRunFiber(vm, fiber, 0, NULL);
ASSERT(_main != NULL, OOPS);
result = pkRunFunction(vm, _main, 0, -1, -1);
pkReleaseHandle(vm, _main);
pkReleaseHandle(vm, fiber);
} while (!done);

View File

@ -51,10 +51,8 @@ int runSource(const char* source) {
if (result != PK_RESULT_SUCCESS) break;
PkHandle* _main = pkModuleGetMainFunction(vm, module);
PkHandle* fiber = pkNewFiber(vm, _main);
result = pkRunFiber(vm, fiber, 0, NULL);
result = pkRunFunction(vm, _main, 0, -1, -1);
pkReleaseHandle(vm, _main);
pkReleaseHandle(vm, fiber);
} while (false);
pkReleaseHandle(vm, module);

View File

@ -293,20 +293,13 @@ PK_PUBLIC PkResult pkInterpretSource(PKVM* vm,
PkStringPtr path,
const PkCompileOptions* options);
// FIXME:
// Remove pkNewFiber and pkRunFiber functions and add interface to run closure
// with pkRunClosure or pkRunFunction.
// Create and return a new fiber around the function [fn].
PK_PUBLIC PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn);
// Runs the fiber's function with the provided arguments (param [arc] is the
// argument count and [argv] are the values). It'll returns it's run status
// result (success or failure) if you need the yielded or returned value use
// the pkFiberGetReturnValue() function, and use pkFiberIsDone() function to
// check if the fiber can be resumed with pkFiberResume() function.
PK_PUBLIC PkResult pkRunFiber(PKVM* vm, PkHandle* fiber,
int argc, PkHandle** argv);
// Run a function with [argc] arguments and their values are stored at the VM's
// slots starting at index [argv_slot] till the next [argc] consequent slots.
// If [argc] is 0 [argv_slot] value will be ignored.
// To store the return value set [ret_slot] to a valid slot index, if it's
// negative the return value will be ignored.
PK_PUBLIC PkResult pkRunFunction(PKVM* vm, PkHandle* fn,
int argc, int argv_slot, int ret_slot);
/*****************************************************************************/
/* NATIVE / RUNTIME FUNCTION API */

View File

@ -734,15 +734,12 @@ DEF(_fiberRun,
ASSERT(IS_OBJ_TYPE(SELF, OBJ_FIBER), OOPS);
Fiber* self = (Fiber*)AS_OBJ(SELF);
// Buffer of argument to call vmPrepareFiber().
Var* args[MAX_ARGC];
for (int i = 0; i < ARGC; i++) {
args[i] = &ARG(i + 1);
}
// Switch fiber and start execution.
if (vmPrepareFiber(vm, self, ARGC, args)) {
// Switch fiber and start execution. New fibers are marked as running in
// either it's stats running with vmRunFiber() or here -- inserting a
// fiber over a running (callee) fiber.
if (vmPrepareFiber(vm, self, ARGC, &ARG(1))) {
self->caller = vm->fiber;
vm->fiber = self;
self->state = FIBER_RUNNING;
}
}

View File

@ -22,6 +22,38 @@
"Given handle is not of type " #type "."); \
} while (false)
#define VALIDATE_SLOT_INDEX(index) \
do { \
ASSERT(index >= 0, "Slot index was negative."); \
ASSERT(index < pkGetSlotsCount(vm), \
"Slot index is too large. Did you forget to call pkReserveSlots()?."); \
} while (false)
// ARGC won't be the real arity if any slots allocated before calling argument
// validation calling this first is the callers responsibility.
#define VALIDATE_ARGC(arg) \
ASSERT(arg > 0 && arg <= ARGC, "Invalid argument index.")
#define CHECK_RUNTIME() \
do { \
ASSERT(vm->fiber != NULL, \
"This function can only be called at runtime."); \
} while (false)
// A convenient macro to get the nth (1 based) argument of the current
// function.
#define ARG(n) (vm->fiber->ret[n])
// Nth slot is same as Nth argument, It'll also work if we allocate more
// slots but the caller should ensure the index.
#define SLOT(n) ARG(n)
// This will work.
#define SET_SLOT(n, val) SLOT(n) = (val);
// Evaluates to the current function's argument count.
#define ARGC ((int)(vm->fiber->sp - vm->fiber->ret) - 1)
// The default allocator that will be used to initialize the PKVM's
// configuration if the host doesn't provided any allocators for us.
static void* defaultRealloc(void* memory, size_t new_size, void* _);
@ -257,77 +289,42 @@ PkResult pkInterpretSource(PKVM* vm, PkStringPtr source, PkStringPtr path,
// inclusion cause a crash.
module->initialized = true;
return vmRunFiber(vm, newFiber(vm, module->body));
Fiber* fiber = newFiber(vm, module->body);
vmPushTempRef(vm, &fiber->_super); // fiber.
vmPrepareFiber(vm, fiber, 0, NULL /* TODO: get argv from user. */);
vmPopTempRef(vm); // fiber.
return vmRunFiber(vm, fiber);
}
PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn) {
PK_PUBLIC PkResult pkRunFunction(PKVM* vm, PkHandle* fn,
int argc, int argv_slot, int ret_slot) {
CHECK_HANDLE_TYPE(fn, OBJ_CLOSURE);
Closure* closure = (Closure*)AS_OBJ(fn->value);
Fiber* fiber = newFiber(vm, (Closure*)AS_OBJ(fn->value));
vmPushTempRef(vm, &fiber->_super); // fiber
PkHandle* handle = vmNewHandle(vm, VAR_OBJ(fiber));
vmPopTempRef(vm); // fiber
return handle;
}
ASSERT(argc >= 0, "argc cannot be negative.");
Var* argv = NULL;
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 (argc != 0) {
for (int i = 0; i < argc; i++) {
VALIDATE_SLOT_INDEX(argv_slot + i);
}
argv = &SLOT(argv_slot);
}
if (!vmPrepareFiber(vm, _fiber, argc, args)) {
return PK_RESULT_RUNTIME_ERROR;
Var* ret = NULL;
if (ret_slot >= 0) {
VALIDATE_SLOT_INDEX(ret_slot);
ret = &SLOT(ret_slot);
}
ASSERT(_fiber->frame_count == 1, OOPS);
return vmRunFiber(vm, _fiber);
}
// TODO: Get resume argument.
PkResult pkResumeFiber(PKVM* vm, PkHandle* fiber) {
__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, NULL /* TODO: argument */)) {
return PK_RESULT_RUNTIME_ERROR;
}
return vmRunFiber(vm, _fiber);
return vmRunFunction(vm, closure, argc, argv, ret);
}
/*****************************************************************************/
/* RUNTIME */
/*****************************************************************************/
#define CHECK_RUNTIME() \
do { \
ASSERT(vm->fiber != NULL, \
"This function can only be called at runtime."); \
} while (false)
// A convenient macro to get the nth (1 based) argument of the current
// function.
#define ARG(n) (vm->fiber->ret[n])
// Nth slot is same as Nth argument, It'll also work if we allocate more
// slots but the caller should ensure the index.
#define SLOT(n) ARG(n)
// This will work.
#define SET_SLOT(n, val) SLOT(n) = (val);
// Evaluates to the current function's argument count.
#define ARGC ((int)(vm->fiber->sp - vm->fiber->ret) - 1)
void pkSetRuntimeError(PKVM* vm, const char* message) {
CHECK_RUNTIME();
VM_SET_ERROR(vm, newString(vm, message));
@ -365,18 +362,6 @@ bool pkCheckArgcRange(PKVM* vm, int argc, int min, int max) {
return true;
}
#define VALIDATE_SLOT_INDEX(index) \
do { \
ASSERT(index >= 0, "Slot index was negative."); \
ASSERT(index < pkGetSlotsCount(vm), \
"Slot index is too large. Did you forget to call pkReserveSlots()?."); \
} while (false)
// ARGC won't be the real arity if any slots allocated before calling argument
// validation calling this first is the callers responsibility.
#define VALIDATE_ARGC(arg) \
ASSERT(arg > 0 && arg <= ARGC, "Invalid argument index.")
// Set error for incompatible type provided as an argument. (TODO: got type).
#define ERR_INVALID_ARG_TYPE(ty_name) \
do { \

View File

@ -466,9 +466,9 @@ struct Fiber {
Upvalue* open_upvalues;
// The stack base pointer of the current frame. It'll be updated before
// calling a native function. (`fiber->ret` === `curr_call_frame->rbp`). And
// also updated if the stack is reallocated (that's when it's about to get
// overflowed.
// calling a native function. (`fiber->ret` === `curr_call_frame->rbp`).
// Return value of the callee fiber will be passed and return value of the
// the function that started the fiber will also be set.
Var* ret;
// The self pointer to of the current method. It'll be updated before

View File

@ -147,7 +147,7 @@ void vmCollectGarbage(PKVM* vm) {
return false; \
} while (false)
bool vmPrepareFiber(PKVM* vm, Fiber* fiber, int argc, Var** argv) {
bool vmPrepareFiber(PKVM* vm, Fiber* fiber, int argc, Var* argv) {
ASSERT(fiber->closure->fn->arity >= -1,
OOPS " (Forget to initialize arity.)");
@ -172,7 +172,7 @@ bool vmPrepareFiber(PKVM* vm, Fiber* fiber, int argc, Var** argv) {
}
ASSERT(fiber->stack != NULL && fiber->sp == fiber->stack + 1, OOPS);
ASSERT(fiber->ret + 1 == fiber->sp, OOPS);
ASSERT(fiber->ret == fiber->stack, OOPS);
// Pass the function arguments.
@ -185,14 +185,10 @@ bool vmPrepareFiber(PKVM* vm, Fiber* fiber, int argc, Var** argv) {
// 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->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;
}
@ -249,6 +245,26 @@ void vmYieldFiber(PKVM* vm, Var* value) {
vm->fiber = caller;
}
PkResult vmRunFunction(PKVM* vm, Closure* fn, int argc, Var* argv, Var* ret) {
ASSERT(argc >= 0, "argc cannot be negative.");
ASSERT(argc == 0 || argv != NULL, "argv was NULL when argc > 0.");
Fiber* fiber = newFiber(vm, fn);
vmPushTempRef(vm, &fiber->_super); // fiber.
vmPrepareFiber(vm, fiber, argc, argv);
vmPopTempRef(vm); // fiber.
Fiber* last = vm->fiber;
if (last != NULL) vmPushTempRef(vm, &last->_super); // last.
PkResult result = vmRunFiber(vm, fiber);
if (last != NULL) vmPopTempRef(vm); // last.
vm->fiber = last;
if (ret != NULL) *ret = *fiber->ret;
return result;
}
/*****************************************************************************/
/* VM INTERNALS */
/*****************************************************************************/
@ -489,8 +505,10 @@ static void reportError(PKVM* vm) {
PkResult vmRunFiber(PKVM* vm, Fiber* fiber_) {
// Set the fiber as the vm's current fiber (another root object) to prevent
// 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.
// If this is being called when running another fiber, that'll be garbage
// collected, if protected with vmPushTempRef() by the caller otherwise.
vm->fiber = fiber_;
ASSERT(fiber_->state == FIBER_NEW || fiber_->state == FIBER_YIELDED, OOPS);
@ -1203,12 +1221,12 @@ L_do_call:
// value on the stack.
//fiber->sp = fiber->stack; ??
FIBER_SWITCH_BACK();
if (fiber == NULL) {
if (fiber->caller == NULL) {
*fiber->ret = ret_value;
return PK_RESULT_SUCCESS;
} else {
FIBER_SWITCH_BACK();
*fiber->ret = ret_value;
}

View File

@ -204,7 +204,7 @@ Module* vmGetModule(PKVM* vm, String* key);
// Prepare a new fiber for execution with the given arguments. That can be used
// different fiber_run apis. Return true on success, otherwise it'll set the
// error to the vm's current fiber (if it has any).
bool vmPrepareFiber(PKVM* vm, Fiber* fiber, int argc, Var** argv);
bool vmPrepareFiber(PKVM* vm, Fiber* fiber, int argc, Var* argv);
// ((Context switching - resume))
// Switch the running fiber of the vm from the current fiber to the provided
@ -221,4 +221,9 @@ void vmYieldFiber(PKVM* vm, Var* value);
// till the next yield or return statement, and return result.
PkResult vmRunFiber(PKVM* vm, Fiber* fiber);
// Runs the script function (if not an assertion will fail) and if the [ret] is
// not null the return value will be set. [argv] should be the first argument
// pointer following the rest of the arguments in an array.
PkResult vmRunFunction(PKVM* vm, Closure* fn, int argc, Var* argv, Var* ret);
#endif // PK_VM_H