Microblaze linker error

taskident.c:62: relocation truncated to fit: R_MICROBLAZE_32_PCREL_LO
  against symbol `_RTEMS_Name_to_id' defined in .text._RTEMS_Name_to_id
  section in ./librtemscpu.a(rtemsnametoid.c.59.o)

Worked with claude a bit on this.
Came to the conclusion that I should be adding -Wl,--no-relax to my compile flags.

I had it look at how other processors are handling linkers and am attaching a possible AI generated ticket notes.

It might be worth adding the -Wl,--no-relax to the stock build?

MicroBlaze linker relaxation: R_MICROBLAZE_32_PCREL_LO truncation

Status: worked-around (not fixed). Follow-up tracked here.
Component: binutils / ld (BFD), MicroBlaze backend — bfd/elf32-microblaze.c.
Toolchain: microblaze-rtems7-*, binutils 2.36.1 (sourceware base commit 7af075d)
plus Xilinx meta-xilinx rel-v2021.1 patches 0001–0013.
Affects: any link of a large MicroBlaze executable (the RTEMS kernel + a heavy
test/app) when linker relaxation is enabled (the default).


1. Symptom

Linking certain test programs fails:

./librtemscpu.a(taskident.c.59.o): in function `rtems_task_ident':
taskident.c:62: relocation truncated to fit: R_MICROBLAZE_32_PCREL_LO
  against symbol `_RTEMS_Name_to_id' defined in .text._RTEMS_Name_to_id
  section in ./librtemscpu.a(rtemsnametoid.c.59.o)
collect2: error: ld returned 1 exit status

It is the MicroBlaze linker relaxation pass mis-shortening a function call.

Measured facts (BSP microblaze/kcu105, BUILD_TESTS=True, relax on)

  • The compiler emits the correct 2-instruction PC-relative call
    (imm + brlid, reloc R_MICROBLAZE_64_PCREL, full ±2 GiB reach).
  • In the failing image the two functions land 32.6 KiB apart
    (_RTEMS_Name_to_idrtems_task_ident = 0x8268 = 33384 bytes),
    just past the ±32 KiB reach of a single brlid (16-bit signed field).
  • Total .text is only ~101 KiB; the gap is filled by ~hundreds of ordinary
    kernel functions (scheduler, thread-queue, timecounter, fs-eval), no single
    large symbol.
  • Scope: of 753 test exes, 24 link both symbols (the rest drop
    rtems_task_ident via --gc-sections); of those, ~6 exceed 32 KiB and
    truncate. So it is a handful of large images, not all builds. The library
    build itself (objects only, no final link) never truncates.

2. Root cause (source-grounded)

File: bfd/elf32-microblaze.c, function microblaze_elf_relax_section.

Relaxation drops the redundant imm when it believes the target is within the
single-instruction range, and downgrades the reloc
R_MICROBLAZE_64_PCRELR_MICROBLAZE_32_PCREL_LO (16-bit,
complain_overflow_signed, HOWTO ~line 103).

The “shall I drop the imm?” gate (2.36.1, ~line 1864):

if ((symval & 0xffff8000) == 0)          /* accepts forward disp in [0, 0x7fff] */
  {
    /* We can delete this instruction. */
    ...
    irel->r_info = ELF32_R_INFO (..., R_MICROBLAZE_32_PCREL_LO);   /* ~line 1876 */
  }

Three properties combine into the defect:

  1. The decision is committed irreversibly, early. Once the imm is deleted
    and the reloc downgraded, every later pass skips it (the reloc-type filter at
    ~line 1785 only reconsiders R_MICROBLAZE_64_PCREL / _64 / _TEXTREL_64).
    The imm is physically gone; it cannot be restored.
  2. It does iterate, but never re-validates. The pass sets *again = TRUE
    when it changed anything (~line 2316), so the linker re-lays-out and re-runs —
    but only to let other sections relax; an already-downgraded call is never
    re-checked.
  3. No safety margin. symval is the displacement against the layout at
    decision time
    . Because code shrinks during relaxation, downstream ALIGN /
    ALIGN_WITH_INPUT padding can change, so a target’s final offset is not a
    monotonic decrease of the decision-time estimate. A call estimated just under
    the boundary can end up just over it (our case: +616 bytes past 32 KiB). The
    final relocate_section then emits “truncated to fit” — the safe failure
    (it refuses to write a wrong branch rather than miscompile).

The in-tree comment confirms the author declined to handle this:

/* We only do this once per section.  We may be able to delete some code
   by running multiple passes, but it is not worth it.  */

Not fixed upstream

Same defect verified in: sourceware 2.36.1 (our toolchain), sourceware 2.42,
Xilinx binutils-2_23-branch, and Xilinx master (a stale 2013 tree). 2.42 is
more aggressive (also relaxes backward calls) and no safer. Bumping binutils
does not fix this.
Also review existing Xilinx patches 0004-LOCAL-Fix- relaxation-... and 0010-fixing-the-imm-bug for overlap — they do not cover
this case.


3. Current workaround (already applied)

configs/config_kcu105.ini:

  • BUILD_TESTS=False and BUILD_SAMPLES=False — the RTEMS tree builds libraries
    only, so no executable link step exists to truncate.
  • (Alternative, when exes are wanted) add -Wl,--no-relax to ABI_FLAGS: keeps
    every imm, ~2% larger .text, sub-1% runtime, and more deterministic call
    encodings (a WCET plus). Measured: sp01 +1.98%, ticker +1.87%, hello +2.19%.

The truncation risk still exists wherever the flight executable is finally
linked (outside this tree). If that image grows past the gap, that link will
fail the same way; fix is targeted -Wl,--no-relax on that one link.


4. Follow-up A — TEMP FIX: AVR-style safety margin (small, heuristic)

Goal: keep relaxation (recover the ~2%) while preventing the truncation, with
a minimal, well-precedented bfd change.

Precedent: bfd/elf32-avr.c:2655-2694. AVR relaxes callrcall /
jmprjmp (same shape as the imm-drop) and guards the identical failure mode
(“shrinking the code size makes the gaps larger … use a heuristical safety
margin to avoid that during relax the distance gets again too large”).

Change: in microblaze_elf_relax_section, lower the acceptance ceiling by a
margin so a committed short call has slack to absorb alignment-induced growth.

/* elf32-microblaze.c — replace the gate (~line 1864, 2.36.1 forward-only form) */
#define MB_RELAX_SAFETY_MARGIN 0x400   /* bytes; absorbs ALIGN re-padding */

-      if ((symval & 0xffff8000) == 0)
+      /* Keep the imm unless the forward displacement fits with margin to spare;
+         later relaxation passes can grow this gap via ALIGN padding and the
+         deletion is irreversible (reloc is downgraded to _32_PCREL_LO).  See
+         the analogous safety margin in elf32-avr.c.  */
+      if (symval <= (bfd_vma) (0x7fff - MB_RELAX_SAFETY_MARGIN))

(If working from the 2.42-style gate that also relaxes backward calls, apply the
margin symmetrically to both the == 0 and == 0xffff8000 bounds.)

Integration (RSB):

  1. Create 0014-microblaze-relax-imm-safety-margin.patch against the patched
    2.36.1 tree (apply after 0001–0013; verify line context shifts).
  2. Add to src/rsb/rtems/config/tools/rtems-xilinx-binutils-2.36.cfg:
    %patch add binutils -p1 file://.../0014-microblaze-relax-imm-safety-margin.patch
    %hash sha512 0014-microblaze-relax-imm-safety-margin.patch <sha>
    
    (Local patches can be hosted in-tree rather than via xilinx_github_url.)
  3. Rebuild the toolchain (build_tools.sh / rsb), reinstall to /local/opt/rtems/7.

Verify:

  • config_kcu105.ini: set BUILD_TESTS=True, remove -Wl,--no-relax.
  • ./waf -j30 cleanly links all test/sample exes (esp. sp42, spfatal36).
  • Confirm relaxation still active and .text ~2% smaller than the --no-relax
    build (proves imms are still being dropped where safe).
  • Spot-check sp42: the rtems_task_ident_RTEMS_Name_to_id call retains its
    imm (objdump -dr), no _32_PCREL_LO truncation.

Effort: ~0.5–1 day. Risk: heuristic — a fixed margin is not provably
sufficient (worst-case alignment growth scales with the number of ALIGN
boundaries between caller and target). AVR itself calls it “heuristical.” Pick a
margin comfortably above observed growth; re-evaluate if images grow a lot.
Caveat for flight: this is a hand-maintained bfd patch you own and must
qualify; --no-relax remains lower-risk for the flight image.


5. Follow-up B — REAL FIX: RISC-V-style alignment relocations (large, correct)

Goal: make relaxation provably correct, eliminating the truncation class
entirely instead of papering over it.

Precedent: bfd/elfnn-riscv.c (+ gas/config/tc-riscv.c). RISC-V’s relax is
the modern reference: the assembler emits an explicit R_RISCV_ALIGN
relocation at every alignment directive, recording the required alignment and the
worst-case padding it reserved. The linker then knows exact alignment constraints
as it deletes bytes, so downstream addresses are computed correctly and a
committed relaxation can never go out of range. (AVR carries the analogous
R_AVR_ALIGN in addition to its margin.)

Scope (this is a real toolchain project, gas + bfd):

  1. Define R_MICROBLAZE_ALIGN — new reloc type + HOWTO in
    bfd/elf32-microblaze.c and the ELF reloc tables (include/elf/microblaze.h).
  2. gas: emit R_MICROBLAZE_ALIGN at .align/.p2align in code sections,
    reserving worst-case padding (gas/config/tc-microblaze.c), mirroring
    riscv_handle_align / tc-riscv.c.
  3. bfd relax: teach microblaze_elf_relax_section to honor alignment relocs
    when computing distances and when deleting bytes, so estimates are exact; then
    the existing range check (no margin needed) becomes correct. Model the
    delete-bytes + alignment handling on _bfd_riscv_relax_delete_bytes /
    _bfd_riscv_relax_align.
  4. Relocatable links / other tools must tolerate the new reloc
    (objcopy, ld -r, readelf).

Verify: all of Follow-up A’s checks, plus with MB_RELAX_SAFETY_MARGIN
removed (margin no longer needed); add a torture test that places a call exactly
at the ±32 KiB boundary across several large .align directives and confirm no
truncation and correct runtime behavior.

Effort: weeks; spans gas + bfd; ideally upstreamed to sourceware (and/or
Xilinx) so it is not a perpetual local carry. Risk: high surface area, but it
is the only option that removes the failure mode rather than reducing its
probability.


6. Recommendation

  • Now: keep the workaround (libraries-only here; -Wl,--no-relax on any
    oversized final link, including the flight image). Zero toolchain risk.
  • Short term (optional): Follow-up A if the ~2% size/relaxation is wanted back
    on test/dev builds.
  • Long term (only if MicroBlaze stays strategic): Follow-up B, upstreamed.
  • For flight, prefer --no-relax over carrying a relaxation patch:
    deterministic, qualifiable, and the ~2%/sub-1% cost is negligible.

7. References

  • bfd/elf32-microblaze.cmicroblaze_elf_relax_section: gate ~line 1864,
    reloc downgrade ~1876, reloc-type filter ~1785, *again ~2316; HOWTO ~103.
  • bfd/elf32-avr.c:2655-2694 — safety-margin precedent.
  • bfd/elfnn-riscv.c, gas/config/tc-riscv.cR_RISCV_ALIGN precedent.
  • RSB: src/rsb/rtems/config/tools/rtems-xilinx-binutils-2.36.cfg (patch chain),
    src/rsb/rtems/config/7/rtems-microblaze.bset (version selection).
  • Repro BSP/config: configs/config_kcu105.ini, build dir
    build/microblaze/kcu105.

Thanks for the post and the details.

Could the separate sections for text and data options be part of the problem? I ask because normally there is only one text section per object file, ie only one pass?

Disabling those options does mean dead code can be linked in but it would be interesting to know as it may help explain the reason for a single pass.

FYI Xilinx uses Microblaze as platform management devices in Versal and ZynqMP devices. The code is built by Vivado (or Vitis?) when you make an FSBL. I looked into the details of the generated code in the Versal case and was surprised to see them using LTO to get the size of the executable down. It did make a difference. I have tried LTO on ARM with RTEMS and had some issues and I have not returned to it.

This is outside the FSBL code. That BSP wont even compile the tests due to size limitations of BRAM.

If I disable the tests it builds the libraries. Compiling my app has not run into these linker issues. Googling showed some other people hitting this.

The AI analysis seemed plausible, but maybe I am wrong about that.

I was hopping we were just missing a xilinx patch but that was not the case.

I don’t think the extra imm symbols will slow the processor down to much.
I guess its better to test how you would compile though.
So If I am disabling this to run tests I should disable it in my other build.

I would think both taskident.c and _RTEMS_Name_to_id would both be in the text section. So I dont think its data vs text issues.

More that removing the imm symbols shifts things up, and then a previous pass now has its jump larger than the 32kb limit. From what the AI found AVR adds a buffer to avoid this case a bit more. And RISC-V adds location symbols in the compile process so the linker doesn’t have to approximate it.

A lower priority thing for me to fix compared to other things at the moment.

Maybe I am wrong about the root cause also.

If I am catching the right stuff here, using -no-relax addressed the failure, had minimal/acceptable impact on size, and improved deterministic performance. I’d say adding the option to the Microblaze BSPs is a good thing.

To be good open source citizens, at least check if this has a binutils issue. Can you produce a small test case?

Mostly trying to get our project back up to the latest RTEMS.
When I get to the point of testing I will provide cpu deltas / code size deltas.

Tried making a stress test on the linker / binutils but that didn’t show much results.

I think I would need to instrument binutils and track down why its getting into this condition.

I find it interesting that different systems approach this this differently. MB vs AVR vs Risc-v

This is where I left off on trying to debug it. I attached the .o files that should cause the error to occur. And notes I would use to start debugging it again.