这是一台非常有限的机器。
好的,最大的问题是您想使用“堆栈”来保存操作数,但它也保存返回地址。所以解决方法:当返回地址妨碍时将其移开,完成后将其放回。
你的例子:
push #2
push #3
lcall my_add
...
myadd:
pop r6 ; save the return address
pop r7
pop a
pop b
add a, b
push a
push r7
push r8
ret
我的猜测是“保存返回地址”、“恢复返回地址”会很常见。我不知道如何对“保存返回地址”进行空间优化,但您可以使大多数子例程的尾部通用:
myadd:
pop r6 ; save the return address
pop r7
pop a
pop b
add a, b
jmp push_a_return
...
; compiler library of commonly used code:
push_ab_return: ; used by subroutines that return answer in AB
push b
push_a_return: ; used by subroutines that return answer in A
push a
return: ; used by subroutines that don't produce a result in register
push r7
push r6
ret
push_b_return: ; used by subroutines that compute answer in B
push b
jmpshort return
但是,您的大部分麻烦似乎是坚持要将操作数压入堆栈。然后,您在返回地址方面遇到了麻烦。你的编译器当然可以处理这个问题,但是你遇到麻烦的事实表明你应该做其他事情,例如,如果你可以帮助它,不要将操作数放在堆栈上。
相反,您的编译器还可以生成面向寄存器的代码,尽可能将操作数保存在寄存器中。毕竟,你有 8 个(我认为)R0..R7 和 A 和 B 很容易访问。
所以你应该做的是首先弄清楚所有操作数(都是由原始程序员命名的,以及编译器需要的临时对象[比如 3-address code] 和操作在你的代码中。其次,应用某种寄存器分配(查找寄存器着色以获得一个很好的示例)以确定哪些操作数将在 R0..R7 中,应用相同的技术将未分配给寄存器的命名变量分配给您的直接寻址(将它们分配给位置 8-'top' , 比如说), 第三次用于你有一些额外空间的临时对象(将它们分配到位置 'top' 到 64). 这会在生成时将其余部分强制放入堆栈, 位置为 65 到 127. (坦率地说,我怀疑你最终会在这个方案中得到很多堆栈,除非你的程序对于 8051 来说太大了)。
一旦每个操作数都有一个指定的位置,代码生成就很容易了。如果已在寄存器中分配了操作数,则可以使用 A、B 和适当的算术计算它,或者使用 MOV 来填充或存储它,如三地址指令所示。
如果操作数在堆栈上,则将其弹出到 A 或 B 如果在顶部;如果它嵌套在堆栈中“深”,您可能会做一些花哨的寻址以到达其实际位置。如果生成的代码在被调用的子程序中并且操作数在堆栈上,则使用返回地址保存技巧;如果 R6 和 R7 忙,则将返回地址保存在另一个寄存器组中。您可能只需要在每个子例程中最多保存一次返回值。
如果堆栈由交错的返回地址和变量组成,编译器实际上可以计算所需变量的位置,并使用堆栈指针的复杂索引来获取它。仅当您跨多个嵌套函数调用进行寻址时,才会发生这种情况;大多数 C 实现不允许这样做(GCC 允许)。所以你可以取缔这个案子,或者根据你的野心决定处理它。
所以对于程序(C风格)
byte X=2;
byte Y=3;
{ word Q=X*Y;
call W()
}
byte S;
W()
{ S=Q; }
我们可以分配(使用寄存器分配算法)
X to R1
Y to location 17
Q to the stack
S to R3
并生成代码
MOV R1,2
MOV A, 3
MOV 17, A
MOV A, 17
MOV B, A
MOV A, R1
MUL
PUSH A ; Q lives on the stack
PUSH B
CALL W
POP A ; Q no longer needed
POP B
...
W:
POP R6
POP R7
POP A
POP B
MOV R3, B
JMP PUSH_AB_RETURN
你几乎可以得到合理的代码。(那很有趣)。