Search code examples
linkerrustllvmavrrelocation

Linking fails with "relocation truncated to fit" with aggressive inlining


To work around a Rust compiler bug in the AVR backend, I have marked a lot of my functions as #[inline(always)], by just adding the annotations until enough case-of-case optimizations etc. would fire that I no longer run into the issue.

However, with these annotations, linking now fails with lots of relocation truncated to fit messages:

target/avr-atmega328p/release/deps/chip8_avr-41c427b8d446a439.o: In function `LBB5_18':
chip8_avr.cgu-0.rs:(.text.main+0x432): relocation truncated to fit: R_AVR_7_PCREL against `no symbol'
target/avr-atmega328p/release/deps/chip8_avr-41c427b8d446a439.o: In function `LBB5_23':
chip8_avr.cgu-0.rs:(.text.main+0x45c): relocation truncated to fit: R_AVR_7_PCREL against `no symbol'
target/avr-atmega328p/release/deps/chip8_avr-41c427b8d446a439.o: In function `LBB5_31':
chip8_avr.cgu-0.rs:(.text.main+0x4ae): relocation truncated to fit: R_AVR_7_PCREL against `no symbol'
target/avr-atmega328p/release/deps/chip8_avr-41c427b8d446a439.o: In function `LBB5_34':
chip8_avr.cgu-0.rs:(.text.main+0x4d2): relocation truncated to fit: R_AVR_7_PCREL against `no symbol'
target/avr-atmega328p/release/deps/chip8_avr-41c427b8d446a439.o: In function `LBB5_146':
chip8_avr.cgu-0.rs:(.text.main+0x58a): relocation truncated to fit: R_AVR_7_PCREL against `no symbol'
chip8_avr.cgu-0.rs:(.text.main+0x58e): relocation truncated to fit: R_AVR_7_PCREL against `no symbol'
chip8_avr.cgu-0.rs:(.text.main+0x592): relocation truncated to fit: R_AVR_7_PCREL against `no symbol'
target/avr-atmega328p/release/deps/chip8_avr-41c427b8d446a439.o: In function `LBB5_153':
chip8_avr.cgu-0.rs:(.text.main+0x59a): relocation truncated to fit: R_AVR_7_PCREL against `no symbol'
chip8_avr.cgu-0.rs:(.text.main+0x59e): relocation truncated to fit: R_AVR_7_PCREL against `no symbol'
chip8_avr.cgu-0.rs:(.text.main+0x5a6): relocation truncated to fit: R_AVR_7_PCREL against `no symbol'
chip8_avr.cgu-0.rs:(.text.main+0x5aa): additional relocation overflows omitted from the output
collect2: error: ld returned 1 exit status

This SO answer implies that large intra-function jumps are something that the compiler needs to be prepared for. What is the equivalent setting on Rust?


Solution

  • After looking at disassemblies in enough detail, it turns out that these relocation targets are all in offsets of branch instructions, not (short) jumps; e.g. at 0x08:

    00000000 <_ZN12chip8_engine7opcodes6decode17haab3c6c935229a6aE>:
       0:   e8 2f           mov     r30, r24
       2:   f9 2f           mov     r31, r25
       4:   80 e0           ldi     r24, 0x00       ; 0
       6:   61 30           cpi     r22, 0x01       ; 1
       8:   01 f4           brne    .+0             ; 0xa <_ZN12chip8_engine7opcodes6decode17haab3c6c935229a6aE+0xa>
       a:   81 83           std     Z+1, r24        ; 0x01
       c:   82 83           std     Z+2, r24        ; 0x02
       e:   81 e0           ldi     r24, 0x01       ; 1
    
    00000010 <LBB0_2>:
      10:   80 83           st      Z, r24
      12:   08 95           ret
    

    The AVR fork of the Rust compiler currently generates these branches with empty (.+0) offsets, then tries to use the linker to fill them in. For large enough functions, these intra-function offsets can become larger than what fits into a single branch instruction.

    I've reported this as a compiler bug in the AVR fork of Rust. One potential solution that came up there was to get the linker to generate a two-step branch (a branch to a jump) for cases where the offset doesn't fit; the other is to not use the linker at all: since the branches in question are intra-function, the relative addresses should be known at compile time, allowing for generating a two-step branch when needed.