The Art of
ASSEMBLY LANGUAGE PROGRAMMING

Chapter Twelve (Part 3)

Table of Content

Chapter Twelve (Part 5) 

CHAPTER TWELVE:
PROCEDURES: ADVANCED TOPICS (Part 4)
12.2 - Passing Variables at Different Lex Levels as Parameters.
12.2.1 - Passing Parameters by Value in a Block Structured Language
12.2.2 - Passing Parameters by Reference, Result, and Value-Result in a Block Structured Language
12.2.3 - Passing Parameters by Name and Lazy-Evaluation in a Block Structured Language
12.3 - Passing Parameters as Parameters to Another Procedure
12.3.1 - Passing Reference Parameters to Other Procedures
12.3.2 - Passing Value-Result and Result Parameters as Parameters
12.3.3 - Passing Name Parameters to Other Procedures
12.3.4 - Passing Lazy Evaluation Parameters as Parameters
12.3.5 - Parameter Passing Summary
12.2 Passing Variables at Different Lex Levels as Parameters.

Accessing variables at different lex levels in a block structured program introduces several complexities to a program. The previous section introduced you to the complexity of non-local variable access. This problem gets even worse when you try to pass such variables as parameters to another program unit. The following subsections discuss strategies for each of the major parameter passing mechanisms.

For the purposes of discussion, the following sections will assume that "local" refers to variables in the current activation record, "global" refers to variables in the data segment, and "intermediate" refers to variables in some activation record other than the current activation record. Note that the following sections will not assume that ds is equal to ss. These sections will also pass all parameters on the stack. You can easily modify the details to pass these parameters elsewhere.

12.2.1 Passing Parameters by Value in a Block Structured Language

Passing value parameters to a program unit is no more difficult than accessing the corresponding variables; all you need do is push the value on the stack before calling the associated procedure.

To pass a global variable by value to another procedure, you could use code like the following:

                push    GlobalVar       ;Assume "GlobalVar" is in DSEG.
                call    Procedure

To pass a local variable by value to another procedure, you could use the following code[6]:

                push    [bp-2]          ;Local variable in current activation
                call    Procedure       ; record.

To pass an intermediate variable as a value parameter, you must first locate that intermediate variable's activation record and then push its value onto the stack. The exact mechanism you use depends on whether you are using static links or a display to keep track of the intermediate variable's activation records. If using static links, you might use code like the following to pass a variable from two lex levels up from the current procedure:

                mov     bx, [bp+4]      ;Assume S.L. is at offset 4.
                mov     bx, ss:[bx+4]   ;Traverse two static links
                push    ss:[bx-2]       ;Push variables value.
                call    Procedure

Passing an intermediate variable by value when you are using a display is somewhat easier. You could use code like the following to pass an intermediate variable from lex level one:

                mov     bx, Display[1*2]        ;Get Display[1] entry.
                push    ss:[bx-2]               ;Push the variable's value.
                call    Procedure
12.2.2 Passing Parameters by Reference, Result, and Value-Result in a Block Structured Language

The pass by reference, result, and value-result parameter mechanisms generally pass the address of parameter on the stack[7]. If global variables reside in the data segment, activation records all exist in the stack segment, and dsss, then you must pass far pointers to access all possible variables[8].

To pass a far pointer you must push a segment value followed by an offset value on the stack. For global variables, the segment value is found in the ds register; for non-global values, ss contains the segment value. To compute the offset portion of the address you would normally use the lea instruction. The following code sequence passes a global variable by reference:

                push    ds                      ;Push segment adrs first.
                lea     ax, GlobalVar           ;Compute offset.
                push    ax                      ;Push offset of GlobalVar
                call    Procedure

Global variables are a special case because the assembler can compute their run-time offsets at assembly time. Therefore, for scalar global variables only, we can shorten the code sequence above to

                push    ds                      ;Push segment adrs.
                push    offset GlobalVar        ;Push offset portion.
                call    Procedure

To pass a local variable by reference you code must first push ss's value onto the stack and then push the local variable's offset. This offset is the variable's offset within the stack segment, not the offset within the activation record! The following code passes the address of a local variable by reference:

                push    ss                      ;Push segment address.
                lea     ax, [bp-2]              ;Compute offset of local
                push    ax                      ; variable and push it.
                call    Procedure

To pass an intermediate variable by reference you must first locate the activation record containing the variable so you can compute the effective address into the stack segment. When using static links, the code to pass the parameter's address might look like the following:

                push    ss              ;Push segment portion.
                mov     bx, [bp+4]      ;Assume S.L. is at offset 4.
                mov     bx, ss:[bx+4]   ;Traverse two static links
                lea     ax, [bx-2]      ;Compute effective address
                push    ax              ;Push offset portion.
                call    Procedure

