.de Ip .IP \\$1 .br .. .nr $1 3 .nr $2 7 .nr $3 0 .NH 3 \*Miconx/start.s\fR .SH Overview .PP The routine \*Mmstart\fP in \*Mstart.s\fP is used to get Icon started. When the Icon interpreter is executed,\^ the C routine \*Mmain\fP passes control to \*Mmstart\fP,\^ and merely serves as a front-end for \*Mmstart\fP. .SH Generic Operation .Ls .Np Call the routine \*Minit\fR with the name of the file to interpret as its argument. .Np Make an Icon list out of the command line arguments using the \*Mllist\fR function. .Np Invoke the main procedure of the Icon program. .Le .SH \*Mstart\fP on the VAX .PP There is a short main program in \*Miconx/main.c\fP that calls \*Mmstart\fP with two arguments: .Ds .S1 main(argc, argv) int argc; char **argv; { mstart(argc, argv); } .De .PP The number of command line arguments is in \*Margc\fP, and \*Margv\fP is a pointer to an array of pointers to strings representing the arguments. \*Margv\^[0]\fP is the command used to invoke the interpreter and \*Margv\^[1]\fP is the name of the file being interpreted. Additional command line arguments are passed along to the main procedure of the Icon program. When \*Mmstart\fP gets control, \*M4(ap)\fP is the \*Margc\fP value and \*M8(ap)\fP is the argv value. .PP The first action taken by \*Mmstart\fP is to call \*Minit\fR to initialize the Icon run-time system. \*Minit\fR loads the header and code portions of the interpretable file into memory,\^ so \*Minit\fR needs the name of the interpretable file. The word at \*M8(ap)\fP is loaded into \*Mr9\fP, pointing it at \*Margv\^[0]\fP. Then the name of the file to interpret (\*Margv\^[1]\fP), residing at \*M4(r9)\fP, is pushed on the stack as the argument for \*Minit\fP, which is then called. .PP In order to provide conformity with the usual execution environment of Icon procedures,\^ an expression frame is created for the execution of the main procedure. Both the old expression frame pointer and the old generator frame pointer are set to be 0 in the expression frame for \*Mmain\fP. The failure label must point to an interpreter opcode that will terminate execution of the program. The opcode 0 is used for this purpose. A word of storage,\^ \*Mflab\fR,\^ is declared and initialized to 0. The failure label points to \*Mflab\fR. Thus,\^ if \*Mmain\fP fails,\^ the interpreter executes the \*Mquit\fR opcode. .PP The next task is to push the descriptor for the procedure main on the stack for later use by \*Minvoke\fR. The variable \*M_globals\fR contains the address of the list of global variable descriptors. The first global variable descriptor is always the one for the procedure main; if no main procedure was found when the program was linked,\^ the descriptor will be \*M&null\fR. The value of \*M_globals\fR is loaded into \*Mr0\fR and the word then referenced by \*Mr0\fR is checked to see if it is equal to \*MD_PROC\fR. (The first word of a descriptor for a procedure is always equal to \*MD_PROC\fR.) .\"\^[? not sure if we really need to check for D_PROC,\^ .\"I think just checking for ~= 0 will suffice.] If the word is not equal to \*MD_PROC\fR,\^ a branch is made to \*Mnomain\fR which generates the appropriate run-time error. Otherwise,\^ the descriptor for \*Mmain\fP is pushed onto the stack. (The effect of the instruction \*Mmovq\ (r0),\^\-(sp)\fR is to move 8 bytes (the size of a quadword) starting at the address referenced by \*Mr0\fR to the 8 bytes referenced by the \*Msp\fR after subtracting 8 from the \*Msp\fR.) .PP The main procedure is to be invoked with a list consisting of the command line arguments (if any). The Icon run-time routine \*Mllist\fR is used to make the list that is passed to the main procedure. \*Mllist\fR stores the descriptor for the list that it creates in the descriptor above its first argument descriptor,\^ so to accommodate the result,\^ a null descriptor is pushed on the stack using the \*Mclrq\fR instruction. Note that because \*Mllist\fP calls \*Msetbound\fP and \*Mclrbound\fP,\^ it is not possible to execute all of \*Mstart\fP until \*Mrt/setbound.s\fP has been completed. .PP At the beginning of this routine,\^ \*Mr9\fR was set to point at the first word of the argument list. Neither the name of the Icon interpreter nor the name of the interpretable file is desired in the argument list passed to main,\^ so \*Mr9\fR is twice incremented by 4 (the size in bytes of a word) to point it at the first actual program argument. .PP The next step is to construct the argument list for \*Mllist\fR. For each command line argument,\^ the address of the string and then its length are pushed on the stack. The length and address pairs form descriptors that \*Mllist\fP makes an Icon list from. \*Mr8\fR is used to count the arguments. After the addresses and lengths of each argument have been pushed,\^ the number of arguments is pushed on the stack. At this point,\^ the stack looks like this: .Ds .ft R .S1 descriptor for main procedure (2 4-byte words) a null descriptor (2 words containing 0) address of first argument to Icon program length of first argument \*(El address of last argument to Icon program length of last argument \*Msp\fR \*(ar number of arguments .De .LP All addresses and lengths are one word in size. The \*Mcalls\fR instruction needs to be told how many words are in its argument list. (This is necessary because when a return is made from the subroutine,\^ the specified number of words are removed from the stack.) There are two words for each argument and an additional word for the number of arguments. (Do not confuse the argument count for the \*Mllist\fR subroutine and the argument list size.) \*Mr8*2+1\fP is calculated in \*Mr8\fP. This value is used as the argument list length for the \*Mcalls\fR instruction. When \*Mllist\fR returns,\^ the arguments are stripped from the stack and the stack looks like this: .Ds .ft R .S1 descriptor for main procedure \*Msp\fR \*(ar descriptor for list of command line arguments .De .LP Note that the null descriptor pushed earlier received the result of the \*Mllist\fR function. .PP At this point,\^ the main procedure is ready to be invoked. The descriptor for the main procedure is \*(a0 and the descriptor for the list of command line arguments is \*(a1. Before invoking the main procedure,\^ the procedure frame pointer and the generator frame pointer are cleared. The main procedure is being invoked with one argument,\^ so a constant 1 is pushed on the stack. The \*Mcalls\fR instruction is given an argument of 3 because a word is used for the number of arguments and two additional words are used for the descriptor for the list of command line arguments. .PP If the main procedure fails,\^ the interpreter will encounter the 0 opcode discussed earlier. If the main procedure returns (this usually isn't done),\^ the return will manifest itself as \f3invoke\fR returning. If this happens,\^ \*M_c_exit\fR is called with an argument of 0. Incidentally,\^ this also happens when the interpreter hits the 0 opcode. .PP There is a block of code labeled \*Mnomain\fR that is executed when no main procedure is found. This calls the routine \*Mrunerr\fR to produce an error message. The actual call made is \*Mrunerr(117,\^0)\fR. The number 117 is looked up in a table of run-time errors. If the second argument to \*Mrunerr\fR is non-zero,\^ it is interpreted as being the address of a descriptor and the descriptor is examined to produce an ``offending value'' to accompany the run-time error. .PP The last portion of executable code in \f3start.s\fR is the subroutine \*M_c_exit\fR. If the variable \*M_monres\fR is non-zero,\^ it indicates that profiling is on,\^ and it must be turned off. This is accomplished by calling \*M_monitor(0)\fR. The routine \*M__cleanup\fR is then called to shut down the i/o system. Finally,\^ \*M_exit\fR is called with the argument of \*M_c_exit\fR to terminate execution of the Icon interpreter. .PP There are several data declarations in \*Mstart.s\fR. The first data declaration is a \*M.space 60\fR. This is an accommodation for the garbage collector. It insures that enough of the start of the data section is used up to force the addresses of other data objects to be greater than the defined constant \*MMAXTYPE\fR in \f3h/rt.h\fR. .PP Some assorted declarations are next. \*Mflab\fR is referenced by the interpreter if the main procedure fails. It must be at least a byte long and contain a 0. \*M_boundary\fR must be a word long and contain a 0. \*M_environ\fR must be a word long; its contents are unimportant as it is written into at the beginning of \*Mstart.s\fR. .PP The \*M_tended\fR array is also used in conjunction with garbage collection. It must declare space for five descriptors (two words per descriptor) that are initialized to 0. .\"\^[?] The label \*M_etended\fR is used to mark the end of the \*M_tended\fR array. .ne 1i .NH 3 \*Mrt/setbound.s\fR .SH Overview .PP \*Msetbound.s\fP contains code for \*Msetbound\fP and \*Mclrbound\fP. \*Msetbound\fP sets \*M_boundary\fP under appropriate conditions and \*Mclrbound\fP clears \*M_boundary\fP under appropriate conditions. .SH Generic Operation .PP When a call is made from Icon into C,\^ \*M_boundary\fP must be set to point at the procedure frame on the top of the stack. C routines that can be called from Icon have a call to \*Msetbound\fP as their first operation. If \*Msetbound\fP is called and \*M_boundary\fP is not set,\^ that is,\^ \*M_boundary\fP has a value of zero,\^ \*Mboundary\fP is set to value of the \*Mfp\fP of the calling procedure. .PP When a C routine returns to Icon,\^ \*M_boundary\fP must be cleared. If \*M_boundary\fP has the same value as \*Mfp\fP,\^ the routine was called from Icon and thus when it returns,\^ it returns to Icon. At appropriate return points in routines,\^ a call to \*Mclrbound\fP is made. If the return takes control back to Icon,\^ \*M_boundary\fP is cleared. .SH \*Msetbound\fP on the VAX .PP The value of \*M_boundary\fP is tested. If \*Mboundary\fP is not set,\^ that is,\^ if it has a value of zero,\^ it is set to the value of the \*Mfp\fP in the calling procedure. Because the call to \*Msetbound\fP creates a procedure frame,\^ the \*Mfp\fP value saved in the frame is used. If \*M_boundary\fP is already set,\^ it is not changed. .SH \*Mclrbound\fP on the VAX .PP The value of \*M_boundary\fP is compared to the \*Mfp\fP in the calling procedure. If the values are the same,\^ the calling procedure was called from Icon and when it returns it returns to Icon. If this is the case,\^ \*M_boundary\fP is cleared. .SH An Alternative Approach .PP If the C system on the target machine uses calls at the beginning and end of C routines to save and restore registers,\^ it is possible to modify these routines to set and clear the boundary. This approach is used on the PDP-11. .PP The file \*Mrt/csv.s\fP contains replacement routines for \*Mcsv\fP and \*Mcret\fP. When \*Mcsv\fP is called to save registers,\^ if \*M_boundary\fP is 0,\^ \*Msp\fP is saved in \*M_boundary\fP. This insures that the first call from Icon into C leaves \*M_boundary\fP at the top of the stack at that point. When \*Mcret\fP is called to restore registers,\^ if the procedure frame pointer is equal to \*M_boundary\fP,\^ the return is taking control back to Icon code and \*M_boundary\fP is cleared. .PP Note the resemblance between calling \*Msetbound\fP at the start of a C routine and having \*Mcsv\fP set \*M_boundary\fP whenever a C routine is entered. Similarly,\^ there is a resemblance between calling \*Mclrbound\fP at appropriate points and having \*Mcret\fP clear \*M_boundary\fP when necessary. .PP The initial implementation of Icon was on the PDP-11 and the modified \*Mcsv\fP and \*Mcret\fP approach was used. When the system was ported to the VAX,\^ the subsumption of \*Mcsv\fP and \*Mcret\fP by the hardware required that a software approach be taken. The trade-offs are marginal if the target machine uses save and restore routines. Having a distinct \*Msetbound\fP and \*Mclrbound\fP may be easier to implement,\^ but using modified save and restore routines is definitely faster. .PP If this approach is used,\^ then \*MSetBound\fP and \*MClearBound\fP in \*Mrt.h\fP should be defined,\^ but given no value. .NH 3 \*Mlib/invoke.s\fR .SH Overview .LP \*Minvoke.s\fP handles four specific tasks. These are .Ds .ft R call a built-in function call an Icon procedure create a record perform mutual evaluation .De .LP Note that all of these operations rise from a source code expression of the form .Ds \*(e0(\*(e1,\^\*(El,\^\*(en) .De where each \*(ai is an expression of some type. Icon has strict left-to-right evaluation and thus,\^ for the preceding expression,\^ \*(e0 is evaluated first,\^ then \*(e1,\^ and so forth through \*(en. As each expression is evaluated,\^ its result is pushed on the stack. After the expressions have been evaluated (and assuming none failed),\^ the stack looks something like .Ds .ft R .S1 value from \*(e0 value from \*(e1 \*(El value from \*(ei \*(El \*Msp\fR \*(ar value from \*(en .De Then,\^ \*(e0 is \fIinvoked\fP. .PP Recall that stacks are represented as growing downward. Thus,\^ \*(en is on the ``top'' of the stack. Also note that each ``value'' on the stack is actually a two-word descriptor. .\"The various \*(ei may be referred to as \fIarguments\fP. .SH Generic Operation .PP \*Minvoke\fP is an interpreter opcode that takes a single operand specifying how many \*(ei are present (\*(e0 is not counted). \*Minvoke\fP is also called from \*Mstart.s\fP to invoke the \*Mmain\fP procedure. .Ls .Np \*Minvoke\fP must determine whether it is to call a built-in function,\^ call an Icon procedure,\^ create a record,\^ or perform mutual evaluation. If \*(e0 is not something that can be invoked,\^ \*Minvoke\fP notes this as a run-time error. .Np If mutual evaluation is to be done,\^ \*Minvoke\fP selects the value of \*(ei that corresponds to the value of \*(e0. That is,\^ if \*(e0 is 2,\^ then \*(e2 is selected and returned. If \*(e0 references a value that is out of range,\^ \*Minvoke\fP fails. .Np If an Icon procedure or built-in procedure is being called,\^ the argument list is adjusted to conform to the number of arguments that the procedure or function is expecting. This may mean supplying \*M&null\fP for missing values,\^ or discarding the last few \*(ei. Some built-in functions take a variable number of arguments,\^ but all Icon procedures take a fixed number of arguments. If the desired operation is creation of a record,\^ \*Minvoke\fP treats this just like invocation of a built-in procedure. .Le .LP Built-in functions and Icon procedures are handled in very different ways. If a built-in function is being invoked,\^ it is simply called. (The calling sequence is somewhat convoluted on the VAX and is described later.) Calling an Icon procedure is more involved; the following actions are taken. .Ls .Np Each \*(ei in the (adjusted) argument list is dereferenced. .Np If \*M&trace\fP has a non-zero value,\^ the function \*Mctrace\fP is called with appropriate arguments. \*Mctrace\fP produces output that includes the name of the procedure being called and the arguments that are being passed to it. .Np The remainder of the procedure frame (partially constructed by the call itself) is built. This includes pushing values for \*M_file\fP and \*M_line\fP on the stack. \*M_file\fP is a pointer to a string that names the source file from which the code currently being executed came. \*M_line\fP is the number of the source line that is currently being executed. A descriptor for \*M&null\fP is pushed on the stack for each dynamic local of the procedure. .Np The generator frame pointer is cleared (because a new expression context is being entered). \*Mipc\fP is loaded with the entry point of the procedure being called. Control is then passed back to the interpreter using a jump instruction. .Le .SH \*Minvoke\fP on the VAX .PP On the VAX,\^ immediately after \*Minvoke\fP has been entered,\^ the stack is .Ds .ft R .St value from \*(e0 value from \*(e1 \*(El 8 value from \*(en 4 number of expressions \- 1 (\*Mnargs\fR) \*Map\fR \*(ar 0 number of words in argument list (\*Mnwords\fR) -4 saved \*Mr11\fR -8 saved \*Mr10\fR \*(El saved \*Mr1\fR saved \*Mpc\fR 12 saved \*Mfp\fR 8 saved \*Map\fR 4 program status word and register mask \*Msp\fR,\^ \*Mfp\fR \*(ar 0 0 (condition handler address) .De .PP The first action of \*Minvoke\fP is to set \*M_boundary\fP to the value of \*Mfp\fP. This is done because \*Minvoke\fP may be invoking a built-in procedure. .PP The number of arguments with which \*(e0 is to be invoked is contained in the \fInargs\fP word,\^ which resides at \*M4(ap)\fP. This value is frequently used and is put into \*Mr8\fP. .PP \*Minvoke\fP makes frequent use of \*(e0,\^ but its address is not a fixed distance from any known point. Rather,\^ the address of \*(e0 must be calculated using the address of \*(en and the number of arguments. The VAX \*Mmovaq\fP instruction makes this calculation easy. The desired calculation is .Ds r11 = 8 + ap + (r8 * 8) .De and is performed by .Ds movaq 8(ap)\^[r8],\^r11 .De \*(e0 may be a variable and if so,\^ it needs to be dereferenced. \*Mr11\fP,\^ which contains the address of \*(e0 is pushed on the stack and \*Mderef\fP is called. The dereferencing is done ``in place''; the previous value of \*(e0 is replaced with the dereferenced value. The dereferenced value is a descriptor whose first word contains type information and whose second word (in some cases) contains the address of a data block which holds the actual value of the object. Note that \*Mr11\fP points to the first word of this descriptor. .PP Recall that the first task of \*Minvoke\fP is to determine what \*(e0 is and to act accordingly. The simplest case is when \*(e0 is a procedure. That is checked for by comparing \*M0(r11)\fP with \*MD_PROC\fP. If \*(e0 is a procedure,\^ a forward jump is made to \*Mdoinvk\fP. .PP It is more interesting if \*(e0 is not a procedure. The first alternative investigated is mutual evaluation. mutual evaluation is similar to a procedure call,\^ but rather than \*(e0 being a procedure,\^ it is an integer that selects one of the \*(ei. The selected \*(ei is the outcome of the mutual evaluation. The routine \*Mcvint\fP is used to try to convert \*(e0 to an integer. If \*(e0 cannot be converted to an integer,\^ a forward branch is taken to \*Mtrystr\fP to explore another possibility. For mutual evaluation,\^ a non-positive value of \*(e0 is acceptable and is converted to a positive value using the \*Mcvpos\fP routine. (Expressions in the argument list are indexed the same way that characters in a string are indexed.) If the position is greater than the number of expressions in the list,\^ that is,\^ if the reference is out of range,\^ the mutual evaluation fails by calling the routine \*Mfail\fP. If the position is in range,\^ the selected \*(ei must be returned as the result of the invocation (and the result of the mutual evaluation). The \*(ei to return is selected by multiplying the position by 8 (each \*(ei is a descriptor) and subtracting that from \*Mr11\fP,\^ which points at \*(e0. The descriptor thus referenced is copied into the location of \*(e0. (Recall that \*(e0 is used to receive the result of an operation.) \*M_boundary\fP is then cleared and \*Minvoke\fP returns. .PP If \*(e0 is not convertible to an integer,\^ conversion to a procedure is attempted. (Note that this is an experimental extension to Icon.) \*(e0 is first converted to a string using \*Mcvstr\fP. If the conversion is successful,\^ the routine \*Mstrprc\fP is called to see if the string ``names'' a procedure. The conversion performed by \*Mstrprc\fP is ``in place'',\^ i.e.,\^ \*(e0 becomes a descriptor for a procedure. If either the conversion in \*Mcvstr\fP or \*Mstrprc\fP fails,\^ \*(e0 is deemed to be uninvocable and this is noted by run-time Error 106. .PP At this point (the label \*Mdoinvk\fP),\^ \*(e0 is a descriptor to a procedure to be invoked and \*Mr11\fP points to \*(e0. .PP The next operation is to make the number of arguments supplied conform to the number of arguments that the procedure is expecting. The number of arguments that a procedure expects is in the fifth word of its procedure block. This value for the procedure being invoked is obtained and placed in \*Mr10\fP. If the value is negative,\^ the number of arguments that the procedure expects is variable. Only built-in procedures can have a variable number of arguments,\^ so if the desired argument count is negative,\^ control passes to the label \*Mbuiltin\fP. .PP \*Mr8\fP contains the number of arguments given and \*Mr10\fP contains the number of arguments desired. The value of \*Mr10\fP is subtracted from \*Mr8\fP,\^ leaving \*Mr8\fP with the difference. If the number of arguments supplied is the same as the number expected,\^ no adjustment is needed and a forward jump is made to \*Mdoderef\fP. Otherwise,\^ the stack must be adjusted. .PP First,\^ \fInwords\fR and \fInargs\fR on the stack are adjusted. Recall that \fInargs\fR is used by Icon and is the number of arguments for a procedure. \fInwords\fR is used by the VAX hardware and is the number of words that the argument list for a subroutine occupies. \fInargs\fR resides at \*M4(ap)\fP and is updated by storing \*Mr10\fP in \*M4(ap)\fP. \fInwords\fR is trickier because only the low-order byte of the \fInwords\fR word is to be used for the word count. The low-order byte of \*Mr10\fP is stored in \*M0(ap)\fP,\^ doubled,\^ and then incremented by one. (The increment by one is to allow for the \fInargs\fR word that is part of the argument list.) .PP The deletion of excess arguments or addition of \*M&null\fP for missing arguments is accomplished by moving the lower portion of the stack up or down to overwrite excess arguments or to leave space for missing arguments. Consider the following: A procedure that expects one argument has been invoked with three arguments. The stack is .Ds .ft R .St 128 \*(e0 120 \*(e1 112 \*(e2 104 \*(e3 100 \*Mnargs\fR (3 at call,\^ 1 after adjustment) \*Map\fR \*(ar 96 \*Mnwords\fR (7 at call,\^ 3 after adjustment) 92 \*Mr11\fR \*(El 48 \*Mr1\fR 44 \*Mpc\fR 40 \*Mfp\fR 36 \*Map\fR 32 \*Mpsw\fR \*Msp\fR,\^ \*Mfp\fR \*(ar 28 0 .De The situation desired is .Ds .ft R 128 \*(e0 120 \*(e1 116 \*Mnargs\fR (1) \*Map\fR \*(ar 112 \*Mnwords\fR (3) 108 \*Mr11\fR \*(El 64 \*Mr1\fR 60 \*Mpc\fR 56 \*Mfp\fR 52 \*Map\fR 48 \*Mpsw\fR \*Msp\fR,\^ \*Mfp\fR \*(ar 44 0 .De Note the address field (which has been arbitrarily assigned). Observe that \*(e0 and \*(e1 are in the same place,\^ but that the lower portion of the stack has moved up. Consider what would be desired if the procedure being invoked requires five arguments and only three are supplied. The desired stack configuration is .Ds .ft R .St 128 \*(e0 120 \*(e1 112 \*(e2 104 \*(e3 96 \*M&null\fP (\*(e4) 88 \*M&null\fP (\*(e5) 84 \*Mnargs\fR (5) \*Map\fR \*(ar 80 \*Mnwords\fR (11) 76 \*Mr11\fR \*(El 32 \*Mr1\fR 28 \*Mpc\fR 24 \*Mfp\fR 20 \*Map\fR 16 \*Mpsw\fR \*Msp\fR,\^ \*Mfp\fR \*(ar 12 0 .De As before,\^ the ``good'' arguments are in the same place,\^ but the stack has moved down to make room for \*(e4 and \*(e5 and a value of \*M&null\fP is supplied for them. .PP The VAX hardware makes these stack manipulations easy. Recall that \*Mr8\fP contains .Ds .ft R number of arguments expected \- number of arguments supplied .De Thus,\^ in the first case \*Mr8\fP has a value of 2,\^ while in the second case \*Mr8\fP is \-2. The value of \*Mr8\fP is multiplied by 8 to turn the argument count into a byte count. This byte count is added to \*Msp\fP,\^ effectively moving it to where it should be. Now,\^ a block of memory starting at where \*Mfp\fP points must be moved to where \*Msp\fP points. The size of the block needs to be considered. It starts at the fp and contains the condition handler,\^ the old psw,\^ \*Map\fP,\^ \*Mfp\fP and \*Mpc\fP \(em five words. Eleven saved registers are included; eleven more words. (A constant,\^ \*MINVREGS\fP,\^ is used to represent the number of saved registers.) Finally,\^ two words for \fInargs\fR and \fInwords\fR \(em a total of 18 words. The VAX instruction that makes the move is .Ds movc3 $(INVREGS+7)*4,\^(fp),\^(sp) .De which moves 18*4 (=72) bytes from where \*Mfp\fP is pointing to where \*Msp\fP pointing. The VAX allows the source and destination fields to overlap without producing ``curious'' results. .PP Some housekeeping must be performed after the move is made. \*Mfp\fP is set to point to the same word \*Msp\fP points to,\^ and the boundary is set to point to the same place. \*Map\fP is adjusted to point to the \fInwords\fR word. .PP If arguments were deleted,\^ the adjustment process is done. If arguments were added,\^ \*M&null\fP must be supplied as the value for each argument that was added. Because the descriptor for \*M&null\fP consists of two words containing 0,\^ the task amounts to filling in null bytes in the ``hole'' that was made. \*Mr8\fP contains the number of bytes in the ``hole''. \*Mr8\fP is negative and it is negated to obtain a positive byte count. The instruction .Ds movc5 $0,\^(r0),\^$0,\^r8,\^(INVREGS+7)*4(sp) .De does all the work. Specifically,\^ this instruction moves bytes of zeroes starting at \*M(r0)\fP to a field that starts at \*M72(sp)\fP and extends for \*Mr8\fP bytes. The third operand of the instruction is a ``fill character'' that is used in the event that the destination field is longer than the source field. In this case,\^ the source field is 0 bytes long,\^ and a null-byte fill character is moved into each byte of the destination field. This is the usual way to zero out a block of memory on the VAX. .PP At this point,\^ the correct number of arguments for the procedure are on the stack. .PP For an Icon procedure,\^ all arguments must be dereferenced before the procedure is called. Procedure blocks have a field that tells how many dynamic local variables the procedure has. For built-in procedures this field has a value of \-1. If a built-in procedure is to be invoked,\^ control jumps to the label \*Mbuiltin\fP. If an Icon procedure is to be invoked,\^ but it has no arguments (and thus they do not need to be dereferenced),\^ a forward jump is made to \*Mcktrace\fP. .PP \*Mr11\fP points to the descriptor for \*(e0. The address of \*(e1 is used later in \*Minvoke\fP,\^ and its address is calculated using \*Mr11\fP because it's handy. \*Mr10\fP contains the number of arguments and this value is stored in \*Mr5\fP for subsequent use of \*Mr5\fP as a counter. The arguments must be dereferenced,\^ this is done by calling \*Mderef\fP with the address of each argument. The instruction .Ds pushaq -(r11) .De is used to decrement \*Mr11\fP by 8 and then push the value of \*Mr11\fP on the stack. Because \*Mr11\fP initially references \*(e0,\^ the first time through \*Mr11\fP is decremented to point at \*(e1 and \*Mderef\fP is called with the address of \*(e1. \*Mr11\fP is backed down through the expression list,\^ with each \*(ei being dereferenced in turn. Note that the \*Msobgeq\fP instruction is used as a loop controller,\^ decrementing \*Mr5\fP and jumping back to \*Mnxtarg\fP as long as \*Mr5\fP is not 0. .PP At this point,\^ an Icon procedure is being invoked; it has the correct number of arguments and they have been dereferenced. .PP If tracing is on,\^ (indicated by a non-zero value for \*M_k_trace\fP),\^ a trace message must be produced at this point. The routine \*Mctrace\fP does all the work. It needs to be called with the appropriate arguments. \*Mctrace\fP requires three arguments: procedure block address,\^ number of arguments,\^ and the address of the first argument. These are pushed on the stack and \*Mctrace\fP is called. .PP The portion of the stack from \*(e0 on down constitutes a partial procedure frame and it must be completed. \*M_line\fP and \*M_file\fP are pushed on the stack. To complete the frame,\^ the local variables must be pushed on the stack. Local variables have an initial value of \*M&null\fP,\^ and the \*Mmovc5\fP idiom used previously is used again to zero out the space required for the local variables. .PP Because an Icon procedure is being invoked,\^ the boundary is cleared and \*M_k_level\fP (the \*M&level\fP keyword) is incremented. The entry point for a procedure is stored in the third word of the procedure block. This value is loaded into \*Mipc\fP. A new expression context is being entered,\^ and both \*Mgfp\fP and \*Mefp\fP are cleared using a \*Mclrq\fP instruction. .PP Control is passed back to the main loop of the interpreter by jumping to \*Minterp\fP. The Icon procedure is now being executed. The procedure eventually terminates by use of \*Mpret\fP or \*Mpfail\fP. .LP The section of code following \*Mbuiltin:\fP handles the case where a built-in procedure is to be invoked. .PP If nothing is changed,\^ when a built-in function returns,\^ it would return to the instruction after the call to \*Minvoke\fP. This is unsatisfactory,\^ since the boundary needs to be cleared because of the transition from the C environment to the Icon environment. Rather than having a call to \*Mclrbound\fP at the end of each built-in procedure,\^ the boundary is cleared at a common return point. .PP The \*Mpc\fP value that is stored at \*M16(fp)\fP is ``hidden'' at \*M20(fp)\fP where the value of \*Mr1\fP should be saved. \*M16(fp)\fP is replaced with the address of the forward label \*Mbprtn\fP. Thus,\^ when the built-in procedure returns,\^ it goes right to \*Mbprtn\fP. Because a C environment is being entered,\^ the boundary is set to the current value of \*Mfp\fP. The third word of the procedure block contains the entry point of the built-in procedure and it is jumped to. It is important to understand that the procedure is not called because the call frame has already been constructed. Also,\^ the entry point address stored in the procedure block \fImust\fR be past any instructions that are used to establish the stack environment for the routine because this environment should already be on the stack. .PP When the built-in procedure returns,\^ it comes to \*Mbprtn\fP. The boundary is cleared because execution is back in an Icon environment. The procedure return restores \*Mr1\fP,\^ which contains the \*Mpc\fP value that \*Minvoke\fP should return to. Because the return has already swept off the old frame,\^ \*M0(r1)\fP is jumped to and \*Minvoke\fP is finished. Note that the built-in procedure has left its result in \*(e0,\^ which is left on the stack. Thus,\^ the direct result of \*Minvoke\fP is an additional value on the stack. .SH Some Comments on \*Minvoke\fR .PP It is important to understand the purpose of \*Minvoke\fP. Unless mutual evaluation is being performed,\^ the task of \*Minvoke\fP is to create a procedure frame for an Icon or built-in procedure and then transfer control to the procedure. .PP On the VAX and the PDP-11,\^ the call to \*Minvoke\fP partially creates the procedure frame. \*Minvoke\fP then completes the frame. For Icon procedures,\^ control eventually passes out of \*Minvoke\fP and goes back to the interpreter loop. However,\^ for built-in procedures,\^ after the frame is built,\^ \*Minvoke\fP \fIbranches into\fP the C code for the procedure. Thus,\^ it appears that the built-in procedure was called directly. This scheme is facilitated by the fact that entry points of C routines on the VAX and PDP-11 are a constant distance from the start of the routine. (The \*MEntryPoint\fP macro in \*Mrt.h\fP specifies the distance.) On some machines this may not be practical and other schemes may need to be developed. .PP Another point that needs to be addressed is that of argument removal. When a built-in procedure,\^ Icon procedure,\^ or built-in operation is performed,\^ the net result almost always is simply the addition of a descriptor to the stack. More precisely,\^ the new descriptor is actually a replacement for \*(a0,\^ which is the descriptor for the procedure being called,\^ or in the case of a built-in operation is \*M&null\fP. Recall that arguments are below \*(a0 on the stack. The \*(a0 word must be on top of the stack when a procedure or operation is complete. The VAX does this via hardware,\^ which manages the stack and removes arguments when a procedure call is complete. The PDP-11 does not have such hardware facilities and thus the arguments must be removed manually. The problem is compounded by the fact that for built-in operations,\^ \*Minvoke\fP is never called; rather,\^ the interpreter loop calls the appropriate subroutine directly. .PP The method employed on the PDP-11 uses the \*Mcret\fP routine to remove arguments. \*Mcret\fP is called at the end of each C routine to restore registers. Icon has a replacement for \*Mcret\fP that restores registers but also removes arguments when appropriate. \*Mrt/csv.s\fP contains the replacement routine. When \*Mcret\fP is called,\^ if \*Mr5\fP (the \*Mpfp\fP on the PDP-11) is equal to \*M_boundary\fP,\^ then \*Mcret\fP is returning to Icon code and any arguments on the stack are removed,\^ leaving \*(a0 on the top of the stack. A similar technique may be needed on the target machine. .PP \*Mrt/csv.s\fP also contains a replacement for the \*Mcsv\fP routine which saves registers upon entry to C functions on the PDP-11. Both \*Mcsv\fP and \*Mcret\fP also contain code that is used to set and clear the boundary at appropriate times. See the description of \*Mrt/setbound.s\fP for more details. .NH 3 \*Miconx/interp.s\fR .SH Overview .PP \*Minterp.s\fP is the main loop for the interpreter. The execution of an Icon program is stack based. As the interpreter executes an Icon program,\^ it fetches instructions and accompanying operands out of the instruction stream of the interpretable file. Operands for interpreter instructions are pushed on the stack and results accumulate on the stack as operands for other instructions. In addition to simple incremental and decremental stack changes,\^ the expression evaluation mechanism may cause portions of the stack to be duplicated and may also cause the top portion of the stack to be removed. .SH Generic Operation .PP An Icon program is executed by interpreting the interpretable file produced by the linker. The interpretation process itself is fairly simple. \*Mipc\fP points at the next instruction to be executed. (Recall that the interpretable file is loaded into memory.) The opcode of the instruction is fetched and the corresponding word in the jump table is taken as the address of a sequence of instructions that perform the desired operations. A branch is taken to the referenced location and the operation is performed. The operation may require operands; if so,\^ they appear in the instruction stream following the opcode. The segment of code that performs a particular operation is responsible for fetching the appropriate operands out of the stream. When the operation is complete,\^ a jump is taken to the top of the interpreter loop and the process continues. .PP Interpreter operations are of two types. Operations of the first type call a routine to perform a task. Operations of the second type are executed entirely by the interpreter; no subroutine call is necessary. .PP Operations that require a call to be made call routines in the \*Moperators\fP or \*Mlib\fP directories. The routine being called may require one or more arguments. If arguments are required,\^ they appear on the stack. When the routine returns,\^ it removes any arguments that it was called with from the stack and leaves its result on the top of the stack. .PP To facilitate the calling of routines,\^ a table known as \*Moptab\fP parallels the jump table. An opcode \fIn\fP references the \fIn\fPth word of the jump table. If the operation designated by the opcode requires a call,\^ the \fIn\fPth word of \*Moptab\fP contains the address of the routine that should be called. .PP The interpreter saves space in its instruction stream by encoding operand information in some opcodes. For example,\^ the \*Mline\fP instruction has one operand that is used to set the value of \*M_line\fP,\^ the current source line number. The \*Mlinex\fP instruction is an alternate form of \*Mline\fP which encodes the line number as the low order bits of the opcode. Specifically,\^ the opcodes from 192 to 256 are \*Mlinex\fP opcodes. For example,\^ opcode 195 is equivalent to a \*Mline\fP opcode with an operand of 3. .SH Implementing the Interpreter Loop .PP \*Minterp.s\fP stands alone among the assembly language files as one that is well suited to coding in a macro fashion. Most of the interpreter loop is written in terms of \fIcpp\fP macros and thus porting it is largely a matter of writing the macros for the target machine. .LP The following \*M#define\fPs must be made. .Ip \*MOp\ \ \ \ \ \fP .br The operand register. Any general purpose register will do. The value of the register need not preserved between instructions; its lifetime is only from the time that an operand is fetched until the next opcode is fetched or a routine is called. .Ip \*MGetOp\fP This must expand into code that fetches the next operand out of the instruction stream and places it in the register \*MOp\fP. Recall that operand size is determined by the \*M#define\fP for \*MOPNDSIZE\fP in the linker. On the VAX,\^ \*MGetOp\fP is merely .Ds .ta 0.6i +0.6i +0.6i +0.6i movl (ipc)+,\^Op .De This is because operands are one word long and can begin on any byte boundary. If the VAX did not support word fetching from arbitrary boundaries,\^ it would be necessary to get the bytes from the instruction stream one at a time and make a word out of them using boolean operations. If such were the case,\^ a reasonable alternative would be to make opcodes one word in size and thus all instruction stream objects (opcodes,\^ operands,\^ and words),\^ would be of the same size and lie on word boundaries. .\".IP \*MGetWord\fP .\"\*MGetWord\fP is similar to \*MGetOp\fP,\^ but rather than loading the .\"next operand,\^ it loads the next \fIword\fP from the instruction stream into .\"the \*MOp\fP register. Recall that the size of a word is defined by .\"the \*MWORDSIZE\fP in the linker. If words are the same size as .\"operands on the target machine,\^ \*MGetWord\fP should be identical to .\"\*MGetOp\fP. .Ip \*MPushOp\fP Push the \*MOp\fP register on the stack. The VAX uses .ta .6i .Ds pushl Op .De .Ip \*MPushNull\fP Push a descriptor for \*M&null\fP on the stack. That is,\^ push two words of zeroes. The VAX \*Mclrq\fP instruction does the trick: .Ds clrq -(sp) .De .Ip \*MPush_R(x)\fP,\^\ \*MPush_S(x)\fP,\^\ \*MPush_K(x)\fP Push the value of \*Mx\fP on the stack. To accommodate machines with non-orthogonal instruction sets,\^ \*MPush_R\fP is used to push a register value,\^ and \*MPush_S\fP is used to push the contents of a storage location. \*MPush_K\fP is used to push a constant value. The VAX uses .Ds pushl x .De for both \*MPush_R(x)\fP and \*MPush_S(x)\fP,\^ while .Ds pushl $x .De is used for \*MPush_K(x)\fP. .Ip \*MPushOpSum_R(x)\fP,\^\ \*MPushOpSum_S(x)\fP \*MPushOpSum_R(x)\fP adds the value of the register \*Mx\fP to \*MOp\fP and pushes the result on the stack. \*MPushOpSum_S(x)\fP is similar,\^ adding the value in the memory location \*Mx\fP to \*MOp\fP and pushing the result. On the VAX,\^ .Ds addl3 Op,\^x,\^-(sp) .De is used for both. .Ip \*MNextInst\fP Branch to the top of the interpreter loop. The VAX uses .Ds jmp _interp .De .Ip \*MCallN(n)\fP Call the routine corresponding to the current opcode with \*Mn\fP arguments. On the VAX,\^ the opcode fetching segment loads \*Mr0\fP with a byte offset into the jump table. This same byte offset references the location in \*Moptab\fP which contains the address of the routine which corresponds to the current opcode. \*MCallN(n)\fP expands to .Ds pushl $n calls $((n*2)+1),\^*optab(r0) .De \*Mpushl $n\fP pushes the number of arguments on the stack. This word becomes the \fInargs\fP word of the procedure frame. The first of operand of the \*Mcalls\fP instruction is the length of words in the argument list,\^ since each argument is a two word descriptor and the \fInargs\fP word occupies another word,\^ \*Mn*2+1\fP is used as the length of the argument list. The address contained in the \*Moptab\fP word referenced by \*Mr0\fP is the routine to call. .Ip \*MCallNameN(n,\^f)\fP Call the routine named \*Mf\fP with \*Mn\fP arguments. This is very similar to \*MCallN\fP,\^ the only difference being that the routine to call is explicitly named rather than being implicitly determined from the opcode value. The VAX uses .Ds pushl $n calls $((n*2)+1),\^f .De .Ip \*MBitClear(m)\fP The constant value \*Mm\fP designates bits in the \*MOp\fP register to leave on. All other bits in \*MOp\fP should be turned off. That is,\^ the complement of \*Mm\fP is \*MAND\fPed with the contents of \*MOp\fP and the result is placed in \*MOp\fP. This is used to decode opcodes with encoded operands. The VAX uses .Ds bicl2 $0!m,\^Op .De .Ip \*MJump(lab)\fP Branch to the label \*Mlab\fP. The destination label is close to the jump,\^ so a short jump of some type may be used. The VAX uses .Ds jbr lab .De .Ip \*MLongJump(lab)\fP \*MLongJump\fP is like \*MJump\fP with the exception that \*Mlab\fP may be quite distant. The VAX uses .Ds jmp lab .De .Ip \*MLabel(lab)\fP Generate a label declaration for \*Mlab\fP. The VAX uses .Ds lab: .De .SH VAX Specific Sections of \*Minterp\fR .PP Several sections of \*Minterp\fP are machine specific and must be coded on a per-machine basis. The sections in question are explained on an individual basis: .Ip \*M_interp\fP The next opcode is fetched and loaded into \*Mr0\fP. \*Mmovzbl\fP moves a byte and zero extends it to a word value. Because a byte was fetched,\^ \*Mipc\fP is incremented by 1. The opcode is saved in \*MOp\fP in case it contains an encoded operand. \*Mr0\fP is multiplied by 4 to turn it into a byte offset. A jump is made to the address indexed by \*Mr0\fP in \*Mjumptab\fP to perform the desired operation. Eventually,\^ a jump returns control to the label \*M_interp\fP to fetch and execute the next instruction. .Ip \*Mop_bscan\fP A descriptor for \*M_k_subject\fP is pushed on the stack. Then the value of \*M_k_pos\fP is pushed,\^ followed by the constant \*MD_INTEGER\fP. The routine corresponding to \*Mop_bscan\fP,\^ \*M_bscan\fP,\^ is called with 0 arguments. (This causes the descriptors for \*M_k_subject\fP and the value of \*M_k_pos\fP to be left on the stack.) When \*M_bscan\fP returns,\^ a branch is made to \*M_interp\fP. .Ip \*Mop_ccase\fP A null descriptor is pushed on the stack. The word immediately above the current expression frame is then pushed on the stack. .Ip \*Mop_chfail\fP The operand of \*Mchfail\fP is fetched into \*MOp\fP. \*MOp\fP and \*Mipc\fP are added together and the result replaces the failure address in the current expression frame. .Ip \*Mop_dup\fP A null descriptor is pushed on the stack. The value that was on top of the stack is now at \*M8(sp)\fP,\^ and it is copied to the top of the stack using a \*Mmovq\fP. .Ip \*Mop_eret\fP \*Meret\fP gets the value on top of the stack,\^ removes the current expression frame and puts the previous top of stack value back on the top of the stack. First of all,\^ .Ds movq (sp)+,\^r0 .De moves the descriptor on the top of the stack into the \*Mr0\-r1\fP register pair and increments the stack pointer by 8. The \*Mgfp\fP is loaded with the \*Mgfp\fP value stored in the expression frame marker. \*Msp\fP is loaded from \*Mefp\fP,\^ bringing the expression marker to the top of the stack. The old \*Mefp\fP value from the marker is loaded into \*Mefp\fP. Finally,\^ the value stored in the \*Mr0\-r1\fP pair is pushed on the stack. .Ip \*Mop_file\fP The operand of \*Mfile\fP is loaded into \*MOp\fP. \*MOp\fP and the value of \*M_ident\fP are added and the result in placed in \*M_file\fP. .Ip \*Mop_goto\fP The operand is loaded into \*MOp\fP and then added to \*Mipc\fP. .Ip \*Mop_incres\fP The eighth word of the co-expression heap block for the current expression is incremented by one. .Ip \*Mop_init\fP This one is tricky. The \*Minit\fP instruction arises from the \*Minitial\fP statement in Icon and is used to effect one-time execution of a segment of code. The operand of \*Minit\fP is the address of the first instruction after the segment that is to be executed once. The instruction .Ds movb $59,\^-(ipc) .De decrements \*Mipc\fP by 1 and then stores the constant 59 in the byte that \*Mipc\fP references,\^ which is the \*Minit\fP opcode. The magic number 59 is the opcode for \*Mgoto\fP,\^ so in effect,\^ the \*Minit\fP had been made into a goto that skips a section of code. By adding 5 to \*Mipc\fP,\^ it leaves \*Mipc\fP pointing at the first instruction of the \*Minitial\fP code. The constant 5 is derived from the width of the opcode and associated operand,\^ i.e.,\^ \*MOPSIZE+OPNDSIZE\fP. .Ip \*Mop_invoke\fP This section has two entry points: \*Mop_invoke\fP gets control if \*Minvoke\fP has an operand,\^ and \*Mop_invkx\fP gets control if the operand is encoded in the opcode. If an operand is specified,\^ it is fetched into \*MOp\fP. If the operand is encoded,\^ \*MBitClear(7)\fP is used to isolate the operand in \*MOp\fP. Control converges at \*Mdoinvoke\fP. The operand is the number of arguments to invoke the procedure with and it is pushed on the stack as the \fInargs\fP word. (The arguments are already on the stack.) The \*Mcalls\fP instruction needs the length of the argument list,\^ so \*MOp\fP is multiplied by 2 and then incremented by 1. \*Minvoke\fP is called. .Ip \*Mop_int\fP As in \*Mop_invoke\fP,\^ \*Mop_int\fP has a secondary entry point,\^ \*Mop_intx\fP,\^ for operands encoded in the opcode. At the \*Mop_int\fP entry point,\^ a \*MWORDSIZE\fP value is fetched out of the instruction stream and loaded into \*MOp\fP. At the \*Mop_intx\fP entry point,\^ the \*MOp\fP value is decoded from the operand. The \*MOp\fP value is pushed on the stack and is followed by a \*MD_INTEGER\fP word,\^ forming an integer descriptor. .Ip \*Mop_line\fP Like \*Mop_invoke\fP and \*Mop_int\fP,\^ \*Mop_line\fP has a secondary entry point. The operand value is obtained and then moved into \*M_line\fP. .Ip \*Mop_llist\fP \*Mllist\fP is similar to \*Minvoke\fP in that it has a number of arguments already on the stack and that the operand specifies the number. The operand is fetched into \*MOp\fP and pushed on the stack to become the \fInargs\fP argument of \*Mllist\fP. \*MOp\fP is then multiplied by 2 and incremented by 1 to serve as an argument list size for \*Mcalls\fP. .Ip \*Mop_mark\fP The operand is fetched into \*MOp\fP and \*Mipc\fP is added to it. \*Mefp\fP is pushed on the stack and the new \*Msp\fP value is put in \*Mefp\fP. \*Mgfp\fP is pushed on the stack and cleared. \*MOp\fP is pushed on the stack. .Ip \*Mop_mark0\fP Like \*Mop_mark\fP,\^ with an implicit operand value of zero. .Ip \*Mop_pop\fP The two \*Mtstl\fP instructions serve to add 8 to \*Msp\fP which removes the top value from the stack. .Ip \*Mop_sdup\fP The descriptor on the top of the stack is pushed on the stack,\^ duplicating it. .Ip \*Mop_unmark\fP The operand,\^ the number of expression frames to remove from the stack,\^ is fetched into \*MOp\fP. \*Mefp\fP is restored from the current expression frame. The instruction .Ds sobgtr Op,\^unmkjmp .De decrements \*MOp\fP and then branches to \*Mdounmark\fP if \*MOp\fP is not zero. This chains through the number of expression frames specified by the operand. \*Mgfp\fP is restored from the current expression marker. \*Mefp\fP is loaded into \*Msp\fP to move the expression marker to the top of the stack. Finally,\^ \*Mefp\fP is restored from the marker and \*Msp\fP is incremented to remove the last word of the marker. .Ip \*Mop_unmk1-7\fP Similar to \*Munmark\fP,\^ but uses successive restorations of \*Mefp\fP rather than a loop. .Ip \*Mop_global\fP Dual entry points are used to deal with possible operand encoding. The operand,\^ which is a number of a variable in the global region,\^ is multiplied by 8 to provide a byte offset from the start of the global region. The sum of \*MOp\fP and the value of \*Mglobals\fP is pushed on the stack to provide a descriptor address. The constant \*MD_VAR\fP is pushed on the stack to complete the descriptor for the global variable. .Ip \*Mop_static\fP Identical to \*Mop_global\fP except that \*Mstatics\fP is used instead of \*Mglobals\fP. .Ip \*Mop_local\fP The operand value is the number of a local variable for which a variable descriptor is to be pushed on the stack. Recall that the local variables lie below the procedure frame and,\^ on the VAX,\^ the descriptor for the first one is at \*M\-16(fp)\fP. \*MOp\fP is negated. The instruction .Ds pushaq -16(fp)\^[Op] .De performs the calculation .Ds -16 + fp + (Op * 8) .De which computes the address of the descriptor of the desired variable and pushes it on the stack. The variable descriptor is completed by pushing \*MD_VAR\fP on the stack. .Ip \*Mop_arg\fP Like \*Mop_local\fP,\^ but it uses \*M8(ap)\fP as the base for the address calculation and the operand value is not negated. .Ip \*Mquit\ \ \ \ \ \fP Push a 0 on the stack and call the routine \*M_c_exit\fP to terminate execution of the Icon program. .Ip \*Merr\ \ \ \ \ \fP \*Merr\fP should never be encountered during normal execution. Reaching it indicates that an invalid opcode was encountered. It need not do anything more than abort execution. On the VAX,\^ it calls \*Msprintf\fP to create a string containing the invalid opcode and the \*Mipc\fP where it was encountered and then calls \*Msyserr\fP with the string as an argument. .NH 3 \*Mlib/efail.s\fR .SH Overview .PP \*Mefail\fP handles the failure of an expression. When Icon evaluates an expression,\^ it tries to produce a result from it. If at some point in the evaluation of an expression the expression fails,\^ Icon resumes inactive generators in the expression in an attempt to make the expression succeed. \*Mefail\fP is at the heart of this activity. .LP \*Mefail\fP has three distinct outcomes: .Ls .Np Resumption of the newest inactive generator in the current expression frame. .Np Failure of the current expression with execution continuing at the failure address contained in the expression marker. .Np Failure of the current expression with propagation of failure to the enclosing expression frame. This is similar to (2),\^ but occurs when the failure address is 0. After the current expression fails,\^ \*Mefail\fP loops back to its entry point to fail again. .Le .PP \*Mefail\fP is branched to rather than being called. This is because it serves as a ``back-end'' for several failure actions that may occur during the course of execution: .Ls .Np When a built-in procedure fails,\^ it calls the routine \*Mfail\fP,\^ which in turn branches to \*Mefail\fP. .Np When an Icon procedure fails via the \*Mpfail\fP routine,\^ \*Mpfail\fP terminates by branching to \*Mefail\fP. .Np When the \*Mefail\fP opcode is executed by the interpreter,\^ \*Mefail\fP is branched to. .Np The generator frames built by \*Mesusp\fP and \*Mlsusp\fP use \*Mefail\fP as a return address. This is explained in detail later. .Le .SH Generic Operation .PP \*Mefail\fP is essentially a simple routine. There are two separate paths of execution that \*Mefail\fP may take. The first is to resume an inactive generator. The second is to cause failure of the expression in lieu of an inactive generator. .PP If there is an inactive generator in the current expression frame,\^ it must be resumed. If the generator is an Icon procedure and tracing is in on,\^ \*Matrace\fP is called with appropriate arguments. \*M_k_level\fP,\^ \*M_line\fP,\^ and \*M_file\fP are restored from the generator frame. A return is performed and the net result is that the stack is restored to the state that it was in before the suspension that created the generator. .PP If there are no inactive generators that can be resumed,\^ the expression being evaluated must fail. This is done by popping the stack back through the current expression frame and resuming execution at the point indicated by the failure address in the expression marker. This is a two-step process. The first is to pop the frame and the second is to resume execution. When the frame is popped,\^ the expression has failed. The failure address in the expression marker is saved before the frame is popped. If this address is not zero,\^ execution is continued by branching to the address. If the address is zero,\^ the failure is propagated to the enclosing expression by branching to efail. .PP Zero failure addresses are generated by the ucode instruction .Ds mark L0 .De Such instructions are used to avoid a special case during code generation and the only purpose they serve during program execution is to create an expression marker for the corresponding \*Munmark\fP instruction to remove. (\*Mmark\fP and \*Munmark\fP instructions are paired.) Thus,\^ whenever \*Mefail\fP pops an expression whose marker has a zero failure address,\^ \*Mefail\fP causes failure in the enclosing expression. .SH \*Mefail\fP on the VAX .PP The first action is to determine if there is an inactive generator that can be reactivated. If the generator frame pointer is non-zero,\^ it points to the newest inactive generator. Note that whenever a new expression frame is created,\^ the generator frame pointer is zeroed. Thus,\^ if \*Mgfp\fP is non-zero,\^ the generator frame that it points to belongs to a generator in the current expression frame. .PP If an inactive generator is available,\^ it must be reactivated. First,\^ \*M_boundary\fP is restored from the generator frame. The stack is popped back to the generator frame by loading \*Mfp\fP from \*Mgfp\fP. But,\^ before \*Mfp\fP is loaded,\^ its value is saved in \*Mr0\fP. \*Mfp\fP now points at word 0 of the generator frame,\^ but that is a word below the actual stack frame that it should be pointing at,\^ so \*Mfp\fP is incremented by 4 using a \*Mtstl\fP. .LP There are three types of generators that may be encountered by \*Mefail\fP. .Ls .Np An Icon procedure that did a \*Msuspend\fP. In such cases,\^ the routine \*Mpsusp\fP handled the suspension. .Np A built-in procedure that called the C function \*Msuspend()\fP. .Np A generator created by an \*Mesusp\fP or \*Mlsusp\fP instruction. Such generators arise from source code constructs like \*M\*(x1 |\ \^\*(x2\fP,\^ \*M|\*(xx\fR,\^ and \*M\*(x1 \e\ \^\*(x2\fR,\^ which are referred to as \fIcontrol regimes\fP. .Le .PP The generators may be treated the same way as far as resumption goes. However,\^ if an Icon procedure is being resumed,\^ a tracing message must be generated if \*M_k_trace\fP is not 0. .PP If the value of \*M_boundary\fP is not the same as \*Mfp\fP,\^ the generator is a built-in procedure and tracing is not done. If the \*Mfp\fP saved in the current frame is the same as the \*Mfp\fP was upon entry to \*Mefail\fP (the value was saved in \*Mr0\fP),\^ the generator was made by an \*Mesusp\fP or an \*Mlsusp\fP and tracing is not done. .PP Otherwise,\^ the generator is an Icon procedure,\^ and \*Matrace\fP must be called. \*Matrace\fP takes one argument,\^ the address of the procedure block for the procedure being resumed. Recall that \*(e0 on the stack is a descriptor for the procedure block. The address of \*(e0 is calculated using .Ds &\*(e0 = ap + 8 + (8 * nargs) .De The resulting address is used as the single argument for \*Matrace\fP. .PP The generator is now ready to be resumed. \*M_k_level\fP,\^ \*M_line\fP,\^ and \*M_file\fP are restored by popping them from the generator frame. If the generator is a built-in procedure,\^ \*M_boundary\fP is cleared. A return is performed to activate the generator. .PP The return has different effects depending on the type of generator being resumed. .PP If the generator is a built-in procedure,\^ the return restores the stack to the state it was in before \*Msuspend\fP was called,\^ and execution proceeds at the point just after \*Msuspend()\fP. In this case the \*Mpc\fP value being returned to references an instruction in the built-in procedure. .PP If the generator is an Icon procedure,\^ the stack is restored to the state it was in before the \*Mpsusp\fP ucode instruction was executed. The \*Mpc\fP value being returned to references an instruction in the interpreter loop. Execution of the program continues with the interpreter instruction following the \*Mpsusp\fP. .PP If the generator is a control regime,\^ the stack is restored to the state it was in before the \*Mesusp\fP or \*Mlsusp\fP that created the generator was performed. The return \*Mpc\fP points to \*Mefail\fP itself. Thus,\^ when the return is done,\^ the stack is cleared,\^ and an \*Mefail\fP is performed. This has the effect of transferring control to the failure label in the expression marker of the bounding expression frame. .\"(The failure label lies at the start of code for the alternative.) .PP If there is no generator to reactivate,\^ the expression must fail. This is handled at the label \*Mnogen\fP. \*Mefp\fP points to the expression frame marker. \*Mipc\fP is loaded from \*M\-8(efp)\fP which contains the address to go to in the event that the current expression fails. (As it has.) \*Mgfp\fP is restored from the expression marker. \*Mefp\fP is restored from the marker and the marker is popped off the stack. .PP If the failure address in \*Mipc\fP is non-zero,\^ control is passed back to the interpreter via a branch and execution of the ucode resumes at the failure address. If \*Mipc\fP is zero,\^ the expression failure is transmitted to the surrounding expression frame by a branch to \*Mefail\fP. (Recall that a zero failure address comes from a \*Mmark L0\fP instruction and that a failure that reaches a \*Mmark L0\fP marker must be propagated to the next expression marker.) .NH 3 \*Mlib/pfail.s\fR .SH Overview .LP \*Mpfail\fP handles the failure of an Icon procedure. An Icon procedure can fail by .Ds .ft R executing a \*Mfail\fP expression executing \*Mreturn \fIexpr\fR when \fIexpr\fR fails allowing the flow of control to reach the end of a procedure .De \*Mpfail\fP is entered via a branch when the interpreter encounters the \*Mpfail\fP instruction. .SH Generic Operation .PP The task of \*Mpfail\fP is to signal failure in the expression that contains the procedure call being evaluated. This is done by removing the Icon procedure frame from the stack,\^ restoring appropriate registers and values,\^ and calling \*Mefail\fP. The key is that all \*Mpfail\fP needs to do is to remove the procedure frame from the stack and from then on things can be handled just like expression failure. Thus,\^ \*Mefail\fP does most of the work. .PP \*Mpfail\fP calls \*Mftrace\fP to produce a trace message if tracing is on. \*Mpfail\fP also decrements \*M_k_level\fP because a procedure is being exited. .PP Note that the procedure frame on the stack is a frame that was created by \*Minvoke\fP. .SH \*Mpfail\fP on the VAX .PP After \*M_k_level\fP is decremented,\^ \*M_k_trace\fP is checked to see if a trace message should be produced. If tracing is on,\^ \*Mftrace\fP must be called. \*Mftrace\fP takes one argument,\^ the address of the procedure block for the failing procedure. \*(e0 is the descriptor for the procedure block,\^ and the address of \*(e0 is calculated using .Ds &\*(e0 = (\fInargs\fP * 8) + 8 + ap .De The resulting address is pushed on the stack and \*Mftrace\fP is called. .PP Execution continues at \*Mdofail\fP to remove the procedure frame from the stack. The frame cannot be merely popped because it contains state information that must be restored. \*M_line\fP and \*M_file\fP are extracted from the frame. \*Mefp\fP,\^ \*Mgfp\fP,\^ and \*Mipc\fP are restored from the frame using addresses relative to \*Map\fP. Note that this works because \*Minvoke\fP saves these registers and their location is known. .PP \*Map\fP and \*Mfp\fP are restored from the frame. When \*Mfp\fP is restored,\^ it serves to remove the procedure frame (made by \*Minvoke\fP) from the stack. At this point,\^ the stack is the same state it was in before the interpreter performed the \*Minvoke\fP instruction. A branch is made to \*Mefail\fP to cause failure in the enclosing expression. .NH 2 Testing the Basis .PP At this point,\^ enough of the system has been written to run some very simple Icon programs. \*Mtest/hello.icn\fP should be functional and more complete testing is in order. Refer to \fITesting the Basis\fP in \^[5].