这个 MIPS 过程调用的错误是什么?

What is mistake of this MIPS procedure call?

此 MIPS 代码包含调用另一个函数 foo(a,b) 的函数 int func(int a, int b)。

func: addi $sp,$sp,-4    
      sw $ra, 0($sp)    
      addi $a0,$a0,1    
      addi $a1,$a1,2    
      jal foo
      addi $a0,$v0,4    
      add $v0,$a0,$a1    
      lw $ra,0($sp)    
      addi $sp,$sp,4    
      jr $ra

这段代码有一些错误,但我不知道哪里错了。

最严重的违规行为是 func 期望 $a1 在对 foo 的函数调用后继续存在——这是不正确的。根据调用约定,$a1 是一个参数寄存器,不会被函数调用保留。 $a1 也不是 return 值,因此在函数调用后应将其视为未初始化,即它包含无用的垃圾。

func: addi $sp,$sp,-4    
      sw $ra, 0($sp)    
      addi $a0,$a0,1    
      addi $a1,$a1,2    
      jal foo           # this call effectively wipes out argument registers (ok)
      addi $a0,$v0,4    # here the function re-initializes $a0 from return value $v0 (ok)
      add $v0,$a0,$a1   # but here it uses uninitliazed $a1 (not ok)
      lw $ra,0($sp)    
      addi $sp,$sp,4    
      jr $ra

保存funcs $a1 参数的正确方法是将其存储在内存中,稍后在函数调用后将其加载回来;这将需要在堆栈帧中添加一个词。

func: addiu $sp,$sp,-8    
      sw $ra, 4($sp)    
      addi $a0,$a0,1    
      addi $a1,$a1,2 
      sw $a1, 0($sp)    # save a1 in stack before call
      jal foo           # this call effectively wipes out argument registers
      lw $a1, 0($sp)    # restore a1 from stack after call
      addi $a0,$v0,4    # here the function re-initializes $a0 from return value $v0
      add $v0,$a0,$a1   # now using re-initialized $a1 (ok)
      lw $ra,4($sp)    
      addiu $sp,$sp,8    
      jr $ra

在上面,我保存了更新后的$a1寄存器,但是可以肯定的是,仅仅阅读代码并不清楚原始的,未增加的$a1 或更新后的 $a1 是实际需要的。看起来 $a1 正在作为参数传递给 foo,所以可能是 foo 想要递增的值,但 func 后来却没有。

或者,可以将 $a1 存储在 $s 寄存器中,因为调用约定确保在函数调用中保留这些寄存器——但是,根据这个定义,$s 注册自己,如果被调用者使用,必须保存和恢复,所以堆栈帧中的一个额外的字仍然是必要的。


其他违反 MIPS 调用约定的是:

  • MIPS CC 要求堆栈帧按 8 字节对齐,因此即使您只需要一个字,我们也应该将堆栈帧大小四舍五入为 8 字节的倍数。然而,许多汇编程序员忽略了这一点,并没有造成严重后果。

  • MIPS CC 还规定 space 将 4 个参数寄存器保存在堆栈中,并且此 space 由调用者提供,以便被调用者可以使用它(不需要被调用者分配任何堆栈)。尽管技术上有要求,但汇编程序员几乎从不在简单的任务中这样做。如果被调用者利用了这 4 个本来应该存在的词,但它们不存在,就会发生不好的事情。 (我没有在上面的解决方案中解决这个问题。)在编写调用 varargs 函数的函数时,我会最低限度地遵循这个要求,比如 printf、sprintf、scanf 等。这个保存区域用于 4 个参数寄存器与第 5 个、第 6 个等参数的内存连续,使整个参数块在内存中连续,这在可变参数函数的实现中很有用。

https://courses.cs.washington.edu/courses/cse410/09sp/examples/MIPSCallingConventionsSummary.pdf


此外,用于分配和释放堆栈的 addi space 比 addiu 更好——因为这是指针算法(地址是无符号的),有符号整数溢出在最好的,最坏的是有害的(通过导致不相关的溢出异常)。