|
Table of Content | Chapter Twelve (Part 5) |
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
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.
|
Table of Content | Chapter Twelve (Part 5) |
Chapter Twelve: Procedures: Advanced
Topics (Part 4)
27 SEP 1996