diff --git a/examples/stress.cosmo b/examples/stress.cosmo index bdecc1d..3cb6782 100644 --- a/examples/stress.cosmo +++ b/examples/stress.cosmo @@ -8,8 +8,8 @@ class Test end end --- stressing the GC -for (var i = 0; ; i=i+1) do +// stressing the GC +for (var i = 0; ; i++) do var x = Test("Hello world " .. i) x.print() end \ No newline at end of file diff --git a/examples/test.cosmo b/examples/test.cosmo index 85431ec..fdc0a6c 100644 --- a/examples/test.cosmo +++ b/examples/test.cosmo @@ -1,7 +1,7 @@ --- crafts a dummy class +// crafts a dummy class class test end --- instance of test +// instance of test var obj = test() test.__index = function(self, key) @@ -11,4 +11,4 @@ test.__index = function(self, key) end end -print(obj["lol"]) -- should print 9001? \ No newline at end of file +print(obj["lol"]) // should print 9001? \ No newline at end of file diff --git a/src/cdebug.c b/src/cdebug.c index 653fb92..7400425 100644 --- a/src/cdebug.c +++ b/src/cdebug.c @@ -12,16 +12,26 @@ int simpleInstruction(const char *name, int offset) { return offset + 1; // consume opcode } -int shortOperandInstruction(const char *name, CChunk *chunk, int offset) { +int u8OperandInstruction(const char *name, CChunk *chunk, int offset) { printf("%-16s [%03d]", name, readu8Chunk(chunk, offset + 1)); return offset + 2; } -int longOperandInstruction(const char *name, CChunk *chunk, int offset) { +int u16OperandInstruction(const char *name, CChunk *chunk, int offset) { printf("%-16s [%05d]", name, readu16Chunk(chunk, offset + 1)); 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 +} + +int u8u16OperandInstruction(const char *name, CChunk *chunk, int offset) { + printf("%-16s [%03d] [%05d]", name, readu8Chunk(chunk, offset + 1), readu16Chunk(chunk, offset + 2)); + return offset + 4; // op + u8 + u16 +} + int constInstruction(const char *name, CChunk *chunk, int offset, int indent) { int index = readu16Chunk(chunk, offset + 1); printf("%-16s [%05d] - ", name, index); @@ -65,25 +75,25 @@ int disasmInstr(CChunk *chunk, int offset, int indent) { case OP_GETGLOBAL: return constInstruction("OP_GETGLOBAL", chunk, offset, indent); case OP_SETLOCAL: - return shortOperandInstruction("OP_SETLOCAL", chunk, offset); + return u8OperandInstruction("OP_SETLOCAL", chunk, offset); case OP_GETLOCAL: - return shortOperandInstruction("OP_GETLOCAL", chunk, offset); + return u8OperandInstruction("OP_GETLOCAL", chunk, offset); case OP_SETUPVAL: - return shortOperandInstruction("OP_SETUPVAL", chunk, offset); + return u8OperandInstruction("OP_SETUPVAL", chunk, offset); case OP_GETUPVAL: - return shortOperandInstruction("OP_GETUPVAL", chunk, offset); + return u8OperandInstruction("OP_GETUPVAL", chunk, offset); case OP_PEJMP: - return longOperandInstruction("OP_PEJMP", chunk, offset); + return u16OperandInstruction("OP_PEJMP", chunk, offset); case OP_EJMP: - return longOperandInstruction("OP_EJMP", chunk, offset); + return u16OperandInstruction("OP_EJMP", chunk, offset); case OP_JMP: - return longOperandInstruction("OP_JMP", chunk, offset); + return u16OperandInstruction("OP_JMP", chunk, offset); case OP_JMPBACK: - return longOperandInstruction("OP_JMPBACK", chunk, offset); + return u16OperandInstruction("OP_JMPBACK", chunk, offset); case OP_POP: - return shortOperandInstruction("OP_POP", chunk, offset); + return u8OperandInstruction("OP_POP", chunk, offset); case OP_CALL: - return shortOperandInstruction("OP_CALL", chunk, offset); + return u8OperandInstruction("OP_CALL", chunk, offset); case OP_CLOSURE: { int index = readu16Chunk(chunk, offset + 1); printf("%-16s [%05d] - ", "OP_CLOSURE", index); @@ -109,13 +119,13 @@ int disasmInstr(CChunk *chunk, int offset, int indent) { case OP_CLOSE: return simpleInstruction("OP_CLOSE", offset); case OP_NEWOBJECT: - return longOperandInstruction("OP_NEWOBJECT", chunk, offset); + return u16OperandInstruction("OP_NEWOBJECT", chunk, offset); case OP_GETOBJECT: return simpleInstruction("OP_GETOBJECT", offset); case OP_SETOBJECT: return simpleInstruction("OP_SETOBJECT", offset); case OP_INVOKE: - return shortOperandInstruction("OP_INVOKE", chunk, offset); + return u8OperandInstruction("OP_INVOKE", chunk, offset); case OP_ADD: return simpleInstruction("OP_ADD", offset); case OP_SUB: @@ -145,7 +155,15 @@ int disasmInstr(CChunk *chunk, int offset, int indent) { case OP_NEGATE: return simpleInstruction("OP_NEGATE", offset); case OP_CONCAT: - return shortOperandInstruction("OP_CONCAT", chunk, offset); + return u8OperandInstruction("OP_CONCAT", chunk, offset); + case OP_INCLOCAL: + return u8u8OperandInstruction("OP_INCLOCAL", chunk, offset); + case OP_INCGLOBAL: + return u8u16OperandInstruction("OP_INCGLOBAL", chunk, offset); + case OP_INCUPVAL: + return u8u8OperandInstruction("OP_INCLOCAL", chunk, offset); + case OP_INCOBJECT: + return u8u16OperandInstruction("OP_INCOBJECT", chunk, offset); case OP_RETURN: return simpleInstruction("OP_RETURN", offset); default: diff --git a/src/clex.c b/src/clex.c index 987e805..ee020d6 100644 --- a/src/clex.c +++ b/src/clex.c @@ -109,8 +109,8 @@ void skipWhitespace(CLexState *state) { case '\t': next(state); // consume the whitespace break; - case '-': // consume comments - if (peekNext(state) == '-') { + case '/': // consume comments + if (peekNext(state) == '/') { // skip to next line (also let \n be consumed on the next iteration to properly handle that) while (!isEnd(state) && peek(state) != '\n' && peek(state) != '\0') // if it's not a newline or null terminator @@ -196,14 +196,15 @@ CToken cosmoL_scanToken(CLexState *state) { case '}': return makeToken(state, TOKEN_RIGHT_BRACE); case '[': return makeToken(state, TOKEN_LEFT_BRACKET); case ']': return makeToken(state, TOKEN_RIGHT_BRACKET); - // fall through case ';': return makeToken(state, TOKEN_EOS); case ',': return makeToken(state, TOKEN_COMMA); - case '+': return makeToken(state, TOKEN_PLUS); - case '-': return makeToken(state, TOKEN_MINUS); case '*': return makeToken(state, TOKEN_STAR); case '/': return makeToken(state, TOKEN_SLASH); // two character tokens + case '+': + return match(state, '+') ? makeToken(state, TOKEN_PLUS_PLUS) : makeToken(state, TOKEN_PLUS); + case '-': + return match(state, '-') ? makeToken(state, TOKEN_MINUS_MINUS) : makeToken(state, TOKEN_MINUS); case '.': return match(state, '.') ? makeToken(state, TOKEN_DOT_DOT) : makeToken(state, TOKEN_DOT); case '!': diff --git a/src/clex.h b/src/clex.h index 9963645..e5292a6 100644 --- a/src/clex.h +++ b/src/clex.h @@ -15,7 +15,9 @@ typedef enum { TOKEN_DOT, TOKEN_DOT_DOT, TOKEN_MINUS, + TOKEN_MINUS_MINUS, TOKEN_PLUS, + TOKEN_PLUS_PLUS, TOKEN_SLASH, TOKEN_STAR, TOKEN_EOS, // end of statement diff --git a/src/cmem.c b/src/cmem.c index 1225cd2..d96f8cf 100644 --- a/src/cmem.c +++ b/src/cmem.c @@ -76,6 +76,8 @@ void tableRemoveWhite(CState *state, CTable *tbl) { cosmoT_remove(state, tbl, entry->key); } } + + cosmoT_checkShrink(state, tbl); // recovers the memory we're no longer using } void markArray(CState *state, CValueArray *array) { diff --git a/src/cmem.h b/src/cmem.h index 354662f..58b7734 100644 --- a/src/cmem.h +++ b/src/cmem.h @@ -36,13 +36,16 @@ #define cosmoM_unfreezeGC(state) \ state->freezeGC--; \ - printf("unfreezing state at %s:%d [%d]\n", __FILE__, __LINE__, state->freezeGC) + printf("unfreezing state at %s:%d [%d]\n", __FILE__, __LINE__, state->freezeGC); \ + cosmoM_checkGarbage(state, 0) #else #define cosmoM_freezeGC(state) \ state->freezeGC++ #define cosmoM_unfreezeGC(state) \ - state->freezeGC-- + state->freezeGC--; \ + cosmoM_checkGarbage(state, 0) + #endif COSMO_API void *cosmoM_reallocate(CState *state, void *buf, size_t oldSize, size_t newSize); diff --git a/src/coperators.h b/src/coperators.h index df40737..626303f 100644 --- a/src/coperators.h +++ b/src/coperators.h @@ -29,19 +29,23 @@ typedef enum { OP_CALL, // calls top[-uint8_t] OP_CLOSURE, OP_CLOSE, - OP_NEWOBJECT, // + OP_NEWOBJECT, OP_GETOBJECT, OP_SETOBJECT, OP_INVOKE, // ARITHMETIC - OP_ADD, + OP_ADD, OP_SUB, OP_MULT, OP_DIV, OP_NOT, OP_NEGATE, OP_CONCAT, // concats uint8_t vars on the stack + OP_INCLOCAL, // pushes old value to stack, adds (uint8_t-128) to local[uint8_t] + OP_INCGLOBAL, // pushes old value to stack, adds (uint8_t-128) to globals[const[uint16_t]] + OP_INCUPVAL, // pushes old value to stack, adds (uint8_t-128) to closure->upval[uint8_t] + OP_INCOBJECT, // pushes old value to stack, adds (uint8_t-128) to obj[const[uint16_t]] // EQUALITY OP_EQUAL, @@ -56,7 +60,6 @@ typedef enum { OP_NIL, OP_RETURN - -} COPCODE; +} COPCODE; // there can be a max of 256 instructions #endif \ No newline at end of file diff --git a/src/cparse.c b/src/cparse.c index ec6f0ef..56dbe73 100644 --- a/src/cparse.c +++ b/src/cparse.c @@ -130,9 +130,7 @@ static void errorAt(CParseState *pstate, CToken *token, const char * msg) { if (token->type == TOKEN_EOF) { fprintf(stderr, " at end"); - } else if (token->type == TOKEN_ERROR) { - - } else { + } else if (!(token->type == TOKEN_ERROR)) { fprintf(stderr, " at '%.*s'", token->length, token->start); } @@ -446,22 +444,25 @@ static void _etterOP(CParseState *pstate, uint8_t op, int arg) { writeu8(pstate, arg); } -static void namedVariable(CParseState *pstate, CToken name, bool canAssign) { - uint8_t opGet, opSet; +static void namedVariable(CParseState *pstate, CToken name, bool canAssign, bool canIncrement) { + uint8_t opGet, opSet, inc; int arg = getLocal(pstate->compiler, &name); if (arg != -1) { // we found it in out local table! opGet = OP_GETLOCAL; opSet = OP_SETLOCAL; + inc = OP_INCLOCAL; } else if ((arg = getUpvalue(pstate->compiler, &name)) != -1) { opGet = OP_GETUPVAL; opSet = OP_SETUPVAL; + inc = OP_INCUPVAL; } else { // local & upvalue wasnt' found, assume it's a global! arg = identifierConstant(pstate, &name); opGet = OP_GETGLOBAL; opSet = OP_SETGLOBAL; + inc = OP_INCGLOBAL; } if (canAssign && match(pstate, TOKEN_EQUAL)) { @@ -469,7 +470,25 @@ static void namedVariable(CParseState *pstate, CToken name, bool canAssign) { expression(pstate); _etterOP(pstate, opSet, arg); valuePopped(pstate, 1); - } else { + } else if (canIncrement && match(pstate, TOKEN_PLUS_PLUS)) { // i++ + // now we increment the value + writeu8(pstate, inc); + writeu8(pstate, 128 + 1); // setting signed values in an unsigned int + if (inc == OP_INCGLOBAL) // globals are stored with a u16 + writeu16(pstate, arg); + else + writeu8(pstate, arg); + valuePushed(pstate, 1); + } else if (canIncrement && match(pstate, TOKEN_MINUS_MINUS)) { // i-- + // now we increment the value + writeu8(pstate, inc); + writeu8(pstate, 128 - 1); // setting signed values in an unsigned int + if (inc == OP_INCGLOBAL) // globals are stored with a u16 + writeu16(pstate, arg); + else + writeu8(pstate, arg); + valuePushed(pstate, 1); + } else { // getter _etterOP(pstate, opGet, arg); valuePushed(pstate, 1); @@ -502,7 +521,7 @@ static void anonFunction(CParseState *pstate, bool canAssign) { } static void variable(CParseState *pstate, bool canAssign) { - namedVariable(pstate, pstate->previous, canAssign); + namedVariable(pstate, pstate->previous, canAssign, true); } static void concat(CParseState *pstate, bool canAssign) { @@ -577,19 +596,31 @@ static void object(CParseState *pstate, bool canAssign) { static void dot(CParseState *pstate, bool canAssign) { consume(pstate, TOKEN_IDENTIFIER, "Expected property name after '.'."); uint16_t name = identifierConstant(pstate, &pstate->previous); - writeu8(pstate, OP_LOADCONST); - writeu16(pstate, name); if (canAssign && match(pstate, TOKEN_EQUAL)) { + writeu8(pstate, OP_LOADCONST); + writeu16(pstate, name); expression(pstate); writeu8(pstate, OP_SETOBJECT); valuePopped(pstate, 2); // pops key, value & object + } else if (match(pstate, TOKEN_PLUS_PLUS)) { // increment the field + writeu8(pstate, OP_INCOBJECT); + writeu8(pstate, 128 + 1); + writeu16(pstate, name); + } else if (match(pstate, TOKEN_MINUS_MINUS)) { // decrement the field + writeu8(pstate, OP_INCOBJECT); + writeu8(pstate, 128 - 1); + writeu16(pstate, name); } else if (match(pstate, TOKEN_LEFT_PAREN)) { // it's an invoked call + writeu8(pstate, OP_LOADCONST); + writeu16(pstate, name); uint8_t args = parseArguments(pstate); writeu8(pstate, OP_INVOKE); writeu8(pstate, args); valuePopped(pstate, args); // pops the function & the object but pushes a result } else { + writeu8(pstate, OP_LOADCONST); + writeu16(pstate, name); writeu8(pstate, OP_GETOBJECT); // pops key & object but also pushes the field so total popped is 1 } @@ -609,6 +640,63 @@ static void _index(CParseState *pstate, bool canAssign) { } } +static void increment(CParseState *pstate, int val) { + CToken name = pstate->previous; + if (match(pstate, TOKEN_DOT)) { // object? + namedVariable(pstate, name, false, false); // just get the object + + consume(pstate, TOKEN_IDENTIFIER, "Expected property name after '.'."); + uint16_t name = identifierConstant(pstate, &pstate->previous); + + writeu8(pstate, OP_INCOBJECT); + writeu8(pstate, 128 + val); // setting signed values in an unsigned int + writeu16(pstate, name); + valuePopped(pstate, 1); // popped the object off the stack + } else { + uint8_t op; + int arg = getLocal(pstate->compiler, &name); + + if (arg != -1) { + // we found it in out local table! + op = OP_INCLOCAL; + } else if ((arg = getUpvalue(pstate->compiler, &name)) != -1) { + op = OP_INCUPVAL; + } else { + // local & upvalue wasnt' found, assume it's a global! + arg = identifierConstant(pstate, &name); + op = OP_INCGLOBAL; + } + + writeu8(pstate, op); + writeu8(pstate, 128 + val); // setting signed values in an unsigned int + if (op == OP_INCGLOBAL) // globals are stored with a u16 + writeu16(pstate, arg); + else + writeu8(pstate, arg); + } + + // increment the old value on the stack + writeConstant(pstate, cosmoV_newNumber(val)); + writeu8(pstate, OP_ADD); +} + +// ++i +static void preincrement(CParseState *pstate, bool canAssign) { + // expect identifier + consume(pstate, TOKEN_IDENTIFIER, "Expected identifier after '++'"); + + increment(pstate, 1); +} + +// --i +static void predecrement(CParseState *pstate, bool canAssign) { + // expect identifier + consume(pstate, TOKEN_IDENTIFIER, "Expected identifier after '--'"); + + increment(pstate, -1); +} + + ParseRule ruleTable[] = { [TOKEN_LEFT_PAREN] = {group, call_, PREC_CALL}, [TOKEN_RIGHT_PAREN] = {NULL, NULL, PREC_NONE}, @@ -620,7 +708,9 @@ ParseRule ruleTable[] = { [TOKEN_DOT] = {NULL, dot, PREC_CALL}, [TOKEN_DOT_DOT] = {NULL, concat, PREC_CONCAT}, [TOKEN_MINUS] = {unary, binary, PREC_TERM}, + [TOKEN_MINUS_MINUS] = {predecrement, NULL, PREC_TERM}, [TOKEN_PLUS] = {NULL, binary, PREC_TERM}, + [TOKEN_PLUS_PLUS] = {preincrement, NULL, PREC_TERM}, [TOKEN_SLASH] = {NULL, binary, PREC_FACTOR}, [TOKEN_STAR] = {NULL, binary, PREC_FACTOR}, [TOKEN_EOS] = {NULL, NULL, PREC_NONE}, @@ -996,7 +1086,9 @@ static void forLoop(CParseState *pstate) { int bodyJmp = writeJmp(pstate, OP_JMP); int iteratorStart = getChunk(pstate)->count; + int savedPushed = pstate->compiler->pushedValues; expression(pstate); + alignStack(pstate, savedPushed); consume(pstate, TOKEN_RIGHT_PAREN, "Expected ')' after iterator"); writeJmpBack(pstate, loopStart); diff --git a/src/ctable.c b/src/ctable.c index a495fb4..010cf6a 100644 --- a/src/ctable.c +++ b/src/ctable.c @@ -109,16 +109,11 @@ static CTableEntry *findEntry(CTableEntry *entries, int mask, CValue key) { static void resizeTbl(CState *state, CTable *tbl, size_t newCapacity) { size_t size = sizeof(CTableEntry) * newCapacity; + int cachedCount = tbl->count; cosmoM_checkGarbage(state, size); // if this allocation would cause a GC, run the GC - // if count > 8 and active entries < tombstones - if (tbl->count > MIN_TABLE_CAPACITY && tbl->count - tbl->tombstones < tbl->tombstones) { - int tombs = tbl->tombstones; - tbl->tombstones = 0; // set this to 0 so in our recursive call to resizeTbl() this branch isn't run again - resizeTbl(state, tbl, nextPow2((tbl->count - tombs) * GROW_FACTOR)); // shrink based on active entries to the next pow of 2 - cosmoM_updateThreshhold(state); // force a threshhold update since this *could* be such a huge memory difference + if (tbl->count < cachedCount) // the GC removed some objects from this table and resized it, ignore our resize event! return; - } CTableEntry *entries = cosmoM_xmalloc(state, size); int newCount = 0; @@ -151,6 +146,16 @@ static void resizeTbl(CState *state, CTable *tbl, size_t newCapacity) { tbl->tombstones = 0; } +bool cosmoT_checkShrink(CState *state, CTable *tbl) { + // if count > 8 and active entries < tombstones + if (tbl->count > MIN_TABLE_CAPACITY && tbl->count - tbl->tombstones < tbl->tombstones) { + resizeTbl(state, tbl, nextPow2((tbl->count - tbl->tombstones) * GROW_FACTOR)); // shrink based on active entries to the next pow of 2 + return true; + } + + return false; +} + // returns a pointer to the allocated value COSMO_API CValue* cosmoT_insert(CState *state, CTable *tbl, CValue key) { // make sure we have enough space allocated diff --git a/src/ctable.h b/src/ctable.h index 796164d..0da078a 100644 --- a/src/ctable.h +++ b/src/ctable.h @@ -22,6 +22,7 @@ COSMO_API void cosmoT_addTable(CState *state, CTable *from, CTable *to); COSMO_API CValue *cosmoT_insert(CState *state, CTable *tbl, CValue key); CObjString *cosmoT_lookupString(CTable *tbl, const char *str, size_t length, uint32_t hash); +bool cosmoT_checkShrink(CState *state, CTable *tbl); bool cosmoT_get(CTable *tbl, CValue key, CValue *val); bool cosmoT_remove(CState *state, CTable *tbl, CValue key); diff --git a/src/cvm.c b/src/cvm.c index 462a22f..c570bd0 100644 --- a/src/cvm.c +++ b/src/cvm.c @@ -498,6 +498,80 @@ bool cosmoV_execute(CState *state) { cosmoV_pushValue(state, cosmoV_newObj(result)); break; } + case OP_INCLOCAL: { // this leaves the value on the stack + int8_t inc = READBYTE() - 128; // ammount we're incrementing by + uint8_t indx = READBYTE(); + StkPtr val = &frame->base[indx]; + + // check that it's a number value + if (val->type == COSMO_TNUMBER) { + cosmoV_pushValue(state, *val); // pushes old value onto the stack :) + *val = cosmoV_newNumber(val->val.num + inc); + } else { + cosmoV_error(state, "Expected number!"); + } + + break; + } + case OP_INCGLOBAL: { + int8_t inc = READBYTE() - 128; // ammount we're incrementing by + uint16_t indx = READUINT(); + CValue ident = constants[indx]; // grabs identifier + CValue *val = cosmoT_insert(state, &state->globals, ident); + + // check that it's a number value + if (val->type == COSMO_TNUMBER) { + cosmoV_pushValue(state, *val); // pushes old value onto the stack :) + *val = cosmoV_newNumber(val->val.num + inc); + } else { + cosmoV_error(state, "Expected number!"); + } + + break; + } + case OP_INCUPVAL: { + int8_t inc = READBYTE() - 128; // ammount we're incrementing by + uint8_t indx = READBYTE(); + CValue *val = frame->closure->upvalues[indx]->val; + + // check that it's a number value + if (val->type == COSMO_TNUMBER) { + cosmoV_pushValue(state, *val); // pushes old value onto the stack :) + *val = cosmoV_newNumber(val->val.num + inc); + } else { + cosmoV_error(state, "Expected number!"); + } + + break; + } + case OP_INCOBJECT: { + int8_t inc = READBYTE() - 128; // ammount we're incrementing by + uint16_t indx = READUINT(); + StkPtr temp = cosmoV_getTop(state, 0); // object should be at the top of the stack + CValue ident = constants[indx]; // grabs identifier + + // sanity check + if (!(temp->type == COSMO_TOBJ) || !(temp->val.obj->type == COBJ_OBJECT)) { + cosmoV_error(state, "Couldn't set a field on a non-object!"); + break; + } + + CObjObject *object = (CObjObject*)temp->val.obj; + CValue *val = cosmoT_insert(state, &object->tbl, ident); + + // pop the object off the stack + cosmoV_pop(state); + + // check that it's a number value + if (val->type == COSMO_TNUMBER) { + cosmoV_pushValue(state, *val); // pushes old value onto the stack :) + *val = cosmoV_newNumber(val->val.num + inc); + } else { + cosmoV_error(state, "Expected number!"); + } + + break; + } case OP_EQUAL: { // pop vals StkPtr valB = cosmoV_pop(state);