From 48ceca1834c27e3a8c67a73a3ef8844d7922623c Mon Sep 17 00:00:00 2001 From: CPunch Date: Sat, 12 Dec 2020 21:53:12 -0600 Subject: [PATCH] multiple return value support added to VM --- src/cbaselib.c | 27 ++++++++--------- src/cbaselib.h | 4 +-- src/cdebug.c | 4 +-- src/cobj.c | 10 +++---- src/cobj.h | 2 +- src/coperators.h | 2 +- src/cparse.c | 2 ++ src/cvm.c | 75 ++++++++++++++++++++++++++++++++---------------- src/cvm.h | 2 +- src/main.c | 12 ++++---- 10 files changed, 84 insertions(+), 56 deletions(-) diff --git a/src/cbaselib.c b/src/cbaselib.c index b5f41c1..e98a309 100644 --- a/src/cbaselib.c +++ b/src/cbaselib.c @@ -11,30 +11,30 @@ void cosmoB_loadLibrary(CState *state) { cosmoM_unfreezeGC(state); } -CValue cosmoB_print(CState *state, int nargs, CValue *args) { +int cosmoB_print(CState *state, int nargs, CValue *args) { for (int i = 0; i < nargs; i++) { CObjString *str = cosmoV_toString(state, args[i]); printf("%s", cosmoO_readCString(str)); } printf("\n"); - return cosmoV_newNil(); // print doesn't return any args + return 0; // print doesn't return any args } -CValue cosmoB_foreach(CState *state, int nargs, CValue *args) { +int cosmoB_foreach(CState *state, int nargs, CValue *args) { if (nargs != 2) { cosmoV_error(state, "foreach() expected 2 parameters, got %d!", nargs); - return cosmoV_newNil(); + return 0; } if (!IS_DICT(args[0])) { cosmoV_error(state, "foreach() expected first parameter to be , got %s!", cosmoV_typeStr(args[0])); - return cosmoV_newNil(); + return 0; } if (!IS_CALLABLE(args[1])) { cosmoV_error(state, "foreach() expected second parameter to be callable, got %s!", cosmoV_typeStr(args[1])); - return cosmoV_newNil(); + return 0; } // loop through dictonary table, calling args[1] on active entries @@ -48,15 +48,14 @@ CValue cosmoB_foreach(CState *state, int nargs, CValue *args) { cosmoV_pushValue(state, args[1]); cosmoV_pushValue(state, entry->key); cosmoV_pushValue(state, entry->val); - cosmoV_call(state, 2); - cosmoV_pop(state); // throw away the return value + cosmoV_call(state, 2, 0); } } - return cosmoV_newNil(); + return 0; } -CValue cosmoB_dsetProto(CState *state, int nargs, CValue *args) { +int cosmoB_dsetProto(CState *state, int nargs, CValue *args) { if (nargs == 2) { CObjObject *obj = cosmoV_readObject(args[0]); // object to set proto too CObjObject *proto = cosmoV_readObject(args[1]); @@ -66,15 +65,17 @@ CValue cosmoB_dsetProto(CState *state, int nargs, CValue *args) { cosmoV_error(state, "Expected 2 parameters, got %d!", nargs); } - return cosmoV_newNil(); // nothing + return 0; // nothing } -CValue cosmoB_dgetProto(CState *state, int nargs, CValue *args) { +int cosmoB_dgetProto(CState *state, int nargs, CValue *args) { if (nargs != 1) { cosmoV_error(state, "Expected 1 parameter, got %d!", nargs); } - return cosmoV_newObj(cosmoV_readObject(args[0])->proto); // just return the proto + cosmoV_pushValue(state, cosmoV_newObj(cosmoV_readObject(args[0])->proto)); // just return the proto + + return 1; // 1 result } void cosmoB_loadDebug(CState *state) { diff --git a/src/cbaselib.h b/src/cbaselib.h index 45baa51..9d9ef26 100644 --- a/src/cbaselib.h +++ b/src/cbaselib.h @@ -6,7 +6,7 @@ COSMO_API void cosmoB_loadLibrary(CState *state); COSMO_API void cosmoB_loadDebug(CState *state); -COSMO_API CValue cosmoB_print(CState *state, int nargs, CValue *args); -COSMO_API CValue cosmoB_foreach(CState *state, int nargs, CValue *args); +COSMO_API int cosmoB_print(CState *state, int nargs, CValue *args); +COSMO_API int cosmoB_foreach(CState *state, int nargs, CValue *args); #endif \ No newline at end of file diff --git a/src/cdebug.c b/src/cdebug.c index de59b3b..3dcfcaa 100644 --- a/src/cdebug.c +++ b/src/cdebug.c @@ -93,7 +93,7 @@ int disasmInstr(CChunk *chunk, int offset, int indent) { case OP_POP: return u8OperandInstruction("OP_POP", chunk, offset); case OP_CALL: - return u8OperandInstruction("OP_CALL", chunk, offset); + return u8u8OperandInstruction("OP_CALL", chunk, offset); case OP_CLOSURE: { int index = readu16Chunk(chunk, offset + 1); printf("%-16s [%05d] - ", "OP_CLOSURE", index); @@ -131,7 +131,7 @@ int disasmInstr(CChunk *chunk, int offset, int indent) { case OP_SETOBJECT: return simpleInstruction("OP_SETOBJECT", offset); case OP_INVOKE: - return u8OperandInstruction("OP_INVOKE", chunk, offset); + return u8u8OperandInstruction("OP_INVOKE", chunk, offset); case OP_ADD: return simpleInstruction("OP_ADD", offset); case OP_SUB: diff --git a/src/cobj.c b/src/cobj.c index 6263d8c..0dfbd84 100644 --- a/src/cobj.c +++ b/src/cobj.c @@ -229,7 +229,7 @@ bool cosmoO_getObject(CState *state, CObjObject *object, CValue key, CValue *val if (cosmoO_getIString(state, object, ISTRING_GETTER, val) && IS_OBJECT(*val) && cosmoO_getObject(state, cosmoV_readObject(*val), key, val)) { cosmoV_pushValue(state, *val); // push function cosmoV_pushValue(state, cosmoV_newObj(object)); // push object - cosmoV_call(state, 1); // call the function with the 1 argument + cosmoV_call(state, 1, 1); // call the function with the 1 argument *val = *cosmoV_pop(state); // set value to the return value of __index return true; } @@ -250,8 +250,7 @@ void cosmoO_setObject(CState *state, CObjObject *object, CValue key, CValue val) cosmoV_pushValue(state, ret); // push function cosmoV_pushValue(state, cosmoV_newObj(object)); // push object cosmoV_pushValue(state, val); // push new value - cosmoV_call(state, 2); - cosmoV_pop(state); // pop return value + cosmoV_call(state, 2, 0); return; } @@ -304,7 +303,7 @@ bool cosmoO_indexObject(CState *state, CObjObject *object, CValue key, CValue *v cosmoV_pushValue(state, *val); // push function cosmoV_pushValue(state, cosmoV_newObj(object)); // push object cosmoV_pushValue(state, key); // push key - cosmoV_call(state, 2); // call the function with the 2 arguments + cosmoV_call(state, 2, 1); // call the function with the 2 arguments *val = *cosmoV_pop(state); // set value to the return value of __index return true; } else { // there's no __index function defined! @@ -322,8 +321,7 @@ bool cosmoO_newIndexObject(CState *state, CObjObject *object, CValue key, CValue cosmoV_pushValue(state, cosmoV_newObj(object)); // push object cosmoV_pushValue(state, key); // push key & value pair cosmoV_pushValue(state, val); - cosmoV_call(state, 3); - cosmoV_pop(state); // pop return value + cosmoV_call(state, 3, 0); return true; } else { // there's no __newindex function defined cosmoV_error(state, "Couldn't set index on object without __newindex function!"); diff --git a/src/cobj.h b/src/cobj.h index 48f54ee..6e8017c 100644 --- a/src/cobj.h +++ b/src/cobj.h @@ -26,7 +26,7 @@ typedef enum { #define readFlag(x, flag) (x & (1u << flag)) #define setFlagOn(x, flag) (x |= (1u << flag)) -typedef CValue (*CosmoCFunction)(CState *state, int argCount, CValue *args); +typedef int (*CosmoCFunction)(CState *state, int argCount, CValue *args); typedef struct CObj { CObjType type; diff --git a/src/coperators.h b/src/coperators.h index 1206d42..e30419b 100644 --- a/src/coperators.h +++ b/src/coperators.h @@ -26,7 +26,7 @@ typedef enum { OP_JMP, // always jumps uint16_t OP_JMPBACK, // jumps -uint16_t OP_POP, // - pops[uint8_t] from stack - OP_CALL, // calls top[-uint8_t] + OP_CALL, // calls top[-uint8_t] expecting uint8_t results OP_CLOSURE, OP_CLOSE, OP_NEWDICT, diff --git a/src/cparse.c b/src/cparse.c index 61e1852..13676b4 100644 --- a/src/cparse.c +++ b/src/cparse.c @@ -549,6 +549,7 @@ static void call_(CParseState *pstate, bool canAssign) { valuePopped(pstate, argCount + 1); // all of these values will be popped off the stack when returned (+1 for the function) writeu8(pstate, OP_CALL); writeu8(pstate, argCount); + writeu8(pstate, 1); // TODO valuePushed(pstate, 1); } @@ -603,6 +604,7 @@ static void dot(CParseState *pstate, bool canAssign) { uint8_t args = parseArguments(pstate); writeu8(pstate, OP_INVOKE); writeu8(pstate, args); + writeu8(pstate, 1); // TODO valuePopped(pstate, args); // pops the function & the object but pushes a result } else { writeu8(pstate, OP_LOADCONST); diff --git a/src/cvm.c b/src/cvm.c index ed4d298..b770fd3 100644 --- a/src/cvm.c +++ b/src/cvm.c @@ -106,20 +106,33 @@ CObjString *cosmoV_concat(CState *state, CObjString *strA, CObjString *strB) { return cosmoO_takeString(state, buf, sz); } -bool cosmoV_execute(CState *state); +int cosmoV_execute(CState *state); -static inline void callCFunction(CState *state, CosmoCFunction cfunc, int args, int offset) { +static inline void callCFunction(CState *state, CosmoCFunction cfunc, int args, int nresults, int offset) { StkPtr savedBase = cosmoV_getTop(state, args); cosmoM_freezeGC(state); // we don't want a GC event during c api because we don't actually trust the user to know how to evade the GC - CValue res = cfunc(state, args, savedBase + 1); + int nres = cfunc(state, args, savedBase + 1); cosmoM_unfreezeGC(state); - - state->top = savedBase + offset; - cosmoV_pushValue(state, res); + + // remember where the return values are + CValue* results = cosmoV_getTop(state, 0); + + state->top = savedBase + offset; // set stack + + if (nres > nresults) // caller function wasn't expecting this many return values, cap it + nres = nresults; + + // push the return value back onto the stack + memcpy(state->top, results, sizeof(CValue) * nres); // copies the return values to the top of the stack + state->top += nres; // and make sure to move state->top to match + + // now, if the caller function expected more return values, push nils onto the stack + for (int i = nres; i < nresults; i++) + cosmoV_pushValue(state, cosmoV_newNil()); } -bool call(CState *state, CObjClosure *closure, int args, int offset) { +bool call(CState *state, CObjClosure *closure, int args, int nresults, int offset) { // missmatched args, thats an obvious user error, so error. if (args != closure->function->args) { cosmoV_error(state, "Expected %d parameters for %s, got %d!", closure->function->args, closure->function->name == NULL ? UNNAMEDCHUNK : closure->function->name->str, args); @@ -130,29 +143,39 @@ bool call(CState *state, CObjClosure *closure, int args, int offset) { pushCallFrame(state, closure, closure->function->args); // execute - if (!cosmoV_execute(state)) + int nres = cosmoV_execute(state); + if (nres == -1) // panic state return false; - // remember where the return value is - CValue* result = cosmoV_getTop(state, 0); + // remember where the return values are + CValue* results = cosmoV_getTop(state, 0); - // pop the callframe and return result :) + // pop the callframe and return results :) popCallFrame(state, offset); + if (nres > nresults) // caller function wasn't expecting this many return values, cap it + nres = nresults; + // push the return value back onto the stack - cosmoV_pushValue(state, *result); + memcpy(state->top, results, sizeof(CValue) * nres); // copies the return values to the top of the stack + state->top += nres; // and make sure to move state->top to match + + // now, if the caller function expected more return values, push nils onto the stack + for (int i = nres; i < nresults; i++) + cosmoV_pushValue(state, cosmoV_newNil()); + return true; } -bool invokeMethod(CState* state, CObjObject *obj, CValue func, int args) { +bool invokeMethod(CState* state, CObjObject *obj, CValue func, int args, int nresults) { // first, set the first argument to the object StkPtr temp = cosmoV_getTop(state, args); *temp = cosmoV_newObj(obj); if (IS_CFUNCTION(func)) { - callCFunction(state, cosmoV_readCFunction(func), args+1, 1); + callCFunction(state, cosmoV_readCFunction(func), args+1, nresults, 1); } else if (IS_CLOSURE(func)) { - call(state, cosmoV_readClosure(func), args+1, 1); // offset = 1 so our stack is properly reset + call(state, cosmoV_readClosure(func), args+1, nresults, 1); // offset = 1 so our stack is properly reset } else { cosmoV_error(state, "Cannot invoke non-function type %s!", cosmoV_typeStr(func)); } @@ -161,7 +184,7 @@ bool invokeMethod(CState* state, CObjObject *obj, CValue func, int args) { } // args = # of pass parameters -COSMOVMRESULT cosmoV_call(CState *state, int args) { +COSMOVMRESULT cosmoV_call(CState *state, int args, int nresults) { StkPtr val = cosmoV_getTop(state, args); // function will always be right above the args if (GET_TYPE(*val) != COSMO_TOBJ) { @@ -172,14 +195,14 @@ COSMOVMRESULT cosmoV_call(CState *state, int args) { switch (cosmoV_readObj(*val)->type) { case COBJ_CLOSURE: { CObjClosure *closure = (CObjClosure*)cosmoV_readObj(*val); - if (!call(state, closure, args, 0)) { + if (!call(state, closure, args, nresults, 0)) { return COSMOVM_RUNTIME_ERR; } break; } case COBJ_METHOD: { CObjMethod *method = (CObjMethod*)cosmoV_readObj(*val); - invokeMethod(state, method->obj, method->func, args); + invokeMethod(state, method->obj, method->func, args, nresults); break; } case COBJ_OBJECT: { // object is being instantiated, making another object @@ -190,7 +213,7 @@ COSMOVMRESULT cosmoV_call(CState *state, int args) { // check if they defined an initalizer if (cosmoO_getIString(state, protoObj, ISTRING_INIT, &ret)) { - invokeMethod(state, newObj, ret, args); + invokeMethod(state, newObj, ret, args, nresults); } else { // no default initalizer if (args != 0) { @@ -206,7 +229,7 @@ COSMOVMRESULT cosmoV_call(CState *state, int args) { case COBJ_CFUNCTION: { // it's a C function, so call it CosmoCFunction cfunc = ((CObjCFunction*)cosmoV_readObj(*val))->cfunc; - callCFunction(state, cfunc, args, 0); + callCFunction(state, cfunc, args, nresults, 0); break; } default: @@ -288,7 +311,7 @@ COSMO_API bool cosmoV_getObject(CState *state, CObjObject *object, CValue key, C } \ // returns false if panic -bool cosmoV_execute(CState *state) { +int cosmoV_execute(CState *state) { CCallFrame* frame = &state->callFrame[state->frameCount - 1]; // grabs the current frame CValue *constants = frame->closure->function->chunk.constants.values; // cache the pointer :) @@ -371,7 +394,8 @@ bool cosmoV_execute(CState *state) { } case OP_CALL: { uint8_t args = READBYTE(); - COSMOVMRESULT result = cosmoV_call(state, args); + uint8_t nres = READBYTE(); + COSMOVMRESULT result = cosmoV_call(state, args, nres); if (result != COSMOVM_OK) { return result; } @@ -509,6 +533,7 @@ bool cosmoV_execute(CState *state) { } case OP_INVOKE: { // this is an optimization made by the parser, instead of allocating a CObjMethod every time we want to invoke a method, we shrink it down into one minimal instruction! uint8_t args = READBYTE(); + uint8_t nres = READBYTE(); StkPtr key = cosmoV_getTop(state, args); // grabs key from stack StkPtr temp = cosmoV_getTop(state, args+1); // grabs object from stack @@ -524,7 +549,7 @@ bool cosmoV_execute(CState *state) { cosmoO_getObject(state, object, *key, &val); // we use cosmoO_getObject instead of the cosmoV_getObject wrapper so we get the raw value from the object instead of the CObjMethod wrapper // now invoke the method! - invokeMethod(state, object, val, args); + invokeMethod(state, object, val, args, nres); // moves return value & resets stack (key now points to the stack location of our return value) *temp = *key; @@ -745,7 +770,7 @@ bool cosmoV_execute(CState *state) { case OP_FALSE: cosmoV_pushBoolean(state, false); break; case OP_NIL: cosmoV_pushValue(state, cosmoV_newNil()); break; case OP_RETURN: { - return true; + return 1; } default: CERROR("unknown opcode!"); @@ -758,7 +783,7 @@ bool cosmoV_execute(CState *state) { #undef READUINT // we'll only reach this is state->panic is true - return false; + return -1; } #undef NUMBEROP \ No newline at end of file diff --git a/src/cvm.h b/src/cvm.h index b3bf9b7..c5e1101 100644 --- a/src/cvm.h +++ b/src/cvm.h @@ -13,7 +13,7 @@ typedef enum { } COSMOVMRESULT; // args = # of pass parameters, nresults = # of expected results -COSMO_API COSMOVMRESULT cosmoV_call(CState *state, int args); +COSMO_API COSMOVMRESULT cosmoV_call(CState *state, int args, int nresults); COSMO_API void cosmoV_makeObject(CState *state, int pairs); COSMO_API void cosmoV_makeDictionary(CState *state, int pairs); COSMO_API bool cosmoV_getObject(CState *state, CObjObject *object, CValue key, CValue *val); diff --git a/src/main.c b/src/main.c index aed1198..e6c9240 100644 --- a/src/main.c +++ b/src/main.c @@ -9,13 +9,13 @@ static bool _ACTIVE = false; -CValue cosmoB_quitRepl(CState *state, int nargs, CValue *args) { +int cosmoB_quitRepl(CState *state, int nargs, CValue *args) { _ACTIVE = false; - return cosmoV_newNil(); // we don't return anything + return 0; // we don't return anything } -CValue cosmoB_input(CState *state, int nargs, CValue *args) { +int cosmoB_input(CState *state, int nargs, CValue *args) { // input() accepts the same params as print()! for (int i = 0; i < nargs; i++) { CObjString *str = cosmoV_toString(state, args[i]); @@ -26,7 +26,9 @@ CValue cosmoB_input(CState *state, int nargs, CValue *args) { char line[1024]; fgets(line, sizeof(line), stdin); - return cosmoV_newObj(cosmoO_copyString(state, line, strlen(line)-1)); // -1 for the \n + cosmoV_pushValue(state, cosmoV_newObj(cosmoO_copyString(state, line, strlen(line)-1))); // -1 for the \n + + return 1; // 1 return value } static void interpret(CState *state, const char *script, const char *mod) { @@ -35,7 +37,7 @@ static void interpret(CState *state, const char *script, const char *mod) { if (func != NULL) { disasmChunk(&func->chunk, func->name != NULL ? func->name->str : "_main", 0); - COSMOVMRESULT res = cosmoV_call(state, 0); // 0 args being passed + COSMOVMRESULT res = cosmoV_call(state, 0, 0); // 0 args being passed, 0 results expected if (res == COSMOVM_RUNTIME_ERR) state->panic = false; // so our repl isn't broken