据我所知,这两个指令的操作码和功能码都为0,那么计算机如何知道它正在执行哪个呢?
正如Jester所说,MIPS CPU如果不想进行任何特殊的性能优化,就不必进行区分。由于对$0的写入被丢弃,并且它没有副作用(MIPS没有FLAGS/condition-codes寄存器),因此sll $0, $0, 0
已经没有架构效果。因此,简单的硬件可以让它通过管道运行。
约定一个nop
操作码的重点在于,如果硬件想要查找一个特殊情况并且甚至不执行它,那么只有一个比特模式需要匹配,而不是考虑每个写入到$0
的指令选择(除了可能仍然故障的负载)。
因此,编译器/汇编器的开发人员不必猜测nop
对某些硬件来说可能是最便宜的。硬件可能会做的事情包括:
nop
和sll $t0, $t1, 12
,即使它通常无法同时运行两个移位。$0
上的每次读写,那么可以跳过nop
。(仅适用于没有旁路转发的玩具CPU,见下文。)sll $t0, $t0, 0
没有架构效果,因为它将寄存器写入了已经存在的相同值。但这是一个更糟糕的选择,因为冒险检测通常会关注寄存器的读写操作,所以它可能导致停顿,或者在类似 R10000 的乱序执行 MIPS 上导致更长的依赖链对于 $t0
。然而,如果 MIPS 没有零寄存器,那么将其作为 NOP 是一个合理的选择,除了在 MIPS 1 中,将其放置在写入 $t0
的加载延迟槽中是不安全的。
lw $0, (mem)
/ nop
在硬件中是否存在任何可能的边角情况,这些硬件并没有特别处理nop
或零寄存器,而是仅在寄存器文件中处理写入丢弃和读取为零的零寄存器?
在这种情况下,它将看到一个指令在下一条指令中读取负载结果。在MIPS I上,由于它不会为无法处理的旁路转发而停顿,因此会产生不可预测的读取值。(在后来的MIPS上,它会停顿;负载延迟槽不是体系结构,只是性能问题。)当然,结果最终只是写入零寄存器,因此没有可观察的差异。
但是,如果简单的硬件没有为旁路转发特别处理零寄存器,则类似addiu $0, $0, 123
/ addi $1, $0, 0
的操作可能会复制非零值。因此,正确的MIPS设计不能如此简单,并且必须在进行旁路转发的危险检测时已经特别处理了$zero
,以确保它不会旁路转发到从$0
读取的值,该值始终为零。
$0
,因为所有值都会经过寄存器文件。但这可能不是MIPS架构师考虑的因素,因为在大多数代码中,旁路转发对于良好的性能至关重要。
nop
是sll $0, $0, 0
的别名,它什么也不做。当然,如果硬件希望的话,它可以特别处理。 - Jester