From 966331dac7da0f9ec03ffeb052dd2cd72829b2eb Mon Sep 17 00:00:00 2001 From: andy Date: Mon, 18 Apr 2005 19:48:47 +0000 Subject: [PATCH] Upgrade to nasal 1.0 test candidate --- simgear/nasal/Makefile.am | 5 +- simgear/nasal/code.c | 771 ++++++++++++++++++++--------------- simgear/nasal/code.h | 88 +++- simgear/nasal/codegen.c | 306 ++++++++++---- simgear/nasal/data.h | 78 ++-- simgear/nasal/debug.c | 211 ---------- simgear/nasal/gc.c | 283 ++++++++----- simgear/nasal/hash.c | 260 ++++++------ simgear/nasal/lex.c | 5 +- simgear/nasal/lib.c | 418 +++++++++++++++---- simgear/nasal/mathlib.c | 27 +- simgear/nasal/misc.c | 35 +- simgear/nasal/nasal.h | 24 +- simgear/nasal/parse.c | 20 +- simgear/nasal/parse.h | 15 +- simgear/nasal/string.c | 32 +- simgear/nasal/thread-posix.c | 57 +++ simgear/nasal/thread-win32.c | 20 + simgear/nasal/vector.c | 100 ++--- 19 files changed, 1691 insertions(+), 1064 deletions(-) delete mode 100644 simgear/nasal/debug.c create mode 100644 simgear/nasal/thread-posix.c create mode 100644 simgear/nasal/thread-win32.c diff --git a/simgear/nasal/Makefile.am b/simgear/nasal/Makefile.am index 20d26230..5f6461c8 100644 --- a/simgear/nasal/Makefile.am +++ b/simgear/nasal/Makefile.am @@ -8,7 +8,6 @@ libsgnasal_a_SOURCES = \ code.c code.h \ codegen.c \ data.h \ - debug.c \ gc.c \ hash.c \ lex.c \ @@ -18,6 +17,8 @@ libsgnasal_a_SOURCES = \ nasal.h \ parse.c parse.h \ string.c \ - vector.c + vector.c \ + thread-posix.c \ + thread-win32.c INCLUDES = -I$(top_srcdir) diff --git a/simgear/nasal/code.c b/simgear/nasal/code.c index d5428364..7e1f9fd5 100644 --- a/simgear/nasal/code.c +++ b/simgear/nasal/code.c @@ -4,6 +4,7 @@ //////////////////////////////////////////////////////////////////////// // Debugging stuff. //////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// +//#define DEBUG_NASAL #if !defined(DEBUG_NASAL) # define DBG(expr) /* noop */ #else @@ -13,12 +14,12 @@ #endif char* opStringDEBUG(int op); void printOpDEBUG(int ip, int op); -void printRefDEBUG(naRef r); void printStackDEBUG(struct Context* ctx); //////////////////////////////////////////////////////////////////////// -// FIXME: need to store a list of all contexts -struct Context globalContext; +struct Globals* globals = 0; + +static naRef bindFunction(struct Context* ctx, struct Frame* f, naRef code); #define ERR(c, msg) naRuntimeError((c),(msg)) void naRuntimeError(struct Context* c, char* msg) @@ -63,7 +64,7 @@ static naRef stringify(struct Context* ctx, naRef r) static int checkVec(struct Context* ctx, naRef vec, naRef idx) { int i = (int)numify(ctx, idx); - if(i < 0 || i >= vec.ref.ptr.vec->size) + if(i < 0 || i >= vec.ref.ptr.vec->rec->size) ERR(ctx, "vector index out of bounds"); return i; } @@ -94,90 +95,157 @@ static void containerSet(struct Context* ctx, naRef box, naRef key, naRef val) static void initContext(struct Context* c) { int i; + c->fTop = c->opTop = c->markTop = 0; for(i=0; ipools[i]), i); + c->nfree[i] = 0; + naVec_setsize(c->temps, 4); + c->callParent = 0; + c->callChild = 0; + c->dieArg = naNil(); + c->error = 0; +} - c->fTop = c->opTop = c->markTop = 0; +static void initGlobals() +{ + globals = (struct Globals*)naAlloc(sizeof(struct Globals)); + naBZero(globals, sizeof(struct Globals)); - naBZero(c->fStack, MAX_RECURSION * sizeof(struct Frame)); - naBZero(c->opStack, MAX_STACK_DEPTH * sizeof(naRef)); + globals->sem = naNewSem(); + globals->lock = naNewLock(); - // Make sure the args vectors (which are static with the context) - // are initialized to nil. - for(i=0; ifStack[i].args = naNil(); + int i; + globals->allocCount = 256; // reasonable starting value + for(i=0; ipools[i]), i); + globals->deadsz = 256; + globals->ndead = 0; + globals->deadBlocks = naAlloc(sizeof(void*) * globals->deadsz); - // Note we can't use naNewVector() for this; it requires that - // temps exist first. - c->temps = naObj(T_VEC, naGC_get(&(c->pools[T_VEC]))); + // Initialize a single context + globals->freeContexts = 0; + globals->allContexts = 0; + struct Context* c = naNewContext(); - c->save = naNil(); + globals->symbols = naNewHash(c); + globals->save = naNewVector(c); // Cache pre-calculated "me", "arg" and "parents" scalars - c->meRef = naStr_fromdata(naNewString(c), "me", 2); - c->argRef = naStr_fromdata(naNewString(c), "arg", 3); - c->parentsRef = naStr_fromdata(naNewString(c), "parents", 7); + globals->meRef = naInternSymbol(naStr_fromdata(naNewString(c), "me", 2)); + globals->argRef = naInternSymbol(naStr_fromdata(naNewString(c), "arg", 3)); + globals->parentsRef = naInternSymbol(naStr_fromdata(naNewString(c), "parents", 7)); + + naFreeContext(c); } struct Context* naNewContext() { - // FIXME: need more than one! - struct Context* c = &globalContext; - initContext(c); + int dummy; + if(globals == 0) + initGlobals(); + + LOCK(); + struct Context* c = globals->freeContexts; + if(c) { + globals->freeContexts = c->nextFree; + c->nextFree = 0; + UNLOCK(); + initContext(c); + } else { + UNLOCK(); + c = (struct Context*)naAlloc(sizeof(struct Context)); + // Chicken and egg, can't use naNew because it requires temps to exist + c->temps = naObj(T_VEC, (naGC_get(&globals->pools[T_VEC], 1, &dummy))[0]); + initContext(c); + LOCK(); + c->nextAll = globals->allContexts; + c->nextFree = 0; + globals->allContexts = c; + UNLOCK(); + } return c; } -void naGarbageCollect() +void naFreeContext(struct Context* c) { - int i; - struct Context* c = &globalContext; // FIXME: more than one! - - for(i=0; i < c->fTop; i++) { - naGC_mark(c->fStack[i].func); - naGC_mark(c->fStack[i].locals); - } - for(i=0; i < MAX_RECURSION; i++) - naGC_mark(c->fStack[i].args); // collect *all* the argument lists - for(i=0; i < c->opTop; i++) - naGC_mark(c->opStack[i]); - - naGC_mark(c->temps); - naGC_mark(c->save); - - naGC_mark(c->meRef); - naGC_mark(c->argRef); - naGC_mark(c->parentsRef); - - // Finally collect all the freed objects - for(i=0; ipools[i])); + naVec_setsize(c->temps, 0); + LOCK(); + c->nextFree = globals->freeContexts; + globals->freeContexts = c; + UNLOCK(); } -void setupFuncall(struct Context* ctx, naRef func, naRef args) +#define PUSH(r) do { \ + if(ctx->opTop >= MAX_STACK_DEPTH) ERR(ctx, "stack overflow"); \ + ctx->opStack[ctx->opTop++] = r; \ + } while(0) + +struct Frame* setupFuncall(struct Context* ctx, int nargs, int mcall, int tail) { + int i; + naRef *frame; struct Frame* f; - if(!IS_FUNC(func) || - !(IS_CCODE(func.ref.ptr.func->code) || - IS_CODE(func.ref.ptr.func->code))) - { + struct naCode* c; + + DBG(printf("setupFuncall(nargs:%d, mcall:%d)\n", nargs, mcall);) + + frame = &ctx->opStack[ctx->opTop - nargs - 1]; + if(!IS_FUNC(frame[0])) ERR(ctx, "function/method call invoked on uncallable object"); + + // Just do native calls right here, and don't touch the stack + // frames; return the current one (unless it's a tail call!). + if(frame[0].ref.ptr.func->code.ref.ptr.obj->type == T_CCODE) { + naRef obj = mcall ? frame[-1] : naNil(); + naCFunction fp = frame[0].ref.ptr.func->code.ref.ptr.ccode->fptr; + naRef result = (*fp)(ctx, obj, nargs, frame + 1); + ctx->opTop -= nargs + 1 + mcall; + PUSH(result); + return &(ctx->fStack[ctx->fTop-1]); } + + if(tail) ctx->fTop--; + else if(ctx->fTop >= MAX_RECURSION) ERR(ctx, "call stack overflow"); + // Note: assign nil first, otherwise the naNew() can cause a GC, + // which will now (after fTop++) see the *old* reference as a + // markable value! f = &(ctx->fStack[ctx->fTop++]); - f->func = func; + f->locals = f->func = naNil(); + f->locals = naNewHash(ctx); + f->func = frame[0]; f->ip = 0; - f->bp = ctx->opTop; - f->line = 0; - - DBG(printf("Entering frame %d\n", ctx->fTop-1);) - - f->args = args; - if(IS_CCODE(func.ref.ptr.func->code)) { - f->locals = naNil(); - } else if(IS_CODE(func.ref.ptr.func->code)) { - f->locals = naNewHash(ctx); - naHash_set(f->locals, ctx->argRef, args); + f->bp = ctx->opTop - (nargs + 1 + mcall); + + if(mcall) + naHash_set(f->locals, globals->meRef, frame[-1]); + + // Set the argument symbols, and put any remaining args in a vector + c = (*frame++).ref.ptr.func->code.ref.ptr.code; + if(nargs < c->nArgs) ERR(ctx, "not enough arguments to function call"); + for(i=0; inArgs; i++) + naHash_newsym(f->locals.ref.ptr.hash, + &c->constants[c->argSyms[i]], &frame[i]); + frame += c->nArgs; + nargs -= c->nArgs; + for(i=0; inOptArgs; i++, nargs--) { + naRef val = nargs > 0 ? frame[i] : c->constants[c->optArgVals[i]]; + if(IS_CODE(val)) + val = bindFunction(ctx, &ctx->fStack[ctx->fTop-2], val); + naHash_newsym(f->locals.ref.ptr.hash, &c->constants[c->optArgSyms[i]], + &val); + } + if(c->needArgVector || nargs > 0) + { + naRef args = naNewVector(ctx); + naVec_setsize(args, nargs > 0 ? nargs : 0); + for(i=0; irec->array[i] = *frame++; + naHash_newsym(f->locals.ref.ptr.hash, &c->restArgSym, &args); } + + ctx->opTop = f->bp; // Pop the stack last, to avoid GC lossage + DBG(printf("Entering frame %d with %d args\n", ctx->fTop-1, nargs);) + return f; } static naRef evalAndOr(struct Context* ctx, int op, naRef ra, naRef rb) @@ -196,114 +264,94 @@ static naRef evalEquality(int op, naRef ra, naRef rb) return naNum((op==OP_EQ) ? result : !result); } -static naRef evalBinaryNumeric(struct Context* ctx, int op, naRef ra, naRef rb) -{ - double a = numify(ctx, ra), b = numify(ctx, rb); - switch(op) { - case OP_PLUS: return naNum(a + b); - case OP_MINUS: return naNum(a - b); - case OP_MUL: return naNum(a * b); - case OP_DIV: return naNum(a / b); - case OP_LT: return naNum(a < b ? 1 : 0); - case OP_LTE: return naNum(a <= b ? 1 : 0); - case OP_GT: return naNum(a > b ? 1 : 0); - case OP_GTE: return naNum(a >= b ? 1 : 0); - } - return naNil(); -} - // When a code object comes out of the constant pool and shows up on // the stack, it needs to be bound with the lexical context. static naRef bindFunction(struct Context* ctx, struct Frame* f, naRef code) { - naRef next = f->func.ref.ptr.func->closure; - naRef closure = naNewClosure(ctx, f->locals, next); naRef result = naNewFunc(ctx, code); - result.ref.ptr.func->closure = closure; + result.ref.ptr.func->namespace = f->locals; + result.ref.ptr.func->next = f->func; return result; } -static int getClosure(struct naClosure* c, naRef sym, naRef* result) +static int getClosure(struct naFunc* c, naRef sym, naRef* result) { while(c) { if(naHash_get(c->namespace, sym, result)) return 1; - c = c->next.ref.ptr.closure; + c = c->next.ref.ptr.func; } return 0; } -// Get a local symbol, or check the closure list if it isn't there -static naRef getLocal(struct Context* ctx, struct Frame* f, naRef sym) +static naRef getLocal2(struct Context* ctx, struct Frame* f, naRef sym) { naRef result; - if(!naHash_get(f->locals, sym, &result)) { - naRef c = f->func.ref.ptr.func->closure; - if(!getClosure(c.ref.ptr.closure, sym, &result)) + if(!naHash_get(f->locals, sym, &result)) + if(!getClosure(f->func.ref.ptr.func, sym, &result)) ERR(ctx, "undefined symbol"); - } return result; } -static int setClosure(naRef closure, naRef sym, naRef val) +static void getLocal(struct Context* ctx, struct Frame* f, + naRef* sym, naRef* out) +{ + struct naFunc* func; + struct naStr* str = sym->ref.ptr.str; + if(naHash_sym(f->locals.ref.ptr.hash, str, out)) + return; + func = f->func.ref.ptr.func; + while(func && func->namespace.ref.ptr.hash) { + if(naHash_sym(func->namespace.ref.ptr.hash, str, out)) + return; + func = func->next.ref.ptr.func; + } + // Now do it again using the more general naHash_get(). This will + // only be necessary if something has created the value in the + // namespace using the more generic hash syntax + // (e.g. namespace["symbol"] and not namespace.symbol). + *out = getLocal2(ctx, f, *sym); +} + +static int setClosure(naRef func, naRef sym, naRef val) { - struct naClosure* c = closure.ref.ptr.closure; + struct naFunc* c = func.ref.ptr.func; if(c == 0) { return 0; } else if(naHash_tryset(c->namespace, sym, val)) { return 1; } else { return setClosure(c->next, sym, val); } } -static naRef setLocal(struct Frame* f, naRef sym, naRef val) +static naRef setSymbol(struct Frame* f, naRef sym, naRef val) { // Try the locals first, if not already there try the closures in // order. Finally put it in the locals if nothing matched. if(!naHash_tryset(f->locals, sym, val)) - if(!setClosure(f->func.ref.ptr.func->closure, sym, val)) + if(!setClosure(f->func, sym, val)) naHash_set(f->locals, sym, val); return val; } // Recursively descend into the parents lists -static int getMember(struct Context* ctx, naRef obj, naRef fld, naRef* result) +static int getMember(struct Context* ctx, naRef obj, naRef fld, + naRef* result, int count) { naRef p; + if(--count < 0) ERR(ctx, "too many parents"); if(!IS_HASH(obj)) ERR(ctx, "non-objects have no members"); if(naHash_get(obj, fld, result)) { return 1; - } else if(naHash_get(obj, ctx->parentsRef, &p)) { - int i; - if(!IS_VEC(p)) ERR(ctx, "parents field not vector"); - for(i=0; isize; i++) - if(getMember(ctx, p.ref.ptr.vec->array[i], fld, result)) - return 1; + } else if(naHash_get(obj, globals->parentsRef, &p)) { + if(IS_VEC(p)) { + int i; + struct VecRec* v = p.ref.ptr.vec->rec; + for(i=0; isize; i++) + if(getMember(ctx, v->array[i], fld, result, count)) + return 1; + } else + ERR(ctx, "parents field not vector"); } return 0; } -static void PUSH(struct Context* ctx, naRef r) -{ - if(ctx->opTop >= MAX_STACK_DEPTH) ERR(ctx, "stack overflow"); - ctx->opStack[ctx->opTop++] = r; -} - -static naRef POP(struct Context* ctx) -{ - if(ctx->opTop == 0) ERR(ctx, "BUG: stack underflow"); - return ctx->opStack[--ctx->opTop]; -} - -static naRef TOP(struct Context* ctx) -{ - if(ctx->opTop == 0) ERR(ctx, "BUG: stack underflow"); - return ctx->opStack[ctx->opTop-1]; -} - -static int ARG16(unsigned char* byteCode, struct Frame* f) -{ - int arg = byteCode[f->ip]<<8 | byteCode[f->ip+1]; - f->ip += 2; - return arg; -} - // OP_EACH works like a vector get, except that it leaves the vector // and index on the stack, increments the index after use, and pops // the arguments and pushes a nil if the index is beyond the end. @@ -311,214 +359,250 @@ static void evalEach(struct Context* ctx) { int idx = (int)(ctx->opStack[ctx->opTop-1].num); naRef vec = ctx->opStack[ctx->opTop-2]; - if(idx >= vec.ref.ptr.vec->size) { + if(!IS_VEC(vec)) naRuntimeError(ctx, "foreach enumeration of non-vector"); + if(!vec.ref.ptr.vec->rec || idx >= vec.ref.ptr.vec->rec->size) { ctx->opTop -= 2; // pop two values - PUSH(ctx, naNil()); + PUSH(naNil()); return; } ctx->opStack[ctx->opTop-1].num = idx+1; // modify in place - PUSH(ctx, naVec_get(vec, idx)); + PUSH(naVec_get(vec, idx)); } -static void run1(struct Context* ctx, struct Frame* f, naRef code) +#define ARG() cd->byteCode[f->ip++] +#define CONSTARG() cd->constants[ARG()] +#define POP() ctx->opStack[--ctx->opTop] +#define STK(n) (ctx->opStack[ctx->opTop-(n)]) +#define FIXFRAME() f = &(ctx->fStack[ctx->fTop-1]); \ + cd = f->func.ref.ptr.func->code.ref.ptr.code; +static naRef run(struct Context* ctx) { - naRef a, b, c; - struct naCode* cd = code.ref.ptr.code; + struct Frame* f; + struct naCode* cd; int op, arg; + naRef a, b, c; - if(f->ip >= cd->nBytes) { - DBG(printf("Done with frame %d\n", ctx->fTop-1);) - ctx->fTop--; - if(ctx->fTop <= 0) - ctx->done = 1; - return; - } - - op = cd->byteCode[f->ip++]; - DBG(printf("Stack Depth: %d\n", ctx->opTop)); - DBG(printOpDEBUG(f->ip-1, op)); - switch(op) { - case OP_POP: - POP(ctx); - break; - case OP_DUP: - PUSH(ctx, ctx->opStack[ctx->opTop-1]); - break; - case OP_XCHG: - a = POP(ctx); b = POP(ctx); - PUSH(ctx, a); PUSH(ctx, b); - break; - case OP_PLUS: case OP_MUL: case OP_DIV: case OP_MINUS: - case OP_LT: case OP_LTE: case OP_GT: case OP_GTE: - a = POP(ctx); b = POP(ctx); - PUSH(ctx, evalBinaryNumeric(ctx, op, b, a)); - break; - case OP_EQ: case OP_NEQ: - a = POP(ctx); b = POP(ctx); - PUSH(ctx, evalEquality(op, b, a)); - break; - case OP_AND: case OP_OR: - a = POP(ctx); b = POP(ctx); - PUSH(ctx, evalAndOr(ctx, op, a, b)); - break; - case OP_CAT: - // stringify can call the GC, so don't take stuff of the stack! - if(ctx->opTop <= 1) ERR(ctx, "BUG: stack underflow"); - a = stringify(ctx, ctx->opStack[ctx->opTop-1]); - b = stringify(ctx, ctx->opStack[ctx->opTop-2]); - c = naStr_concat(naNewString(ctx), b, a); - ctx->opTop -= 2; - PUSH(ctx, c); - break; - case OP_NEG: - a = POP(ctx); - PUSH(ctx, naNum(-numify(ctx, a))); - break; - case OP_NOT: - a = POP(ctx); - PUSH(ctx, naNum(boolify(ctx, a) ? 0 : 1)); - break; - case OP_PUSHCONST: - a = cd->constants[ARG16(cd->byteCode, f)]; - if(IS_CODE(a)) a = bindFunction(ctx, f, a); - PUSH(ctx, a); - break; - case OP_PUSHONE: - PUSH(ctx, naNum(1)); - break; - case OP_PUSHZERO: - PUSH(ctx, naNum(0)); - break; - case OP_PUSHNIL: - PUSH(ctx, naNil()); - break; - case OP_NEWVEC: - PUSH(ctx, naNewVector(ctx)); - break; - case OP_VAPPEND: - b = POP(ctx); a = TOP(ctx); - naVec_append(a, b); - break; - case OP_NEWHASH: - PUSH(ctx, naNewHash(ctx)); - break; - case OP_HAPPEND: - c = POP(ctx); b = POP(ctx); a = TOP(ctx); // a,b,c: hash, key, val - naHash_set(a, b, c); - break; - case OP_LOCAL: - a = getLocal(ctx, f, POP(ctx)); - PUSH(ctx, a); - break; - case OP_SETLOCAL: - a = POP(ctx); b = POP(ctx); - PUSH(ctx, setLocal(f, b, a)); - break; - case OP_MEMBER: - a = POP(ctx); b = POP(ctx); - if(!getMember(ctx, b, a, &c)) - ERR(ctx, "no such member"); - PUSH(ctx, c); - break; - case OP_SETMEMBER: - c = POP(ctx); b = POP(ctx); a = POP(ctx); // a,b,c: hash, key, val - if(!IS_HASH(a)) ERR(ctx, "non-objects have no members"); - naHash_set(a, b, c); - PUSH(ctx, c); - break; - case OP_INSERT: - c = POP(ctx); b = POP(ctx); a = POP(ctx); // a,b,c: box, key, val - containerSet(ctx, a, b, c); - PUSH(ctx, c); - break; - case OP_EXTRACT: - b = POP(ctx); a = POP(ctx); // a,b: box, key - PUSH(ctx, containerGet(ctx, a, b)); - break; - case OP_JMP: - f->ip = ARG16(cd->byteCode, f); - DBG(printf(" [Jump to: %d]\n", f->ip);) - break; - case OP_JIFNIL: - arg = ARG16(cd->byteCode, f); - a = TOP(ctx); - if(IS_NIL(a)) { - POP(ctx); // Pops **ONLY** if it's nil! - f->ip = arg; + FIXFRAME(); + + while(1) { + op = cd->byteCode[f->ip++]; + DBG(printf("Stack Depth: %d\n", ctx->opTop)); + DBG(printOpDEBUG(f->ip-1, op)); + switch(op) { + case OP_POP: + ctx->opTop--; + break; + case OP_DUP: + PUSH(ctx->opStack[ctx->opTop-1]); + break; + case OP_XCHG: + a = STK(1); STK(1) = STK(2); STK(2) = a; + break; + +#define BINOP(expr) do { \ + double l = IS_NUM(STK(2)) ? STK(2).num : numify(ctx, STK(2)); \ + double r = IS_NUM(STK(1)) ? STK(1).num : numify(ctx, STK(1)); \ + STK(2).ref.reftag = ~NASAL_REFTAG; \ + STK(2).num = expr; \ + ctx->opTop--; } while(0) + + case OP_PLUS: BINOP(l + r); break; + case OP_MINUS: BINOP(l - r); break; + case OP_MUL: BINOP(l * r); break; + case OP_DIV: BINOP(l / r); break; + case OP_LT: BINOP(l < r ? 1 : 0); break; + case OP_LTE: BINOP(l <= r ? 1 : 0); break; + case OP_GT: BINOP(l > r ? 1 : 0); break; + case OP_GTE: BINOP(l >= r ? 1 : 0); break; + +#undef BINOP + + case OP_EQ: case OP_NEQ: + STK(2) = evalEquality(op, STK(2), STK(1)); + ctx->opTop--; + break; + case OP_AND: case OP_OR: + STK(2) = evalAndOr(ctx, op, STK(2), STK(1)); + ctx->opTop--; + break; + case OP_CAT: + // stringify can call the GC, so don't take stuff of the stack! + a = stringify(ctx, ctx->opStack[ctx->opTop-1]); + b = stringify(ctx, ctx->opStack[ctx->opTop-2]); + c = naStr_concat(naNewString(ctx), b, a); + ctx->opTop -= 2; + PUSH(c); + break; + case OP_NEG: + STK(1) = naNum(-numify(ctx, STK(1))); + break; + case OP_NOT: + STK(1) = naNum(boolify(ctx, STK(1)) ? 0 : 1); + break; + case OP_PUSHCONST: + a = CONSTARG(); + if(IS_CODE(a)) a = bindFunction(ctx, f, a); + PUSH(a); + break; + case OP_PUSHONE: + PUSH(naNum(1)); + break; + case OP_PUSHZERO: + PUSH(naNum(0)); + break; + case OP_PUSHNIL: + PUSH(naNil()); + break; + case OP_NEWVEC: + PUSH(naNewVector(ctx)); + break; + case OP_VAPPEND: + naVec_append(STK(2), STK(1)); + ctx->opTop--; + break; + case OP_NEWHASH: + PUSH(naNewHash(ctx)); + break; + case OP_HAPPEND: + naHash_set(STK(3), STK(2), STK(1)); + ctx->opTop -= 2; + break; + case OP_LOCAL: + a = CONSTARG(); + getLocal(ctx, f, &a, &b); + PUSH(b); + break; + case OP_SETSYM: + STK(2) = setSymbol(f, STK(2), STK(1)); + ctx->opTop--; + break; + case OP_SETLOCAL: + naHash_set(f->locals, STK(2), STK(1)); + STK(2) = STK(1); // FIXME: reverse order of arguments instead! + ctx->opTop--; + break; + case OP_MEMBER: + if(!getMember(ctx, STK(1), CONSTARG(), &STK(1), 64)) + ERR(ctx, "no such member"); + break; + case OP_SETMEMBER: + if(!IS_HASH(STK(3))) ERR(ctx, "non-objects have no members"); + naHash_set(STK(3), STK(2), STK(1)); + STK(3) = STK(1); // FIXME: fix arg order instead + ctx->opTop -= 2; + break; + case OP_INSERT: + containerSet(ctx, STK(3), STK(2), STK(1)); + STK(3) = STK(1); // FIXME: codegen order again... + ctx->opTop -= 2; + break; + case OP_EXTRACT: + STK(2) = containerGet(ctx, STK(2), STK(1)); + ctx->opTop--; + break; + case OP_JMPLOOP: + // Identical to JMP, except for locking + naCheckBottleneck(); + f->ip = cd->byteCode[f->ip]; DBG(printf(" [Jump to: %d]\n", f->ip);) - } - break; - case OP_JIFNOT: - arg = ARG16(cd->byteCode, f); - if(!boolify(ctx, POP(ctx))) { - f->ip = arg; + break; + case OP_JMP: + f->ip = cd->byteCode[f->ip]; DBG(printf(" [Jump to: %d]\n", f->ip);) + break; + case OP_JIFNIL: + arg = ARG(); + if(IS_NIL(STK(1))) { + ctx->opTop--; // Pops **ONLY** if it's nil! + f->ip = arg; + DBG(printf(" [Jump to: %d]\n", f->ip);) + } + break; + case OP_JIFNOT: + arg = ARG(); + if(!boolify(ctx, POP())) { + f->ip = arg; + DBG(printf(" [Jump to: %d]\n", f->ip);) + } + break; + case OP_FCALL: + f = setupFuncall(ctx, ARG(), 0, 0); + cd = f->func.ref.ptr.func->code.ref.ptr.code; + break; + case OP_FTAIL: + f = setupFuncall(ctx, ARG(), 0, 1); + cd = f->func.ref.ptr.func->code.ref.ptr.code; + break; + case OP_MCALL: + f = setupFuncall(ctx, ARG(), 1, 0); + cd = f->func.ref.ptr.func->code.ref.ptr.code; + break; + case OP_MTAIL: + f = setupFuncall(ctx, ARG(), 1, 1); + cd = f->func.ref.ptr.func->code.ref.ptr.code; + break; + case OP_RETURN: + a = STK(1); + if(--ctx->fTop <= 0) return a; + ctx->opTop = f->bp + 1; // restore the correct opstack frame! + STK(1) = a; + FIXFRAME(); + break; + case OP_EACH: + evalEach(ctx); + break; + case OP_MARK: // save stack state (e.g. "setjmp") + if(ctx->markTop >= MAX_MARK_DEPTH) + naRuntimeError(ctx, "mark stack overflow"); + ctx->markStack[ctx->markTop++] = ctx->opTop; + break; + case OP_UNMARK: // pop stack state set by mark + ctx->markTop--; + break; + case OP_BREAK: // restore stack state (FOLLOW WITH JMP!) + ctx->opTop = ctx->markStack[--ctx->markTop]; + break; + default: + ERR(ctx, "BUG: bad opcode"); } - break; - case OP_FCALL: - b = POP(ctx); a = POP(ctx); // a,b = func, args - setupFuncall(ctx, a, b); - break; - case OP_MCALL: - c = POP(ctx); b = POP(ctx); a = POP(ctx); // a,b,c = obj, func, args - naVec_append(ctx->temps, a); - setupFuncall(ctx, b, c); - naHash_set(ctx->fStack[ctx->fTop-1].locals, ctx->meRef, a); - break; - case OP_RETURN: - a = POP(ctx); - ctx->opTop = f->bp; // restore the correct stack frame! - ctx->fTop--; - ctx->fStack[ctx->fTop].args.ref.ptr.vec->size = 0; - PUSH(ctx, a); - break; - case OP_LINE: - f->line = ARG16(cd->byteCode, f); - break; - case OP_EACH: - evalEach(ctx); - break; - case OP_MARK: // save stack state (e.g. "setjmp") - ctx->markStack[ctx->markTop++] = ctx->opTop; - break; - case OP_UNMARK: // pop stack state set by mark - ctx->markTop--; - break; - case OP_BREAK: // restore stack state (FOLLOW WITH JMP!) - ctx->opTop = ctx->markStack[--ctx->markTop]; - break; - default: - ERR(ctx, "BUG: bad opcode"); + ctx->temps.ref.ptr.vec->rec->size = 0; // reset GC temp vector + DBG(printStackDEBUG(ctx);) } - - if(ctx->fTop <= 0) - ctx->done = 1; -} - -static void nativeCall(struct Context* ctx, struct Frame* f, naRef ccode) -{ - naCFunction fptr = ccode.ref.ptr.ccode->fptr; - naRef result = (*fptr)(ctx, f->args); - ctx->fTop--; - ctx->fStack[ctx->fTop].args.ref.ptr.vec->size = 0; - PUSH(ctx, result); + return naNil(); // unreachable } +#undef POP +#undef CONSTARG +#undef STK +#undef FIXFRAME void naSave(struct Context* ctx, naRef obj) { - ctx->save = obj; + naVec_append(globals->save, obj); } +// FIXME: handle ctx->callParent int naStackDepth(struct Context* ctx) { return ctx->fTop; } +// FIXME: handle ctx->callParent int naGetLine(struct Context* ctx, int frame) { - return ctx->fStack[ctx->fTop-1-frame].line; + struct Frame* f = &ctx->fStack[ctx->fTop-1-frame]; + naRef func = f->func; + int ip = f->ip; + if(IS_FUNC(func) && IS_CODE(func.ref.ptr.func->code)) { + struct naCode* c = func.ref.ptr.func->code.ref.ptr.code; + unsigned short* p = c->lineIps + c->nLines - 2; + while(p >= c->lineIps && p[0] > ip) + p -= 2; + return p[1]; + } + return -1; } +// FIXME: handle ctx->callParent naRef naGetSourceFile(struct Context* ctx, int frame) { naRef f = ctx->fStack[ctx->fTop-1-frame].func; @@ -528,41 +612,33 @@ naRef naGetSourceFile(struct Context* ctx, int frame) char* naGetError(struct Context* ctx) { + if(IS_STR(ctx->dieArg)) + return ctx->dieArg.ref.ptr.str->data; return ctx->error; } -static naRef run(naContext ctx) +naRef naBindFunction(naContext ctx, naRef code, naRef closure) { - // Return early if an error occurred. It will be visible to the - // caller via naGetError(). - ctx->error = 0; - if(setjmp(ctx->jumpHandle)) - return naNil(); - - ctx->done = 0; - while(!ctx->done) { - struct Frame* f = &(ctx->fStack[ctx->fTop-1]); - naRef code = f->func.ref.ptr.func->code; - if(IS_CCODE(code)) nativeCall(ctx, f, code); - else run1(ctx, f, code); - - ctx->temps.ref.ptr.vec->size = 0; // Reset the temporaries - DBG(printStackDEBUG(ctx);) - } - - DBG(printStackDEBUG(ctx);) - return ctx->opStack[--ctx->opTop]; + naRef func = naNewFunc(ctx, code); + func.ref.ptr.func->namespace = closure; + func.ref.ptr.func->next = naNil(); + return func; } -naRef naBindFunction(naContext ctx, naRef code, naRef closure) +naRef naBindToContext(naContext ctx, naRef code) { naRef func = naNewFunc(ctx, code); - func.ref.ptr.func->closure = naNewClosure(ctx, closure, naNil()); + struct Frame* f = &ctx->fStack[ctx->fTop-1]; + func.ref.ptr.func->namespace = f->locals; + func.ref.ptr.func->next = f->func; return func; } naRef naCall(naContext ctx, naRef func, naRef args, naRef obj, naRef locals) { + naRef result; + if(!ctx->callParent) naModLock(ctx); + // We might have to allocate objects, which can call the GC. But // the call isn't on the Nasal stack yet, so the GC won't find our // C-space arguments. @@ -571,23 +647,38 @@ naRef naCall(naContext ctx, naRef func, naRef args, naRef obj, naRef locals) naVec_append(ctx->temps, obj); naVec_append(ctx->temps, locals); - if(IS_NIL(args)) - args = naNewVector(ctx); if(IS_NIL(locals)) locals = naNewHash(ctx); - if(!IS_FUNC(func)) { - // Generate a noop closure for bare code objects - naRef code = func; - func = naNewFunc(ctx, code); - func.ref.ptr.func->closure = naNewClosure(ctx, locals, naNil()); - } + if(!IS_FUNC(func)) + func = naNewFunc(ctx, func); // bind bare code objects + + if(!IS_NIL(args)) + naHash_set(locals, globals->argRef, args); if(!IS_NIL(obj)) - naHash_set(locals, ctx->meRef, obj); + naHash_set(locals, globals->meRef, obj); + + ctx->dieArg = naNil(); - ctx->fTop = ctx->opTop = ctx->markTop = 0; - setupFuncall(ctx, func, args); - ctx->fStack[ctx->fTop-1].locals = locals; + ctx->opTop = ctx->markTop = 0; + ctx->fTop = 1; + ctx->fStack[0].func = func; + ctx->fStack[0].locals = locals; + ctx->fStack[0].ip = 0; + ctx->fStack[0].bp = ctx->opTop; - return run(ctx); + // Return early if an error occurred. It will be visible to the + // caller via naGetError(). + ctx->error = 0; + if(setjmp(ctx->jumpHandle)) + return naNil(); + + if(IS_CCODE(func.ref.ptr.func->code)) { + naCFunction fp = func.ref.ptr.func->code.ref.ptr.ccode->fptr; + struct naVec* av = args.ref.ptr.vec; + result = (*fp)(ctx, obj, av->rec->size, av->rec->array); + } else + result = run(ctx); + if(!ctx->callParent) naModUnlock(ctx); + return result; } diff --git a/simgear/nasal/code.h b/simgear/nasal/code.h index 7e9c7653..8b45a75d 100644 --- a/simgear/nasal/code.h +++ b/simgear/nasal/code.h @@ -5,33 +5,65 @@ #include "nasal.h" #include "data.h" -#define MAX_STACK_DEPTH 1024 +#define MAX_STACK_DEPTH 512 #define MAX_RECURSION 128 -#define MAX_MARK_DEPTH 32 +#define MAX_MARK_DEPTH 128 + +// Number of objects (per pool per thread) asked for using naGC_get(). +// Testing with fib.nas shows that this gives the best performance, +// without too much per-thread overhead. +#define OBJ_CACHE_SZ 128 enum { OP_AND, OP_OR, OP_NOT, OP_MUL, OP_PLUS, OP_MINUS, OP_DIV, OP_NEG, OP_CAT, OP_LT, OP_LTE, OP_GT, OP_GTE, OP_EQ, OP_NEQ, OP_EACH, - OP_JMP, OP_JIFNOT, OP_JIFNIL, OP_FCALL, OP_MCALL, OP_RETURN, + OP_JMP, OP_JMPLOOP, OP_JIFNOT, OP_JIFNIL, OP_FCALL, OP_MCALL, OP_RETURN, OP_PUSHCONST, OP_PUSHONE, OP_PUSHZERO, OP_PUSHNIL, OP_POP, OP_DUP, OP_XCHG, OP_INSERT, OP_EXTRACT, OP_MEMBER, OP_SETMEMBER, OP_LOCAL, OP_SETLOCAL, OP_NEWVEC, OP_VAPPEND, OP_NEWHASH, OP_HAPPEND, - OP_LINE, OP_MARK, OP_UNMARK, OP_BREAK + OP_MARK, OP_UNMARK, OP_BREAK, OP_FTAIL, OP_MTAIL, OP_SETSYM }; struct Frame { naRef func; // naFunc object naRef locals; // local per-call namespace - naRef args; // vector of arguments int ip; // instruction pointer into code int bp; // opStack pointer to start of frame - int line; // current line number }; -struct Context { +struct Globals { // Garbage collecting allocators: struct naPool pools[NUM_NASAL_TYPES]; + int allocCount; + + // Dead blocks waiting to be freed when it is safe + void** deadBlocks; + int deadsz; + int ndead; + + // Threading stuff + int nThreads; + int waitCount; + int needGC; + int bottleneck; + void* sem; + void* lock; + + // Constants + naRef meRef; + naRef argRef; + naRef parentsRef; + // A hash of symbol names + naRef symbols; + + naRef save; + + struct Context* freeContexts; + struct Context* allContexts; +}; + +struct Context { // Stack(s) struct Frame fStack[MAX_RECURSION]; int fTop; @@ -39,26 +71,44 @@ struct Context { int opTop; int markStack[MAX_MARK_DEPTH]; int markTop; - int done; - // Constants - naRef meRef; - naRef argRef; - naRef parentsRef; + // Free object lists, cached from the global GC + struct naObj** free[NUM_NASAL_TYPES]; + int nfree[NUM_NASAL_TYPES]; + + // GC-findable reference point for objects that may live on the + // processor ("real") stack during execution. naNew() places them + // here, and clears the array each instruction + naRef temps; // Error handling jmp_buf jumpHandle; char* error; + naRef dieArg; - // GC-findable reference point for objects that may live on the - // processor ("real") stack during execution. naNew() places them - // here, and clears the array each time we return from a C - // function. - naRef temps; + // Sub-call lists + struct Context* callParent; + struct Context* callChild; - naRef save; + // Linked list pointers in globals + struct Context* nextFree; + struct Context* nextAll; }; -void printRefDEBUG(naRef r); +#define globals nasal_globals +extern struct Globals* globals; + +// Threading low-level functions +void* naNewLock(); +void naLock(void* lock); +void naUnlock(void* lock); +void* naNewSem(); +void naSemDown(void* sem); +void naSemUpAll(void* sem, int count); + +void naCheckBottleneck(); + +#define LOCK() naLock(globals->lock) +#define UNLOCK() naUnlock(globals->lock) #endif // _CODE_H diff --git a/simgear/nasal/codegen.c b/simgear/nasal/codegen.c index f481e419..c909c928 100644 --- a/simgear/nasal/codegen.c +++ b/simgear/nasal/codegen.c @@ -1,6 +1,8 @@ #include "parse.h" #include "code.h" +#define MAX_FUNARGS 32 + // These are more sensical predicate names in most contexts in this file #define LEFT(tok) ((tok)->children) #define RIGHT(tok) ((tok)->lastChild) @@ -9,24 +11,24 @@ // Forward references for recursion static void genExpr(struct Parser* p, struct Token* t); static void genExprList(struct Parser* p, struct Token* t); +static naRef newLambda(struct Parser* p, struct Token* t); -static void emit(struct Parser* p, int byte) +static void emit(struct Parser* p, int val) { - if(p->cg->nBytes >= p->cg->codeAlloced) { + if(p->cg->codesz >= p->cg->codeAlloced) { int i, sz = p->cg->codeAlloced * 2; - unsigned char* buf = naParseAlloc(p, sz); + unsigned short* buf = naParseAlloc(p, sz*sizeof(unsigned short)); for(i=0; icg->codeAlloced; i++) buf[i] = p->cg->byteCode[i]; p->cg->byteCode = buf; p->cg->codeAlloced = sz; } - p->cg->byteCode[p->cg->nBytes++] = (unsigned char)byte; + p->cg->byteCode[p->cg->codesz++] = (unsigned short)val; } -static void emitImmediate(struct Parser* p, int byte, int arg) +static void emitImmediate(struct Parser* p, int val, int arg) { - emit(p, byte); - emit(p, arg >> 8); - emit(p, arg & 0xff); + emit(p, val); + emit(p, arg); } static void genBinOp(int op, struct Parser* p, struct Token* t) @@ -55,36 +57,87 @@ static naRef getConstant(struct Parser* p, int idx) // Interns a scalar (!) constant and returns its index static int internConstant(struct Parser* p, naRef c) { - int i, j, n = naVec_size(p->cg->consts); + int i, n = naVec_size(p->cg->consts); + if(IS_CODE(c)) return newConstant(p, c); for(i=0; icg->consts, i); - if(IS_NUM(b) && IS_NUM(c) && b.num == c.num) - return i; - if(IS_STR(b) && IS_STR(c)) { - int len = naStr_len(c); - char* cs = naStr_data(c); - char* bs = naStr_data(b); - if(naStr_len(b) != len) - continue; - for(j=0; jtype == c.ref.ptr.obj->type) - if(naEqual(b, c)) - return i; + if(IS_NUM(b) && IS_NUM(c) && b.num == c.num) return i; + else if(IS_NIL(b) && IS_NIL(c)) return i; + else if(naStrEqual(b, c)) return i; } return newConstant(p, c); } +naRef naInternSymbol(naRef sym) +{ + naRef result; + if(naHash_get(globals->symbols, sym, &result)) + return result; + naHash_set(globals->symbols, sym, sym); + return sym; +} + +static int findConstantIndex(struct Parser* p, struct Token* t) +{ + naRef c; + if(t->type == TOK_NIL) c = naNil(); + else if(t->str) { + c = naStr_fromdata(naNewString(p->context), t->str, t->strlen); + if(t->type == TOK_SYMBOL) c = naInternSymbol(c); + } else if(t->type == TOK_FUNC) c = newLambda(p, t); + else if(t->type == TOK_LITERAL) c = naNum(t->num); + else naParseError(p, "invalid/non-constant constant", t->line); + return internConstant(p, c); +} + +static int lastExprInBlock(struct Token* t) +{ + if(!t->parent) return 1; + if(t->parent->type == TOK_TOP || t->parent->type == TOK_LCURL) return 1; + if(t->parent->type == TOK_SEMI) + if(!t->next || t->next->type == TOK_EMPTY) + return 1; + return 0; +} + +// Returns true if the node is in "tail context" -- either a child of +// a return, the last child of a func block, or else the +// last child of an if/elsif/if that is itself in tail context. +static int tailContext(struct Token* t) +{ + if(t->parent && t->parent->type == TOK_RETURN) + return 1; + else if(!lastExprInBlock(t)) + return 0; + + // Walk up the tree. It is ok to see semicolons, else's, elsifs + // and curlies. If we reach the top or a func, then we are in + // tail context. If we hit an if, then we are in tail context + // only if the "if" node is. + while((t = t->parent) != 0) + switch(t->type) { + case TOK_SEMI: case TOK_LCURL: break; + case TOK_ELSE: case TOK_ELSIF: break; + case TOK_TOP: case TOK_FUNC: return 1; + case TOK_IF: return tailContext(t); + default: return 0; + } + return 0; +} + static void genScalarConstant(struct Parser* p, struct Token* t) { - naRef c = (t->str - ? naStr_fromdata(naNewString(p->context), t->str, t->strlen) - : naNum(t->num)); - int idx = internConstant(p, c); - emitImmediate(p, OP_PUSHCONST, idx); + // These opcodes are for special-case use in other constructs, but + // we might as well use them here to save a few bytes in the + // instruction stream. + if(t->str == 0 && t->num == 1) { + emit(p, OP_PUSHONE); + } else if(t->str == 0 && t->num == 0) { + emit(p, OP_PUSHZERO); + } else { + int idx = findConstantIndex(p, t); + emitImmediate(p, OP_PUSHCONST, idx); + } } static int genLValue(struct Parser* p, struct Token* t) @@ -93,7 +146,7 @@ static int genLValue(struct Parser* p, struct Token* t) return genLValue(p, LEFT(t)); // Handle stuff like "(a) = 1" } else if(t->type == TOK_SYMBOL) { genScalarConstant(p, t); - return OP_SETLOCAL; + return OP_SETSYM; } else if(t->type == TOK_DOT && RIGHT(t) && RIGHT(t)->type == TOK_SYMBOL) { genExpr(p, LEFT(t)); genScalarConstant(p, RIGHT(t)); @@ -102,40 +155,85 @@ static int genLValue(struct Parser* p, struct Token* t) genExpr(p, LEFT(t)); genExpr(p, RIGHT(t)); return OP_INSERT; + } else if(t->type == TOK_VAR && RIGHT(t)->type == TOK_SYMBOL) { + genScalarConstant(p, RIGHT(t)); + return OP_SETLOCAL; } else { naParseError(p, "bad lvalue", t->line); return -1; } } -static void genLambda(struct Parser* p, struct Token* t) +static int defArg(struct Parser* p, struct Token* t) +{ + if(t->type == TOK_LPAR) return defArg(p, RIGHT(t)); + return findConstantIndex(p, t); +} + +static void genArgList(struct Parser* p, struct naCode* c, struct Token* t) +{ + naRef sym; + if(t->type == TOK_EMPTY) return; + if(!IDENTICAL(c->restArgSym, globals->argRef)) + naParseError(p, "remainder must be last", t->line); + if(t->type == TOK_ELLIPSIS) { + if(LEFT(t)->type != TOK_SYMBOL) + naParseError(p, "bad function argument expression", t->line); + sym = naStr_fromdata(naNewString(p->context), + LEFT(t)->str, LEFT(t)->strlen); + c->restArgSym = naInternSymbol(sym); + c->needArgVector = 1; + } else if(t->type == TOK_ASSIGN) { + if(LEFT(t)->type != TOK_SYMBOL) + naParseError(p, "bad function argument expression", t->line); + c->optArgSyms[c->nOptArgs] = findConstantIndex(p, LEFT(t)); + c->optArgVals[c->nOptArgs++] = defArg(p, RIGHT(t)); + } else if(t->type == TOK_SYMBOL) { + if(c->nOptArgs) + naParseError(p, "optional arguments must be last", t->line); + if(c->nArgs >= MAX_FUNARGS) + naParseError(p, "too many named function arguments", t->line); + c->argSyms[c->nArgs++] = findConstantIndex(p, t); + } else if(t->type == TOK_COMMA) { + genArgList(p, c, LEFT(t)); + genArgList(p, c, RIGHT(t)); + } else + naParseError(p, "bad function argument expression", t->line); +} + +static naRef newLambda(struct Parser* p, struct Token* t) { - int idx; struct CodeGenerator* cgSave; naRef codeObj; - if(LEFT(t)->type != TOK_LCURL) + struct Token* arglist; + if(RIGHT(t)->type != TOK_LCURL) naParseError(p, "bad function definition", t->line); // Save off the generator state while we do the new one cgSave = p->cg; - codeObj = naCodeGen(p, LEFT(LEFT(t))); + arglist = LEFT(t)->type == TOK_LPAR ? LEFT(LEFT(t)) : 0; + codeObj = naCodeGen(p, LEFT(RIGHT(t)), arglist); p->cg = cgSave; + return codeObj; +} - idx = newConstant(p, codeObj); - emitImmediate(p, OP_PUSHCONST, idx); +static void genLambda(struct Parser* p, struct Token* t) +{ + emitImmediate(p, OP_PUSHCONST, newConstant(p, newLambda(p, t))); } -static void genList(struct Parser* p, struct Token* t) +static int genList(struct Parser* p, struct Token* t, int doAppend) { if(t->type == TOK_COMMA) { genExpr(p, LEFT(t)); - emit(p, OP_VAPPEND); - genList(p, RIGHT(t)); + if(doAppend) emit(p, OP_VAPPEND); + return 1 + genList(p, RIGHT(t), doAppend); } else if(t->type == TOK_EMPTY) { - return; + return 0; } else { genExpr(p, t); - emit(p, OP_VAPPEND); + if(doAppend) emit(p, OP_VAPPEND); + return 1; } } @@ -165,18 +263,19 @@ static void genHash(struct Parser* p, struct Token* t) static void genFuncall(struct Parser* p, struct Token* t) { int op = OP_FCALL; + int nargs = 0; if(LEFT(t)->type == TOK_DOT) { genExpr(p, LEFT(LEFT(t))); emit(p, OP_DUP); - genScalarConstant(p, RIGHT(LEFT(t))); - emit(p, OP_MEMBER); + emitImmediate(p, OP_MEMBER, findConstantIndex(p, RIGHT(LEFT(t)))); op = OP_MCALL; } else { genExpr(p, LEFT(t)); } - emit(p, OP_NEWVEC); - if(RIGHT(t)) genList(p, RIGHT(t)); - emit(p, op); + if(RIGHT(t)) nargs = genList(p, RIGHT(t), 0); + if(tailContext(t)) + op = op == OP_FCALL ? OP_FTAIL : OP_MTAIL; + emitImmediate(p, op, nargs); } static void pushLoop(struct Parser* p, struct Token* label) @@ -202,17 +301,15 @@ static int emitJump(struct Parser* p, int op) { int ip; emit(p, op); - ip = p->cg->nBytes; - emit(p, 0xff); // dummy address - emit(p, 0xff); + ip = p->cg->codesz; + emit(p, 0xffff); // dummy address return ip; } // Points a previous jump instruction at the current "end-of-bytecode" static void fixJumpTarget(struct Parser* p, int spot) { - p->cg->byteCode[spot] = p->cg->nBytes >> 8; - p->cg->byteCode[spot+1] = p->cg->nBytes & 0xff; + p->cg->byteCode[spot] = p->cg->codesz; } static void genShortCircuit(struct Parser* p, struct Token* t) @@ -251,6 +348,20 @@ static void genIfElse(struct Parser* p, struct Token* t) genIf(p, t, t->children->next->next); } +static void genQuestion(struct Parser* p, struct Token* t) +{ + int jumpNext, jumpEnd; + if(!RIGHT(t) || RIGHT(t)->type != TOK_COLON) + naParseError(p, "invalid ?: expression", t->line); + genExpr(p, LEFT(t)); // the test + jumpNext = emitJump(p, OP_JIFNOT); + genExpr(p, LEFT(RIGHT(t))); // the "if true" expr + jumpEnd = emitJump(p, OP_JMP); + fixJumpTarget(p, jumpNext); + genExpr(p, RIGHT(RIGHT(t))); // the "else" expr + fixJumpTarget(p, jumpEnd); +} + static int countSemis(struct Token* t) { if(!t || t->type != TOK_SEMI) return 0; @@ -266,7 +377,7 @@ static void genLoop(struct Parser* p, struct Token* body, p->cg->loops[p->cg->loopTop-1].breakIP = jumpEnd-1; jumpOverContinue = emitJump(p, OP_JMP); - p->cg->loops[p->cg->loopTop-1].contIP = p->cg->nBytes; + p->cg->loops[p->cg->loopTop-1].contIP = p->cg->codesz; cont = emitJump(p, OP_JMP); fixJumpTarget(p, jumpOverContinue); @@ -274,7 +385,7 @@ static void genLoop(struct Parser* p, struct Token* body, emit(p, OP_POP); fixJumpTarget(p, cont); if(update) { genExpr(p, update); emit(p, OP_POP); } - emitImmediate(p, OP_JMP, loopTop); + emitImmediate(p, OP_JMPLOOP, loopTop); fixJumpTarget(p, jumpEnd); popLoop(p); emit(p, OP_PUSHNIL); // Leave something on the stack @@ -287,7 +398,7 @@ static void genForWhile(struct Parser* p, struct Token* init, int loopTop, jumpEnd; if(init) { genExpr(p, init); emit(p, OP_POP); } pushLoop(p, label); - loopTop = p->cg->nBytes; + loopTop = p->cg->codesz; genExpr(p, test); jumpEnd = emitJump(p, OP_JIFNOT); genLoop(p, body, update, label, loopTop, jumpEnd); @@ -352,7 +463,7 @@ static void genForEach(struct Parser* p, struct Token* t) pushLoop(p, label); genExpr(p, vec); emit(p, OP_PUSHZERO); - loopTop = p->cg->nBytes; + loopTop = p->cg->codesz; emit(p, OP_EACH); jumpEnd = emitJump(p, OP_JIFNIL); assignOp = genLValue(p, elem); @@ -393,18 +504,34 @@ static void genBreakContinue(struct Parser* p, struct Token* t) emitImmediate(p, OP_JMP, t->type == TOK_BREAK ? bp : cp); } +static void newLineEntry(struct Parser* p, int line) +{ + int i; + if(p->cg->nextLineIp >= p->cg->nLineIps) { + int nsz = p->cg->nLineIps*2 + 1; + unsigned short* n = naParseAlloc(p, sizeof(unsigned short)*2*nsz); + for(i=0; i<(p->cg->nextLineIp*2); i++) + n[i] = p->cg->lineIps[i]; + p->cg->lineIps = n; + p->cg->nLineIps = nsz; + } + p->cg->lineIps[p->cg->nextLineIp++] = (unsigned short) p->cg->codesz; + p->cg->lineIps[p->cg->nextLineIp++] = (unsigned short) line; +} + static void genExpr(struct Parser* p, struct Token* t) { int i; - if(t == 0) - naParseError(p, "BUG: null subexpression", -1); if(t->line != p->cg->lastLine) - emitImmediate(p, OP_LINE, t->line); + newLineEntry(p, t->line); p->cg->lastLine = t->line; switch(t->type) { case TOK_IF: genIfElse(p, t); break; + case TOK_QUESTION: + genQuestion(p, t); + break; case TOK_WHILE: genWhile(p, t); break; @@ -432,7 +559,7 @@ static void genExpr(struct Parser* p, struct Token* t) genBinOp(OP_EXTRACT, p, t); // a[i] } else { emit(p, OP_NEWVEC); - genList(p, LEFT(t)); + genList(p, LEFT(t), 1); } break; case TOK_LCURL: @@ -447,6 +574,7 @@ static void genExpr(struct Parser* p, struct Token* t) case TOK_RETURN: if(RIGHT(t)) genExpr(p, RIGHT(t)); else emit(p, OP_PUSHNIL); + for(i=0; icg->loopTop; i++) emit(p, OP_UNMARK); emit(p, OP_RETURN); break; case TOK_NOT: @@ -454,8 +582,7 @@ static void genExpr(struct Parser* p, struct Token* t) emit(p, OP_NOT); break; case TOK_SYMBOL: - genScalarConstant(p, t); - emit(p, OP_LOCAL); + emitImmediate(p, OP_LOCAL, findConstantIndex(p, t)); break; case TOK_LITERAL: genScalarConstant(p, t); @@ -479,8 +606,7 @@ static void genExpr(struct Parser* p, struct Token* t) genExpr(p, LEFT(t)); if(RIGHT(t)->type != TOK_SYMBOL) naParseError(p, "object field not symbol", RIGHT(t)->line); - genScalarConstant(p, RIGHT(t)); - emit(p, OP_MEMBER); + emitImmediate(p, OP_MEMBER, findConstantIndex(p, RIGHT(t))); break; case TOK_EMPTY: case TOK_NIL: emit(p, OP_PUSHNIL); break; // *NOT* a noop! @@ -515,7 +641,7 @@ static void genExprList(struct Parser* p, struct Token* t) } } -naRef naCodeGen(struct Parser* p, struct Token* t) +naRef naCodeGen(struct Parser* p, struct Token* block, struct Token* arglist) { int i; naRef codeObj; @@ -524,26 +650,62 @@ naRef naCodeGen(struct Parser* p, struct Token* t) cg.lastLine = 0; cg.codeAlloced = 1024; // Start fairly big, this is a cheap allocation - cg.byteCode = naParseAlloc(p, cg.codeAlloced); - cg.nBytes = 0; + cg.byteCode = naParseAlloc(p, cg.codeAlloced *sizeof(unsigned short)); + cg.codesz = 0; cg.consts = naNewVector(p->context); cg.loopTop = 0; + cg.lineIps = 0; + cg.nLineIps = 0; + cg.nextLineIp = 0; p->cg = &cg; - genExprList(p, t); + genExprList(p, block); + emit(p, OP_RETURN); // Now make a code object codeObj = naNewCode(p->context); code = codeObj.ref.ptr.code; - code->nBytes = cg.nBytes; - code->byteCode = naAlloc(cg.nBytes); - for(i=0; i < cg.nBytes; i++) + + // Parse the argument list, if any + code->restArgSym = globals->argRef; + code->nArgs = code->nOptArgs = 0; + code->argSyms = code->optArgSyms = code->optArgVals = 0; + code->needArgVector = 1; + if(arglist) { + code->argSyms = naParseAlloc(p, sizeof(int) * MAX_FUNARGS); + code->optArgSyms = naParseAlloc(p, sizeof(int) * MAX_FUNARGS); + code->optArgVals = naParseAlloc(p, sizeof(int) * MAX_FUNARGS); + code->needArgVector = 0; + genArgList(p, code, arglist); + if(code->nArgs) { + int i, *nsyms; + nsyms = naAlloc(sizeof(int) * code->nArgs); + for(i=0; inArgs; i++) nsyms[i] = code->argSyms[i]; + code->argSyms = nsyms; + } else code->argSyms = 0; + if(code->nOptArgs) { + int i, *nsyms, *nvals; + nsyms = naAlloc(sizeof(int) * code->nOptArgs); + nvals = naAlloc(sizeof(int) * code->nOptArgs); + for(i=0; inOptArgs; i++) nsyms[i] = code->optArgSyms[i]; + for(i=0; inOptArgs; i++) nvals[i] = code->optArgVals[i]; + code->optArgSyms = nsyms; + code->optArgVals = nvals; + } else code->optArgSyms = code->optArgVals = 0; + } + + code->codesz = cg.codesz; + code->byteCode = naAlloc(cg.codesz * sizeof(unsigned short)); + for(i=0; i < cg.codesz; i++) code->byteCode[i] = cg.byteCode[i]; code->nConstants = naVec_size(cg.consts); code->constants = naAlloc(code->nConstants * sizeof(naRef)); code->srcFile = p->srcFile; for(i=0; inConstants; i++) code->constants[i] = getConstant(p, i); - + code->nLines = p->cg->nextLineIp; + code->lineIps = naAlloc(sizeof(unsigned short)*p->cg->nLineIps*2); + for(i=0; icg->nLineIps*2; i++) + code->lineIps[i] = p->cg->lineIps[i]; return codeObj; } diff --git a/simgear/nasal/data.h b/simgear/nasal/data.h index 4ed0f07c..28fc4bfd 100644 --- a/simgear/nasal/data.h +++ b/simgear/nasal/data.h @@ -5,25 +5,27 @@ // Notes: A CODE object is a compiled set of bytecode instructions. // What actually gets executed at runtime is a bound FUNC object, -// which combines the raw code with a pointer to a CLOSURE chain of -// namespaces. -enum { T_STR, T_VEC, T_HASH, T_CODE, T_CLOSURE, T_FUNC, T_CCODE, T_GHOST, +// which combines the raw code with a namespace and a pointer to +// parent function in the lexical closure. +enum { T_STR, T_VEC, T_HASH, T_CODE, T_FUNC, T_CCODE, T_GHOST, NUM_NASAL_TYPES }; // V. important that this come last! #define IS_REF(r) ((r).ref.reftag == NASAL_REFTAG) #define IS_NUM(r) ((r).ref.reftag != NASAL_REFTAG) #define IS_OBJ(r) (IS_REF((r)) && (r).ref.ptr.obj != 0) +//#define IS_OBJ(r) (IS_REF((r)) && (r).ref.ptr.obj != 0 && (((r).ref.ptr.obj->type == 123) ? *(int*)0 : 1)) #define IS_NIL(r) (IS_REF((r)) && (r).ref.ptr.obj == 0) #define IS_STR(r) (IS_OBJ((r)) && (r).ref.ptr.obj->type == T_STR) #define IS_VEC(r) (IS_OBJ((r)) && (r).ref.ptr.obj->type == T_VEC) #define IS_HASH(r) (IS_OBJ((r)) && (r).ref.ptr.obj->type == T_HASH) #define IS_CODE(r) (IS_OBJ((r)) && (r).ref.ptr.obj->type == T_CODE) #define IS_FUNC(r) (IS_OBJ((r)) && (r).ref.ptr.obj->type == T_FUNC) -#define IS_CLOSURE(r) (IS_OBJ((r)) && (r).ref.ptr.obj->type == T_CLOSURE) #define IS_CCODE(r) (IS_OBJ((r)) && (r).ref.ptr.obj->type == T_CCODE) #define IS_GHOST(r) (IS_OBJ((r)) && (r).ref.ptr.obj->type == T_GHOST) #define IS_CONTAINER(r) (IS_VEC(r)||IS_HASH(r)) #define IS_SCALAR(r) (IS_NUM((r)) || IS_STR((r))) +#define IDENTICAL(a, b) (IS_REF(a) && IS_REF(b) \ + && a.ref.ptr.obj == b.ref.ptr.obj) // This is a macro instead of a separate struct to allow compilers to // avoid padding. GCC on x86, at least, will always padd the size of @@ -31,7 +33,7 @@ enum { T_STR, T_VEC, T_HASH, T_CODE, T_CLOSURE, T_FUNC, T_CCODE, T_GHOST, // implementing objects to pack in 16 bits worth of data "for free". #define GC_HEADER \ unsigned char mark; \ - unsigned char type + unsigned char type; \ struct naObj { GC_HEADER; @@ -41,13 +43,18 @@ struct naStr { GC_HEADER; int len; unsigned char* data; + unsigned int hashcode; }; -struct naVec { - GC_HEADER; +struct VecRec { int size; int alloced; - naRef* array; + naRef array[]; +}; + +struct naVec { + GC_HEADER; + struct VecRec* rec; }; struct HashNode { @@ -56,32 +63,40 @@ struct HashNode { struct HashNode* next; }; -struct naHash { - GC_HEADER; +struct HashRec { int size; + int dels; int lgalloced; struct HashNode* nodes; - struct HashNode** table; - int nextnode; + struct HashNode* table[]; +}; + +struct naHash { + GC_HEADER; + struct HashRec* rec; }; struct naCode { GC_HEADER; - unsigned char* byteCode; - int nBytes; + unsigned char nArgs; + unsigned char nOptArgs; + unsigned char needArgVector; + unsigned short nConstants; + unsigned short nLines; + unsigned short codesz; + unsigned short* byteCode; naRef* constants; - int nConstants; + int* argSyms; // indices into constants + int* optArgSyms; + int* optArgVals; + unsigned short* lineIps; // pairs of {ip, line} naRef srcFile; + naRef restArgSym; // The "..." vector name, defaults to "arg" }; struct naFunc { GC_HEADER; naRef code; - naRef closure; -}; - -struct naClosure { - GC_HEADER; naRef namespace; naRef next; // parent closure }; @@ -100,11 +115,12 @@ struct naGhost { struct naPool { int type; int elemsz; - int nblocks; struct Block* blocks; - int nfree; // number of entries in the free array - int freesz; // size of the free array - void** free; // pointers to usable elements + void** free0; // pointer to the alloced buffer + int freesz; // size of the alloced buffer + void** free; // current "free frame" + int nfree; // down-counting index within the free frame + int freetop; // curr. top of the free list }; void naFree(void* m); @@ -112,11 +128,9 @@ void* naAlloc(int n); void naBZero(void* m, int n); int naTypeSize(int type); -void naGarbageCollect(); naRef naObj(int type, struct naObj* o); naRef naNew(naContext c, int type); naRef naNewCode(naContext c); -naRef naNewClosure(naContext c, naRef namespace, naRef next); int naStr_equal(naRef s1, naRef s2); naRef naStr_fromnum(naRef dest, double num); @@ -124,16 +138,14 @@ int naStr_numeric(naRef str); int naStr_parsenum(char* str, int len, double* result); int naStr_tonum(naRef str, double* out); -void naVec_init(naRef vec); - int naHash_tryset(naRef hash, naRef key, naRef val); // sets if exists -void naHash_init(naRef hash); +int naHash_sym(struct naHash* h, struct naStr* sym, naRef* out); +void naHash_newsym(struct naHash* h, naRef* sym, naRef* val); void naGC_init(struct naPool* p, int type); -struct naObj* naGC_get(struct naPool* p); -int naGC_size(struct naPool* p); -void naGC_mark(naRef r); -void naGC_reap(struct naPool* p); +struct naObj** naGC_get(struct naPool* p, int n, int* nout); +void naGC_swapfree(void** target, void* val); +void naGC_freedead(); void naStr_gcclean(struct naStr* s); void naVec_gcclean(struct naVec* s); diff --git a/simgear/nasal/debug.c b/simgear/nasal/debug.c deleted file mode 100644 index aedf441e..00000000 --- a/simgear/nasal/debug.c +++ /dev/null @@ -1,211 +0,0 @@ -#include -#include -#include - -#include "nasal.h" -#include "parse.h" -#include "code.h" - -// Bytecode operator to string -char* opStringDEBUG(int op) -{ - static char buf[256]; - switch(op) { - case OP_AND: return "AND"; - case OP_OR: return "OR"; - case OP_NOT: return "NOT"; - case OP_MUL: return "MUL"; - case OP_PLUS: return "PLUS"; - case OP_MINUS: return "MINUS"; - case OP_DIV: return "DIV"; - case OP_NEG: return "NEG"; - case OP_CAT: return "CAT"; - case OP_LT: return "LT"; - case OP_LTE: return "LTE"; - case OP_GT: return "GT"; - case OP_GTE: return "GTE"; - case OP_EQ: return "EQ"; - case OP_NEQ: return "NEQ"; - case OP_EACH: return "EACH"; - case OP_JMP: return "JMP"; - case OP_JIFNOT: return "JIFNOT"; - case OP_JIFNIL: return "JIFNIL"; - case OP_FCALL: return "FCALL"; - case OP_MCALL: return "MCALL"; - case OP_RETURN: return "RETURN"; - case OP_PUSHCONST: return "PUSHCONST"; - case OP_PUSHONE: return "PUSHONE"; - case OP_PUSHZERO: return "PUSHZERO"; - case OP_PUSHNIL: return "PUSHNIL"; - case OP_POP: return "POP"; - case OP_DUP: return "DUP"; - case OP_XCHG: return "XCHG"; - case OP_INSERT: return "INSERT"; - case OP_EXTRACT: return "EXTRACT"; - case OP_MEMBER: return "MEMBER"; - case OP_SETMEMBER: return "SETMEMBER"; - case OP_LOCAL: return "LOCAL"; - case OP_SETLOCAL: return "SETLOCAL"; - case OP_NEWVEC: return "NEWVEC"; - case OP_VAPPEND: return "VAPPEND"; - case OP_NEWHASH: return "NEWHASH"; - case OP_HAPPEND: return "HAPPEND"; - case OP_LINE: return "LINE"; - case OP_MARK: return "MARK"; - case OP_UNMARK: return "UNMARK"; - case OP_BREAK: return "BREAK"; - } - sprintf(buf, "\n", op); - return buf; -} - -// Print a bytecode operator -void printOpDEBUG(int ip, int op) -{ - printf("IP: %d OP: %s\n", ip, opStringDEBUG(op)); -} - -// Print a naRef -void printRefDEBUG(naRef r) -{ - int i; - if(IS_NUM(r)) { - printf("%f\n", r.num); - } else if(IS_NIL(r)) { - printf("\n"); - } else if(IS_STR(r)) { - printf("\""); - for(i=0; ilen; i++) - printf("%c", r.ref.ptr.str->data[i]); - printf("\"\n"); - } else if(IS_VEC(r)) { - printf("\n"); - } else if(IS_HASH(r)) { - printf("\n"); - } else if(IS_FUNC(r)) { - printf("\n"); - } else if(IS_CLOSURE(r)) { - printf("DEBUG: closure object on stack!\n"); - } else if(IS_CODE(r)) { - printf("DEBUG: code object on stack!\n"); - } else printf("DEBUG ACK\n"); -} - -// Print the operand stack of the specified context -void printStackDEBUG(struct Context* ctx) -{ - int i; - printf("\n"); - for(i=ctx->opTop-1; i>=0; i--) { - printf("] "); - printRefDEBUG(ctx->opStack[i]); - } - printf("\n"); -} - -// Token type to string -char* tokString(int tok) -{ - switch(tok) { - case TOK_TOP: return "TOK_TOP"; - case TOK_AND: return "TOK_AND"; - case TOK_OR: return "TOK_OR"; - case TOK_NOT: return "TOK_NOT"; - case TOK_LPAR: return "TOK_LPAR"; - case TOK_RPAR: return "TOK_RPAR"; - case TOK_LBRA: return "TOK_LBRA"; - case TOK_RBRA: return "TOK_RBRA"; - case TOK_LCURL: return "TOK_LCURL"; - case TOK_RCURL: return "TOK_RCURL"; - case TOK_MUL: return "TOK_MUL"; - case TOK_PLUS: return "TOK_PLUS"; - case TOK_MINUS: return "TOK_MINUS"; - case TOK_NEG: return "TOK_NEG"; - case TOK_DIV: return "TOK_DIV"; - case TOK_CAT: return "TOK_CAT"; - case TOK_COLON: return "TOK_COLON"; - case TOK_DOT: return "TOK_DOT"; - case TOK_COMMA: return "TOK_COMMA"; - case TOK_SEMI: return "TOK_SEMI"; - case TOK_ASSIGN: return "TOK_ASSIGN"; - case TOK_LT: return "TOK_LT"; - case TOK_LTE: return "TOK_LTE"; - case TOK_EQ: return "TOK_EQ"; - case TOK_NEQ: return "TOK_NEQ"; - case TOK_GT: return "TOK_GT"; - case TOK_GTE: return "TOK_GTE"; - case TOK_IF: return "TOK_IF"; - case TOK_ELSIF: return "TOK_ELSIF"; - case TOK_ELSE: return "TOK_ELSE"; - case TOK_FOR: return "TOK_FOR"; - case TOK_FOREACH: return "TOK_FOREACH"; - case TOK_WHILE: return "TOK_WHILE"; - case TOK_RETURN: return "TOK_RETURN"; - case TOK_BREAK: return "TOK_BREAK"; - case TOK_CONTINUE: return "TOK_CONTINUE"; - case TOK_FUNC: return "TOK_FUNC"; - case TOK_SYMBOL: return "TOK_SYMBOL"; - case TOK_LITERAL: return "TOK_LITERAL"; - case TOK_EMPTY: return "TOK_EMPTY"; - case TOK_NIL: return "TOK_NIL"; - } - return 0; -} - -// Diagnostic: check all list pointers for sanity -void ack() -{ - printf("Bad token list!\n"); - exit(1); -} -void checkList(struct Token* start, struct Token* end) -{ - struct Token* t = start; - while(t) { - if(t->next && t->next->prev != t) ack(); - if(t->next==0 && t != end) ack(); - t = t->next; - } - t = end; - while(t) { - if(t->prev && t->prev->next != t) ack(); - if(t->prev==0 && t != start) ack(); - t = t->prev; - }; -} - - -// Prints a single parser token to stdout -void printToken(struct Token* t, char* prefix) -{ - int i; - printf("%sline %d %s ", prefix, t->line, tokString(t->type)); - if(t->type == TOK_LITERAL || t->type == TOK_SYMBOL) { - if(t->str) { - printf("\""); - for(i=0; istrlen; i++) printf("%c", t->str[i]); - printf("\" (len: %d)", t->strlen); - } else { - printf("%f ", t->num); - } - } - printf("\n"); -} - -// Prints a parse tree to stdout -void dumpTokenList(struct Token* t, int prefix) -{ - char prefstr[128]; - int i; - - prefstr[0] = 0; - for(i=0; ichildren, prefix+1); - t = t->next; - } -} - diff --git a/simgear/nasal/gc.c b/simgear/nasal/gc.c index 60f6c422..a8c0976e 100644 --- a/simgear/nasal/gc.c +++ b/simgear/nasal/gc.c @@ -1,44 +1,123 @@ #include "nasal.h" #include "data.h" +#include "code.h" #define MIN_BLOCK_SIZE 256 // "type" for an object freed by the collector #define T_GCFREED 123 // DEBUG +static void reap(struct naPool* p); +static void mark(naRef r); + struct Block { int size; char* block; + struct Block* next; }; -// Decremented every allocation. When it reaches zero, we do a -// garbage collection. The value is reset to 1/2 of the total object -// count each collection, which is sane: it ensures that no more than -// 50% growth can happen between collections, and ensures that garbage -// collection work is constant with allocation work (i.e. that O(N) -// work is done only every O(1/2N) allocations). -static int GlobalAllocCount = 256; - -static void appendfree(struct naPool*p, struct naObj* o) -{ - // Need more space? - if(p->freesz <= p->nfree) { - int i, n = 1+((3*p->nfree)>>1); - void** newf = naAlloc(n * sizeof(void*)); - for(i=0; infree; i++) - newf[i] = p->free[i]; - naFree(p->free); - p->free = newf; - p->freesz = n; +// Must be called with the giant exclusive lock! +static void freeDead() +{ + int i; + for(i=0; indead; i++) + naFree(globals->deadBlocks[i]); + globals->ndead = 0; +} + + +// Must be called with the big lock! +static void garbageCollect() +{ + int i; + struct Context* c; + globals->allocCount = 0; + c = globals->allContexts; + while(c) { + for(i=0; infree[i] = 0; + for(i=0; i < c->fTop; i++) { + mark(c->fStack[i].func); + mark(c->fStack[i].locals); + } + for(i=0; i < c->opTop; i++) + mark(c->opStack[i]); + mark(c->dieArg); + mark(c->temps); + c = c->nextAll; } - p->free[p->nfree++] = o; + mark(globals->save); + mark(globals->symbols); + mark(globals->meRef); + mark(globals->argRef); + mark(globals->parentsRef); + + // Finally collect all the freed objects + for(i=0; ipools[i])); + + // Make enough space for the dead blocks we need to free during + // execution. This works out to 1 spot for every 2 live objects, + // which should be limit the number of bottleneck operations + // without imposing an undue burden of extra "freeable" memory. + if(globals->deadsz < globals->allocCount) { + globals->deadsz = globals->allocCount; + if(globals->deadsz < 256) globals->deadsz = 256; + naFree(globals->deadBlocks); + globals->deadBlocks = naAlloc(sizeof(void*) * globals->deadsz); + } + globals->needGC = 0; +} + +void naModLock() +{ + naCheckBottleneck(); + LOCK(); + globals->nThreads++; + UNLOCK(); +} + +void naModUnlock() +{ + LOCK(); + globals->nThreads--; + UNLOCK(); +} + +// Must be called with the main lock. Engages the "bottleneck", where +// all threads will block so that one (the last one to call this +// function) can run alone. This is done for GC, and also to free the +// list of "dead" blocks when it gets full (which is part of GC, if +// you think about it). +static void bottleneck() +{ + struct Globals* g = globals; + g->bottleneck = 1; + while(g->bottleneck && g->waitCount < g->nThreads - 1) { + g->waitCount++; + UNLOCK(); naSemDown(g->sem); LOCK(); + g->waitCount--; + } + if(g->waitCount >= g->nThreads - 1) { + freeDead(); + if(g->needGC) garbageCollect(); + if(g->waitCount) naSemUpAll(g->sem, g->waitCount); + g->bottleneck = 0; + } +} + +void naCheckBottleneck() +{ + if(globals->bottleneck) { LOCK(); bottleneck(); UNLOCK(); } } static void naCode_gcclean(struct naCode* o) { - naFree(o->byteCode); o->byteCode = 0; - naFree(o->constants); o->constants = 0; + naFree(o->byteCode); o->byteCode = 0; + naFree(o->constants); o->constants = 0; + naFree(o->argSyms); o->argSyms = 0; + naFree(o->optArgSyms); o->argSyms = 0; } static void naGhost_gcclean(struct naGhost* g) @@ -49,9 +128,6 @@ static void naGhost_gcclean(struct naGhost* g) static void freeelem(struct naPool* p, struct naObj* o) { - // Mark the object as "freed" for debugging purposes - o->type = T_GCFREED; // DEBUG - // Free any intrinsic (i.e. non-garbage collected) storage the // object might have switch(p->type) { @@ -73,77 +149,78 @@ static void freeelem(struct naPool* p, struct naObj* o) } // And add it to the free list - appendfree(p, o); + o->type = T_GCFREED; // DEBUG + p->free[p->nfree++] = o; } static void newBlock(struct naPool* p, int need) { int i; - char* buf; - struct Block* newblocks; + struct Block* newb; - if(need < MIN_BLOCK_SIZE) - need = MIN_BLOCK_SIZE; - - newblocks = naAlloc((p->nblocks+1) * sizeof(struct Block)); - for(i=0; inblocks; i++) newblocks[i] = p->blocks[i]; - naFree(p->blocks); - p->blocks = newblocks; - buf = naAlloc(need * p->elemsz); - naBZero(buf, need * p->elemsz); - p->blocks[p->nblocks].size = need; - p->blocks[p->nblocks].block = buf; - p->nblocks++; + if(need < MIN_BLOCK_SIZE) need = MIN_BLOCK_SIZE; + + newb = naAlloc(sizeof(struct Block)); + newb->block = naAlloc(need * p->elemsz); + newb->size = need; + newb->next = p->blocks; + p->blocks = newb; + naBZero(newb->block, need * p->elemsz); - for(i=0; ielemsz); + if(need > p->freesz - p->freetop) need = p->freesz - p->freetop; + p->nfree = 0; + p->free = p->free0 + p->freetop; + for(i=0; i < need; i++) { + struct naObj* o = (struct naObj*)(newb->block + i*p->elemsz); o->mark = 0; - o->type = p->type; - appendfree(p, o); + o->type = T_GCFREED; // DEBUG + p->free[p->nfree++] = o; } + p->freetop += need; } void naGC_init(struct naPool* p, int type) { p->type = type; p->elemsz = naTypeSize(type); - p->nblocks = 0; p->blocks = 0; - p->nfree = 0; - p->freesz = 0; - p->free = 0; - naGC_reap(p); + + p->free0 = p->free = 0; + p->nfree = p->freesz = p->freetop = 0; + reap(p); } -int naGC_size(struct naPool* p) +static int poolsize(struct naPool* p) { - int i, total=0; - for(i=0; inblocks; i++) - total += ((struct Block*)(p->blocks + i))->size; + int total = 0; + struct Block* b = p->blocks; + while(b) { total += b->size; b = b->next; } return total; } -struct naObj* naGC_get(struct naPool* p) +struct naObj** naGC_get(struct naPool* p, int n, int* nout) { - // Collect every GlobalAllocCount allocations. - // This gets set to ~50% of the total object count each - // collection (it's incremented in naGC_reap()). - if(--GlobalAllocCount < 0) { - GlobalAllocCount = 0; - naGarbageCollect(); + struct naObj** result; + naCheckBottleneck(); + LOCK(); + while(globals->allocCount < 0 || (p->nfree == 0 && p->freetop >= p->freesz)) { + globals->needGC = 1; + bottleneck(); } - - // If we're out, then allocate an extra 12.5% if(p->nfree == 0) - newBlock(p, naGC_size(p)/8); - return p->free[--p->nfree]; + newBlock(p, poolsize(p)/8); + n = p->nfree < n ? p->nfree : n; + *nout = n; + p->nfree -= n; + globals->allocCount -= n; + result = (struct naObj**)(p->free + p->nfree); + UNLOCK(); + return result; } // Sets the reference bit on the object, and recursively on all -// objects reachable from it. Clumsy: uses C stack recursion, which -// is slower than it need be and may cause problems on some platforms -// due to the very large stack depths that result. -void naGC_mark(naRef r) +// objects reachable from it. Uses the processor stack for recursion... +static void mark(naRef r) { int i; @@ -159,56 +236,61 @@ void naGC_mark(naRef r) r.ref.ptr.obj->mark = 1; switch(r.ref.ptr.obj->type) { case T_VEC: - for(i=0; isize; i++) - naGC_mark(r.ref.ptr.vec->array[i]); + if(r.ref.ptr.vec->rec) + for(i=0; irec->size; i++) + mark(r.ref.ptr.vec->rec->array[i]); break; case T_HASH: - if(r.ref.ptr.hash->table == 0) - break; - for(i=0; i < (1<lgalloced); i++) { - struct HashNode* hn = r.ref.ptr.hash->table[i]; - while(hn) { - naGC_mark(hn->key); - naGC_mark(hn->val); - hn = hn->next; + if(r.ref.ptr.hash->rec != 0) { + struct HashRec* hr = r.ref.ptr.hash->rec; + for(i=0; i < (1<lgalloced); i++) { + struct HashNode* hn = hr->table[i]; + while(hn) { + mark(hn->key); + mark(hn->val); + hn = hn->next; + } } } break; case T_CODE: - naGC_mark(r.ref.ptr.code->srcFile); + mark(r.ref.ptr.code->srcFile); for(i=0; inConstants; i++) - naGC_mark(r.ref.ptr.code->constants[i]); - break; - case T_CLOSURE: - naGC_mark(r.ref.ptr.closure->namespace); - naGC_mark(r.ref.ptr.closure->next); + mark(r.ref.ptr.code->constants[i]); break; case T_FUNC: - naGC_mark(r.ref.ptr.func->code); - naGC_mark(r.ref.ptr.func->closure); + mark(r.ref.ptr.func->code); + mark(r.ref.ptr.func->namespace); + mark(r.ref.ptr.func->next); break; } } // Collects all the unreachable objects into a free list, and // allocates more space if needed. -void naGC_reap(struct naPool* p) +static void reap(struct naPool* p) { - int i, elem, total = 0; + struct Block* b; + int elem, freesz, total = poolsize(p); p->nfree = 0; - for(i=0; inblocks; i++) { - struct Block* b = p->blocks + i; - total += b->size; + freesz = total < MIN_BLOCK_SIZE ? MIN_BLOCK_SIZE : total; + freesz = (3 * freesz / 2) + (globals->nThreads * OBJ_CACHE_SZ); + if(p->freesz < freesz) { + naFree(p->free0); + p->freesz = freesz; + p->free = p->free0 = naAlloc(sizeof(void*) * p->freesz); + } + + for(b = p->blocks; b; b = b->next) for(elem=0; elem < b->size; elem++) { struct naObj* o = (struct naObj*)(b->block + elem * p->elemsz); if(o->mark == 0) freeelem(p, o); o->mark = 0; } - } - // Add 50% of our total to the global count - GlobalAllocCount += total/2; + // allocs of this type until the next collection + globals->allocCount += total/2; // Allocate more if necessary (try to keep 25-50% of the objects // available) @@ -219,5 +301,18 @@ void naGC_reap(struct naPool* p) if(need > 0) newBlock(p, need); } + p->freetop = p->nfree; } +// Atomically replaces target with a new pointer, and adds the old one +// to the list of blocks to free the next time something holds the +// giant lock. +void naGC_swapfree(void** target, void* val) +{ + LOCK(); + while(globals->ndead >= globals->deadsz) + bottleneck(); + globals->deadBlocks[globals->ndead++] = *target; + *target = val; + UNLOCK(); +} diff --git a/simgear/nasal/hash.c b/simgear/nasal/hash.c index 8c8e537d..1de3fdf5 100644 --- a/simgear/nasal/hash.c +++ b/simgear/nasal/hash.c @@ -1,42 +1,22 @@ #include "nasal.h" #include "data.h" -static void realloc(naRef hash) -{ - struct naHash* h = hash.ref.ptr.hash; - int i, sz, oldsz = h->size; - int oldcols = h->table ? 1 << h->lgalloced : 0; - - // Keep a handle to our original objects - struct HashNode* oldnodes = h->nodes; - struct HashNode** oldtable = h->table; - - // Figure out how big we need to be (start with a minimum size of - // 16 entries) - for(i=3; 1<lgalloced = i+1; - - // Allocate new ones (note that all the records are allocated in a - // single chunk, to avoid zillions of tiny node allocations) - sz = 1<lgalloced; - h->nodes = naAlloc(sz * (sizeof(struct HashNode) + sizeof(void*))); - h->table = (struct HashNode**)(((char*)h->nodes) + sz*sizeof(struct HashNode)); - naBZero(h->table, sz * sizeof(void*)); - h->nextnode = 0; - h->size = 0; - - // Re-insert everything from scratch - for(i=0; ikey, hn->val); - hn = hn->next; - } - } +#define MIN_HASH_SIZE 4 - // Free the old memory - naFree(oldnodes); -} +#define EQUAL(a, b) (((a).ref.reftag == (b).ref.reftag \ + && (a).ref.ptr.obj == (b).ref.ptr.obj) \ + || naEqual(a, b)) + +#define HASH_MAGIC 2654435769u + +#define INSERT(hh, hkey, hval, hcol) do { \ + unsigned int cc = (hcol), iidx=(hh)->size++; \ + if(iidx < (1<<(hh)->lgalloced)) { \ + struct HashNode* hnn = &(hh)->nodes[iidx]; \ + hnn->key = (hkey); hnn->val = (hval); \ + hnn->next = (hh)->table[cc]; \ + (hh)->table[cc] = hnn; \ + }} while(0) // Computes a hash code for a given scalar static unsigned int hashcode(naRef r) @@ -48,61 +28,106 @@ static unsigned int hashcode(naRef r) // 2*sizeof(int). unsigned int* p = (unsigned int*)&(r.num); return p[0] ^ p[1]; + } else if(r.ref.ptr.str->hashcode) { + return r.ref.ptr.str->hashcode; } else { // This is Daniel Bernstein's djb2 hash function that I found // on the web somewhere. It appears to work pretty well. unsigned int i, hash = 5831; for(i=0; ilen; i++) hash = (hash * 33) ^ r.ref.ptr.str->data[i]; + r.ref.ptr.str->hashcode = hash; return hash; } } // Which column in a given hash does the key correspond to. -static unsigned int hashcolumn(struct naHash* h, naRef key) +static unsigned int hashcolumn(struct HashRec* h, naRef key) { // Multiply by a big number, and take the top N bits. Note // assumption that sizeof(unsigned int) == 4. - return (2654435769u * hashcode(key)) >> (32 - h->lgalloced); + return (HASH_MAGIC * hashcode(key)) >> (32 - h->lgalloced); } -struct HashNode* find(struct naHash* h, naRef key) +static struct HashRec* realloc(struct naHash* hash) { - struct HashNode* hn; - if(h->table == 0) - return 0; - hn = h->table[hashcolumn(h, key)]; - while(hn) { - if(naEqual(key, hn->key)) - return hn; - hn = hn->next; + struct HashRec *h, *h0 = hash->rec; + int lga, cols, need = h0 ? h0->size - h0->dels : MIN_HASH_SIZE; + + for(lga=0; 1<lgalloced = lga; + h->nodes = (struct HashNode*)(((char*)h) + + sizeof(struct HashRec) + + cols * sizeof(struct HashNode*)); + for(lga=0; h0 != 0 && lga<(1<lgalloced); lga++) { + struct HashNode* hn = h0->table[lga]; + while(hn) { + INSERT(h, hn->key, hn->val, hashcolumn(h, hn->key)); + hn = hn->next; + } + } + naGC_swapfree((void**)&hash->rec, h); + return h; +} + +// Special, optimized version of naHash_get for the express purpose of +// looking up symbols in the local variables hash (OP_LOCAL is by far +// the most common opcode and deserves some special case +// optimization). Elides all the typing checks that are normally +// required, presumes that the key is a string and has had its +// hashcode precomputed, checks only for object identity, and inlines +// the column computation. +int naHash_sym(struct naHash* hash, struct naStr* sym, naRef* out) +{ + struct HashRec* h = hash->rec; + if(h) { + int col = (HASH_MAGIC * sym->hashcode) >> (32 - h->lgalloced); + struct HashNode* hn = h->table[col]; + while(hn) { + if(hn->key.ref.ptr.str == sym) { + *out = hn->val; + return 1; + } + hn = hn->next; + } } return 0; } -void naHash_init(naRef hash) +static struct HashNode* find(struct naHash* hash, naRef key) { - struct naHash* h = hash.ref.ptr.hash; - h->size = 0; - h->lgalloced = 0; - h->table = 0; - h->nodes = 0; + struct HashRec* h = hash->rec; + if(h) { + struct HashNode* hn = h->table[hashcolumn(h, key)]; + while(hn) { + if(EQUAL(key, hn->key)) + return hn; + hn = hn->next; + } + } + return 0; } // Make a temporary string on the stack -static naRef tmpStr(struct naStr* str, char* key) +static void tmpStr(naRef* out, struct naStr* str, char* key) { - char* p = key; - while(*p) { p++; } - str->len = p - key; + str->len = 0; str->data = key; - return naObj(T_STR, (struct naObj*)str); + while(key[str->len]) str->len++; + *out = naNil(); + out->ref.ptr.str = str; } naRef naHash_cget(naRef hash, char* key) { struct naStr str; - naRef result, key2 = tmpStr(&str, key); + naRef result, key2; + tmpStr(&key2, &str, key); if(naHash_get(hash, key2, &result)) return result; return naNil(); @@ -111,80 +136,86 @@ naRef naHash_cget(naRef hash, char* key) void naHash_cset(naRef hash, char* key, naRef val) { struct naStr str; - naRef key2 = tmpStr(&str, key); + naRef key2; + tmpStr(&key2, &str, key); naHash_tryset(hash, key2, val); } int naHash_get(naRef hash, naRef key, naRef* out) { - struct naHash* h = hash.ref.ptr.hash; - struct HashNode* n; - if(!IS_HASH(hash)) return 0; - n = find(h, key); - if(n) { - *out = n->val; - return 1; - } else { - *out = naNil(); - return 0; + if(IS_HASH(hash)) { + struct HashNode* n = find(hash.ref.ptr.hash, key); + if(n) { *out = n->val; return 1; } } + return 0; } // Simpler version. Don't create a new node if the value isn't there int naHash_tryset(naRef hash, naRef key, naRef val) { - struct HashNode* n; - if(!IS_HASH(hash)) return 0; - n = find(hash.ref.ptr.hash, key); - if(n) n->val = val; - return n != 0; + if(IS_HASH(hash)) { + struct HashNode* n = find(hash.ref.ptr.hash, key); + if(n) n->val = val; + return n != 0; + } + return 0; +} + +// Special purpose optimization for use in function call setups. Sets +// a value that is known *not* to be present in the hash table. As +// for naHash_sym, the key must be a string with a precomputed hash +// code. +void naHash_newsym(struct naHash* hash, naRef* sym, naRef* val) +{ + int col; + struct HashRec* h = hash->rec; + if(!h || h->size >= 1<lgalloced) + h = realloc(hash); + col = (HASH_MAGIC * sym->ref.ptr.str->hashcode) >> (32 - h->lgalloced); + INSERT(h, *sym, *val, col); +} + +// The cycle check is an integrity requirement for multithreading, +// where raced inserts can potentially cause cycles. This ensures +// that the "last" thread to hold a reference to an inserted node +// breaks any cycles that might have happened (at the expense of +// potentially dropping items out of the hash). Under normal +// circumstances, chains will be very short and this will be fast. +static void chkcycle(struct HashNode* node, int count) +{ + struct HashNode* hn = node; + while(hn && (hn = hn->next) != 0) + if(count-- <= 0) { node->next = 0; return; } } void naHash_set(naRef hash, naRef key, naRef val) { - struct naHash* h = hash.ref.ptr.hash; - unsigned int col; + int col; + struct HashRec* h; struct HashNode* n; - if(!IS_HASH(hash)) return; - - n = find(h, key); - if(n) { - n->val = val; - return; - } - - if(h->size+1 >= 1<lgalloced) - realloc(hash); - + if((n = find(hash.ref.ptr.hash, key))) { n->val = val; return; } + h = hash.ref.ptr.hash->rec; + while(!h || h->size >= 1<lgalloced) + h = realloc(hash.ref.ptr.hash); col = hashcolumn(h, key); - n = h->nodes + h->nextnode++; - n->key = key; - n->val = val; - n->next = h->table[col]; - h->table[col] = n; - h->size++; -} - -// FIXME: this implementation does a realloc() after each delete, and -// is therefore needlessly O(N). (The reason is that this avoids the -// need to keep a free list around for the much more common case of -// adding a new value. Modifying an existing value is O(1), of -// course.) + INSERT(h, key, val, hashcolumn(h, key)); + chkcycle(h->table[col], h->size - h->dels); +} + void naHash_delete(naRef hash, naRef key) { - struct naHash* h = hash.ref.ptr.hash; + struct HashRec* h = hash.ref.ptr.hash->rec; int col; struct HashNode *last=0, *hn; - if(!IS_HASH(hash)) return; + if(!IS_HASH(hash) || !h) return; col = hashcolumn(h, key); hn = h->table[col]; while(hn) { - if(naEqual(hn->key, key)) { + if(EQUAL(hn->key, key)) { if(last == 0) h->table[col] = hn->next; else last->next = hn->next; - h->size--; - realloc(hash); + h->dels++; return; } last = hn; @@ -194,9 +225,9 @@ void naHash_delete(naRef hash, naRef key) void naHash_keys(naRef dst, naRef hash) { - struct naHash* h = hash.ref.ptr.hash; int i; - if(!IS_HASH(hash) || !h->table) return; + struct HashRec* h = hash.ref.ptr.hash->rec; + if(!IS_HASH(hash) || !h) return; for(i=0; i<(1<lgalloced); i++) { struct HashNode* hn = h->table[i]; while(hn) { @@ -206,18 +237,15 @@ void naHash_keys(naRef dst, naRef hash) } } -int naHash_size(naRef h) +int naHash_size(naRef hash) { - if(!IS_HASH(h)) return 0; - return h.ref.ptr.hash->size; + struct HashRec* h = hash.ref.ptr.hash->rec; + if(!IS_HASH(hash) || !h) return 0; + return h->size - h->dels; } void naHash_gcclean(struct naHash* h) { - naFree(h->nodes); - h->nodes = 0; - h->size = 0; - h->lgalloced = 0; - h->table = 0; - h->nextnode = 0; + naFree(h->rec); + h->rec = 0; } diff --git a/simgear/nasal/lex.c b/simgear/nasal/lex.c index 1cb3a22a..91635626 100644 --- a/simgear/nasal/lex.c +++ b/simgear/nasal/lex.c @@ -40,7 +40,10 @@ struct Lexeme { {"return", TOK_RETURN}, {"break", TOK_BREAK}, {"continue", TOK_CONTINUE}, - {"func", TOK_FUNC} + {"func", TOK_FUNC}, + {"...", TOK_ELLIPSIS}, + {"?", TOK_QUESTION}, + {"var", TOK_VAR}, }; // Build a table of where each line ending is diff --git a/simgear/nasal/lib.c b/simgear/nasal/lib.c index 7597742c..f424438d 100644 --- a/simgear/nasal/lib.c +++ b/simgear/nasal/lib.c @@ -1,68 +1,70 @@ +#include +#include +#include +#include +#include + +#ifdef _MSC_VER // sigh... +#define vsnprintf _vsnprintf +#endif + #include "nasal.h" +#include "code.h" -// No need to include just for this: -// It needs a funny name because MSVC wants to treat "strlen" as a -// special symbol. Ugh... -static int StrLen(char* s) -{ - char* s0 = s; - while(*s) s++; - return s - s0; -} +#define NEWSTR(c, s, l) naStr_fromdata(naNewString(c), s, l) -static naRef size(naContext c, naRef args) +static naRef size(naContext c, naRef me, int argc, naRef* args) { - naRef r; - if(naVec_size(args) == 0) return naNil(); - r = naVec_get(args, 0); - if(naIsString(r)) return naNum(naStr_len(r)); - if(naIsVector(r)) return naNum(naVec_size(r)); - if(naIsHash(r)) return naNum(naHash_size(r)); + if(argc == 0) return naNil(); + if(naIsString(args[0])) return naNum(naStr_len(args[0])); + if(naIsVector(args[0])) return naNum(naVec_size(args[0])); + if(naIsHash(args[0])) return naNum(naHash_size(args[0])); + naRuntimeError(c, "object has no size()"); return naNil(); } -static naRef keys(naContext c, naRef args) +static naRef keys(naContext c, naRef me, int argc, naRef* args) { - naRef v, h = naVec_get(args, 0); + naRef v, h = args[0]; if(!naIsHash(h)) return naNil(); v = naNewVector(c); naHash_keys(v, h); return v; } -static naRef append(naContext c, naRef args) +static naRef append(naContext c, naRef me, int argc, naRef* args) { - naRef v = naVec_get(args, 0); - naRef e = naVec_get(args, 1); - if(!naIsVector(v)) return naNil(); - naVec_append(v, e); - return v; + int i; + if(argc < 2) return naNil(); + if(!naIsVector(args[0])) return naNil(); + for(i=1; i 2 ? naNumValue(args[2]) : naNil(); if(!naIsNil(nlen)) - len = (int)naNumValue(naVec_get(args, 2)).num; + len = (int)nlen.num; if(!naIsVector(v) || start < 0 || start >= naVec_size(v) || len < 0) return naNil(); if(len == 0 || len > naVec_size(v) - start) len = naVec_size(v) - start; @@ -73,44 +75,38 @@ static naRef subvec(naContext c, naRef args) return result; } -static naRef delete(naContext c, naRef args) +static naRef delete(naContext c, naRef me, int argc, naRef* args) { - naRef h = naVec_get(args, 0); - naRef k = naVec_get(args, 1); - if(naIsHash(h)) naHash_delete(h, k); + if(argc > 1 && naIsHash(args[0])) naHash_delete(args[0], args[1]); return naNil(); } -static naRef intf(naContext c, naRef args) +static naRef intf(naContext c, naRef me, int argc, naRef* args) { - naRef n = naNumValue(naVec_get(args, 0)); - if(!naIsNil(n)) n.num = (int)n.num; - return n; + if(argc > 0) { + naRef n = naNumValue(args[0]); + if(naIsNil(n)) return n; + if(n.num < 0) n.num = -floor(-n.num); + else n.num = floor(n.num); + return n; + } else return naNil(); } -static naRef num(naContext c, naRef args) +static naRef num(naContext c, naRef me, int argc, naRef* args) { - return naNumValue(naVec_get(args, 0)); + return argc > 0 ? naNumValue(args[0]) : naNil(); } -static naRef streq(naContext c, naRef args) +static naRef streq(naContext c, naRef me, int argc, naRef* args) { - int i; - naRef a = naVec_get(args, 0); - naRef b = naVec_get(args, 1); - if(!naIsString(a) || !naIsString(b)) return naNil(); - if(naStr_len(a) != naStr_len(b)) return naNum(0); - for(i=0; i 1 ? naNum(naStrEqual(args[0], args[1])) : naNil(); } -static naRef substr(naContext c, naRef args) +static naRef substr(naContext c, naRef me, int argc, naRef* args) { - naRef src = naVec_get(args, 0); - naRef startR = naVec_get(args, 1); - naRef lenR = naVec_get(args, 2); + naRef src = argc > 1 ? args[0] : naNil(); + naRef startR = argc > 1 ? args[1] : naNil(); + naRef lenR = argc > 2 ? args[2] : naNil(); int start, len; if(!naIsString(src)) return naNil(); startR = naNumValue(startR); @@ -126,18 +122,39 @@ static naRef substr(naContext c, naRef args) return naStr_substr(naNewString(c), src, start, len); } -static naRef contains(naContext c, naRef args) +static naRef f_strc(naContext c, naRef me, int argc, naRef* args) +{ + int idx; + struct naStr* str = args[0].ref.ptr.str; + naRef idr = argc > 1 ? naNumValue(args[1]) : naNum(0); + if(argc < 2 || IS_NIL(idr) || !IS_STR(args[0])) + naRuntimeError(c, "bad arguments to strc"); + idx = (int)naNumValue(idr).num; + if(idx > str->len) naRuntimeError(c, "strc index out of bounds"); + return naNum(str->data[idx]); +} + +static naRef f_chr(naContext c, naRef me, int argc, naRef* args) +{ + char chr[1]; + naRef cr = argc ? naNumValue(args[0]) : naNil(); + if(IS_NIL(cr)) naRuntimeError(c, "chr argument not string"); + chr[0] = (char)cr.num; + return NEWSTR(c, chr, 1); +} + +static naRef contains(naContext c, naRef me, int argc, naRef* args) { - naRef hash = naVec_get(args, 0); - naRef key = naVec_get(args, 1); + naRef hash = argc > 0 ? args[0] : naNil(); + naRef key = argc > 1 ? args[1] : naNil(); if(naIsNil(hash) || naIsNil(key)) return naNil(); if(!naIsHash(hash)) return naNil(); return naHash_get(hash, key, &key) ? naNum(1) : naNum(0); } -static naRef typeOf(naContext c, naRef args) +static naRef typeOf(naContext c, naRef me, int argc, naRef* args) { - naRef r = naVec_get(args, 0); + naRef r = argc > 0 ? args[0] : naNil(); char* t = "unknown"; if(naIsNil(r)) t = "nil"; else if(naIsNum(r)) t = "scalar"; @@ -146,10 +163,258 @@ static naRef typeOf(naContext c, naRef args) else if(naIsHash(r)) t = "hash"; else if(naIsFunc(r)) t = "func"; else if(naIsGhost(r)) t = "ghost"; - r = naStr_fromdata(naNewString(c), t, StrLen(t)); + r = NEWSTR(c, t, strlen(t)); return r; } +static naRef f_compile(naContext c, naRef me, int argc, naRef* args) +{ + int errLine; + naRef script, code, fname; + script = argc > 0 ? args[0] : naNil(); + if(!naIsString(script)) return naNil(); + fname = NEWSTR(c, "", 9); + code = naParseCode(c, fname, 1, + naStr_data(script), naStr_len(script), &errLine); + if(!naIsCode(code)) return naNil(); // FIXME: export error to caller... + return naBindToContext(c, code); +} + +// Funcation metacall API. Allows user code to generate an arg vector +// at runtime and/or call function references on arbitrary objects. +static naRef f_call(naContext c, naRef me, int argc, naRef* args) +{ + naContext subc; + naRef callargs, callme, result; + callargs = argc > 1 ? args[1] : naNil(); + callme = argc > 2 ? args[2] : naNil(); // Might be nil, that's OK + if(!naIsFunc(args[0])) naRuntimeError(c, "call() on non-function"); + if(naIsNil(callargs)) callargs = naNewVector(c); + else if(!naIsVector(callargs)) naRuntimeError(c, "call() args not vector"); + if(!naIsHash(callme)) callme = naNil(); + subc = naNewContext(); + subc->callParent = c; + c->callChild = subc; + result = naCall(subc, args[0], callargs, callme, naNil()); + c->callChild = 0; + if(argc > 2 && !IS_NIL(subc->dieArg)) + if(naIsVector(args[argc-1])) + naVec_append(args[argc-1], subc->dieArg); + naFreeContext(subc); + return result; +} + +static naRef f_die(naContext c, naRef me, int argc, naRef* args) +{ + c->dieArg = argc > 0 ? args[0] : naNil(); + naRuntimeError(c, "__die__"); + return naNil(); // never executes +} + +// Wrapper around vsnprintf, iteratively increasing the buffer size +// until it fits. Returned buffer should be freed by the caller. +char* dosprintf(char* f, ...) +{ + char* buf; + va_list va; + int len = 16; + while(1) { + buf = naAlloc(len); + va_start(va, f); + if(vsnprintf(buf, len, f, va) < len) { + va_end(va); + return buf; + } + va_end(va); + naFree(buf); + len *= 2; + } +} + +// Inspects a printf format string f, and finds the next "%..." format +// specifier. Stores the start of the specifier in out, the length in +// len, and the type in type. Returns a pointer to the remainder of +// the format string, or 0 if no format string was found. Recognizes +// all of ANSI C's syntax except for the "length modifier" feature. +// Note: this does not validate the format character returned in +// "type". That is the caller's job. +static char* nextFormat(naContext ctx, char* f, char** out, int* len, char* type) +{ + // Skip to the start of the format string + while(*f && *f != '%') f++; + if(!*f) return 0; + *out = f++; + + while(*f && (*f=='-' || *f=='+' || *f==' ' || *f=='0' || *f=='#')) f++; + + // Test for duplicate flags. This is pure pedantry and could + // be removed on all known platforms, but just to be safe... + { char *p1, *p2; + for(p1 = *out + 1; p1 < f; p1++) + for(p2 = p1+1; p2 < f; p2++) + if(*p1 == *p2) + naRuntimeError(ctx, "duplicate flag in format string"); } + + while(*f && *f >= '0' && *f <= '9') f++; + if(*f && *f == '.') f++; + while(*f && *f >= '0' && *f <= '9') f++; + if(!*f) naRuntimeError(ctx, "invalid format string"); + + *type = *f++; + *len = f - *out; + return f; +} + +#define ERR(m) naRuntimeError(ctx, m) +#define APPEND(r) result = naStr_concat(naNewString(ctx), result, r) +static naRef f_sprintf(naContext ctx, naRef me, int argc, naRef* args) +{ + char t, nultmp, *fstr, *next, *fout=0, *s; + int flen, argn=1; + naRef format, arg, result = naNewString(ctx); + + if(argc < 1) ERR("not enough arguments to sprintf"); + format = naStringValue(ctx, argc > 0 ? args[0] : naNil()); + if(naIsNil(format)) ERR("bad format string in sprintf"); + s = naStr_data(format); + + while((next = nextFormat(ctx, s, &fstr, &flen, &t))) { + APPEND(NEWSTR(ctx, s, fstr-s)); // stuff before the format string + if(flen == 2 && fstr[1] == '%') { + APPEND(NEWSTR(ctx, "%", 1)); + s = next; + continue; + } + if(argn >= argc) ERR("not enough arguments to sprintf"); + arg = args[argn++]; + nultmp = fstr[flen]; // sneaky nul termination... + fstr[flen] = 0; + if(t == 's') { + arg = naStringValue(ctx, arg); + if(naIsNil(arg)) fout = dosprintf(fstr, "nil"); + else fout = dosprintf(fstr, naStr_data(arg)); + } else { + arg = naNumValue(arg); + if(naIsNil(arg)) + fout = dosprintf(fstr, "nil"); + else if(t=='d' || t=='i' || t=='c') + fout = dosprintf(fstr, (int)naNumValue(arg).num); + else if(t=='o' || t=='u' || t=='x' || t=='X') + fout = dosprintf(fstr, (unsigned int)naNumValue(arg).num); + else if(t=='e' || t=='E' || t=='f' || t=='F' || t=='g' || t=='G') + fout = dosprintf(fstr, naNumValue(arg).num); + else + ERR("invalid sprintf format type"); + } + fstr[flen] = nultmp; + APPEND(NEWSTR(ctx, fout, strlen(fout))); + naFree(fout); + s = next; + } + APPEND(NEWSTR(ctx, s, strlen(s))); + return result; +} + +// FIXME: handle ctx->callParent frames too! +static naRef f_caller(naContext ctx, naRef me, int argc, naRef* args) +{ + int fidx; + struct Frame* frame; + naRef result, fr = argc ? naNumValue(args[0]) : naNil(); + if(IS_NIL(fr)) naRuntimeError(ctx, "non numeric argument to caller()"); + fidx = (int)fr.num; + if(fidx > ctx->fTop - 1) return naNil(); + frame = &ctx->fStack[ctx->fTop - 1 - fidx]; + result = naNewVector(ctx); + naVec_append(result, frame->locals); + naVec_append(result, frame->func); + naVec_append(result, frame->func.ref.ptr.func->code.ref.ptr.code->srcFile); + naVec_append(result, naNum(naGetLine(ctx, fidx))); + return result; +} + +static naRef f_closure(naContext ctx, naRef me, int argc, naRef* args) +{ + int i; + naRef func, idx; + struct naFunc* f; + func = argc > 0 ? args[0] : naNil(); + idx = argc > 1 ? naNumValue(args[1]) : naNil(); + if(!IS_FUNC(func) || IS_NIL(idx)) + naRuntimeError(ctx, "bad arguments to closure()"); + i = (int)idx.num; + f = func.ref.ptr.func; + while(i > 0 && f) { i--; f = f->next.ref.ptr.func; } + if(!f) return naNil(); + return f->namespace; +} + +static int match(char* a, char* b, int l) +{ + int i; + for(i=0; idata, args[0].ref.ptr.str->len, + args[1].ref.ptr.str->data, args[1].ref.ptr.str->len)); +} + +static naRef f_split(naContext ctx, naRef me, int argc, naRef* args) +{ + int sl, dl, i; + char *s, *d, *s0; + naRef result; + if(argc < 2 || !IS_STR(args[0]) || !IS_STR(args[1])) + naRuntimeError(ctx, "bad/missing argument to split"); + d = naStr_data(args[0]); dl = naStr_len(args[0]); + s = naStr_data(args[1]); sl = naStr_len(args[1]); + result = naNewVector(ctx); + if(dl == 0) { // special case zero-length delimiter + for(i=0; i 0 ? args[0] : naNil()); if(naIsNil(a)) naRuntimeError(c, "non numeric argument to sin()"); a.num = sin(a.num); return a; } -static naRef f_cos(naContext c, naRef args) +static naRef f_cos(naContext c, naRef me, int argc, naRef* args) { - naRef a = naNumValue(naVec_get(args, 0)); + naRef a = naNumValue(argc > 0 ? args[0] : naNil()); if(naIsNil(a)) naRuntimeError(c, "non numeric argument to cos()"); a.num = cos(a.num); return a; } -static naRef f_exp(naContext c, naRef args) +static naRef f_exp(naContext c, naRef me, int argc, naRef* args) { - naRef a = naNumValue(naVec_get(args, 0)); + naRef a = naNumValue(argc > 0 ? args[0] : naNil()); if(naIsNil(a)) naRuntimeError(c, "non numeric argument to exp()"); a.num = exp(a.num); return a; } -static naRef f_ln(naContext c, naRef args) +static naRef f_ln(naContext c, naRef me, int argc, naRef* args) { - naRef a = naNumValue(naVec_get(args, 0)); + naRef a = naNumValue(argc > 0 ? args[0] : naNil()); if(naIsNil(a)) naRuntimeError(c, "non numeric argument to ln()"); a.num = log(a.num); return a; } -static naRef f_sqrt(naContext c, naRef args) +static naRef f_sqrt(naContext c, naRef me, int argc, naRef* args) { - naRef a = naNumValue(naVec_get(args, 0)); + naRef a = naNumValue(argc > 0 ? args[0] : naNil()); if(naIsNil(a)) naRuntimeError(c, "non numeric argument to sqrt()"); a.num = sqrt(a.num); return a; } -static naRef f_atan2(naContext c, naRef args) +static naRef f_atan2(naContext c, naRef me, int argc, naRef* args) { - naRef a = naNumValue(naVec_get(args, 0)); - naRef b = naNumValue(naVec_get(args, 1)); + naRef a = naNumValue(argc > 0 ? args[0] : naNil()); + naRef b = naNumValue(argc > 1 ? args[1] : naNil()); if(naIsNil(a) || naIsNil(b)) naRuntimeError(c, "non numeric argument to atan2()"); a.num = atan2(a.num, b.num); @@ -87,6 +87,7 @@ naRef naMathLib(naContext c) naHash_set(namespace, name, naNum(M_PI)); name = naStr_fromdata(naNewString(c), "e", 1); + name = naInternSymbol(name); naHash_set(namespace, name, naNum(M_E)); return namespace; diff --git a/simgear/nasal/misc.c b/simgear/nasal/misc.c index acdca6a3..f74ca95e 100644 --- a/simgear/nasal/misc.c +++ b/simgear/nasal/misc.c @@ -49,7 +49,10 @@ naRef naStringValue(naContext c, naRef r) naRef naNew(struct Context* c, int type) { - naRef result = naObj(type, naGC_get(&(c->pools[type]))); + if(c->nfree[type] == 0) + c->free[type] = naGC_get(&globals->pools[type], + OBJ_CACHE_SZ, &c->nfree[type]); + naRef result = naObj(type, c->free[type][--c->nfree[type]]); naVec_append(c->temps, result); return result; } @@ -59,20 +62,21 @@ naRef naNewString(struct Context* c) naRef s = naNew(c, T_STR); s.ref.ptr.str->len = 0; s.ref.ptr.str->data = 0; + s.ref.ptr.str->hashcode = 0; return s; } naRef naNewVector(struct Context* c) { naRef r = naNew(c, T_VEC); - naVec_init(r); + r.ref.ptr.vec->rec = 0; return r; } naRef naNewHash(struct Context* c) { naRef r = naNew(c, T_HASH); - naHash_init(r); + r.ref.ptr.hash->rec = 0; return r; } @@ -92,18 +96,11 @@ naRef naNewFunc(struct Context* c, naRef code) { naRef func = naNew(c, T_FUNC); func.ref.ptr.func->code = code; - func.ref.ptr.func->closure = naNil(); + func.ref.ptr.func->namespace = naNil(); + func.ref.ptr.func->next = naNil(); return func; } -naRef naNewClosure(struct Context* c, naRef namespace, naRef next) -{ - naRef closure = naNew(c, T_CLOSURE); - closure.ref.ptr.closure->namespace = namespace; - closure.ref.ptr.closure->next = next; - return closure; -} - naRef naNewGhost(naContext c, naGhostType* type, void* ptr) { naRef ghost = naNew(c, T_GHOST); @@ -162,6 +159,19 @@ int naEqual(naRef a, naRef b) return na == nb ? 1 : 0; } +int naStrEqual(naRef a, naRef b) +{ + int i; + if(!(IS_STR(a) && IS_STR(b))) + return 0; + if(a.ref.ptr.str->len != b.ref.ptr.str->len) + return 0; + for(i=0; ilen; i++) + if(a.ref.ptr.str->data[i] != b.ref.ptr.str->data[i]) + return 0; + return 1; +} + int naTypeSize(int type) { switch(type) { @@ -170,7 +180,6 @@ int naTypeSize(int type) case T_HASH: return sizeof(struct naHash); case T_CODE: return sizeof(struct naCode); case T_FUNC: return sizeof(struct naFunc); - case T_CLOSURE: return sizeof(struct naClosure); case T_CCODE: return sizeof(struct naCCode); case T_GHOST: return sizeof(struct naGhost); }; diff --git a/simgear/nasal/nasal.h b/simgear/nasal/nasal.h index 85fff966..a083d02b 100644 --- a/simgear/nasal/nasal.h +++ b/simgear/nasal/nasal.h @@ -62,7 +62,6 @@ typedef union { struct naHash* hash; struct naCode* code; struct naFunc* func; - struct naClosure* closure; struct naCCode* ccode; struct naGhost* ghost; } ptr; @@ -75,10 +74,11 @@ typedef union { typedef struct Context* naContext; // The function signature for an extension function: -typedef naRef (*naCFunction)(naContext ctx, naRef args); +typedef naRef (*naCFunction)(naContext ctx, naRef me, int argc, naRef* args); // All Nasal code runs under the watch of a naContext: naContext naNewContext(); +void naFreeContext(naContext c); // Save this object in the context, preventing it (and objects // referenced by it) from being garbage collected. @@ -95,6 +95,10 @@ naRef naParseCode(naContext c, naRef srcFile, int firstLine, // information from function objects. naRef naBindFunction(naContext ctx, naRef code, naRef closure); +// Similar, but it binds to the current context's closure (i.e. the +// namespace at the top of the current call stack). +naRef naBindToContext(naContext ctx, naRef code); + // Call a code or function object with the specifed arguments "on" the // specified object and using the specified hash for the local // variables. Any of args, obj or locals may be nil. @@ -146,6 +150,7 @@ naRef naNewCCode(naContext c, naCFunction fptr); // Some useful conversion/comparison routines int naEqual(naRef a, naRef b); +int naStrEqual(naRef a, naRef b); int naTrue(naRef b); naRef naNumValue(naRef n); naRef naStringValue(naContext c, naRef n); @@ -156,6 +161,7 @@ char* naStr_data(naRef s); naRef naStr_fromdata(naRef dst, char* data, int len); naRef naStr_concat(naRef dest, naRef s1, naRef s2); naRef naStr_substr(naRef dest, naRef str, int start, int len); +naRef naInternSymbol(naRef sym); // Vector utilities: int naVec_size(naRef v); @@ -183,6 +189,20 @@ naGhostType* naGhost_type(naRef ghost); void* naGhost_ptr(naRef ghost); int naIsGhost(naRef r); +// Acquires a "modification lock" on a context, allowing the C code to +// modify Nasal data without fear that such data may be "lost" by the +// garbage collector (the C stack is not examined in GC!). This +// disallows garbage collection until the current thread can be +// blocked. The lock should be acquired whenever modifications to +// Nasal objects are made. It need not be acquired when only read +// access is needed. It MUST NOT be acquired by naCFunction's, as +// those are called with the lock already held; acquiring two locks +// for the same thread will cause a deadlock when the GC is invoked. +// It should be UNLOCKED by naCFunction's when they are about to do +// any long term non-nasal processing and/or blocking I/O. +void naModLock(); +void naModUnlock(); + #ifdef __cplusplus } // extern "C" #endif diff --git a/simgear/nasal/parse.c b/simgear/nasal/parse.c index 920d7342..87c4440b 100644 --- a/simgear/nasal/parse.c +++ b/simgear/nasal/parse.c @@ -12,14 +12,16 @@ struct precedence { int rule; } PRECEDENCE[] = { { { TOK_SEMI, TOK_COMMA }, PREC_REVERSE }, - { { TOK_COLON }, PREC_BINARY }, + { { TOK_ELLIPSIS }, PREC_SUFFIX }, { { TOK_RETURN, TOK_BREAK, TOK_CONTINUE }, PREC_PREFIX }, { { TOK_ASSIGN }, PREC_REVERSE }, + { { TOK_COLON, TOK_QUESTION }, PREC_REVERSE }, + { { TOK_VAR }, PREC_PREFIX }, { { TOK_OR }, PREC_BINARY }, { { TOK_AND }, PREC_BINARY }, { { TOK_EQ, TOK_NEQ }, PREC_BINARY }, { { TOK_LT, TOK_LTE, TOK_GT, TOK_GTE }, PREC_BINARY }, - { { TOK_PLUS, TOK_MINUS, TOK_CAT }, PREC_REVERSE }, + { { TOK_PLUS, TOK_MINUS, TOK_CAT }, PREC_REVERSE }, { { TOK_MUL, TOK_DIV }, PREC_BINARY }, { { TOK_MINUS, TOK_NEG, TOK_NOT }, PREC_PREFIX }, { { TOK_LPAR, TOK_LBRA }, PREC_SUFFIX }, @@ -210,7 +212,15 @@ static void fixBlockStructure(struct Parser* p, struct Token* start) t = start; while(t) { switch(t->type) { - case TOK_ELSE: case TOK_FUNC: + case TOK_FUNC: + // Slurp an optional paren block containing an arglist, then + // fall through to parse the curlies... + if(t->next && t->next->type == TOK_LPAR) { + c = t->next; + addNewChild(t, c); + fixBlockStructure(p, c); + } + case TOK_ELSE: // and TOK_FUNC! // These guys precede a single curly block if(!t->next || t->next->type != TOK_LCURL) oops(p, t); c = t->next; @@ -276,6 +286,8 @@ static void fixBlockStructure(struct Parser* p, struct Token* start) addSemi = 1; break; } + if(t->next && t->next->type == TOK_SEMI) + addSemi = 0; // don't bother if it's already there! if(addSemi) { struct Token* semi = emptyToken(p); semi->type = TOK_SEMI; @@ -519,7 +531,7 @@ naRef naParseCode(struct Context* c, naRef srcFile, int firstLine, p.tree.lastChild = t; // Generate code! - codeObj = naCodeGen(&p, &(p.tree)); + codeObj = naCodeGen(&p, &(p.tree), 0); // Clean up our mess naParseDestroy(&p); diff --git a/simgear/nasal/parse.h b/simgear/nasal/parse.h index 24b89fcb..1e5e2d90 100644 --- a/simgear/nasal/parse.h +++ b/simgear/nasal/parse.h @@ -14,7 +14,7 @@ enum { TOK_ASSIGN, TOK_LT, TOK_LTE, TOK_EQ, TOK_NEQ, TOK_GT, TOK_GTE, TOK_IF, TOK_ELSIF, TOK_ELSE, TOK_FOR, TOK_FOREACH, TOK_WHILE, TOK_RETURN, TOK_BREAK, TOK_CONTINUE, TOK_FUNC, TOK_SYMBOL, - TOK_LITERAL, TOK_EMPTY, TOK_NIL + TOK_LITERAL, TOK_EMPTY, TOK_NIL, TOK_ELLIPSIS, TOK_QUESTION, TOK_VAR }; struct Token { @@ -58,7 +58,7 @@ struct Parser { // Computed line number table for the lexer int* lines; int nLines; - + struct CodeGenerator* cg; }; @@ -66,10 +66,15 @@ struct CodeGenerator { int lastLine; // Accumulated byte code array - unsigned char* byteCode; - int nBytes; + unsigned short* byteCode; + int codesz; int codeAlloced; + // Inst. -> line table, stores pairs of {ip, line} + unsigned short* lineIps; + int nLineIps; // number of pairs + int nextLineIp; + // Stack of "loop" frames for break/continue statements struct { int breakIP; @@ -87,7 +92,7 @@ void naParseInit(struct Parser* p); void* naParseAlloc(struct Parser* p, int bytes); void naParseDestroy(struct Parser* p); void naLex(struct Parser* p); -naRef naCodeGen(struct Parser* p, struct Token* tok); +naRef naCodeGen(struct Parser* p, struct Token* block, struct Token* arglist); void naParse(struct Parser* p); diff --git a/simgear/nasal/string.c b/simgear/nasal/string.c index 69aa473a..a94468c6 100644 --- a/simgear/nasal/string.c +++ b/simgear/nasal/string.c @@ -8,11 +8,6 @@ // double. #define DIGITS 16 -// The minimum size we'll allocate for a string. Since a string -// structure is already 12 bytes, and each naRef that points to it is -// 8, there isn't much point in being stingy. -#define MINLEN 16 - static int tonum(unsigned char* s, int len, double* result); static int fromnum(double val, unsigned char* s); @@ -30,16 +25,10 @@ char* naStr_data(naRef s) static void setlen(struct naStr* s, int sz) { - int currSz, waste; - sz += 1; // Allow for an extra nul terminator - currSz = s->len+1 < MINLEN ? MINLEN : s->len+1; - waste = currSz - sz; // how much extra if we don't reallocate? - if(s->data == 0 || waste < 0 || waste > MINLEN) { - naFree(s->data); - s->data = naAlloc(sz < MINLEN ? MINLEN : sz); - } - s->len = sz - 1; - s->data[s->len] = 0; // nul terminate + if(s->data) naFree(s->data); + s->len = sz; + s->data = naAlloc(sz+1); + s->data[sz] = 0; // nul terminate } naRef naStr_fromdata(naRef dst, char* data, int len) @@ -110,10 +99,8 @@ int naStr_numeric(naRef str) void naStr_gcclean(struct naStr* str) { - if(str->len > MINLEN) { - naFree(str->data); - str->data = 0; - } + naFree(str->data); + str->data = 0; str->len = 0; } @@ -203,8 +190,11 @@ static int tonum(unsigned char* s, int len, double* result) if(i == 0) return 0; // Read the exponent, if any - if(i < len && (s[i] == 'e' || s[i] == 'E')) + if(i < len && (s[i] == 'e' || s[i] == 'E')) { + int i0 = i+1; i = readsigned(s, len, i+1, &exp); + if(i == i0) return 0; // Must have a number after the "e" + } // compute the result *result = sgn * (val + frac * decpow(-fraclen)) * decpow(exp); @@ -281,7 +271,7 @@ static int fromnum(double val, unsigned char* s) if(raw[i] != '0') break; digs = i+1; - if(exp > 0 || exp < -(DIGITS+2)) { + if(exp > 0 || exp < -(DIGITS+3)) { // Standard scientific notation exp += DIGITS-1; *ptr++ = raw[0]; diff --git a/simgear/nasal/thread-posix.c b/simgear/nasal/thread-posix.c new file mode 100644 index 00000000..4b3ff7c7 --- /dev/null +++ b/simgear/nasal/thread-posix.c @@ -0,0 +1,57 @@ +#ifndef _WIN32 + +#include +#include "code.h" + +void* naNewLock() +{ + pthread_mutex_t* lock = naAlloc(sizeof(pthread_mutex_t)); + pthread_mutex_init(lock, 0); + return lock; +} + +void naLock(void* lock) +{ + pthread_mutex_lock((pthread_mutex_t*)lock); +} + +void naUnlock(void* lock) +{ + pthread_mutex_unlock((pthread_mutex_t*)lock); +} + +struct naSem { + pthread_mutex_t lock; + pthread_cond_t cvar; + int count; +}; + +void* naNewSem() +{ + struct naSem* sem = naAlloc(sizeof(struct naSem)); + pthread_mutex_init(&sem->lock , 0); + pthread_cond_init(&sem->cvar, 0); + sem->count = 0; + return sem; +} + +void naSemDown(void* sh) +{ + struct naSem* sem = (struct naSem*)sh; + pthread_mutex_lock(&sem->lock); + while(sem->count <= 0) + pthread_cond_wait(&sem->cvar, &sem->lock); + sem->count--; + pthread_mutex_unlock(&sem->lock); +} + +void naSemUpAll(void* sh, int count) +{ + struct naSem* sem = (struct naSem*)sh; + pthread_mutex_lock(&sem->lock); + sem->count = count; + pthread_cond_broadcast(&sem->cvar); + pthread_mutex_unlock(&sem->lock); +} + +#endif diff --git a/simgear/nasal/thread-win32.c b/simgear/nasal/thread-win32.c new file mode 100644 index 00000000..c86c1f76 --- /dev/null +++ b/simgear/nasal/thread-win32.c @@ -0,0 +1,20 @@ +#ifdef _WIN32 + +#include + +#define MAX_SEM_COUNT 1024 // What are the tradeoffs with this value? + +void* naNewLock() +{ + LPCRITICAL_SECTION lock = malloc(sizeof(CRITICAL_SECTION)); + InitializeCriticalSection(lock); + return lock; +} + +void naLock(void* lock) { EnterCriticalSection((LPCRITICAL_SECTION)lock); } +void naUnlock(void* lock) { LeaveCriticalSection((LPCRITICAL_SECTION)lock); } +void* naNewSem() { return CreateSemaphore(0, 0, MAX_SEM_COUNT, 0); } +void naSemDown(void* sem) { WaitForSingleObject((HANDLE)sem, INFINITE); } +void naSemUpAll(void* sem, int count) { ReleaseSemaphore(sem, count, 0); } + +#endif diff --git a/simgear/nasal/vector.c b/simgear/nasal/vector.c index b9e03e6d..6196805c 100644 --- a/simgear/nasal/vector.c +++ b/simgear/nasal/vector.c @@ -3,82 +3,88 @@ static void realloc(struct naVec* v) { - int i, newsz = 1 + ((v->size*3)>>1); - naRef* na = naAlloc(sizeof(naRef) * newsz); - v->alloced = newsz; - for(i=0; isize; i++) - na[i] = v->array[i]; - naFree(v->array); - v->array = na; -} - -void naVec_init(naRef vec) -{ - struct naVec* v = vec.ref.ptr.vec; - v->array = 0; - v->size = 0; - v->alloced = 0; + struct VecRec* old = v->rec; + int i, oldsz = old ? old->size : 0, newsz = 1 + ((oldsz*3)>>1); + struct VecRec* vr = naAlloc(sizeof(struct VecRec) + sizeof(naRef) * newsz); + if(oldsz > newsz) oldsz = newsz; // race protection + vr->alloced = newsz; + vr->size = oldsz; + for(i=0; iarray[i] = old->array[i]; + naGC_swapfree((void**)&(v->rec), vr); } void naVec_gcclean(struct naVec* v) { - naFree(v->array); - v->size = 0; - v->alloced = 0; - v->array = 0; + naFree(v->rec); + v->rec = 0; } naRef naVec_get(naRef v, int i) { - if(!IS_VEC(v)) return naNil(); - if(i >= v.ref.ptr.vec->size) return naNil(); - return v.ref.ptr.vec->array[i]; + if(IS_VEC(v)) { + struct VecRec* r = v.ref.ptr.vec->rec; + if(r && i < r->size) return r->array[i]; + } + return naNil(); } void naVec_set(naRef vec, int i, naRef o) { - struct naVec* v = vec.ref.ptr.vec; - if(!IS_VEC(vec) || i >= v->size) return; - v->array[i] = o; + if(IS_VEC(vec)) { + struct VecRec* r = vec.ref.ptr.vec->rec; + if(r && i >= r->size) return; + r->array[i] = o; + } } int naVec_size(naRef v) { - if(!IS_VEC(v)) return 0; - return v.ref.ptr.vec->size; + if(IS_VEC(v)) { + struct VecRec* r = v.ref.ptr.vec->rec; + return r ? r->size : 0; + } + return 0; } int naVec_append(naRef vec, naRef o) { - struct naVec* v = vec.ref.ptr.vec; - if(!IS_VEC(vec)) return 0; - if(v->size >= v->alloced) - realloc(v); - v->array[v->size] = o; - return v->size++; + if(IS_VEC(vec)) { + struct VecRec* r = vec.ref.ptr.vec->rec; + if(!r || r->size >= r->alloced) { + realloc(vec.ref.ptr.vec); + r = vec.ref.ptr.vec->rec; + } + r->array[r->size] = o; + return r->size++; + } + return 0; } void naVec_setsize(naRef vec, int sz) { int i; - struct naVec* v = vec.ref.ptr.vec; - naRef* na = naAlloc(sizeof(naRef) * sz); + struct VecRec* v = vec.ref.ptr.vec->rec; + struct VecRec* nv = naAlloc(sizeof(struct VecRec) + sizeof(naRef) * sz); + nv->size = sz; + nv->alloced = sz; for(i=0; isize) ? v->array[i] : naNil(); - naFree(v->array); - v->array = na; - v->size = sz; - v->alloced = sz; + nv->array[i] = (v && i < v->size) ? v->array[i] : naNil(); + naFree(v); + vec.ref.ptr.vec->rec = nv; } naRef naVec_removelast(naRef vec) { naRef o; - struct naVec* v = vec.ref.ptr.vec; - if(!IS_VEC(vec) || v->size == 0) return naNil(); - o = v->array[v->size - 1]; - v->size--; - if(v->size < (v->alloced >> 1)) - realloc(v); - return o; + if(IS_VEC(vec)) { + struct VecRec* v = vec.ref.ptr.vec->rec; + if(!v || v->size == 0) return naNil(); + o = v->array[v->size - 1]; + v->size--; + if(v->size < (v->alloced >> 1)) + realloc(vec.ref.ptr.vec); + return o; + } + return naNil(); } -- 2.39.5