在 Assembly 的过程调用中将寄存器保存在堆栈上

Save registers on a stack in a procedure call in Assembly

我正在学习有关汇编的入门课程,我遇到了以下代码。我不确定堆栈在这里是如何工作的。这是应该被翻译成汇编的 C 代码:(对不起,我忘了提到我们正在使用 MIPS)

int leaf_example (int g, h, i, j) {
     int f;
     f = (g + h) - (i + j);
     return f;
}

在class中,教授说正确的翻译应该是:(我在评论中添加了我的问题)

leaf_example:
   addi $sp, $sp, -12 # Why do we need to allocate 3 bytes for each register?
   sw $t1, 8($sp) # I don't really understand the meaning of this line and the next two lines
   sw $t0, 4($sp)
   sw $s0, 0($sp)
   add $t0, $a0, $a1
   add $t1, $a2, $a3
   sub $s0, $t0, $t1
   add $v0, $s0, $zero
   lw $s0, 0($sp) # After we sw the registers on the stack, why do we need to load them back?
   lw $t0, 4($sp)
   lw $t1, 8($sp)
   addi $sp, $sp, 12
   jr $ra

我想我对$s寄存器和$t寄存器的区别有一个模糊的理解:在调用函数时,$s寄存器的内容保持不变,但不保证$t寄存器的内容保持不变.但是这个事实与将寄存器压入堆栈有什么关系呢?非常感谢您的帮助!

没有保证,只有约定。您可以编写一个破坏 $s 寄存器或做其他坏事的函数。惯例是preserve $s0-$s7, $gp, $sp and $fp。如果不这样做,很可能会导致调用链上的某个函数以某种方式失败(如果评分,则会扣分)。

所以如果你要使用 $s 寄存器,你必须以某种方式备份它们,并在返回之前恢复它们。

这就是代码的作用,尽管我认为称其为“ 正确的翻译”具有误导性。这是一种方式。但它也保留了 $t 寄存器,虽然它不是必须的。它还可以将 $s0 保存在 $t 寄存器中。它甚至可以首先不使用 $0,这更简单。它只将结果放在 $s0 中 立即 将它移到 $v0 无论如何,它可以直接把它放在那里然后一切都变得容易了:

leaf_example:
   add $t0, $a0, $a1
   add $t1, $a2, $a3
   sub $v0, $t0, $t1
   jr $ra

没有更多的废话。

但也许目标是展示叶函数的一般工作原理。或者您可能必须严格遵守一些特定的翻译规则,例如始终专门为局部变量分配寄存器并始终为 return x 语句生成 $v0 的移动。

  addi $sp, $sp, -12 Allocate space for 3-4byte registers
  sw $t1, 8($sp)   Store the t1 register 8 bytes from the current SP location
  sw $t0, 4($sp)    Store the t0 register 4 bytes . . . .
  sw $s0, 0($sp)   Store the s0 register at the end of the stack.
  add $t0, $a0, $a1
  add $t1, $a2, $a3
  sub $s0, $t0, $t1
  add $v0, $s0, $zero
  // Now put the registers back the way they were before the function was called.
  // If  you don't do that, the caller will find the register values have changed.
  lw $t0, 4($sp) // This reverses the process above.
  lw $t1, 8($sp)
  addi $sp, $sp, 12
  jr $ra

不是恢复S0寄存器。所以它可能一开始就不需要保存它。