In practice, just use an li
pseudo-instruction that gets the assembler to optimize to one instruction if possible (a single lui or a single addi), and if not does the math for you.
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
I separated each "group" with spaces. Only the first one (into t0
) needed two instructions.
$ 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
If you do want to do it manually, most assemblers for RISC-V (or at least GAS / clang) have %lo
and %hi
"macros" so you can 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)
assemble with clang, disassemble with llvm-objdump again:
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
Note that lui a0, 0
is a silly waste of an instruction that results from naively using hi/lo on 0xffffffff without realizing that the whole thing fits in a sign-extended 12-bit immediate.
There are good use-cases for manual %hi/%lo, especially for addresses, where you have one aligned "anchor" point and want to load or store to some label after that:
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)
So instead of wasting instructions doing a separate lui for each symbol, if you know they're in the same 2k aligned block you can reference them all relative to one base with the assembler's help. Or actually to a 4k aligned block with the "anchor" in the middle, since %lo
can be negative.
(The PC-relative version of this with auipc
is just as efficient but looks a little different: What do %pcrel_hi and %pcrel_lo actually do? - %pcrel_lo actually references a %pcrel_hi relocation to find out the actual target symbol as well as the location of the relative reference.)