assemblyriscv

What do %pcrel_hi and %pcrel_lo actually do?


In Control and Status Registers section of riscv-asm-manual, there is an example:

.equ RTC_BASE,      0x40000000
.equ TIMER_BASE,    0x40004000

# setup machine trap vector
1:      auipc   t0, %pcrel_hi(mtvec)        # load mtvec(hi)
        addi    t0, t0, %pcrel_lo(1b)       # load mtvec(lo)
        csrrw   zero, mtvec, t0

...
# break on interrupt
mtvec:
        csrrc  t0, mcause, zero
        bgez t0, fail       # interrupt causes are less than zero
        slli t0, t0, 1      # shift off high bit
...

I guess %pcrel_hi(mtvec) calculate the hi-distance between mtvec and current PC (here is the address of 1 symbol). Suppose the address of 1 symbol is 0x80010000, and that of mtvec is 0x80020040. Then %pcrel_hi(mtvec) = (0x80020040 - 0x80010000) >> 12 = 0x00010, so the result of auipc is 0x00010 << 12 + PC = 0x00010000 + 0x80010000 = 0x80020000.

But %pcrel_lo takes 1b as its argument. How to calculate its result and get the final address of mtvec? addi t0, t0, %pcrel_lo(mtvect) seems to be the intuitive code, but actually not. Why?


Solution

  • As indicated by peter Cordes in his commen,in your link and also in this one. For the addi, a label is used and not the symbol directly because the addi must contain the 12 low significant bits of relative address between pc and symbol. however the pc must be the same as the one used for the pcrel_high and because there is no guarantee from the binutils point of view that these two instructions will follow each other (which would have made it possible to calculate differently). So the solution that was chosen, to provide the right pc, was to use a label.

    Now for the 1b in your exemple, it is not a result of a calculation. All the calculations are done at the linker level, the assembler just takes care of generating the necessary relocation information. The 1b means the backward label 1. Numeric labels are used for local references. References to local labels are suffixed with 'f' for a forward reference or 'b' for a backwards reference ( it is present in your link also).

    If you assemble this assembly file, you will see that the label will be changed to something like .L1XXX depending on your assembler version and then if you do an riscv-objdump -r you will see that you have a R_RISCV_PCREL_LO12_I to this label on the offset corresponding to the addi.

    Basically you will have something like :

    OFFSET           TYPE              VALUE 
    0000000000000000 R_RISCV_PCREL_HI20  mtvec
    0000000000000000 R_RISCV_RELAX     *ABS*
    0000000000000004 R_RISCV_PCREL_LO12_I  .L1^B
    

    In this example the offset 0 is the offset of .L1^B1 ( the label 1 which was transformed). So the linker will use this relocation to calculate the value that will be used for the auipc.

    Then for the offset 4 which is the offset of the addi instruction, The linker will find the R_RISCV_PCREL_LO12_I relocation. it will use The value .L1^B1 to get the pc and the symbol from the relocation (R_RISCV_PCREL_HI20) which corresponds to the offset of this value. then it will take the LSB 12 bits of the relative address between the pc of .L1^B1 and the address of the found symbol mtvec.