CS222 Lecture: Procedures; The VAX Procedure-Calling Standard;          10/12/91
               Recursion                                        Revised 1/27/99

Materials:

1. Handout: ARGUMENTS.MAR
2. Transparencies: VAX Procedure Call Frame; H & P Figures 3.12, 3.13

I. Introduction
-  ------------

   A. As we know, any large program should be written as a collection of
      independent routines which call and are called by one another.

   B. Further, the preparation of programs can be made much simpler by
      making use of LIBRARIES of pre-written routines.

      1. Some of your labs have made use of a library of routines
         to do simple IO tasks: RNUM, PNUM, etc.

      2. Compilers for HLL's often translate statements into calls to a
         support library - e.g. Pascal write, read etc. are implemented by
         such routines.

   C. Today, we begin to consider how routines are implemented at the
      machine level.  We must consider issues like:

      1. Routine call/return mechanisms

      2. Passing of parameters

      3. Handling of values returned by functions

      4. Preservation of register values when both the caller and the
         called routine use the same registers

      5. Handling of recursion.

   D. On the VAX, some of these issues are handled directly by the hardware,
      while others are handled by following certain conventions in software.

      1. In particular, we will become familiar with a document called "The VAX
         Procedure Calling Standard" which prescribes conventions that software
         should follow when using procedures.  

         a. This standard is followed by all system software (e.g. the VMS
            run-time library and system services.)

         b. It is followed by all VAX language compilers. Adherence to these 
            conventions allows procedures written in one programming language 
            to call procedures written in another; in particular, we will 
            learn how Pascal routines can call MACRO procedures and vice-versa.

         c. Procedures within a user-written program that call one another do
            not have to adhere to this standard - but generally should.

      2. For generality, we will also look at how some of these issues are
         handled by machines other than the VAX - in particular, MIPS.

II. Routine call/return mechanisms
--  ------- ----------- ----------

   A. The VAX hardware provides two distinct mechanisms to allow routines to
      call other routines.

      1. The subroutine mechanism.

      2. The procedure mechanism.

   B. We have already met the VAX subroutine mechanism.  

      1. It uses 4 instructions: 3 for calling a routine and 1 for return

         a. Call instructions:  BSBB, BSBW, JSB

         b. Return instruction: RSB

      2. It does not provide any special support for parameters.  (The IO
         library routines we have used have passed parameters through registers
         R0 and/or R1.)

   C. The procedure mechanism is much more powerful and general.

      1. It uses 3 instructions: 2 for calling a routine and 1 for return

         a. Call instructions: CALLS, CALLG

         b. Return instruction: RET

      2. It provides significant support for parameters - and should normally
         be used whenever a routine must have parameters.  (Earlier examples 
         used subroutines for IO to avoid having to introduce the full 
         procedure mechanism too early.)

      3. It is also normally the mechanism to be used when routines in one 
         module call routines in another module.  (Again, the IO library forms
         an exception.)

      4. Finally, it is the mechanism that MUST be used when a routine written
         in one language calls a routine written in another language.

   D. The MIPS call/return mechanism makes use of two instructions:

      1. jal - jump and link - saves the address of the next instruction in
         a designated register (typically $31) and jumps to the start of a
         procedure.

      2. jr - jump register - jumps to a location whose address is contained
         in a register (again, typically $31).

