Issue 134525
Summary [RISC-V] mcmodel=medany 'relocation R_RISCV_PCREL_HI20 out of range' for >2MiB relocations
Labels new issue
Assignees
Reporter midnightveil
    Compiling & linking the following code under `mcmodel=medany` creates a relocation failure:

```c
extern char ki_end[];
void _assert_fail(char *, const char *);
void example(void);

_Static_assert(sizeof(long) == 8, "64-bit ki_end");

void example(void) {
  if (((long)ki_end) - (0xFFFFFFFF80200000ul) <= 0x20000ul) {
      _assert_fail("(ELF_TOP - ELF_BASE) <= RISCV_GET_LVL_PGSIZE(0)", __func__);
  }
}
```

When using a linker script (similar) to this one:

```ld
OUTPUT_ARCH(riscv)
ENTRY(_start)

/* This is very similar to our assertion */
/* KERNEL_OFFSET = (0xFFFFFFFF80000000 + (0x80200000 & ((1 << (30)) - 1))) - 0x80200000; */
KERNEL_OFFSET = 0xFFFFFFFF00000000;
SECTIONS
{
    . = 0xFFFFFFFF80200000;

    .text . : AT(ADDR(.text) - KERNEL_OFFSET)
    {
        /* Make the linker happy(ier) */
        _start = .;
        _assert_fail = .;

        *(.text)
    }

    .data . : AT(ADDR(.data) - KERNEL_OFFSET)
    {
        *(.data)
        *(.rodata)
        *(.rodata.*)
    }

    .bss . (NOLOAD): AT(ADDR(.bss) - KERNEL_OFFSET)
    {
        *(.bss)
        *(.sbss)
    }

    .bss.ki_clone_mem . (NOLOAD): AT(ADDR(.bss.ki_clone_mem) - KERNEL_OFFSET)
    {
        /* This moves the ki_end more than about 2MiB from the location of the
           relocation that will be performed.
           2M is 20 bits which is the size of the PCREL_HI20 relocation which fails.

           Generally somewhere around 1.99MiB should make this link again.
         */
        . = . + 4M;
    }

    ki_end = .;
}
```

This generates assembly in the object file (pre-linking) which looks something like:

```asm
0000000000000000 <example>:
; .L.str():
       0: 00000517      auipc   a0, 0x0
                0000000000000000:  R_RISCV_PCREL_HI20   ki_end+0x7fe00000
                0000000000000000:  R_RISCV_RELAX        *ABS*
       4: 00050513      mv      a0, a0
                0000000000000004:  R_RISCV_PCREL_LO12_I .Lpcrel_hi0
                0000000000000004:  R_RISCV_RELAX        *ABS*
       8: 000205b7      lui     a1, 0x20
       c: 00a5e063      bltu    a1, a0, 0xc <example+0xc>
                000000000000000c:  R_RISCV_BRANCH       .L0
```

Using instead
```c
  if (((long)ki_end) <= 0xFFFFFFFF80200000ul + 0x20000ul) {
```
works fine.

It appears to work fine for values up to 2MiB, but past a 2MiB offset the linking fails.
This is weird, because `auipc` should support 32 bit signed (±2GiB) offsets.
The same code compiled with `medlow` links fine (as these addresses are *technically* OK for medlow, as it's within ±2GiB of the 0 address).

Both gcc and clang seem to be doing some sort of "constant folding", moving around the 0xFFFF'FFFF'8020'0000 to the signed equivalent of (negative that becomes positive due to the subtraction) 0x7FE0'0000. Which is then out of bounds of a 2GiB relocation.

```
+ clang -target riscv64-none-elf --sysroot=<prefix>kernel-reduce -O2 -mabi=lp64d -march=rv64imafdc_zicsr_zifencei -fno-pic -fno-pie -nostdlib -mcmodel=medany -std=c99 -Wall -Werror -ffreestanding -fno-stack-protector -fno-asynchronous-unwind-tables -fno-common -o kernel.obj -c kernel.i
+ clang -target riscv64-none-elf --sysroot=<prefix>kernel-reduce -O2 -mabi=lp64d -march=rv64imafdc_zicsr_zifencei -fno-pic -fno-pie -nostdlib -mcmodel=medany -static -Wl,--build-id=none -Wl,-n -Wl,-T ./linker.lds kernel.obj -o kernel.elf
ld.lld: error: kernel.obj:(function example: .text+0x0): relocation R_RISCV_PCREL_HI20 out of range: 549376 is not in [-524288, 524287]; references 'ki_end'
```


"Longer" gist here: https://gist.github.com/midnightveil/263e6b08d1f50c58cfaee9d2215778a6, with full reproducer steps.
*This appears to be a bug in both GCC/LLVM, both as gcc+ld,clang+lld,gcc+lld,clang+ld*.


_______________________________________________
llvm-bugs mailing list
llvm-bugs@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to