为什么 lw 指令的第二个参数同时包含偏移量和 regSource?
Why does the lw instruction's second argument take in both an offset and regSource?
所以lw指令的格式如下:lw RegDest, Offset(RegSource)
。为什么第二个参数同时接受偏移量和寄存器源?为什么不只有一个(即只注册源)?
因为您还打算用剩下的 32 位指令字做什么? (假设您是 CPU 设计 MIPS 指令集的架构师)。
它让 LUI + LW 在 2 条指令中从任意 32 位地址加载,而不是 3。对于循环展开或结构指针->成员访问,避免指针数学的 ADDIU 指令。即 在 LW/SW 上花费 space 的编码量可以使 MIPS 程序更高效。 有时您只需要 0($reg)
,但其他时候它会计算寄存器中的最终地址是浪费指令。
省略16位立即数位移并不能使指令更短。 MIPS 是具有固定长度指令字的 RISC。 (它可能是 R 型而不是 I 型,但你仍然会有那种格式的未使用位。经典 MIPS 有很多未使用的编码 space,并且在 [=63] 上花费编码 space =], LB/LBU/SB, 等等, 是值得的。)
MIPS 没有很多不同的操作码(尤其是没有任何 FPU 指令和 64 位指令的经典 MIPS)。它使用了大量的指令编码space来支持大多数指令的立即数形式,立即数很大。 (例如,与 ARM32 不同,ARM32 在每条指令中使用 4 位用于预测执行,而更多位用于 "flexible" 源操作数(可选地旋转或移位常量或另一个寄存器,或立即数常量)。但是 ARM 立即数被编码作为 8 位旋转,允许许多在现实生活中常见的有用位模式。)
MIPS 只有一种寻址模式,与 (reg)
.[ 相比,imm16(reg)
可以节省大量 addiu
条指令。 =23=]
例如,考虑加载或存储到静态(或全局)变量的 C 函数。喜欢
unsigned rng(void) {
static unsigned seed = 1234;
return (seed = seed * 5678 + 0x1234);
}
编译器生成的(或手写的)asm 需要从 seed
加载和存储,所以你需要它在寄存器中。但它是一个 32 位常量,不适合单条指令。在手写的 asm 中,您可能会使用像 la $t0, rng.seed
这样的伪指令,它将 assemble 转换为 lui $t0, hi(rng.seed)
/ ori $t0, $t0, lo(rng.seed)
。 (hi 和 lo 得到 32 位地址的一半)。
但你可以做得更好:
lui $t0, hi(rng.seed)
lw $t1, lo(rng.seed) ($t0)
即使用地址的低16位作为加载指令中的16位位移。这实际上就是 compilers like gcc do:
rng: # gcc5.4 -O3
lui ,%hi(seed.1482)
lw ,%lo(seed.1482)()
nop ; classic MIPS has a 1-cycle "shadow" for loads before the result is usable, with no pipeline interlock
sll ,,5 ; I should have picked a simpler multiply constant (with fewer bits set)
sll ,,3
subu ,,
sll ,,3
subu ,,
subu ,,
sll ,,4
addu ,,
sll ,,1
addiu ,,4660
j
sw ,%lo(seed.1482)() ; branch-delay slot
seed.1482:
.word 1234
寄存器的小立即位移还有很多其他用途。例如:
- 如果编译器溢出任何内容,则访问堆栈上的局部变量
struct
字段
- 展开循环中的数组访问。 (MIPS 有 32 个整数寄存器,几乎是为软件流水线设计的,以展开循环)。
- 小型编译时常量数组索引。
如我所说,对于那些非常适合 MIPS 的额外 16 位指令字,您无能为力。您可以保留少于 16 位的位移,但 MIPS 不是 PowerPC(那里有很多很多操作码)。
所以lw指令的格式如下:lw RegDest, Offset(RegSource)
。为什么第二个参数同时接受偏移量和寄存器源?为什么不只有一个(即只注册源)?
因为您还打算用剩下的 32 位指令字做什么? (假设您是 CPU 设计 MIPS 指令集的架构师)。
它让 LUI + LW 在 2 条指令中从任意 32 位地址加载,而不是 3。对于循环展开或结构指针->成员访问,避免指针数学的 ADDIU 指令。即 在 LW/SW 上花费 space 的编码量可以使 MIPS 程序更高效。 有时您只需要 0($reg)
,但其他时候它会计算寄存器中的最终地址是浪费指令。
省略16位立即数位移并不能使指令更短。 MIPS 是具有固定长度指令字的 RISC。 (它可能是 R 型而不是 I 型,但你仍然会有那种格式的未使用位。经典 MIPS 有很多未使用的编码 space,并且在 [=63] 上花费编码 space =], LB/LBU/SB, 等等, 是值得的。)
MIPS 没有很多不同的操作码(尤其是没有任何 FPU 指令和 64 位指令的经典 MIPS)。它使用了大量的指令编码space来支持大多数指令的立即数形式,立即数很大。 (例如,与 ARM32 不同,ARM32 在每条指令中使用 4 位用于预测执行,而更多位用于 "flexible" 源操作数(可选地旋转或移位常量或另一个寄存器,或立即数常量)。但是 ARM 立即数被编码作为 8 位旋转,允许许多在现实生活中常见的有用位模式。)
MIPS 只有一种寻址模式,与 (reg)
.[ 相比,imm16(reg)
可以节省大量 addiu
条指令。 =23=]
例如,考虑加载或存储到静态(或全局)变量的 C 函数。喜欢
unsigned rng(void) {
static unsigned seed = 1234;
return (seed = seed * 5678 + 0x1234);
}
编译器生成的(或手写的)asm 需要从 seed
加载和存储,所以你需要它在寄存器中。但它是一个 32 位常量,不适合单条指令。在手写的 asm 中,您可能会使用像 la $t0, rng.seed
这样的伪指令,它将 assemble 转换为 lui $t0, hi(rng.seed)
/ ori $t0, $t0, lo(rng.seed)
。 (hi 和 lo 得到 32 位地址的一半)。
但你可以做得更好:
lui $t0, hi(rng.seed)
lw $t1, lo(rng.seed) ($t0)
即使用地址的低16位作为加载指令中的16位位移。这实际上就是 compilers like gcc do:
rng: # gcc5.4 -O3
lui ,%hi(seed.1482)
lw ,%lo(seed.1482)()
nop ; classic MIPS has a 1-cycle "shadow" for loads before the result is usable, with no pipeline interlock
sll ,,5 ; I should have picked a simpler multiply constant (with fewer bits set)
sll ,,3
subu ,,
sll ,,3
subu ,,
subu ,,
sll ,,4
addu ,,
sll ,,1
addiu ,,4660
j
sw ,%lo(seed.1482)() ; branch-delay slot
seed.1482:
.word 1234
寄存器的小立即位移还有很多其他用途。例如:
- 如果编译器溢出任何内容,则访问堆栈上的局部变量
struct
字段- 展开循环中的数组访问。 (MIPS 有 32 个整数寄存器,几乎是为软件流水线设计的,以展开循环)。
- 小型编译时常量数组索引。
如我所说,对于那些非常适合 MIPS 的额外 16 位指令字,您无能为力。您可以保留少于 16 位的位移,但 MIPS 不是 PowerPC(那里有很多很多操作码)。