The Art of
ASSEMBLY LANGUAGE PROGRAMMING

Chapter Eleven (Part 2)

Table of Content

Chapter Eleven (Part 4) 

CHAPTER ELEVEN:
PROCEDURES AND FUNCTIONS (Part 3)
11.5.7 - Passing Parameters in Registers
11.5.8 - Passing Parameters in Global Variables

11.5.7 Passing Parameters in Registers

Having touched on how to pass parameters to a procedure, the next thing to discuss is where to pass parameters. Where you pass parameters depends, to a great extent, on the size and number of those parameters. If you are passing a small number of bytes to a procedure, then the registers are an excellent place to pass parameters. The registers are an ideal place to pass value parameters to a procedure. If you are passing a single parameter to a procedure you should use the following registers for the accompanying data types:

Data Size       Pass in this Register
Byte:                   al 
Word:                   ax 
Double Word:            dx:ax or eax (if 80386 or better)

This is, by no means, a hard and fast rule. If you find it more convenient to pass 16 bit values in the si or bx register, by all means do so. However, most programmers use the registers above to pass parameters.

If you are passing several parameters to a procedure in the 80x86's registers, you should probably use up the registers in the following order:

First                                    Last
        ax, dx, si, di, bx, cx 

In general, you should avoid using bp register. If you need more than six words, perhaps you should pass your values elsewhere.

The UCR Standard Library package provides several good examples of procedures that pass parameters by value in the registers. Putc, which outputs an ASCII character code to the video display, expects an ASCII value in the al register. Likewise, puti expects the value of a signed integer in the ax register. As another example, consider the following putsi (put short integer) routine that outputs the value in al as a signed integer:

putsi           proc
                push    ax      ;Save AH's value.
                cbw             ;Sign extend AL -> AX.
                puti            ;Let puti do the real work.
                pop     ax      ;Restore AH.
                ret
putsi           endp

The other four parameter passing mechanisms (pass by reference, value-returned, result, and name) generally require that you pass a pointer to the desired object (or to a thunk in the case of pass by name). When passing such parameters in registers, you have to consider whether you're passing an offset or a full segmented address. Sixteen bit offsets can be passed in any of the 80x86's general purpose 16 bit registers. si, di, and bx are the best place to pass an offset since you'll probably need to load it into one of these registers anyway[4]. You can pass 32 bit segmented addresses dx:ax like other double word parameters. However, you can also pass them in ds:bx, ds:si, ds:di, es:bx, es:si, or es:di and be able to use them without copying into a segment register.

The UCR Stdlib routine puts, which prints a string to the video display, is a good example of a subroutine that uses pass by reference. It wants the address of a string in the es:di register pair. It passes the parameter in this fashion, not because it modifies the parameter, but because strings are rather long and passing them some other way would be inefficient. As another example, consider the following strfill(str,c) that copies the character c (passed by value in al) to each character position in str (passed by reference in es:di) up to a zero terminating byte:

; strfill-      copies value in al to the string pointed at by es:di
;               up to a zero terminating byte.

byp             textequ <byte ptr>

strfill         proc
                pushf                   ;Save direction flag.
                cld                     ;To increment D with STOS.
                push    di              ;Save, because it's changed.
                jmp     sfStart

sfLoop:         stosb                   ;es:[di] := al, di := di + 1;
sfStart:        cmp     byp es:[di], 0  ;Done yet?
                jne     sfLoop

                pop     di              ;Restore di.
                popf                    ;Restore direction flag.
                ret
strfill         endp

When passing parameters by value-returned or by result to a subroutine, you could pass in the address in a register. Inside the procedure you would copy the value pointed at by this register to a local variable (value-returned only). Just before the procedure returns to the caller, it could store the final result back to the address in the register.

The following code requires two parameters. The first is a pass by value-returned parameter and the subroutine expects the address of the actual parameter in bx. The second is a pass by result parameter whose address is in si. This routine increments the pass by value-result parameter and stores the previous result in the pass by result parameter:

; CopyAndInc-   BX contains the address of a variable. This routine
;               copies that variable to the location specified in SI
;               and then increments the variable BX points at.
;               Note: AX and CX hold the local copies of these
;               parameters during execution.

