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.
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 |
| C++ | 1, 2, 5, 6, 7, 3, 4 |
| Assembler | 1, 2, 3, 4, 9, 10, 11, 12, 5, 6, 7, 8 |
| 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 |
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.
// 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:
// 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
|
| 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 |
| 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.
| 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 |
| 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 |
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
|
| 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
| 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 |