想要将一个立即数加载到寄存器中,可以使用伪指令li,但是li实际对应的指令是什么呢?汇编器是如何做的?

sext全称Sign EXTension;通过上图可以看到,立即数只有12位,那么如果我想处理的立即数不止12位怎么办?

lui指令虽然有load,但是和访存一点关系也没有,其能够设置一个寄存器的高20位的值,与此同时,低12位的值被设置为0。
那么,如果想要将一个32位的立即数加载到寄存器中,就需要结合addi和lui了,即伪指令li。

li的具体展开由汇编器来实现,会根据立即数的位数来决定展开后的指令。

shamt全称SHift AMounT,表示移位量,共6位,即最多移63位。故对于RV32而言,shamt[5]=0时才合法,否则目的寄存器为0。
在RV32I中,假设想要将32位立即数0x12345678加载到寄存器x5中:
lui x5, 0x12345 # x5:0x12345000
addi x5, x5, 0x678 # x5:0x12345678
在RV64I中,假设想要将64位立即数0x123456789ABCDE0F加载到寄存器x5中:
lui x5, 0x12345 # x5:0x0000000012345000
addi x5, x5, 0x678 # x5:0x0000000012345678
slli x5, x5, 12 # x5:0x0000012345678000
addi x5, x5, 0x9AB # x5:0x00000123456789AB
slli x5, x5, 12 # x5:0x00123456789AB000
addi x5, x5, 0xCDE # x5:0x00123456789ABCDE
slli x5, x5, 8 # x5:0x123456789ABCDE00
addi x5, x5, 0x0F # x5:0x123456789ABCDE0F
以上过程,均可以用li伪指令来替代,但汇编器在解析li时做的事情不仅是简单的转换,还需要考虑立即数的实际情况,比如考虑下面这种情况:
在RV32I中,假设想要将32位立即数0x12345FFF加载到寄存器x5中:
lui x5, 0x12345 # x5:0x12345000
addi x5, x5, 0xFFF # x5:0x12344FFF
通过上面步骤得到的x5的值是0x12344FFF,因为addi在执行加法操作前,会将立即数sext为32位,故而如果只是将li做简单转换而不分析立即数的实际情况,会出现错误,正确做法:
lui x5, 0x12346 # x5:0x12345000
addi x5, x5, 0xFFF # x5:0x12345FFF
li伪指令总结(RV32I):
接下来看看实际的程序,这里有一个C语言程序:
#include <stdio.h>
int main()
{
int a = 0x00000678;
int b = 0x12345678;
int c = 0x12345FFF;
return 0;
}
经过gcc和objdump可以得到:
# gcc -g -c li.c -o li.o
# objdump -S li.o
li.o: file format elf64-littleriscv
Disassembly of section .text:
0000000000000000 <main>:
#include <stdio.h>
int main()
{
0: 1101 add sp,sp,-32
2: ec22 sd s0,24(sp)
4: 1000 add s0,sp,32
int a = 0x00000678;
6: 67800793 li a5,1656
a: fef42623 sw a5,-20(s0)
int b=0x12345678;
e: 123457b7 lui a5,0x12345
12: 67878793 add a5,a5,1656 # 12345678 <.LASF9+0x123455a1>
16: fef42423 sw a5,-24(s0)
int c=0x12345FFF;
1a: 123467b7 lui a5,0x12346
1e: 17fd add a5,a5,-1 # 12345fff <.LASF9+0x12345f28>
20: fef42223 sw a5,-28(s0)
return 0;
24: 4781 li a5,0
26: 853e mv a0,a5
28: 6462 ld s0,24(sp)
2a: 6105 add sp,sp,32
2c: 8082 ret