我目前正在学习MIPS,并希望对存储/加载单词进行一些澄清。
是:
sw $t0, 4($s0)
同
addi $s0, $s0, 4 # offsets are in bytes/word 8*4*4
sw $t0, 0($s0)
此外,我知道偏移量是 16 位签名的即时偏移量。但是,如果它像 32 位即时一样更大呢?
例
sw $t0, x($s0) # x is a 32bits offset
如果它像 32 位即时一样大怎么办?
然后MIPS指令字必须更长,如48位,以便仍然有空间容纳操作码和寄存器号以及32位即时码。
但我想你在问,如果你有一个太大而无法立即使用的 32 位偏移量怎么办?
显而易见的方法是自己进行所有地址计算,将 32 位值具体化到具有lui
/addiu
的临时寄存器中,然后使用addu
计算寄存器中的最终地址。 (因为 MIPS 没有 reg+reg 寻址模式,只有reg+imm16
(。 但是,我们根本不会在负载中利用imm16。
我们可以通过将偏移的低部分用作负载中的直接部分来做得更好,只需使用 2 条额外指令而不是 3 条。 编译器知道这个技巧,所以很容易显示一个示例:
char foo(char *p) {
return p[0x123456];
}
在 Godbolt 编译器资源管理器上使用 MIPS gcc5.4 编译到这个 asm:
# gcc5.4 -O3 -fno-delayed-branch
foo:
li $2,1179648 # 0x120000
addu $4,$4,$2
lb $2,13398($4) # 0x3456($4)
j $31
nop
$4
是$a0
,$2
是$v0
。 所以这是用lui/addu做reg += %hi(0x123456)
,然后使用%lo(0x123456)
作为偏移量。
如果您使用静态数组和寄存器索引,类似的技巧应该是可能的,但这不是 GCC 实际所做的。 可能是因为它不知道静态地址的低 16 是否会设置其高位,因此符号扩展为地址的上半部分创建了一个可能的 off-by-one。 也许没有重新定位类型可以解决这个问题并根据%lo()
部分的高位调整%hi()
部分,使这种优化变得不可能:/
我使用了char
,所以 C 不会缩放我的索引:数组索引 = 字节偏移量,因为sizeof(char) = 1
.lb
是将符号扩展加载到字寄存器中,因为这就是MIPS的调用约定对窄参数的工作方式。