Chapter 5.1-5.6 Notes

Overview

The importance of algorithm abstraction and modularity is such that virtually all high-level languages realize these notions, generally as procedures and functions with parameters. Translation from Pascal or C++ algorithms to Assembly can be made once a few basic rules for passing parameters are established. Though there are numerous methods to pass data to a procedure, the simplest and the one discussed here is the use of global variables and register parameters to interface the calling and the called functions. The more general and useful method of passing parameters via the stack will also be discussed later as this method is most often used by high level languages.

Macros are another method of implementing modularity and abstraction in Assembler (and C, spreadsheets, and many languages). Though both are abstraction techniques, macros are very different from a function in use.

Functions

An Assembly and a C++ (or Pascal, etc.) function operates identically. The following C++ and Assembly program call a function f that has zero parameters but each produces the same results. It is instructive to follow the execution of the C++.
 
C++ versus Assembly Functions
int x;       // Global x
1. void main(void) {       
2.    f();
3.    x = 4; // Side effect
4. }
5. void f(void) {
6.    x = 5; // Side effect 
7. }
.Data
.Code
1.  main Proc    Far
2.       mov eAX, eBX 
3.       mov eCX, eDX     
4.       Call    f
5.       Mov     X, 4  ;; Side effect

6.       
7.
8.  main Endp
9.  f    Proc    Near
10.      Mov     X, 5  ;; Side effect
11.      Ret
12. f    Endp

       End      main

Call/Ret - Function Execution

An execution trace of the C++ and Assembler programs above reveals the following execution sequence. Notice that the Call f transfers control to the function f, the Ret returns control back to the caller.
 
C++ and Assembler Execution Sequence
C++ 1, 2, 5, 6, 7, 3, 4
Assembler 1, 2, 3, 4, 9, 10, 11, 12, 5, 6, 7, 8
Calling a Function - Calling a function generally requires the following steps (specifically for Call f):
  1. Save the return location  - The IP points to the next instruction after the Call f to execute. The eIP = 000Cx must be saved on the stack before the function begins execution. The effect is:
  2. Execute first instruction of function - By setting eIP to the location of the first instruction of the function , that instruction will be fetched and executed next. The first instruction of function f is at offset 0012x, the effect is:
Execution of Call f 
Assembly Listing Stack, Sp, and IP
     12 0005  C7 06 0000r 0004               Mov     X, 4
     13 000B  E8 0012                        Call    f
     14
     15 000E                                 Mov     X,eAX
     16 0010                            
     17 0012                           main  Endp
     18
     19 0012                           f     Proc    Near
     20 0012  C7 06 0000r 0005               Mov     X, 5
     21 0018  C3                             Ret
     22 0019                           f     Endp
After Call f
       ____
      |____|
eSp-> |000C|
      |    |
eIp =  0012

After Ret
       ____
eSp ->|____|
      |000E|
      |    |
eIp =  000E
Function Return - Returning from a function is the reverse of the Call requiring the following step:
  1. Restore the return location  - When executing the Ret, the eIP points to the next instruction after the Ret, eIP = 0019. The return location must be restored from the stack to IP. The effect of restoring eIP=000Ex is to resume execution at offset 000Ex:

Parameters

Parameters allow the same code of a function to produce a range of results dependent upon the parameter value. For example, the sqrt(4) is 2 and the sqrt(25) is 5. This supports reuse of generic code rather than implementing unique code for each possible case (e.g. sqrt4() and sqrt25()). The following examines two simple methods of implementing parameters, global variables and registers. Both methods have problems that will be identified in the discussion.


Global Variables - It should be remembered that all variables declared in the data segment and all CPU registers are accessible globally, from within any function in the algorithm. While this may seem wonderfully useful, reflect upon writing a large C++ program with no local variables or function parameters, it would be easy to accidentally change a variable. When global variables are used to exchange data between functions, the likelihood of creating unwanted side-effects increases with the number of assignment statements and the number of global variables. The potential for interactions increases exponentially as the number of global variables increase, creating a daunting challenge for a programmer to produce reliable programs. When globals are used to exchange data, the same variable is accessed in one or more functions. The effects of function execution are not localized to that function, since global variables can be changed in any function as a hidden side-effect of the functions execution.

