![](/img/trans.png)
[英]RISC-V generate -1 / 0xFFFFFFFF in a register with LUI / ADDI?
[英]RISC-V build 32-bit constants with LUI and ADDI
LUI(加载上位立即数)用于构建 32 位常量并使用 U 型格式。 LUI 将 U 立即数放在目标寄存器 rd 的前 20 位,用零填充最低的 12 位。
我在手册中找到了这个,但是如果我想将 0xffffffff 移动到寄存器,我需要的所有代码是:
LUI x2, 0xfffff000
ADDI x2, x2, 0xfff
但是出现了一个问题,ADDI 会将符号扩展为立即数数据为有符号数,因此0xfff
将扩展为0xffffffff
。
它使x2
到0xffffefff
但不0xffffffff
将 32 位立即移动到寄存器的好方法是什么?
RISC-V 汇编器支持伪指令li x2, 0xFFFFFFFF
。
令N
是一个有符号的 2 的补码 32 位整数。
li x2,N
常见情况实现是:
# sign extend low 12 bits
M=(N << 20) >> 20
# Upper 20 bits
K=((N-M) >> 12) <<12
# Load upper 20 bits
LUI x2,K
# Add lower bits
ADDI x2,x2,M
当然,加载短立即li
可以使用
addi x2,x0,imm
所以, li x2, 0xFFFFFFFF
是addi x2,x0,-1
。
TL;DR:您要加载到x2
的 32 位常量是0xffffffff
,它对应于-1 。 由于-1在[-2048, 2047]范围内,因此可以使用单个指令加载此常量: addi x2, zero, -1
。 您还可以使用li
伪指令: li, x2, -1
,汇编程序反过来将其转换为addi x2, zero, -1
。
lui
+ addi
序列加载 32 位常量一般来说,我们需要一个lui
+ addi
序列——两条指令——来将 32 位常量加载到寄存器中。 lui
指令编码 20 位立即数,而addi
指令编码 12 位立即数。 lui
和addi
可用于分别加载 32 位常量的高 20 位和低 12 位。
让N是一个我们想要加载到寄存器中的 32 位常量: N ≡ n 31 ... n 0 。 然后,我们可以将这个常数分成高 20 位和低 12 位,分别为N U和N L : N U ≡ n 31 ... n 12 ; N L ≡ n 11 ... n 0
原则上,我们编码N个U在眼前的lui
和N L的眼前的addi
。 because the immediate value encoded in the addi
instruction is to 32 bits.然而,如果addi
12 位立即数的最高有效位为 ,则处理起来很困难,因为addi
指令中编码的立即数被为 32 位。 instead — -4096 (or -2 12 ) is the resulting number when the upper 20 bits are s and the lower 12 bits are s.如果是这种情况, addi
指令将添加到目标寄存器而不是N L ,而是 - -4096 (或 -2 12 )是当高 20 位是和低 12 位时的结果数是秒。
为了补偿不希望长期-4096,我们可以添加1〜 lui
的眼前- LSB的在不久的lui
对应位#12 -所以,在添加4096到抵消了目标寄存器加1,这个立竿见影的效果-4096项。
addi
指令加载 32 位常量上面解释的问题是由于直接 in addi
经历的符号扩展。 决定扩展addi
的立即数的决定可能是允许加载小整数-介于-2048和2047之间的整数,两者都包括在内 - 使用单个addi
指令。 例如,如果addi
中的立即数是零扩展而不是符号扩展,则不可能仅用一条指令将像-1这样的频繁常量加载到寄存器中。
li
伪指令加载 32 位常量在任何情况下,您始终可以使用li
伪指令加载 32 位常量,而不必关心要加载的常量的值是什么。 该伪指令可以将任何 32 位数字加载到寄存器中,因此它比手动编写lui
+ addi
序列更易于使用且不易出错。
如果数字适合addi
的立即数字段( [-2048, 2047] ),汇编器会将li
伪指令翻译成一条addi
指令,否则, li
将被翻译成lui
+ addi
序列,上面解释的复杂情况是由汇编程序自动处理。
我本想说“使用ORI
而不是ADDI
”,但后来我阅读了指令集手册,结果证明这也不起作用,因为所有低 12 个立即操作数都得到了符号扩展,即使对于逻辑操作也是如此.
AFAICT 您必须以预期用于设置低 12 位的指令的效果的方式来偏置您放入高 20 位的值。 所以如果你想在前 20 位得到一个值 X 并且你要使用ADDI
来设置低 12 位,而那些低 12 位在最左边的位置有一个 1,你必须做LUI (X+1)
而不是LUI X
。 同样,如果你要使用XORI
来设置低12位,而那些低12位在最左边的位置是1,你必须做LUI (~X)
(即LUI (~X)
的按位逆)而不是LUI X
.
但是在你做任何这些之前,我会看看你的汇编程序是否已经有某种“立即加载”伪操作或宏来为你处理这个问题。 如果没有,那么看看你是否可以写一个:-)
RISC 处理器需要程序员(或者更常见的是编译器)的这种额外努力并不罕见。 这个想法是“保持硬件简单,以便它可以快速运行,如果这会使构建软件变得更加困难,这并不重要”。
在实践中,如果可能的话,只需使用li
伪指令让汇编器优化到一条指令(单个 lui 或单个 addi),如果不是,则为您进行数学计算。
li t0, 0x12345678
li t1, 123
li t2, -1
li t3, 0xffffffff # same as -1 in 32-bit 2's complement
li t4, 1<<17
我用空格分隔了每个“组”。 只有第一个(进入t0
)需要两条指令。
$ clang -c -target riscv32 rv.s # on my x86-64 Arch GNU/Linux desktop
$ llvm-objdump -d rv.o
...
00000000 <.text>:
0: 01 00 nop
2: 01 00 nop
4: b7 52 34 12 lui t0, 74565
8: 93 82 82 67 addi t0, t0, 1656
c: 13 03 b0 07 addi t1, zero, 123
10: fd 53 addi t2, zero, -1
12: 7d 5e addi t3, zero, -1
14: b7 0e 02 00 lui t4, 32
如果您确实想手动执行此操作,大多数 RISC-V(或至少 GAS/clang)的汇编程序都有%lo
和%hi
“宏”,因此您可以lui dst, %hi(value)
/ addi dst, dst, %lo(value)
。
lui x9, %hi(0x12345678)
addi x9, x9, %lo(0x12345678)
lui x10, %hi(0xFFFFFFFF)
addi x10, x10, %lo(0xFFFFFFFF)
用 clang 组装,再次用 llvm-objdump 反汇编:
18: b7 54 34 12 lui s1, 74565
1c: 93 84 84 67 addi s1, s1, 1656
20: 37 05 00 00 lui a0, 0
24: 7d 15 addi a0, a0, -1
请注意, lui a0, 0
是对一条指令的愚蠢浪费,这是由于天真地在 0xffffffff 上使用 hi/lo 而没有意识到整个事情适合符号扩展的 12 位立即数。
手动 %hi/%lo 有很好的用例,特别是对于地址,你有一个对齐的“锚”点,然后想要加载或存储到某个标签:
lui t0, %hi(symbol)
lw t1, %lo(symbol)(t0)
lw t2, %lo(symbol2)(t0)
addi t3, t0, %lo(symbol3) # also put an address in a register
...
sw t1, %lo(symbol)(t0)
因此,与其浪费指令为每个符号做一个单独的 lui,如果你知道它们在同一个 2k 对齐的块中,你可以在汇编程序的帮助下相对于一个基数引用它们。 或者实际上是一个 4k 对齐的块,中间有“锚点”,因为%lo
可以是负数。
(使用auipc
的 PC 相关版本同样高效,但看起来有些不同: %pcrel_hi 和 %pcrel_lo 实际上是做什么的? - %pcrel_lo 实际上引用了 %pcrel_hi 重定位以找出实际的目标符号以及相对参考的位置。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.