CopyAndInc      proc
                push    ax              ;Preserve AX across call.
                push    cx              ;Preserve CX across call.
                mov     ax, [bx]        ;Get local copy of 1st parameter.
                mov     cx, ax          ;Store into 2nd parm's local var.
                inc     ax              ;Increment 1st parameter.
                mov     [si], cx        ;Store away pass by result parm.
                mov     [bx], ax        ;Store away pass by value/ret parm.
                pop     cx              ;Restore CX's value.
                pop     ax              ;Restore AX's value.
                ret
CopyAndInc      endp

To make the call CopyAndInc(I,J) you would use code like the following:

                lea     bx, I
                lea     si, J
                call    CopyAndInc

This is, of course, a trivial example whose implementation is very inefficient. Nevertheless, it shows how to pass value-returned and result parameters in the 80x86's registers. If you are willing to trade a little space for some speed, there is another way to achieve the same results as pass by value-returned or pass by result when passing parameters in registers. Consider the following implementation of CopyAndInc:

CopyAndInc      proc
                mov     cx, ax  ;Make a copy of the 1st parameter,
                inc     ax      ; then increment it by one.
                ret
CopyAndInc      endp

To make the CopyAndInc(I,J) call, as before, you would use the following 80x86 code:

                mov     ax, I
                call    CopyAndInc
                mov     I, ax
                mov     J, cx

Note that this code does not pass any addresses at all; yet it has the same semantics (that is, performs the same operations) as the previous version. Both versions increment I and store the pre-incremented version into J. Clearly the latter version is faster, although your program will be slightly larger if there are many calls to CopyAndInc in your program (six or more).

You can pass a parameter by name or by lazy evaluation in a register by simply loading that register with the address of the thunk to call. Consider the Panacea PassByName procedure (see "Pass by Name"). One implementation of this procedure could be the following:

;PassByName-    Expects a pass by reference parameter index
;               passed in si and a pass by name parameter, item,
;               passed in dx (the thunk returns the address in bx).

PassByName      proc
                push    ax                      ;Preserve AX across call
                mov     word ptr [si], 0        ;Index := 0;
ForLoop:        cmp     word ptr [si], 10       ;For loop ends at ten.
                jg      ForDone
                call    dx                      ;Call thunk item.
                mov     word ptr [bx], 0        ;Store zero into item.
                inc     word ptr [si]           ;Index := Index + 1;
                jmp     ForLoop

ForDone:        pop     ax                      ;Restore AX.
                ret                             ;All Done!
PassByName      endp

You might call this routine with code that looks like the following:

                lea     si, I
                lea     dx, Thunk_A
                call    PassByName
                 .
                 .
                 .
Thunk_A         proc
                mov     bx, I
                shl     bx, 1
                lea     bx, A[bx]
                ret
Thunk_A         endp

The advantage to this scheme, over the one presented in the earlier section, is that you can call different thunks, not just the ItemThunk routine appearing in the earlier example.

11.5.8 Passing Parameters in Global Variables

Once you run out of registers, the only other (reasonable) alternative you have is main memory. One of the easiest places to pass parameters is in global variables in the data segment. The following code provides an example:

                mov     ax, xxxx                ;Pass this parameter by value
                mov     Value1Proc1, ax
                mov     ax, offset yyyy         ;Pass this parameter by ref
                mov     word ptr Ref1Proc1, ax
                mov     ax, seg yyyy
                mov     word ptr Ref1Proc1+2, ax
                call    ThisProc
                 .
                 .
                 .
ThisProc        proc    near
                push    es
                push    ax
                push    bx
                les     bx, Ref1Proc1           ;Get address of ref parm. 
                mov     ax, Value1Proc1         ;Get value parameter
                mov     es:[bx], ax             ;Store into loc pointed at by
                pop     bx                      ; the ref parameter.
                pop     ax
                pop     es
                ret
ThisProc        endp

Passing parameters in global locations is inelegant and inefficient. Furthermore, if you use global variables in this fashion to pass parameters, the subroutines you write cannot use recursion (see "Recursion"). Fortunately, there are better parameter passing schemes for passing data in memory so you do not need to seriously consider this scheme.


[4] This does not apply to thunks. You may pass the address of a thunk in any 16 bit register. Of course, on an 80386 or later processor, you can use any of the 80386's 32-bit registers to hold an address.

Chapter Eleven (Part 2)

Table of Content

Chapter Eleven (Part 4) 

Chapter Eleven: Procedures and Functions (Part 3)
27 SEP 1996