How to Turn MacroAssembler into a High Level Language






4.44/5 (6 votes)
Macros to help assembler programmers to improve source code
Introduction
Programmers that use MacroAssembler
, often would like to have the same flexibility that high level languages offer in structured blocks such as "if
.. then
.. else
.. endif
", "while
.. wend
", "repeat
.. until
" or "for
..next
". Why has this class of syntax not been included in MacroAssembler
? Probably because the power of the MacroAssembler
language are the macros itself, so we can mimic the above mentioned blocks and write a more clear code.
I'll use VB naming convention because it is nearer to human readable syntax. (When you, as "C" programmer, reads "}
" in your code, you have to go back in your source to know if it closes a "if
", "else
", "while
" or "do
" block.)
As said, all these blocks are executed (or not) on conditions. A single condition can be read as the result of Operand1 Operator Operand2
where Operand1
and Operand2
are variables or constant values in your code and operator
is an arithmetic one such as =
, >
, <
, >=
, <=
, ...
So, a high level language can process a syntax such as:
IF age > 50 THEN 'Age is Operand1, > is the operator and 50 is Operand2
'Actions
ENDIF
-or said in another way-
IF Condition THEN
'Actions
ENDIF
In this case, if we think about what a VB program does when it finds this block in the code is:
- Evaluate the condition.
- If result is
true
, then process all the actions existing from 'THEN
' up to 'ENDIF
' keywords.
A little more elaborated block could be:
IF Condition THEN
'Actions_when_true
ELSE
'Actions_when_false
ENDIF
In this case, the process is:
- Evaluate the condition
- If result is
true
, then process all the actions from 'THEN
' to 'ELSE
' labels, else process all the actions from 'ELSE
' to the 'ENDIF
' labels
Before going deeper into the creation of structured macros, we have to take into account that MacroAssembler
doesn't have the syntax of arithmetic operators. In their place, pnemonics are used. Let's say: E (Equal), L (Less), LE (Less or Equal), B (Below), BE (Below or Equal), G (Greater), GE (Greater or Equal), A (Above), AE (Above or Equal)
or their contraries:
NE (No equal), NL (No less), NLE (No less equal), NB (no below), NBE (no below equal), NG (no greater), NGE (no greater equal), NA (no above), NAE (no Above equal).
What Is a Macro?
For those who use variants of the 'C' language, a macro is the equivalent to #define
a result based on parameters. When MacroAssembler
finds in your code a macro that has previously been defined, replaces the parameters and expands the macro.
Once arrived at this point, we can write (and study) our first very basic macro with the following syntax: (Text between []
means optional from so on):
;===================================================
;$iif op1, oper, op2, label1 [,label2]
;===================================================
$iif macro op1, oper, op2, label1, label2
cmp &op1, &op2
j&oper &label1
ifnb <&label2> ;if label2 is no blank,
(the 'label2' parameter exists) then MacroAssembler assembles then next line
jmp &label2
endif
endm
If we place the following text in our code ...
$iif ax,e,5,IsFive
...MacroAssemble will expand the macro to ...
cmp ax, 5
je IsFive
if we place the following text in our code ...
$iif ax,e,5,IsFive,NoIsFive
...MacroAssemble will expand the macro to ...
cmp ax, 5
je IsFive
jmp NoIsFive
So, by the use of this little macro, we can reduce all the source code in comparisons from two or three lines to only one, making our code very much readable.
The next thing we can study are the high level language Iterating blocks:
-
Repeat 'Actions Until Condition (is true)
In this case, what a compiler does when it finds the '
Repeat
' tag is to save the address of the first action to do. When it finds the 'Until
' tag, the program has to evaluate the condition. If condition evaluates tofalse
, then the program has to jump to the address of the first action in the block saved before (else the work has done). -
While Condition (is true) 'Actions Wend
In this case, when the compiler finds the '
While
' tag, it saves the starting position of the actions. The runtime evaluates 'condition
'. If it'strue
, the execution will continue in the first action if the condition evaluates tofalse
, then execution will continue after 'Wend
' tag. -
For variable,Initial_Value,Final_Value,Step 'Actions Next
This is a bit complex. The function is to execute '
Actions
' with 'variable
' values from 'Initial_Value
' to 'Final_Value
', increasing or decreasing 'variable
' in each iteration by 'Step
'. When 'variable
' exceeds 'final_value
', the iteration is finished.
First, the runtime assigns 'Initial_Value
' to 'variable
'. Second evaluates if 'variable
' exceeds 'Final_Value
'. If Final_Value
is not exceeded, then all the actions are executed, variable is increased or decreased and the process continues at point 2. If final_value
is exceeded, then the iteration is finished.
Putting All Together for MacroAssembler
MacroAssembler assembles top-down in your code, as all high level languages. That means that when assembler tries to assembly an instruction (or macro) it knows where is this instruction in your code (its address). First of all, we have a counter of symbolic places in the code. Each place name for macroassemble will be $sim_
plus the counter value.
As iterating blocks require to know where they start and/or end to allow jumps to this places, some internal macros are provided:
$pushaddr
: When found, it increases the counter of symbolic names and generates a$sim_counter
name to identify the start of a block equal to the place where the macro has been found.$popaddr
: When found, it recovers the$sim_counter
value as$jmp
variable and decreases the counter of symbolic names.$makenops
: Allocates a 3 byte code for ajmp
'xxxx' that$filljmp
will set later$filljmp
: Fills the preallocated 3 bytes space with ajmp
'xxxx'.
NOTE: These macros were created for MacroAssembler 5.1. In assembling code for flat memory, perhaps you should add more nops
in $makenops
to allow offsets in code to be greater than a signed word. To do so, you have to write a little program such as:
jmp veryfar
db 100000 dup(0)
veryfar:
... when assembled, you can see how many bytes "jmp veryfar
" uses to store the instruction. This number of bytes are the nops you have to reserve in $makenops
. Remember to revise the keyword 'near
' in the code provided if needed.
Explanations of every macro are detailed in the included text. This macros can be nested, that means that from now on, you can write MacroAssembler code as:
$IF value,le,100
$THEN
;actions
$ELSE
;actions
$ENDIF
$FOR x,0,1366-1,1
$FOR y,0,768-1,1
;GetPixel(x,y)
;some actions more
$NEXT
$NEXT
$REPEAT
;Get Keyboard key
$UNTIL key,e,Escape
$WHILE value,l,100
;some actions more
;value = calculated_value
$WEND
Using the Code
Just include the text provided at the beginning of your code or #include
it.
Code
.XLIST
.XCREF
; STRUCTURED PROGRAMMING MACROS ======================================================
;-------------------------------------------------------------------------------------
; internals
;-------------------------------------------------------------------------------------
$simcount = 0 ;Counter used to generate symbolic names along the program as $sim_1, &sim_2 ...
$pushaddr macro
$simcount = $simcount + 1 ; increase symbol count
$newsim %$simcount ; creates new sim name
endm
$newsim macro $n
$sim_&$n = this near ;generates symbolic address. I.E. $sim_100 = place into code where $newsim was expanded, I.E.= 506
endm
$popaddr macro
$getsim %$simcount
$simcount = $simcount - 1 ; decrease symbol count
endm
$getsim macro $n
ifndef $sim_&$n
%out fail in structure !!!
endif
$jmp = $sim_&$n
endm
$makenops macro
nop ;allocate space to place a jmp when other macros will be processed
nop ;a jmp instructions use 1 byte for the coding of the instruction itself plus 2 bytes more
nop ;that are treated as a signed word (offset potitive or negative from position of the instruction)
endm
$makejump macro towhere
here = this near ;we save or position in the source code
$popaddr ;recover address of last 3 nops
org $jmp ;we go there ...
jmp &towhere ;... and replace the three nops with "jmp towhere"
org here ;we come back to our position in the source code
endm
;-------------------------------------------------------------------------------------
; $iif op1, oper, op2, label1 [,label2]
;-------------------------------------------------------------------------------------
$iif macro op1, oper, op2, label1, label2
cmp &op1, &op2
j&cond &label1
ifnb <&label2> ;if label2 was passed as argument, then is expanded
jmp &label2
endif
endm
;=====================================================================================
; Sintax:
; $IF op1, oper ,op2
; [$THEN]
; block1
; [$ELSE]
; block2
; $ENDIF
;=====================================================================================
$IF macro op1, oper, op2
local block1
$iif <op1>, oper, <op2>, block1
$pushaddr
$makenops ;nops will be filled by $ELSE or by $ENDIF with a jmp tosomewhere when condition evaluates false
block1: ;this is the start of block of instructions executed when condition evaluates to true
endm
;-------------------------------------------------------------------------------------
$THEN macro
endm ;macro formal. $THEN is optional and used to produce only a better readability of the source code
;-------------------------------------------------------------------------------------
$ELSE macro
local block2
$makejump block2 ;as we use $ELSE macro, the $IF nops has to be replaced by a jmp to the start of the $ELSE block
$pushaddr ;also, we have to reserve space for the jmp out the $IF
$makenops
block2: ;this is the start of block of instructions executed when the $IF condition evaluates to false
endm
;-------------------------------------------------------------------------------------
$ENDIF macro
local exitif
$makejump exitif ;if $ELSE is not used then the $IF nops are replaced with "jmp exitif"
exitif: ;if $ELSE has been used then the $ELSE nops are replaced with "jmp exitif"
endm
;=====================================================================================
; Sintax:
; $WHILE op1, oper, op2
; ... ;Actions executed if condition evaluates true
; $WEND
;=====================================================================================
$WHILE macro op1, oper, op2
local istrue
$pushaddr ;address of start of loop
$iif <op1>, oper, <op2>, istrue
$pushaddr ;this is the address reached when condition is false
$makenops ;reserve nops to be filled with 'jmp outofloop'
istrue:
endm
$WEND macro
local quitloop
$makejump quitloop
$popaddr ;recover address of the start of loop
jmp $jmp ;goto there
quitloop:
endm
;=====================================================================================
; Sintax:
; $REPEAT
; ... ;Actions executed until condition evaluates true
; $UNTIL op1, oper, op2
;=====================================================================================
$REPEAT macro
$pushaddr ;address of start of loop
endm
$UNTIL macro op1, oper, op2
local quitloop
$iif <op1>, oper, <op2>, quitloop
$popaddr ;recover address of the start of loop
jmp $jmp ;goto there
quitloop:
endm
;=====================================================================================
; Syntax:
; $FOR index,initial_value,final_value,step_value,[register]
; ...
; $NEXT | $LOOP
; register has to be used if both index and initial_value are variables
;=====================================================================================
$FOR macro index,initial_value,final_value,step,register
local compare, inrange
;set the initial value to index
ifnb <®ister>
mov ®ister, &initial_value
mov &index, ®ister
else
mov &index, &initial_value
endif
jmp short compare
$pushaddr ;address of start of loop
if step eq 1
inc index
else
if step eq -1
dec index
else
add index, step
endif
endif
compare:
ifnb <®ister>
mov ®ister, &final_value
cmp &index, ®ister
else
cmp &index, &final_value
endif
if step gt 0
jle inrange ;goto block if less or equal
else
jge inrange ;goto block if greater or equal
endif
$pushaddr ;here = address of out of range
$makenops ;we will fill nops with "jmp quitloop"
inrange:
endm
$NEXT macro
local quitloop
$makejump quitloop
$popaddr ;recover address of the start of loop
jmp $jmp ;goto there
quitloop:
endm
$LOOP equ <$NEXT>
;----------------------------------------------------------------------
.CREF
.LIST
Points of Interest
After decompiling "C" code snippet, I was able to determine what a compiler does, so the macros mimic the same process.
History
These macros haven't ever been modified.