[lld] [lld][ARM] Don't emit veneers for wraparound branches. (PR #165263)
Simon Tatham via llvm-commits
llvm-commits at lists.llvm.org
Tue Oct 28 08:09:01 PDT 2025
https://github.com/statham-arm updated https://github.com/llvm/llvm-project/pull/165263
>From ee2d0fe1238f18d2d3272b65db69e09fff8ee27f Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Mon, 27 Oct 2025 13:31:39 +0000
Subject: [PATCH 1/2] [lld][ARM] Don't emit veneers for wraparound branches.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
If an instruction at the high end of the 32-bit address space branches
to one at the low end, then the branch can be within range for a B or
BL instruction, and doesn't need a veneer. `ARM::inBranchRange` was
failing to detect this because it calculated the offset as an int64_t,
so that the offset was a small value ± 2^32 instead of just the smalle
value.
Fixes #165211.
---
lld/ELF/Arch/ARM.cpp | 2 +-
lld/test/ELF/arm-wraparound-veneer.s | 118 +++++++++++++++++++++++++++
2 files changed, 119 insertions(+), 1 deletion(-)
create mode 100644 lld/test/ELF/arm-wraparound-veneer.s
diff --git a/lld/ELF/Arch/ARM.cpp b/lld/ELF/Arch/ARM.cpp
index 91a673f13d68e..6c4290ff1e448 100644
--- a/lld/ELF/Arch/ARM.cpp
+++ b/lld/ELF/Arch/ARM.cpp
@@ -472,7 +472,7 @@ bool ARM::inBranchRange(RelType type, uint64_t src, uint64_t dst) const {
// Bit 0 == 1 denotes Thumb state, it is not part of the range.
dst &= ~0x1;
- int64_t offset = dst - src;
+ int64_t offset = llvm::SignExtend64<32>(dst - src);
switch (type) {
case R_ARM_PC24:
case R_ARM_PLT32:
diff --git a/lld/test/ELF/arm-wraparound-veneer.s b/lld/test/ELF/arm-wraparound-veneer.s
new file mode 100644
index 0000000000000..6fe211edc5e10
--- /dev/null
+++ b/lld/test/ELF/arm-wraparound-veneer.s
@@ -0,0 +1,118 @@
+// REQUIRES: arm
+// RUN: rm -rf %t && split-file %s %t && cd %t
+// RUN: llvm-mc -filetype=obj -triple=armv7-none-eabi code.s -o code.o
+// RUN: ld.lld -T unsigned1.ld code.o -o unsigned1.elf
+// RUN: llvm-objdump --triple=armv7 -d unsigned1.elf | FileCheck %s --check-prefix=UNSIGNED1
+// RUN: ld.lld -T unsigned2.ld code.o -o unsigned2.elf
+// RUN: llvm-objdump --triple=armv7 -d unsigned2.elf | FileCheck %s --check-prefix=UNSIGNED2
+// RUN: ld.lld -T signed1.ld code.o -o signed1.elf
+// RUN: llvm-objdump --triple=armv7 -d signed1.elf | FileCheck %s --check-prefix=SIGNED1
+// RUN: ld.lld -T signed2.ld code.o -o signed2.elf
+// RUN: llvm-objdump --triple=armv7 -d signed2.elf | FileCheck %s --check-prefix=SIGNED2
+
+// The aim of this test is to ensure that a BL instruction near one end of the
+// address space can reach a function at the extreme other end, directly, using
+// a branch offset that makes the address wrap round. We check this at both the
+// unsigned wraparound point (one address near 0 and the other near 0xFFFFFFFF)
+// and the signed wraparound point (addresses either side of 0x80000000),
+// crossing the boundary in both directions. In all four cases we expect a
+// direct branch with no veneer.
+
+// UNSIGNED1: Disassembly of section .text.lowaddr:
+// UNSIGNED1: 00010000 <func>:
+// UNSIGNED1: 10000: e12fff1e bx lr
+//
+// UNSIGNED1: Disassembly of section .text.highaddr:
+// UNSIGNED1: ffff0000 <_start>:
+// UNSIGNED1: ffff0000: eb007ffe bl 0x10000
+// UNSIGNED1: ffff0004: e12fff1e bx lr
+
+// UNSIGNED2: Disassembly of section .text.lowaddr:
+// UNSIGNED2: 00010000 <_start>:
+// UNSIGNED2: 10000: ebff7ffe bl 0xffff0000
+// UNSIGNED2: 10004: e12fff1e bx lr
+//
+// UNSIGNED2: Disassembly of section .text.highaddr:
+// UNSIGNED2: ffff0000 <func>:
+// UNSIGNED2: ffff0000: e12fff1e bx lr
+
+// SIGNED1: Disassembly of section .text.posaddr:
+// SIGNED1: 7fff0000 <_start>:
+// SIGNED1: 7fff0000: eb007ffe bl 0x80010000
+// SIGNED1: 7fff0004: e12fff1e bx lr
+//
+// SIGNED1: Disassembly of section .text.negaddr:
+// SIGNED1: 80010000 <func>:
+// SIGNED1: 80010000: e12fff1e bx lr
+
+// SIGNED2: Disassembly of section .text.posaddr:
+// SIGNED2: 7fff0000 <func>:
+// SIGNED2: 7fff0000: e12fff1e bx lr
+//
+// SIGNED2: Disassembly of section .text.negaddr:
+// SIGNED2: 80010000 <_start>:
+// SIGNED2: 80010000: ebff7ffe bl 0x7fff0000
+// SIGNED2: 80010004: e12fff1e bx lr
+
+//--- code.s
+
+ .section .text.callee, "ax", %progbits
+ .global func
+ .type func, %function
+func:
+ bx lr
+
+ .section .text.caller, "ax", %progbits
+ .global _start
+ .type _start, %function
+_start:
+ bl func
+ bx lr
+
+//--- unsigned1.ld
+
+ENTRY(_start)
+PHDRS {
+ lowaddr PT_LOAD FLAGS(0x1 | 0x4);
+ highaddr PT_LOAD FLAGS(0x1 | 0x4);
+}
+SECTIONS {
+ .text.lowaddr 0x00010000 : { *(.text.callee) } :lowaddr
+ .text.highaddr 0xffff0000 : { *(.text.caller) } :highaddr
+}
+
+//--- unsigned2.ld
+
+ENTRY(_start)
+PHDRS {
+ lowaddr PT_LOAD FLAGS(0x1 | 0x4);
+ highaddr PT_LOAD FLAGS(0x1 | 0x4);
+}
+SECTIONS {
+ .text.lowaddr 0x00010000 : { *(.text.caller) } :lowaddr
+ .text.highaddr 0xffff0000 : { *(.text.callee) } :highaddr
+}
+
+//--- signed1.ld
+
+ENTRY(_start)
+PHDRS {
+ posaddr PT_LOAD FLAGS(0x1 | 0x4);
+ negaddr PT_LOAD FLAGS(0x1 | 0x4);
+}
+SECTIONS {
+ .text.posaddr 0x7fff0000 : { *(.text.caller) } :posaddr
+ .text.negaddr 0x80010000 : { *(.text.callee) } :negaddr
+}
+
+//--- signed2.ld
+
+ENTRY(_start)
+PHDRS {
+ posaddr PT_LOAD FLAGS(0x1 | 0x4);
+ negaddr PT_LOAD FLAGS(0x1 | 0x4);
+}
+SECTIONS {
+ .text.posaddr 0x7fff0000 : { *(.text.callee) } :posaddr
+ .text.negaddr 0x80010000 : { *(.text.caller) } :negaddr
+}
>From b7114abfa479469e6002249125f40872cfc652c5 Mon Sep 17 00:00:00 2001
From: Simon Tatham <simon.tatham at arm.com>
Date: Tue, 28 Oct 2025 15:00:38 +0000
Subject: [PATCH 2/2] Polish tests
---
lld/test/ELF/arm-wraparound-veneer.s | 94 ++++++++++++----------------
1 file changed, 39 insertions(+), 55 deletions(-)
diff --git a/lld/test/ELF/arm-wraparound-veneer.s b/lld/test/ELF/arm-wraparound-veneer.s
index 6fe211edc5e10..74dd6f29d8170 100644
--- a/lld/test/ELF/arm-wraparound-veneer.s
+++ b/lld/test/ELF/arm-wraparound-veneer.s
@@ -2,57 +2,57 @@
// RUN: rm -rf %t && split-file %s %t && cd %t
// RUN: llvm-mc -filetype=obj -triple=armv7-none-eabi code.s -o code.o
// RUN: ld.lld -T unsigned1.ld code.o -o unsigned1.elf
-// RUN: llvm-objdump --triple=armv7 -d unsigned1.elf | FileCheck %s --check-prefix=UNSIGNED1
+// RUN: llvm-objdump --triple=armv7 --no-show-raw-insn -d unsigned1.elf | FileCheck %s --check-prefix=UNSIGNED1
// RUN: ld.lld -T unsigned2.ld code.o -o unsigned2.elf
-// RUN: llvm-objdump --triple=armv7 -d unsigned2.elf | FileCheck %s --check-prefix=UNSIGNED2
+// RUN: llvm-objdump --triple=armv7 --no-show-raw-insn -d unsigned2.elf | FileCheck %s --check-prefix=UNSIGNED2
// RUN: ld.lld -T signed1.ld code.o -o signed1.elf
-// RUN: llvm-objdump --triple=armv7 -d signed1.elf | FileCheck %s --check-prefix=SIGNED1
+// RUN: llvm-objdump --triple=armv7 --no-show-raw-insn -d signed1.elf | FileCheck %s --check-prefix=SIGNED1
// RUN: ld.lld -T signed2.ld code.o -o signed2.elf
-// RUN: llvm-objdump --triple=armv7 -d signed2.elf | FileCheck %s --check-prefix=SIGNED2
+// RUN: llvm-objdump --triple=armv7 --no-show-raw-insn -d signed2.elf | FileCheck %s --check-prefix=SIGNED2
-// The aim of this test is to ensure that a BL instruction near one end of the
-// address space can reach a function at the extreme other end, directly, using
-// a branch offset that makes the address wrap round. We check this at both the
-// unsigned wraparound point (one address near 0 and the other near 0xFFFFFFFF)
-// and the signed wraparound point (addresses either side of 0x80000000),
-// crossing the boundary in both directions. In all four cases we expect a
-// direct branch with no veneer.
+/// The aim of this test is to ensure that a BL instruction near one end of the
+/// address space can reach a function at the extreme other end, directly,
+/// using a branch offset that makes the address wrap round. We check this at
+/// both the unsigned wraparound point (one address near 0 and the other near
+/// 0xFFFFFFFF) and the signed wraparound point (addresses either side of
+/// 0x80000000), crossing the boundary in both directions. In all four cases we
+/// expect a direct branch with no veneer.
// UNSIGNED1: Disassembly of section .text.lowaddr:
-// UNSIGNED1: 00010000 <func>:
-// UNSIGNED1: 10000: e12fff1e bx lr
+// UNSIGNED1: <func>:
+// UNSIGNED1: 10000: bx lr
//
// UNSIGNED1: Disassembly of section .text.highaddr:
-// UNSIGNED1: ffff0000 <_start>:
-// UNSIGNED1: ffff0000: eb007ffe bl 0x10000
-// UNSIGNED1: ffff0004: e12fff1e bx lr
+// UNSIGNED1: <_start>:
+// UNSIGNED1: ffff0000: bl 0x10000
+// UNSIGNED1-NEXT: bx lr
// UNSIGNED2: Disassembly of section .text.lowaddr:
-// UNSIGNED2: 00010000 <_start>:
-// UNSIGNED2: 10000: ebff7ffe bl 0xffff0000
-// UNSIGNED2: 10004: e12fff1e bx lr
+// UNSIGNED2: <_start>:
+// UNSIGNED2: 10000: bl 0xffff0000
+// UNSIGNED2-NEXT: bx lr
//
// UNSIGNED2: Disassembly of section .text.highaddr:
-// UNSIGNED2: ffff0000 <func>:
-// UNSIGNED2: ffff0000: e12fff1e bx lr
+// UNSIGNED2: <func>:
+// UNSIGNED2: ffff0000: bx lr
// SIGNED1: Disassembly of section .text.posaddr:
-// SIGNED1: 7fff0000 <_start>:
-// SIGNED1: 7fff0000: eb007ffe bl 0x80010000
-// SIGNED1: 7fff0004: e12fff1e bx lr
+// SIGNED1: <_start>:
+// SIGNED1: 7fff0000: bl 0x80010000
+// SIGNED1-NEXT: bx lr
//
// SIGNED1: Disassembly of section .text.negaddr:
-// SIGNED1: 80010000 <func>:
-// SIGNED1: 80010000: e12fff1e bx lr
+// SIGNED1: <func>:
+// SIGNED1: 80010000: bx lr
// SIGNED2: Disassembly of section .text.posaddr:
-// SIGNED2: 7fff0000 <func>:
-// SIGNED2: 7fff0000: e12fff1e bx lr
+// SIGNED2: <func>:
+// SIGNED2: 7fff0000: bx lr
//
// SIGNED2: Disassembly of section .text.negaddr:
-// SIGNED2: 80010000 <_start>:
-// SIGNED2: 80010000: ebff7ffe bl 0x7fff0000
-// SIGNED2: 80010004: e12fff1e bx lr
+// SIGNED2: <_start>:
+// SIGNED2: 80010000: bl 0x7fff0000
+// SIGNED2-NEXT: bx lr
//--- code.s
@@ -72,47 +72,31 @@ _start:
//--- unsigned1.ld
ENTRY(_start)
-PHDRS {
- lowaddr PT_LOAD FLAGS(0x1 | 0x4);
- highaddr PT_LOAD FLAGS(0x1 | 0x4);
-}
SECTIONS {
- .text.lowaddr 0x00010000 : { *(.text.callee) } :lowaddr
- .text.highaddr 0xffff0000 : { *(.text.caller) } :highaddr
+ .text.lowaddr 0x00010000 : AT(0x00010000) { *(.text.callee) }
+ .text.highaddr 0xffff0000 : AT(0xffff0000) { *(.text.caller) }
}
//--- unsigned2.ld
ENTRY(_start)
-PHDRS {
- lowaddr PT_LOAD FLAGS(0x1 | 0x4);
- highaddr PT_LOAD FLAGS(0x1 | 0x4);
-}
SECTIONS {
- .text.lowaddr 0x00010000 : { *(.text.caller) } :lowaddr
- .text.highaddr 0xffff0000 : { *(.text.callee) } :highaddr
+ .text.lowaddr 0x00010000 : AT(0x00010000) { *(.text.caller) }
+ .text.highaddr 0xffff0000 : AT(0xffff0000) { *(.text.callee) }
}
//--- signed1.ld
ENTRY(_start)
-PHDRS {
- posaddr PT_LOAD FLAGS(0x1 | 0x4);
- negaddr PT_LOAD FLAGS(0x1 | 0x4);
-}
SECTIONS {
- .text.posaddr 0x7fff0000 : { *(.text.caller) } :posaddr
- .text.negaddr 0x80010000 : { *(.text.callee) } :negaddr
+ .text.posaddr 0x7fff0000 : AT(0x7fff0000) { *(.text.caller) }
+ .text.negaddr 0x80010000 : AT(0x80010000) { *(.text.callee) }
}
//--- signed2.ld
ENTRY(_start)
-PHDRS {
- posaddr PT_LOAD FLAGS(0x1 | 0x4);
- negaddr PT_LOAD FLAGS(0x1 | 0x4);
-}
SECTIONS {
- .text.posaddr 0x7fff0000 : { *(.text.callee) } :posaddr
- .text.negaddr 0x80010000 : { *(.text.caller) } :negaddr
+ .text.posaddr 0x7fff0000 : AT(0x7fff0000) { *(.text.callee) }
+ .text.negaddr 0x80010000 : AT(0x80010000) { *(.text.caller) }
}
More information about the llvm-commits
mailing list