何时使用 MIPS 汇编语言中的临时寄存器和保存寄存器?

When to use temporary and saved registers in MIPS Assembly language?

本题中:

x = x + y - 10 - A[20]

x - $s0
y - $s1
A - $s3

对于我的回答,我写道:

add   $t0,   $s0,   $s1    # value of x ($s0) + y ($s1) gets stored in temp $t0
addi  $t0,   $t0,  - 10    # subtracts value of $t0 from 10. $t0 now holds the new value
lw    $t1,   80($s3)       # loads value of A[20] into new temp $t1
sub   $s0,   $t0,  $t1     # subtracts values $t0 from $t1 and stores it in x 
                           ($s0)

但是模型解决方案说:

add   $s0,   $s0,   $s1   
addi  $s0,   $s0,  - 10    
lw    $t0,   80($s3)   
sub   $s0,   $s0,  $t0

我原来的回答正确吗?不是输出相同的结果吗?如果我错了,请解释原因。

两个答案都正确;有许多 种方法来编译该表达式,使用您想要的任何寄存器以及不同的操作顺序或不同的指令选择。

将结果写入指令读取为输入的寄存器没有任何缺点。如果有更多的外围代码作为大型函数的一部分,则使用更少的临时寄存器可能是一个优势。如果您将寄存器分配压力视为大型函数的一部分,则模型解决方案可以说比您的更优化,反复修改 $s0 而不是使用更多的临时对象。

但是因为没有周边代码,所以没有理由说你的有什么问题。也许将原始 x 值延长一些会很有用。


重新排列以获得更短的关键路径延迟,instruction-level parallelism on a superscalar CPU. (e.g. MIPS r10k 是乱序执行的 4 宽超标量)

需要三个添加/子操作,您问题中的两个版本都具有通过所有三个 ALU 操作的串行依赖性。

2 的补码算法是关联的。但是 MIPS add 会在有符号溢出时出错,所以创建什么临时结果很重要(就像具有舍入错误的 FP 一样)。但是 MIPS 也有 addu 包装而不是错误,所以如果你不关心在有符号溢出时引发异常,请使用 addu / addiu / subu 然后例如,您可以将操作重新排序为 (x - A[20]) + (y - 10)

lw    $t0,   80($s3)       # load as early as possible
addiu $t1,   $s1,  -10     # y-10 in the shadow of the load delay slot
subu  $s0,   $s0,  $t0     # x-A[20]
addu  $s0,   $s0,  $t1

请注意 x ($s0) 在第 3 条指令之前不必准备就绪,因此我们可以隐藏 x 输入的一些延迟。如果您知道您的操作数来自什么指令,请对您的操作进行排序,以便最后需要可能最后准备好的操作。 (特别是如果你正在调整有序 CPU,不像 r10k)

当然,如果您预计会有一些加载延迟,那么在使用加载结果之前完成所有 ALU 工作会更有意义,即使这意味着通过使它们依赖于每个操作来序列化所有三个 add/sub 操作其他

When to use temporary and saved registers in MIPS Assembly language?

Peter Cordes 回答的附录。

MIPS 汇编的通用调用约定要求您保留 "saved" 寄存器中的值,并允许您自由修改 "temporary" 寄存器,这在您调用子程序时生效来自您的代码。

由于子程序可以自由修改临时寄存器,如果需要值,则必须在子程序调用周围preserve/restore它们,因此临时寄存器通常用于子程序调用之间的值可以有有限的生命周期。

"saved" 寄存器必须由您的代码保留,而不是为上面的调用者修改它们,即每当您使用另一个新保存的寄存器时,您应该将其原始值放在某个地方(通常在堆栈内存中) ) 然后在返回给调用者之前恢复它。这会对性能造成轻微影响,因此您可能希望避免在代码中完全使用 "saved" 寄存器,除非您正在调用多个子例程,并且您将通过在保存的寄存器中使用具有更长生命周期的值来提高性能,而不是到 preserve/restore 每次调用它们(如果它们在临时寄存器中)。

根据经验法则:

  1. 根本不使用register/value(通过更聪明的算法或代码结构消除,更好地重用已经受影响的寄存器)
  2. 对生命周期有限的值使用临时的,特别是如果它不与子例程调用冲突
  3. 将保存的寄存器用于在子程序调用期间必须保留的值(尤其是几个子程序调用)

(这是 "calling CONVENTION" 定义的,即您可以定义 + 使用自己的约定来打破 temporary/saved 寄存器使用的规则。这不是 CPU 内部设计的东西)