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