While global variables and the globally accessible CPU registers share many of the same problems when used to exchange data between assembly procedures, there are two strong reasons for using registers over globals. 1) As part of the CPU, the same registers are known by the same name everywhere and are shared by all functions making it possible to pass data between two separately written functions. Global variable names are normally limited to the source program and data segment where defined. 2) The Push/Pop mechanism can be used to preserve register values from unwanted side-effects.

For a simple example of global variable as parameters see the program above, variable x in the C++ and X in the Assembler are globals, accessible from all functions. To illustrate the difference between the use of global variables in the data segment and register parameters, programs in C++ and Assembler using globals and another using register parameters are listed below. Each of the included examples compute N!. All the factorial functions return a value in the Ax register. Ax is used by convention for returning a word result from a function.
 

Factorial Function Example
// Determine sum of 4! and 5! 
// using global variables
#include <iostream.h>

int Sum, N      // Global variable N   
                // parameter to Factorial 

int Factorial(void) {

     int F = 1;
     while (N > 1) {
       F = F * N;        //
       N = N - 1;        // Side-effect N
     };                  // 

     return F;
} // Factorial 

void main(void) {
     N = 4;
     Sum = Factorial();  //
     N = 5;              // Side-effect N
     Sum =+ Factorial(); //
     cout << N;            
}                          































       Extrn         WriteDec

.data
	Sum	dd	?
	N	dd	?

.code       

;; int Factorial()
;;                    - Calculate N!
;;  Parameters        - N (global) 
;;  Returns           - Ax factorial value
;;  Registers Altered - Ax

Factorial Proc    Near     ;; int Factorial()
        Push      eDx      ;; Save eDx

        Mov       eAx, 1   ;; F = 1;
