如何将宏汇编器变成一种高级语言






4.44/5 (6投票s)
有助于汇编程序员改进源代码的宏
引言
使用 MacroAssembler
的程序员,经常希望获得高级语言在结构化块中所提供的灵活性,例如“if
.. then
.. else
.. endif
”、“while
.. wend
”、“repeat
.. until
”或“for
..next
”。为什么这类语法没有包含在 MacroAssembler
中?可能是因为 MacroAssembler
语言的强大之处在于其宏本身,因此我们可以模仿上述块并编写更清晰的代码。
我将使用 VB 命名约定,因为它更接近人类可读的语法。(当您作为“C”程序员在代码中看到“}
”时,您必须回溯源代码才能知道它关闭的是“if
”、“else
”、“while
”还是“do
”块。)
如前所述,所有这些块都是基于条件执行(或不执行)的。单个条件可以被读取为 Operand1 Operator Operand2
的结果,其中 Operand1
和 Operand2
是代码中的变量或常量值,而 operator
是算术运算符,例如 =
、>
、<
、>=
、<=
,等等。
因此,高级语言可以处理如下语法
IF age > 50 THEN 'Age is Operand1, > is the operator and 50 is Operand2
'Actions
ENDIF
- 或者换句话说 -
IF Condition THEN
'Actions
ENDIF
在这种情况下,如果我们考虑 VB 程序在代码中找到这个块时会做什么,那就是
- 评估条件。
- 如果结果为
true
,则处理从“THEN
”到“ENDIF
”关键字之间的所有操作。
一个稍微复杂一点的块可以是
IF Condition THEN
'Actions_when_true
ELSE
'Actions_when_false
ENDIF
在这种情况下,过程是
- 评估条件
- 如果结果为
true
,则处理从“THEN
”到“ELSE
”标签之间的所有操作,否则处理从“ELSE
”到“ENDIF
”标签之间的所有操作。
在深入研究结构化宏的创建之前,我们必须考虑到 MacroAssembler
没有算术运算符的语法。取而代之的是助记符。例如:E (等于), L (小于), LE (小于等于), B (低于), BE (低于等于), G (大于), GE (大于等于), A (高于), AE (高于等于)
或它们的相反
NE (不等于), NL (不小于), NLE (不小于等于), NB (不低于), NBE (不低于等于), NG (不大于), NGE (不大于等于), NA (不高于), NAE (不高于等于)。
什么是宏?
对于使用 C 语言变体的人来说,宏相当于基于参数的 #define
结果。当 MacroAssembler
在您的代码中找到一个先前已定义的宏时,它会替换参数并展开宏。
到了这一点,我们可以使用以下语法编写(并研究)我们的第一个非常基本的宏:([]
之间的文本表示从此处开始可选)
;===================================================
;$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
如果我们在代码中放入以下文本...
$iif ax,e,5,IsFive
...MacroAssemble 将宏展开为...
cmp ax, 5
je IsFive
如果我们将在代码中放入以下文本...
$iif ax,e,5,IsFive,NoIsFive
...MacroAssemble 将宏展开为...
cmp ax, 5
je IsFive
jmp NoIsFive
因此,通过使用这个小宏,我们可以将比较中的所有源代码从两三行减少到一行,从而使我们的代码更具可读性。
接下来我们可以研究的是高级语言的迭代块。
-
Repeat 'Actions Until Condition (is true)
在这种情况下,编译器在找到“
Repeat
”标签时所做的是保存第一个操作的地址。当找到“Until
”标签时,程序需要评估条件。如果条件求值为false
,那么程序需要跳转到之前保存的块的第一个操作的地址(否则工作已完成)。 -
While Condition (is true) 'Actions Wend
在这种情况下,当编译器找到“
While
”标签时,它会保存操作的起始位置。运行时会评估“condition
”。如果为true
,执行将继续执行第一个操作;如果条件求值为false
,则执行将继续到“Wend
”标签之后。 -
For variable,Initial_Value,Final_Value,Step 'Actions Next
这有点复杂。其功能是使用“
variable
”值从“Initial_Value
”到“Final_Value
”执行“Actions
”,每次迭代将“variable
”增加或减少“Step
”。当“variable
”超过“final_value
”时,迭代完成。
首先,运行时将“Initial_Value
”赋给“variable
”。其次,评估“variable
”是否超过“Final_Value
”。如果未超过 Final_Value
,则执行所有操作,变量增加或减少,然后过程在第 2 点继续。如果超过 final_value
,则迭代完成。
将所有内容整合到 MacroAssembler
MacroAssembler 像所有高级语言一样,在您的代码中自上而下进行汇编。这意味着当汇编器尝试汇编指令(或宏)时,它知道该指令在代码中的位置(其地址)。首先,我们有一个代码中符号位置的计数器。Macroassemble 的每个位置名称将是 $sim_
加上计数器值。
由于迭代块需要知道它们的开始和/或结束位置以允许跳转到这些位置,因此提供了一些内部宏。
$pushaddr
:找到时,它会增加符号名称的计数器,并生成一个$sim_counter
名称来标识块的开始,该块等于宏所在的位置。$popaddr
:找到时,它将$sim_counter
值恢复为$jmp
变量,并减少符号名称的计数器。$makenops
:为“jmp
'xxxx'”分配一个 3 字节的代码,该代码稍后将由$filljmp
设置。$filljmp
:用“jmp
'xxxx'”填充预分配的 3 字节空间。
注意:这些宏是为 MacroAssembler 5.1 创建的。在汇编扁平内存代码时,您可能应该在 $makenops
中添加更多的 nops
,以允许代码的偏移量大于有符号字。要做到这一点,您需要编写一个小程序,例如
jmp veryfar
db 100000 dup(0)
veryfar:
... 汇编后,您可以查看“jmp veryfar
”使用了多少字节来存储指令。这些字节数就是您需要在 $makenops
中保留的 nops。请记住,如果需要,请在提供的代码中修改关键字“near
”。
每个宏的解释在包含的文本中有详细说明。这些宏可以嵌套,这意味着从现在开始,您可以像这样编写 MacroAssembler 代码:
$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
只需将提供的文本包含在代码的开头或使用 #include
。
代码
.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
关注点
在反编译了“C”代码片段后,我能够确定编译器是如何工作的,因此这些宏模仿了相同的过程。
历史
这些宏从未被修改过。