When using a display, the calling sequence might look like the following:

                push    ss                      ;Push segment portion.
                mov     bx, Display[1*2]        ;Get Display[1] entry.
                lea     ax, [bx-2]              ;Get the variable's offset
                push    ax                      ; and push it.
                call    Procedure

As you may recall from the previous chapter, there is a second way to pass a parameter by value-result. You can push the value onto the stack and then, when the procedure returns, pop this value off the stack and store it back into the variable from whence it came. This is just a special case of the pass by value mechanism described in the previous section.

12.2.3 Passing Parameters by Name and Lazy-Evaluation in a Block Structured Language

Since you pass the address of a thunk when passing parameters by name or by lazy-evaluation, the presence of global, intermediate, and local variables does not affect the calling sequence to the procedure. Instead, the thunk has to deal with the differing locations of these variables. The following examples will present thunks for pass by name, you can easily modify these thunks for lazy-evaluation parameters.

The biggest problem a thunk has is locating the activation record containing the variable whose address it returns. In the last chapter, this wasn't too much of a problem since variables existed either in the current activation record or in the global data space. In the presence of intermediate variables, this task becomes somewhat more complex. The easiest solution is to pass two pointers when passing a variable by name. The first pointer should be the address of the thunk, the second pointer should be the offset of the activation record containing the variable the thunk must access[9]. When the procedure calls the thunk, it must pass this activation record offset as a parameter to the thunk. Consider the following Panacea procedures:

TestThunk:procedure(name item:integer; var j:integer);
begin TestThunk;

        for j in 0..9 do item := 0;

end TestThunk;

CallThunk:procedure;
var
        A: array[0..9] : integer;
        I: integer;
endvar;
begin CallThunk;

        TestThunk(A[I], I);

end CallThunk;

The assembly code for the above might look like the following:

; TestThunk AR:
;
;       BP+10-  Address of thunk
;       BP+8-   Ptr to AR for Item and J parameters (must be in the same AR).
;       BP+4-   Far ptr to J.

TestThunk       proc    near
                push    bp
                mov     bp, sp
                push    ax
                push    bx
                push    es

                les     bx, [bp+4]              ;Get ptr to J.
                mov     word ptr es:[bx], 0     ;J := 0;
ForLoop:        cmp     word ptr es:[bx], 9     ;Is J > 9?
                ja      ForDone
                push    [bp+8]                  ;Push AR passed by caller.
                call    word ptr [bp+10]        ;Call the thunk.
                mov     word ptr ss:[bx], 0     ;Thunk returns adrs in BX.
                les     bx, [bp+4]              ;Get ptr to J.
                inc     word ptr es:[bx]        ;Add one to it.
                jmp     ForLoop

ForDone:        pop     es
                pop     bx
                pop     ax
                pop     bp
                ret     8
TestThunk       endp


CallThunk       proc    near
                push    bp
                mov     bp, sp
                sub     sp, 12          ;Make room for locals.

                jmp     OverThunk
Thunk           proc
                push    bp
                mov     bp, sp
                mov     bp, [bp+4]      ;Get AR address.
                mov     ax, [bp-22]     ;Get I's value.
                add     ax, ax          ;Double, since A is a word array.
                add     bx, -20         ;Offset to start of A
                add     bx, ax          ;Compute address of A[I] and
                pop     bp              ; return it in BX.
                ret     2               ;Remove parameter from stack.
Thunk           endp

OverThunk:      push    offset Thunk    ;Push (near) address of thunk
                push    bp              ;Push ptr to A/I's AR for thunk
                push    ss              ;Push address of I onto stack.
                lea     ax, [bp-22]     ; Offset portion of I.
                push    ax
                call    TestThunk
                mov     sp, bp
                ret
CallThunk       endp
12.3 Passing Parameters as Parameters to Another Procedure

When a procedure passes one of its own parameters as a parameter to another procedure, certain problems develop that do not exist when passing variables as parameters. Indeed, in some (rare) cases it is not logically possible to pass some parameter types to some other procedure. This section deals with the problems of passing one procedure's parameters to another procedure.

Pass by value parameters are essentially no different than local variables. All the techniques in the previous sections apply to pass by value parameters. The following sections deal with the cases where the calling procedure is passing a parameter passed to it by reference, value-result, result, name, and lazy evaluation.

