diff --git a/core/compile_slotpool.c b/2 similarity index 86% rename from core/compile_slotpool.c rename to 2 index 6db8c37a..7618d93f 100644 --- a/core/compile_slotpool.c +++ b/2 @@ -26,7 +26,7 @@ void dst_compile_slotpool_init(DstSlotPool *pool) { pool->s = NULL; pool->count = 0; - pool->free = 0; + pool->max; pool->cap = 0; } @@ -34,35 +34,34 @@ void dst_compile_slotpool_deinit(DstSlotPool *pool) { free(pool->s); pool->s = NULL; pool->cap = 0; + pool->max = 0; pool->count = 0; - pool->free = 0; } void dst_compile_slotpool_extend(DstSlotPool *pool, int32_t extra) { int32_t i; - int32_t newcount = pool->count + extra; - if (newcount > pool->cap) { - int32_t newcap = 2 * newcount; + int32_t newcap = pool->cap + extra; + if (newcap > pool->cap) { + newcap *= 2; pool->s = realloc(pool->s, newcap * sizeof(DstSlot)); if (NULL == pool->s) { DST_OUT_OF_MEMORY; } - pool->cap = newcap; } + pool->cap = newcap; /* Mark all new slots as free */ - for (i = pool->count; i < newcount; i++) { + for (i = pool->count; i < newcap; i++) { pool->s[i].flags = 0; } - pool->count = newcount; } DstSlot *dst_compile_slotpool_alloc(DstSlotPool *pool) { int32_t oldcount = pool->count; int32_t newcount = oldcount == 0xF0 ? 0x101 : oldcount + 1; int32_t index = newcount - 1; - while (pool->free < pool->count) { - if (!(pool->s[pool->free].flags & DST_SLOT_NOTEMPTY)) { - return pool->s + pool->free; + while (pool->count < pool->cap) { + if (!(pool->s[pool->count].flags & DST_SLOT_NOTEMPTY)) { + return pool->s + pool->count; } pool->free++; } diff --git a/Makefile b/Makefile index b8cfecf1..df92f51f 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ PREFIX=/usr/local DST_TARGET=dst DST_XXD=xxd DEBUGGER=lldb -DST_INTERNAL_HEADERS=$(addprefix core/,symcache.h opcodes.h strtod.h compile.h) +DST_INTERNAL_HEADERS=$(addprefix core/,symcache.h opcodes.h strtod.h compile.h gc.h) DST_HEADERS=$(addprefix include/dst/,dst.h) ############################# @@ -59,7 +59,7 @@ $(DST_XXD): libs/xxd.c ################################### DST_CORE_SOURCES=$(addprefix core/,\ - array.c asm.c buffer.c compile.c compile_slotpool.c \ + array.c asm.c buffer.c compile.c\ fiber.c func.c gc.c parse.c string.c strtod.c\ struct.c symcache.c syscalls.c table.c tuple.c userdata.c util.c\ value.c vm.c wrap.c) diff --git a/core/array.c b/core/array.c index 638a82e4..8b7d8b40 100644 --- a/core/array.c +++ b/core/array.c @@ -21,6 +21,7 @@ */ #include +#include "gc.h" /* Iniializes an array */ DstArray *dst_array_init(DstArray *array, int32_t capacity) { @@ -43,7 +44,7 @@ void dst_array_deinit(DstArray *array) { /* Creates a new array */ DstArray *dst_array(int32_t capacity) { - DstArray *array = dst_alloc(DST_MEMORY_ARRAY, sizeof(DstArray)); + DstArray *array = dst_gcalloc(DST_MEMORY_ARRAY, sizeof(DstArray)); return dst_array_init(array, capacity); } diff --git a/core/asm.c b/core/asm.c index 24a414fd..209bf1cc 100644 --- a/core/asm.c +++ b/core/asm.c @@ -24,6 +24,7 @@ #include #include "opcodes.h" +#include "gc.h" /* Bytecode op argument types */ @@ -511,7 +512,7 @@ static DstAssembleResult dst_asm1(DstAssembler *parent, DstAssembleOptions opts) DstValue x; /* Initialize funcdef */ - def = dst_alloc(DST_MEMORY_FUNCDEF, sizeof(DstFuncDef)); + def = dst_gcalloc(DST_MEMORY_FUNCDEF, sizeof(DstFuncDef)); def->environments = NULL; def->constants = NULL; def->bytecode = NULL; @@ -756,7 +757,7 @@ DstFunction *dst_asm_func(DstAssembleResult result) { if (result.status != DST_ASSEMBLE_OK) { return NULL; } - DstFunction *func = dst_alloc(DST_MEMORY_FUNCTION, sizeof(DstFunction)); + DstFunction *func = dst_gcalloc(DST_MEMORY_FUNCTION, sizeof(DstFunction)); func->def = result.funcdef; func->envs = NULL; return func; @@ -906,5 +907,8 @@ DstValue dst_disasm(DstFuncDef *def) { dst_table_put(ret, dst_csymbolv("environments"), dst_wrap_array(envs)); } + /* Add slotcount */ + dst_table_put(ret, dst_csymbolv("slotcount"), dst_wrap_integer(def->slotcount)); + return dst_wrap_struct(dst_table_to_struct(ret)); } diff --git a/core/buffer.c b/core/buffer.c index 6dbd337b..806cef71 100644 --- a/core/buffer.c +++ b/core/buffer.c @@ -21,6 +21,7 @@ */ #include +#include "gc.h" /* Initialize a buffer */ DstBuffer *dst_buffer_init(DstBuffer *buffer, int32_t capacity) { @@ -44,7 +45,7 @@ void dst_buffer_deinit(DstBuffer *buffer) { /* Initialize a buffer */ DstBuffer *dst_buffer(int32_t capacity) { - DstBuffer *buffer = dst_alloc(DST_MEMORY_BUFFER, sizeof(DstBuffer)); + DstBuffer *buffer = dst_gcalloc(DST_MEMORY_BUFFER, sizeof(DstBuffer)); return dst_buffer_init(buffer, capacity); } diff --git a/core/compile.c b/core/compile.c index 61185e14..7f1a8877 100644 --- a/core/compile.c +++ b/core/compile.c @@ -22,6 +22,7 @@ #include #include "compile.h" +#include "gc.h" /* Lazily sort the optimizers */ /*static int optimizers_sorted = 0;*/ @@ -65,17 +66,130 @@ DstFormOptions dst_compile_getopts_value(DstFormOptions opts, DstValue key) { opts.sourcemap = sourcemap; return opts; } -/* Eneter a new scope */ + +/* Allocate a slot index */ +static int32_t slotalloc_index(DstScope *scope) { + /* Get the nth bit in the array */ + int32_t i, biti; + biti = -1; + for (i = 0; i < scope->scap; i++) { + uint32_t block = scope->slots[i]; + if (block != 0xFFFFFFFF) { + biti = i << 5; /* + clz(block) */ + while (block & 1) { + biti++; + block >>= 1; + } + break; + } + } + if (biti == -1) { + int32_t j; + int32_t newcap = scope->scap * 2 + 1; + scope->slots = realloc(scope->slots, sizeof(int32_t) * newcap); + if (NULL == scope->slots) { + DST_OUT_OF_MEMORY; + } + for (j = scope->scap; j < newcap; j++) { + /* Preallocate slots 0xF0 through 0xFF. */ + scope->slots[j] = j == 7 ? 0xFFFF0000 : 0x00000000; + } + biti = scope->scap << 5; + scope->scap = newcap; + } + /* set the bit at index biti */ + scope->slots[biti >> 5] |= 1 << (biti & 0x1F); + if (biti > scope->smax) + scope->smax = biti; + return biti; +} + +/* Allocate a slot */ +static DstSlot slotalloc(DstScope *scope) { + DstSlot ret; + ret.index = slotalloc_index(scope); + ret.envindex = 0; + ret.constant = dst_wrap_nil(); + ret.flags = 0; + return ret; +} + +/* Free a slot index */ +static void slotfree_index(DstScope *scope, int32_t index) { + /* Don't free the pre allocated slots */ + if (index < 0xF0 || index > 0xFF) + scope->slots[index >> 5] &= ~(1 << (index & 0x1F)); +} + +/* Free a slot */ +static void slotfree(DstScope *scope, DstSlot s) { + if (s.flags & DST_SLOT_CONSTANT) + return; + if (s.envindex > 0) + return; + slotfree_index(scope, s.index); +} + +/* Find a slot given a symbol. Return 1 if found, otherwise 0. */ +static int slotsymfind(DstScope *scope, const uint8_t *sym, DstSlot *out) { + int32_t i; + for (i = 0; i < scope->symcount; i++) { + if (scope->syms[i].sym == sym) { + *out = scope->syms[i].slot; + return 1; + } + } + return 0; +} + +/* Add a slot to a scope with a symbol associated with it (def or var). */ +static void slotsym(DstScope *scope, const uint8_t *sym, DstSlot s) { + int32_t index = scope->symcount; + int32_t newcount = index + 1; + if (newcount > scope->symcap) { + int32_t newcap = 2 * newcount; + scope->syms = realloc(scope->syms, newcap * sizeof(scope->syms[0])); + if (NULL == scope->syms) { + DST_OUT_OF_MEMORY; + } + scope->symcap = newcap; + } + scope->symcount = newcount; + scope->syms[index].sym = sym; + scope->syms[index].slot = s; +} + +/* Add a constant to the current scope. Return the index of the constant. */ +static int32_t addconst(DstCompiler *c, const DstValue *sourcemap, DstValue x) { + DstScope *scope = dst_compile_topscope(c); + int32_t i, index, newcount; + for (i = 0; i < scope->ccount; i++) { + if (dst_equals(x, scope->consts[i])) + return i; + } + if (scope->ccount >= 0xFFFF) + dst_compile_cerror(c, sourcemap, "too many constants"); + index = scope->ccount; + newcount = index + 1; + if (newcount > scope->ccap) { + int32_t newcap = 2 * newcount; + scope->consts = realloc(scope->consts, newcap * sizeof(DstValue)); + if (NULL == scope->consts) { + DST_OUT_OF_MEMORY; + } + scope->ccap = newcap; + } + scope->consts[index] = x; + scope->ccount = newcount; + return index; +} + +/* Enter a new scope */ void dst_compile_scope(DstCompiler *c, int newfn) { int32_t newcount, oldcount; - int32_t newlevel, oldlevel; DstScope *scope; oldcount = c->scopecount; newcount = oldcount + 1; - oldlevel = c->scopecount - ? c->scopes[c->scopecount - 1].level - : 0; - newlevel = oldlevel + newfn; if (newcount > c->scopecap) { int32_t newcap = 2 * newcount; c->scopes = realloc(c->scopes, newcap * sizeof(DstScope)); @@ -86,9 +200,16 @@ void dst_compile_scope(DstCompiler *c, int newfn) { } scope = c->scopes + oldcount; c->scopecount = newcount; - dst_array_init(&(scope->constants), 0); - dst_table_init(&scope->symbols, 4); - dst_table_init(&scope->constantrev, 4); + + /* Initialize the scope */ + + scope->consts = NULL; + scope->ccap = 0; + scope->ccount = 0; + + scope->syms = NULL; + scope->symcount = 0; + scope->symcap = 0; scope->envs = NULL; scope->envcount = 0; @@ -96,10 +217,11 @@ void dst_compile_scope(DstCompiler *c, int newfn) { scope->bytecode_start = c->buffercount; - dst_compile_slotpool_init(&scope->slots); - dst_compile_slotpool_init(&scope->unorderedslots); + /* Initialize slots */ + scope->slots = NULL; + scope->scap = 0; + scope->smax = -1; - scope->level = newlevel; scope->flags = newfn ? DST_SCOPE_FUNCTION : 0; } @@ -112,36 +234,27 @@ void dst_compile_popscope(DstCompiler *c) { * We need to know the total number of slots used when compiling the function. */ if (!(scope->flags & DST_SCOPE_FUNCTION) && c->scopecount) { DstScope *newscope = dst_compile_topscope(c); - dst_compile_slotpool_extend(&newscope->slots, scope->slots.count); + if (newscope->smax < scope->smax) + newscope->smax = scope->smax; } - dst_table_deinit(&scope->symbols); - dst_table_deinit(&scope->constantrev); - dst_array_deinit(&scope->constants); - dst_compile_slotpool_deinit(&scope->slots); - dst_compile_slotpool_deinit(&scope->unorderedslots); + free(scope->consts); + free(scope->slots); + free(scope->syms); free(scope->envs); } -DstSlot *dst_compile_constantslot(DstCompiler *c, DstValue x) { - DstScope *scope = dst_compile_topscope(c); - DstSlot *ret = dst_compile_slotpool_alloc(&scope->unorderedslots); - ret->flags = (1 << dst_type(x)) | DST_SLOT_CONSTANT | DST_SLOT_NOTEMPTY; - ret->index = -1; - ret->constant = x; - ret->envindex = 0; +DstSlot dst_compile_constantslot(DstValue x) { + DstSlot ret; + ret.flags = (1 << dst_type(x)) | DST_SLOT_CONSTANT; + ret.index = -1; + ret.constant = x; + ret.envindex = 0; return ret; } /* Free a single slot */ -void dst_compile_freeslot(DstCompiler *c, DstSlot *slot) { - DstScope *scope = dst_compile_topscope(c); - if (slot->flags & (DST_SLOT_CONSTANT)) { - return; - } - if (slot->envindex != 0) { - return; - } - dst_compile_slotpool_free(&scope->slots, slot); +void dst_compile_freeslot(DstCompiler *c, DstSlot slot) { + slotfree(dst_compile_topscope(c), slot); } /* @@ -163,36 +276,48 @@ void dst_compile_freeslot(DstCompiler *c, DstSlot *slot) { */ /* Allow searching for symbols. Return information about the symbol */ -DstSlot *dst_compile_resolve( +DstSlot dst_compile_resolve( DstCompiler *c, const DstValue *sourcemap, const uint8_t *sym) { - DstSlot *ret = NULL; + DstSlot ret = dst_compile_constantslot(dst_wrap_nil()); DstScope *scope = dst_compile_topscope(c); - int32_t env_index = 0; + int32_t envindex = 0; int foundlocal = 1; /* Search scopes for symbol, starting from top */ while (scope >= c->scopes) { - DstValue check = dst_table_get(&scope->symbols, dst_wrap_symbol(sym)); - if (dst_checktype(check, DST_USERDATA)) { - ret = dst_unwrap_pointer(check); + if (slotsymfind(scope, sym, &ret)) goto found; - } - scope--; if (scope->flags & DST_SCOPE_FUNCTION) foundlocal = 0; + scope--; } - /* Symbol not found */ - dst_compile_error(c, sourcemap, dst_formatc("unknown symbol %q", sym)); + /* Symbol not found - check for global */ + { + DstValue check = dst_get(c->env, dst_wrap_symbol(sym)); + if (dst_checktype(check, DST_STRUCT) || dst_checktype(check, DST_TABLE)) { + DstValue ref = dst_get(check, dst_csymbolv("ref")); + if (dst_checktype(ref, DST_ARRAY)) { + DstSlot ret = dst_compile_constantslot(ref); + ret.flags |= DST_SLOT_REF; + return ret; + } else { + DstValue value = dst_get(check, dst_csymbolv("value")); + return dst_compile_constantslot(value); + } + } else { + dst_compile_error(c, sourcemap, dst_formatc("unknown symbol %q", sym)); + } + } /* Symbol was found */ found: /* Constants can be returned immediately (they are stateless) */ - if (ret->flags & DST_SLOT_CONSTANT) + if (ret.flags & DST_SLOT_CONSTANT) return ret; /* non-local scope needs to expose its environment */ @@ -218,14 +343,15 @@ DstSlot *dst_compile_resolve( int scopefound = 0; /* Check if scope already has env. If so, break */ for (j = 1; j < scope->envcount; j++) { - if (scope->envs[j] == env_index) { + if (scope->envs[j] == envindex) { scopefound = 1; - env_index = j; + envindex = j; break; } } + /* Add the environment if it is not already referenced */ if (!scopefound) { - env_index = scope->envcount; + envindex = scope->envcount; /* Ensure capacity for adding scope */ if (newcount > scope->envcap) { int32_t newcap = 2 * newcount; @@ -235,21 +361,14 @@ DstSlot *dst_compile_resolve( } scope->envcap = newcap; } - scope->envs[scope->envcount] = env_index; + scope->envs[scope->envcount] = envindex; scope->envcount = newcount; } } scope++; } - /* Store in the unordered slots so we don't modify the original slot. */ - if (!foundlocal) { - DstSlot *newret = dst_compile_slotpool_alloc(&scope->unorderedslots); - *newret = *ret; - newret->envindex = env_index; - ret = newret; - } - + ret.envindex = envindex; return ret; } @@ -274,210 +393,359 @@ void dst_compile_emit(DstCompiler *c, const DstValue *sourcemap, uint32_t instr) c->buffer[index] = instr; } -/* Represents a local slot - not a constant, and within a specified range. Also - * contains if it corresponds to a real slot. If temp, then the slot index - * should be free right after use */ -typedef struct DstLocalSlot DstLocalSlot; -struct DstLocalSlot { - DstSlot *orig; - int temp; - int dirty; - int32_t index; -}; - -/* Get the index of a constant */ -static int32_t dst_compile_constant_index(DstCompiler *c, const DstValue *sourcemap, DstValue x) { - DstScope *scope = dst_compile_topscope(c); - DstValue check; - int32_t count = scope->constants.count; - check = dst_table_get(&scope->constantrev, x); - if (dst_checktype(check, DST_INTEGER)) { - return dst_unwrap_integer(check); - } - if (count >= 0xFFFF) { - dst_compile_cerror(c, sourcemap, "too many constants"); - } - dst_array_push(&scope->constants, x); - dst_table_put(&scope->constantrev, x, dst_wrap_integer(count)); - return count; -} - /* Realize any slot to a local slot. Call this to get a slot index * that can be used in an instruction. */ -static DstLocalSlot dst_compile_slot_pre( +static int32_t dst_compile_preread( DstCompiler *c, const DstValue *sourcemap, int32_t max, - int32_t hint, - int isdest, int nth, - DstSlot *s) { + DstSlot s) { DstScope *scope = dst_compile_topscope(c); - DstLocalSlot ret; - ret.orig = s; - ret.dirty = isdest; - ret.temp = 0; + int32_t ret; - if (s->flags & DST_SLOT_CONSTANT) { + if (s.flags & DST_SLOT_REF) + max = 0xFF; + + if (s.flags & DST_SLOT_CONSTANT) { int32_t cindex; - int32_t nextfree = dst_compile_slotpool_alloc(&scope->slots)->index; - if (hint >= 0 && hint <= 0xFF) { - ret.index = hint; - } else if (nextfree >= 0xF0) { - ret.index = 0xF0 + nth; - dst_compile_slotpool_freeindex(&scope->slots, nextfree); - } else { - ret.temp = 1; - ret.index = nextfree; + ret = slotalloc_index(scope); + if (ret > max) { + slotfree_index(scope, ret); + ret = 0xF0 + nth; } /* Use instructions for loading certain constants */ - switch (dst_type(s->constant)) { + switch (dst_type(s.constant)) { case DST_NIL: - dst_compile_emit(c, sourcemap, ((uint32_t)(ret.index) << 8) | DOP_LOAD_NIL); + dst_compile_emit(c, sourcemap, (ret << 8) | DOP_LOAD_NIL); break; case DST_TRUE: - dst_compile_emit(c, sourcemap, ((uint32_t)(ret.index) << 8) | DOP_LOAD_TRUE); + dst_compile_emit(c, sourcemap, (ret << 8) | DOP_LOAD_TRUE); break; case DST_FALSE: - dst_compile_emit(c, sourcemap, ((uint32_t)(ret.index) << 8) | DOP_LOAD_FALSE); + dst_compile_emit(c, sourcemap, (ret << 8) | DOP_LOAD_FALSE); break; case DST_INTEGER: { - int32_t i = dst_unwrap_integer(s->constant); + int32_t i = dst_unwrap_integer(s.constant); if (i <= INT16_MAX && i >= INT16_MIN) { dst_compile_emit(c, sourcemap, - ((uint32_t)i << 16) | - ((uint32_t)(ret.index) << 8) | + (i << 16) | + (ret << 8) | DOP_LOAD_INTEGER); break; } /* fallthrough */ } default: - cindex = dst_compile_constant_index(c, sourcemap, s->constant); - if (isdest) - dst_compile_cerror(c, sourcemap, "cannot write to a constant"); + cindex = addconst(c, sourcemap, s.constant); dst_compile_emit(c, sourcemap, - ((uint32_t)cindex << 16) | - ((uint32_t)(ret.index) << 8) | + (cindex << 16) | + (ret << 8) | DOP_LOAD_CONSTANT); break; } - } else if (s->envindex > 0 || s->index > max) { + /* If we also are a reference, deref the one element array */ + if (s.flags & DST_SLOT_REF) { + dst_compile_emit(c, sourcemap, + (ret << 16) | + (ret << 8) | + DOP_GET_INDEX); + } + } else if (s.envindex > 0 || s.index > max) { /* Get a local slot to shadow the environment or far slot */ - int32_t nextfree = dst_compile_slotpool_alloc(&scope->slots)->index; - if (hint >= 0 && hint <= 0xFF) { - ret.index = hint; - } else if (nextfree >= 0xF0) { - ret.index = 0xF0 + nth; - dst_compile_slotpool_freeindex(&scope->slots, nextfree); + ret = slotalloc_index(scope); + if (ret > max) { + slotfree_index(scope, ret); + ret = 0xF0 + nth; + } + /* Move the remote slot into the local space */ + if (s.envindex > 0) { + /* Load the higher slot */ + dst_compile_emit(c, sourcemap, + ((uint32_t)(s.index) << 24) | + ((uint32_t)(s.envindex) << 16) | + ((uint32_t)(ret) << 8) | + DOP_LOAD_UPVALUE); } else { - ret.temp = 1; - ret.index = nextfree; + /* Slot is a far slot: greater than 0xFF. Get + * the far data and bring it to the near slot. */ + dst_compile_emit(c, sourcemap, + ((uint32_t)(s.index) << 16) | + ((uint32_t)(ret) << 8) | + DOP_MOVE_NEAR); } - if (!isdest) { - /* Move the remote slot into the local space */ - if (s->envindex > 0) { - /* Load the higher slot */ - dst_compile_emit(c, sourcemap, - ((uint32_t)(s->index) << 24) | - ((uint32_t)(s->envindex) << 16) | - ((uint32_t)(ret.index) << 8) | - DOP_LOAD_UPVALUE); - } else { - /* Slot is a far slot: greater than 0xFF. Get - * the far data and bring it to the near slot. */ - dst_compile_emit(c, sourcemap, - ((uint32_t)(s->index) << 16) | - ((uint32_t)(ret.index) << 8) | - DOP_MOVE_NEAR); - } - } - } else if (hint >= 0 && hint <= 0xFF && isdest) { - ret.index = hint; } else { /* We have a normal slot that fits in the required bit width */ - ret.index = s->index; + ret = s.index; } return ret; } -/* Call this on a DstLocalSlot to free the slot or sync any changes - * made after the instruction has been emitted. */ -static void dst_compile_slot_post( +/* Call this to release a read handle after emitting the instruction. */ +static void dst_compile_postread(DstCompiler *c, DstSlot s, int32_t index) { + if (index != s.index || s.envindex > 0 || s.flags & DST_SLOT_CONSTANT) { + /* We need to free the temporary slot */ + DstScope *scope = dst_compile_topscope(c); + slotfree_index(scope, index); + } +} + +/* Get a write slot index to emit an instruction. */ +static int32_t dst_compile_prewrite( DstCompiler *c, const DstValue *sourcemap, - DstLocalSlot ls) { - DstSlot *s = ls.orig; - DstScope *scope = dst_compile_topscope(c); - if (ls.temp) - dst_compile_slotpool_freeindex(&scope->slots, ls.index); - if (ls.dirty) { - /* We need to save the data in the local slot to the original slot */ - if (s->envindex > 0) { + int32_t nth, + DstSlot s) { + int32_t ret = 0; + if (s.flags & DST_SLOT_CONSTANT) { + if (!(s.flags & DST_SLOT_REF)) { + dst_compile_cerror(c, sourcemap, "cannot write to constant"); + } + } else if (s.envindex > 0 || s.index > 0xFF) { + DstScope *scope = dst_compile_topscope(c); + /* Get a local slot to shadow the environment or far slot */ + ret = slotalloc_index(scope); + if (ret > 0xFF) { + slotfree_index(scope, ret); + ret = 0xF0 + nth; + } + /* Move the remote slot into the local space */ + if (s.envindex > 0) { /* Load the higher slot */ dst_compile_emit(c, sourcemap, - ((uint32_t)(s->index) << 24) | - ((uint32_t)(s->envindex) << 16) | - ((uint32_t)(ls.index) << 8) | - DOP_SET_UPVALUE); - } else if (s->index != ls.index) { - /* There was a local remapping */ + ((uint32_t)(s.index) << 24) | + ((uint32_t)(s.envindex) << 16) | + ((uint32_t)(ret) << 8) | + DOP_LOAD_UPVALUE); + } else { + /* Slot is a far slot: greater than 0xFF. Get + * the far data and bring it to the near slot. */ dst_compile_emit(c, sourcemap, - ((uint32_t)(s->index) << 16) | - ((uint32_t)(ls.index) << 8) | - DOP_MOVE_FAR); + ((uint32_t)(s.index) << 16) | + ((uint32_t)(ret) << 8) | + DOP_MOVE_NEAR); } + } else { + /* We have a normal slot that fits in the required bit width */ + ret = s.index; + } + return ret; +} + +/* Release a write index after emitting the instruction */ +static void dst_compile_postwrite( + DstCompiler *c, + const DstValue *sourcemap, + DstSlot s, + int32_t index) { + + /* Set the ref */ + if (s.flags & DST_SLOT_REF) { + DstScope *scope = dst_compile_topscope(c); + int32_t cindex = addconst(c, sourcemap, s.constant); + int32_t refindex = slotalloc_index(scope); + if (refindex > 0xFF) { + slotfree_index(scope, refindex); + refindex = 0xFF; + } + dst_compile_emit(c, sourcemap, + (cindex << 16) | + (refindex << 8) | + DOP_LOAD_CONSTANT); + dst_compile_emit(c, sourcemap, + (index << 16) | + (refindex << 8) | + DOP_PUT_INDEX); + slotfree_index(scope, refindex); + return; + } + + /* We need to save the data in the local slot to the original slot */ + if (s.envindex > 0) { + /* Load the higher slot */ + dst_compile_emit(c, sourcemap, + ((uint32_t)(s.index) << 24) | + ((uint32_t)(s.envindex) << 16) | + ((uint32_t)(index) << 8) | + DOP_SET_UPVALUE); + } else if (s.index != index) { + /* There was a local remapping */ + dst_compile_emit(c, sourcemap, + ((uint32_t)(s.index) << 16) | + ((uint32_t)(index) << 8) | + DOP_MOVE_FAR); + } + if (index != s.index || s.envindex > 0) { + /* We need to free the temporary slot */ + DstScope *scope = dst_compile_topscope(c); + slotfree_index(scope, index); } } /* Generate the return instruction for a slot. */ -static void dst_compile_return(DstCompiler *c, const DstValue *sourcemap, DstSlot *s) { - if (s->flags & DST_SLOT_CONSTANT && dst_checktype(s->constant, DST_NIL)) { - dst_compile_emit(c, sourcemap, DOP_RETURN_NIL); +static void dst_compile_return(DstCompiler *c, const DstValue *sourcemap, DstSlot s) { + if (s.flags & DST_SLOT_CONSTANT && dst_checktype(s.constant, DST_NIL)) { + dst_compile_emit(c, sourcemap, DOP_RETURN_NIL); } else { - DstLocalSlot ls = dst_compile_slot_pre( - c, sourcemap, 0xFFFF, -1, - 0, 1, s); - dst_compile_emit(c, sourcemap, DOP_RETURN | (ls.index << 8)); - dst_compile_slot_post(c, sourcemap, ls); + int32_t ls = dst_compile_preread(c, sourcemap, 0xFFFF, 1, s); + dst_compile_emit(c, sourcemap, DOP_RETURN | (ls << 8)); + dst_compile_postread(c, s, ls); } } -DstSlot *dst_compile_def(DstFormOptions opts, int32_t argn, const DstValue *argv) { +/* Check if the last instructions emitted returned. Relies on the fact that + * a form should emit no more instructions after returning. */ +static int dst_compile_did_return(DstCompiler *c) { + uint32_t lastop; + if (!c->buffercount) + return 0; + lastop = (c->buffer[c->buffercount - 1]) & 0xFF; + return lastop == DOP_RETURN || + lastop == DOP_RETURN_NIL || + lastop == DOP_TAILCALL; +} + +/* Get a target slot for emitting an instruction. */ +static DstSlot dst_compile_gettarget(DstFormOptions opts) { DstScope *scope; - DstSlot *rvalue; - DstFormOptions subopts; - DstValue check; - if (argn != 2) - dst_compile_cerror(opts.compiler, opts.sourcemap, "expected 2 arguments"); - if (!dst_checktype(argv[0], DST_SYMBOL)) - dst_compile_cerror(opts.compiler, opts.sourcemap, "expected symbol"); - scope = dst_compile_topscope(opts.compiler); - check = dst_table_get(&scope->symbols, argv[0]); - if (dst_checktype(check, DST_INTEGER)) { - dst_compile_cerror(opts.compiler, opts.sourcemap, "cannot redefine symbol"); + DstSlot ret; + if (opts.flags & DST_FOPTS_HINT) { + return opts.hint; } - subopts = dst_compile_getopts_index(opts, 1); - rvalue = dst_compile_value(subopts); - dst_table_put(&scope->symbols, argv[0], dst_wrap_userdata(rvalue)); - return rvalue; + scope = dst_compile_topscope(opts.compiler); + ret = slotalloc(scope); + /* Inherit type of opts */ + ret.flags |= opts.flags & DST_SLOTTYPE_ANY; + return ret; } -/* Compile an array */ +/* Push a series of values */ +static void dst_compile_pushtuple( + DstCompiler *c, + const DstValue *sourcemap, + DstValue x) { + DstFormOptions opts; + int32_t i, len; + + opts.compiler = c; + opts.hint = dst_compile_constantslot(dst_wrap_nil()); + opts.flags = 0; + opts.x = x; + opts.sourcemap = sourcemap; + + len = dst_length(x); + for (i = 1; i < len - 2; i += 3) { + DstFormOptions o1 = dst_compile_getopts_index(opts, i); + DstFormOptions o2 = dst_compile_getopts_index(opts, i + 1); + DstFormOptions o3 = dst_compile_getopts_index(opts, i + 2); + DstSlot s1 = dst_compile_value(o1); + DstSlot s2 = dst_compile_value(o2); + DstSlot s3 = dst_compile_value(o3); + int32_t ls1 = dst_compile_preread(c, o1.sourcemap, 0xFF, 1, s1); + int32_t ls2 = dst_compile_preread(c, o2.sourcemap, 0xFF, 2, s2); + int32_t ls3 = dst_compile_preread(c, o3.sourcemap, 0xFF, 3, s3); + dst_compile_emit(c, o1.sourcemap, + (ls3 << 24) | + (ls2 << 16) | + (ls1 << 8) | + DOP_PUSH_3); + dst_compile_postread(c, s1, ls1); + dst_compile_postread(c, s2, ls2); + dst_compile_postread(c, s3, ls3); + dst_compile_freeslot(c, s1); + dst_compile_freeslot(c, s2); + dst_compile_freeslot(c, s3); + } + if (i == len - 2) { + DstFormOptions o1 = dst_compile_getopts_index(opts, i); + DstFormOptions o2 = dst_compile_getopts_index(opts, i + 1); + DstSlot s1 = dst_compile_value(o1); + DstSlot s2 = dst_compile_value(o2); + int32_t ls1 = dst_compile_preread(c, o1.sourcemap, 0xFF, 1, s1); + int32_t ls2 = dst_compile_preread(c, o2.sourcemap, 0xFFFF, 2, s2); + dst_compile_emit(c, o1.sourcemap, + (ls2 << 16) | + (ls1 << 8) | + DOP_PUSH_2); + dst_compile_postread(c, s1, ls1); + dst_compile_postread(c, s2, ls2); + dst_compile_freeslot(c, s1); + dst_compile_freeslot(c, s2); + } else if (i == len - 1) { + DstFormOptions o1 = dst_compile_getopts_index(opts, i); + DstSlot s1 = dst_compile_value(o1); + int32_t ls1 = dst_compile_preread(c, o1.sourcemap, 0xFFFFFF, 1, s1); + dst_compile_emit(c, o1.sourcemap, + (ls1 << 8) | + DOP_PUSH); + dst_compile_postread(c, s1, ls1); + dst_compile_freeslot(c, s1); + } +} + +/* Compile a tuplle */ +DstSlot dst_compile_tuple(DstFormOptions opts) { + DstSlot head; + DstFormOptions subopts; + DstCompiler *c = opts.compiler; + const DstValue *tup = dst_unwrap_tuple(opts.x); + int headcompiled = 0; + subopts = dst_compile_getopts_index(opts, 0); + subopts.flags &= DST_FUNCTION | DST_CFUNCTION; + if (dst_tuple_length(tup) == 0) { + return dst_compile_constantslot(opts.x); + } + if (dst_checktype(tup[0], DST_SYMBOL)) { + /* Check specials */ + } else { + head = dst_compile_value(subopts); + headcompiled = 1; + if ((head.flags & DST_SLOT_CONSTANT)) { + if (dst_checktype(head.constant, DST_CFUNCTION)) { + /* Cfunction optimization */ + printf("add cfunction optimization here...\n"); + } + /* Could also later check for other optimizations here, such + * as function inlining and aot evaluation on pure functions. */ + } + } + /* Compile a normal function call */ + { + int32_t headindex; + DstSlot retslot; + if (!headcompiled) { + head = dst_compile_value(subopts); + headcompiled = 1; + } + headindex = dst_compile_preread(c, subopts.sourcemap, 0xFFFF, 1, head); + dst_compile_pushtuple(opts.compiler, opts.sourcemap, opts.x); + if (opts.flags & DST_FOPTS_TAIL) { + dst_compile_emit(c, subopts.sourcemap, (headindex << 8) | DOP_TAILCALL); + retslot = dst_compile_constantslot(dst_wrap_nil()); + } else { + int32_t retindex; + retslot = dst_compile_gettarget(opts); + retindex = dst_compile_preread(c, subopts.sourcemap, 0xFF, 2, retslot); + dst_compile_emit(c, subopts.sourcemap, (headindex << 16) | (retindex << 8) | DOP_CALL); + dst_compile_postread(c, retslot, retindex); + } + dst_compile_postread(c, head, headindex); + return retslot; + } +} /* Compile a single value */ -DstSlot *dst_compile_value(DstFormOptions opts) { - DstSlot *ret; - int doreturn = opts.flags & DST_FOPTS_TAIL; +DstSlot dst_compile_value(DstFormOptions opts) { + DstSlot ret; if (opts.compiler->recursion_guard <= 0) { dst_compile_cerror(opts.compiler, opts.sourcemap, "recursed too deeply"); } opts.compiler->recursion_guard--; switch (dst_type(opts.x)) { default: - ret = dst_compile_constantslot(opts.compiler, opts.x); + ret = dst_compile_constantslot(opts.x); break; case DST_SYMBOL: { @@ -485,13 +753,13 @@ DstSlot *dst_compile_value(DstFormOptions opts) { if (dst_string_length(sym) > 0 && sym[0] != ':') { ret = dst_compile_resolve(opts.compiler, opts.sourcemap, sym); } else { - ret = dst_compile_constantslot(opts.compiler, opts.x); + ret = dst_compile_constantslot(opts.x); } break; } - /*case DST_TUPLE:*/ - /*ret = dst_compile_tuple(opts); */ - /*break;*/ + case DST_TUPLE: + ret = dst_compile_tuple(opts); + break; /*case DST_ARRAY:*/ /*ret = dst_compile_array(opts); */ /*break;*/ @@ -502,7 +770,7 @@ DstSlot *dst_compile_value(DstFormOptions opts) { /*ret = dst_compile_table(opts);*/ /*break;*/ } - if (doreturn) { + if ((opts.flags & DST_FOPTS_TAIL) && !dst_compile_did_return(opts.compiler)) { dst_compile_return(opts.compiler, opts.sourcemap, ret); } opts.compiler->recursion_guard++; @@ -515,10 +783,12 @@ static DstFuncDef *dst_compile_pop_funcdef(DstCompiler *c) { DstFuncDef *def; /* Initialize funcdef */ - def = dst_alloc(DST_MEMORY_FUNCDEF, sizeof(DstFuncDef)); + def = dst_gcalloc(DST_MEMORY_FUNCDEF, sizeof(DstFuncDef)); def->environments = NULL; def->constants = NULL; - def->slotcount = scope->slots.count; + def->source = NULL; + def->sourcepath = NULL; + def->slotcount = scope->smax + 1; /* Copy envs */ def->environments_length = scope->envcount; @@ -531,13 +801,13 @@ static DstFuncDef *dst_compile_pop_funcdef(DstCompiler *c) { } /* Copy constants */ - def->constants_length = scope->constants.count; - if (def->constants) { - def->constants = malloc(sizeof(DstValue) * scope->constants.count); + def->constants_length = scope->ccount; + if (def->constants_length) { + def->constants = malloc(sizeof(DstValue) * scope->ccount); if (NULL == def->constants) { DST_OUT_OF_MEMORY; } - memcpy(def->constants, scope->constants.data, def->constants_length * sizeof(DstValue)); + memcpy(def->constants, scope->consts, def->constants_length * sizeof(DstValue)); } /* Copy bytecode */ @@ -547,17 +817,16 @@ static DstFuncDef *dst_compile_pop_funcdef(DstCompiler *c) { if (NULL == def->bytecode) { DST_OUT_OF_MEMORY; } - memcpy(def->bytecode, c->buffer + c->buffercount, def->bytecode_length * sizeof(uint32_t)); + memcpy(def->bytecode, c->buffer + scope->bytecode_start, def->bytecode_length * sizeof(uint32_t)); } - /* Copy source map over */ if (c->mapbuffer) { def->sourcemap = malloc(sizeof(int32_t) * 2 * def->bytecode_length); if (NULL == def->sourcemap) { DST_OUT_OF_MEMORY; } - memcpy(def->sourcemap, c->mapbuffer + 2 * c->buffercount, def->bytecode_length * 2 * sizeof(int32_t)); + memcpy(def->sourcemap, c->mapbuffer + 2 * scope->bytecode_start, def->bytecode_length * 2 * sizeof(int32_t)); } /* Reset bytecode gen */ @@ -578,21 +847,48 @@ static DstFuncDef *dst_compile_pop_funcdef(DstCompiler *c) { return def; } -/* Print a slot for debugging */ -/*static void print_slot(DstSlot *s) {*/ - /*if (!(s->flags & DST_SLOT_NOTEMPTY)) {*/ - /*printf("X");*/ - /*} else if (s->flags & DST_SLOT_CONSTANT) {*/ - /*dst_puts(dst_short_description(s->constant));*/ - /*} else if (s->envindex > 0) {*/ - /*printf("UP%d[%d]", s->envindex, s->index);*/ - /*} else {*/ - /*printf("%d", s->index);*/ - /*}*/ -/*}*/ +/* Merge an environment */ + + + +/* Load an environment */ +void dst_compile_loadenv(DstCompiler *c, DstValue env) { + int32_t count, cap; + const DstValue *hmap; + DstValue defs = dst_get(env, dst_csymbolv("defs")); + /*DstValue vars = dst_get(env, dst_csymbol("vars"));*/ + /* TODO - add global vars via single element arrays. */ + if (dst_hashtable_view(defs, &hmap, &count, &cap)) { + DstScope *scope = dst_compile_topscope(c); + int32_t i; + for (i = 0; i < cap; i += 2) { + const uint8_t *sym; + if (!dst_checktype(hmap[i], DST_SYMBOL)) continue; + sym = dst_unwrap_symbol(hmap[i]); + slotsym(scope, sym, dst_compile_constantslot(hmap[i+1])); + } + } +} + +/* Initialize a compiler */ +static void dst_compile_init(DstCompiler *c) { + c->scopecount = 0; + c->scopecap = 0; + c->scopes = NULL; + c->buffercap = 0; + c->buffercount = 0; + c->buffer = NULL; + c->mapbuffer = NULL; + c->recursion_guard = DST_RECURSION_GUARD; + + /* Push an empty function scope. This will be the global scope. */ + dst_compile_scope(c, 0); + + dst_compile_topscope(c)->flags |= DST_SCOPE_TOP; +} /* Deinitialize a compiler struct */ -static void dst_compile_cleanup(DstCompiler *c) { +static void dst_compile_deinit(DstCompiler *c) { while (c->scopecount) dst_compile_popscope(c); free(c->scopes); @@ -601,55 +897,64 @@ static void dst_compile_cleanup(DstCompiler *c) { c->buffer = NULL; c->mapbuffer = NULL; c->scopes = NULL; + c->env = dst_wrap_nil(); } -DstCompileResults dst_compile(DstCompileOptions opts) { - DstCompiler c; +/* Compile a single form */ +DstCompileResults dst_compile_one(DstCompiler *c, DstCompileOptions opts) { DstFormOptions fopts; - DstSlot *s; + DstSlot s; - if (setjmp(c.on_error)) { - c.results.status = DST_COMPILE_ERROR; - dst_compile_cleanup(&c); - c.results.funcdef = NULL; - return c.results; + /* Ensure only one scope */ + while (c->scopecount > 1) + dst_compile_popscope(c); + + if (setjmp(c->on_error)) { + c->results.status = DST_COMPILE_ERROR; + c->results.funcdef = NULL; + return c->results; } - /* Initialize the compiler struct */ - c.scopecount = 0; - c.scopecap = 0; - c.scopes = NULL; - c.buffercap = 0; - c.buffercount = 0; - c.buffer = NULL; - c.mapbuffer = NULL; - c.recursion_guard = 1024; - /* Push a function scope */ - dst_compile_scope(&c, 1); + dst_compile_scope(c, 1); - fopts.compiler = &c; + /* Set the global environment */ + c->env = opts.env; + + fopts.compiler = c; fopts.sourcemap = opts.sourcemap; fopts.flags = DST_FOPTS_TAIL | DST_SLOTTYPE_ANY; - fopts.hint = 0; + fopts.hint = dst_compile_constantslot(dst_wrap_nil()); fopts.x = opts.source; /* Compile the value */ s = dst_compile_value(fopts); - c.results.funcdef = dst_compile_pop_funcdef(&c); - c.results.status = DST_COMPILE_OK; + c->results.funcdef = dst_compile_pop_funcdef(c); + c->results.status = DST_COMPILE_OK; - dst_compile_cleanup(&c); + return c->results; +} - return c.results; +/* Compile a form. */ +DstCompileResults dst_compile(DstCompileOptions opts) { + DstCompiler c; + DstCompileResults res; + + dst_compile_init(&c); + + res = dst_compile_one(&c, opts); + + dst_compile_deinit(&c); + + return res; } DstFunction *dst_compile_func(DstCompileResults res) { if (res.status != DST_COMPILE_OK) { return NULL; } - DstFunction *func = dst_alloc(DST_MEMORY_FUNCTION, sizeof(DstFunction)); + DstFunction *func = dst_gcalloc(DST_MEMORY_FUNCTION, sizeof(DstFunction)); func->def = res.funcdef; func->envs = NULL; return func; diff --git a/core/compile.h b/core/compile.h index 6c88d0a2..acb6809b 100644 --- a/core/compile.h +++ b/core/compile.h @@ -39,7 +39,8 @@ typedef struct DstCFunctionOptimizer DstCFunctionOptimizer; #define DST_SLOT_CONSTANT 0x10000 #define DST_SLOT_NAMED 0x20000 #define DST_SLOT_MUTABLE 0x40000 -#define DST_SLOT_NOTEMPTY 0x80000 +#define DST_SLOT_REF 0x80000 +/* Needed for handling single element arrays as global vars. */ #define DST_SLOTTYPE_ANY 0xFFFF @@ -62,33 +63,35 @@ struct DstSlot { * var * varset * do + * apply (overloaded with normal function) */ #define DST_SCOPE_FUNCTION 1 #define DST_SCOPE_LASTSLOT 2 #define DST_SCOPE_FIRSTSLOT 4 #define DST_SCOPE_ENV 8 - -/* Hold a bunch of slots together */ -typedef struct DstSlotPool DstSlotPool; -struct DstSlotPool { - DstSlot *s; - int32_t count; - int32_t cap; - int32_t free; -}; +#define DST_SCOPE_TOP 16 /* A lexical scope during compilation */ struct DstScope { - int32_t level; - DstArray constants; /* Constants for the funcdef */ - DstTable constantrev; /* Map constants -> constant inidices */ - DstTable symbols; /* Map symbols -> Slot pointers */ - /* Hold all slots in use. Data structures that store - * slots should link them to this datatstructure */ - DstSlotPool slots; - DstSlotPool unorderedslots; + /* Constants for this funcdef */ + int32_t ccount; + int32_t ccap; + DstValue *consts; + + /* Map of symbols to slots. Use a simple linear scan for symbols. */ + int32_t symcap; + int32_t symcount; + struct { + const uint8_t *sym; + DstSlot slot; + } *syms; + + /* Bit vector with allocated slot indices. Used to allocate new slots */ + uint32_t *slots; + int32_t scap; + int32_t smax; /* Referenced closure environents. The values at each index correspond * to which index to get the environment from in the parent. The enironment @@ -116,11 +119,14 @@ struct DstCompiler { uint32_t *buffer; int32_t *mapbuffer; + /* Hold the environment */ + DstValue env; + DstCompileResults results; }; #define DST_FOPTS_TAIL 0x10000 -#define DST_FOPTS_FORCESLOT 0x20000 +#define DST_FOPTS_HINT 0x20000 /* Compiler state */ struct DstFormOptions { @@ -128,7 +134,7 @@ struct DstFormOptions { DstValue x; const DstValue *sourcemap; uint32_t flags; /* bit set of accepted primitive types */ - int32_t hint; + DstSlot hint; }; /* A grouping of optimizations on a cfunction given certain conditions @@ -149,25 +155,18 @@ extern DstCFunctionOptimizer dst_compiler_optimizers[255]; /* An array of special forms */ extern DstSpecial dst_compiler_specials[16]; -void dst_compile_slotpool_init(DstSlotPool *pool); -void dst_compile_slotpool_deinit(DstSlotPool *pool); -DstSlot *dst_compile_slotpool_alloc(DstSlotPool *pool); -void dst_compile_slotpool_extend(DstSlotPool *pool, int32_t extra); -void dst_compile_slotpool_free(DstSlotPool *pool, DstSlot *s); -void dst_compile_slotpool_freeindex(DstSlotPool *pool, int32_t index); - /* Dispatch to correct form compiler */ -DstSlot *dst_compile_value(DstFormOptions opts); +DstSlot dst_compile_value(DstFormOptions opts); /* Compile special forms */ -DstSlot *dst_compile_do(DstFormOptions opts, int32_t argn, const DstValue *argv); -DstSlot *dst_compile_fn(DstFormOptions opts, int32_t argn, const DstValue *argv); -DstSlot *dst_compile_cond(DstFormOptions opts, int32_t argn, const DstValue *argv); -DstSlot *dst_compile_while(DstFormOptions opts, int32_t argn, const DstValue *argv); -DstSlot *dst_compile_quote(DstFormOptions opts, int32_t argn, const DstValue *argv); -DstSlot *dst_compile_def(DstFormOptions opts, int32_t argn, const DstValue *argv); -DstSlot *dst_compile_var(DstFormOptions opts, int32_t argn, const DstValue *argv); -DstSlot *dst_compile_varset(DstFormOptions opts, int32_t argn, const DstValue *argv); +DstSlot dst_compile_do(DstFormOptions opts, int32_t argn, const DstValue *argv); +DstSlot dst_compile_fn(DstFormOptions opts, int32_t argn, const DstValue *argv); +DstSlot dst_compile_cond(DstFormOptions opts, int32_t argn, const DstValue *argv); +DstSlot dst_compile_while(DstFormOptions opts, int32_t argn, const DstValue *argv); +DstSlot dst_compile_quote(DstFormOptions opts, int32_t argn, const DstValue *argv); +DstSlot dst_compile_def(DstFormOptions opts, int32_t argn, const DstValue *argv); +DstSlot dst_compile_var(DstFormOptions opts, int32_t argn, const DstValue *argv); +DstSlot dst_compile_varset(DstFormOptions opts, int32_t argn, const DstValue *argv); /****************************************************/ @@ -183,14 +182,13 @@ DstFormOptions dst_compile_getopts_value(DstFormOptions opts, DstValue key); void dst_compile_scope(DstCompiler *c, int newfn); void dst_compile_popscope(DstCompiler *c); -DstSlot *dst_compile_constantslot(DstCompiler *c, DstValue x); -void dst_compile_freeslot(DstCompiler *c, DstSlot *slot); +DstSlot dst_compile_constantslot(DstValue x); +void dst_compile_freeslot(DstCompiler *c, DstSlot slot); /* Search for a symbol */ -DstSlot *dst_compile_resolve(DstCompiler *c, const DstValue *sourcemap, const uint8_t *sym); +DstSlot dst_compile_resolve(DstCompiler *c, const DstValue *sourcemap, const uint8_t *sym); /* Emit instructions. */ - void dst_compile_emit(DstCompiler *c, const DstValue *sourcemap, uint32_t instr); #endif diff --git a/core/fiber.c b/core/fiber.c index 148bfc0c..d92af6f0 100644 --- a/core/fiber.c +++ b/core/fiber.c @@ -21,10 +21,11 @@ */ #include +#include "gc.h" /* Initialize a new fiber */ DstFiber *dst_fiber(int32_t capacity) { - DstFiber *fiber = dst_alloc(DST_MEMORY_FIBER, sizeof(DstFiber)); + DstFiber *fiber = dst_gcalloc(DST_MEMORY_FIBER, sizeof(DstFiber)); fiber->capacity = capacity; if (capacity) { DstValue *data = malloc(sizeof(DstValue) * capacity); diff --git a/core/gc.c b/core/gc.c index fb043efe..9cdf37fd 100644 --- a/core/gc.c +++ b/core/gc.c @@ -22,12 +22,18 @@ #include #include "symcache.h" +#include "gc.h" /* GC State */ void *dst_vm_blocks; uint32_t dst_vm_memory_interval; uint32_t dst_vm_next_collection; +/* Roots */ +DstValue *dst_vm_roots; +uint32_t dst_vm_root_count; +uint32_t dst_vm_root_capacity; + /* Helpers for marking the various gc types */ static void dst_mark_funcenv(DstFuncEnv *env); static void dst_mark_funcdef(DstFuncDef *def); @@ -58,46 +64,6 @@ void dst_mark(DstValue x) { } } -/* Pin a value. This prevents a value from being garbage collected. - * Needed if the valueis not accesible to the garbage collector, but - * still in use by the program. For example, a c function that - * creates a table, and then runs the garbage collector without - * ever saving the table anywhere (except on the c stack, which - * the gc cannot inspect). */ -void dst_pin(DstValue x) { - switch (dst_type(x)) { - default: break; - case DST_STRING: - case DST_SYMBOL: dst_pin_string(dst_unwrap_string(x)); break; - case DST_FUNCTION: dst_pin_function(dst_unwrap_function(x)); break; - case DST_ARRAY: dst_pin_array(dst_unwrap_array(x)); break; - case DST_TABLE: dst_pin_table(dst_unwrap_table(x)); break; - case DST_STRUCT: dst_pin_struct(dst_unwrap_struct(x)); break; - case DST_TUPLE: dst_pin_tuple(dst_unwrap_tuple(x)); break; - case DST_BUFFER: dst_pin_buffer(dst_unwrap_buffer(x)); break; - case DST_FIBER: dst_pin_fiber(dst_unwrap_fiber(x)); break; - case DST_USERDATA: dst_pin_userdata(dst_unwrap_pointer(x)); break; - } -} - -/* Unpin a value. This enables the GC to collect the value's - * memory again. */ -void dst_unpin(DstValue x) { - switch (dst_type(x)) { - default: break; - case DST_STRING: - case DST_SYMBOL: dst_unpin_string(dst_unwrap_string(x)); break; - case DST_FUNCTION: dst_unpin_function(dst_unwrap_function(x)); break; - case DST_ARRAY: dst_unpin_array(dst_unwrap_array(x)); break; - case DST_TABLE: dst_unpin_table(dst_unwrap_table(x)); break; - case DST_STRUCT: dst_unpin_struct(dst_unwrap_struct(x)); break; - case DST_TUPLE: dst_unpin_tuple(dst_unwrap_tuple(x)); break; - case DST_BUFFER: dst_unpin_buffer(dst_unwrap_buffer(x)); break; - case DST_FIBER: dst_unpin_fiber(dst_unwrap_fiber(x)); break; - case DST_USERDATA: dst_unpin_userdata(dst_unwrap_pointer(x)); break; - } -} - static void dst_mark_string(const uint8_t *str) { dst_gc_mark(dst_string_raw(str)); } @@ -298,7 +264,7 @@ void dst_sweep() { } /* Allocate some memory that is tracked for garbage collection */ -void *dst_alloc(DstMemoryType type, size_t size) { +void *dst_gcalloc(DstMemoryType type, size_t size) { DstGCMemoryHeader *mdata; size_t total = size + sizeof(DstGCMemoryHeader); @@ -326,12 +292,47 @@ void *dst_alloc(DstMemoryType type, size_t size) { /* Run garbage collection */ void dst_collect() { + uint32_t i; if (dst_vm_fiber) dst_mark_fiber(dst_vm_fiber); + for (i = 0; i < dst_vm_root_count; i++) + dst_mark(dst_vm_roots[i]); dst_sweep(); dst_vm_next_collection = 0; } +/* Add a root value to the GC. This prevents the GC from removing a value + * and all of its children. If gcroot is called on a value n times, unroot + * must also be called n times to remove it as a gc root. */ +void dst_gcroot(DstValue root) { + uint32_t newcount = dst_vm_root_count + 1; + if (newcount > dst_vm_root_capacity) { + uint32_t newcap = 2 * newcount; + dst_vm_roots = realloc(dst_vm_roots, sizeof(DstValue) * newcap); + if (NULL == dst_vm_roots) { + DST_OUT_OF_MEMORY; + } + dst_vm_root_capacity = newcap; + } + dst_vm_roots[dst_vm_root_count] = root; + dst_vm_root_count = newcount; +} + +/* Remove a root value from the GC. This allows the gc to potentially reclaim + * a value and all its children. */ +int dst_gcunroot(DstValue root) { + DstValue *vtop = dst_vm_roots + dst_vm_root_count; + DstValue *v = dst_vm_roots; + /* Search from top to bottom as access is most likely LIFO */ + for (v = dst_vm_roots; v < vtop; v++) { + if (dst_equals(root, *v)) { + *v = dst_vm_roots[--dst_vm_root_count]; + return 1; + } + } + return 0; +} + /* Free all allocated memory */ void dst_clear_memory() { DstGCMemoryHeader *current = dst_vm_blocks; diff --git a/core/gc.h b/core/gc.h new file mode 100644 index 00000000..f15ffbcc --- /dev/null +++ b/core/gc.h @@ -0,0 +1,72 @@ +/* +* Copyright (c) 2017 Calvin Rose +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to +* deal in the Software without restriction, including without limitation the +* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +* sell copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +* IN THE SOFTWARE. +*/ + +#ifndef DST_GC_H +#define DST_GC_H + +#include + +/* The metadata header associated with an allocated block of memory */ +#define dst_gc_header(mem) ((DstGCMemoryHeader *)(mem) - 1) + +#define DST_MEM_TYPEBITS 0xFF +#define DST_MEM_REACHABLE 0x100 +#define DST_MEM_DISABLED 0x200 + +#define dst_gc_settype(m, t) ((dst_gc_header(m)->flags |= (0xFF & (t)))) +#define dst_gc_type(m) (dst_gc_header(m)->flags & 0xFF) + +#define dst_gc_mark(m) (dst_gc_header(m)->flags |= DST_MEM_REACHABLE) +#define dst_gc_unmark(m) (dst_gc_header(m)->flags &= ~DST_MEM_COLOR) +#define dst_gc_reachable(m) (dst_gc_header(m)->flags & DST_MEM_REACHABLE) + + +/* Memory header struct. Node of a linked list of memory blocks. */ +typedef struct DstGCMemoryHeader DstGCMemoryHeader; +struct DstGCMemoryHeader { + DstGCMemoryHeader *next; + uint32_t flags; +}; + +/* Memory types for the GC. Different from DstType to include funcenv and funcdef. */ +typedef enum DstMemoryType DstMemoryType; +enum DstMemoryType { + DST_MEMORY_NONE, + DST_MEMORY_STRING, + DST_MEMORY_SYMBOL, + DST_MEMORY_ARRAY, + DST_MEMORY_TUPLE, + DST_MEMORY_TABLE, + DST_MEMORY_STRUCT, + DST_MEMORY_FIBER, + DST_MEMORY_BUFFER, + DST_MEMORY_FUNCTION, + DST_MEMORY_USERDATA, + DST_MEMORY_FUNCENV, + DST_MEMORY_FUNCDEF +}; + +/* To allocate collectable memory, one must calk dst_alloc, initialize the memory, + * and then call when dst_enablegc when it is initailize and reachable by the gc (on the DST stack) */ +void *dst_gcalloc(DstMemoryType type, size_t size); + +#endif diff --git a/core/parse.c b/core/parse.c index a4dcc75b..643550f5 100644 --- a/core/parse.c +++ b/core/parse.c @@ -51,20 +51,94 @@ static int is_whitespace(uint8_t c) { || c == ','; } -/* Check if a character is a valid symbol character */ -/* TODO - allow utf8 - shouldn't be difficult, err on side - * of inclusivity */ -static int is_symbol_char(uint8_t c) { +/* Code gen + +printf("static uint32_t symchars[8] = {\n\t"); +for (int i = 0; i < 256; i += 32) { + uint32_t block = 0; + for (int j = 0; j < 32; j++) { + block |= is_symbol_char_gen(i + j) << j; + } + printf("0x%08x%s", block, (i == (256 - 32)) ? "" : ", "); +} +printf("\n};\n"); + +static int is_symbol_char_gen(uint8_t c) { if (c >= 'a' && c <= 'z') return 1; if (c >= 'A' && c <= 'Z') return 1; - if (c >= '0' && c <= ':') return 1; - if (c >= '<' && c <= '@') return 1; - if (c >= '*' && c <= '/') return 1; - if (c >= '$' && c <= '&') return 1; - if (c == '_') return 1; - if (c == '^') return 1; - if (c == '!') return 1; - return 0; + if (c >= '0' && c <= '9') return 1; + return (c == '!' || + c == '$' || + c == '&' || + c == '*' || + c == '+' || + c == '-' || + c == '.' || + c == '/' || + c == ':' || + c == '<' || + c == '=' || + c == '>' || + c == '@' || + c == '\\' || + c == '^' || + c == '_' || + c == '~' || + c == '|'); +} + +The table contains 256 bits, where each bit is 1 +if the corresponding ascci code is a symbol char, and 0 +if not. */ +static uint32_t symchars[256] = { + 0x00000000, 0x77ffec52, 0xd7ffffff, 0x57fffffe, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff +}; + +/* Check if a character is a valid symbol character */ +/* TODO - allow utf8 - shouldn't be difficult, err on side + * of inclusivity + * symbol chars are A-Z, a-z, 0-9, or one of !$&*+-./:<=>@\^_~| */ +static int is_symbol_char(uint8_t c) { + return symchars[c >> 5] & (1 << (c & 0x1F)); +} + +/* Validate some utf8. Useful for identifiers. Only validates + * the encoding, does not check for valid codepoints (they + * are less well defined than the encoding). */ +static int valid_utf8(const uint8_t *str, int32_t len) { + int32_t i = 0; + int32_t j; + while (i < len) { + int32_t nexti; + uint8_t c = str[i]; + + /* Check the number of bytes in code point */ + if (c < 0x80) nexti = i + 1; + else if ((c >> 5) == 0x06) nexti = i + 2; + else if ((c >> 4) == 0x0E) nexti = i + 3; + else if ((c >> 3) == 0x1E) nexti = i + 4; + /* Don't allow 5 or 6 byte code points */ + else return 0; + + /* No overflow */ + if (nexti > len) + return 0; + + /* Ensure trailing bytes are well formed (10XX XXXX) */ + for (j = i + 1; j < nexti; j++) { + if ((str[j] >> 6) != 2) + return 0; + } + + /* Check for overlong encodings */ + if ((nexti == i + 2) && str[i] < 0xC2) return 0; + if ((str[i] == 0xE0) && str[i + 1] < 0xA0) return 0; + if ((str[i] == 0xF0) && str[i + 1] < 0x90) return 0; + + i = nexti; + } + return 1; } /* Get hex digit from a letter */ @@ -172,6 +246,8 @@ static const uint8_t *parse_recur( if (*src >= '0' && *src <= '9') { goto sym_nodigits; } else { + if (!valid_utf8(src, tokenend - src)) + goto invalid_utf8; ret = dst_symbolv(src, tokenend - src); } } @@ -398,6 +474,11 @@ static const uint8_t *parse_recur( args->status = DST_PARSE_ERROR; return src; + invalid_utf8: + args->errmsg = "identifier is not valid utf-8"; + args->status = DST_PARSE_ERROR; + return src; + too_much_recur: args->errmsg = "recursed too deeply in parsing"; args->status = DST_PARSE_ERROR; diff --git a/core/stl.c b/core/stl.c new file mode 100644 index 00000000..b5e13378 --- /dev/null +++ b/core/stl.c @@ -0,0 +1,866 @@ +/* +* Copyright (c) 2017 Calvin Rose +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to +* deal in the Software without restriction, including without limitation the +* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +* sell copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +* IN THE SOFTWARE. +*/ + +#include + +#define MAKE_BINOP(name, op)\ +static DstValue dst_stl_binop_##name(DstValue lhs, DstValue rhs) {\ + if (dst_checktype(lhs, DST_INTEGER))\ + if (dst_checktype(rhs, DST_INTEGER))\ + return dst_wrap_integer(dst_unwrap_integer(lhs) op dst_unwrap_integer(rhs));\ + else if (dst_checktype(rhs, DST_REAL))\ + return dst_wrap_real(dst_unwrap_integer(lhs) op dst_unwrap_real(lhs));\ + else\ + return dst_wrap_nil();\ + else if (dst_checktype(lhs, DST_REAL))\ + if (dst_checktype(rhs, DST_INTEGER))\ + return dst_wrap_real(dst_unwrap_real(lhs) op dst_unwrap_integer(rhs));\ + else if (dst_checktype(rhs, DST_REAL))\ + return dst_wrap_real(dst_unwrap_real(lhs) op dst_unwrap_real(rhs));\ + else\ + return dst_wrap_nil();\ + else\ + return dst_wrap_nil();\ +} + +#define SIMPLE_ACCUM_FUNCTION(name, op)\ +MAKE_BINOP(name, op)\ +int dst_stl_##name(DstValue *argv, int32_t argn) {\ + DstValue lhs, rhs;\ + int32_t j, count;\ + lhs = argv[0];\ + rhs = argv[1];\ + for (j = 1; j < argn; ++j) {\ + lhs = dst_stl_binop_##name(lhs, rhs);\ + }\ + if (dst_checktype(lhs, DST_NIL))\ + dst_c_throwc(vm, "expected number");\ + dst_vm_fiber->ret = lhs; + return 0;\ +} + +SIMPLE_ACCUM_FUNCTION(add, +) +SIMPLE_ACCUM_FUNCTION(mul, *) +SIMPLE_ACCUM_FUNCTION(sub, -) + +/* Detect division by zero */ +MAKE_BINOP(div, /) +int dst_stl_div(Dst *vm) { + DstValue lhs, rhs; + uint32_t j, count; + count = dst_args(vm); + lhs = dst_arg(vm, 0); + for (j = 1; j < count; ++j) { + rhs = dst_arg(vm, j); + if (lhs.type == DST_INTEGER && rhs.type == DST_INTEGER && rhs.as.integer == 0) + dst_c_throwc(vm, "cannot integer divide by 0"); + lhs = dst_stl_binop_div(lhs, rhs); + } + if (lhs.type == DST_NIL) + dst_c_throwc(vm, "expected number"); + dst_c_return(vm, lhs); +} + +#undef SIMPLE_ACCUM_FUNCTION + +#define BITWISE_FUNCTION(name, op) \ +int dst_stl_##name(Dst *vm) {\ + DstValue ret;\ + uint32_t i, count;\ + count = dst_args(vm);\ + ret = dst_arg(vm, 0);\ + if (ret.type != DST_INTEGER) {\ + dst_c_throwc(vm, "expected integer");\ + }\ + if (count < 2) {\ + dst_c_return(vm, ret);\ + }\ + for (i = 1; i < count; ++i) {\ + DstValue next = dst_arg(vm, i);\ + if (next.type != DST_INTEGER) {\ + dst_c_throwc(vm, "expected integer");\ + }\ + ret.as.integer = ret.as.integer op next.as.integer;\ + }\ + dst_c_return(vm, ret);\ +} + +BITWISE_FUNCTION(band, &) +BITWISE_FUNCTION(bor, |) +BITWISE_FUNCTION(bxor, ^) +BITWISE_FUNCTION(blshift, <<) +BITWISE_FUNCTION(brshift, >>) + +#undef BITWISE_FUNCTION + +int dst_stl_bnot(Dst *vm) { + DstValue in = dst_arg(vm, 0); + uint32_t count = dst_args(vm); + if (count != 1 || in.type != DST_INTEGER) { + dst_c_throwc(vm, "expected 1 integer argument"); + } + in.as.integer = ~in.as.integer; + dst_c_return(vm, in); +} + +#define COMPARE_FUNCTION(name, check)\ +int dst_stl_##name(Dst *vm) {\ + DstValue ret;\ + uint32_t i, count;\ + count = dst_args(vm);\ + ret.as.boolean = 1;\ + ret.type = DST_BOOLEAN;\ + if (count < 2) {\ + dst_c_return(vm, ret);\ + }\ + for (i = 1; i < count; ++i) {\ + DstValue lhs = dst_arg(vm, i - 1);\ + DstValue rhs = dst_arg(vm, i);\ + if (!(check)) {\ + ret.as.boolean = 0;\ + break;\ + }\ + }\ + dst_c_return(vm, ret);\ +} + +COMPARE_FUNCTION(lessthan, dst_compare(lhs, rhs) < 0) +COMPARE_FUNCTION(greaterthan, dst_compare(lhs, rhs) > 0) +COMPARE_FUNCTION(equal, dst_equals(lhs, rhs)) +COMPARE_FUNCTION(notequal, !dst_equals(lhs, rhs)) +COMPARE_FUNCTION(lessthaneq, dst_compare(lhs, rhs) <= 0) +COMPARE_FUNCTION(greaterthaneq, dst_compare(lhs, rhs) >= 0) + +#undef COMPARE_FUNCTION + +/* Boolean not */ +int dst_stl_not(Dst *vm) { + dst_c_return(vm, boolean(!dst_truthy(dst_arg(vm, 0)))); +} + +/****/ +/* Core */ +/****/ + +/* Empty a mutable datastructure */ +int dst_stl_clear(Dst *vm) { + DstValue x = dst_arg(vm, 0); + switch (x.type) { + default: + dst_c_throwc(vm, "cannot clear"); + case DST_ARRAY: + x.as.array->count = 0; + break; + case DST_BUFFER: + x.as.buffer->count = 0; + break; + case DST_TABLE: + dst_table_clear(x.as.table); + break; + } + dst_c_return(vm, x); +} + +/* Get length of object */ +int dst_stl_length(Dst *vm) { + dst_set_integer(vm, 0, dst_length(vm, 0)); + dst_return(vm, 0); + return 0; +} + +/* Get hash of a value */ +int dst_stl_hash(Dst *vm) { + dst_set_integer(vm, 0, dst_hash(vm, 0);); + dst_return(vm, 0); + return 0; +} + +/* Convert to integer */ +int dst_stl_to_int(Dst *vm) { + DstValue x = dst_arg(vm, 0); + if (x.type == DST_INTEGER) dst_c_return(vm, x); + if (x.type == DST_REAL) + dst_c_return(vm, integer((DstInteger) x.as.real)); + else + dst_c_throwc(vm, "expected number"); +} + +/* Convert to integer */ +int dst_stl_to_real(Dst *vm) { + DstValue x = dst_arg(vm, 0); + if (x.type == DST_REAL) dst_c_return(vm, x); + if (x.type == DST_INTEGER) + dst_c_return(vm, dst_wrap_real((DstReal) x.as.integer)); + else + dst_c_throwc(vm, "expected number"); +} + +/* Get a slice of a sequence */ +int dst_stl_slice(Dst *vm) { + uint32_t count = dst_args(vm); + int32_t from, to; + DstValue x; + const DstValue *data; + const uint8_t *cdata; + uint32_t length; + uint32_t newlength; + DstInteger num; + + /* Get data */ + x = dst_arg(vm, 0); + if (!dst_seq_view(x, &data, &length) && + !dst_chararray_view(x, &cdata, &length)) { + dst_c_throwc(vm, "expected array/tuple/buffer/symbol/string"); + } + + /* Get from index */ + if (count < 2) { + from = 0; + } else { + if (!dst_check_integer(vm, 1, &num)) + dst_c_throwc(vm, DST_EXPECTED_INTEGER); + from = dst_startrange(num, length); + } + + /* Get to index */ + if (count < 3) { + to = length; + } else { + if (!dst_check_integer(vm, 2, &num)) + dst_c_throwc(vm, DST_EXPECTED_INTEGER); + to = dst_endrange(num, length); + } + + /* Check from bad bounds */ + if (from < 0 || to < 0 || to < from) + dst_c_throwc(vm, "index out of bounds"); + + /* Build slice */ + newlength = to - from; + if (x.type == DST_TUPLE) { + DstValue *tup = dst_tuple_begin(vm, newlength); + dst_memcpy(tup, data + from, newlength * sizeof(DstValue)); + dst_c_return(vm, dst_wrap_tuple(dst_tuple_end(vm, tup))); + } else if (x.type == DST_ARRAY) { + DstArray *arr = dst_array(vm, newlength); + arr->count = newlength; + dst_memcpy(arr->data, data + from, newlength * sizeof(DstValue)); + dst_c_return(vm, dst_wrap_array(arr)); + } else if (x.type == DST_STRING) { + dst_c_return(vm, dst_wrap_string(dst_string_b(vm, x.as.string + from, newlength))); + } else if (x.type == DST_SYMBOL) { + dst_c_return(vm, dst_wrap_symbol(dst_string_b(vm, x.as.string + from, newlength))); + } else { /* buffer */ + DstBuffer *b = dst_buffer(vm, newlength); + dst_memcpy(b->data, x.as.buffer->data, newlength); + b->count = newlength; + dst_c_return(vm, dst_wrap_buffer(b)); + } +} + +/* Create array */ +int dst_stl_array(Dst *vm) { + uint32_t i; + uint32_t count = dst_args(vm); + DstArray *array = dst_array(vm, count); + for (i = 0; i < count; ++i) + array->data[i] = dst_arg(vm, i); + dst_c_return(vm, dst_wrap_array(array)); +} + +/* Create tuple */ +int dst_stl_tuple(Dst *vm) { + uint32_t i; + uint32_t count = dst_args(vm); + DstValue *tuple= dst_tuple_begin(vm, count); + for (i = 0; i < count; ++i) + tuple[i] = dst_arg(vm, i); + dst_c_return(vm, dst_wrap_tuple(dst_tuple_end(vm, tuple))); +} + +/* Create object */ +int dst_stl_table(Dst *vm) { + uint32_t i; + uint32_t count = dst_args(vm); + DstTable *table; + if (count % 2 != 0) + dst_c_throwc(vm, "expected even number of arguments"); + table = dst_table(vm, 4 * count); + for (i = 0; i < count; i += 2) + dst_table_put(vm, table, dst_arg(vm, i), dst_arg(vm, i + 1)); + dst_c_return(vm, dst_wrap_table(table)); +} + +/* Create struct */ +int dst_stl_struct(Dst *vm) { + uint32_t i; + uint32_t count = dst_args(vm); + DstValue *st; + if (count % 2 != 0) + dst_c_throwc(vm, "expected even number of arguments"); + st = dst_struct_begin(vm, count / 2); + for (i = 0; i < count; i += 2) + dst_struct_put(st, dst_arg(vm, i), dst_arg(vm, i + 1)); + dst_c_return(vm, dst_wrap_struct(dst_struct_end(vm, st))); +} + +/* Create a buffer */ +int dst_stl_buffer(Dst *vm) { + uint32_t i, count; + const uint8_t *dat; + uint32_t slen; + DstBuffer *buf = dst_buffer(vm, 10); + count = dst_args(vm); + for (i = 0; i < count; ++i) { + if (dst_chararray_view(dst_arg(vm, i), &dat, &slen)) + dst_buffer_append(vm, buf, dat, slen); + else + dst_c_throwc(vm, DST_EXPECTED_STRING); + } + dst_c_return(vm, dst_wrap_buffer(buf)); +} + +/* Create a string */ +int dst_stl_string(Dst *vm) { + uint32_t j; + uint32_t count = dst_args(vm); + uint32_t length = 0; + uint32_t index = 0; + uint8_t *str; + const uint8_t *dat; + uint32_t slen; + /* Find length and assert string arguments */ + for (j = 0; j < count; ++j) { + if (!dst_chararray_view(dst_arg(vm, j), &dat, &slen)) { + DstValue newarg; + dat = dst_to_string(vm, dst_arg(vm, j)); + slen = dst_string_length(dat); + newarg.type = DST_STRING; + newarg.as.string = dat; + dst_set_arg(vm, j, newarg); + } + length += slen; + } + /* Make string */ + str = dst_string_begin(vm, length); + for (j = 0; j < count; ++j) { + dst_chararray_view(dst_arg(vm, j), &dat, &slen); + dst_memcpy(str + index, dat, slen); + index += slen; + } + dst_c_return(vm, dst_wrap_string(dst_string_end(vm, str))); +} + +/* Create a symbol */ +int dst_stl_symbol(Dst *vm) { + int ret = dst_stl_string(vm); + if (ret == DST_RETURN_OK) { + vm->ret.type = DST_SYMBOL; + } + return ret; +} + +/* Create a thread */ +int dst_stl_thread(Dst *vm) { + DstThread *t; + DstValue callee = dst_arg(vm, 0); + DstValue parent = dst_arg(vm, 1); + DstValue errorParent = dst_arg(vm, 2); + t = dst_thread(vm, callee, 10); + if (callee.type != DST_FUNCTION && callee.type != DST_CFUNCTION) + dst_c_throwc(vm, "expected function in thread constructor"); + if (parent.type == DST_THREAD) { + t->parent = parent.as.thread; + } else if (parent.type != DST_NIL) { + dst_c_throwc(vm, "expected thread/nil as parent"); + } else { + t->parent = vm->thread; + } + dst_c_return(vm, dst_wrap_thread(t)); +} + +/* Get current thread */ +int dst_stl_current(Dst *vm) { + dst_c_return(vm, dst_wrap_thread(vm->thread)); +} + +/* Get parent of a thread */ +/* TODO - consider implications of this function + * for sandboxing */ +int dst_stl_parent(Dst *vm) { + DstThread *t; + if (!dst_check_thread(vm, 0, &t)) + dst_c_throwc(vm, "expected thread"); + if (t->parent == NULL) + dst_c_return(vm, dst_wrap_nil()); + dst_c_return(vm, dst_wrap_thread(t->parent)); +} + +/* Get the status of a thread */ +int dst_stl_status(Dst *vm) { + DstThread *t; + const char *cstr; + if (!dst_check_thread(vm, 0, &t)) + dst_c_throwc(vm, "expected thread"); + switch (t->status) { + case DST_THREAD_PENDING: + cstr = "pending"; + break; + case DST_THREAD_ALIVE: + cstr = "alive"; + break; + case DST_THREAD_DEAD: + cstr = "dead"; + break; + case DST_THREAD_ERROR: + cstr = "error"; + break; + } + dst_c_return(vm, dst_string_cv(vm, cstr)); +} + +/* Associative get */ +int dst_stl_get(Dst *vm) { + DstValue ret; + uint32_t count; + const char *err; + count = dst_args(vm); + if (count != 2) + dst_c_throwc(vm, "expects 2 arguments"); + err = dst_get(dst_arg(vm, 0), dst_arg(vm, 1), &ret); + if (err != NULL) + dst_c_throwc(vm, err); + else + dst_c_return(vm, ret); +} + +/* Associative set */ +int dst_stl_set(Dst *vm) { + uint32_t count; + const char *err; + count = dst_args(vm); + if (count != 3) + dst_c_throwc(vm, "expects 3 arguments"); + err = dst_set(vm, dst_arg(vm, 0), dst_arg(vm, 1), dst_arg(vm, 2)); + if (err != NULL) + dst_c_throwc(vm, err); + else + dst_c_return(vm, dst_arg(vm, 0)); +} + +/* Push to end of array */ +int dst_stl_push(Dst *vm) { + DstValue ds = dst_arg(vm, 0); + if (ds.type != DST_ARRAY) + dst_c_throwc(vm, "expected array"); + dst_array_push(vm, ds.as.array, dst_arg(vm, 1)); + dst_c_return(vm, ds); +} + +/* Pop from end of array */ +int dst_stl_pop(Dst *vm) { + DstValue ds = dst_arg(vm, 0); + if (ds.type != DST_ARRAY) + dst_c_throwc(vm, "expected array"); + dst_c_return(vm, dst_array_pop(ds.as.array)); +} + +/* Peek at end of array */ +int dst_stl_peek(Dst *vm) { + DstValue ds = dst_arg(vm, 0); + if (ds.type != DST_ARRAY) + dst_c_throwc(vm, "expected array"); + dst_c_return(vm, dst_array_peek(ds.as.array)); +} + +/* Ensure array capacity */ +int dst_stl_ensure(Dst *vm) { + DstValue ds = dst_arg(vm, 0); + DstValue cap = dst_arg(vm, 1); + if (ds.type != DST_ARRAY) + dst_c_throwc(vm, "expected array"); + if (cap.type != DST_INTEGER) + dst_c_throwc(vm, DST_EXPECTED_INTEGER); + dst_array_ensure(vm, ds.as.array, (uint32_t) cap.as.integer); + dst_c_return(vm, ds); +} + +/* Get next key in struct or table */ +int dst_stl_next(Dst *vm) { + DstValue ds = dst_arg(vm, 0); + DstValue key = dst_arg(vm, 1); + if (ds.type == DST_TABLE) { + dst_c_return(vm, dst_table_next(ds.as.table, key)); + } else if (ds.type == DST_STRUCT) { + dst_c_return(vm, dst_struct_next(ds.as.st, key)); + } else { + dst_c_throwc(vm, "expected table or struct"); + } +} + +/* Print values for inspection */ +int dst_stl_print(Dst *vm) { + uint32_t j, count; + count = dst_args(vm); + for (j = 0; j < count; ++j) { + uint32_t i; + const uint8_t *string = dst_to_string(vm, dst_arg(vm, j)); + uint32_t len = dst_string_length(string); + for (i = 0; i < len; ++i) + fputc(string[i], stdout); + } + fputc('\n', stdout); + return DST_RETURN_OK; +} + +/* Long description */ +int dst_stl_description(Dst *vm) { + DstValue x = dst_arg(vm, 0); + const uint8_t *buf = dst_description(vm, x); + dst_c_return(vm, dst_wrap_string(buf)); +} + +/* Short description */ +int dst_stl_short_description(Dst *vm) { + DstValue x = dst_arg(vm, 0); + const uint8_t *buf = dst_short_description(vm, x); + dst_c_return(vm, dst_wrap_string(buf)); +} + +/* Exit */ +int dst_stl_exit(Dst *vm) { + int ret; + DstValue x = dst_arg(vm, 0); + ret = x.type == DST_INTEGER ? x.as.integer : (x.type == DST_REAL ? x.as.real : 0); + exit(ret); + return DST_RETURN_OK; +} + +/* Throw error */ +int dst_stl_error(Dst *vm) { + dst_c_throw(vm, dst_arg(vm, 0)); +} + +/***/ +/* Function reflection */ +/***/ + +int dst_stl_funcenv(Dst *vm) { + DstFunction *fn; + if (!dst_check_function(vm, 0, &fn)) + dst_c_throwc(vm, "expected function"); + if (fn->env) + dst_c_return(vm, dst_wrap_funcenv(fn->env)); + else + return DST_RETURN_OK; +} + +int dst_stl_funcdef(Dst *vm) { + DstFunction *fn; + if (!dst_check_function(vm, 0, &fn)) + dst_c_throwc(vm, "expected function"); + dst_c_return(vm, dst_wrap_funcdef(fn->def)); +} + +int dst_stl_funcparent(Dst *vm) { + DstFunction *fn; + if (!dst_check_function(vm, 0, &fn)) + dst_c_throwc(vm, "expected function"); + if (fn->parent) + dst_c_return(vm, dst_wrap_function(fn->parent)); + else + return DST_RETURN_OK; +} + +int dst_stl_def(Dst *vm) { + DstValue key = dst_arg(vm, 0); + if (dst_args(vm) != 2) { + dst_c_throwc(vm, "expected 2 arguments to global-def"); + } + if (key.type != DST_STRING && key.type != DST_SYMBOL) { + dst_c_throwc(vm, "expected string/symbol as first argument"); + } + key.type = DST_SYMBOL; + dst_env_put(vm, vm->env, key, dst_arg(vm, 1)); + dst_c_return(vm, dst_arg(vm, 1)); +} + +int dst_stl_var(Dst *vm) { + DstValue key = dst_arg(vm, 0); + if (dst_args(vm) != 2) { + dst_c_throwc(vm, "expected 2 arguments to global-var"); + } + if (key.type != DST_STRING && key.type != DST_SYMBOL) { + dst_c_throwc(vm, "expected string as first argument"); + } + key.type = DST_SYMBOL; + dst_env_putvar(vm, vm->env, key, dst_arg(vm, 1)); + dst_c_return(vm, dst_arg(vm, 1)); +} + +/****/ +/* IO */ +/****/ + +/* File type definition */ +static DstUserType dst_stl_filetype = { + "stl.file", + NULL, + NULL, + NULL, + NULL +}; + +/* Open a a file and return a userdata wrapper arounf the C file API. */ +int dst_stl_open(Dst *vm) { + const uint8_t *fname = dst_to_string(vm, dst_arg(vm, 0)); + const uint8_t *fmode = dst_to_string(vm, dst_arg(vm, 1)); + FILE *f; + FILE **fp; + if (dst_args(vm) < 2 || dst_arg(vm, 0).type != DST_STRING + || dst_arg(vm, 1).type != DST_STRING) + dst_c_throwc(vm, "expected filename and filemode"); + f = fopen((const char *)fname, (const char *)fmode); + if (!f) + dst_c_throwc(vm, "could not open file"); + fp = dst_userdata(vm, sizeof(FILE *), &dst_stl_filetype); + *fp = f; + dst_c_return(vm, dst_wrap_userdata(fp)); +} + +/* Read an entire file into memory */ +int dst_stl_slurp(Dst *vm) { + DstBuffer *b; + long fsize; + FILE *f; + FILE **fp = dst_check_userdata(vm, 0, &dst_stl_filetype); + if (fp == NULL) dst_c_throwc(vm, "expected file"); + if (!dst_check_buffer(vm, 1, &b)) b = dst_buffer(vm, 10); + f = *fp; + /* Read whole file */ + fseek(f, 0, SEEK_END); + fsize = ftell(f); + fseek(f, 0, SEEK_SET); + /* Ensure buffer size */ + dst_buffer_ensure(vm, b, b->count + fsize); + fread((char *)(b->data + b->count), fsize, 1, f); + b->count += fsize; + dst_c_return(vm, dst_wrap_buffer(b)); +} + +/* Read a certain number of bytes into memory */ +int dst_stl_read(Dst *vm) { + DstBuffer *b; + FILE *f; + int64_t len; + FILE **fp = dst_check_userdata(vm, 0, &dst_stl_filetype); + if (fp == NULL) dst_c_throwc(vm, "expected file"); + if (!(dst_check_integer(vm, 1, &len))) dst_c_throwc(vm, "expected integer"); + if (!dst_check_buffer(vm, 2, &b)) b = dst_buffer(vm, 10); + f = *fp; + /* Ensure buffer size */ + dst_buffer_ensure(vm, b, b->count + len); + b->count += fread((char *)(b->data + b->count), len, 1, f) * len; + dst_c_return(vm, dst_wrap_buffer(b)); +} + +/* Write bytes to a file */ +int dst_stl_write(Dst *vm) { + FILE *f; + const uint8_t *data; + uint32_t len; + FILE **fp = dst_check_userdata(vm, 0, &dst_stl_filetype); + if (fp == NULL) dst_c_throwc(vm, "expected file"); + if (!dst_chararray_view(dst_arg(vm, 1), &data, &len)) dst_c_throwc(vm, "expected string|buffer"); + f = *fp; + fwrite(data, len, 1, f); + return DST_RETURN_OK; +} + +/* Close a file */ +int dst_stl_close(Dst *vm) { + FILE **fp = dst_check_userdata(vm, 0, &dst_stl_filetype); + if (fp == NULL) dst_c_throwc(vm, "expected file"); + fclose(*fp); + dst_c_return(vm, dst_wrap_nil()); +} + +/****/ +/* Temporary */ +/****/ + +/* Force garbage collection */ +int dst_stl_gcollect(Dst *vm) { + dst_collect(vm); + return DST_RETURN_OK; +} + +/***/ +/* Compilation */ +/***/ + +/* Generate a unique symbol */ +static int dst_stl_gensym(Dst *vm) { + DstValue source = dst_arg(vm, 0); + const uint8_t *sym = NULL; + uint32_t len; + const uint8_t *data; + if (source.type == DST_NIL) { + sym = dst_string_cu(vm, ""); + } else if (dst_chararray_view(source, &data, &len)) { + sym = dst_string_bu(vm, data, len); + } else { + dst_c_throwc(vm, "exepcted string/buffer/symbol/nil"); + } + dst_c_return(vm, dst_wrap_symbol(sym)); +} + +/* Compile a value */ +static int dst_stl_compile(Dst *vm) { + DstTable *env = vm->env; + if (dst_arg(vm, 1).type == DST_TABLE) { + env = dst_arg(vm, 1).as.table; + } + dst_c_return(vm, dst_compile(vm, env, dst_arg(vm, 0))); +} + +/* Get vm->env */ +static int dst_stl_getenv(Dst *vm) { + dst_c_return(vm, dst_wrap_table(vm->env)); +} + +/* Set vm->env */ +static int dst_stl_setenv(Dst *vm) { + DstValue newEnv = dst_arg(vm, 0); + if (newEnv.type != DST_TABLE) { + dst_c_throwc(vm, "expected table"); + } + vm->env = newEnv.as.table; + return DST_RETURN_OK; +} + +/****/ +/* Bootstraping */ +/****/ + +static const DstModuleItem std_module[] = { + /* Arithmetic */ + {"+", dst_stl_add}, + {"*", dst_stl_mul}, + {"-", dst_stl_sub}, + {"/", dst_stl_div}, + /* Comparisons */ + {"<", dst_stl_lessthan}, + {">", dst_stl_greaterthan}, + {"=", dst_stl_equal}, + {"not=", dst_stl_notequal}, + {"<=", dst_stl_lessthaneq}, + {">=", dst_stl_greaterthaneq}, + /* Bitwise arithmetic */ + {"band", dst_stl_band}, + {"bor", dst_stl_bor}, + {"bxor", dst_stl_bxor}, + {"blshift", dst_stl_blshift}, + {"brshift", dst_stl_brshift}, + {"bnot", dst_stl_bnot}, + /* IO */ + {"open", dst_stl_open}, + {"slurp", dst_stl_slurp}, + {"read", dst_stl_read}, + {"write", dst_stl_write}, + /* Compile */ + {"gensym", dst_stl_gensym}, + {"getenv", dst_stl_getenv}, + {"setenv", dst_stl_setenv}, + {"compile", dst_stl_compile}, + /* Other */ + {"not", dst_stl_not}, + {"clear", dst_stl_clear}, + {"length", dst_stl_length}, + {"hash", dst_stl_hash}, + {"integer", dst_stl_to_int}, + {"real", dst_stl_to_real}, + {"type", dst_stl_type}, + {"slice", dst_stl_slice}, + {"array", dst_stl_array}, + {"tuple", dst_stl_tuple}, + {"table", dst_stl_table}, + {"struct", dst_stl_struct}, + {"buffer", dst_stl_buffer}, + {"string", dst_stl_string}, + {"symbol", dst_stl_symbol}, + {"thread", dst_stl_thread}, + {"status", dst_stl_status}, + {"current", dst_stl_current}, + {"parent", dst_stl_parent}, + {"print", dst_stl_print}, + {"description", dst_stl_description}, + {"short-description", dst_stl_short_description}, + {"exit!", dst_stl_exit}, + {"get", dst_stl_get}, + {"set!", dst_stl_set}, + {"next", dst_stl_next}, + {"error", dst_stl_error}, + {"push!", dst_stl_push}, + {"pop!", dst_stl_pop}, + {"peek", dst_stl_peek}, + {"ensure!", dst_stl_ensure}, + {"open", dst_stl_open}, + {"slurp", dst_stl_slurp}, + {"read", dst_stl_read}, + {"write", dst_stl_write}, + {"close", dst_stl_close}, + {"funcenv", dst_stl_funcenv}, + {"funcdef", dst_stl_funcdef}, + {"funcparent", dst_stl_funcparent}, + {"gcollect", dst_stl_gcollect}, + {"global-def", dst_stl_def}, + {"global-var", dst_stl_var}, + {NULL, NULL} +}; + +/* Load stl library into the current environment. Create stl module object + * only if it is not yet created. */ +void dst_stl_load(Dst *vm) { + DstValue maybeEnv = dst_table_get(vm->modules, dst_string_cvs(vm, "std")); + if (maybeEnv.type == DST_TABLE) { + /* Module already created, so merge into main vm. */ + dst_env_merge(vm, vm->env, maybeEnv.as.table); + } else { + /* Module not yet created */ + /* Load the normal c functions */ + dst_module_mutable(vm, "std", std_module); + /* Wrap stdin and stdout */ + FILE **inp = dst_userdata(vm, sizeof(FILE *), &dst_stl_filetype); + FILE **outp = dst_userdata(vm, sizeof(FILE *), &dst_stl_filetype); + FILE **errp = dst_userdata(vm, sizeof(FILE *), &dst_stl_filetype); + *inp = stdin; + *outp = stdout; + *errp = stderr; + dst_module_put(vm, "std", "stdin", dst_wrap_userdata(inp)); + dst_module_put(vm, "std", "stdout", dst_wrap_userdata(outp)); + dst_module_put(vm, "std", "stderr", dst_wrap_userdata(outp)); + /* Now merge */ + maybeEnv = dst_table_get(vm->modules, dst_string_cvs(vm, "std")); + dst_env_merge(vm, vm->env, maybeEnv.as.table); + } +} diff --git a/core/string.c b/core/string.c index 145f503f..12aceefe 100644 --- a/core/string.c +++ b/core/string.c @@ -22,10 +22,11 @@ #include #include "strtod.h" +#include "gc.h" /* Begin building a string */ uint8_t *dst_string_begin(int32_t length) { - char *data = dst_alloc(DST_MEMORY_STRING, 2 * sizeof(int32_t) + length); + char *data = dst_gcalloc(DST_MEMORY_STRING, 2 * sizeof(int32_t) + length); uint8_t *str = (uint8_t *) (data + 2 * sizeof(int32_t)); dst_string_length(str) = length; return str; @@ -40,7 +41,7 @@ const uint8_t *dst_string_end(uint8_t *str) { /* Load a buffer as a string */ const uint8_t *dst_string(const uint8_t *buf, int32_t len) { int32_t hash = dst_string_calchash(buf, len); - char *data = dst_alloc(DST_MEMORY_STRING, 2 * sizeof(int32_t) + len); + char *data = dst_gcalloc(DST_MEMORY_STRING, 2 * sizeof(int32_t) + len); uint8_t *str = (uint8_t *) (data + 2 * sizeof(int32_t)); memcpy(str, buf, len); dst_string_length(str) = len; diff --git a/core/struct.c b/core/struct.c index c96c6798..8f57ea48 100644 --- a/core/struct.c +++ b/core/struct.c @@ -21,6 +21,7 @@ */ #include +#include "gc.h" #define dst_struct_maphash(cap, hash) (((uint32_t)(hash) % (cap)) & 0xFFFFFFFE); @@ -34,7 +35,7 @@ DstValue *dst_struct_begin(int32_t count) { * is added or s is changed, change the macro dst_struct_capacity in internal.h */ int32_t capacity = 4 * count; size_t s = sizeof(int32_t) * 2 + (capacity * sizeof(DstValue)); - char *data = dst_alloc(DST_MEMORY_STRUCT, s); + char *data = dst_gcalloc(DST_MEMORY_STRUCT, s); DstValue *st = (DstValue *) (data + 2 * sizeof(int32_t)); dst_memempty(st, capacity); dst_struct_length(st) = count; diff --git a/core/symcache.c b/core/symcache.c index 5f1f050d..4fb5e949 100644 --- a/core/symcache.c +++ b/core/symcache.c @@ -26,6 +26,7 @@ * whole program. Equality is then just a pointer check. */ #include +#include "gc.h" /* Cache state */ const uint8_t **dst_vm_cache = NULL; @@ -170,7 +171,7 @@ const uint8_t *dst_symbol(const uint8_t *str, int32_t len) { const uint8_t **bucket = dst_symcache_findmem(str, len, hash, &success); if (success) return *bucket; - newstr = dst_alloc(DST_MEMORY_SYMBOL, 2 * sizeof(int32_t) + len) + newstr = dst_gcalloc(DST_MEMORY_SYMBOL, 2 * sizeof(int32_t) + len) + (2 * sizeof(int32_t)); dst_string_hash(newstr) = hash; dst_string_length(newstr) = len; @@ -223,7 +224,7 @@ const uint8_t *dst_symbol_gen(const uint8_t *buf, int32_t len) { * is enough for resolving collisions. */ int32_t newlen = len + 8; int32_t newbufsize = newlen + 2 * sizeof(int32_t); - uint8_t *str = (uint8_t *)(dst_alloc(DST_MEMORY_SYMBOL, newbufsize) + 2 * sizeof(int32_t)); + uint8_t *str = (uint8_t *)(dst_gcalloc(DST_MEMORY_SYMBOL, newbufsize) + 2 * sizeof(int32_t)); dst_string_length(str) = newlen; memcpy(str, buf, len); str[len] = '-'; diff --git a/core/table.c b/core/table.c index 2dffbcbf..5c56bdb3 100644 --- a/core/table.c +++ b/core/table.c @@ -21,6 +21,7 @@ */ #include +#include "gc.h" #define dst_table_maphash(cap, hash) (((uint32_t)(hash) % (cap)) & 0xFFFFFFFE) @@ -46,7 +47,7 @@ void dst_table_deinit(DstTable *table) { /* Create a new table */ DstTable *dst_table(int32_t capacity) { - DstTable *table = dst_alloc(DST_MEMORY_TABLE, sizeof(DstTable)); + DstTable *table = dst_gcalloc(DST_MEMORY_TABLE, sizeof(DstTable)); return dst_table_init(table, capacity); } @@ -56,6 +57,7 @@ static DstValue *dst_table_find(DstTable *t, DstValue key) { int32_t index = dst_table_maphash(t->capacity, dst_hash(key)); int32_t i, j; int32_t start[2], end[2]; + DstValue *first_bucket = NULL; start[0] = index; end[0] = t->capacity; start[1] = 0; end[1] = index; for (j = 0; j < 2; ++j) { @@ -64,13 +66,16 @@ static DstValue *dst_table_find(DstTable *t, DstValue key) { if (dst_checktype(t->data[i + 1], DST_NIL)) { /* Empty */ return t->data + i; + } else if (NULL == first_bucket) { + /* Marked deleted and not seen free bucket yet. */ + first_bucket = t->data + i; } } else if (dst_equals(t->data[i], key)) { return t->data + i; } } } - return NULL; + return first_bucket; } /* Resize the dictionary table. */ @@ -176,10 +181,24 @@ const DstValue *dst_table_to_struct(DstTable *t) { int32_t i; DstValue *st = dst_struct_begin(t->count); for (i = 0; i < t->capacity; i += 2) { - if (!dst_checktype(t->data[i], DST_NIL)) + if (!dst_checktype(t->data[i], DST_NIL)) { dst_struct_put(st, t->data[i], t->data[i + 1]); + } } return dst_struct_end(st); } +/* Merge a struct or another table into a table. */ +void dst_table_merge(DstTable *t, DstValue other) { + int32_t count, cap, i; + const DstValue *hmap; + if (dst_hashtable_view(other, &hmap, &count, &cap)) { + for (i = 0; i < cap; i += 2) { + if (!dst_checktype(hmap[i], DST_NIL)) { + dst_table_put(t, hmap[i], hmap[i + 1]); + } + } + } +} + #undef dst_table_maphash diff --git a/core/tuple.c b/core/tuple.c index 86622efd..6e96d1de 100644 --- a/core/tuple.c +++ b/core/tuple.c @@ -22,12 +22,13 @@ #include #include "symcache.h" +#include "gc.h" /* Create a new empty tuple of the given size. This will return memory * which should be filled with DstValues. The memory will not be collected until * dst_tuple_end is called. */ DstValue *dst_tuple_begin(int32_t length) { - char *data = dst_alloc(DST_MEMORY_TUPLE, 2 * sizeof(int32_t) + length * sizeof(DstValue)); + char *data = dst_gcalloc(DST_MEMORY_TUPLE, 2 * sizeof(int32_t) + length * sizeof(DstValue)); DstValue *tuple = (DstValue *)(data + (2 * sizeof(int32_t))); dst_tuple_length(tuple) = length; return tuple; diff --git a/core/userdata.c b/core/userdata.c index ae8e164f..e24781e1 100644 --- a/core/userdata.c +++ b/core/userdata.c @@ -21,10 +21,11 @@ */ #include +#include "gc.h" /* Create new userdata */ void *dst_userdata(size_t size, const DstUserType *utype) { - char *data = dst_alloc(DST_MEMORY_USERDATA, sizeof(DstUserdataHeader) + size); + char *data = dst_gcalloc(DST_MEMORY_USERDATA, sizeof(DstUserdataHeader) + size); DstUserdataHeader *header = (DstUserdataHeader *)data; void *user = data + sizeof(DstUserdataHeader); header->size = size; diff --git a/core/util.c b/core/util.c index e1fa564d..02199c28 100644 --- a/core/util.c +++ b/core/util.c @@ -116,3 +116,16 @@ int dst_hashtable_view(DstValue tab, const DstValue **data, int32_t *len, int32_ return 0; } +/* Load c functions into an environment */ +DstValue dst_loadreg(DstReg *regs, size_t count) { + size_t i; + DstTable *t = dst_table(count); + for (i = 0; i < count; i++) { + DstValue sym = dst_csymbolv(regs[i].name); + DstValue func = dst_wrap_cfunction(regs[i].function); + DstTable *subt = dst_table(1); + dst_table_put(subt, dst_csymbolv("value"), func); + dst_table_put(t, sym, dst_wrap_table(subt)); + } + return dst_wrap_table(t); +} diff --git a/core/vm.c b/core/vm.c index f3b9c381..4342daeb 100644 --- a/core/vm.c +++ b/core/vm.c @@ -23,9 +23,10 @@ #include #include "opcodes.h" #include "symcache.h" +#include "gc.h" /* VM State */ -DstFiber *dst_vm_fiber; +DstFiber *dst_vm_fiber = NULL; /* Helper to ensure proper fiber is activated after returning */ static int dst_update_fiber() { @@ -417,14 +418,14 @@ int dst_continue() { vm_assert((int32_t)oparg(2, 0xFFFF) < func->def->constants_length, "invalid constant"); vm_assert(dst_checktype(func->def->constants[oparg(2, 0xFFFF)], DST_NIL), "constant must be funcdef"); fd = (DstFuncDef *)(dst_unwrap_pointer(func->def->constants[(int32_t)oparg(2, 0xFFFF)])); - fn = dst_alloc(DST_MEMORY_FUNCTION, sizeof(DstFunction)); + fn = dst_gcalloc(DST_MEMORY_FUNCTION, sizeof(DstFunction)); fn->envs = malloc(sizeof(DstFuncEnv *) * fd->environments_length); if (NULL == fn->envs) { DST_OUT_OF_MEMORY; } if (fd->flags & DST_FUNCDEF_FLAG_NEEDSENV) { /* Delayed capture of current stack frame */ - DstFuncEnv *env = dst_alloc(DST_MEMORY_FUNCENV, sizeof(DstFuncEnv)); + DstFuncEnv *env = dst_gcalloc(DST_MEMORY_FUNCENV, sizeof(DstFuncEnv)); env->offset = dst_vm_fiber->frame; env->as.fiber = dst_vm_fiber; env->length = func->def->slotcount; @@ -498,7 +499,7 @@ int dst_continue() { case DOP_TAILCALL: { - DstValue callee = stack[oparg(2, 0xFFFF)]; + DstValue callee = stack[oparg(1, 0xFFFFFF)]; if (dst_checktype(callee, DST_FUNCTION)) { func = dst_unwrap_function(callee); dst_fiber_funcframe_tail(dst_vm_fiber, func); @@ -684,6 +685,10 @@ int dst_init() { dst_symcache_init(); /* Set thread */ dst_vm_fiber = NULL; + /* Initialize gc roots */ + dst_vm_roots = NULL; + dst_vm_root_count = 0; + dst_vm_root_capacity = 0; return 0; } @@ -692,4 +697,8 @@ void dst_deinit() { dst_clear_memory(); dst_vm_fiber = NULL; dst_symcache_deinit(); + free(dst_vm_roots); + dst_vm_roots = NULL; + dst_vm_root_count = 0; + dst_vm_root_capacity = 0; } diff --git a/dsttest/basic.dst b/dsttest/basic.dst index 6492c9fb..b825aa40 100644 --- a/dsttest/basic.dst +++ b/dsttest/basic.dst @@ -1,4 +1,4 @@ # A really basic for to compile. for testing the compiler. Will extend # as compiler is extended. -123 +(∑ 1 2 3) diff --git a/dsttest/compile.dsts b/dsttest/compile.dsts deleted file mode 100644 index e69de29b..00000000 diff --git a/gsttests/basic.dst b/gsttests/basic.dst index 729a8265..138ed15b 100644 --- a/gsttests/basic.dst +++ b/gsttests/basic.dst @@ -1,3 +1,23 @@ +# Copyright (c) 2017 Calvin Rose +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + (print "\nRunning basic tests...\n") (var num-tests-passed 0) @@ -126,4 +146,4 @@ # report (print "\n" num-tests-passed " of " num-tests-run " tests passed\n") -(if (not (= num-tests-passed num-tests-run)) (exit! 1)) \ No newline at end of file +(if (not (= num-tests-passed num-tests-run)) (exit! 1)) diff --git a/include/dst/dst.h b/include/dst/dst.h index 59889257..57dc1147 100644 --- a/include/dst/dst.h +++ b/include/dst/dst.h @@ -113,6 +113,9 @@ #define DST_INTEGER_MIN INT32_MIN #define DST_INTEGER_MAX INT32_MAX +/* Helper for debugging */ +#define dst_trace(x) dst_puts(dst_formatc("DST TRACE %s, %d: %v\n", __FILE__, __LINE__, x)) + /* Prevent some recursive functions from recursing too deeply * ands crashing (the parser). Instead, error out. */ #define DST_RECURSION_GUARD 1000 @@ -133,14 +136,23 @@ typedef struct DstTable DstTable; typedef struct DstFiber DstFiber; /* Other structs */ +typedef struct DstReg DstReg; typedef struct DstUserdataHeader DstUserdataHeader; typedef struct DstFuncDef DstFuncDef; typedef struct DstFuncEnv DstFuncEnv; typedef struct DstStackFrame DstStackFrame; -typedef union DstValueUnion DstValueUnion; typedef struct DstUserType DstUserType; typedef int (*DstCFunction)(DstValue *argv, int32_t argn); +typedef enum DstAssembleStatus DstAssembleStatus; +typedef struct DstAssembleResult DstAssembleResult; +typedef struct DstAssembleOptions DstAssembleOptions; +typedef enum DstCompileStatus DstCompileStatus; +typedef struct DstCompileOptions DstCompileOptions; +typedef struct DstCompileResults DstCompileResults; +typedef struct DstParseResult DstParseResult; +typedef enum DstParseStatus DstParseStatus; + /* Names of all of the types */ extern const char *dst_type_names[16]; @@ -372,6 +384,12 @@ DstValue dst_wrap_pointer(void *x); /* End of tagged union implementation */ #endif +/* Used for creating libraries of cfunctions. */ +struct DstReg { + const char *name; + DstCFunction function; +}; + /* A lightweight green thread in dst. Does not correspond to * operating system threads. */ struct DstFiber { @@ -452,7 +470,7 @@ struct DstFuncEnv { DstValue *values; } as; int32_t length; /* Size of environment */ - int32_t offset; /* Stack offset when values still on stack. If offset is 0, then + int32_t offset; /* Stack offset when values still on stack. If offset is <= 0, then environment is no longer on the stack. */ }; @@ -477,12 +495,64 @@ struct DstUserdataHeader { size_t size; }; -/* The VM state. Rather than a struct that is passed - * around, the vm state is global for simplicity and performance. */ +/* Assemble structs */ +enum DstAssembleStatus { + DST_ASSEMBLE_OK, + DST_ASSEMBLE_ERROR +}; -/* TODO - somehow wrap these for windows dynamic linking. Either that, - * or force static linking. see - * https://stackoverflow.com/questions/19373061/what-happens-to-global-and-static-variables-in-a-shared-library-when-it-is-dynam */ +struct DstAssembleOptions { + const DstValue *sourcemap; + DstValue source; + uint32_t flags; +}; + +struct DstAssembleResult { + DstFuncDef *funcdef; + const uint8_t *error; + int32_t error_start; + int32_t error_end; + DstAssembleStatus status; +}; + +/* Compile structs */ +enum DstCompileStatus { + DST_COMPILE_OK, + DST_COMPILE_ERROR +}; + +struct DstCompileResults { + DstCompileStatus status; + DstFuncDef *funcdef; + const uint8_t *error; + int32_t error_start; + int32_t error_end; +}; + +struct DstCompileOptions { + uint32_t flags; + const DstValue *sourcemap; + DstValue source; + DstValue env; +}; + +/* Parse structs */ +enum DstParseStatus { + DST_PARSE_OK, + DST_PARSE_ERROR, + DST_PARSE_UNEXPECTED_EOS +}; + +struct DstParseResult { + DstValue value; + const uint8_t *error; + const DstValue *map; + int32_t bytes_read; + DstParseStatus status; +}; + +/* The VM state. Rather than a struct that is passed + * around, the vm state is global for simplicity. */ /* Garbage collection */ extern void *dst_vm_blocks; @@ -498,6 +568,11 @@ extern uint32_t dst_vm_cache_deleted; /* Syscall table */ extern const DstCFunction dst_vm_syscalls[256]; +/* GC roots */ +extern DstValue *dst_vm_roots; +extern uint32_t dst_vm_root_count; +extern uint32_t dst_vm_root_capacity; + /* GC roots - TODO consider a top level fiber pool (per thread?) */ extern DstFiber *dst_vm_fiber; @@ -585,6 +660,7 @@ DstValue dst_table_remove(DstTable *t, DstValue key); void dst_table_put(DstTable *t, DstValue key, DstValue value); DstValue dst_table_next(DstTable *t, DstValue key); const DstValue *dst_table_to_struct(DstTable *t); +void dst_table_merge(DstTable *t, DstValue other); /* Fiber */ DstFiber *dst_fiber(int32_t capacity); @@ -607,24 +683,6 @@ void dst_fiber_popframe(DstFiber *fiber); void dst_function_detach(DstFunction *func); /* Assembly */ -typedef enum { - DST_ASSEMBLE_OK, - DST_ASSEMBLE_ERROR -} DstAssembleStatus; -typedef struct DstAssembleResult DstAssembleResult; -typedef struct DstAssembleOptions DstAssembleOptions; -struct DstAssembleResult { - DstFuncDef *funcdef; - const uint8_t *error; - int32_t error_start; - int32_t error_end; - DstAssembleStatus status; -}; -struct DstAssembleOptions { - const DstValue *sourcemap; - DstValue source; - uint32_t flags; -}; DstAssembleResult dst_asm(DstAssembleOptions opts); DstFunction *dst_asm_func(DstAssembleResult result); DstValue dst_disasm(DstFuncDef *def); @@ -655,21 +713,9 @@ void dst_setindex(DstValue ds, DstValue value, int32_t index); extern const char dst_base64[65]; int32_t dst_array_calchash(const DstValue *array, int32_t len); int32_t dst_string_calchash(const uint8_t *str, int32_t len); +DstValue dst_loadreg(DstReg *regs, size_t count); /* Parsing */ -typedef enum { - DST_PARSE_OK, - DST_PARSE_ERROR, - DST_PARSE_UNEXPECTED_EOS -} DstParseStatus; -typedef struct DstParseResult DstParseResult; -struct DstParseResult { - DstValue value; - const uint8_t *error; - const DstValue *map; - int32_t bytes_read; - DstParseStatus status; -}; DstParseResult dst_parse(const uint8_t *src, int32_t len); DstParseResult dst_parsec(const char *src); const DstValue *dst_parse_submap_index(const DstValue *map, int32_t index); @@ -684,25 +730,6 @@ int dst_run(DstValue callee); DstValue dst_transfer(DstFiber *fiber, DstValue x); /* Compile */ -typedef enum DstCompileStatus { - DST_COMPILE_OK, - DST_COMPILE_ERROR -} DstCompileStatus; - -/* Results of compilation */ -typedef struct DstCompileResults { - DstCompileStatus status; - DstFuncDef *funcdef; - const uint8_t *error; - int32_t error_start; - int32_t error_end; -} DstCompileResults; - -typedef struct DstCompileOptions { - uint32_t flags; - const DstValue *sourcemap; - DstValue source; -} DstCompileOptions; /* Compile source code into FuncDef. */ DstCompileResults dst_compile(DstCompileOptions opts); @@ -710,98 +737,12 @@ DstFunction *dst_compile_func(DstCompileResults results); /* GC */ -/* The metadata header associated with an allocated block of memory */ -#define dst_gc_header(mem) ((DstGCMemoryHeader *)(mem) - 1) - -#define DST_MEM_TYPEBITS 0xFF -#define DST_MEM_REACHABLE 0x100 -#define DST_MEM_DISABLED 0x200 - -#define dst_gc_settype(m, t) ((dst_gc_header(m)->flags |= (0xFF & (t)))) -#define dst_gc_type(m) (dst_gc_header(m)->flags & 0xFF) - -#define dst_gc_mark(m) (dst_gc_header(m)->flags |= DST_MEM_REACHABLE) -#define dst_gc_unmark(m) (dst_gc_header(m)->flags &= ~DST_MEM_COLOR) -#define dst_gc_reachable(m) (dst_gc_header(m)->flags & DST_MEM_REACHABLE) - - -/* Memory header struct. Node of a linked list of memory blocks. */ -typedef struct DstGCMemoryHeader DstGCMemoryHeader; -struct DstGCMemoryHeader { - DstGCMemoryHeader *next; - uint32_t flags; -}; - -/* Memory types for the GC. Different from DstType to include funcenv and funcdef. */ -typedef enum DstMemoryType DstMemoryType; -enum DstMemoryType { - DST_MEMORY_NONE, - DST_MEMORY_STRING, - DST_MEMORY_SYMBOL, - DST_MEMORY_ARRAY, - DST_MEMORY_TUPLE, - DST_MEMORY_TABLE, - DST_MEMORY_STRUCT, - DST_MEMORY_FIBER, - DST_MEMORY_BUFFER, - DST_MEMORY_FUNCTION, - DST_MEMORY_USERDATA, - DST_MEMORY_FUNCENV, - DST_MEMORY_FUNCDEF -}; - -/* Preventn GC from freeing some memory. */ -#define dst_disablegc(m) dst_gc_header(m)->flags |= DST_MEM_DISABLED - -/* To allocate collectable memory, one must calk dst_alloc, initialize the memory, - * and then call when dst_enablegc when it is initailize and reachable by the gc (on the DST stack) */ -void *dst_alloc(DstMemoryType type, size_t size); -#define dst_enablegc(m) dst_gc_header(m)->flags &= ~DST_MEM_DISABLED - -/* When doing C interop, it is often needed to disable GC on a value. This is - * needed when a garbage collection could occur in the middle of a c function. - * This could happen, for example, if one calls back into dst inside of a c - * function. The pin and unpin functions toggle garbage collection on a value - * when needed. Note that no dst functions will call gc when you don't want it - * to. GC only happens automatically in the interpreter loop. Pinning values - * wil NOT recursively pin sub values. - * - * Be careful whennig bypassing garbage collection like this. It can easily - * lead to memory leaks or other undesirable side effects. */ -void dst_pin(DstValue x); -void dst_unpin(DstValue x); - -/* Specific types can also be pinned and unpinned as well. */ -#define dst_pin_table dst_disablegc -#define dst_pin_array dst_disablegc -#define dst_pin_buffer dst_disablegc -#define dst_pin_function dst_disablegc -#define dst_pin_fiber dst_disablegc -#define dst_pin_string(s) dst_disablegc(dst_string_raw(s)) -#define dst_pin_symbol(s) dst_disablegc(dst_string_raw(s)) -#define dst_pin_tuple(s) dst_disablegc(dst_tuple_raw(s)) -#define dst_pin_struct(s) dst_disablegc(dst_struct_raw(s)) -#define dst_pin_userdata(s) dst_disablegc(dst_userdata_header(s)) - -#define dst_unpin_table dst_enablegc -#define dst_unpin_array dst_enablegc -#define dst_unpin_buffer dst_enablegc -#define dst_unpin_function dst_enablegc -#define dst_unpin_fiber dst_enablegc -#define dst_unpin_string(s) dst_enablegc(dst_string_raw(s)) -#define dst_unpin_symbol(s) dst_enablegc(dst_string_raw(s)) -#define dst_unpin_tuple(s) dst_enablegc(dst_tuple_raw(s)) -#define dst_unpin_struct(s) dst_enablegc(dst_struct_raw(s)) -#define dst_unpin_userdata(s) dst_enablegc(dst_userdata_header(s)) - void dst_mark(DstValue x); void dst_sweep(); - -/* Collect some memory */ void dst_collect(); - -/* Clear all memory. */ void dst_clear_memory(); +void dst_gcroot(DstValue root); +int dst_gcunroot(DstValue root); /* Run garbage collection if needed */ #define dst_maybe_collect() do {\ diff --git a/unittests/compile_test.c b/unittests/compile_test.c index 9e5c56be..67c8caea 100644 --- a/unittests/compile_test.c +++ b/unittests/compile_test.c @@ -1,6 +1,15 @@ #include "unit.h" #include +int testprint(DstValue *argv, int32_t argn) { + printf("hello!\n"); + return 0; +} + +DstReg testreg[] = { + {"print", testprint} +}; + int main() { DstParseResult pres; DstCompileOptions opts; @@ -33,6 +42,8 @@ int main() { opts.flags = 0; opts.source = pres.value; opts.sourcemap = pres.map; + opts.env = dst_loadreg(testreg, sizeof(testreg)/sizeof(DstReg)); + dst_puts(dst_formatc("initial compile env: %v\n", opts.env)); cres = dst_compile(opts); if (cres.status == DST_COMPILE_ERROR) { diff --git a/unittests/fiber_test.c b/unittests/fiber_test.c index 62913b39..e23ad5cc 100644 --- a/unittests/fiber_test.c +++ b/unittests/fiber_test.c @@ -1,10 +1,11 @@ #include "unit.h" +#include "../core/gc.h" #include #include /* Create dud funcdef and function */ static DstFunction *dud_func(uint32_t slotcount, uint32_t arity, int varargs) { - DstFuncDef *def = dst_alloc(DST_MEMORY_FUNCDEF, sizeof(DstFuncDef)); + DstFuncDef *def = dst_gcalloc(DST_MEMORY_FUNCDEF, sizeof(DstFuncDef)); def->environments_length = 0; def->constants_length = 0; def->bytecode_length = 0; @@ -14,7 +15,7 @@ static DstFunction *dud_func(uint32_t slotcount, uint32_t arity, int varargs) { def->flags = varargs ? DST_FUNCDEF_FLAG_VARARG : 0; def->arity = arity; def->slotcount = slotcount; - DstFunction *f = dst_alloc(DST_MEMORY_FUNCTION, sizeof(DstFunction)); + DstFunction *f = dst_gcalloc(DST_MEMORY_FUNCTION, sizeof(DstFunction)); f->envs = NULL; f->def = def; return f; @@ -84,5 +85,7 @@ int main() { dst_fiber_funcframe_tail(fiber1, dud_func(20, 0, 0)); debug_print_fiber(fiber1, 1); + //dst_deinit(); + return 0; }