[lld] [lld][Hexagon] Fix out-of-range PLT branch thunks (PR #186545)

Brian Cain via llvm-commits llvm-commits at lists.llvm.org
Fri Mar 13 18:08:46 PDT 2026


https://github.com/androm3da created https://github.com/llvm/llvm-project/pull/186545

Linking large Hexagon binaries (e.g. ASan runtime with >8 MiB of text) fails with R_HEX_B22_PCREL / R_HEX_PLT_B22_PCREL relocation overflow on calls to PLT entries, even though the thunk infrastructure exists and needsThunks is set.

needsThunk() always used s.getVA() to compute the branch destination, even for PLT calls where the actual destination is the PLT entry.  This meant the distance check used the wrong address and failed to create thunks when the PLT entry was out of B22_PCREL range.

Fix by using s.getPltVA() when expr == R_PLT_PC.  Also add an early return for undefined weak symbols without PLT entries: they resolve to the next instruction.  Override getThunkSectionSpacing() so ThunkSections are pre-created at appropriate intervals for large binaries.

>From 2b7aab1a179c15cd84e6b1f458ff3eaa18ff56ab Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Fri, 13 Mar 2026 16:23:15 -0700
Subject: [PATCH] [lld][Hexagon] Fix out-of-range PLT branch thunks

Linking large Hexagon binaries (e.g. ASan runtime with >8 MiB of text)
fails with R_HEX_B22_PCREL / R_HEX_PLT_B22_PCREL relocation overflow
on calls to PLT entries, even though the thunk infrastructure exists and
needsThunks is set.

needsThunk() always used s.getVA() to compute the branch destination,
even for PLT calls where the actual destination is the PLT entry.  This
meant the distance check used the wrong address and failed to create
thunks when the PLT entry was out of B22_PCREL range.

Fix by using s.getPltVA() when expr == R_PLT_PC.  Also add an early
return for undefined weak symbols without PLT entries: they resolve to
the next instruction.  Override getThunkSectionSpacing() so ThunkSections
are pre-created at appropriate intervals for large binaries.
---
 lld/ELF/Arch/Hexagon.cpp               | 19 +++++-
 lld/test/ELF/hexagon-thunk-range-plt.s | 89 +++++++++++---------------
 lld/test/ELF/hexagon-thunks-packets.s  | 80 ++++++++++++-----------
 lld/test/ELF/hexagon-thunks.s          | 16 ++---
 4 files changed, 103 insertions(+), 101 deletions(-)

diff --git a/lld/ELF/Arch/Hexagon.cpp b/lld/ELF/Arch/Hexagon.cpp
index 4204e2f4e524a..9c9f6a1b5905e 100644
--- a/lld/ELF/Arch/Hexagon.cpp
+++ b/lld/ELF/Arch/Hexagon.cpp
@@ -46,6 +46,7 @@ class Hexagon final : public TargetInfo {
   bool needsThunk(RelExpr expr, RelType type, const InputFile *file,
                   uint64_t branchAddr, const Symbol &s,
                   int64_t a) const override;
+  uint32_t getThunkSectionSpacing() const override;
   bool inBranchRange(RelType type, uint64_t src, uint64_t dst) const override;
   void relocate(uint8_t *loc, const Relocation &rel,
                 uint64_t val) const override;
@@ -370,7 +371,10 @@ bool Hexagon::inBranchRange(RelType type, uint64_t src, uint64_t dst) const {
 bool Hexagon::needsThunk(RelExpr expr, RelType type, const InputFile *file,
                          uint64_t branchAddr, const Symbol &s,
                          int64_t a) const {
-  // Only check branch range for supported branch relocation types
+  // If s is an undefined weak symbol without a PLT entry it resolves to
+  // the next instruction; no thunk is needed.
+  if (s.isUndefined() && !s.isInPlt(ctx))
+    return false;
   switch (type) {
   case R_HEX_B22_PCREL:
   case R_HEX_PLT_B22_PCREL:
@@ -378,13 +382,22 @@ bool Hexagon::needsThunk(RelExpr expr, RelType type, const InputFile *file,
   case R_HEX_LD_PLT_B22_PCREL:
   case R_HEX_B15_PCREL:
   case R_HEX_B13_PCREL:
-  case R_HEX_B9_PCREL:
-    return !ctx.target->inBranchRange(type, branchAddr, s.getVA(ctx, a));
+  case R_HEX_B9_PCREL: {
+    uint64_t dst = (expr == R_PLT_PC) ? s.getPltVA(ctx) : s.getVA(ctx, a);
+    return !ctx.target->inBranchRange(type, branchAddr, dst);
+  }
   default:
     return false;
   }
 }
 
+uint32_t Hexagon::getThunkSectionSpacing() const {
+  // B22_PCREL has a range of +/- 8 MiB (22-bit signed offset * 4).
+  // Pre-create ThunkSections at intervals below this to leave room for
+  // thunk growth.
+  return 0x800000 - 0x30000;
+}
+
 void Hexagon::relocate(uint8_t *loc, const Relocation &rel,
                        uint64_t val) const {
   switch (rel.type) {
diff --git a/lld/test/ELF/hexagon-thunk-range-plt.s b/lld/test/ELF/hexagon-thunk-range-plt.s
index 3a8f50b681d81..86909a5231205 100644
--- a/lld/test/ELF/hexagon-thunk-range-plt.s
+++ b/lld/test/ELF/hexagon-thunk-range-plt.s
@@ -2,74 +2,61 @@
 # RUN: rm -rf %t && split-file %s %t && cd %t
 # RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-elf external.s -o external.o
 # RUN: ld.lld -shared external.o -soname external.so -o external.so
+
+## PLT calls within range (2 MiB padding) — no thunks needed.
 # RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-elf main.s -o main.o
 # RUN: ld.lld main.o external.so -o test
-# RUN: llvm-objdump -d --no-show-raw-insn test | FileCheck %s
+# RUN: llvm-objdump -d --no-show-raw-insn test | \
+# RUN:     FileCheck --check-prefix=INRANGE %s
+
+## PLT calls out of range (>8 MiB padding) — thunks required.
+# RUN: llvm-mc -filetype=obj \
+# RUN:         -triple=hexagon-unknown-elf main-large.s -o main-large.o
+# RUN: ld.lld main-large.o external.so -o test-large
+# RUN: llvm-objdump -d --no-show-raw-insn test-large | \
+# RUN:     FileCheck --check-prefix=OUTRANGE %s
 
 ## Test thunk range scenarios for Hexagon R_HEX_PLT_B22_PCREL relocations.
-## PLT calls use the same ±8MB range as regular calls but go through PLT entries.
-## This test verifies thunk generation for PLT calls at range boundaries.
+## PLT calls use the same +/- 8 MiB range as regular B22_PCREL calls.
+## When the PLT entry is beyond this range, a thunk must be created.
 
 #--- external.s
-.globl extern_within_range, extern_beyond_range, extern_close
-.type extern_within_range, @function
-.type extern_beyond_range, @function
-.type extern_close, @function
-
-extern_within_range:
-  jumpr r31
-
-extern_beyond_range:
-  jumpr r31
-
-extern_close:
+.globl extern_func
+.type extern_func, @function
+extern_func:
   jumpr r31
 
 #--- main.s
+## Within-range case: 2 MiB padding keeps PLT entries reachable.
 .globl _start
 .type _start, @function
 _start:
-  ## Test PLT calls to external functions at various ranges
-  call extern_within_range at PLT
-  call extern_beyond_range at PLT
-  call extern_close at PLT
+  call extern_func at PLT
   jumpr r31
 
 .skip 0x200000
 
-# CHECK: Disassembly of section .text:
-# CHECK:     <_start>:
-# CHECK-NEXT:  2021c:  { call 0x220250 <extern_within_range at plt> }
-# CHECK-NEXT:    { call 0x220260 <extern_beyond_range at plt> }
-# CHECK-NEXT:    { call 0x220270 <extern_close at plt> }
-# CHECK-NEXT:    { jumpr r31 }
+#--- main-large.s
+## Out-of-range case: >8 MiB padding pushes PLT entries beyond B22_PCREL range.
+.globl _start
+.type _start, @function
+_start:
+  call extern_func at PLT
+  jumpr r31
 
-## Verify PLT header and entries are created with exact addresses
-# CHECK: Disassembly of section .plt:
-# CHECK:      <.plt>:
-# CHECK-NEXT:   220230:  { immext(#0x20080)
-# CHECK-NEXT:      r28 = add(pc,##0x200b8) }
-# CHECK-NEXT:    { r14 -= add(r28,#0x10)
-# CHECK-NEXT:      r15 = memw(r28+#0x8)
-# CHECK-NEXT:      r28 = memw(r28+#0x4) }
-# CHECK-NEXT:    { r14 = asr(r14,#0x2)
-# CHECK-NEXT:      jumpr r28 }
-# CHECK-NEXT:    { trap0(#0xdb) }
+.skip 0x900000
 
-# CHECK:      <extern_within_range at plt>:
-# CHECK-NEXT:   220250:  { immext(#0x20080)
-# CHECK-NEXT:      r14 = add(pc,##0x200a8) }
-# CHECK-NEXT:    { r28 = memw(r14+#0x0) }
-# CHECK-NEXT:    { jumpr r28 }
+## Within-range: _start calls the PLT entry directly (no thunk).
+# INRANGE:      <_start>:
+# INRANGE-NEXT:   201ac: { call 0x2201e0 <extern_func at plt> }
+# INRANGE-NEXT:          { jumpr r31 }
 
-# CHECK:      <extern_beyond_range at plt>:
-# CHECK-NEXT:   220260: { immext(#0x20080)
-# CHECK-NEXT:      r14 = add(pc,##0x2009c) }
-# CHECK-NEXT:    { r28 = memw(r14+#0x0) }
-# CHECK-NEXT:    { jumpr r28 }
+## Out-of-range: thunk uses immext+jump to reach the PLT entry.
+# OUTRANGE:      <__hexagon_thunk_extern_func_from_.text.thunk>:
+# OUTRANGE-NEXT:   201ac: { immext(#0x900000)
+# OUTRANGE-NEXT:            jump 0x9201e0 <extern_func at plt> }
 
-# CHECK:      <extern_close at plt>:
-# CHECK-NEXT:   220270:  { immext(#0x20080)
-# CHECK-NEXT:      r14 = add(pc,##0x20090) }
-# CHECK-NEXT:    { r28 = memw(r14+#0x0) }
-# CHECK-NEXT:    { jumpr r28 }
+## _start calls the thunk instead of the PLT entry directly.
+# OUTRANGE:      <_start>:
+# OUTRANGE-NEXT:   201b4: { call 0x201ac <__hexagon_thunk_extern_func_from_.text.thunk> }
+# OUTRANGE-NEXT:          { jumpr r31 }
diff --git a/lld/test/ELF/hexagon-thunks-packets.s b/lld/test/ELF/hexagon-thunks-packets.s
index c8aaad4341ff3..6d4a19c3bfcfb 100644
--- a/lld/test/ELF/hexagon-thunks-packets.s
+++ b/lld/test/ELF/hexagon-thunks-packets.s
@@ -15,18 +15,9 @@
 
 # CHECK:  Disassembly of section .text:
 
-# CHECK-NONPIC: 000200b4 <__hexagon_thunk_myfn_a_from_.text.thunk>:
-# CHECK-NONPIC: { immext(#0x1000040)
-# CHECK-NONPIC:   jump 0x1020110 <myfn_a> }
-
-# CHECK-PIC:    00010150 <__hexagon_thunk_myfn_a_from_.text.thunk>:
-# CHECK-PIC-NEXT:    { immext(#0x1000040)
-# CHECK-PIC-NEXT:      r14 = add(pc,##0x1000060) }
-# CHECK-PIC-NEXT:    { jumpr r14 }
-
-# CHECK-NONPIC: 000200bc <myfn_b>:
+# CHECK-NONPIC: 000200b4 <myfn_b>:
 # CHECK-NONPIC: { jumpr r31 }
-# CHECK-PIC:    0001015c <myfn_b>:
+# CHECK-PIC:    00010150 <myfn_b>:
 # CHECK-PIC:    { jumpr r31 }
     .globl myfn_b
     .type  myfn_b, @function
@@ -34,47 +25,47 @@ myfn_b:
     jumpr r31
     .size  myfn_b, .-myfn_b
 
-# CHECK-PIC:    00010160 <main>:
+# CHECK-PIC:    00010154 <main>:
     .globl main
     .type  main, @function
 main:
     { r0 = #0
       call myfn_a }
-# CHECK-PIC:      { call 0x10150 <__hexagon_thunk_myfn_a_from_.text.thunk>
-# CHECK-NONPIC:   { call 0x200b4 <__hexagon_thunk_myfn_a_from_.text.thunk>
+# CHECK-PIC:      { call 0x101a4 <__hexagon_thunk_myfn_a_from_.text.thunk>
+# CHECK-NONPIC:   { call 0x20108 <__hexagon_thunk_myfn_a_from_.text.thunk>
 # CHECK-NEXT:       r0 = #0x0 }
     call myfn_a
-# CHECK-PIC:    call 0x10150 <__hexagon_thunk_myfn_a_from_.text.thunk>
-# CHECK-NONPIC: call 0x200b4 <__hexagon_thunk_myfn_a_from_.text.thunk>
+# CHECK-PIC:    call 0x101a4 <__hexagon_thunk_myfn_a_from_.text.thunk>
+# CHECK-NONPIC: call 0x20108 <__hexagon_thunk_myfn_a_from_.text.thunk>
     call myfn_b
-# CHECK-PIC-NEXT:    call 0x1015c <myfn_b>
-# CHECK-NONPIC-NEXT: call 0x200bc <myfn_b>
+# CHECK-PIC-NEXT:    call 0x10150 <myfn_b>
+# CHECK-NONPIC-NEXT: call 0x200b4 <myfn_b>
 
     { r2 = add(r0, r1)
       if (p0) call #myfn_b
       if (!p0) call #myfn_a }
-# CHECK-PIC-NEXT:     { if (p0) call 0x1015c <myfn_b>
-# CHECK-PIC-NEXT:       if (!p0) call 0x10150 <__hexagon_thunk_myfn_a_from_.text.thunk>
-# CHECK-NONPIC-NEXT:  { if (p0) call 0x200bc <myfn_b>
-# CHECK-NONPIC-NEXT:    if (!p0) call 0x200b4 <__hexagon_thunk_myfn_a_from_.text.thunk>
+# CHECK-PIC-NEXT:     { if (p0) call 0x10150 <myfn_b>
+# CHECK-PIC-NEXT:       if (!p0) call 0x101a4 <__hexagon_thunk_myfn_a_from_.text.thunk>
+# CHECK-NONPIC-NEXT:  { if (p0) call 0x200b4 <myfn_b>
+# CHECK-NONPIC-NEXT:    if (!p0) call 0x20108 <__hexagon_thunk_myfn_a_from_.text.thunk>
 
 # CHECK-NEXT:       r2 = add(r0,r1) }
 
     { r2 = add(r0, r1)
       if (p0) call #myfn_a
       if (!p0) call #myfn_a }
-# CHECK-PIC-NEXT:  { if (p0) call 0x10150 <__hexagon_thunk_myfn_a_from_.text.thunk>
-# CHECK-PIC-NEXT:    if (!p0) call 0x10150 <__hexagon_thunk_myfn_a_from_.text.thunk>
-# CHECK-NONPIC-NEXT:  { if (p0) call 0x200b4 <__hexagon_thunk_myfn_a_from_.text.thunk>
-# CHECK-NONPIC-NEXT:    if (!p0) call 0x200b4 <__hexagon_thunk_myfn_a_from_.text.thunk>
+# CHECK-PIC-NEXT:  { if (p0) call 0x101a4 <__hexagon_thunk_myfn_a_from_.text.thunk>
+# CHECK-PIC-NEXT:    if (!p0) call 0x101a4 <__hexagon_thunk_myfn_a_from_.text.thunk>
+# CHECK-NONPIC-NEXT:  { if (p0) call 0x20108 <__hexagon_thunk_myfn_a_from_.text.thunk>
+# CHECK-NONPIC-NEXT:    if (!p0) call 0x20108 <__hexagon_thunk_myfn_a_from_.text.thunk>
 # CHECK-NEXT:           r2 = add(r0,r1) }
 
     { r2 = add(r0, r1)
       r1 = r4
       r4 = r5
       if (r0 == #0) jump:t #myfn_a }
-# CHECK-PIC-NEXT:     { if (r0==#0) jump:t 0x10150
-# CHECK-NONPIC-NEXT:  { if (r0==#0) jump:t 0x200b4
+# CHECK-PIC-NEXT:     { if (r0==#0) jump:t 0x101a4
+# CHECK-NONPIC-NEXT:  { if (r0==#0) jump:t 0x20108
 # CHECK-NEXT:    r2 = add(r0,r1)
 # CHECK-NEXT:    r1 = r4; r4 = r5 }
 
@@ -82,19 +73,19 @@ main:
       r4 = r5
       if (r0 <= #0) jump:t #myfn_a
       p1 = cmp.eq(r0, #0); if (p1.new) jump:nt #myfn_a }
-# CHECK-NONPIC-NEXT:  { if (r0<=#0) jump:t 0x200b4
-# CHECK-NONPIC-NEXT:    p1 = cmp.eq(r0,#0x0); if (p1.new) jump:nt 0x200b4 <__hexagon_thunk_myfn_a_from_.text.thunk>
-# CHECK-PIC-NEXT:     { if (r0<=#0) jump:t 0x10150
-# CHECK-PIC-NEXT:       p1 = cmp.eq(r0,#0x0); if (p1.new) jump:nt 0x10150 <__hexagon_thunk_myfn_a_from_.text.thunk>
+# CHECK-NONPIC-NEXT:  { if (r0<=#0) jump:t 0x20108
+# CHECK-NONPIC-NEXT:    p1 = cmp.eq(r0,#0x0); if (p1.new) jump:nt 0x20108 <__hexagon_thunk_myfn_a_from_.text.thunk>
+# CHECK-PIC-NEXT:     { if (r0<=#0) jump:t 0x101a4
+# CHECK-PIC-NEXT:       p1 = cmp.eq(r0,#0x0); if (p1.new) jump:nt 0x101a4 <__hexagon_thunk_myfn_a_from_.text.thunk>
 # CHECK-NEXT:           r2 = add(r0,r1)
 # CHECK-NEXT:           r4 = r5 }
 
     {r0 = #0; jump #myfn_a}
-# CHECK-PIC-NEXT:    { r0 = #0x0 ; jump 0x10150 <__hexagon_thunk_myfn_a_from_.text.thunk> }
-# CHECK-NONPIC-NEXT: { r0 = #0x0 ; jump 0x200b4 <__hexagon_thunk_myfn_a_from_.text.thunk> }
+# CHECK-PIC-NEXT:    { r0 = #0x0 ; jump 0x101a4 <__hexagon_thunk_myfn_a_from_.text.thunk> }
+# CHECK-NONPIC-NEXT: { r0 = #0x0 ; jump 0x20108 <__hexagon_thunk_myfn_a_from_.text.thunk> }
     {r0 = #0; jump #myfn_b}
-# CHECK-PIC-NEXT:    { r0 = #0x0 ; jump 0x1015c <myfn_b> }
-# CHECK-NONPIC-NEXT: { r0 = #0x0 ; jump 0x200bc <myfn_b> }
+# CHECK-PIC-NEXT:    { r0 = #0x0 ; jump 0x10150 <myfn_b> }
+# CHECK-NONPIC-NEXT: { r0 = #0x0 ; jump 0x200b4 <myfn_b> }
     jumpr r31
     .size   main, .-main
 
@@ -108,15 +99,28 @@ myfn_a:
     jumpr r31
     .size  myfn_a, .-myfn_a
 
+# CHECK-NONPIC: 00020108 <__hexagon_thunk_myfn_a_from_.text.thunk>:
+# CHECK-NONPIC-NEXT: { immext(#0x1000000)
+# CHECK-NONPIC-NEXT:   jump 0x1020110 <myfn_a> }
+
+# CHECK-PIC:    000101a4 <__hexagon_thunk_myfn_a_from_.text.thunk>:
+# CHECK-PIC-NEXT:    { immext(#0x1000000)
+# CHECK-PIC-NEXT:      r14 = add(pc,##0x100000c) }
+# CHECK-PIC-NEXT:    { jumpr r14 }
+
 # CHECK-NONPIC: 01020110 <myfn_a>:
 # CHECK-NONPIC-NEXT: { r0 = #0x0 ; jump 0x1020118 <__hexagon_thunk_myfn_b_from_.text.thunk> }
 # CHECK-NONPIC-NEXT: { jumpr r31 }
 
 # CHECK-NONPIC: 01020118 <__hexagon_thunk_myfn_b_from_.text.thunk>:
 # CHECK-NONPIC-NEXT: { immext(#0xfeffff80)
-# CHECK-NONPIC-NEXT:   jump 0x200bc <myfn_b> }
+# CHECK-NONPIC-NEXT:   jump 0x200b4 <myfn_b> }
+
+# CHECK-PIC:    010101b0 <myfn_a>:
+# CHECK-PIC-NEXT: { r0 = #0x0 ; jump 0x10101b8 <__hexagon_thunk_myfn_b_from_.text.thunk> }
+# CHECK-PIC-NEXT: { jumpr r31 }
 
 # CHECK-PIC:    010101b8 <__hexagon_thunk_myfn_b_from_.text.thunk>:
 # CHECK-PIC-NEXT:    { immext(#0xfeffff80)
-# CHECK-PIC-NEXT:      r14 = add(pc,##0xfeffffa4) }
+# CHECK-PIC-NEXT:      r14 = add(pc,##0xfeffff98) }
 # CHECK-PIC-NEXT:    { jumpr r14 }
diff --git a/lld/test/ELF/hexagon-thunks.s b/lld/test/ELF/hexagon-thunks.s
index 211074e1784b9..9f6cdbd60392a 100644
--- a/lld/test/ELF/hexagon-thunks.s
+++ b/lld/test/ELF/hexagon-thunks.s
@@ -29,19 +29,17 @@ myfn:
 
 # CHECK:  Disassembly of section .text_low:
 
+# CHECK:             <main>:
+# CHECK-NEXT:          200b4: { call 0x200bc <__hexagon_thunk_myfn_from_.text.thunk> }
+# CHECK-NEXT:                 { jumpr r31 }
+
 # CHECK:             <__hexagon_thunk_myfn_from_.text.thunk>:
-# CHECK-NONPIC-NEXT:    200b4: { immext(#0x1000000)
+# CHECK-NONPIC-NEXT:    200bc: { immext(#0x1000000)
 # CHECK-NONPIC-NEXT:             jump 0x10200bc <myfn> }
-# CHECK-PIC-NEXT:       200b4: { immext(#0x1000000)
-# CHECK-PIC-NEXT:               r14 = add(pc,##0x1000008) }
+# CHECK-PIC-NEXT:       200bc: { immext(#0x1000000)
+# CHECK-PIC-NEXT:               r14 = add(pc,##0x1000000) }
 # CHECK-PIC-NEXT:       { jumpr r14 }
 
-# CHECK-NONPIC:      <main>:
-# CHECK-NONPIC-NEXT:   200bc: { call 0x200b4 <__hexagon_thunk_myfn_from_.text.thunk> }
-# CHECK-PIC:         <main>:
-# CHECK-PIC-NEXT:      200c0: { call 0x200b4 <__hexagon_thunk_myfn_from_.text.thunk> }
-# CHECK-NEXT:                 { jumpr r31 }
-
 # CHECK:  Disassembly of section .text_high:
 # CHECK:    <myfn>:
 # CHECK-NEXT: 10200bc: { jumpr r31 }



More information about the llvm-commits mailing list