[Chaper Eleven][Previous] [Next]
[Art of Assembly][Randall
Hyde]
Art of Assembly: Chaper Eleven
- 11.5 - Parameters
- 11.5.1 - Pass by Value
- 11.5.2 - Pass by Reference
- 11.5.3 - Pass by Value-Returned
- 11.5.4 - Pass by Result
- 11.5.5 - Pass by Name
- 11.5.6 - Pass by Lazy-Evaluation
11.5 Parameters
Although there is a large class of procedures that are totally self-contained,
most procedures require some input data and return some data to the caller.
Parameters are values that you pass to and from a procedure. There are many
facets to parameters. Questions concerning parameters include:
- where is the data coming from?
- how do you pass and return data?
- what is the amount of data to pass?
There are six major mechanisms for passing data to and from a procedure,
they are
- pass by value,
- pass by reference,
- pass by value/returned,
- pass by result, and
- pass by name.
- pass by lazy evaluation
You also have to worry about where you can pass parameters. Common places
are
- in registers,
- in global memory locations,
- on the stack,
- in the code stream, or
- in a parameter block referenced via a pointer.
Finally, the amount of data has a direct bearing on where and how to pass
it. The following sections take up these issues.
11.5.1 Pass by Value
A parameter passed by value is just that - the caller passes a value
to the procedure. Pass by value parameters are input only parameters. That
is, you can pass them to a procedure but the procedure cannot return them.
In HLLs, like Pascal, the idea of a pass by value parameter being an input
only parameter makes a lot of sense. Given the Pascal procedure call:
CallProc(I);
If you pass I
by value, the CallProc
does not
change the value of I
, regardless of what happens to the parameter
inside CallProc
.
Since you must pass a copy of the data to the procedure, you should only
use this method for passing small objects like bytes, words, and double
words. Passing arrays and strings by value is very inefficient (since you
must create and pass a copy of the structure to the procedure).
11.5.2 Pass by Reference
To pass a parameter by reference, you must pass the address of a variable
rather than its value. In other words, you must pass a pointer to the data.
The procedure must dereference this pointer to access the data. Passing
parameters by reference is useful when you must modify the actual parameter
or when you pass large data structures between procedures.
Passing parameters by reference can produce some peculiar results. The following
Pascal procedure provides an example of one problem you might encounter:
program main(input,output);
var m:integer;
procedure bletch(var i,j:integer);
begin
i := i+2;
j := j-i;
writeln(i,' ',j);
end;
.
.
.
begin {main}
m := 5;
bletch(m,m);
end.
This particular code sequence will print "00" regardless of m
's
value. This is because the parameters i
and j
are pointers to the actual data and they both point at the same object.
Therefore, the statement j:=j-i;
always produces zero since
i
and j
refer to the same variable.
Pass by reference is usually less efficient than pass by value. You must
dereference all pass by reference parameters on each access; this is slower
than simply using a value. However, when passing a large data structure,
pass by reference is faster because you do not have to copy a large data
structure before calling the procedure.
11.5.3 Pass by Value-Returned
Pass by value-returned (also known as value-result) combines features
from both the pass by value and pass by reference mechanisms. You pass a
value-returned parameter by address, just like pass by reference parameters.
However, upon entry, the procedure makes a temporary copy of this parameter
and uses the copy while the procedure is executing. When the procedure finishes,
it copies the temporary copy back to the original parameter.
The Pascal code presented in the previous section would operate properly
with pass by value-returned parameters. Of course, when Bletch
returns to the calling code, m
could only contain one of the
two values, but while Bletch
is executing, i
and
j
would contain distinct values.
In some instances, pass by value-returned is more efficient than pass by
reference, in others it is less efficient. If a procedure only references
the parameter a couple of times, copying the parameter's data is expensive.
On the other hand, if the procedure uses this parameter often, the procedure
amortizes the fixed cost of copying the data over many inexpensive accesses
to the local copy.
11.5.4 Pass by Result
Pass by result is almost identical to pass by value-returned. You pass
in a pointer to the desired object and the procedure uses a local copy of
the variable and then stores the result through the pointer when returning.
The only difference between pass by value-returned and pass by result is
that when passing parameters by result you do not copy the data upon entering
the procedure. Pass by result parameters are for returning values, not passing
data to the procedure. Therefore, pass by result is slightly more efficient
than pass by value-returned since you save the cost of copying the data
into the local variable.
11.5.5 Pass by Name
Pass by name is the parameter passing mechanism used by macros, text
equates, and the #define
macro facility in the C programming
language. This parameter passing mechanism uses textual substitution on
the parameters. Consider the following MASM macro:
PassByName macro Parameter1, Parameter2
mov ax, Parameter1
add ax, Parameter2
endm
If you have a macro invocation of the form:
PassByName bx, I
MASM emits the following code, substituting bx for Parameter1 and I for
Parameter2:
mov ax, bx
add ax, I
Some high level languages, such as ALGOL-68 and Panacea, support pass by
name parameters. However, implementing pass by name using textual substitution
in a compiled language (like ALGOL-68) is very difficult and inefficient.
Basically, you would have to recompile a function everytime you call it.
So compiled languages that support pass by name parameters generally use
a different technique to pass those parameters. Consider the following Panacea
procedure:
PassByName: procedure(name item:integer; var index:integer);
begin PassByName;
foreach index in 0..10 do
item := 0;
endfor;
end PassByName;
Assume you call this routine with the statement PassByName(A[i], i);
where A
is an array of integers having (at least) the
elements A[0]..A[10]. Were you to substitute the pass by name parameter
item you would obtain the following code:
begin PassByName;
foreach index in 0..10 do
A[I] := 0; (* Note that index and I are aliases *)
endfor;
end PassByName;
This code zeros out elements 0..10 of array A
.
High level languages like ALGOL-68 and Panacea compile pass by name parameters
into functions that return the address of a given parameter. So in one respect,
pass by name parameters are similar to pass by reference parameters insofar
as you pass the address of an object. The major difference is that with
pass by reference you compute the address of an object before calling a
subroutine; with pass by name the subroutine itself calls some function
to compute the address of the parameter.
So what difference does this make? Well, reconsider the code above. Had
you passed A[I]
by reference rather than by name, the calling
code would compute the address of A[I]
just before the call
and passed in this address. Inside the PassByName
procedure
the variable item
would have always referred to a single address,
not an address that changes along with I
. With pass by name
parameters, item
is really a function that computes the address
of the parameter into which the procedure stores the value zero. Such a
function might look like the following:
ItemThunk proc near
mov bx, I
shl bx, 1
lea bx, A[bx]
ret
ItemThunk endp
The compiled code inside the PassByName procedure might look something like
the following:
; item := 0;
call ItemThunk
mov word ptr [bx], 0
Thunk is the historical term for these functions that compute the address
of a pass by name parameter. It is worth noting that most HLLs supporting
pass by name parameters do not call thunks directly (like the call
above). Generally, the caller passes the address of a thunk and the subroutine
calls the thunk indirectly. This allows the same sequence of instructions
to call several different thunks (corresponding to different calls to the
subroutine).
11.5.6 Pass by Lazy-Evaluation
Pass by name is similar to pass by reference insofar as the procedure
accesses the parameter using the address of the parameter. The primary difference
between the two is that a caller directly passes the address on the stack
when passing by reference, it passes the address of a function that computes
the parameter's address when passing a parameter by name. The pass by lazy
evaluation mechanism shares this same relationship with pass by value parameters
- the caller passes the address of a function that computes the parameter's
value if the first access to that parameter is a read operation.
Pass by lazy evaluation is a useful parameter passing technique if the cost
of computing the parameter value is very high and the procedure may not
use the value. Consider the following Panacea procedure header:
PassByEval: procedure(eval a:integer; eval b:integer; eval c:integer);
When you call the PassByEval
function it does not evaluate
the actual parameters and pass their values to the procedure. Instead, the
compiler generates thunks that will compute the value of the parameter at
most one time. If the first access to an eval
parameter is
a read, the thunk will compute the parameter's value and store that into
a local variable. It will also set a flag so that all future accesses will
not call the thunk (since it has already computed the parameter's value).
If the first access to an eval
parameter is a write, then the
code sets the flag and future accesses within the same procedure activation
will use the written value and ignore the thunk.
Consider the PassByEval
procedure above. Suppose it takes several
minutes to compute the values for the a, b,
and c
parameters (these could be, for example, three different possible paths
in a Chess game). Perhaps the PassByEval
procedure only uses
the value of one of these parameters. Without pass by lazy evaluation, the
calling code would have to spend the time to compute all three parameters
even though the procedure will only use one of the values. With pass by
lazy evaluation, however, the procedure will only spend the time computing
the value of the one parameter it needs. Lazy evaluation is a common technique
artificial intelligence (AI) and operating systems use to improve performance.
- 11.5 - Parameters
- 11.5.1 - Pass by Value
- 11.5.2 - Pass by Reference
- 11.5.3 - Pass by Value-Returned
- 11.5.4 - Pass by Result
- 11.5.5 - Pass by Name
- 11.5.6 - Pass by Lazy-Evaluation
Art of Assembly: Chaper Eleven - 27 SEP 1996
[Chapter Eleven][Previous] [Next]
[Art of Assembly][Randall
Hyde]