12.3.1 Passing Reference Parameters to Other Procedures

Passing a reference parameter though to another procedure is where the complexity begins. Consider the following (pseudo) Pascal procedure skeleton:

procedure HasRef(var refparm:integer);

        procedure ToProc(???? parm:integer);
        begin
          .
          .
          .
        end;

begin {HasRef}
         .
         .
         .
        ToProc(refParm);
         .
         .
         .
end;

The "????" in the ToProc parameter list indicates that we will fill in the appropriate parameter passing mechanism as the discussion warrants.

If ToProc expects a pass by value parameter (i.e., ???? is just an empty string), then HasRef needs to fetch the value of the refparm parameter and pass this value to ToProc. The following code accomplishes this[10]:

                les     bx, [bp+4]      ;Fetch address of refparm
                push    es:[bx]         ;Push integer pointed at by refparm
                call    ToProc

To pass a reference parameter by reference, value-result, or result parameter is easy - just copy the caller's parameter as-is onto the stack. That is, if the parm parameter in ToProc above is a reference parameter, a value-result parameter, or a result parameter, you would use the following calling sequence:

                push    [bp+6]          ;Push segment portion of ref parm.
                push    [bp+4]          ;Push offset portion of ref parm.
                call    ToProc

To pass a reference parameter by name is fairly easy. Just write a thunk that grabs the reference parameter's address and returns this value. In the example above, the call to ToProc might look like the following:

                jmp     SkipThunk
Thunk0          proc    near
                les     bx, [bp+4]      ;Assume BP points at HasRef's AR.
                ret
Thunk0          endp

SkipThunk:      push    offset Thunk0   ;Address of thunk.
                push    bp              ;AR containing thunk's vars.
                call    ToProc

Inside ToProc, a reference to the parameter might look like the following:

                push    bp              ;Save our AR ptr.
                mov     bp, [bp+4]      ;Ptr to Parm's AR.
                call    near ptr [bp+6] ;Call the thunk.
                pop     bp              ;Retrieve our AR ptr.
                mov     ax, es:[bx]     ;Access variable.
                 .      
                 .
                 .

To pass a reference parameter by lazy evaluation is very similar to passing it by name. The only difference (in ToProc's calling sequence) is that the thunk must return the value of the variable rather than its address. You can easily accomplish this with the following thunk:

Thunk1          proc    near
                push    es
                push    bx
                les     bx, [bp+4]      ;Assume BP points at HasRef's AR.
                mov     ax, es:[bx]     ;Return value of ref parm in ax.
                pop     bx
                pop     es
                ret
Thunk1          endp
12.3.2 Passing Value-Result and Result Parameters as Parameters

Assuming you've created a local variable that holds the value of a value-result or result parameter, passing one of these parameters to another procedure is no different than passing value parameters to other code. Once a procedure makes a local copy of the value-result parameter or allocates storage for a result parameter, you can treat that variable just like a value parameter or a local variable with respect to passing it on to other procedures.

Of course, it doesn't make sense to use the value of a result parameter until you've stored a value into that parameter's local storage. Therefore, take care when passing result parameters to other procedures that you've initialized a result parameter before using its value.

12.3.3 Passing Name Parameters to Other Procedures

Since a pass by name parameter's thunk returns the address of a parameter, passing a name parameter to another procedure is very similar to passing a reference parameter to another procedure. The primary differences occur when passing the parameter on as a name parameter.

When passing a name parameter as a value parameter, you first call the thunk, dereference the address the thunk returns, and then pass the value to the new procedure. The following code demonstrates such a call when the thunk returns the variable's address in es:bx (assume pass by name parameter's AR pointer is at address bp+4 and the pointer to the thunk is at address bp+6):

                push    bp              ;Save our AR ptr.
                mov     bp, [bp+4]      ;Ptr to Parm's AR.
                call    near ptr [bp+6] ;Call the thunk.
                push    word ptr es:[bx];Push parameter's value.
                pop     bp              ;Retrieve our AR ptr.
                call    ToProc          ;Call the procedure.
                 .      
                 .
                 .

Passing a name parameter to another procedure by reference is very easy. All you have to do is push the address the thunk returns onto the stack. The following code, that is very similar to the code above, accomplishes this:

                push    bp              ;Save our AR ptr.
                mov     bp, [bp+4]      ;Ptr to Parm's AR.
                call    near ptr [bp+6] ;Call the thunk.
                pop     bp              ;Retrieve our AR ptr.
                push    es              ;Push seg portion of adrs.
                push    bx              ;Push offset portion of adrs.
                call    ToProc          ;Call the procedure.
                 .      
                 .
                 .

