Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
patrick-rivos opened this issue Aug 19, 2024 · 4 comments

Comments

@patrick-rivos
Copy link
Contributor

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
Copy link
Collaborator

topperc commented Aug 19, 2024

CC @MaskRay

@MaskRay
Copy link
Member

MaskRay commented Aug 22, 2024

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
Copy link
Member

llvmbot commented Aug 22, 2024

@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.
</details>

@patrick-rivos
Copy link
Contributor Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants