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
This commit is contained in:
Thakee Nathees 2022-04-21 06:59:47 +05:30
parent 167355be59
commit 03bac026ee
7 changed files with 136 additions and 30 deletions

View File

@ -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.

View File

@ -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
}

View File

@ -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());

View File

@ -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.

View File

@ -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).

View File

@ -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.

View File

@ -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 {