llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
28k stars 11.56k forks source link

[RISC-V] Unlinked obj file branches contain a fake infinite-loop destination. #104853

Open patrick-rivos opened 4 weeks ago

patrick-rivos commented 4 weeks ago

Using the same input assembly file from:

int x = 0;
int min(int y)
{
    while (x != 192) {
        x += 1;
    }
    return y;
}
int main() {
    min(1);
}

gnu-as assembled program looks normal:

map-binutils.o: file format elf64-littleriscv

Disassembly of section .text:

0000000000000000 <min>:
       0: 000005b7      lui     a1, 0x0
       4: 0005a683      lw      a3, 0x0(a1)
       8: 0c000613      li      a2, 0xc0
       c: 00c68463      beq     a3, a2, 0x14 <.LBB0_2>
      10: 00c5a023      sw      a2, 0x0(a1)

0000000000000014 <.LBB0_2>:
      14: 8082          ret

0000000000000016 <main>:
      16: 00000537      lui     a0, 0x0
      1a: 00052603      lw      a2, 0x0(a0)
      1e: 0c000593      li      a1, 0xc0
      22: 00b60463      beq     a2, a1, 0x2a <.LBB1_2>
      26: 00b52023      sw      a1, 0x0(a0)

000000000000002a <.LBB1_2>:
      2a: 4501          li      a0, 0x0
      2c: 8082          ret
      2e: 0000          unimp

But LLVM's obj file looks like: (each branch is an infinite loop)

map.o:  file format elf64-littleriscv

Disassembly of section .text:

0000000000000000 <min>:
       0: 000005b7      lui     a1, 0x0
       4: 0005a683      lw      a3, 0x0(a1)
       8: 0c000613      li      a2, 0xc0
       c: 00c68063      beq     a3, a2, 0xc <min+0xc>
      10: 00c5a023      sw      a2, 0x0(a1)
      14: 8082          ret

0000000000000016 <main>:
      16: 00000537      lui     a0, 0x0
      1a: 00052603      lw      a2, 0x0(a0)
      1e: 0c000593      li      a1, 0xc0
      22: 00b60063      beq     a2, a1, 0x22 <main+0xc>
      26: 00b52023      sw      a1, 0x0(a0)
      2a: 4501          li      a0, 0x0
      2c: 8082          ret

gnu-objdump and llvm-objdump agree on this output so it's probably not on the objdump side of things.

When actually linked the offsets are updated to the correct addresses (with both lld/ld):

000000000001129e <min>:
   1129e: 000155b7      lui a1, 0x15
   112a2: 2f05a683      lw  a3, 0x2f0(a1)
   112a6: 0c000613      li  a2, 0xc0
   112aa: 00c68463      beq a3, a2, 0x112b2 <min+0x14>
   112ae: 2ec5a823      sw  a2, 0x2f0(a1)
   112b2: 8082          ret

I think the actual dests in the LLVM obj file are encoded using R_RISCV_BRANCH.

This behavior makes inspecting unlinked object files unintuitive at first glance.

topperc commented 4 weeks ago

CC @MaskRay

MaskRay commented 3 weeks ago

A fixup describes a modified section location. If the assembler decides to generate a relocation for a fixup, the content bits can be kept as zero like LLVM does and non-internal branches like call foo on x86. It seems that GNU assembler's riscv port modifies the location.

LLVM's choice is simpler and enables better compression. We can use llvm-objdump -dr to dump inline relocations. Therefore, I am not sure we want to change LLVM integrated assembler.

llvmbot commented 3 weeks ago

@llvm/issue-subscribers-backend-risc-v

Author: Patrick O'Neill (patrick-rivos)

Using the same input assembly file from: ```c int x = 0; int min(int y) { while (x != 192) { x += 1; } return y; } int main() { min(1); } ``` gnu-as assembled program looks normal: ``` map-binutils.o: file format elf64-littleriscv Disassembly of section .text: 0000000000000000 <min>: 0: 000005b7 lui a1, 0x0 4: 0005a683 lw a3, 0x0(a1) 8: 0c000613 li a2, 0xc0 c: 00c68463 beq a3, a2, 0x14 <.LBB0_2> 10: 00c5a023 sw a2, 0x0(a1) 0000000000000014 <.LBB0_2>: 14: 8082 ret 0000000000000016 <main>: 16: 00000537 lui a0, 0x0 1a: 00052603 lw a2, 0x0(a0) 1e: 0c000593 li a1, 0xc0 22: 00b60463 beq a2, a1, 0x2a <.LBB1_2> 26: 00b52023 sw a1, 0x0(a0) 000000000000002a <.LBB1_2>: 2a: 4501 li a0, 0x0 2c: 8082 ret 2e: 0000 unimp ``` But LLVM's obj file looks like: (each branch is an infinite loop) ``` map.o: file format elf64-littleriscv Disassembly of section .text: 0000000000000000 <min>: 0: 000005b7 lui a1, 0x0 4: 0005a683 lw a3, 0x0(a1) 8: 0c000613 li a2, 0xc0 c: 00c68063 beq a3, a2, 0xc <min+0xc> 10: 00c5a023 sw a2, 0x0(a1) 14: 8082 ret 0000000000000016 <main>: 16: 00000537 lui a0, 0x0 1a: 00052603 lw a2, 0x0(a0) 1e: 0c000593 li a1, 0xc0 22: 00b60063 beq a2, a1, 0x22 <main+0xc> 26: 00b52023 sw a1, 0x0(a0) 2a: 4501 li a0, 0x0 2c: 8082 ret ``` gnu-objdump and llvm-objdump agree on this output so it's probably not on the objdump side of things. When actually linked the offsets are updated to the correct addresses (with both lld/ld): ``` 000000000001129e <min>: 1129e: 000155b7 lui a1, 0x15 112a2: 2f05a683 lw a3, 0x2f0(a1) 112a6: 0c000613 li a2, 0xc0 112aa: 00c68463 beq a3, a2, 0x112b2 <min+0x14> 112ae: 2ec5a823 sw a2, 0x2f0(a1) 112b2: 8082 ret ``` I think the actual dests in the LLVM obj file are encoded using R_RISCV_BRANCH. This behavior makes inspecting unlinked object files unintuitive at first glance.
patrick-rivos commented 3 weeks ago

We can use llvm-objdump -dr to dump inline relocations. Therefore, I am not sure we want to change LLVM integrated assembler.

Thanks for the reply. Just a note that -dr also requires -x if you want to be able to see the branch destination. The label is not displayed inline with the disassembled code.

llvm-objdump -drx map.o | grep "L0"
0000000000000014 l       .text  0000000000000000 .L0
000000000000002a l       .text  0000000000000000 .L0
                000000000000000c:  R_RISCV_BRANCH       .L0
                0000000000000022:  R_RISCV_BRANCH       .L0

non-internal branches like call foo on x86.

In my opinion those are easier to reason about since the relocation objdumps to include the function name so I don't need to cross-reference the headers: https://godbolt.org/z/84jKoqW5o

LLVM's choice is simpler and enables better compression.

From LLVM's side it seems simpler to implement but the user experience of chasing down branch destinations is not simple (at least the way I'm doing it :-) ).

I'm speaking out of ignorance here - maybe I don't understand the magnitude of the savings here or how often unlinked obj files are compressed - but compression seems like strange justification for this behavior.