假设你有一个没有外部RAM的8051微控制器。内部RAM是128字节,您有大约80字节可用。你想为一种堆栈语言编写一个编译器。
假设您想编译一个RPN表达式2 3 +。8051有原生的push和pop指令,所以你可以写
push #2
push #3然后,您可以将+实现为:
pop A ; pop 2 into register A
pop B ; pop 3 into register B
add A, B ; A = A + B
push A ; push the result on the stack很简单,对吧?但在本例中,+是作为内联程序集实现的。如果您想重用这些代码,并将其放入子例程中,该怎么办?幸运的是,8051有lcall和ret指令。lcall LABEL将返回地址压入堆栈并跳转到标签,而ret则返回堆栈顶部指定的地址。然而,这些操作会干扰我们的堆栈,所以如果我们执行lcall跳转到+的实现,第一条指令pop A将弹出返回地址,而不是我们想要操作的值。
在一种语言中,我们可以预先知道每个函数的参数数量,我们可以重新排列堆栈顶部的几个值,并将参数放在堆栈顶部,然后进一步向下推送返回地址。但是对于基于堆栈的语言,我们不知道每个函数需要多少参数。
那么,在这些情况下可以采取什么方法来实现函数调用呢?
下面是8051指令集描述:http://sites.fas.harvard.edu/~phys123/8051_refs/8051_instruc_set_ref.pdf
发布于 2014-08-13 08:56:40
这是一台非常有限的机器。
好吧,最大的问题是你想使用“栈”来保存操作数,但它也保存返回地址。因此,解决方法是:将返回地址移出,并在完成时放回原处。
您的示例:
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我的猜测是“保存返回地址”、“恢复返回地址”将非常常见。我不知道如何优化"save return address“的空格,但你可以让大多数子例程的尾部变得通用:
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地址代码和操作指定的临时值)都在你的代码中。其次,应用某种类型的寄存器分配(查看寄存器着色以获得一个很好的示例)来确定哪些操作数将在R0..R7中,应用相同的技术将未分配给寄存器的命名变量分配给可直接寻址的(例如,将它们分配到位置8-'top‘),第三次将它们分配给有额外空间的临时变量(将它们的位置'top’分配到64)。当它们被生成时,THis强制其余部分进入堆栈,其位置为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这样你几乎可以得到合理的代码。(这很有趣)。
https://stackoverflow.com/questions/25274153
复制相似问题