III. Parameter-Passing
---  -----------------

   A. Two basic issues:

      1. WHERE are parameters passed?

      2. WHAT information is passed when a parameter is passed?

   B. Some options for the WHERE issue.

      1. Parameters can be passed through fixed locations in memory

         Example: The RSTS/E operating system - used on the PDP-11 - requires
                  that parameters to system routines be passed in one or both
                  of two fixed regions in memory called FIRQB (address 402
                  octal and up) and XRB (begins just after FIRQB)

         Problems:

         a. The caller needs to know a lot of detail about the routine it calls.

         b. Recursion is difficult - the parameters for one call have to be
            "moved out of the way" to make room for another.

      2. The CPU registers - e.g. first parameter in R0, second in R1 etc.

         Example: The IO library used in some of our examples.

         Problems:

         a. The number of parameters is limited by the number of registers.

         b. Before a procedure can call another, it must first move its own
            parameters out of the way.

         MIPS uses this approach, with 4 or 8 registers dedicated by convention 
         to this purpose ($4 .. $7 or $4 .. $11 - two different sets of
         conventions).  If a given procedure requires more parameters, one of 
         the other approaches must be used for the rest.

      3. On the stack - the caller pushes a series of parameters onto the
         run-time stack.

         a. This mechanism is used by many CPU's.

         b. It fully supports flexibility in calling other procedures from
            within a procedure - including recursively.

         c. It is one of two options commonly used on the VAX.

      4. The VAX approach is based on the use of PARAMETER LISTS.

         a. A parameter list is a series of longwords in adjacent memory
            cells.  The AP register (R12) points to the first of these.

            i. The first longword holds (in its low order byte), a COUNT of
               the number of parameters.  This allows routines to take a
               variable number of parameters.

               Note: This value must lie in the range 0..255.  The high order
                     24 bits of this longword are not used and must be zero.

           ii. The remaining longwords hold the individual parameters.

               Example: call procedure foo with actual parameters 1 and 17.
                        The parameter list would look like this:

                        _________________
                (AP):   | (zero)   | 2  |
                        |---------------|
                        |       1       |
                        |---------------|
                        |       17      |
                        -----------------

         b. The parameter list can be constructed in one of two ways:

            i. It can be created at compile time in the caller's data area.
               When the procedure is called, the AP is loaded with the
               address of this list.

           ii. It can be created at run time by pushing the parameters on
               the run-time stack.  (Note: the parameters must be pushed in
               the order LAST, SECOND TO LAST .. FIRST, COUNT because the
               stack grows from high addresses toward low.)  When the procedure
               is called, the AP is loaded with the address of the top of the
               stack (which holds the count.)

          iii. The first approach is more efficient when the parameters are
               known at compile-time - e.g. when the parameters are constants
               or the addresses of variables.  The second approach must be used
               if the items to be included in the parameter list are not known
               until run-time.

         c. The two different VAX CALL instructions reflect these two
            options.

            i. CALLG is used when the parameter list can be set up ahead of
               time.

               Example: 

               C:                       foo(1, 17);

               MACRO:   ; In data area:

                        FOO_PARAMS:     .LONG   2
                                        .LONG   1
                                        .LONG   17

                        ; Actual call:

                                        CALLG   FOO_PARAMS, FOO

                        The hardware places the address of the parameter
                        list in AP, and then calls the procedure

           ii. CALLS is used when the parameter list is to be created on
               the stack.  Example:

               C:                       foo(x, y);

               MACRO:                   PUSHL   Y
                                        PUSHL   X
                                        CALLS   #2, FOO

                        The hardware completes the parameter list by
                        pushing the count longword (derived from #2), and
                        then copies SP into AP before calling the procedure.

          iii. When a procedure is called using CALLS, the RET instruction
               pops its argument list from the stack automatically.  (The CALLS
               instruction sets a flag to indicate that CALLS was used, and
               the RET instruction checks this flag and then (if it set) uses
               the count field in the argument list to determine how many
               longwords to remove.)

   C. Some options for the WHAT is passed issue

      1. Recall that Pascal supports two types of parameters:

         a. Value - The procedure receives a COPY of the actual parameter.  Any
            changes made to it do not affect the original belonging to the
            caller.

         b. Var (reference) - The procedure receives the ADDRESS of the caller's
            actual parameter.  Any changes made by the procedure do affect the
            original.  (Also - as a consequence - the actual parameter must be
            a variable or a component of a variable - it cannot be an expression
            that is evaluated at run time.

      2. The following examples show how each type of parameter COULD be 
         handled.  (VAX Pascal doesn't actually do it this way, but many other
         Pascal implementations do.)

         a. Value parameters:

            Pascal:     procedure foo(a: integer);
                          begin
                            b := a + 1;
                            ...
                          end;

                        ...

                        foo(x);

            VAX MACRO:  .ENTRY  FOO, ^M<>
                        ADDL3   #1, 4(AP), B
                        ...
                        RET

                        PUSHL   X
                        CALLS   #1, FOO

            MIPS:       .ent    foo
                        .globl  foo
                foo:
                        addi    $2, $4, 1       ; $2 used as temporary
                        sw      $2, b
                        ...

                        lw      $4, x
                        jal     foo

         b. Var parameters:

            Pascal:     procedure foo(var a: integer);
                          begin
                            b := a + 1;
                            ...
                          end;

                        ...

                        foo(x);

            VAX MACRO:  .ENTRY  FOO, ^M<>
                        ADDL3   #1, @4(AP), B           ; Note deferred mode
                        ...
                        RET

                        PUSHAL  X                       ; Note push ADDRESS
                        CALLS   #1, FOO

                or      Alternate code for caller:

                PLIST:  .LONG   1
                        .ADDRESS X
                        ...
                        CALLG   PLIST, FOO

            MIPS:       .ent    foo
                        .globl  foo
                foo:
                        lw      $2, 0($4)       ; $2 used as temp - value of a
                        addi    $2, $2, 1
                        sw      $2, b
                        ...

                        addiu   $4, $0, x       ; $4 gets ADDRESS of x
                        jal     foo

      3. The VAX calls these two mechanisms CALL BY IMMEDIATE VALUE and CALL
         BY REFERENCE (respectively).

         a. Immediate value is limited to use with parameters that are either a
            longword or can be converted to longwords.

            i. Byte or word must be widened (using CVTxx or MOVZxx)

           ii. Immediate value cannot be used with D, G, or H float or arrays 
               or records.

         b. Reference can be used with any data type, since an ADDRESS is always
            a longword.

         c. VAX Pascal actually uses call by reference for both value and var
            parameters, and other Pascals may use call by reference for value
            parameters when the actual parameter is a structured type (array,
            etc.)  In this case, it becomes the task of the CALLED procedure to
            construct a private copy before proceeding.

            Example: VAX Pascal implementation of the example above example
                     when the parameter of foo is NOT a var parameter:

            MACRO:      .ENTRY  FOO, ^M<>
                        PUSHL   @4(AP)          ; Private copy
                        ADDL3   #1, -4(FP), B
                        ...
                        RET

                        PUSHAL  X
                        CALLS   #1, FOO

           Note: When the actual parameter is a large array, this making of a
                 local copy can consume a lot of time and space.  This is why
                 it is more efficient to make large arrays var parameters.

      4. The VAX Procedure Calling Standard sepcifies a third mechanism: CALL
         BY DESCRIPTOR.

         a. This is most often used for complex structures - e.g. character
            strings whose lengths may vary, or conformant array parameters.
            (A conformant array parameters is one which may accept actual
            parameter arrays having different bounds - e.g. in Pascal:

                procedure foo(x: array[lo..hi: integer] of real)

            This could be called with any one-dimensional array of reals as the
            actual parameter.

         b. The following is the general format of a descriptor:

                _________________________________
                | CLASS | DTYPE | Length        |
                |-------------------------------|
                |           Pointer             |
                |-------------------------------|
                  (Additional items as needed)

            i. CLASS indicates the type of descriptor - e.g. string, array, etc.

           ii. DTYPE indicates the data type of the individual elements of the
               array or string

          iii. Length indicates the overall length of a character string, or the
               length of an individual element of an array.

           iv. Pointer is the address of the actual data item.

            v. Additional items are used for arrays to give bounds information.
         
         c. Three different types of descriptors can be used for character 
            strings:

            i. Fixed-length string: the size of the string cannot be altered
               by the procedure receiving it.

               CLASS = DSC$K_CLASS_S

               DTYPE = DSC$K_DTYPE_T

           ii. Dynamic string: the size of the string can be altered by 
               allocating new space for it and altering the length and pointer
               fields in the descriptor.  (The old space should also be
               recycled.)

               CLASS = DSC$K_CLASS_D

               DTYPE = DSC$K_DTYPE_T

          iii. Variable string: the size of the string can be altered within
               a FIXED allocation.  The length field gives the MAXIMUM
               length, and the string is immediately preceeded by a word giving
               the CURRENT length.

               CLASS = DSC$K_CLASS_VS

               DTYPE = DSC$K_DTYPE_VT

               Note: pointer points to a structure that looks like this:

                        Current length (word)
                        Body of string
                        ...

         d. Array descriptors can get complex.  We will only touch on some of
            the fields:

                _________________________________
                | CLASS | DTYPE | Length        |
                |-------------------------------|
                |           Pointer             |
                |-------------------------------|
                | DIMCT | AFLAGS | (ignore)     |
                |-------------------------------|
                |           ARSIZE              |
                |-------------------------------|
                |       A0                      |
                |-------------------------------|
                |       M1                      |
                |-------------------------------|
                |       M2                      |
                |-------------------------------|
                        ...
                |-------------------------------|
                |       Mn                      |
                |-------------------------------|
                |       L1                      |
                |-------------------------------|
                |       U1                      |
                |-------------------------------|
                |       L2                      |
                |-------------------------------|
                |       U2                      |
                        ...
                |-------------------------------|
                |       Ln                      |
                |-------------------------------|
                |       Un                      |
                ---------------------------------

            i. CLASS is DSC$K_CLASS_A

           ii. DTYPE indicates the data type of the elements, and length gives
               the size of an individual element.  (The Procedure Calling
               Standard gives a table of DTYPE values for various standard
               types - e.g. DSC$K_DTYPE_L is longword integer; and length would
               be 4 for this type.)

          iii. Pointer, as always, points to the beginning of the space 
               allocated for the array.

           iv. DIMCT is the number of dimensions (e.g. 1 for a vector, 2 for
               a two-dimensional array, etc.)

            v. AFLAGS contains individual bits that indicate various features.
               For most purposes, the two bits set will be 
                <1 @ DSC$V_FL_COEFF> + <1 @ DSC$V_FL_BOUNDS>

           vi. ARSIZE is the total size of the array.

          vii. A0 is the address of element [0, 0, 0 ... ,0].  (Note that
               this may not actually lie within the space allocated for the
               array.)

         viii. M1, M2 .. are the multiplicative coefficients for each
               dimension - i.e. (Ui - Li + 1)

           ix. L1, U1, L2, U2 .. are the lower and upper bounds for each
               dimension.

            x. The address of element [i1, i2, i3 .. in] is calculated as

                A0 + ([[[I1 * M2] + I2] * M3 ... ] * Mn + In) * Length

         e. When a parameter is passed by descriptor, what goes into the
            argument list is the ADDRESS of the descriptor.

            Example: To pass a variable-length string with maximum length 16
                     whose initial value is "Hello, world" to procedure foo.
                     (Note that .ASCID cannot be used since the string is not
                     a fixed-length string.)

                DESC:   .WORD           16
                        .BYTE           DSC$K_DTYPE_VT
                        .BYTE           DSC$K_CLASS_VS
                        .ADDRESS        HELLO
                HELLO:  .WORD           12
                        .ASCII          "Hello, world"
                        .BLKB           4       ; (Remainder of 16 bytes.)


                        PUSHAB          DESC
                        CALLS           #1, FOO

         f. Some examples of call by descriptor:

            i. A routine to print a variable length string (passed by 
               descriptor) by calling the PLINE routine we have used for labs:

                .ENTRY  PSTRING, ^M<>
                MOVL    4(AP), R0               ; Address of descriptor -> R0
                MOVL    4(R0), R0               ; Pointer to string -> R0
                MOVZWL  (R0)+, R1               ; Current length -> R1;
                                                ; R0 left at body
                JSB     PLINE
                RET

           ii. A routine to read into a variable length string (passed by
               descriptor) by calling the RLINE routine we have used for
               projects:

                .ENTRY  RSTRING, ^M<>
                MOVL    4(AP), R0               ; Address of descriptor -> R0
                MOVZWL  (R0), R1                ; Max length -> R1
                MOVL    4(R0), R0               ; Pointer to string -> R0
                MOVAB   2(R0), R0               ; Address of string body -> R0
                JSB     RLINE
                MOVL    4(AP), R0               ; Address of descriptor -> R0
                MOVW    R1, @4(R0)              ; Fill in current length field
                RET

      5. An example: HANDOUT ARGUMENTS.MAR

IV. Returning Values from a Function to its Caller
-  --------- ------ ---- - -------- -- --- ------

   A. We have discussed the ideas of passing parameters into a procedure or
      function.  In the case of a function, we need some mechanism to allow
      it to return a value to its caller.

   B. On most machines, including the VAX and MIPS, the convention is to return
      a function value via a designated CPU register.  

      1. On the VAX, the Procedure Calling Standard specifies that R0 is used
         for byte, word, or longword-sized values; and R0 and R1 together are 
         used for quadword-sized values (e.g. double-precision real.)

      2. On MIPS, $2 (and $3, if needed) are used in this same way.

      3. How do we handle function values that may be larger what a register
         (or register pair) can hold - e.g. arrays?

         a. Many programming languages do not allow such results to begin with.

         b. However, some do.  A typical implementation is to pass an extra, 
            hidden reference parameter that points to an area in the caller's 
            data space set aside to receive the value.

            Example: VAX Pascal lets a function return a string as a value.
                     Such functions are called with an extra initial parameter
                     that points to a temporary created by the caller to
                     receive the result.  In addition, the function returns
                     the address of this temporary as its value in R0.

V. Preservation of Information Belonging to the Caller
-  ------------ -- ----------- --------- -- --- ------

   A. In general, we want procedures to be modular, with no unintended side
      effects.

      1. Parameter passing provides a controlled means of communication.

      2. Global variables - rightly used - can do the same.

   B. One possible set of surprise interactions we want to avoid is interaction
      through the registers.

      1. As a general rule, any register used by both a procedure and its 
         caller must be saved at the time of the call and restored to its
         original value when the procedure returns.

      2. This can be handled in one of two ways:

         a. We can require that the caller save any registers it is currently
            using just before calling the procedure.  This would allow the
            procedure to freely use any registers it needs.

            e.g. Assume that the caller is currently using R3, R4:

                PUSHL   R3
                PUSHL   R4
                CALL ---
                MOVL    (SP)+, R4
                MOVL    (SP)+, R3

         b. We could require that the procedure save and later restore any
            registers it intends to use.

            e.g. Assume that the procedure needs R2, R5:

                .ENTRY  ---
                PUSHL   R2
                PUSHL   R5
                ...
                MOVL    (SP)+,R5
                MOVL    (SP)+,R2
                RET

      3. The VAX uses the latter approach, and the CALL_ instruction provides
         hardware support for saving/restoring registers automatically.

         a. The first word of a procedure is an entry mask (which we have thus
            far been simply setting to zero.)  Each bit in the mask corresponds
            to one register that might need to be saved upon procedure entry
            and restored upon procedure exit, as follows:

                ^  ^  ^  ^  ^  ^  ^  ^  ^  ^  ^  ^  ^  ^  ^  ^  ^
                |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
                |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  save r0
                |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  save r1
                |  |  |  |  |  |  |  |  |  |  |  |  |  |  save r2
                |  |  |  |  |  |  |  |  |  |  |  |  |  save r3
                |  |  |  |  |  |  |  |  |  |  |  |  save r4
                |  |  |  |  |  |  |  |  |  |  |  save r5
                |  |  |  |  |  |  |  |  |  |  save r6
                |  |  |  |  |  |  |  |  |  save r7
                |  |  |  |  |  |  |  |  save r8
                |  |  |  |  |  |  | save r9 
                |  |  |  |  |  save r10
                |  |  |  |  save r11
                (to be discussed 
                 later)

         b. Upon procedure call, the entry mask is scanned and the specified
            registers are pushed on the stack.  In addition, AP, FP, and PC
            are ALWAYS pushed (which is why the bits that would correspond
            to these registers in the entry mask can be used for something
            else.)  A copy of the entry mask is also saved on the stack.

         c. Upon procedure return, the hardware scans the saved entry mask
            and pops the specified registers, along with AP, FP, and PC.

         d. Note that the entry mask contains bits to specify saving and
            restoring R0 and R1.  However, the convention specified by the
            Procedure Calling Standard is that these bits are never set -
            i.e. R0 and R1 are never saved and restored.  (The procedure
            may use them freely; the caller must not assume that their
            values will be preserved.)  This correlates with the usage of R0 and
            possibly R1 for the return value of functions.

         e. As we have already seen, the .ENTRY directive serves to both create
            a global label for a procedure and set up its entry mask.  MACRO
            uses the syntax ^M< registers > to specify an entry mask.

            Example:    Procedure foo uses registers r0, r3, r5, and r7

                        .ENTRY  FOO, ^M<R3, R5, R7>
        
                        -- note omission of R0

         f. We have noted that 4 bits in the entry mask are not used to
            specify registers, since the registers they would specify are
            handled in a special way.

            i. Two are used to specify the setting of the DV and IV overflow
               trap enable bits in the PSL when the procedure starts
               executing.

           ii. Two (the two nearest the middle) are required to be zero.

       4. MIPS uses a mixed approach.

          a. By convention, certain registers use the callee save convention -
             if a called procedure alters them, it must save their original
             values  upon entry and restore them upon exit.

             i. The following registers are required to be preserved across
                calls:

                $16 .. $23

                Note: These registers are often referred to by the alternate
                      names s0 .. s7 - standing for "saved register" ...

            ii. In addition, certain other registers have special uses that
                in effect require a form of callee save:

                $28 .. $31 - to be discussed below

         b. Most of the other registers are not preserved by the callee; if
            the caller needs them, it must preserve them:

         c. In either case, registers that need to be saved are pushed on the
            stack, and are later popped from the stack by the same routine that
            pushed them (caller or callee, as the case may be.)

VI. Recursion
--  ---------

   A. One especially interesting kind of procedure is a recursive one.
      Recursive procedures are interesting because several activations of
      the same procedure may be active at the same time.

      Example:  function fact(n: integer): integer;
                  begin
                    if n = 0 then fact := 1 else fact := n * fact(n-1)
                  end;

      If this function is called by fact(6), at one point there will be
      seven copies of it active: fact(6), fact(5), fact(4), ... fact(0).

   B. Of course, each activation of the procedure must have its own private
      copies of its parameters, return address, local variables, etc.

      1. The natural data structure for managing this is the stack.

      2. The conventional approach is to allocate space for local variables
         on the stack upon procedure entry.

         a. On the VAX, a special register called the frame pointer (FP) is set
            to point to the top of the stack at procedure entry by the CALL
            instruction.  As a result, anything pushed onto the stack by the
            procedure can then be referenced by NEGATIVE offsets relative to FP.

            Example: A procedure has two integer local variables i and j.

                     Upon entry:        SUBL    #8, SP

                     To reference i, use:       -8(FP)
                     To reference j, use:       -4(FP)

            Because of the way the VAX RET instruction works, anything pushed
            on the stack above FP is automatically discarded when it is
            executed.  

         b. A very similar approach is used on MIPS, except that the programmer
            must manage the frame pointer register ($30 also known as $fp)
            explicitly and must both allocate and deallocate the space
            on the stack.

VII. Call Frames
---  ---- ------

   A. We have seen that various items are pushed on the stack as part of the
      procedure call conventions - e.g. the return address from the procedure,
      registers that need to be saved, local variables, and perhap parameters.
      The net effect of all this pushing is to create a run-time data structure
      called as STACK FRAME or CALL FRAME.  The exact form this structure
      takes, and the presence of special hardware support for it, depends on
      the CPU.

   B. On the VAX, most of the work of constructing a call grame is done by the
      CALL instructions, and most of the work of destroying it is done by
      the RET instruction.  The FP register (R13) always points to the call
      frame of the currently executing procedure.  A VAX call frame always has
      the following structure:

      TRANSPARENCY: VAX Procedure Call Frame.

   C. As one would expect on a RISC, a MIPS call frame is constructed by
      a series of explicit instructions created by the programmer or compiler,
      and is destroyed by another series of explicit instructions.  One
      consequence of this is that the structure of a call frame is less
      rigidly defined.  Typically, it has the following structure:

      TRANSPARENCY: MIPS Procedure Call Frame (H & P Figure 3.12)

      [Note: the GNU compiler puts $fp at the bottom of the frame, where
       $sp points - not at the top as shown in transparency]

      Creation of a frame like this requires a protocol like the following:

      1. Procedure entry protocol:

         Subtract size needed for frame from $sp
         Save registers as necessary, including old value of $fp *
         Copy $sp to $fp

         * $ra and argument registers must be saved if procedure intends
           to call other procedures

      2. Procedure exit protocol

         Restore registers that were saved
         Add size of frame to $sp
         Return
         
   D. At this point, it might be helpful to review how MIPS uses its registers.

      TRANSPARENCY: H & P Figure 3.13

VIII. Inter-Language Calls Between Pascal and VAX MACRO
----  -------------- ----- ------- ------ --- --- -----

   A. We are now prepared to discuss how procedures written in Pascal and
      procedures written in MACRO can interface to one another.  (Similar
      principles will govern other sorts of inter-language calls.)

   B. To call a MACRO procedure from Pascal, include an external declaration
      in the Pascal program, using appropriate foreign-mechanism specifiers
      to specify how arguments are to be passed.  (The MACRO routine is
      "the boss" on this.)

      Example: The following is the declaration for a function that takes 
               an array passed by descriptor, two integers passed by value,
               and two integers passed by reference, returning a boolean:

function MOUSE(%descr Maze: array[lr..hr: integer; lc..hc: integer] of char;
               %immed StartRow, StartCol: integer;
               %ref CheeseRow, CheeseCol: integer): boolean; external;

   C. To call a Pascal procedure from MACRO, preceed the procedure declaration
      in the Pascal program by [global], and note that the MACRO caller must
      obey Pascal's calling conventions - e.g.

      1. Most parameters are passed by reference.

      2. Conformant arrays and strings are passed by descriptor.

      Example: The following is the declaration for a procedure that takes
               a conformant array parameter (passed by descriptor) and
               two integers (passed by reference).

[global]
procedure Draw_Mouse(var Maze: array[lr..hr: integer; lc..hc: integer] of char;
                     CurrentRow, CurrentCol: integer);

Copyright ©1999 - Russell C. Bjork