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.

SourceForge.net Logo