首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >实现8051的函数调用

实现8051的函数调用
EN

Stack Overflow用户
提问于 2014-08-13 05:14:15
回答 1查看 1.1K关注 0票数 4

假设你有一个没有外部RAM的8051微控制器。内部RAM是128字节,您有大约80字节可用。你想为一种堆栈语言编写一个编译器。

假设您想编译一个RPN表达式2 3 +。8051有原生的pushpop指令,所以你可以写

代码语言:javascript
复制
push #2
push #3

然后,您可以将+实现为:

代码语言:javascript
复制
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有lcallret指令。lcall LABEL将返回地址压入堆栈并跳转到标签,而ret则返回堆栈顶部指定的地址。然而,这些操作会干扰我们的堆栈,所以如果我们执行lcall跳转到+的实现,第一条指令pop A将弹出返回地址,而不是我们想要操作的值。

在一种语言中,我们可以预先知道每个函数的参数数量,我们可以重新排列堆栈顶部的几个值,并将参数放在堆栈顶部,然后进一步向下推送返回地址。但是对于基于堆栈的语言,我们不知道每个函数需要多少参数。

那么,在这些情况下可以采取什么方法来实现函数调用呢?

下面是8051指令集描述:http://sites.fas.harvard.edu/~phys123/8051_refs/8051_instruc_set_ref.pdf

EN

回答 1

Stack Overflow用户

发布于 2014-08-13 08:56:40

这是一台非常有限的机器。

好吧,最大的问题是你想使用“栈”来保存操作数,但它也保存返回地址。因此,解决方法是:将返回地址移出,并在完成时放回原处。

您的示例:

代码语言:javascript
复制
    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“的空格,但你可以让大多数子例程的尾部变得通用:

代码语言:javascript
复制
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风格)

代码语言:javascript
复制
 byte X=2;
 byte Y=3;
 { word Q=X*Y;
   call W()
 }     

 byte S;

  W()
    { S=Q; }

我们可以分配(使用寄存器分配算法)

代码语言:javascript
复制
 X to R1
 Y to location 17
 Q to the stack
 S to R3

并生成代码

代码语言:javascript
复制
 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

这样你几乎可以得到合理的代码。(这很有趣)。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/25274153

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档