1 /*
2 * $Id: fnctn.c,v 1.4 2010-07-03 19:42:31 dhmunro Exp $
3 */
4 /* Copyright (c) 2005, The Regents of the University of California.
5 * All rights reserved.
6 * This file is part of yorick (http://yorick.sourceforge.net).
7 * Read the accompanying LICENSE file for details.
8 */
9
10 /*
11 EvalFN evaluates a Yorick interpretive function. This is one half
12 of the Yorick Eval instruction. The other half is array indexing,
13 which is handled in the array.c file.
14
15 The most general Yorick function has 3 distinct kinds of dummy
16 parameters:
17 func sample(p1, p2, p3, .., pA=, pB=, pC=, pD=)
18
19 where p1, p2, and p3 are ordinary positional parameters,
20 .. means that more positional parameters are accessible
21 by this function-- the function NextArg() returns
22 these additional positional parameters one at a time,
23 pA, PB, pC, and pD are keyword parameters,
24 Both positional and keyword parameters are optional; if no
25 corresponding actual parameter is provided, EvalFN must initialize
26 them to nil.
27
28 On entry to EvalFN, the Yorick program stack might look as follows:
29
30 .. sample p1 kB pB p2 p3 kA pA p4 kD pD p5
31 * * pB * * * pA * * pD * *
32
33 where 3 keywords and 2 extra positional parameters have been supplied.
34 The second line shows where the Symbol->index pointers point; on input
35 only the keyword markers kA, kB, and kD are significant.
36 On exit from EvalFN, the stack would read:
37
38 .. sample p1' kB pB' p2' p3' kA pA' p4' kD pD' p4 p5 (locals) R
39 -2 p1 -1 pB p2 p3 -1 pA p4 -1 pD -1 -1 -1 ?
40
41 The R entry contains the return address.
42 In addition to what is shown above, the keyword markers
43 now have meaningful offset pointers, namely:
44
45 kL -> kA -> kB -> kD
46 k4 -> p4
47
48 */
49
50 #include "play.h"
51 #include "ydata.h"
52
53 /*--------------------------------------------------------------------------*/
54
55 extern UnaryOp EvalFN, EvalBI;
56
57 extern VMaction Return;
58 extern VMaction NextArg, MoreArgs;
59
60 /* YRecoverExterns must be called after an asynchronous interrupt
61 of either EvalFN or Return in order to recover from the "partial"
62 function call or return sequence. */
63 extern void YRecoverExterns(void);
64
65 /* ClearStack and AbortReturn are a higher level interface than
66 YRecoverExterns. ClearStack positions the stack to the topmost
67 returnSym (if any), returning the associated return pc (or 0).
68 AbortReturn clears the stack with ClearStack, then returns to the
69 caller WITHOUT leaving any result on the stack. This is useful
70 only during debugging (see task.c). */
71 extern Instruction *ClearStack(void);
72 extern Instruction *AbortReturn(void);
73
74 extern void YCatchDrop(long isp); /* in task.c */
75 extern long ispCatch; /* in task.c */
76
77 static void Swap(Symbol *sp, long index);
78 static Symbol *ExtractKey(int index);
79
80 /*--------------------------------------------------------------------------*/
81
82 /*
83 EvalFN uses Swap to swap the external values of the dummy parameters
84 with the values of the actual parameters found on the stack. This is
85 complicated by the possibility of a referenceSym in the actual
86 parameter list on the stack. Any referenceSyms have been copied in a
87 first pass through the actual parameters to avoid a possible prior
88 swap by a dummy parameter of the same name.
89 Swap then does the actual swapping.
90
91 Return uses YRecoverExterns to restore the external values of local
92 variables. YRecoverExterns must also be called after an asynchronous
93 interrupt of either EvalFN or Return in order to recover from the
94 "partial" function call or return sequence.
95 */
96
97 /* The code must ensure that the external values can be recovered even
98 if the program is interrupted asynchronously in mid-swap. */
99 static Symbol *spRecover= 0;
100
101 /* YRecoverExterns is usually called by Return, which needs the
102 beginning of the referenceSym list, and the beginning of the
103 function call list. */
104 static Symbol *spFunction= 0;
105 static Symbol *spReference= 0;
106 static int nReferences;
107
Swap(Symbol * stack,long index)108 static void Swap(Symbol *stack, long index)
109 {
110 Symbol *dummy= &globTab[index]; /* dummy parameter */
111
112 /* The order of the following operations assures that the external
113 value can be restored, even if the program is asynchronously
114 interrupted during the swapping process (see RecoverExterns). */
115
116 /* There is a simpler, faster implementation, which takes advantage of
117 the fact that dummy->value.db->references does not actually change
118 here (the Unref undoes the Ref above). However, for a very brief
119 time, the better algorithm either leaves two references to
120 dummy->value.db without incrementing its reference counter, or
121 leaves the external value of dummy "unprotected" by spRecover.
122 As a first cut, I take the bomb-proof but less efficient solution.
123 If this routine proves to be a significant bottleneck for code
124 timing, I would consider switching back to the faster, riskier
125 algorithm... */
126
127 /* copy stack value to temporary */
128 OpTable *opsX= stack->ops;
129 SymbolValue valueX= stack->value;
130 OpTable *opsD= dummy->ops;
131 int isDB= (opsD==&dataBlockSym);
132
133 /* for bomb-proof safety, "dud" the stack entry */
134 stack->ops= &intScalar; /* value now does NOT reference a pointer */
135
136 /* copy external value to stack */
137 if (isDB) stack->value.db= Ref(dummy->value.db); /* more bomb-proofing */
138 else stack->value= dummy->value;
139 stack->ops= opsD;
140
141 /* update pointer for YRecoverExterns */
142 stack->index= index; /* mark where to put it back */
143 spRecover= stack;
144
145 /* now it is OK to "dud" globTab entry and delete the temporary use */
146 if (isDB) {
147 dummy->ops= &intScalar;
148 Unref(stack->value.db);
149 }
150
151 /* copy original stack value into globTab */
152 dummy->value= valueX;
153 dummy->ops= opsX;
154
155 if (opsX==&dataBlockSym) {
156 DataBlock *db= dummy->value.db;
157 if (db->ops==&lvalueOps) {
158 /* fetch LValue now to avoid repeated fetches during execution */
159 Array *array= FetchLValue(db, dummy);
160 if (!array->type.dims) {
161 if (array->ops==&doubleOps) {
162 dummy->ops= &doubleScalar;
163 dummy->value.d= array->value.d[0];
164 Unref(array);
165 } else if (array->ops==&longOps) {
166 dummy->ops= &longScalar;
167 dummy->value.l= array->value.l[0];
168 Unref(array);
169 } else if (array->ops==&intOps) {
170 dummy->ops= &intScalar;
171 dummy->value.i= array->value.i[0];
172 Unref(array);
173 }
174 }
175 }
176 }
177 }
178
YRecoverExterns(void)179 void YRecoverExterns(void)
180 {
181 if (spRecover) {
182 int index;
183 Symbol *local, *spnow;
184 OpTable *opsX, *ops;
185 SymbolValue valueX;
186 int isDB;
187
188 /* Same remark about implementation as for Swap function above. */
189
190 nReferences= 0;
191
192 for (index=spRecover->index ; index!=-2 ; index=spRecover->index) {
193 if (index==-1) { spRecover--; continue; }
194 ops= spRecover->ops;
195 if (ops==&referenceSym) {
196 nReferences++;
197 spReference= spRecover--;
198 continue;
199 }
200 isDB= (ops==&dataBlockSym);
201
202 local= &globTab[index];
203
204 /* copy local value to temporary */
205 opsX= local->ops;
206 valueX= local->value;
207
208 /* temporarily "dud" local value for bomb-proofing */
209 local->ops= &intScalar;
210
211 /* copy external value back to globTab */
212 if (isDB) local->value.db= Ref(spRecover->value.db);
213 else local->value= spRecover->value;
214 local->ops= ops;
215
216 /* update pointer for YRecoverExterns */
217 spnow= spRecover--;
218
219 /* now it is OK to "dud" stack entry and delete the temporary use */
220 if (isDB) {
221 (spRecover+1)->ops= &intScalar;
222 Unref((spRecover+1)->value.db);
223 }
224
225 /* copy local value back to stack */
226 spnow->value= valueX;
227 spnow->ops= opsX;
228 }
229
230 spFunction= spRecover;
231 spRecover= 0;
232 }
233 }
234
235 /*--------------------------------------------------------------------------*/
236
237 static Symbol *actualKeys;
238
ExtractKey(int index)239 static Symbol *ExtractKey(int index)
240 {
241 Symbol *key= actualKeys, *prev= 0;
242 while (key!=prev && (key+1)->index!=index) {
243 prev= key;
244 key-= key->value.offset;
245 }
246 if (key!=prev) {
247 /* key found, unlink from actualKeys list */
248 int offset= key->value.offset;
249 if (prev) prev->value.offset= offset? prev-(key-offset) : 0;
250 else actualKeys= offset? key-offset : 0;
251 } else {
252 /* no key corresponds to index in actualKeys list */
253 key= 0;
254 }
255 return key;
256 }
257
258 /*--------------------------------------------------------------------------*/
259
EvalFN(Operand * op)260 void EvalFN(Operand *op)
261 {
262 Symbol *stack= op->owner;
263 int n= op->references; /* (sic) # of actual parameters supplied */
264 Function *func= op->value;
265 Instruction *code= &func->code[1]; /* (code[0] is index to function) */
266 int nReq= func->nReq; /* (see CheckStack call below) */
267 int nPos= func->nPos; /* number of dummy positional parameters */
268 int nKey= func->nKey; /* number of dummy keyword parameters */
269 int nLoc= func->nLocal; /* number of local variables */
270 long hasPosList= func->hasPosList;
271 long posList;
272
273 int actual, dummy, index, nExtra;
274 Symbol *spnow, *extraPos, *key;
275
276 P_SOFTFPE_TEST;
277
278 /* Be sure the stack is long enough for a worst-case invocation of this
279 function. nReq= nPos + (hasPosList&1) + nKey + nLoc + (deepest stack
280 required for expression evaluation) + 10
281 + 1 for return address for this function
282 The nPos and nKey terms must be present because they may not be
283 actual arguments, and because even if they are supplied they may
284 be referenceSyms which must be copied for use during return.
285 The extra 10 is so that builtin procedures are always guaranteed
286 8(+2 for luck) free stack slots without calling CheckStack. */
287 if (CheckStack(nReq)) stack= sp-n;
288
289 /* Handle all actual parameters.
290 This must be done in two passes to avoid accidental collisions
291 between dummy parameters and indirect references on the stack
292 to external variables of the same name. All of this could be
293 avoided if function parameters were always passed by value,
294 never by reference. But I can't bring myself to disallow the
295 FORTRAN-like function which uses its parameters to return values. */
296
297 /* First pass copies any indirect references.
298 The parser has guaranteed that index (dummy) will not be repeated,
299 since there may not be 2 dummy parameters with the same name.
300 However, nothing prevents the one or more of the actual parameters
301 (stack) from being referenceSyms to the same name as a dummy
302 parameter. This possibility requires copying all referenceSym
303 actual parameters onto the stack (possibly multiple times).
304 Also, note that a globTab entry may NEVER be a referenceSym, so
305 if return is to affect external values of parameters, any
306 referenceSym parameters must remain on the stack. */
307 posList= hasPosList>>1;
308 hasPosList&= 1;
309 nExtra= -nPos;
310 spnow= stack;
311 for (actual=0 ; actual<n ; actual++) {
312 spnow++;
313 if (spnow->ops) {
314 if (spnow->ops==&referenceSym) {
315 if (posList) {
316 if (nExtra<0 && (posList&1)) {
317 /* this is an output parameter */
318 extraPos= sp+1; /* push copy of referenceSym */
319 extraPos->ops= &referenceSym;
320 extraPos->index= spnow->index;
321 extraPos->value.offset= extraPos-spnow; /* install ref offset */
322 sp= extraPos;
323 }
324 posList>>= 1;
325 }
326 ReplaceRef(spnow); /* replace original reference by object */
327 } else if (posList) {
328 posList>>= 1;
329 }
330 nExtra++;
331 } else {
332 /* skip over keyword arguments */
333 spnow++;
334 actual++;
335 }
336 }
337
338 /* Mark beginning of function call for YRecoverExterns. This MUST
339 be done before spRecover has been set (by Swap). */
340 stack->index= -2;
341
342 /* Second pass swaps the external values onto the stack and local
343 values into the global symbol table. */
344 posList= -1;
345 dummy= 0;
346 extraPos= actualKeys= 0;
347 spnow= stack;
348 for (actual=0 ; actual<n ; actual++) {
349 spnow++;
350 if (spnow->ops!=0) { /* actual parameter is positional */
351 if (dummy<nPos) {
352 dummy++;
353 index= (code++)->index;
354 Swap(spnow, index);
355 } else {
356 if (!extraPos) {
357 if (!hasPosList) {
358 YRecoverExterns();
359 YError("too many actual parameters in function call");
360 }
361 dummy++;
362 posList= (code++)->index;
363 extraPos= spnow;
364 }
365 spnow->index= -1; /* extras cannot be swapped back on return */
366 }
367
368 } else { /* actual parameter is keyword */
369 index= spnow->index;
370 spnow->index= -1; /* keywords must not be swapped back on return */
371 spnow->value.offset= actualKeys? spnow-actualKeys : 0;
372 actualKeys= spnow++;
373 actual++; /* increment actual, spnow to keyword parameter */
374 Swap(spnow, index);
375 }
376 }
377
378 /* initialize non-actual dummy positionals to nil */
379 while (dummy<nPos) {
380 dummy++;
381 PushDataBlock(RefNC(&nilDB));
382 Swap(sp, (code++)->index);
383 }
384 if (hasPosList && posList<0) posList= (code++)->index;
385
386 /* initialize non-actual dummy keywords to nil */
387 for (dummy=0 ; dummy<nKey ; dummy++) {
388 index= (code++)->index;
389 key= ExtractKey(index); /* unlinks key (index) from list */
390 if (!key) {
391 /* missing dummy keywords initialized to nil */
392 PushDataBlock(RefNC(&nilDB));
393 Swap(sp, index);
394 }
395 }
396 if (actualKeys) {
397 YRecoverExterns();
398 YError("unrecognized keyword parameter(s) in function call");
399 }
400
401 /* handle NextArg() parameter -- this is an entry in the global
402 symbol table with the illegal name "*va*" */
403 /* NOTE-- assumes fewer than 2048 actual parameters, and that the
404 stack depth is less than a million Symbols... */
405 if (posList>=0) {
406 spnow= sp+1;
407 spnow->ops= &longScalar;
408 spnow->value.l= extraPos? ((extraPos-spBottom)<<11 | nExtra) : 0;
409 sp++;
410 Swap(sp, posList);
411 }
412
413 /* initialize all local variables to nil */
414 for (dummy=0 ; dummy<nLoc ; dummy++) {
415 PushDataBlock(RefNC(&nilDB));
416 Swap(sp, (code++)->index);
417 }
418
419 /* push return address marker */
420 spnow= sp+1;
421 spnow->ops= &returnSym;
422 spnow->index= 0; /* offset to object-context, if any */
423 spnow->value.pc= pc;
424 sp++;
425
426 /* stack is again intact, YRecoverExterns can be a no-op */
427 spRecover= 0;
428
429 /* set stack and branch into this function */
430 pc= code;
431 }
432
EvalBI(Operand * op)433 void EvalBI(Operand *op)
434 {
435 Symbol *stack= op->owner;
436 long stackIndex= stack-spBottom; /* see comment after function call */
437 int n= op->references; /* interpret misuse in FormEvalOp */
438 BIFunction *bif= op->value;
439
440 /* Invoke built-in function */
441 P_SOFTFPE_TEST;
442 bif->function(n);
443 P_SOFTFPE_TEST;
444
445 /* Adjust remembered stack to allow for the stack being moved -- this
446 can happen in Y_require and Y_include, and there is no other way
447 to handle the problem. The efficiency loss from this instruction
448 and the stackIndex definition above is regrettable... */
449 stack= spBottom+stackIndex;
450
451 /* Move return value to what will be the top of the stack, and
452 discard the reference to the function which is returning.
453 However, allow builtin to chain to interpreted function. */
454 if (sp>stack && sp->ops!=&returnSym) {
455 Symbol *spnow= sp--;
456 stack->ops= &intScalar; /* "dud" BIFunction reference */
457 stack->value= spnow->value; /* move final value into place (dudded) */
458 Unref(bif);
459 stack->ops= spnow->ops; /* "arm" final value */
460 /* discard the input parameters and scratch space */
461 if (sp>stack) Drop((int)(sp-stack));
462 }
463 }
464
465 /*--------------------------------------------------------------------------*/
466
Return(void)467 void Return(void)
468 {
469 Symbol *spnow, *extrn;
470 OpTable *opsX;
471 SymbolValue valueX;
472
473 P_SOFTFPE_TEST;
474
475 /* Pop off any pending catch calls. */
476 if ((sp-1-spBottom)<=ispCatch) YCatchDrop(sp-1-spBottom);
477
478 /* check for object context, update object if present */
479 if ((sp-1)->index) yo_cupdate(1 + (int)(sp-1)->index);
480
481 /* Set pc to caller. Must do this BEFORE the return PC stack element
482 is stripped away-- otherwise, there is no way to get back to the
483 caller if this routine is asynchronously interrupted. */
484 pc= (sp-1)->value.pc;
485
486 /* Restore external values of local variables in a way that is
487 protected against asynchronous interruption. */
488 spRecover= sp-2;
489 YRecoverExterns();
490
491 /* Move return value to what will be the top of the stack, and
492 discard the reference to the function which is returning. */
493 spnow= sp--;
494 valueX= spFunction->value; /* (know that ops is dataBlockSym) */
495 spFunction->ops= &intScalar;
496 spFunction->value= spnow->value;
497 spFunction->ops= spnow->ops;
498 Unref(valueX.db); /* may clobber sp+1 = spnow !! */
499
500 /* Redefine any actual parameters which were referenceSyms. */
501 while (nReferences--) {
502 spnow= spReference - spReference->value.offset;
503 extrn= &globTab[spReference->index];
504 spReference++;
505
506 /* YRecoverExterns has moved the local value of the dummy argument
507 onto the stack. Delete the external value which is about to
508 be replaced, then "dud" the stack value before moving it into
509 the external location. */
510 opsX= extrn->ops;
511 valueX= extrn->value;
512 if (opsX==&dataBlockSym) {
513 extrn->ops= &intScalar;
514 Unref(valueX.db);
515 }
516 opsX= spnow->ops;
517 spnow->ops= &intScalar;
518 extrn->value= spnow->value;
519 extrn->ops= opsX;
520 }
521
522 /* Clean local variables off stack (where YRecoverExterns put them). */
523 if (sp>spFunction) Drop((int)(sp-spFunction));
524 }
525
ClearStack(void)526 Instruction *ClearStack(void)
527 {
528 DataBlock *db;
529 YRecoverExterns();
530 while (sp>spBottom) {
531 if (sp->ops==&returnSym) return sp->value.pc;
532 db= (sp->ops==&dataBlockSym)? sp->value.db : 0;
533 sp--;
534 Unref(db);
535 }
536 return 0;
537 }
538
AbortReturn(void)539 Instruction *AbortReturn(void)
540 {
541 Instruction *pcRet= ClearStack();
542
543 /* Pop off any pending catch calls. */
544 if ((sp-spBottom)<=ispCatch) YCatchDrop(sp-spBottom);
545
546 /* Set pc to caller. Must do this BEFORE the return PC stack element
547 is stripped away-- otherwise, there is no way to get back to the
548 caller if this routine is asynchronously interrupted. */
549 if ((pc= pcRet)) {
550 /* Restore external values of local variables in a way that is
551 protected against asynchronous interruption. */
552 spRecover= sp-1;
553 YRecoverExterns();
554 Drop((int)(sp-spFunction+1));
555 }
556
557 return pcRet;
558 }
559
560 /*--------------------------------------------------------------------------*/
561
562 /* If a Yorick function definition includes a ".." dummy parameter, then
563 the parser will allow calls to the pseudo-function NextArg(), which
564 successively returns positional parameters beyond the named dummy
565 parameters. The MoreArgs() function returns true if there are any
566 more positional parameters available with NextArg() (which returns
567 nil if there are no more). */
568
MoreArgs(void)569 void MoreArgs(void)
570 {
571 /* this function returns the number of remaining positional parameters */
572 long va= globTab[(pc++)->index].value.l;
573 PushIntValue(va & 0x7ff);
574 }
575
NextArg(void)576 void NextArg(void)
577 {
578 long vaIndex= (pc++)->index;
579 long va= globTab[vaIndex].value.l;
580 int nExtra= (va & 0x7ff);
581 if (nExtra--) {
582 Symbol *extraPos= spBottom + (va>>11);
583 /* push copy of actual parameter to top of stack */
584 (sp+1)->ops= extraPos->ops;
585 (sp+1)->value= extraPos->value;
586 /* since you only get one crack at this parameter with NextArg, may
587 as well trash the original variable to avoid having to increment
588 a possible DataBlock reference counter */
589 extraPos->ops= &intScalar;
590 sp++;
591 /* update *va* in globTab for next call to NextArg() or MoreArgs() */
592 if (nExtra) {
593 /* skip over keywordSym pairs to find next positional parameter */
594 while ((++extraPos)->ops==0) extraPos++;
595 globTab[vaIndex].value.l= ((extraPos-spBottom)<<11 | nExtra);
596 } else {
597 globTab[vaIndex].value.l= 0;
598 }
599 } else {
600 PushDataBlock(RefNC(&nilDB));
601 }
602 }
603
604 /*--------------------------------------------------------------------------*/
605