The 80x86 call
instruction does two things. First, it pushes the address of the instruction immediately following the call
onto the stack; then it transfers control to the address of the specified procedure. The value that call
pushes onto the stack is known as the return address. When the procedure wants to return to the caller and continue execution with the first statement following the call
instruction, the procedure simply pops the return address off the stack and jumps (indirectly) to that address. Most procedures return to their caller by executing a ret
(return) instruction. The ret
instruction pops a return address off the stack and transfers control indirectly to the address it pops off the stack.
By default, the HLA compiler automatically places a ret
instruction (along with a few other instructions) at the end of each HLA procedure you write. This is why you haven't had to explicitly use the ret
instruction up to this point. To disable the default code generation in an HLA procedure, specify the following options when declaring your procedures:
procedureProcName
; @noframe; @nodisplay; beginProcName
; . . . endProcName
;
The @noframe
and @nodisplay
clauses are examples of procedure options. HLA procedures support several such options, including @returns
, @noframe
, @nodisplay
, and @noalignstack
. You'll see the purpose of @noalignstack
and a couple of other procedure options in Section 5.14. These procedure options may appear in any order following the procedure name (and parameters, if any). Note that @noframe
and @nodisplay
(as well as @noalignstack
) may appear only in an actual procedure declaration. You cannot specify these options in a forward declaration.
The @noframe
option tells HLA that you don't want the compiler to automatically generate entry and exit code for the procedure. This tells HLA not to automatically generate the ret
instruction (along with several other instructions).
The @nodisplay
option tells HLA that it should not allocate storage in procedure's local variable area for a display. The display is a mechanism you use to access nonlocal var
objects in a procedure. Therefore, a display is necessary only if you nest procedures in your programs. This book will not consider the display or nested procedures; for more details on the display and nested procedures see the appropriate chapter in the electronic edition appearing at http://www.artofasm.com/ or http://webster.cs.ucr.edu/, or check out the HLA reference manual. Until then, you can safely specify the @nodisplay
option on all your procedures. Indeed, for all of the procedures appearing in this chapter up to this point, specifying the @nodisplay
option makes a lot of sense because none of those procedures actually use the display. Procedures that have the @nodisplay
option are a tiny bit faster and a tiny bit shorter than those procedures that do not specify this option.
The following is an example of the minimal procedure:
procedure minimal; @nodisplay; @noframe; @noalignstack; begin minimal; ret(); end minimal;
If you call this procedure with the call
instruction, minimal
will simply pop the return address off the stack and return back to the caller. You should note that a ret
instruction is absolutely necessary when you specify the @noframe
procedure option.[82] If you fail to put the ret
instruction in the procedure, the program will not return to the caller upon encountering the end minimal;
statement. Instead, the program will fall through to whatever code happens to follow the procedure in memory. The example program in Example 5-10 demonstrates this problem.
Example 5-10. Effect of a missing ret
instruction in a procedure
program missingRET; #include( "stdlib.hhf" ); // This first procedure has the @noframe // option but does not have a ret instruction. procedure firstProc; @noframe; @nodisplay; begin firstProc; stdout.put( "Inside firstProc" nl ); end firstProc; // Because the procedure above does not have a // ret instruction, it will "fall through" to // the following instruction. Note that there // is no call to this procedure anywhere in // this program. procedure secondProc; @noframe; @nodisplay; begin secondProc; stdout.put( "Inside secondProc" nl ); ret(); end secondProc; begin missingRET; // Call the procedure that doesn't have // a ret instruction. call firstProc; end missingRET;
Although this behavior might be desirable in certain rare circumstances, it usually represents a defect in most programs. Therefore, if you specify the @noframe
option, always remember to explicitly return from the procedure using the ret
instruction.
[82] Strictly speaking, this isn't true. But some mechanism that pops the return address off the stack and jumps to the return address is necessary in the procedure's body.