]> git.mxchange.org Git - simgear.git/blob - simgear/nasal/code.c
Phil Cazzola:
[simgear.git] / simgear / nasal / code.c
1 #include "nasal.h"
2 #include "code.h"
3
4 ////////////////////////////////////////////////////////////////////////
5 // Debugging stuff. ////////////////////////////////////////////////////
6 ////////////////////////////////////////////////////////////////////////
7 //#define DEBUG_NASAL
8 #if !defined(DEBUG_NASAL)
9 # define DBG(expr) /* noop */
10 #else
11 # define DBG(expr) expr
12 # include <stdio.h>
13 # include <stdlib.h>
14 #endif
15 char* opStringDEBUG(int op);
16 void printOpDEBUG(int ip, int op);
17 void printStackDEBUG(struct Context* ctx);
18 ////////////////////////////////////////////////////////////////////////
19
20 struct Globals* globals = 0;
21
22 static naRef bindFunction(struct Context* ctx, struct Frame* f, naRef code);
23
24 #define ERR(c, msg) naRuntimeError((c),(msg))
25 void naRuntimeError(struct Context* c, char* msg)
26
27     c->error = msg;
28     longjmp(c->jumpHandle, 1);
29 }
30
31 static int boolify(struct Context* ctx, naRef r)
32 {
33     if(IS_NUM(r)) return r.num != 0;
34     if(IS_NIL(r)) return 0;
35     if(IS_STR(r)) {
36         double d;
37         if(naStr_len(r) == 0) return 0;
38         if(naStr_tonum(r, &d)) return d != 0;
39         else return 1;
40     }
41     ERR(ctx, "non-scalar used in boolean context");
42     return 0;
43 }
44
45 static double numify(struct Context* ctx, naRef o)
46 {
47     double n;
48     if(IS_NUM(o)) return o.num;
49     else if(IS_NIL(o)) ERR(ctx, "nil used in numeric context");
50     else if(!IS_STR(o)) ERR(ctx, "non-scalar in numeric context");
51     else if(naStr_tonum(o, &n)) return n;
52     else ERR(ctx, "non-numeric string in numeric context");
53     return 0;
54 }
55
56 static naRef stringify(struct Context* ctx, naRef r)
57 {
58     if(IS_STR(r)) return r;
59     if(IS_NUM(r)) return naStr_fromnum(naNewString(ctx), r.num);
60     ERR(ctx, "non-scalar in string context");
61     return naNil();
62 }
63
64 static int checkVec(struct Context* ctx, naRef vec, naRef idx)
65 {
66     int i = (int)numify(ctx, idx);
67     if(i < 0 || i >= vec.ref.ptr.vec->rec->size)
68         ERR(ctx, "vector index out of bounds");
69     return i;
70 }
71
72 static naRef containerGet(struct Context* ctx, naRef box, naRef key)
73 {
74     naRef result = naNil();
75     if(!IS_SCALAR(key)) ERR(ctx, "container index not scalar");
76     if(IS_HASH(box)) {
77         if(!naHash_get(box, key, &result))
78             ERR(ctx, "undefined value in container");
79     } else if(IS_VEC(box)) {
80         result = naVec_get(box, checkVec(ctx, box, key));
81     } else {
82         ERR(ctx, "extract from non-container");
83     }
84     return result;
85 }
86
87 static void containerSet(struct Context* ctx, naRef box, naRef key, naRef val)
88 {
89     if(!IS_SCALAR(key))   ERR(ctx, "container index not scalar");
90     else if(IS_HASH(box)) naHash_set(box, key, val);
91     else if(IS_VEC(box))  naVec_set(box, checkVec(ctx, box, key), val);
92     else                  ERR(ctx, "insert into non-container");
93 }
94
95 static void initContext(struct Context* c)
96 {
97     int i;
98     c->fTop = c->opTop = c->markTop = 0;
99     for(i=0; i<NUM_NASAL_TYPES; i++)
100         c->nfree[i] = 0;
101     naVec_setsize(c->temps, 4);
102     c->callParent = 0;
103     c->callChild = 0;
104     c->dieArg = naNil();
105     c->error = 0;
106 }
107
108 static void initGlobals()
109 {
110     int i;
111     struct Context* c;
112     globals = (struct Globals*)naAlloc(sizeof(struct Globals));
113     naBZero(globals, sizeof(struct Globals));
114
115     globals->sem = naNewSem();
116     globals->lock = naNewLock();
117
118     globals->allocCount = 256; // reasonable starting value
119     for(i=0; i<NUM_NASAL_TYPES; i++)
120         naGC_init(&(globals->pools[i]), i);
121     globals->deadsz = 256;
122     globals->ndead = 0;
123     globals->deadBlocks = naAlloc(sizeof(void*) * globals->deadsz);
124
125     // Initialize a single context
126     globals->freeContexts = 0;
127     globals->allContexts = 0;
128     c = naNewContext();
129
130     globals->symbols = naNewHash(c);
131     globals->save = naNewVector(c);
132
133     // Cache pre-calculated "me", "arg" and "parents" scalars
134     globals->meRef = naInternSymbol(naStr_fromdata(naNewString(c), "me", 2));
135     globals->argRef = naInternSymbol(naStr_fromdata(naNewString(c), "arg", 3));
136     globals->parentsRef = naInternSymbol(naStr_fromdata(naNewString(c), "parents", 7));
137
138     naFreeContext(c);
139 }
140
141 struct Context* naNewContext()
142 {
143     int dummy;
144     struct Context* c;
145     if(globals == 0)
146         initGlobals();
147
148     LOCK();
149     c = globals->freeContexts;
150     if(c) {
151         globals->freeContexts = c->nextFree;
152         c->nextFree = 0;
153         UNLOCK();
154         initContext(c);
155     } else {
156         UNLOCK();
157         c = (struct Context*)naAlloc(sizeof(struct Context));
158         // Chicken and egg, can't use naNew because it requires temps to exist
159         c->temps = naObj(T_VEC, (naGC_get(&globals->pools[T_VEC], 1, &dummy))[0]);
160         initContext(c);
161         LOCK();
162         c->nextAll = globals->allContexts;
163         c->nextFree = 0;
164         globals->allContexts = c;
165         UNLOCK();
166     }
167     return c;
168 }
169
170 void naFreeContext(struct Context* c)
171 {
172     naVec_setsize(c->temps, 0);
173     LOCK();
174     c->nextFree = globals->freeContexts;
175     globals->freeContexts = c;
176     UNLOCK();
177 }
178
179 #define PUSH(r) do { \
180     if(ctx->opTop >= MAX_STACK_DEPTH) ERR(ctx, "stack overflow"); \
181     ctx->opStack[ctx->opTop++] = r; \
182     } while(0)
183
184 struct Frame* setupFuncall(struct Context* ctx, int nargs, int mcall, int tail)
185 {
186     int i;
187     naRef *frame;
188     struct Frame* f;
189     struct naCode* c;
190     
191     DBG(printf("setupFuncall(nargs:%d, mcall:%d)\n", nargs, mcall);)
192
193     frame = &ctx->opStack[ctx->opTop - nargs - 1];
194     if(!IS_FUNC(frame[0]))
195         ERR(ctx, "function/method call invoked on uncallable object");
196
197     // Just do native calls right here, and don't touch the stack
198     // frames; return the current one (unless it's a tail call!).
199     if(frame[0].ref.ptr.func->code.ref.ptr.obj->type == T_CCODE) {
200         naRef obj = mcall ? frame[-1] : naNil();
201         naCFunction fp = frame[0].ref.ptr.func->code.ref.ptr.ccode->fptr;
202         naRef result = (*fp)(ctx, obj, nargs, frame + 1);
203         ctx->opTop -= nargs + 1 + mcall;
204         PUSH(result);
205         return &(ctx->fStack[ctx->fTop-1]);
206     }
207     
208     if(tail) ctx->fTop--;
209     else if(ctx->fTop >= MAX_RECURSION) ERR(ctx, "call stack overflow");
210
211     // Note: assign nil first, otherwise the naNew() can cause a GC,
212     // which will now (after fTop++) see the *old* reference as a
213     // markable value!
214     f = &(ctx->fStack[ctx->fTop++]);
215     f->locals = f->func = naNil();
216     f->locals = naNewHash(ctx);
217     f->func = frame[0];
218     f->ip = 0;
219     f->bp = ctx->opTop - (nargs + 1 + mcall);
220
221     if(mcall)
222         naHash_set(f->locals, globals->meRef, frame[-1]);
223
224     // Set the argument symbols, and put any remaining args in a vector
225     c = (*frame++).ref.ptr.func->code.ref.ptr.code;
226     if(nargs < c->nArgs) ERR(ctx, "not enough arguments to function call");
227     for(i=0; i<c->nArgs; i++)
228         naHash_newsym(f->locals.ref.ptr.hash,
229                       &c->constants[c->argSyms[i]], &frame[i]);
230     frame += c->nArgs;
231     nargs -= c->nArgs;
232     for(i=0; i<c->nOptArgs; i++, nargs--) {
233         naRef val = nargs > 0 ? frame[i] : c->constants[c->optArgVals[i]];
234         if(IS_CODE(val))
235             val = bindFunction(ctx, &ctx->fStack[ctx->fTop-2], val);
236         naHash_newsym(f->locals.ref.ptr.hash, &c->constants[c->optArgSyms[i]], 
237                       &val);
238     }
239     if(c->needArgVector || nargs > 0)
240     {
241         naRef args = naNewVector(ctx);
242         naVec_setsize(args, nargs > 0 ? nargs : 0);
243         for(i=0; i<nargs; i++)
244             args.ref.ptr.vec->rec->array[i] = *frame++;
245         naHash_newsym(f->locals.ref.ptr.hash, &c->restArgSym, &args);
246     }
247
248     ctx->opTop = f->bp; // Pop the stack last, to avoid GC lossage
249     DBG(printf("Entering frame %d with %d args\n", ctx->fTop-1, nargs);)
250     return f;
251 }
252
253 static naRef evalAndOr(struct Context* ctx, int op, naRef ra, naRef rb)
254 {
255     int a = boolify(ctx, ra);
256     int b = boolify(ctx, rb);
257     int result;
258     if(op == OP_AND) result = a && b ? 1 : 0;
259     else             result = a || b ? 1 : 0;
260     return naNum(result);
261 }
262
263 static naRef evalEquality(int op, naRef ra, naRef rb)
264 {
265     int result = naEqual(ra, rb);
266     return naNum((op==OP_EQ) ? result : !result);
267 }
268
269 // When a code object comes out of the constant pool and shows up on
270 // the stack, it needs to be bound with the lexical context.
271 static naRef bindFunction(struct Context* ctx, struct Frame* f, naRef code)
272 {
273     naRef result = naNewFunc(ctx, code);
274     result.ref.ptr.func->namespace = f->locals;
275     result.ref.ptr.func->next = f->func;
276     return result;
277 }
278
279 static int getClosure(struct naFunc* c, naRef sym, naRef* result)
280 {
281     while(c) {
282         if(naHash_get(c->namespace, sym, result)) return 1;
283         c = c->next.ref.ptr.func;
284     }
285     return 0;
286 }
287
288 static naRef getLocal2(struct Context* ctx, struct Frame* f, naRef sym)
289 {
290     naRef result;
291     if(!naHash_get(f->locals, sym, &result))
292         if(!getClosure(f->func.ref.ptr.func, sym, &result))
293             ERR(ctx, "undefined symbol");
294     return result;
295 }
296
297 static void getLocal(struct Context* ctx, struct Frame* f,
298                      naRef* sym, naRef* out)
299 {
300     struct naFunc* func;
301     struct naStr* str = sym->ref.ptr.str;
302     if(naHash_sym(f->locals.ref.ptr.hash, str, out))
303         return;
304     func = f->func.ref.ptr.func;
305     while(func && func->namespace.ref.ptr.hash) {
306         if(naHash_sym(func->namespace.ref.ptr.hash, str, out))
307             return;
308         func = func->next.ref.ptr.func;
309     }
310     // Now do it again using the more general naHash_get().  This will
311     // only be necessary if something has created the value in the
312     // namespace using the more generic hash syntax
313     // (e.g. namespace["symbol"] and not namespace.symbol).
314     *out = getLocal2(ctx, f, *sym);
315 }
316
317 static int setClosure(naRef func, naRef sym, naRef val)
318 {
319     struct naFunc* c = func.ref.ptr.func;
320     if(c == 0) { return 0; }
321     else if(naHash_tryset(c->namespace, sym, val)) { return 1; }
322     else { return setClosure(c->next, sym, val); }
323 }
324
325 static naRef setSymbol(struct Frame* f, naRef sym, naRef val)
326 {
327     // Try the locals first, if not already there try the closures in
328     // order.  Finally put it in the locals if nothing matched.
329     if(!naHash_tryset(f->locals, sym, val))
330         if(!setClosure(f->func, sym, val))
331             naHash_set(f->locals, sym, val);
332     return val;
333 }
334
335 // Recursively descend into the parents lists
336 static int getMember(struct Context* ctx, naRef obj, naRef fld,
337                      naRef* result, int count)
338 {
339     naRef p;
340     if(--count < 0) ERR(ctx, "too many parents");
341     if(!IS_HASH(obj)) ERR(ctx, "non-objects have no members");
342     if(naHash_get(obj, fld, result)) {
343         return 1;
344     } else if(naHash_get(obj, globals->parentsRef, &p)) {
345         if(IS_VEC(p)) {
346             int i;
347             struct VecRec* v = p.ref.ptr.vec->rec;
348             for(i=0; i<v->size; i++)
349                 if(getMember(ctx, v->array[i], fld, result, count))
350                     return 1;
351         } else
352             ERR(ctx, "parents field not vector");
353     }
354     return 0;
355 }
356
357 // OP_EACH works like a vector get, except that it leaves the vector
358 // and index on the stack, increments the index after use, and pops
359 // the arguments and pushes a nil if the index is beyond the end.
360 static void evalEach(struct Context* ctx, int useIndex)
361 {
362     int idx = (int)(ctx->opStack[ctx->opTop-1].num);
363     naRef vec = ctx->opStack[ctx->opTop-2];
364     if(!IS_VEC(vec)) naRuntimeError(ctx, "foreach enumeration of non-vector");
365     if(!vec.ref.ptr.vec->rec || idx >= vec.ref.ptr.vec->rec->size) {
366         ctx->opTop -= 2; // pop two values
367         PUSH(naNil());
368         return;
369     }
370     ctx->opStack[ctx->opTop-1].num = idx+1; // modify in place
371     PUSH(useIndex ? naNum(idx) : naVec_get(vec, idx));
372 }
373
374 #define ARG() cd->byteCode[f->ip++]
375 #define CONSTARG() cd->constants[ARG()]
376 #define POP() ctx->opStack[--ctx->opTop]
377 #define STK(n) (ctx->opStack[ctx->opTop-(n)])
378 #define FIXFRAME() f = &(ctx->fStack[ctx->fTop-1]); \
379                    cd = f->func.ref.ptr.func->code.ref.ptr.code;
380 static naRef run(struct Context* ctx)
381 {
382     struct Frame* f;
383     struct naCode* cd;
384     int op, arg;
385     naRef a, b, c;
386
387     FIXFRAME();
388
389     while(1) {
390         op = cd->byteCode[f->ip++];
391         DBG(printf("Stack Depth: %d\n", ctx->opTop));
392         DBG(printOpDEBUG(f->ip-1, op));
393         switch(op) {
394         case OP_POP:
395             ctx->opTop--;
396             break;
397         case OP_DUP:
398             PUSH(ctx->opStack[ctx->opTop-1]);
399             break;
400         case OP_DUP2:
401             PUSH(ctx->opStack[ctx->opTop-2]);
402             PUSH(ctx->opStack[ctx->opTop-2]);
403             break;
404         case OP_XCHG:
405             a = STK(1); STK(1) = STK(2); STK(2) = a;
406             break;
407
408 #define BINOP(expr) do { \
409     double l = IS_NUM(STK(2)) ? STK(2).num : numify(ctx, STK(2)); \
410     double r = IS_NUM(STK(1)) ? STK(1).num : numify(ctx, STK(1)); \
411     STK(2).ref.reftag = ~NASAL_REFTAG; \
412     STK(2).num = expr; \
413     ctx->opTop--; } while(0)
414
415         case OP_PLUS:  BINOP(l + r);         break;
416         case OP_MINUS: BINOP(l - r);         break;
417         case OP_MUL:   BINOP(l * r);         break;
418         case OP_DIV:   BINOP(l / r);         break;
419         case OP_LT:    BINOP(l <  r ? 1 : 0); break;
420         case OP_LTE:   BINOP(l <= r ? 1 : 0); break;
421         case OP_GT:    BINOP(l >  r ? 1 : 0); break;
422         case OP_GTE:   BINOP(l >= r ? 1 : 0); break;
423
424 #undef BINOP
425
426         case OP_EQ: case OP_NEQ:
427             STK(2) = evalEquality(op, STK(2), STK(1));
428             ctx->opTop--;
429             break;
430         case OP_AND: case OP_OR:
431             STK(2) = evalAndOr(ctx, op, STK(2), STK(1));
432             ctx->opTop--;
433             break;
434         case OP_CAT:
435             // stringify can call the GC, so don't take stuff of the stack!
436             a = stringify(ctx, ctx->opStack[ctx->opTop-1]);
437             b = stringify(ctx, ctx->opStack[ctx->opTop-2]);
438             c = naStr_concat(naNewString(ctx), b, a);
439             ctx->opTop -= 2;
440             PUSH(c);
441             break;
442         case OP_NEG:
443             STK(1) = naNum(-numify(ctx, STK(1)));
444             break;
445         case OP_NOT:
446             STK(1) = naNum(boolify(ctx, STK(1)) ? 0 : 1);
447             break;
448         case OP_PUSHCONST:
449             a = CONSTARG();
450             if(IS_CODE(a)) a = bindFunction(ctx, f, a);
451             PUSH(a);
452             break;
453         case OP_PUSHONE:
454             PUSH(naNum(1));
455             break;
456         case OP_PUSHZERO:
457             PUSH(naNum(0));
458             break;
459         case OP_PUSHNIL:
460             PUSH(naNil());
461             break;
462         case OP_NEWVEC:
463             PUSH(naNewVector(ctx));
464             break;
465         case OP_VAPPEND:
466             naVec_append(STK(2), STK(1));
467             ctx->opTop--;
468             break;
469         case OP_NEWHASH:
470             PUSH(naNewHash(ctx));
471             break;
472         case OP_HAPPEND:
473             naHash_set(STK(3), STK(2), STK(1));
474             ctx->opTop -= 2;
475             break;
476         case OP_LOCAL:
477             a = CONSTARG();
478             getLocal(ctx, f, &a, &b);
479             PUSH(b);
480             break;
481         case OP_SETSYM:
482             STK(2) = setSymbol(f, STK(2), STK(1));
483             ctx->opTop--;
484             break;
485         case OP_SETLOCAL:
486             naHash_set(f->locals, STK(2), STK(1));
487             STK(2) = STK(1); // FIXME: reverse order of arguments instead!
488             ctx->opTop--;
489             break;
490         case OP_MEMBER:
491             if(!getMember(ctx, STK(1), CONSTARG(), &STK(1), 64))
492                 ERR(ctx, "no such member");
493             break;
494         case OP_SETMEMBER:
495             if(!IS_HASH(STK(3))) ERR(ctx, "non-objects have no members");
496             naHash_set(STK(3), STK(2), STK(1));
497             STK(3) = STK(1); // FIXME: fix arg order instead
498             ctx->opTop -= 2;
499             break;
500         case OP_INSERT:
501             containerSet(ctx, STK(3), STK(2), STK(1));
502             STK(3) = STK(1); // FIXME: codegen order again...
503             ctx->opTop -= 2;
504             break;
505         case OP_EXTRACT:
506             STK(2) = containerGet(ctx, STK(2), STK(1));
507             ctx->opTop--;
508             break;
509         case OP_JMPLOOP:
510             // Identical to JMP, except for locking
511             naCheckBottleneck();
512             f->ip = cd->byteCode[f->ip];
513             DBG(printf("   [Jump to: %d]\n", f->ip);)
514             break;
515         case OP_JMP:
516             f->ip = cd->byteCode[f->ip];
517             DBG(printf("   [Jump to: %d]\n", f->ip);)
518             break;
519         case OP_JIFNIL:
520             arg = ARG();
521             if(IS_NIL(STK(1))) {
522                 ctx->opTop--; // Pops **ONLY** if it's nil!
523                 f->ip = arg;
524                 DBG(printf("   [Jump to: %d]\n", f->ip);)
525             }
526             break;
527         case OP_JIFNOT:
528             arg = ARG();
529             if(!boolify(ctx, POP())) {
530                 f->ip = arg;
531                 DBG(printf("   [Jump to: %d]\n", f->ip);)
532             }
533             break;
534         case OP_FCALL:
535             f = setupFuncall(ctx, ARG(), 0, 0);
536             cd = f->func.ref.ptr.func->code.ref.ptr.code;
537             break;
538         case OP_FTAIL:
539             f = setupFuncall(ctx, ARG(), 0, 1);
540             cd = f->func.ref.ptr.func->code.ref.ptr.code;
541             break;
542         case OP_MCALL:
543             f = setupFuncall(ctx, ARG(), 1, 0);
544             cd = f->func.ref.ptr.func->code.ref.ptr.code;
545             break;
546         case OP_MTAIL:
547             f = setupFuncall(ctx, ARG(), 1, 1);
548             cd = f->func.ref.ptr.func->code.ref.ptr.code;
549             break;
550         case OP_RETURN:
551             a = STK(1);
552             if(--ctx->fTop <= 0) return a;
553             ctx->opTop = f->bp + 1; // restore the correct opstack frame!
554             STK(1) = a;
555             FIXFRAME();
556             break;
557         case OP_EACH:
558             evalEach(ctx, 0);
559             break;
560         case OP_INDEX:
561             evalEach(ctx, 1);
562             break;
563         case OP_MARK: // save stack state (e.g. "setjmp")
564             if(ctx->markTop >= MAX_MARK_DEPTH)
565                 naRuntimeError(ctx, "mark stack overflow");
566             ctx->markStack[ctx->markTop++] = ctx->opTop;
567             break;
568         case OP_UNMARK: // pop stack state set by mark
569             ctx->markTop--;
570             break;
571         case OP_BREAK: // restore stack state (FOLLOW WITH JMP!)
572             ctx->opTop = ctx->markStack[--ctx->markTop];
573             break;
574         default:
575             ERR(ctx, "BUG: bad opcode");
576         }
577         ctx->temps.ref.ptr.vec->rec->size = 0; // reset GC temp vector
578         DBG(printStackDEBUG(ctx);)
579     }
580     return naNil(); // unreachable
581 }
582 #undef POP
583 #undef CONSTARG
584 #undef STK
585 #undef FIXFRAME
586
587 void naSave(struct Context* ctx, naRef obj)
588 {
589     naVec_append(globals->save, obj);
590 }
591
592 // FIXME: handle ctx->callParent
593 int naStackDepth(struct Context* ctx)
594 {
595     return ctx->fTop;
596 }
597
598 // FIXME: handle ctx->callParent
599 int naGetLine(struct Context* ctx, int frame)
600 {
601     struct Frame* f = &ctx->fStack[ctx->fTop-1-frame];
602     naRef func = f->func;
603     int ip = f->ip;
604     if(IS_FUNC(func) && IS_CODE(func.ref.ptr.func->code)) {
605         struct naCode* c = func.ref.ptr.func->code.ref.ptr.code;
606         unsigned short* p = c->lineIps + c->nLines - 2;
607         while(p >= c->lineIps && p[0] > ip)
608             p -= 2;
609         return p[1];
610     }
611     return -1;
612 }
613
614 // FIXME: handle ctx->callParent
615 naRef naGetSourceFile(struct Context* ctx, int frame)
616 {
617     naRef f = ctx->fStack[ctx->fTop-1-frame].func;
618     f = f.ref.ptr.func->code;
619     return f.ref.ptr.code->srcFile;
620 }
621
622 char* naGetError(struct Context* ctx)
623 {
624     if(IS_STR(ctx->dieArg))
625         return ctx->dieArg.ref.ptr.str->data;
626     return ctx->error;
627 }
628
629 naRef naBindFunction(naContext ctx, naRef code, naRef closure)
630 {
631     naRef func = naNewFunc(ctx, code);
632     func.ref.ptr.func->namespace = closure;
633     func.ref.ptr.func->next = naNil();
634     return func;
635 }
636
637 naRef naBindToContext(naContext ctx, naRef code)
638 {
639     naRef func = naNewFunc(ctx, code);
640     struct Frame* f = &ctx->fStack[ctx->fTop-1];
641     func.ref.ptr.func->namespace = f->locals;
642     func.ref.ptr.func->next = f->func;
643     return func;
644 }
645
646 naRef naCall(naContext ctx, naRef func, naRef args, naRef obj, naRef locals)
647 {
648     naRef result;
649     if(!ctx->callParent) naModLock(ctx);
650
651     // We might have to allocate objects, which can call the GC.  But
652     // the call isn't on the Nasal stack yet, so the GC won't find our
653     // C-space arguments.
654     naVec_append(ctx->temps, func);
655     naVec_append(ctx->temps, args);
656     naVec_append(ctx->temps, obj);
657     naVec_append(ctx->temps, locals);
658
659     if(IS_NIL(locals))
660         locals = naNewHash(ctx);
661     if(!IS_FUNC(func))
662         func = naNewFunc(ctx, func); // bind bare code objects
663
664     if(!IS_NIL(args))
665         naHash_set(locals, globals->argRef, args);
666     if(!IS_NIL(obj))
667         naHash_set(locals, globals->meRef, obj);
668
669     ctx->dieArg = naNil();
670
671     ctx->opTop = ctx->markTop = 0;
672     ctx->fTop = 1;
673     ctx->fStack[0].func = func;
674     ctx->fStack[0].locals = locals;
675     ctx->fStack[0].ip = 0;
676     ctx->fStack[0].bp = ctx->opTop;
677
678     // Return early if an error occurred.  It will be visible to the
679     // caller via naGetError().
680     ctx->error = 0;
681     if(setjmp(ctx->jumpHandle))
682         return naNil();
683
684     if(IS_CCODE(func.ref.ptr.func->code)) {
685         naCFunction fp = func.ref.ptr.func->code.ref.ptr.ccode->fptr;
686         struct naVec* av = args.ref.ptr.vec;
687         result = (*fp)(ctx, obj, av->rec->size, av->rec->array);
688     } else
689         result = run(ctx);
690     if(!ctx->callParent) naModUnlock(ctx);
691     return result;
692 }
693