Passing a name parameter to another procedure as a pass by name parameter is very easy; all you need to do is pass the thunk (and associated pointers) on to the new procedure. The following code accomplishes this:

                push    [bp+6]          ;Pass Thunk's address.
                push    [bp+4]          ;Pass adrs of Thunk's AR.
                call    ToProc

To pass a name parameter to another procedure by lazy evaluation, you need to create a thunk for the lazy-evaluation parameter that calls the pass by name parameter's thunk, dereferences the pointer, and then returns this value. The implementation is left as a programming project.

12.3.4 Passing Lazy Evaluation Parameters as Parameters

Lazy evaluation parameters typically consist of three components: the address of a thunk, a location to hold the value the thunk returns, and a boolean variable that determines whether the procedure must call the thunk to get the parameter's value or if it can simply use the value previously returned by the thunk (see the exercises in the previous chapter to see how to implement lazy evaluation parameters). When passing a parameter by lazy evaluation to another procedure, the calling code must first check the boolean variable to see if the value field is valid. If not, the code must first call the thunk to get this value. If the boolean field is true, the calling code can simply use the data in the value field. In either case, once the value field has data, passing this data on to another procedure is no different than passing a local variable or a value parameter to another procedure.

12.3.5 Parameter Passing Summary

Passing Parameters as Parameters to Another Procedure
  Pass as Value Pass as Reference Pass as Value-Result Pass as Result Pass as Name Pass as Lazy Evaluation
Value Pass the value Pass address of the value parameter Pass address of the value parameter Pass address of the value parameter Create a thunk that returns the address of the value parameter Create a thunk that returns the value
Reference Dereference parameter and pass the value it points at Pass the address (value of the reference parameter) Pass the address (value of the reference parameter) Pass the address (value of the reference parameter) Create a thunk that passes the address (value of the reference parameter) Create a thunk that deferences the reference parameter and returns its value
Value-Result Pass the local value as the value parameter Pass the address of the local value as the parameter Pass the address of the local value as the parameter Pass the address of the local value as the parameter Create a thunk that returns the address of the local value of the value-result parameter Create a thunk that returns the value in the local value of the value-result parameter
Result Pass the local value as the value parameter Pass the address of the local value as the parameter Pass the address of the local value as the parameter Pass the address of the local value as the parameter Create a thunk that returns the address of the local value of the result parameter Create a thunk that returns the value in the local value of the result parameter
Name Call the thunk, dereference the pointer, and pass the value at the address the thunk returns Call the thunk and pass the address it returns as the parameter Call the thunk and pass the address it returns as the parameter Call the thunk and pass the address it returns as the parameter Pass the address of the thunk and any other values associated with the name parameter Write a thunk that calls the name parameter's thunk, dereferences the address it returns, and then returns the value at that address
Lazy

Evaluation
If necessary, call the thunk to obtain the Lazy Eval parameter's value.

Pass the local value as the value parameter
If necessary, call the thunk to obtain the Lazy Eval parameter's value.

Pass the address of the local value as the parameter
If necessary, call the thunk to obtain the Lazy Eval parameter's value.

Pass the address of the local value as the parameter
If necessary, call the thunk to obtain the Lazy Eval parameter's value.

Pass the address of the local value as the parameter
If necessary, call the thunk to obtain the Lazy Eval parameter's value.

Create a thunk that returns the address of the Lazy Eval's value field
Create a thunk that checks the boolean field of the caller's Lazy Eval parameter. It should call the corresponding thunk if this variable is false. It should set the boolean field to true and then return the data in the value field

[6] The non-global examples all assume the variable is at offset -2 in their activation record. Change this as appropriate in your code.

[7] As you may recall, pass by reference, value-result, and result all use the same calling sequence. The differences lie in the procedures themselves.

[8] You can use near pointers if ds=ss or if you keep global variables in the main program's activation record in the stack segment.

[9] Actually, you may need to pass several pointers to activation records. For example, if you pass the variable "A[i,j,k]" by name and A, i, j, and k are all in different activation records, you will need to pass pointers to each activation record. We will ignore this problem here.

[10] The examples in this section all assume the use of a display. If you are using static links, be sure to adjust all the offsets and the code to allow for the static link that the caller must push immediately before a call.

Chapter Twelve (Part 3)

Table of Content

Chapter Twelve (Part 5) 

Chapter Twelve: Procedures: Advanced Topics (Part 4)
27 SEP 1996