Parallel Virtual Machine specifications
---------------------------------------

1. Virtual Machine Model
   ---------------------

The parallel virtual machine (PVM) model is basically a set of
instructions and a set of operand classes.  The operand classes are
divided in 2: LOCATIONS and VALUES.  Locations can be used both as
source and destination operands in instructions whereas values can
only be used as source operands.  A location can be either one of:

  - register
  - stack slot
  - global variable
  - closed variable (i.e. a slot in a closure's environment)

 registers                       stack                global variables
 ________                                                ________
|________| 0                   |________|               |________| +
|________| 1                   |________| frame size    |________| CAR
|________| 2                   |________| ...           |________| FOO
|________| ...                 |________| 2  /|\        |________| .
|________| target.nb-regs - 1  |________| 1   |         |________| .

The number of registers in the VM is a parameter that can be adjusted
to the target machine.  For this reason, the definition of `target.nb-regs'
comes from the back end.  `target.nb-regs' is guaranteed to be at least 2.


2. Virtual Machine Operands
   ------------------------

Available PVM operands are:

Locations:

    NOTATION     OPERAND CLASS

    rN           register (N = 0 .. target.nb-regs - 1)
    sN           stack slot (N = 1 .. inf, s1 is oldest slot)
    "name"       global variable
    base:N       closed variable (base is any class of operand,
                                  N is slot number)

Values:

    NOTATION     OPERAND CLASS

    'obj         scheme object
    lN           address of a label in the code
    ?loc         `potentially future' operand

A `potentially future' operand should be touched if the actual value
of the operand is needed.  All other operands denote the actual value
and do not need to be touched.  Note that a future can be converted to
its actual value at any time so, for example, one can not rely on
`r1' to be identical to `r2' after the sequence

  COPY(r1,r2)
  APPLY(+,?r1,'1,r3)

because the garbage-collector or the APPLY operation itself could have
transformed the (future) value in r1 into its actual value.


3. Virtual machine instructions
   ----------------------------

The virtual machine's instruction set is composed of the following
instructions:

`frame_size' is the size of the current frame.

LABEL( lbl_num )                                  (1)
LABEL( lbl_num, TASK, method [,task_size] )       (2)
LABEL( lbl_num, PROC, param_pattern [,CLOSED] )   (3)
LABEL( lbl_num, RETURN [,method [,task_size]] )   (4)
|
| All of these instruction forms declare the local label `lbl_num'.
| `frame_size' is the size of the stack frame (in number of slots) after
| the LABEL instruction has been executed.
|
| The form (1) declares a simple label to be used as a destination of an
| JUMP instruction of form (1) or a COND instruction.
|
| The form (2) declares the entry point of a task.  Task's are similar to
| procedures in that they are passed a continuation and they return a value
| to this continuation.  When a task label is jumped to, a new thread is
| created to compute the value of the task and deliver it to the continuation.
| Results are delivered in the same location as for procedures.  The back end
| defines what PVM location contains the continuation.  Note that the
| task label's stack frame will have been removed when the continuation
| is entered.  The actual implementation method used for the task is
| determined by the value of `method' which is one of `DELAY', `EAGER',
| `EAGER-INLINE' or `LAZY'.
|
| The form (3) declares the entry label of a procedure where
| `param_pattern' defines how parameters are passed to the procedure.
| `param_pattern' is of the form `[min-] nb_parms [...]' and the label
| expects the same number of arguments as a lambda-expression that declares
| `nb-parms' parameters, of which `min' are required (if present) and whose
| last parameter is a rest parameter if `...' is present.
| If `CLOSED' is specified, the procedure is actually the body of a true
| closure and the label can only be referenced in a MAKE_CLOSURES instruction.
| The back end defines the state of the stack and registers following
| this form of LABEL instruction and also in what location procedures return
| their value.
|
| The form (4) declares a label to be used as a return address.
| It can only be jumped to by a JUMP instruction of form (1).
| The back end defines what PVM location contains the result of a
| procedure.  Return labels associated with tasks are marked with the same
| method and task_size as the corresponding task label.
|
| Labels of the form (3) that are not CLOSED and of the form (4) declare
| `first class' labels that can be used as any other Scheme value (i.e. the
| GC knows what to do with them).  However, only labels of form (3) that are
| not CLOSED and closures created by the MAKE_CLOSURES instruction are true
| `callable' Scheme procedures.  Labels of the form (1) and (2) can only be
| referenced in JUMP instructions of form (1) and COND instructions, labels of
| the form (3) that are CLOSED can only be referenced in MAKE_CLOSURES
| instructions.

APPLY( prim, opnd1, ..., opndn, [loc] )
|
| Compute the primitive `prim' applied to the operands and, if `loc' is
| specified, put the result in the location `loc'.
|
| For example: APPLY(##car,r1,r2)
|
| The back end defines which primitives can be used in this instruction.

COPY( opnd, loc )
|
| Copy the value of `opnd' into location `loc'.

MAKE_CLOSURES( loc1,closed_lbl_num1,slot1_opnd,slot2_opnd,... /
               ... /
               locn,closed_lbl_numn,... )
|
| Create `n' closure objects and, in a left to right order, put their
| addresses in the corresponding location.  A closure object is composed
| of 2 parts: a code pointer and an environment that contains the the closure's
| closed variables.  Each closure will have as many slots for closed variables
| as the number of corresponding opnds.  Note that each closed variable slot
| is initially undefined.  Then, in an  unspecified order, assign each operand
| to the corresponding slot.  This operation must be performed atomically
| (i.e. no intervening GC) because some slots are undefined.  The code pointer
| of a closure also gets initialized to the corresponding closed PROC label.
| When a closure object is jumped to, control will be transferred to the
| corresponding closed PROC label and a pointer to the closure's environment
| will be passed in a location defined by the back end.

COND( prim, opnd1, ..., opndn, true_lbl_num, false_lbl_num )
|
| Jump to label `true_lbl_num' if `prim' applied to the operand(s) is not
| false and to `false_lbl_num' otherwise.  `frame-size' is the size the
| stack frame will be shrunk to before entering the target label or the
| fall through label.  The back end defines which primitives are testable.

JUMP( opnd          [,INTR-CHECK] )    (1)
JUMP( opnd, nb_args [,INTR-CHECK] )    (2)
|
| These two instruction forms jump to the address `opnd'.  `frame-size' is
| the size of the stack frame before entering the target.
|
| The form (1) can only be used to jump to LABEL instructions of form (1),
| (2) and (4).
|
| The form (2) is used to call procedures where `nb_args' is the number
| of arguments passed to the called procedure.  The back end defines the
| locations where the arguments must be at the time of the JUMP.
|
| `INTR-CHECK' is specified when interrupts (e.g. user interrupt, stack
| overflow interrupt, GC interrupt, etc.) should be polled for.


4. Example
   -------

Here is an exemple of PVM code generated by the Gambit compiler, using the
M68000 back end.  The source code is the parallel fibonacci procedure:

(##declare (multilisp) (standard-bindings) (fixnum) (not autotouch))

(define (pfib n)
  (if (< n 2)
    n
    (let* ((x (FUTURE (pfib (- n 1))))
           (y (pfib (- n 2))))
      (+ (TOUCH x) y))))

Each PVM instruction is prefixed by the size of the active part of the
frame after the instruction has executed.  The comment at the end of
each line describes the location of each live variable in the context
(stack frame and registers) after the instruction has executed.

**** #[procedure pfib] =
[ 0] LABEL(L1,PROC,1)                          {r0=RET r1="n"}
[ 0]   COND(##fixnum.<,r1,'2,L3,L4)            {r0=RET r1="n"}
[ 0] LABEL(L4,SIMP)                            {r0=RET r1="n"}
[ 1]   COPY(r0,s1)                             {s1=RET r1="n"}
[ 2]   COPY(r1,s2)                             {s1=RET s2="n" r1="n"}
[ 2]   COPY(L6,r0)                             {s1=RET s2="n" r0=TMP r1="n"}
[ 2]   JUMP(L5,INTR-CHECK)                     {s1=RET s2="n" r0=TMP r1="n"}
[ 0] LABEL(L5,TASK,LAZY,100)                   {r0=RET r1="n"}
[ 0]   APPLY(##fixnum.-,r1,'1,r1)              {r0=RET r1=TMP}
[ 0]   JUMP("pfib",1)                          {r0=RET r1=TMP}
[ 2] LABEL(L6,RETURN,LAZY,100)                 {s1=RET s2="n" r1=TMP}
[ 3]   COPY(r1,s3)                             {s1=RET s2="n" s3="x" r1="x"}
[ 3]   APPLY(##fixnum.-,s2,'2,r1)              {s1=RET s2 s3="x" r1=TMP}
[ 3]   COPY(L7,r0)                             {s1=RET s2 s3="x" r0=TMP r1=TMP}
[ 3]   JUMP("pfib",1)                          {s1=RET s2 s3="x" r0=TMP r1=TMP}
[ 3] LABEL(L7,RETURN)                          {s1=RET s2 s3="x" r1=TMP}
[ 1]   APPLY(##touch,s3,r2)                    {s1=RET s2 s3 r1="y" r2=TMP}
[ 1]   APPLY(##fixnum.+,r2,r1,r1)              {s1=RET s2 s3 r1=TMP}
[ 0]   JUMP(s1,INTR-CHECK)                     {r1=TMP}
[ 0] LABEL(L3,SIMP)                            {r0=RET r1="n"}
[ 0]   JUMP(r0,INTR-CHECK)                     {r1=TMP}
