plot
procedure that graphs some generic math function passed as a parameter to plot
.procedure DoCall(procedure x); begin x; end;
The statement DoCall(xyz);
calls DoCall
that, in turn, calls procedure xyz
.
Passing a procedure or function as a parameter may seem like an easy task - just pass the address of the function or procedure as the following example demonstrates:
procedure PassMe; begin Writeln('PassMe was called'); end; procedure CallPassMe(procedure x); begin x; end; begin {main} CallPassMe(PassMe); end.
The 80x86 code to implement the above could look like the following:
PassMe proc near print byte "PassMe was called",cr,lf,0 ret PassMe endp CallPassMe proc near push bp mov bp, sp call word ptr [bp+4] pop bp ret 2 CallPassMe endp Main proc near lea bx, PassMe ;Pass address of PassMe to push bx ; CallPassMe call CallPassMe ExitPgm Main endp
For an example as simple as the one above, this technique works fine. However, it does not always work properly if PassMe needs to access non-local variables. The following Pascal code demonstrates the problem that could occur:
program main; procedure dummy; begin end; procedure Recurse1(i:integer; procedure x); procedure Print; begin writeln(i); end; procedure Recurse2(j:integer; procedure y); begin if (j=1) then y else if (j=5) then Recurse1(j-1, Print) else Recurse1(j-1, y); end; begin {Recurse1} Recurse2(i, x); end; begin {Main} Recurse1(5,dummy); end.
This code produces the following call sequence:
Recurse1(5,dummy)
-->Recurse2(5,dummy)
-->Recurse1(4,Print)
-->Recurse2(4,Print)
-->Recurse1(3,Print)
-->Recurse2(3,Print)
-->Recurse1(2,Print)
-->Recurse2(2,Print)
-->Recurse1(1,Print)
-->Recurse2(1,Print)
-->
Print
will print the value of Recurse1
's i
variable to the standard output. However, there are several activation records present on the stack that raises the obvious question, "which copy of i
does Print
display?" Without giving it much thought, you might conclude that it should print the value "1" since Recurse2
calls Print
when Recurse1
's value for i
is one. Note, though, that when Recurse2
passes the address of Print
to Recurse1
, i
's value is four. Pascal, like most block structured languages, will use the value of i
at the point Recurse2
passes the address of Print
to Recurse1
. Hence, the code above should print the value four, not the value one.
This creates a difficult implementation problem. After all, Print
cannot simply access the display to gain access to the global variable i
- the display's entry for Recurse1
points at the latest copy of Recurse1
's activation record, not the entry containing the value four which is what you want.
The most common solution in systems using a display is to make a local copy of each display whenever calling a procedure or function. When passing a procedure or function as a parameter, the calling code copies the display along with the address of the procedure or function. This is why Intel's enter
instruction makes a copy of the display when building the activation record.
If you are passing procedures and functions as parameters, you may want to consider using static links rather than a display. When using a static link you need only pass a single pointer (the static link) along with the routine's address. Of course, it is more work to access non-local variables, but you don't have to copy the display on every call, which is quite expensive.
The following 80x86 code provides the implementation of the above code using static links:
wp textequ <word ptr> Dummy proc near ret Dummy endp ; PrintIt; (Use the name PrintIt to avoid conflict). ; ; stack: ; ; bp+4: static link. PrintIt proc near push bp mov bp, sp mov bx, [bp+4] ;Get static link mov ax, ss:[bx-10] ;Get i's value. puti pop bp ret 2 PrintIt endp ; Recurse1(i:integer; procedure x); ; ; stack: ; ; bp+10: i ; bp+8: x's static link ; bp+6: x's address Recurse1 proc near push bp mov bp, sp push wp [bp+10] ;Push value of i onto stack. push wp [bp+8] ;Push x's static link. push wp [bp+6] ;Push x's address. push bp ;Push Recurse1's static link. call Recurse1 pop bp ret 6 Recurse1 endp ; Recurse2(i:integer; procedure y); ; ; stack: ; ; bp+10: j ; bp+8: y's static link. ; bp+6: y's address. ; bp+4: Recurse2's static link. Recurse2 proc near push bp mov bp, sp cmp wp [bp+10], 1 ;Is j=1? jne TryJeq5 push [bp+8] ;y's static link. call wp [bp+6] ;Call y. jmp R2Done TryJeq5: cmp wp [bp+10], 5 ;Is j=5? jne Call1 mov ax, [bp+10] dec ax push ax push [bp+4] ;Push static link to R1. lea ax, PrintIt ;Push address of print. push ax call Recurse1 jmp R2Done Call1: mov ax, [bp+10] dec ax push ax push [bp+8] ;Pass along existing push [bp+6] ; address and link. call Recurse1 R2Done: pop bp ret 6 Recurse1 endp main proc push bp mov bp, sp mov ax, 5 ;Push first parameter. push ax push bp ;Dummy static link. lea ax, Dummy ;Push address of dummy. push ax call Recurse1 pop bp ExitPgm main endp
There are several ways to improve this code. Of course, this particular program doesn't really need to maintain a display or static list because only PrintIt
accesses non-local variables; however, ignore that fact for the time being and pretend it does. Since you know that PrintIt
only accesses variables at one particular lex level, and the program only calls PrintIt
indirectly, you can pass a pointer to the appropriate activation record; this is what the above code does, although it maintains full static links as well. Compilers must always assume the worst case and often generate inefficient code. If you study your particular needs, however, you may be able to improve the efficiency of your code by avoiding much of the overhead of maintaining static lists or copying displays.
Keep in mind that thunks are special cases of functions that you call indirectly. They suffer from the same problems and drawbacks as procedure and function parameters with respect to accessing non-local variables. If such routines access non-local variables (and thunks almost always will) then you must exercise care when calling such routines. Fortunately, thunks never cause indirect recursion (which is responsible for the crazy problems in the Recurse1
/ Recurse2
example) so you can use the display to access any non-local variables appearing within the thunk.