Minor refactoring, added OP_GETMETHOD

You can use ':' without invoking. So you can grab a method binding to an object and later call it. Also some GC bugs were fixed.

A new preprocessor macro (VM_DEBUG) has been added. Defining this macro will print each instruction thats being executed in the VM, as
well as print each call.
This commit is contained in:
CPunch 2021-01-16 15:40:58 -06:00
parent e995bb75fb
commit 2864c7be53
9 changed files with 148 additions and 101 deletions

View File

@ -4,6 +4,8 @@
#include "cobj.h"
#include "cmem.h"
// ================================================================ [BASELIB] ================================================================
int cosmoB_print(CState *state, int nargs, CValue *args) {
for (int i = 0; i < nargs; i++) {
CObjString *str = cosmoV_toString(state, args[i]);

View File

@ -22,6 +22,12 @@ int u16OperandInstruction(const char *name, CChunk *chunk, int offset) {
return offset + 1 + (sizeof(uint16_t) / sizeof(INSTRUCTION));
}
int JumpInstruction(const char *name, CChunk *chunk, int offset, int dir) {
int jmp = ((int)readu16Chunk(chunk, offset + 1)) * dir;
printf("%-16s [%05d] - jumps to %04d", name, jmp, offset + 3 + jmp);
return offset + 1 + (sizeof(uint16_t) / sizeof(INSTRUCTION));
}
int u8u8OperandInstruction(const char *name, CChunk *chunk, int offset) {
printf("%-16s [%03d] [%03d]", name, readu8Chunk(chunk, offset + 1), readu8Chunk(chunk, offset + 2));
return offset + 3; // op + u8 + u8
@ -51,7 +57,7 @@ int constInstruction(const char *name, CChunk *chunk, int offset) {
void disasmChunk(CChunk *chunk, const char *name, int indent) {
printIndent(indent);
printf("===[[ %s ]]===\n", name);
printf("===[[ disasm for %s ]]===\n", name);
for (size_t offset = 0; offset < chunk->count;) {
offset = disasmInstr(chunk, offset, indent);
@ -88,13 +94,13 @@ int disasmInstr(CChunk *chunk, int offset, int indent) {
case OP_GETUPVAL:
return u8OperandInstruction("OP_GETUPVAL", chunk, offset);
case OP_PEJMP:
return u16OperandInstruction("OP_PEJMP", chunk, offset);
return JumpInstruction("OP_PEJMP", chunk, offset, 1);
case OP_EJMP:
return u16OperandInstruction("OP_EJMP", chunk, offset);
return JumpInstruction("OP_EJMP", chunk, offset, 1);
case OP_JMP:
return u16OperandInstruction("OP_JMP", chunk, offset);
return JumpInstruction("OP_JMP", chunk, offset, 1);
case OP_JMPBACK:
return u16OperandInstruction("OP_JMPBACK", chunk, offset);
return JumpInstruction("OP_JMPBACK", chunk, offset, -1);
case OP_POP:
return u8OperandInstruction("OP_POP", chunk, offset);
case OP_CALL:
@ -133,10 +139,12 @@ int disasmInstr(CChunk *chunk, int offset, int indent) {
return simpleInstruction("OP_NEWINDEX", offset);
case OP_NEWOBJECT:
return u16OperandInstruction("OP_NEWOBJECT", chunk, offset);
case OP_GETOBJECT:
return constInstruction("OP_GETOBJECT", chunk, offset);
case OP_SETOBJECT:
return constInstruction("OP_SETOBJECT", chunk, offset);
case OP_GETOBJECT:
return constInstruction("OP_GETOBJECT", chunk, offset);
case OP_GETMETHOD:
return constInstruction("OP_GETMETHOD", chunk, offset);
case OP_INVOKE:
return u8u8u16OperandInstruction("OP_INVOKE", chunk, offset);
case OP_ITER:

View File

@ -6,4 +6,6 @@
COSMO_API void disasmChunk(CChunk *chunk, const char *name, int indent);
COSMO_API int disasmInstr(CChunk *chunk, int offset, int indent);
void printIndent(int indent);
#endif

View File

@ -5,7 +5,7 @@
#include "cstate.h"
//#define GC_STRESS
#define GC_STRESS
//#define GC_DEBUG
// arrays will grow by a factor of 2
#define GROW_FACTOR 2

View File

@ -111,7 +111,7 @@ typedef struct CObjUpval {
#define IS_STRING(x) isObjType(x, COBJ_STRING)
#define IS_OBJECT(x) isObjType(x, COBJ_OBJECT)
#define IS_TABLE(x) isObjType(x, COBJ_TABLE)
#define IS_TABLE(x) isObjType(x, COBJ_TABLE)
#define IS_FUNCTION(x) isObjType(x, COBJ_FUNCTION)
#define IS_CFUNCTION(x) isObjType(x, COBJ_CFUNCTION)
#define IS_METHOD(x) isObjType(x, COBJ_METHOD)
@ -125,6 +125,8 @@ typedef struct CObjUpval {
#define cosmoV_readMethod(x) ((CObjMethod*)cosmoV_readObj(x))
#define cosmoV_readClosure(x) ((CObjClosure*)cosmoV_readObj(x))
#define cosmoO_readCString(x) ((CObjString*)x)->str
static inline bool isObjType(CValue val, CObjType type) {
return IS_OBJ(val) && cosmoV_readObj(val)->type == type;
}
@ -181,7 +183,7 @@ CObjString *cosmoO_allocateString(CState *state, const char *str, size_t sz, uin
/*
formats strings to push onto the VM stack, formatting supported:
'%d' - decimal numbers [int]
'%d' - integers [int]
'%f' - floating point [double]
'%s' - strings [const char*]
'%t' - cosmo tokens [CToken *]
@ -191,8 +193,6 @@ CObjString *cosmoO_pushVFString(CState *state, const char *format, va_list args)
COSMO_API void printObject(CObj *o);
const char *cosmoO_typeStr(CObj* obj);
#define cosmoO_readCString(x) ((CObjString*)x)->str
CObjString *cosmoO_toString(CState *state, CObj *obj);
cosmo_Number cosmoO_toNumber(CState *state, CObj *obj);

View File

@ -29,6 +29,7 @@ typedef enum {
OP_NEWOBJECT,
OP_SETOBJECT,
OP_GETOBJECT,
OP_GETMETHOD,
OP_INVOKE,
OP_ITER,
OP_NEXT,

View File

@ -31,7 +31,7 @@ typedef struct {
typedef enum {
FTYPE_FUNCTION,
FTYPE_METHOD, // a function bounded to an object (can use "this" identifer to access the current object :pog:)
FTYPE_METHOD,
FTYPE_SCRIPT
} FunctionType;
@ -52,7 +52,7 @@ typedef struct CCompilerState {
typedef struct {
CLexState *lex;
CCompilerState *compiler;
CObjString *module; // name of the module (can be NULL)
CObjString *module; // name of the module
CState *state;
CToken current;
CToken previous; // token right after the current token
@ -747,28 +747,30 @@ static void _index(CParseState *pstate, bool canAssign, Precedence prec) {
}
static void invoke(CParseState *pstate, bool canAssign, Precedence prec) {
int returnNum = pstate->compiler->expectedValues;
uint16_t name;
uint8_t args;
consume(pstate, TOKEN_IDENTIFIER, "Expected method name after ':'.");
name = identifierConstant(pstate, &pstate->previous);
consume(pstate, TOKEN_LEFT_PAREN, "Expected '(' after method identifier!");
args = parseArguments(pstate);
if (match(pstate, TOKEN_LEFT_PAREN)) { // invoke
int returnNum = pstate->compiler->expectedValues;
uint8_t args = parseArguments(pstate);
// if we're not the last token in this expression or we're expecting multiple values, we should return only 1 value!!
if (!isLast(pstate, prec) || (returnNum > 1 && check(pstate, TOKEN_COMMA)))
returnNum = 1;
writeu8(pstate, OP_INVOKE);
writeu8(pstate, args);
writeu8(pstate, returnNum);
writeu16(pstate, name);
valuePopped(pstate, args+1); // args + function
valuePushed(pstate, returnNum);
// if we're not the last token in this expression or we're expecting multiple values, we should return only 1 value!!
if (!isLast(pstate, prec) || (returnNum > 1 && check(pstate, TOKEN_COMMA)))
returnNum = 1;
writeu8(pstate, OP_INVOKE);
writeu8(pstate, args);
writeu8(pstate, returnNum);
writeu16(pstate, name);
valuePopped(pstate, args+1); // args + function
valuePushed(pstate, returnNum);
} else { // just get the method
writeu8(pstate, OP_GETMETHOD);
writeu16(pstate, name);
}
}
// ++test.field[1]
@ -1570,10 +1572,9 @@ static void declaration(CParseState *pstate) {
}
static CObjFunction *endCompiler(CParseState *pstate) {
popLocals(pstate, pstate->compiler->scopeDepth); // remove the locals from other scopes
writeu8(pstate, OP_NIL);
popLocals(pstate, pstate->compiler->scopeDepth + 1); // remove the locals from other scopes
writeu8(pstate, OP_RETURN);
writeu8(pstate, 1);
writeu8(pstate, 0);
// update pstate to next compiler state
CCompilerState *cachedCCState = pstate->compiler;

169
src/cvm.c
View File

@ -33,8 +33,9 @@ COSMO_API bool cosmoV_compileString(CState *state, const char *src, const char *
if ((func = cosmoP_compileString(state, src, name)) != NULL) {
// success
#ifdef VM_DEBUG
disasmChunk(&func->chunk, func->module->str, 0);
#endif
// push function onto the stack so it doesn't it cleaned up by the GC, at the same stack location put our closure
cosmoV_pushValue(state, cosmoV_newObj(func));
*(cosmoV_getTop(state, 0)) = cosmoV_newObj(cosmoO_newClosure(state, func));
@ -296,6 +297,13 @@ static bool rawCall(CState *state, CObjClosure *closure, int args, int nresults,
}
bool callCValue(CState *state, CValue func, int args, int nresults, int offset) {
#ifdef VM_DEBUG
printf("\n");
printIndent(state->frameCount - 1);
printValue(func);
printf("()\n");
#endif
if (GET_TYPE(func) != COSMO_TOBJ) {
cosmoV_error(state, "Cannot call non-function type %s!", cosmoV_typeStr(func));
return false;
@ -472,7 +480,10 @@ COSMO_API bool cosmoV_getMethod(CState *state, CObj *obj, CValue key, CValue *va
// if the result is callable, wrap it in an method
if (IS_CALLABLE(*val)) {
// push object to stack so the GC can find it
cosmoV_pushValue(state, cosmoV_newObj(obj));
CObjMethod *method = cosmoO_newMethod(state, *val, obj);
cosmoV_pop(state); // pop the object
*val = cosmoV_newObj(method);
}
@ -539,20 +550,22 @@ int cosmoV_execute(CState *state) {
#define READUINT() (frame->pc += 2, *(uint16_t*)(&frame->pc[-2]))
while (!state->panic) {
/*disasmInstr(&frame->closure->function->chunk, frame->pc - frame->closure->function->chunk.buf, 0);
printf("\n");*/
#ifdef VM_DEBUG
disasmInstr(&frame->closure->function->chunk, frame->pc - frame->closure->function->chunk.buf, state->frameCount - 1);
printf("\n");
#endif
switch (READBYTE()) {
case OP_LOADCONST: { // push const[uint] to stack
uint16_t indx = READUINT();
cosmoV_pushValue(state, constants[indx]);
break;
continue;
}
case OP_SETGLOBAL: {
uint16_t indx = READUINT();
CValue ident = constants[indx]; // grabs identifier
CValue *val = cosmoT_insert(state, &state->globals, ident);
*val = *cosmoV_pop(state); // sets the value in the hash table
break;
continue;
}
case OP_GETGLOBAL: {
uint16_t indx = READUINT();
@ -560,27 +573,27 @@ int cosmoV_execute(CState *state) {
CValue val; // to hold our value
cosmoT_get(&state->globals, ident, &val);
cosmoV_pushValue(state, val); // pushes the value to the stack
break;
continue;
}
case OP_SETLOCAL: {
uint8_t indx = READBYTE();
frame->base[indx] = *cosmoV_pop(state);
break;
continue;
}
case OP_GETLOCAL: {
uint8_t indx = READBYTE();
cosmoV_pushValue(state, frame->base[indx]);
break;
continue;
}
case OP_GETUPVAL: {
uint8_t indx = READBYTE();
cosmoV_pushValue(state, *frame->closure->upvalues[indx]->val);
break;
continue;
}
case OP_SETUPVAL: {
uint8_t indx = READBYTE();
*frame->closure->upvalues[indx]->val = *cosmoV_pop(state);
break;
continue;
}
case OP_PEJMP: { // pop equality jump
uint16_t offset = READUINT();
@ -588,7 +601,7 @@ int cosmoV_execute(CState *state) {
if (isFalsey(cosmoV_pop(state))) { // pop, if the condition is false, jump!
frame->pc += offset;
}
break;
continue;
}
case OP_EJMP: { // equality jump
uint16_t offset = READUINT();
@ -596,21 +609,21 @@ int cosmoV_execute(CState *state) {
if (isFalsey(cosmoV_getTop(state, 0))) { // if the condition is false, jump!
frame->pc += offset;
}
break;
continue;
}
case OP_JMP: { // jump
uint16_t offset = READUINT();
frame->pc += offset;
break;
continue;
}
case OP_JMPBACK: {
uint16_t offset = READUINT();
frame->pc -= offset;
break;
continue;
}
case OP_POP: { // pops value off the stack
cosmoV_setTop(state, READBYTE());
break;
continue;
}
case OP_CALL: {
uint8_t args = READBYTE();
@ -618,7 +631,7 @@ int cosmoV_execute(CState *state) {
if (cosmoV_call(state, args, nres) != COSMOVM_OK) {
return -1;
}
break;
continue;
}
case OP_CLOSURE: {
uint16_t index = READUINT();
@ -638,17 +651,17 @@ int cosmoV_execute(CState *state) {
}
}
break;
continue;
}
case OP_CLOSE: {
closeUpvalues(state, state->top - 1);
cosmoV_pop(state);
break;
continue;
}
case OP_NEWTABLE: {
uint16_t pairs = READUINT();
cosmoV_makeTable(state, pairs);
break;
continue;
}
case OP_NEWARRAY: {
uint16_t pairs = READUINT();
@ -667,7 +680,7 @@ int cosmoV_execute(CState *state) {
// once done, pop everything off the stack + push new table
cosmoV_setTop(state, pairs + 1); // + 1 for our table
cosmoV_pushValue(state, cosmoV_newObj(newObj));
break;
continue;
}
case OP_INDEX: {
StkPtr key = cosmoV_getTop(state, 0); // key should be the top of the stack
@ -705,7 +718,7 @@ int cosmoV_execute(CState *state) {
cosmoV_error(state, "Couldn't index type %s!", cosmoV_typeStr(*temp));
}
break;
continue;
}
case OP_NEWINDEX: {
StkPtr value = cosmoV_getTop(state, 0); // value is at the top of the stack
@ -744,31 +757,12 @@ int cosmoV_execute(CState *state) {
return -1;
}
break;
continue;
}
case OP_NEWOBJECT: {
uint16_t pairs = READUINT();
cosmoV_makeObject(state, pairs);
break;
}
case OP_GETOBJECT: {
CValue val; // to hold our value
StkPtr temp = cosmoV_getTop(state, 0); // that should be the object
uint16_t ident = READUINT(); // use for the key
// sanity check
if (IS_OBJ(*temp)) {
if (!cosmoV_get(state, cosmoV_readObj(*temp), constants[ident], &val))
return -1;
} else {
CObjString *field = cosmoV_toString(state, constants[ident]);
cosmoV_error(state, "Couldn't get field '%s' from type %s!", field->str, cosmoV_typeStr(*temp));
return -1;
}
cosmoV_setTop(state, 1); // pops the object
cosmoV_pushValue(state, val); // pushes the field result
break;
continue;
}
case OP_SETOBJECT: {
StkPtr value = cosmoV_getTop(state, 0); // value is at the top of the stack
@ -787,7 +781,45 @@ int cosmoV_execute(CState *state) {
// pop everything off the stack
cosmoV_setTop(state, 2);
break;
continue;
}
case OP_GETOBJECT: {
CValue val; // to hold our value
StkPtr temp = cosmoV_getTop(state, 0); // that should be the object
uint16_t ident = READUINT(); // use for the key
// sanity check
if (IS_OBJ(*temp)) {
if (!cosmoV_get(state, cosmoV_readObj(*temp), constants[ident], &val))
return -1;
} else {
CObjString *field = cosmoV_toString(state, constants[ident]);
cosmoV_error(state, "Couldn't get field '%s' from type %s!", field->str, cosmoV_typeStr(*temp));
return -1;
}
cosmoV_setTop(state, 1); // pops the object
cosmoV_pushValue(state, val); // pushes the field result
continue;
}
case OP_GETMETHOD: {
CValue val; // to hold our value
StkPtr temp = cosmoV_getTop(state, 0); // that should be the object
uint16_t ident = READUINT(); // use for the key
// this is almost identical to GETOBJECT, however cosmoV_getMethod is used instead of just cosmoV_get
if (IS_OBJ(*temp)) {
if (!cosmoV_getMethod(state, cosmoV_readObj(*temp), constants[ident], &val))
return -1;
} else {
CObjString *field = cosmoV_toString(state, constants[ident]);
cosmoV_error(state, "Couldn't get field '%s' from type %s!", field->str, cosmoV_typeStr(*temp));
return -1;
}
cosmoV_setTop(state, 1); // pops the object
cosmoV_pushValue(state, val); // pushes the field result
continue;
}
case OP_INVOKE: {
uint8_t args = READBYTE();
@ -809,7 +841,7 @@ int cosmoV_execute(CState *state) {
return -1;
}
break;
continue;
}
case OP_ITER: {
StkPtr temp = cosmoV_getTop(state, 0); // should be the object/table
@ -877,7 +909,7 @@ int cosmoV_execute(CState *state) {
break;
}
}
break;
continue;
}
case OP_NEXT: {
uint8_t nresults = READBYTE();
@ -897,23 +929,23 @@ int cosmoV_execute(CState *state) {
cosmoV_setTop(state, nresults); // pop the return values
frame->pc += jump;
}
break;
continue;
}
case OP_ADD: { // pop 2 values off the stack & try to add them together
NUMBEROP(cosmoV_newNumber, +);
break;
continue;
}
case OP_SUB: { // pop 2 values off the stack & try to subtracts them
NUMBEROP(cosmoV_newNumber, -)
break;
continue;
}
case OP_MULT: { // pop 2 values off the stack & try to multiplies them together
NUMBEROP(cosmoV_newNumber, *)
break;
continue;
}
case OP_DIV: { // pop 2 values off the stack & try to divides them
NUMBEROP(cosmoV_newNumber, /)
break;
continue;
}
case OP_MOD: {
StkPtr valA = cosmoV_getTop(state, 1);
@ -925,11 +957,11 @@ int cosmoV_execute(CState *state) {
cosmoV_error(state, "Expected numbers, got %s and %s!", cosmoV_typeStr(*valA), cosmoV_typeStr(*valB));
return -1; \
} \
break;
continue;
}
case OP_NOT: {
cosmoV_pushBoolean(state, isFalsey(cosmoV_pop(state)));
break;
continue;
}
case OP_NEGATE: { // pop 1 value off the stack & try to negate
StkPtr val = cosmoV_getTop(state, 0);
@ -941,7 +973,7 @@ int cosmoV_execute(CState *state) {
cosmoV_error(state, "Expected number, got %s!", cosmoV_typeStr(*val));
return -1;
}
break;
continue;
}
case OP_COUNT: { // pop 1 value off the stack & if it's a table return the amount of active entries it has
StkPtr temp = cosmoV_getTop(state, 0);
@ -954,12 +986,12 @@ int cosmoV_execute(CState *state) {
CObjTable *tbl = (CObjTable*)cosmoV_readObj(*temp);
cosmoV_pop(state);
cosmoV_pushNumber(state, cosmoT_count(&tbl->tbl)); // pushes the count onto the stack
break;
continue;
}
case OP_CONCAT: {
uint8_t vals = READBYTE();
cosmoV_concat(state, vals);
break;
continue;
}
case OP_INCLOCAL: { // this leaves the value on the stack
int8_t inc = READBYTE() - 128; // amount we're incrementing by
@ -975,7 +1007,7 @@ int cosmoV_execute(CState *state) {
return -1;
}
break;
continue;
}
case OP_INCGLOBAL: {
int8_t inc = READBYTE() - 128; // amount we're incrementing by
@ -992,7 +1024,7 @@ int cosmoV_execute(CState *state) {
return -1;
}
break;
continue;
}
case OP_INCUPVAL: {
int8_t inc = READBYTE() - 128; // amount we're incrementing by
@ -1008,7 +1040,7 @@ int cosmoV_execute(CState *state) {
return -1;
}
break;
continue;
}
case OP_INCINDEX: {
int8_t inc = READBYTE() - 128; // amount we're incrementing by
@ -1065,7 +1097,7 @@ int cosmoV_execute(CState *state) {
return -1;
}
break;
continue;
}
case OP_INCOBJECT: {
int8_t inc = READBYTE() - 128; // amount we're incrementing by
@ -1119,7 +1151,7 @@ int cosmoV_execute(CState *state) {
return -1;
}
break;
continue;
}
case OP_EQUAL: {
// pop vals
@ -1128,27 +1160,27 @@ int cosmoV_execute(CState *state) {
// compare & push
cosmoV_pushBoolean(state, cosmoV_equal(*valA, *valB));
break;
continue;
}
case OP_GREATER: {
NUMBEROP(cosmoV_newBoolean, >)
break;
continue;
}
case OP_LESS: {
NUMBEROP(cosmoV_newBoolean, <)
break;
continue;
}
case OP_GREATER_EQUAL: {
NUMBEROP(cosmoV_newBoolean, >=)
break;
continue;
}
case OP_LESS_EQUAL: {
NUMBEROP(cosmoV_newBoolean, <=)
break;
continue;
}
case OP_TRUE: cosmoV_pushBoolean(state, true); break;
case OP_FALSE: cosmoV_pushBoolean(state, false); break;
case OP_NIL: cosmoV_pushValue(state, cosmoV_newNil()); break;
case OP_TRUE: cosmoV_pushBoolean(state, true); continue;
case OP_FALSE: cosmoV_pushBoolean(state, false); continue;
case OP_NIL: cosmoV_pushValue(state, cosmoV_newNil()); continue;
case OP_RETURN: {
uint8_t res = READBYTE();
return res;
@ -1157,7 +1189,6 @@ int cosmoV_execute(CState *state) {
CERROR("unknown opcode!");
exit(0);
}
//cosmoV_printStack(state);
}
#undef READBYTE

View File

@ -6,6 +6,8 @@
#include "cosmo.h"
#include "cstate.h"
//#define VM_DEBUG
typedef enum {
COSMOVM_OK,
COSMOVM_RUNTIME_ERR,