在这个问题中:
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
我的原始答案是否正确?它输出的结果是否相同?如果我错了,请解释原因。
答案 0 :(得分:3)
何时以MIPS汇编语言使用临时和已保存的寄存器?
Peter Cordes的补充答案。
MIPS程序集的公共调用约定要求您保留“已保存”寄存器中的值,并允许您自由修改“临时”寄存器,这些寄存器在从代码调用子例程时生效。
由于临时寄存器可以由子程序自由修改,如果需要该值,则必须在子程序调用周围保留/恢复它们,因此临时寄存器通常用于子程序调用之间,用于可能具有有限寿命的值
“保存”寄存器必须由代码保留,以便不为上面的调用者修改它们,即无论何时使用其他新保存的寄存器,都应将其原始值放在某处(通常放入堆栈内存中)然后恢复它在返回呼叫者之前。这会带来轻微的性能损失,因此您可能希望在代码中完全避免使用“已保存”的寄存器,除非您调用多个子例程,而是通过在保存的寄存器中使用具有更长生命周期的值来获得性能,而不必保留/在每次通话时恢复它们(如果它们在临时注册中)。
所以经验法则是:
(这是“调用CONVENTION”定义的,即你可以定义+使用你自己的约定来破坏临时/保存的寄存器使用的规则..这不是在CPU内设计的东西)
答案 1 :(得分:2)
两个答案都是正确的;有许多方法来编译该表达式,使用您想要的任何寄存器,以及不同的操作顺序或不同的指令选择。
将结果写入指令作为输入读取的寄存器没有任何缺点。如果有更多的周围代码作为大型函数的一部分,那么使用较少的临时寄存器可能是一个优势。如果您将寄存器分配压力视为大函数的一部分,那么模型解决方案可能比您的更优化,反复修改$s0
而不是使用更多临时值。
但由于没有周围的代码,因此没有理由说你的代码有任何问题。将原来的x
值更长一段时间也许有用。
重新排列较短的关键路径延迟,并在超标量CPU上重新设置instruction-level parallelism 。 (例如MIPS r10k是4-wide superscalar with乱序执行)
需要三个添加/子操作,并且您的问题中的两个版本都具有贯穿所有三个ALU操作的串行依赖性。
2&#39的补码算术是关联的。但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操作。