From 03bac026eee1928d14b83a1bcbf6644607541079 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Thu, 21 Apr 2022 06:59:47 +0530 Subject: [PATCH] `METHOD_CALL` opcode implemented As of this commit there is only one method in the entier pocketlang thats List.append() have added (the reset is todo). method searching algorithm should be optimized in the future by sorting the methods according to their names and do a binary search --- src/pk_compiler.c | 26 ++++++++++++++++--- src/pk_core.c | 44 +++++++++++++++++++++++++++----- src/pk_debug.c | 18 +++++++++++++ src/pk_opcodes.h | 5 ++++ src/pk_value.c | 1 + src/pk_value.h | 7 +++++ src/pk_vm.c | 65 ++++++++++++++++++++++++++++++++--------------- 7 files changed, 136 insertions(+), 30 deletions(-) diff --git a/src/pk_compiler.c b/src/pk_compiler.c index 139af8a..8fd9994 100644 --- a/src/pk_compiler.c +++ b/src/pk_compiler.c @@ -1926,7 +1926,11 @@ static void exprMap(Compiler* compiler) { consume(compiler, TK_RBRACE, "Expected '}' after map elements."); } -static void exprCall(Compiler* compiler) { +// This function is reused between calls and method calls. if the [call_type] +// is OP_METHOD_CALL the [method] should refer a string in the module's +// constant pool, otherwise it's ignored. +static void _compileCall(Compiler* compiler, Opcode call_type, int method) { + ASSERT((call_type == OP_CALL) || (call_type == OP_METHOD_CALL), OOPS); // Compile parameters. int argc = 0; @@ -1940,14 +1944,24 @@ static void exprCall(Compiler* compiler) { consume(compiler, TK_RPARAN, "Expected ')' after parameter list."); } - emitOpcode(compiler, OP_CALL); + emitOpcode(compiler, call_type); + emitByte(compiler, argc); + if (call_type == OP_METHOD_CALL) { + ASSERT_INDEX(method, (int)compiler->module->constants.count); + emitShort(compiler, method); + } + // After the call the arguments will be popped and the callable // will be replaced with the return value. compilerChangeStack(compiler, -argc); } +static void exprCall(Compiler* compiler) { + _compileCall(compiler, OP_CALL, -1); +} + static void exprAttrib(Compiler* compiler) { consume(compiler, TK_NAME, "Expected an attribute name after '.'."); const char* name = compiler->parser.previous.start; @@ -1958,6 +1972,12 @@ static void exprAttrib(Compiler* compiler) { moduleAddString(compiler->module, compiler->parser.vm, name, length, &index); + // Check if it's a method call. + if (match(compiler, TK_LPARAN)) { + _compileCall(compiler, OP_METHOD_CALL, index); + return; + } + if (compiler->l_value && matchAssignment(compiler)) { TokenType assignment = compiler->parser.previous.type; skipNewLines(compiler); @@ -3168,7 +3188,7 @@ PkResult compile(PKVM* vm, Module* module, const char* source, } #if DUMP_BYTECODE - dumpFunctionCode(compiler->parser.vm, module->body); + dumpFunctionCode(compiler->parser.vm, module->body->fn); #endif // Return the compilation result. diff --git a/src/pk_core.c b/src/pk_core.c index e8bd365..456d750 100644 --- a/src/pk_core.c +++ b/src/pk_core.c @@ -115,9 +115,8 @@ void pkClassAddMethod(PKVM* vm, PkHandle* cls, } void* pkGetSelf(const PKVM* vm) { - Var self = vm->fiber->frames[vm->fiber->frame_count - 1].self; - ASSERT(IS_OBJ_TYPE(self, OBJ_INST), OOPS); - Instance* inst = (Instance*)AS_OBJ(self); + ASSERT(IS_OBJ_TYPE(vm->fiber->self, OBJ_INST), OOPS); + Instance* inst = (Instance*)AS_OBJ(vm->fiber->self); ASSERT(inst->native != NULL, OOPS); return inst->native; } @@ -1243,11 +1242,15 @@ static void initializeCoreModules(PKVM* vm) { #undef DEF /*****************************************************************************/ -/* BUILTIN CLASS METHODS */ +/* BUILTIN CLASS CONSTRUCTORS */ /*****************************************************************************/ -static forceinline void _setSelf(PKVM* vm, Var value) { - vm->fiber->frames[vm->fiber->frame_count - 1].self = value; +static inline void _setSelf(PKVM* vm, Var value) { + vm->fiber->self = value; +} + +static inline Var _getSelf(PKVM* vm) { + return vm->fiber->self; } static void _ctorNull(PKVM* vm) { @@ -1301,6 +1304,17 @@ static void _ctorFiber(PKVM* vm) { _setSelf(vm, VAR_OBJ(newFiber(vm, closure))); } +/*****************************************************************************/ +/* BUILTIN CLASS METHODS */ +/*****************************************************************************/ + +static void _listAppend(PKVM* vm) { + Var self = _getSelf(vm); + ASSERT(IS_OBJ_TYPE(self, OBJ_LIST), OOPS); + listAppend(vm, ((List*)AS_OBJ(self)), ARG(1)); + RET(self); +} + /*****************************************************************************/ /* BUILTIN CLASS INITIALIZATION */ /*****************************************************************************/ @@ -1333,7 +1347,23 @@ static void initializePrimitiveClasses(PKVM* vm) { ADD_CTOR(PK_MAP, "@ctorMap", _ctorMap, 0); ADD_CTOR(PK_FIBER, "@ctorFiber", _ctorFiber, 1); - // TODO: add methods. +#undef ADD_CTOR + +#define ADD_METHOD(type, name, ptr, arity_) \ + do { \ + Function* fn = newFunction(vm, name, (int)strlen(name), \ + NULL, true, NULL, NULL); \ + fn->native = ptr; \ + fn->arity = arity_; \ + vmPushTempRef(vm, &fn->_super); /* fn. */ \ + pkClosureBufferWrite(&vm->builtin_classes[type]->methods, \ + vm, newClosure(vm, fn)); \ + vmPopTempRef(vm); /* fn. */ \ + } while (false) + + ADD_METHOD(PK_LIST, "append", _listAppend, 1); + +#undef ADD_METHOD } diff --git a/src/pk_debug.c b/src/pk_debug.c index 1a0f593..2525731 100644 --- a/src/pk_debug.c +++ b/src/pk_debug.c @@ -282,6 +282,24 @@ void dumpFunctionCode(PKVM* vm, Function* func) { break; } + case OP_METHOD_CALL: + { + int argc = READ_BYTE(); + int index = READ_SHORT(); + String* name = moduleGetStringAt(func->owner, index); + ASSERT(name != NULL, OOPS); + + // Prints: %5d (argc) %d '%s'\n + PRINT_INT(argc); + PRINT(" (argc) "); + + _PRINT_INT(index, 0); + PRINT(" '"); + PRINT(name->data); + PRINT("'\n"); + break; + } + case OP_CALL: // Prints: %5d (argc)\n PRINT_INT(READ_BYTE()); diff --git a/src/pk_opcodes.h b/src/pk_opcodes.h index f329f96..bf6fd73 100644 --- a/src/pk_opcodes.h +++ b/src/pk_opcodes.h @@ -123,6 +123,11 @@ OPCODE(POP, 0, -1) // params: 2 byte name index. OPCODE(IMPORT, 2, 1) +// Call a method on the variable at the stack top. See opcode CALL for detail. +// params: 2 bytes method name index in the constant pool. +// 1 byte argc. +OPCODE(METHOD_CALL, 3, -0) //< Stack size will be calculated at compile time. + // Calls a function using stack's top N values as the arguments and once it // done the stack top should be stored otherwise it'll be disregarded. The // function should set the 0 th argment to return value. diff --git a/src/pk_value.c b/src/pk_value.c index ebfb174..dba3176 100644 --- a/src/pk_value.c +++ b/src/pk_value.c @@ -495,6 +495,7 @@ Fiber* newFiber(PKVM* vm, Closure* closure) { } fiber->open_upvalues = NULL; + fiber->self = VAR_UNDEFINED; // Initialize the return value to null (doesn't really have to do that here // but if we're trying to debut it may crash when dumping the return value). diff --git a/src/pk_value.h b/src/pk_value.h index 13a6609..20d128f 100644 --- a/src/pk_value.h +++ b/src/pk_value.h @@ -466,6 +466,13 @@ struct Fiber { // overflowed. Var* ret; + // The self pointer to of the current method. It'll be updated before + // calling a native method. (Because native methods doesn't have a call + // frame we're doing it this way). Also updated just before calling a + // script method, and will be captured by the next allocated callframe + // and reset to VAR_UNDEFINED. + Var self; + // Heap allocated array of call frames will grow as needed. CallFrame* frames; int frame_capacity; //< Capacity of the frames array. diff --git a/src/pk_vm.c b/src/pk_vm.c index 5c975f4..2b9847a 100644 --- a/src/pk_vm.c +++ b/src/pk_vm.c @@ -550,7 +550,10 @@ static inline void pushCallFrame(PKVM* vm, const Closure* closure, Var* rbp) { frame->rbp = rbp; frame->closure = closure; frame->ip = closure->fn->fn->opcodes.data; - frame->self = VAR_UNDEFINED; + + // Eat the self. + frame->self = vm->fiber->self; + vm->fiber->self = VAR_UNDEFINED; } static inline void reuseCallFrame(PKVM* vm, const Closure* closure) { @@ -564,6 +567,7 @@ static inline void reuseCallFrame(PKVM* vm, const Closure* closure) { CallFrame* frame = fb->frames + fb->frame_count - 1; frame->closure = closure; frame->ip = closure->fn->fn->opcodes.data; + frame->self = VAR_UNDEFINED; ASSERT(*frame->rbp == VAR_NULL, OOPS); @@ -791,12 +795,18 @@ L_vm_main_loop: // defined, the next line become a declaration (Opcode instruction;). NO_OP; +#define _DUMP_STACK() \ + do { \ + system("cls"); /* FIXME: */ \ + dumpGlobalValues(vm); \ + dumpStackFrame(vm); \ + DEBUG_BREAK(); \ + } while (false) + #if DUMP_STACK - system("cls"); // FIXME: - dumpGlobalValues(vm); - dumpStackFrame(vm); - DEBUG_BREAK(); + _DUMP_STACK(); #endif +#undef _DUMP_STACK SWITCH() { @@ -1045,32 +1055,49 @@ L_vm_main_loop: DISPATCH(); } + { + uint8_t argc; + Var callable; + const Closure* closure; + + OPCODE(METHOD_CALL): + argc = READ_BYTE(); + fiber->ret = (fiber->sp - argc - 1); + fiber->self = *fiber->ret; //< Self for the next call. + + uint16_t index = READ_SHORT(); + bool is_method; + String* name = moduleGetStringAt(module, (int)index); + callable = getMethod(vm, fiber->self, name, &is_method); + CHECK_ERROR(); + goto L_do_call; + OPCODE(CALL): OPCODE(TAIL_CALL): - { - const uint8_t argc = READ_BYTE(); - Var* callable = fiber->sp - argc - 1; - - const Closure* closure = NULL; + argc = READ_BYTE(); + fiber->ret = fiber->sp - argc - 1; + callable = *fiber->ret; +L_do_call: // Raw functions cannot be on the stack, since they're not first class // citizens. - ASSERT(!IS_OBJ_TYPE(*callable, OBJ_FUNC), OOPS); + ASSERT(!IS_OBJ_TYPE(callable, OBJ_FUNC), OOPS); - if (IS_OBJ_TYPE(*callable, OBJ_CLOSURE)) { - closure = (const Closure*)AS_OBJ(*callable); + if (IS_OBJ_TYPE(callable, OBJ_CLOSURE)) { + closure = (const Closure*)AS_OBJ(callable); - } else if (IS_OBJ_TYPE(*callable, OBJ_CLASS)) { - closure = (const Closure*)((Class*)AS_OBJ(*callable))->ctor; + } else if (IS_OBJ_TYPE(callable, OBJ_CLASS)) { + closure = (const Closure*)((Class*)AS_OBJ(callable))->ctor; } else { RUNTIME_ERROR(stringFormat(vm, "$ $(@).", "Expected a callable to " "call, instead got", - varTypeName(*callable), toString(vm, *callable))); + varTypeName(callable), toString(vm, callable))); DISPATCH(); } // If we reached here it's a valid callable. + ASSERT(closure != NULL, OOPS); // -1 argument means multiple number of args. if (closure->fn->arity != -1 && closure->fn->arity != argc) { @@ -1080,8 +1107,6 @@ L_vm_main_loop: RUNTIME_ERROR(msg); } - // 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) { @@ -1116,9 +1141,9 @@ L_vm_main_loop: } else { - if (instruction == OP_CALL) { + if (instruction == OP_CALL || instruction == OP_METHOD_CALL) { UPDATE_FRAME(); //< Update the current frame's ip. - pushCallFrame(vm, closure, callable); + pushCallFrame(vm, closure, fiber->ret); LOAD_FRAME(); //< Load the top frame to vm's execution variables. } else {