REF VMCODE John Gibson Jun 1995 COPYRIGHT University of Sussex 1995. All Rights Reserved. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<< POPLOG VIRTUAL MACHINE >>>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<< INSTRUCTION SET >>>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Compilation of user procedures in Poplog is effected by constructing a list of Poplog Virtual Machine (VM) instructions from the source code, and then, when this list is complete for a single procedure, compiling the list of VM code into machine code in a procedure record. This happens whenever sysENDPROCEDURE is called. The procedures which plant each type of VM instruction are given along with occasional instructions on how to use them (especially on the implementation of dynamic scoping). CONTENTS - (Use <ENTER> g to access required sections) 1 Introduction 2 Compiler Control Instructions and Variables 3 Declaring Identifiers 4 Referencing Identifiers in VM Instructions 5 Compile-Time Assignment 6 Dynamic Local Expressions 7 User Stack Manipulation 8 Procedure Calling 9 Labels and Jump Instructions 10 Accessing/Updating Data Structures 11 Implementation Issues 11.1 Lexical Scoping 11.2 Non-Local Jumps 12 More On Dynamic Local Expressions --------------- 1 Introduction --------------- Compilers which use the following procedures should always commence code-planting for a new file or input stream by calling sysCOMPILE (see below). Planting of VM code can be 'turned off' by setting the variable pop_syntax_only to true (see below). Some other aspects of the VM compiler are controlled by flag bits set in the integer variable pop_vm_flags; these are defined in INCLUDE * VM_FLAGS. See also HELP * compile_mode for the syntax construct compile_mode, which enables setting and clearing of these flags (either individually or in groups) via keyword arguments. ---------------------------------------------- 2 Compiler Control Instructions and Variables ---------------------------------------------- sysCOMPILE(compile_p) [protected procedure variable] This procedure should be called when compilation of a new file or input stream is to begin. Its action is to re-initialise the VM context to execute level (setting popexecute to true), and apply the procedure compile_p, which should be the user's compilation routine, i.e. the thing which will actually compile code and plant VM instructions. The virtual machine compiler maintains a number of global variables which record the current code-planting context (e.g. the variable popexecute, the stack of procedures being constructed, the lexical variable context, etc), and sysCOMPILE has all these variables as locals. By applying compile_p through sysCOMPILE, nested compilation streams are thus properly distinguished, and exiting through the call of sysCOMPILE will recover the previous context. sysEXECUTE() [protected procedure variable] Executes any instructions currently planted at execute level, i.e. when not inside any procedures or non-executing lblocks, and popexecute is true (a call of sysCOMPILE establishes a new execute level). (N.B. If there are any lexical constants which are still waiting for an assignment with sysPASSIGN, the code will not actually be executed until these assignments are in.) The code is executed by compiling it into a procedure, and applying that with pop_vm_exec_apply (see below). sysEXEC_COMPILE(compile_p, flags) [protected procedure variable] This procedure enables the code planted by the user compilation procedure compile_p to be executed immediately, regardless of the current context. If popexecute is true (and there is no execute-level code outstanding, i.e. planted but not executed), then compile_p is called, followed by sysEXECUTE. Otherwise, compile_p is called inside a new procedure context, i.e. preceded by sysPROCEDURE and followed by sysENDPROCEDURE, and the resulting procedure is applied (through pop_vm_exec_apply, as for sysEXECUTE). Note that (unless bit 1 is set in flags) it is an error for the code planted to use any (non-top-level) lexical variables non-locally, or employ any non-local jumps, etc. flags is an integer flags argument whose bits have the following meanings: Bit Meaning --- ------- 0 If set, compile_p is assumed to produce one result, and this is saved before executing the code and returned after (thus preventing it from being obscured on the stack by anything produced by the code execution). 1 If set, then 'delayed' evaluation is allowed for code that uses (non-top-level) lexical variables non-locally, or refers to (as yet) unassigned lexical constants. In this case, a 'procedure compilation' structure (a closure) is returned as the result of 'evaluating' the code: this structure is specially marked so that it will actually be executed (through pop_vm_exec_apply) only when the outermost procedure being compiled is finished (when executed, the code must produce a single result). Note, however, that it is still an error for the code to reference non-local lexical variables that require runtime identifiers (i.e. type-3s, see Implementation of Lexical Scoping below). flags may also be a boolean, where false = 0 and true = bit 0 set. (Note also that sysEXEC_COMPILE sets pop_syntax_only locally false, on the assumption that this is always required for compile-time execution of code.) sysEXEC_OPTION_COMPILE(compile_p) [protected procedure variable] -> executed Like sysEXEC_COMPILE, but different in that it allows the execution of the code planted by compile_p to depend on the result of that procedure. That is, compile_p is called and expected to return a boolean result; if this result is true, the planted code is executed, otherwise not. The boolean result executed is true if execution has taken place. The main point about this procedure is that when the code is NOT executed, its context remains unaffected, i.e. if inside a procedure then the code will remain as part of that procedure (unlike sysEXEC_COMPILE, where prior allowance is made for executing the code by bundling it into a new procedure, etc). Because of the complexities of untangling the code planted by compile_p from an enclosing procedure so as to be able to execute it, execution works only for simple cases; thus sysEXEC_COMPILE is provided when execution is always wanted. (N.B. This procedure was previously used by the Pop-11 list and vector constructors [...] and {...}. These now use sysCONSTRUCT, which is more powerful when building structures.) sysCONSTRUCT(compile_p, type) [protected procedure variable] This procedure allows code to be compiled for constructing a structure, with the possibility of executing the code and producing the structure at compile time. Compile-time execution will happen if and only if the structure can be a compile-time constant, i.e. has a fixed number of elements, each of which is also a compile-time constant. compile_p is a procedure of the form compile_p() -> (count_or_false, cons_wordarg) It must compile VM code to push the structure elements, and then return ¤ a count of the number of elements, or false if there are a variable number, and ¤ a wordarg argument suitable for sysCALL, which specifies the actual constructor procedure to be called. The type argument is an integer specifying four different types of constructor procedure, as follows: type Meaning ---- ------- 1 Stackmark: the constructor procedure given by cons_wordarg is like sysconslist or sysconslist_onto, which requires popstackmark to delineate the extent of the structure elements on the stack. 2 Prior stacklength: the constructor procedure is like sysanyvecons, which requires as argument the stacklength before any elements were pushed. 3 Counted: the constructor procedure is like consvector or conslist, requiring the number of elements as argument. 4 Simple: anything else (e.g. a record constructor), which requires no additional arguments. Note that in all cases, compile_p is expected to push only the structure elements; all other code necessary for the four types is supplied automatically. If the structure is produced at compile time, sysCONSTRUCT replaces itself with a sysPUSHQ for the structure. For this to happen, count_or_false must be a fixed count, the constructor procedure given by cons_wordarg must be constant, and all count structure elements must be compile-time constants. This means that the code planted by compile_p can consist only of ¤ sysPUSHQs; ¤ sysPUSHs for (assigned) constants; ¤ sysIDENTs for identifiers other than type-3 lexical variables; ¤ recursive calls to sysCONSTRUCT which in turn result in a sysPUSHQ. In all other cases, run-time code is planted. sysPROCEDURE(props, nargs) [protected procedure variable] Start code generation for a new procedure, i.e. stack up the code list for any procedure currently being compiled, restoring it after a sysENDPROCEDURE. popexecute is set false. When the procedure record is produced, it will have the item props as its pdprops and the integer nargs as its pdnargs. Note that pop_vm_compiling_list is a list of 'procedure compilation' structures for all procedures currently being compiled. sysENDPROCEDURE() -> p [protected procedure variable] Causes the procedure for which code is currently being generated to be produced as an object p, with pdprops and pdnargs as specified by the arguments to sysPROCEDURE. p is returned as result, and the code list stacked up by the last sysPROCEDURE is restored to be the current codelist. popexecute is restored to its value before the previous sysPROCEDURE. Note that p will be a normal executable procedure record ONLY where the procedure does not use any (non-top-level) lexical variables non-locally, and does not employ any non-local jumps. Otherwise it will be a 'procedure compilation' structure (a closure) which must be passed to sysPASSIGN, sysPUSHQ or sysCALLQ (qv). sysLBLOCK(executing) [protected procedure variable] Establishes a new scope for lexical identifiers (and for symbolic labels, see Labels and Jump Instructions below). All lexical identifier declarations made with sysLVARS, etc, after a call of this procedure are cancelled by the call of the matching sysENDLBLOCK which terminates the scope; this applies both inside procedures and at execute level. (Note that sysPROCEDURE and sysENDPROCEDURE also establish a new lexical scope, i.e. they represent implicit calls of sysLBLOCK and sysENDLBLOCK respectively.) The boolean argument executing says whether this is an 'executing' lblock or not, i.e. whether sysEXECUTE can be called inside it to execute code BEFORE the block has been terminated. The difference between executing and non-executing is that ¤ An executing lblock is allowed only in execute context, i.e. when popexecute is true, and not inside a procedure or non-executing lblock; ¤ The first non-executing lblock encountered in execute context sets popexecute false and then behaves as if it were a procedure (i.e. as if sysPROCEDURE had been called, although this does not appear on pop_vm_compiling_list). The sysENDLBLOCK which terminates the block then plants an implicit sysCALLQ for the 'hidden' procedure. Thus lexical variables declared inside non-executing blocks behave like procedure-local variables -- see the discussion under Implementation of Lexical Scoping below. sysENDLBLOCK() [protected procedure variable] Cancels all lexical identifier declarations (and symbolic labels) made since the call of the matching sysLBLOCK that preceded it. popexecute -> BOOL [protected variable] This boolean variable is true when the current invocation of the VM through sysCOMPILE is at execute level, and false when code is being planted inside a procedure or a non-executing lblock. pop_vm_exec_apply(p) [protected procedure variable] This variable procedure is used by sysEXECUTE and sysEXEC_COMPILE etc to run procedures produced from executable code; it just applies its argument p. Its default value is fast_apply. pop_vm_compiling_list -> list [protected variable] Holds a list of 'procedure compilation' structures (closures) for all procedures for which code is currently being compiled (i.e. one corresponding to each call of sysPROCEDURE, where the innnermost procedure is at the beginning of the list and the outermost at the end, etc). Each closure has pdprops and pdnargs as given to the call of sysPROCEDURE that created it. (The appropriate test for being inside compilation of a procedure is therefore pop_vm_compiling_list /== [] Note that in previous versions of the system, testing not(popexecute) would have been equivalent to this, but with the introduction of 'non-executing' lblocks this is no longer the case since popexecute will be false inside such an lblock, even though pop_vm_compiling_list may be empty.) pop_vm_flags -> int [variable] int -> pop_vm_flags Flag bits in this integer variable control certain aspects of the virtual machine's operation. These are defined and described in UNIX: $usepop/pop/lib/include/vm_flags.ph VMS: usepop:[pop.lib.include]vm_flags.ph. Note that the value of pop_vm_flags is localised to each compilation stream, procedure or lexical block, as delineated by sysCOMPILE, sysPROCEDURE and sysLBLOCK respectively; thus when a given scope is terminated, the value of pop_vm_flags is reset to its value on entry to that scope. (Note also that some of the flags are only effective when pop_debugging is false). See SHOWLIB * COMPILE_MODE for the syntax construct compile_mode, which enables setting and clearing of these flags (either individually or in groups) via keyword arguments. pop_syntax_only -> bool [variable] bool -> pop_syntax_only If set true, planting of VM code is turned off, and procedures which actually plant instructions do nothing (although those that return results still do, e.g. sysENDPROCEDURE merely returns identfn). Permanent declarations (e.g. as performed by sysSYNTAX) as also disabled; however, lexical declarations are still effective. The procedure sys_use_current_ident will return a dummy lexical identifier for any undeclared name. (Default value is false.) pop_debugging -> bool [variable] bool -> pop_debugging This variable (default true) is intended as a general indication to various parts of the system (but principally the VM) whether programs are being compiled in debugging mode or 'real-use' mode. Note that pop_debugging is NOT localised by any system procedure. With one exception, all parts of the system that use this variable simply test it to have a false/non-false value, i.e. 'if pop_debugging ...' or 'unless pop_debugging ...' etc. The exception is the test made by sysPUSH, sysCALL and sysUCALL. When given a user-defined permanent constant as argument, and pop_debugging has the value true (as opposed to merely non-false), such a constant is treated as a variable. (This allows tracing of a constant user procedure to operate in code that was compiled before the procedure was traced.) Hence setting pop_debugging to some non-false value other than true (e.g. the word "undef") will enable compilation of user constants as constants, but without any of the effects of a false value. As far as the rest of the VM is concerned, the effect of setting pop_debugging false is twofold: (1) it enables those flags in pop_vm_flags which prevent the inclusion of run-time checking code (which are ineffective when pop_debugging is non-false), and (2) it causes the VM compiler to make extra optimisations in the code for compiled procedures (which may slow down compilation a little). (In particular, some system procedures are optimised to in-line code when they would otherwise not be, because to do so would prevent their appearance in the DOING list of mishap messages caused by userstack underflow during their execution.) pop_pas_mode -> bool [variable] bool -> pop_pas_mode Used by the system compiler Popc (for system use only). Normally, this active variable is false; when compiling a file with Popc, it has the value "popc". ------------------------ 3 Declaring Identifiers ------------------------ sysSYNTAX(word, idprops, const_mode) [protected procedure variable] Declares the word word as a permanent identifier with identprops idprops in the current section. This procedure calls ident_declare (see REF * IDENT) with the given arguments, and is identical to that procedure except in one respect: if word is currently declared as a lexical identifier, the lexical declaration is cancelled (providing the lexical declaration is not local to the current procedure and the identifier has not been referenced -- otherwise, the mishap 'ILLEGAL REDECLARATION OF LEXICAL IDENTIFIER' results). sysGLOBAL(word, global) [protected procedure variable] sysGLOBAL(word) Makes the permanent identifier currently associated with word be either global (global = true) or nonglobal (global = false). If the boolean global is omitted it defaults to true. When global, an identifier is automatically imported into any subsection of a section in which it is accessible. (Note that the term 'global' in this sense is slightly misleading since so marking an identifier does NOT imply that it is exported to top or any other level, merely that it will 'sink' down the section tree from the highest level at which it is accessible. See REF * SECTIONS.) sysVARS(word, idprops) [protected procedure variable] Used by the Pop-11 vars statement, this procedure at execute level is the same as sysSYNTAX(word, idprops, false). When not at execute level, it should produce a mishap. However, for compatibility with the old-style use of vars statements inside Pop-11 procedures to simultaneously declare and dynamically localise a permanent variable, its effect is roughly similar to sysSYNTAX(word, idprops, false); sysLOCAL(word) although it is not guaranteed to work properly in all situations (e.g. with active variables). You are not advised to use it in this way: if you wish to declare and/or dynamically localise a permanent variable, use sysSYNTAX and/or sysLOCAL. sysCONSTANT(word, idprops) [protected procedure variable] Used by the Pop-11 constant statement, this procedure is the same as sysSYNTAX(word, idprops, true), but produces the mishap 'CONSTANT DECLARATION INSIDE PROCEDURE' if not at execute level. sysLVARS(word, idprops) [protected procedure variable] sysDLVARS(word, idprops) [protected procedure variable] Declares the word word as a lexically-scoped variable, the scope of which is the current lexical block, the current procedure, or the current compilation stream if not inside any lexical blocks or procedures. For the duration of the scope, any permanent declaration for word is overidden. The sysCOMPILE, sysENDPROCEDURE or sysENDLBLOCK which terminates the scope cancels the declaration and no further reference to the variable is possible. word must not have been declared as a dynamic local of the current procedure. The idprops argument is the same as for sysSYNTAX and ident_declare (see REF * IDENT), but with the restriction that procedure-local syntax, syntax operator and macro declarations are not allowed, i.e. these are permissible only at execute level. (This is because procedure local variables cannot take compile-time values, and therefore don't make sense as syntax words or macros. The restriction does not apply to sysLCONSTANT.) Top-level lexical variables (those declared at execute level either inside or outside of any lexical blocks) are initialised to a fixed undef record whose undefword is false (this record prints as <undef>). Procedure-local variables, on the other hand, are NOT initialised: on entry to a procedure (or on starting a lexical block inside a procedure), their values are undefined. Full lexical scoping is supported; you can reference word anywhere inside its current scope, including non-locally inside a nested procedure. See the discussion below under Implementation of Lexical Scoping. The flag bit VM_MIX_NONLOCAL_AND_LOCAL_LEX in pop_vm_flags controls whether a procedure is allowed to first access a lexical identifier word while it is non-local and then redeclare it as local. If set then this allowed, and subsequent references to word will get the local value; otherwise, attempting to do this will cause a mishap. (Note that referencing an identifier in an outer lexical block but in the SAME procedure does not count as non-local use.) sysDLVARS is identical to sysLVARS, except that the most general mechanism for implementing non-local access to the lexical variable word is disabled. See under Implementation of Lexical Scoping below. sysLCONSTANT(word, idprops) [protected procedure variable] Declares the word word as a lexically-scoped constant, with the same scope as for a lexical variable. The idprops argument is as for sysSYNTAX (but with no restrictions). A lexical constant is assigned a value with sysPASSIGN. This must happen somewhere within its 'own' scope (i.e. NOT inside a nested scope), but need not be done before referencing it (e.g. in a sysPUSH, sysCALL, etc). Note that (unlike permanent constants) it is perfectly permissible to have nested definitions for lexical constants, although at each level only one definition of each lexical constant is allowed. sysNEW_LVAR() -> word [protected procedure variable] Used to generate temporary working local variables. Returns a word word which is declared as a lexical variable with sysLVARS in the current lexical scope established by sysCOMPILE, sysPROCEDURE or sysLBLOCK. and which is guaranteed to be different from any other variable declared so far in that scope and all enclosing ones. pop_new_lvar_list -> list [variable] list -> pop_new_lvar_list This is a dynamic list that generates the words produced by sysNEW_LVAR. Making it local to a procedure that calls sysNEW_LVAR and plants code will mean that on exit from the procedure, any temporary variables generated will be freed for re-use. --------------------------------------------- 4 Referencing Identifiers in VM Instructions --------------------------------------------- All code-planting procedures which require a word specifying an identifier as argument use the procedure sys_use_current_ident to extract the identifier from the argument given; the procedure descriptions that follow refer to this argument as wordarg. In addition to being an actual word, wordarg may also be a pair of the form conspair(word, info) where info specifies additional information about how the identifier is to be interpreted (or, where more than one info is needed, a pair whose front is another pair, e.g. conspair(conspair(word, info1), info2) etc). Currently, possible values for info are: "nonactive" For an active identifier, this constitutes a reference to its nonactive value, e.g. sysPUSH(conspair(word, "nonactive")) will push the nonactive procedure value of word (rather than turning into a call of that procedure). "weakref" This allows for a 'weak' reference to a permanent identifier which has only a weak declaration (i.e. one for which isdeclared is true but not isdefined, see REF * IDENT). An ordinary ('strong') reference to such an identifier causes sysdeclare to be called. {dword1 dword2 ... dwordN} When info is a full vector of words dword1, ..., dwordN, this indicates a weak reference as for "weakref", but additionally declares word as being dependent on the identifiers named by dword1, ..., dwordN. This means that whenever any member of the dependency set becomes fully defined (including the case where one or more already are), then the dependent identifier word is forced to be defined also (as if a 'strong' reference to it had occurred). consref("weakref") or consref({dword1 ... dwordN}) In the normal interactive system, this behaves as if the cont of the ref alone had been given. However, in Popc compilatiion, putting the "weakref" or vector argument inside a ref indicates that this (weak) reference to the identifier word should not cause a subsequent strong reference to be treated as an error. (Popc insists that an identifier must only be referenced either weakly or strongly, but not both; this facility enables a 'noncommittal' reference to be made.) If there is no lexical or permanent identifier associated with the word given, sys_use_current_ident calls the procedure sysdeclare on the word to define it: sys_use_current_ident(wordarg) -> (ident, nonactive) [procedure] sys_use_current_ident(wordarg, try_nofast) -> (ident, nonactive) Given a wordarg argument as described above, returns the lexical or permanent identifier record currently associated with the word word specified by the argument. If there is no such identifier (or if ident has only a weak permanent declaration and the reference is not "weakref"), sysdeclare(word) is called; a mishap occurs if there is still no ident after this. The result nonactive is true if wordarg contained the keyword "nonactive", false otherwise. The optional boolean argument try_nofast defaults to false. If supplied and true, and the flag VM_NO_FAST is set in pop_vm_flags, then a word with an associated permanent identifier is processed through the procedure pop_nofast_trans (see LIB * pop_nofast_trans). If this returns a 'non-fast' identifier for word, that is returned instead of the 'fast' one. Note that the identifier returned is the compile-time record, i.e. for procedure-local lexical variables it will be a "lextoken" identifier (and not necessarily the same as the run-time identifier that would be produced by sysIDENT). See REF * IDENT (N.B. sysdeclare is not called if pop_syntax_only is true. In this case, a dummy lexical identifier is returned instead.) sys_current_val(wordarg) -> item [procedure] item -> sys_current_val(wordarg) This procedure accesses or updates the compile-time value of the lexical or permanent identifier currently associated with the word derived from wordarg. (It can be thought of as a more efficient equivalent of sysEXEC_COMPILE(wordarg, sysPUSH, false) or sysPOP for the updater.) It uses sys_use_current_ident(wordarg) to get the identifier, and then (in normal compilation) uses idval to access or update its value. However, in Popc compilation, a separate set of 'shadow' values is maintained for all permanent identifiers assigned to by execute-level VM code; in this context therefore, sys_current_val accesses or updates the Popc values of permanent identifiers (there is no difference for lexical identifiers). sysdeclare(word) [protected procedure variable] Called by sys_use_current_ident as described above. The default value of this procedure is approximately as follows: define vars sysdeclare(word); lvars word; sys_autoload(word) -> ; unless isdefined(word) then ident_declare(word, 0, false); sys_raise_exception(word, 1, {'%DECLARING VARIABLE %P' '' 16:01}, 'vm-ident:name-ref-none', `W`) endunless enddefine; That is, first try to autoload word (see * sys_autoload), then if this doesn't define it as a permanent identifier, define it as a permanent variable and call * sys_raise_exception to raise the warning 'vm-ident:name-ref-none'. sysdeclare is user assignable if first unprotected, using sysunprotect. sys_current_ident(word) -> ident [procedure] sys_current_ident(word, try_nofast) -> ident Given a word word, returns the lexical or permanent identifier record currently associated with it (including weak permanent declarations), or false if word is not declared. The optional boolean argument try_nofast defaults to false. If supplied and true, and the flag VM_NO_FAST is set in pop_vm_flags, then a word with an associated permanent identifier is processed through the procedure pop_nofast_trans (see LIB * pop_nofast_trans). If this returns a 'non-fast' identifier for word, that is returned instead of the 'fast' one. The identifier returned is the compile-time record, i.e. for procedure-local lexical variables it will be a "lextoken" identifier (and not necessarily the same as the run-time identifier that would be produced by sysIDENT). See REF * IDENT pop_vm_dummy_idents -> list [variable] list -> pop_vm_dummy_idents This list is used by sys_use_current_ident and sys_current_ident; any word argument to these procedures which appears in the list will have a dummy lexical identifier returned for it. (This mechanism is used, e.g. by the POP11 compiler to temporarily 'declare' formal parameters to procedures before it knows what their correct declarations are.) -------------------------- 5 Compile-Time Assignment -------------------------- See Referencing Identifiers in VM Instructions above for a description of the wordarg argument to sysPASSIGN and sysUPASSIGN. sysPASSIGN(item, wordarg) [protected procedure variable] -> sysPASSIGN(item, wordarg) 'Assigns' item to be the value of the identifier ident derived from the wordarg argument. The assignment is done as follows: (a) If ident is declared as a lexical constant, and this is not an active reference (i.e. ident is not an active variable, or wordarg specifies "nonactive"), then item is simply assigned to the value of ident. ident must not have been assigned a value previously. (b) If popexecute is false, i.e. if a procedure is currently being compiled, then code is planted to pop the item into ident at run-time, i.e. sysPUSHQ(item), sysPOP(wordarg); (c) Otherwise, item is simply assigned directly to the (active/nonactive) value of ident, unless item is a procedure and the current (active/nonactive) value of ident is a a procedure Q, in which case: ¤ If Q has an updater, and item has not, then the updater of Q is assigned to be the updater of item; ¤ If Q is a closure of systrace, i.e. Q is a traced procedure, then item replaces the previously traced procedure. The updater of this procedure is a procedure that calls sysUPASSIGN (as the latter is variable); in this case item must be a procedure. sysUPASSIGN(p, wordarg) [protected procedure variable] 'Assigns' p to be the updater of the value of the identifier ident derived from the wordarg argument, where p is a procedure (including a result of sysENDPROCEDURE). The assignment is done as follows: (a) If ident is declared as a lexical constant, and this is not an active reference (i.e. ident is not an active variable, or wordarg specifies "nonactive"), then it must have had a procedure already assigned to it with sysPASSIGN. p is simply made the updater of this. (b) Unless popexecute is true, the following code is generated: sysPUSHQ(p), sysPUSH(wordarg); sysUCALL("updater"); Note that this will only produce a proper local updater if ident is local to the current procedure, and has had a local procedure assigned to it -- otherwise the updater will be changed non-locally! (c) Otherwise, if the current (active/nonactive) value of ident is a procedure Q then p is assigned to the updater of Q, unless Q is traced (i.e. a closure of systrace), in which case p replaces the previously traced updater of Q. If the current value of ident is not a procedure, then a procedure which will produce the mishap 'ONLY UPDATER DEFINED' when applied is made its value, and p is made the updater of this. ---------------------------- 6 Dynamic Local Expressions ---------------------------- sysLOCAL(code_p) [protected procedure variable] sysLOCAL(wordarg) This procedure enables the value, or values, produced by an arbitrary expression to be made dynamically local to the procedure for which code is currently being planted (or, if called at execute level, to the current compilation stream). Dynamically local means that the value(s) of the expression will be saved on each entry to the procedure and the saved value(s) restored on each exit (this applies in all contexts, including abnormal exits from procedures and the suspension and resumption of processes created with consproc). At execute level, the expression is added as an `on-the-fly' dynamic local of the current call of sysCOMPILE. The value(s) of the expression are saved immediately when sysLOCAL is called; thereafter, they are restored and resaved on each exit and re-entry to the sysCOMPILE call. The expression must have an access part and an update part, the two parts being represented respectively by the base and updater of the code-planting procedure argument code_p. That is, code_p() is assumed to plant VM code to access the value(s) and push them on the stack, while -> code_p() is assumed to plant code to update them by popping an equal number of items off the stack. The number M of values produced/updated is called the multiplicity of the expression, and is specified by the pdprops of code_p (an integer in the range 0-255). Making the values dynamically local is achieved by incorporating the access code and update code at appropriate points in the procedure, and by allocating M local lvars in which to save the values. Effectively this can be thought of as access-code -> (save1, save2, ..., saveM) on entry to the procedure, and (save1, save2, ..., saveM) -> update-code on exit (see More On Dynamic Local Expressions below for a fuller description). Calling sysLOCAL with a wordarg argument (for a description of which see Referencing Identifiers in VM Instructions above) is functionally identical to sysLOCAL(sysPUSH(% wordarg %)) with the pdprops of the sysPUSH closure set to 1 (or, for an active variable, to its multiplicity). That is, the expression is just the (active/nonactive) value of the identifier derived from wordarg. However, since the dynamic localising of simple identifiers (i.e. excluding active variables) is handled specially, sysLOCAL(wordarg) produces much more efficient code than the above. dlocal_context -> int [variable] int -> dlocal_context dlocal_process -> proc [variable] proc -> dlocal_process These are two special (active) variables which may only be used directly inside the code of a dynamic local expression. They are described under More On Dynamic Local Expressions below. -------------------------- 7 User Stack Manipulation -------------------------- See Referencing Identifiers in VM Instructions above for a description of the wordarg argument to sysPUSH, sysPOP and sysIDENT. sysPUSH(wordarg) [protected procedure variable] -> sysPUSH(wordarg) Plants code to push the value of the (lexical/permanent) identifier derived from wordarg onto the stack. (Note that this is optimised to a sysPUSHQ for the value of an initialised constant, except for a non-system permanent constant when pop_debugging == true.) If the identifier is active, and wordarg does not specify "nonactive", then this instruction is equivalent to a sysCALL on the nonactive value of the identifier, i.e. sysCALL(conspair(wordarg, "nonactive")) The updater of this procedure is a procedure that calls sysPOP. sysPOP(wordarg) [protected procedure variable] Plants code to pop the top item from the stack and store it in the value of the identifier derived from wordarg. If the identifier is active, and wordarg does not specify "nonactive", then this instruction is equivalent to a sysUCALL on the nonactive value of the identifier, i.e. sysUCALL(conspair(wordarg, "nonactive")) sysIDENT(wordarg) [protected procedure variable] Plants code to push the run-time identifier record of the identifier derived from wordarg onto the stack (see note under sys_use_current_ident and Implementation of Lexical Scoping below). sysPUSHQ(item) [protected procedure variable] Plants code to push the item item onto the stack. sysPUSHS(dummy) [protected procedure variable] Plants code to push the top item onto the stack onto the stack again, i.e duplicate it. This procedure is given a dummy argument dummy. sysERASE(dummy) [protected procedure variable] Plants code to erase the top item on the stack. This procedure is given a dummy argument dummy. sysSWAP(N, M) [protected procedure variable] Plants code to swap the items at the N-th and M-th positions on the user stack (where the top item on the stack is at position 1). -------------------- 8 Procedure Calling -------------------- See Referencing Identifiers in VM Instructions above for a description of the wordarg argument to sysCALL and sysUCALL. sysCALL(wordarg) [protected procedure variable] -> sysCALL(wordarg) Plants code to execute the value of the (lexical/permanent) identifier derived from wordarg. (Note that this is optimised to a sysCALLQ for the value of an initialised constant, except for a non-system permanent constant when pop_debugging == true.) If the identifier is active, and wordarg does not specify "nonactive", then this instruction is equivalent to a sysCALL on the nonactive value of the identifier followed by a sysCALLS, i.e. sysCALL(conspair(wordarg, "nonactive")); sysCALLS(0); ;;; (0 dummy arg) The updater of this procedure is a procedure that calls sysUCALL. sysUCALL(wordarg) [protected procedure variable] Plants code to execute the updater of the value of the (lexical/permanent) identifier derived from wordarg. (Note that this is optimised to a sysUCALLQ for the value of an initialised constant, except for a non-system permanent constant when pop_debugging == true.) If the identifier is active, and wordarg does not specify "nonactive", then this instruction is equivalent to a sysCALL on the nonactive value of the identifier followed by a sysUCALLS, i.e. sysCALL(conspair(wordarg, "nonactive")); sysUCALLS(0); ;;; (0 dummy arg) sysCALLQ(item) [protected procedure variable] -> sysCALLQ(item) Plants code to execute the item item. The updater of this procedure is a procedure that calls sysUCALLQ. sysUCALLQ(item) [protected procedure variable] Plants code to execute the updater of item, or execute item in update mode if a non-procedure (i.e. call the updater of its class_apply, etc). sysCALLS(dummy) [protected procedure variable] -> sysCALLS(dummy) Plants code to pop and execute the item on top of the stack; the procedure takes a dummy argument dummy. The updater of this procedure is a procedure that calls sysUCALLS. sysUCALLS(dummy) [protected procedure variable] Plants code to execute the updater of the item, or execute the item in update mode, etc; the procedure takes a dummy argument dummy. ------------------------------- 9 Labels and Jump Instructions ------------------------------- A label is an object used for referencing a position within the stream of VM code, and which can be specified as the target of jump instructions like GOTO, IFSO, AND, etc; a label is attached at a given position in the code by calling sysLABEL (or sysDLABEL) at that point. A jump instruction may reference a label anywhere in the procedure for which code is currently being planted (a local jump), or anywhere in an outer, enclosing procedure (a non-local jump). In the latter case, the effect is similar to a call of exitto for the target procedure occurring at the point of the jump, followed by a local jump to the label (although see Implementation of Non-Local Jumps below for more details on this). Labels come in two kinds, symbolic and absolute. Symbolic labels are chosen by the user, and are usually words (although they can in fact be any Poplog objects except pairs); this type of label is lexically-scoped, meaning that a jump to a label LAB will reference the innermost occurence of LAB in the whole nest of lexical blocks and procedures currently being compiled (equality of labels being tested with ==). Absolute labels, on the other hand, are unique items (pairs) generated by sysNEW_LABEL, and are not scoped at all. Jump instructions that refer to an absolute label go to wherever that label is planted, be it in the current procedure or an enclosing one. sysNEW_LABEL() -> lab [protected procedure variable] Generates a new absolute label lab. This can be planted with sysLABEL or sysDLABEL at any point in the code of a procedure, and referenced as the target of a jump instruction. sysLABEL(lab) [protected procedure variable] sysDLABEL(lab) [protected procedure variable] Plant a label: the label lab will reference the next instruction planted, i.e. that instruction will be the target of a GOTO, IFSO, IFNOT, AND, OR or GO_ON instruction using lab, as described above. sysDLABEL is a special variant of sysLABEL that allows optimisation of non-local jumps -- see Implementation of Non-Local Jumps below. sysGOTO(lab) [protected procedure variable] Plant a GOTO instruction: this will jump unconditionally to the label lab. sysIFSO(lab) [protected procedure variable] Plant an IFSO instruction: this will jump to the label lab if the top item on the stack is not false, removing it from the stack whether the jump is taken or not. See sysLABEL for a description of labels. sysIFNOT(lab) [protected procedure variable] Plant an IFNOT instruction: this will jump to the label lab if the top item on the stack is false, removing it from the stack whether the jump is taken or not. sysAND(lab) [protected procedure variable] Plant an AND instruction: this will ¤ jump to the label lab if the top item on the stack is false, leaving the item on the stack; ¤ remove the item from the stack otherwise. sysOR(lab) [protected procedure variable] Plant an OR instruction: this will ¤ go to the label lab if the top item on the stack is not false, leaving the item on the stack; ¤ remove the item from the stack otherwise. sysGO_ON(lablist, elselab, base) [protected procedure variable] sysGO_ON(lablist, elselab) sysGO_ON(compile_p) Plant a GO_ON instruction: this will ¤ Take an integer I off the stack; ¤ If base <= I <= base-1+N, where N = length(lablist), then jump to the (I-base+1)-th label in the label list lablist; ¤ If I is not within range and the 'else' label elselab is false then a mishap results, otherwise a jump to elselab is taken. In the second form, the value of base defaults to 1. In the last form, compile_p is a procedure which will be called to compile code to follow the GO_ON instruction, and should return the values for lablist, elselab and base, i.e. compile_p() -> (lablist, elselab, base) (In other words, using compile_p is a way of deferring supplying the values of lablist, elselab and base until some later point.) Unless the flag bit VM_NO_CHECK_GO_ON_INT in pop_vm_flags was set when the sysGO_ON was executed (and pop_debugging was false), then a mishap will result if I is not an integer. -------------------------------------- 10 Accessing/Updating Data Structures -------------------------------------- The VM provides the instructions below for accessing or updating a field of a structure; the code produced by these corresponds exactly to the code inside the access/update procedures constructed by cons_access. The following should therefore be read in conjunction with the description of field specifiers, etc, and cons_access itself, in REF * KEYS sysFIELD(N, spec, check, mode) [protected procedure variable] -> sysFIELD(N, spec, check, mode) sysUFIELD(N, spec, check, mode) [protected procedure variable] These procedures respectively generate in-line access or update code corresponding exactly to the access and update procedures produced by cons_access (see REF * KEYS). The arguments are identical to those for cons_access, except for the first argument N. The updater of sysFIELD just calls sysUFIELD. As for cons_access, the spec argument may be either a spec_list (access/update a Poplog record/external structure field), a spec_pair (access/update a Poplog vector/external array element), a spec_vec (call an external function), or any other spec (access an external data type). For external accessing of structure or array components, the mode argument may also specify that an external pointer to the component should be returned rather than its value (in this case therefore, "value" in the descriptions below means a pointer to the value). In the spec_list case, N is an integer >= 1, specifying that the N-th field of the structure is being accessed/updated. The effect of the instruction is to remove the structure item on top of the stack and either push back on the field value (sysFIELD), or pop the next item off the stack into the field (sysUFIELD). In the spec_pair case, N is either an integer >= 1 for a fixed vector/array subscript, or false for a variable subscript coming off the stack after the vector/array item. The effect of the instruction is to remove the vector/array item on top of the stack, remove (when N false) the subscript next on the stack, and then either push back on the element value (sysFIELD), or pop the next item off the stack into the element (sysUFIELD). The spec_vec case is allowed only for sysFIELD; the mode argument must specify 1 level of external dereference, and the argument N is ignored. The effect of the instruction is to remove the external function pointer from the stack and call it, which will remove its arguments (if any) from the stack, and push back on its result (if any). For any other spec the N argument is ignored, and behaves the same as an external structure with one field of that type, i.e. same as the spec_list case with a spec_list of [>-> ^spec] and N = 1. Examples: sysPUSH("list"); sysFIELD(2, [full full], false, false) is equivalent to fast_back(list). sysPUSH("item"); sysPUSH("list"); sysUFIELD(2, [full full], false, false); is equivalent to item -> fast_back(list). sysPUSH("N"); sysPUSH("string"); sysFIELD(false, conspair("byte", false), false, false); is equivalent to fast_subscrs(N, string). sysPUSHQ(65); sysPUSH("string"); sysUFIELD(2, conspair("byte", false), false, false); is equivalent to 65 -> fast_subscrs(2, string). ------------------------- 11 Implementation Issues ------------------------- 11.1 Lexical Scoping --------------------- This section describes the way in which lexically-scoped variables declared with sysLVARS will actually be implemented in the final output code. Note that (except for 'non-executing' ones outside of procedures), lexical blocks created with sysLBLOCK and sysENDLBLOCK have no affect in this respect, but merely control the scopes and lifetimes of variables; it is the usage of variables with respect to procedures that determines how they are implemented. Dealing first with execute-level variables (i.e. those outside of procedures/non-executing lblocks): these simply translate to unique fixed identifiers, and are just like permanent variables except with lexical scope. Otherwise, lexical variables are those declared inside procedures (or inside non-executing lblocks not contained in procedures; the latter is equivalent to the former, since the outermost of a nest of such blocks is treated as a procedure). A given variable X in a procedure P falls into one of three categories, depending on its usage: type-1 Used strictly locally, i.e. X is not referenced non-locally inside any procedure nested within P, and is not pushed as an identifier with sysIDENT. type-2 Used non-locally inside a nested procedure Q, but does not cross a 'push' boundary. This means that neither Q nor any other procedure that encloses it inside P is pushed as a data object (see below). Also not pushed as an identifier with sysIDENT. type-3 Used non-locally inside a nested procedure, and does cross a 'push' boundary, or directly pushed as an identifier with sysIDENT. A procedure is 'pushed as a data object' if the object returned by sysENDPROCEDURE is given to sysPUSHQ, or sysPASSIGNed to any identifier other than a lexical constant. If assigned to a lexical constant, it is also 'pushed' by any sysPUSH for that constant. In Pop-11 terms, this means that the only kind of nested define that does not count directly as a 'push' is 'define lconstant ...'. Thus for example, in the following Pop-11 procedure W is of type 1, X is of type 2, and Y and Z are of type 3: define P(); lvars W, X, Y, Z; W => ;;; W only used locally define lconstant Q(); X => ;;; X used non-locally enddefine; Q(); ;;; Q is called but not pushed define lconstant R(); define lconstant S(); Y => ;;; Y used non-locally enddefine; S(); ;;; S is called but not pushed enddefine; R -> W; ;;; but R is pushed define dlocal T(); ;;; T is pushed by being dlocal Z => ;;; and uses Z non-locally enddefine; enddefine; The different variable types are implemented by the following mechanisms, given in order of decreasing efficiency: ¤ The first N type-1 variables (in order of declaration) are allocated to machine registers, where the number N is implementation dependent. (For example: N = 11 on DEC Alpha, 8 on SPARC, 6 on HP PA-RISC, 2 on VAX/MC680x0, and 0 on Intel x86.) ¤ The rest of the type-1s are allocated to stack frame cells. ¤ Type-2 variables are allocated to unique dynamically local identifiers. ¤ Type-3's require the runtime construction of identifier records in the heap to hold their values, since a 'pushed' procedure that uses them non-locally (or the identifiers themselves if pushed with sysIDENT) can be passed either (a) up and right out of the current lexical environment, or (b) down into another invocation of that same environment. This type therefore uses type-1 variables to hold runtime identifiers created at appropriate points in the home procedure (i.e. immediately on entry for variables outside lexical blocks, or at points in the code where lexical blocks start, etc). The run-time identifiers are then passed down as hidden extra arguments to nested procedures that use the variables non-locally. Whenever such a procedure is 'pushed', a closure of the base procedure with the run-time identifier arguments is created and, in addition, an auxilary variable is generated to hold the closure, ensuring that it is only produced once for each invocation of the environment. (This variable itself may come out as any of the three types, depending on whether it is used non-locally or across a push boundary.) Similarily, a direct push of an identifier with sysIDENT produces the run-time identifier. In fact, providing neither (a) nor (b) actually happens for a type-3, a unique dynamic variable (as for type-2 variables) would suffice; while the VM compiler is unable to recognise this, the user may be able to in many situations. For this reason, the sysDLVARS declaration is provided: it behaves exactly like sysLVARS, except that type-3 sysDLVARS are treated as type-2s. A typical use of this in Pop-11 would be something like the following procedure to count the number of characters printed for an object: define countchars(x) -> n; lvars x; dlvars n = 0; ;;; initialise count to 0 define dlocal cucharout(); -> ; ;;; erase the character n+1 -> n ;;; increment non-local count enddefine; pr(x) enddefine; On the other hand, it may be known that an individual 'push' for a particular procedure (or a push of an identifier with sysIDENT) does not necessitate the use of type-3 variables. E.g. in applist([1 2 3 4], P) where P is a procedure that uses non-locals, (a) or (b) cannot happen because applist is a system procedure that can never do either with P. Where this sort of information is known to the compiler writer, the flag VM_DISCOUNT_LEX_PUSHES in pop_vm_flags can be employed. While set, sysPUSH, sysPUSHQ and sysIDENT instructions that would otherwise cause variables to be marked as type-3 will not do so. (However, any other sysPUSH or sysPUSHQ on the relevant procedure while this flag is NOT set will cause the 'damage' to be done, irrevocably.) (In fact, the VM compiler DOES recognise this situation when the pushed procedure is the last argument to one of a small number of system procedures: currently, these are just the 'app' procedures applist, maplist, appdata and appproperty. So, the particular example above would not (in itself) cause the production of type-3 variables.) 11.2 Non-Local Jumps --------------------- A non-local jump from a procedure Q to a label in a lexically-enclosing procedure P closely parallels a non-local access to a lexical variable; if the label is thought of as a variable, and the jump instruction as an access to it, then what matters from the point of view of how the jump is implemented is whether the label is 'type-3' or not; that is, whether it crosses a push boundary. In the case where it does not, the nested procedure Q can only be called directly from within P or some other directly-called sub-procedure of P. This means there cannot be any other intervening call of P between the call of Q and the target stack frame; to unwind the call stack to the correct call of P it is therefore sufficient to do exitto(P). (Moreover, it can safely be assumed that the target call of P is still extant, obviating the need to run up the call stack to check this first.) When the jump does cross a push boundary this is not sufficient, because Q can now have been passed down into other invocations of P (or indeed right out of P altogether), and there there could be any number of intervening calls of P to be erased before reaching the target. A means of identifying different invocations of P is therefore required, and the system does this as follows: For each procedure P that can be the target of a non-local jump across a push boundary, a unique dynamic variable is generated, and made a local of P; on entry to P, the value of this variable is set to a sequentially-generated integer (which will be unique for each call of P). The identifying integer is then passed down (like a type-3 lvar) as an extra, hidden argument to pushed sub-procedures that jump to P, enabling them to identify which call of P to exit to (by inspecting the current value of the dynamic variable). In addition, this type of jump has first to inspect the saved values of the variable on the call stack to check that the target call is still extant. It is therefore slightly slower than the first type, and (as with a type-3 lvar), pushing a sub-procedure that does such a jump requires the creation of a closure. Again as with lvars, the user may know that a pushed procedure doing a non-local jump will in practice require only the simple case (i.e. target will always be extant, and no intervening calls of the same procedure). Analagously to sysDLVARS, the VM provides sysDLABEL to specify this: non-local jumps to a label planted with sysDLABEL (rather than sysLABEL) will always use the simple mechanism, and pushing sub-procedures doing the jumps will not create closures. (In Pop-11, use the label syntax LABEL:* rather than LABEL: when sysDLABEL is appropriate.) ------------------------------------- 12 More On Dynamic Local Expressions ------------------------------------- As described under sysLOCAL, expression values are made dynamically local to a procedure by incorporating the access code and update code for the expression at appropriate points, and by the allocation of local lvars in which to save the values. This section describes the process in detail. The access code for an expression is actually incorporated at three places in a procedure, and the update code at four places. As previously mentioned, the normal entry code for the procedure contains access-code -> (save1, save2, ..., saveM); (where save1, ..., saveM are the local lvars allocated to the expression), while the normal exit code contains (save1, save2, ..., saveM) -> update-code; However, separate code sections are used for abnormal exit (i.e. when the procedure is exited with chain, chainfrom, exitfrom, etc), and for swapping in and out of the procedure when it forms part of a consproc process being resumed or suspended. For abnormal exit, the expression code used is the same as for normal exit, i.e. (save1, save2, ..., saveM) -> update-code; Suspend and resume, on the other hand, are a little different: in these cases dynamic local values must be exchanged with their saved values. For process suspension, the current value is swapped with the saved value in the procedure's stack frame before that stack frame is saved in the process record; for resumption the opposite happens, i.e. the values are swapped around after restoring the stack frame from the process (and thus putting everything back as it was before the process was suspended). The code generated for each of these contexts is therefore an interleaving of the code for entry and exit, i.e. (access-code, save1, save2, ..., saveM) -> (save1, save2, ..., saveM, update-code); for suspend, and (save1, save2, ..., saveM, access-code) -> (update-code, save1, save2, ..., saveM); for resume. To enable the access or update code to determine in which context it is being called, an integer context value (1 - 4) is available via the special active variable dlocal_context (this variable may be referenced ONLY in local expression code, and nowhere else). The contexts and their applicablity are as follows: value context applies to ----- ------- ---------- 1 normal entry access normal exit update 2 abnormal exit update 3 suspend access update 4 resume access update In the case of suspend and resume, the current process record is also available to the code from the active variable dlocal_process (with similar restrictions on its use). The value this returns is defined only for contexts 3 and 4, and is UNDEFINED for the other two. Using dlocal_process enables the access or update code to manipulate the process undergoing suspension or resumption in any desired way. (Note that owing to consproc_to, which uses the suspend and resume mechanisms to create a process from an existing section of the call stack, the process given by dlocal_process is not necessarily the same as pop_current_process.) Having dealt with the code generated for individual local expressions, we now discuss the overall handling of a complete set of N expressions in a given procedure, declared with sysLOCAL in the order 1 to N: Basically, expressions are executed in declaration order (1 upto N) for 'ingoing' contexts (normal entry and resume), and in the reverse order (N down to 1) for 'outgoing' contexts (normal/abnormal exit and suspend). However, a prime constraint is that the restore/update code for any expression must not be run unless the corresponding access/save code has been executed first (since the latter must produce the values to be given to the former); because a procedure can be interrupted during the execution of the access code for a particular expression (or indeed, before any of the access code sections have been started), and because such an interrupt can lead to an abnormal exit or a process suspend, it is incumbent on the procedure to provide a mechanism that prevents this constraint being violated. To this end, the procedure maintains a counter, or index, of expressions run. The index is initialised to 0 immediately on entry and before interrupts can be serviced; thereafter, for each ingoing context, the index is set to I after completing code for the I-th expression, and for each outgoing context to J-1 before starting to produce code for the J-th expression. E.g., for normal entry and exit: 0 -> index; ;;; before interrupts normal entry normal exit ------------ ----------- save-expr1; LabN: N-1 -> index; 1 -> index; restore-exprN; save-expr2; . 2 -> index; . . . . Lab2: 1 -> index; . restore-expr2; save-exprN; Lab1: 0 -> index; N -> index; restore-expr1; Lab0: The abnormal exit code is then similar to that for normal exit, except that it switches to LabI, where I is the value of the index; this prevents expressions being restored that have not been saved. Notice that it also prevents a second or subsequent attempt to chain out of the procedure from restoring expressions that have already been restored. Suspend and resume are more complicated. A suspend at a time when the index has value I runs the swap code for expressions I down to 1 (reducing the index actually saved in the stack frame to 0), but the corresponding resume that will follow it must undo only what the suspend did and no more, that is, run the swap code from 1 upto, and finishing at, I. A suspend therefore starts by saving the current index in another location: this is then the limit value for a subsequent resume. (An additional complication here is that both the suspend code and the resume code can themselves be interrupted by a recursive suspend and resume under certain conditions, and so the code has to be re-entrant to cope with this.) Finally, note that all local expression code is run inside the normal local-identifier environment of a procedure, i.e. expression entry code follows the creation of local lvars and the saving of (non-active) dynamic local identifiers, and expression exit code precedes the unwinding of this environment. (Pop-11 users should also note that access code for dlocal expressions is run before popping procedure formal arguments from the stack, and that update code is run after pushing formal result variables, etc.) +-+ C.all/ref/vmcode +-+ Copyright University of Sussex 1995. All rights reserved.