While:  Cmp       N, 1     ;; while (N > 1) {
        JA        Do       ;;   F = F * N;
        Jmp       EndWhile ;;   N = N - 1; 
     Do:  
        Mul       N        ;;  ______________ 
        Dec       N        ;; |Side-effect N |
        Jmp       While    ;; |______________|
EndWhile: 
        Pop       eDx      ;; Restore eDx
        Ret

Factorial Endp

Main   Proc     Far
       
       Mov      N, 4
       Call     Factorial
       Mov      Sum, eAx    ;; Sum = Factorial

       Mov      N, 5
       Call     Factorial
       Add      Sum, eAx   ;; Sum =+ Factorial;
                           ;;  ________________
       Mov      eAx, N     ;; | N side-effected|
       Call     WriteDec     ;; |________________|

       
Main     Endp
       End       Main


Register Parameters -When registers are used to pass parameters to a procedure or function, it once again creates the opportunity for unwanted side-effects. However, using Push and Pop to save and restore registers used within the procedure one can eliminate at least one type of side-effect. The C++ algorithms reflects the use of parameters although, as we will see in later chapters, C++ passes parameters on the stack rather than in registers (with only a few registers the number of possible parameters would be severely limited). The Assembler example instead uses registers to pass data to the function. The key weaknesses of register parameters are:

In the following the C++ and Assembly programs both compute 4! + 5!. In the C++ example, 4 and 5 are passed as parameters, in the Assembler example, eBx register is used to pass the parameters 4 and 5 to the Factorial function, the result is returned in Ax register. In the Assembler program at line:
  1. The value 4 is moved to parameter Bx.
  2. The returned result eAx = 4! is used.
  3. The value 5 is moved to the parameter Bx.
  4. The returned result eAx = 5! is usd.
  5. The value of parameter is saved on stack.
  6. The parameter eBx is changed in function.
  7. The original value of parameter eBx is restored.
Parameter passing in C++ and using Registers in Assembler
// Determine sum of 4! and 5!  
// using parameters

#include <iostream.h>

int Sum, N;                 
                         

void main(void) {
     N = 4;
     Sum = Factorial(N);
     N = 5;
     Sum =+ Factorial(N);  
     cout << N;            
}         

int Factorial( int N ) {

     int F=1;
     while (N > 1) {
       F = F * N;   // ______________ 
       N = N - 1;   //|No Side-effect|
     };             //|______________|
     return F;
}  //Factorial 

































                  
       Extrn         WriteDec
                         
.Data
       Sum    dd   ?     
.Code
Main   Proc     Far
       

1.     Mov      eBx, 4      ;; eBx parameter
       Call     Factorial
2.     Mov      Sum, eAx    ;; Sum := Factorial(eBx)

3.     Mov      eBx, 5      ;; eBx parameter
       Call     Factorial
4.     Add      Sum, eAx    ;; Sum =+ Factorial(eBx)

       Mov      eAx, Sum    ;; cout << Sum;
       Call     WriteDec

       
Main   Endp

;; int Factorial( int eBx );
;;                         - Calculate N!
;;       Parameters        - eBx positive int 
;;       Returns           - eAx factorial value
;;       Registers Altered - eAx

Factorial Proc   Near       
                            ;;   ____________
        Push      eDx       ;;  | Save eBx eDx |
5.      Push      eBx       ;;  |___________  _|

        Mov       eAx, 1    ;; F = 1
While:  
        Cmp       eBx, 1    ;; while (eBx > 1) {
        JA        Do        ;;   F = F * N
        Jmp       EndWhile  ;;   eBx = eBx - 1  
     Do:                    ;;  }
        Mul       eBx       ;;  ______________
6.      Dec       eBx       ;; |Side-effect eBx|
        Jmp       While     ;; |_____________  _|
EndWhile: 
7.      Pop       eBx        ;;   _______________
        Pop       eDx        ;;  | Restore eBx eDx |
        Ret                 ;;   | eAx has result |
Factorial Endp

         End       Main

Returning Function Results

Functions often return a single result of the execution. The standard method followed in this course and for most high level languages is the following: It is of course possible to use any other registers to return results but that is nonstandard use. The factorial functions examined above use eAX to return a double word result. One common error is to Push/Pop eAX on entry and exit to the function, the undesirable effect is that eAX may change during the function execution but is restored to its original value.

Near/Far Functions

Functions can be defined as either Near or Far. The following distinguishes between the two:

Macros

Macros allow a programmer to define pseudo operations, typically operations that are generally desirable, are not implemented as part of the processor instruction, and can be implemented as a sequence of instructions. Each use of a macro generates new program instructions, the macro has the effect of automating writing of the program. Consider a typical scenario where one needs to do a number of divisions of eAX register by 10. The following lists the typical evolution of macro development and usage. The final result is the expansion of the macro which becomes part of the program. In the following example the macro use simply inserts the three instructions of the macro definition.
Typical evolution of a Macro
Instruction
Needed
Real
Operations
Macro
Definition
Use in
Program
Macro
Expansion
Div  10



Mov  eDx, 0
Mov  eBx, 10  
Div  eBx

.Div10  Macro
        Mov    eDx, 0
        Mov    eBx, 10    
        Div    eBx
endm
Call  ReadDec
.Div10
Call  WriteDec   

  
Call  ReadDec
Mov   eDx, 0
Mov   eBx, 10
Div   eBx
Call  WriteDec
 Parameters - Macro parameters support code reuse, allowing one macro definition to implement multiple algorithms. In the following, the .DIV macro has a single parameter N. When the macro is used in the program, the actual parameter used is substituted for the formal parameter defined in the macro prototype during the macro expansion. Now the same macro, when expanded, can produce code to divide by any unsigned integer.
Macro Parameters
Instruction
Needed
Real
Operations
Macro
Definition
Use in
Program
Macro
Expansion
Div  N



Mov  eDx, 0
Mov  eBx, N  
Div  eBx

.Div    Macro  N
        Mov    eDx, 0
        Mov    eBx, &N    
        Div    eBx
endm
Call  ReadDec
.Div  34
Call  WriteDec   

  
Call  ReadDec
Mov   eDx, 0
Mov   eBx, 34
Div   eBx
Call  WriteDec

Labels - Labels must be unique. When used in macros, multiple expansions of a macro can produce multiple definitions of the same label. To see the problem consider a macro to produce the absolute value of a signed integer variable. For one use of the .Abs macro, the if:, then:, and endif: label definitions are unique, two or more uses of the .Abs macro leads to multiple definitions.
Local Symbols - The solution is to generate unique symbol(s) each time the macro is used. The solution implemented is to keep a counter that is incremented each time a symbol defined to be local is encountered during macro expansion. The following illustrates the use of local symbols to produce unique symbols. The first table is correct but produces labels that bear little connection to the original structure. The second produces more readable code.

Macro Label Solution
Instruction
Needed
Real
Operations
Macro
Definition
Use in
Program
Macro
Expansion
Abs   X







if: Cmp X, 0 
    Jl  then
    Jmp endif
then:
    Neg X
endif:


.Abs  Macro   X
  local if, then, endif
  if: Cmp &X, 0 
      Jl  then
      Jmp endif
  then:
      Neg &X
  endif:
endm
Call  ReadInt
.Abs  Z
Call  WriteInt   

  



       Call  ReadInt
$0001: Cmp   Z, 0 
       Jl    $0002
       Jmp   $0003
$0002:
       Neg   Z
$0003:
       Call  WriteInt
Macro Label Solution
Instruction
Needed
Real
Operations
Macro
Definition
Use in
Program
Macro
Expansion
Abs   X






if: Cmp X, 0 
    Jl  then
    Jmp endif
then:
    Neg X
endif:

.Abs  Macro   X
  local label
  if&label: Cmp &X, 0 
      Jl  then
      Jmp endif
  then&label:
      Neg &X
  endif&label:
endm
Call  ReadInt
.Abs  Z
Call  WriteInt  

  


         Call ReadInt
if$0001: Cmp  Z, 0 
         Jl   then$0002
         Jmp  endif$0003
then$0002:
         Neg  Z
endif$0003:
         Call WriteInt
Conditional Assembly - Means that some sections of the program may be optional, either included or not in the final program, dependent upon specified conditions. A reasonable use of conditional assembly would be to combine two versions of a program, one that prints debugging information during test executions for the developer, another version for production operation that displays only results of interest for the average user. A program fragment that assembles the instructions to print the eAX register only if Debug is true is given below. Note that true is any non-zero value.
 
Display Ax when Debug is non-zero
Debug  equ  1         ; Debug is true
      :
      :
     Mul     eBx
     If      Debug    ; Assemble only when Debug is true
     Push    eBx   
     XOR     BH,BH
     Call    WriteDec
     Pop     eBx
     Endif
     Sub     eCx, 12
Testing for Blank Macro Parameters/Exit Expansion - It is possible to perform some tests for correctness of macro parameters, the simplest is to verify that parameters are defined. Consider a macro .Max that returns the maximum of two unsigned integers in the eAX register. If either of the two values are missing the macro should display an error message to the screen but not expand to produce any program instructions. code.
Testing for Blank Parameters
Instruction
Needed
Real
Operations
Macro
Definition
Use in
Program
Macro
Expansion
Max   X, Y 















    Mov eAX, X
if: Cmp eAX, Y  
    Ja  then
    Jmp else
then:
    Mov eAX, X
    Jmp endif
else:
    Mov eAX, Y
endif:






.Max    Macro  X, Y  
        local L
ifB    <&Y> 
        %outm Error 
        exitm
endif
        Mov eAX, &X
  if&L: Cmp eAX, &Y 
        Ja  then&L
        Jmp else&L
  then&L:
        Mov eAX, &X
        Jmp endif&L
  else&L:
        Mov eAX, &Y
  endif&L:
endm
Call  ReadDec
.Max  Z, 5
Call  WriteDec  
.Max  Z   








  



       Call  ReadDec
       .Max    Z, 5
       Mov eAX, Z
if$01: Cmp eAX, 5    
       Ja  then$01
       Jmp else$01
then&01:
       Mov eAX, Z
       Jmp endif$01
else$01:
       Mov eAX, 5
endif$01:
       Call  WriteDec

       .Max  Z
No expansion due to
missing parameter.

Examples
 

Euclid's Greatest Common Divisor Macro
Modulus operator
Result must be a variable or register other
than eAX, eBX, or eDX
Interchange operator 
All parameters must variables 
or registers other than eAX, eBX
GCD operator
Parameters must be variables or
registers other than eAX, eBX, eDX
.Mod   Macro   N, Divisor, Result
       PushA   
       Mov     eAx, &N
       Mov     eBx, &Divisor
       Mov     eDx, 0
       Div     eBx
       Mov     &Result, eDx
       PopA
endm


.Swap Macro  A, B
      PushA  
      Mov    eAx, &A
      Mov    eBx, &B
      Mov    &A, eBX
      Mov    &B, eAX
      PopA
endm
             


.Gcd Macro  X, Y
       local  L
  while&L:
       cmp    &Y, 0
       Jne    do&L
       Jmp    endwhile&L
   do&L:
       .Mod   &X, &Y, &X
       .Swap  &X, &Y
       Jmp    while&L
  endwhile&L:
endm


Document last modified: