[PATCH v2] AArch64: Optimize short-range ADRP/ADD sequences
Sivan Shani
sivan.shani@arm.com
Mon Sep 21 14:58:06 GMT 2026
The "ELF for the Arm 64-bit Architecture" specification, section 5.7.9,
recommends that linkers optimize PC-relative addressing sequences when
the target is within range of ADR. In particular:
ADRP x0, symbol // R_<CLS>*ADR_PREL_PG_HI21
ADD x0, x0, :lo12: symbol // R*<CLS>_ADD_ABS_LO12_NC
may be replaced with:
NOP
ADR x0, symbol // R_<CLS>_ADR_PREL_LO21
Apply this optimization when the ADRP and ADD relocations refer to the
same symbol and addend, use matching registers, and the target is within
ADR range. Do not optimize sections containing overlapping relocations.
Preserve the transformed relocation information for --emit-relocs.
Add LP64 and ILP32 coverage for range boundaries, --no-relax,
ineligible pairs and overlapping relocations.
bfd/ChangeLog:
* elfnn-aarch64.c (elfNN_aarch64_relocs_nonoverlapping_p): New.
(elfNN_aarch64_relax_adrp_add): Likewise.
(elfNN_aarch64_relocate_section): Relax eligible ADRP/ADD pairs.
ld/ChangeLog:
* testsuite/ld-aarch64/aarch64-elf.exp: Run new ADRP/ADD relaxation
tests.
* testsuite/ld-aarch64/emit-relocs-local-addend.d: Update expected
relocations after ADRP/ADD relaxation.
* testsuite/ld-aarch64/adrp-add-relax-ilp32.d: New test.
* testsuite/ld-aarch64/adrp-add-relax-ilp32.s: New test.
* testsuite/ld-aarch64/adrp-add-relax-norelax.d: New test.
* testsuite/ld-aarch64/adrp-add-relax-overlap.d: New test.
* testsuite/ld-aarch64/adrp-add-relax-overlap.s: New test.
* testsuite/ld-aarch64/adrp-add-relax-reject.d: New test.
* testsuite/ld-aarch64/adrp-add-relax-reject.s: New test.
* testsuite/ld-aarch64/adrp-add-relax.d: New test.
* testsuite/ld-aarch64/adrp-add-relax.s: New test.
---
Changes in v2:
- Reference AAELF64 section 5.7.9 in the commit message.
- Complete the ChangeLog entries.
bfd/elfnn-aarch64.c | 134 ++++++++++++++++++
ld/testsuite/ld-aarch64/aarch64-elf.exp | 5 +
.../ld-aarch64/adrp-add-relax-ilp32.d | 17 +++
.../ld-aarch64/adrp-add-relax-ilp32.s | 15 ++
.../ld-aarch64/adrp-add-relax-norelax.d | 18 +++
.../ld-aarch64/adrp-add-relax-overlap.d | 12 ++
.../ld-aarch64/adrp-add-relax-overlap.s | 19 +++
.../ld-aarch64/adrp-add-relax-reject.d | 16 +++
.../ld-aarch64/adrp-add-relax-reject.s | 28 ++++
ld/testsuite/ld-aarch64/adrp-add-relax.d | 18 +++
ld/testsuite/ld-aarch64/adrp-add-relax.s | 22 +++
.../ld-aarch64/emit-relocs-local-addend.d | 8 +-
12 files changed, 308 insertions(+), 4 deletions(-)
create mode 100644 ld/testsuite/ld-aarch64/adrp-add-relax-ilp32.d
create mode 100644 ld/testsuite/ld-aarch64/adrp-add-relax-ilp32.s
create mode 100644 ld/testsuite/ld-aarch64/adrp-add-relax-norelax.d
create mode 100644 ld/testsuite/ld-aarch64/adrp-add-relax-overlap.d
create mode 100644 ld/testsuite/ld-aarch64/adrp-add-relax-overlap.s
create mode 100644 ld/testsuite/ld-aarch64/adrp-add-relax-reject.d
create mode 100644 ld/testsuite/ld-aarch64/adrp-add-relax-reject.s
create mode 100644 ld/testsuite/ld-aarch64/adrp-add-relax.d
create mode 100644 ld/testsuite/ld-aarch64/adrp-add-relax.s
diff --git a/bfd/elfnn-aarch64.c b/bfd/elfnn-aarch64.c
index ce060dd755b..3200993d3b9 100644
--- a/bfd/elfnn-aarch64.c
+++ b/bfd/elfnn-aarch64.c
@@ -7047,6 +7047,124 @@ elfNN_aarch64_tls_relax (struct elf_aarch64_link_hash_table *globals,
return bfd_reloc_ok;
}
+/* Return true if all live relocations in INPUT_SECTION are ordered and their
+ relocation fields do not overlap. */
+
+static bool
+elfNN_aarch64_relocs_nonoverlapping_p (bfd *input_bfd,
+ asection *input_section,
+ const Elf_Internal_Rela *relocs)
+{
+ const Elf_Internal_Rela *rel;
+ const Elf_Internal_Rela *relend
+ = relocs + input_section->reloc_count;
+ bfd_vma previous_end = 0;
+
+ for (rel = relocs; rel < relend; rel++)
+ {
+ unsigned int r_type = ELFNN_R_TYPE (rel->r_info);
+ reloc_howto_type *howto;
+ bfd_size_type size;
+
+ if (r_type == R_AARCH64_NONE || r_type == R_AARCH64_NULL)
+ continue;
+
+ howto = elfNN_aarch64_howto_from_type (input_bfd, r_type);
+ if (howto == NULL)
+ return false;
+
+ size = bfd_get_reloc_size (howto);
+ if (size == 0
+ || rel->r_offset > input_section->size
+ || size > input_section->size - rel->r_offset
+ || rel->r_offset < previous_end)
+ return false;
+
+ previous_end = rel->r_offset + size;
+ }
+
+ return true;
+}
+
+/* Optimize adjacent ADRP/ADD relocation pair.
+ Overwrite both instructions when the ADD target is in range of ADR. */
+
+static bool
+elfNN_aarch64_relax_adrp_add (asection *input_section,
+ bfd_byte *contents,
+ Elf_Internal_Rela *relocs,
+ Elf_Internal_Rela *rel,
+ bfd_vma relocation,
+ struct bfd_link_info *info,
+ asection *sym_sec,
+ struct elf_link_hash_entry *h,
+ char sym_type,
+ bool unresolved_reloc)
+{
+ Elf_Internal_Rela *adrp_rel;
+ bfd_signed_vma offset;
+ bfd_vma place;
+ uint32_t adrp;
+ uint32_t add;
+ unsigned int reg;
+ unsigned long r_symndx;
+
+ if (info->disable_target_specific_optimizations > 1
+ || rel == relocs
+ || unresolved_reloc
+ || sym_sec == NULL
+ || sym_sec->output_section == NULL
+ || bfd_is_und_section (sym_sec)
+ || sym_type == STT_TLS
+ || sym_type == STT_GNU_IFUNC)
+ return false;
+
+ if (h != NULL
+ && ((h->root.type != bfd_link_hash_defined
+ && h->root.type != bfd_link_hash_defweak)
+ || !_bfd_elf_symbol_refs_local_p (h, info, 1)))
+ return false;
+
+ adrp_rel = rel - 1;
+ if (input_section->size < 8
+ || adrp_rel->r_offset > input_section->size - 8
+ || ELFNN_R_TYPE (adrp_rel->r_info)
+ != AARCH64_R (ADR_PREL_PG_HI21)
+ || ELFNN_R_TYPE (rel->r_info) != AARCH64_R (ADD_ABS_LO12_NC)
+ || adrp_rel->r_offset + 4 != rel->r_offset
+ || ELFNN_R_SYM (adrp_rel->r_info) != ELFNN_R_SYM (rel->r_info)
+ || adrp_rel->r_addend != rel->r_addend
+ || (adrp_rel->r_offset & 3) != 0)
+ return false;
+
+ adrp = bfd_getl32 (contents + adrp_rel->r_offset);
+ add = bfd_getl32 (contents + rel->r_offset);
+ reg = adrp & 0x1f;
+ if ((adrp & 0x9f000000) != 0x90000000
+ || (add & 0xffc00000) != 0x91000000
+ || reg == 31
+ || (add & 0x1f) != reg
+ || ((add >> 5) & 0x1f) != reg)
+ return false;
+
+ place = (input_section->output_section->vma
+ + input_section->output_offset + rel->r_offset);
+ offset = (bfd_signed_vma) (relocation + rel->r_addend - place);
+ if (offset < -((bfd_signed_vma) 1 << 20)
+ || offset >= ((bfd_signed_vma) 1 << 20))
+ return false;
+
+ r_symndx = ELFNN_R_SYM (rel->r_info);
+ bfd_putl32 (INSN_NOP, contents + adrp_rel->r_offset);
+ bfd_putl32 (_bfd_aarch64_reencode_adr_imm
+ (AARCH64_ADR_OP | reg, (uint32_t) offset),
+ contents + rel->r_offset);
+ adrp_rel->r_info = ELFNN_R_INFO (STN_UNDEF, R_AARCH64_NONE);
+ adrp_rel->r_addend = 0;
+ rel->r_info = ELFNN_R_INFO (r_symndx, AARCH64_R (ADR_PREL_LO21));
+ return true;
+}
+
/* Relocate an AArch64 ELF section. */
static int
@@ -7065,6 +7183,7 @@ elfNN_aarch64_relocate_section (struct bfd_link_info *info,
const char *name;
struct elf_aarch64_link_hash_table *globals;
bool save_addend = false;
+ bool adrp_add_relax_safe;
bfd_vma addend = 0;
globals = elf_aarch64_hash_table (info);
@@ -7074,6 +7193,11 @@ elfNN_aarch64_relocate_section (struct bfd_link_info *info,
rel = relocs;
relend = relocs + input_section->reloc_count;
+ adrp_add_relax_safe
+ = (!bfd_link_relocatable (info)
+ && info->disable_target_specific_optimizations <= 1
+ && elfNN_aarch64_relocs_nonoverlapping_p
+ (input_bfd, input_section, relocs));
for (; rel < relend; rel++)
{
unsigned int r_type;
@@ -7224,6 +7348,16 @@ elfNN_aarch64_relocate_section (struct bfd_link_info *info,
else
save_addend = false;
+ if (r == bfd_reloc_continue
+ && adrp_add_relax_safe
+ && elfNN_aarch64_relax_adrp_add
+ (input_section, contents, relocs, rel, relocation, info, sec,
+ h, sym_type, unresolved_reloc))
+ {
+ r = bfd_reloc_ok;
+ unresolved_reloc = false;
+ }
+
if (r == bfd_reloc_continue)
r = elfNN_aarch64_final_link_relocate (howto, input_bfd,
info->output_bfd,
diff --git a/ld/testsuite/ld-aarch64/aarch64-elf.exp b/ld/testsuite/ld-aarch64/aarch64-elf.exp
index f4941b01518..46fd6a0c6f8 100644
--- a/ld/testsuite/ld-aarch64/aarch64-elf.exp
+++ b/ld/testsuite/ld-aarch64/aarch64-elf.exp
@@ -290,6 +290,11 @@ run_dump_test_lp64 "add-imm-zero-opt-norelax"
run_dump_test_lp64 "add-imm-zero-opt-tlsgd"
run_dump_test_lp64 "add-imm-zero-opt-tlsld"
run_dump_test_lp64 "add-imm-zero-opt-tlsdesc"
+run_dump_test_lp64 "adrp-add-relax"
+run_dump_test_lp64 "adrp-add-relax-norelax"
+run_dump_test_lp64 "adrp-add-relax-reject"
+run_dump_test_lp64 "adrp-add-relax-overlap"
+run_dump_test "adrp-add-relax-ilp32"
run_dump_test "tlsle-symbol-offset"
run_dump_test "gc-got-relocs"
run_dump_test "gc-tls-relocs"
diff --git a/ld/testsuite/ld-aarch64/adrp-add-relax-ilp32.d b/ld/testsuite/ld-aarch64/adrp-add-relax-ilp32.d
new file mode 100644
index 00000000000..f72844b50e0
--- /dev/null
+++ b/ld/testsuite/ld-aarch64/adrp-add-relax-ilp32.d
@@ -0,0 +1,17 @@
+#source: adrp-add-relax-ilp32.s
+#as: -mabi=ilp32
+#ld: -m [aarch64_choose_ilp32_emul] -T relocs-ilp32.ld -e0 --emit-relocs
+#notarget: *-*-nto*
+#objdump: -dr
+
+.*: +file format .*
+
+Disassembly of section .text:
+
+0+10000 <_start>:
+ +10000: d503201f nop
+#...
+ +10004: 10000040 adr x0, 1000c <target>
+ 10004: R_AARCH64_P32_ADR_PREL_LO21 target
+ +10008: d65f03c0 ret
+#...
diff --git a/ld/testsuite/ld-aarch64/adrp-add-relax-ilp32.s b/ld/testsuite/ld-aarch64/adrp-add-relax-ilp32.s
new file mode 100644
index 00000000000..91f4991a23b
--- /dev/null
+++ b/ld/testsuite/ld-aarch64/adrp-add-relax-ilp32.s
@@ -0,0 +1,15 @@
+ .text
+ .global _start
+ .type _start, %function
+_start:
+ adrp x0, target
+ add x0, x0, :lo12:target
+ ret
+ .size _start, .-_start
+
+ .global target
+ .hidden target
+ .type target, %object
+target:
+ .word 0
+ .size target, .-target
diff --git a/ld/testsuite/ld-aarch64/adrp-add-relax-norelax.d b/ld/testsuite/ld-aarch64/adrp-add-relax-norelax.d
new file mode 100644
index 00000000000..32a816c76fa
--- /dev/null
+++ b/ld/testsuite/ld-aarch64/adrp-add-relax-norelax.d
@@ -0,0 +1,18 @@
+#source: adrp-add-relax.s
+#ld: -Ttext=0x200000 --defsym=neg_limit=0x100004 --defsym=neg_overflow=0x10000b --defsym=pos_limit=0x300013 --defsym=pos_overflow=0x30001c -e _start --no-relax
+#objdump: -dr
+
+.*: +file format .*
+
+Disassembly of section .text:
+
+0+200000 <_start>:
+ +200000: 90fff800 adrp x0, .*
+ +200004: 91001000 add x0, x0, #0x4
+ +200008: 90fff801 adrp x1, .*
+ +20000c: 91002c21 add x1, x1, #0xb
+ +200010: 90000802 adrp x2, .*
+ +200014: 91004c42 add x2, x2, #0x13
+ +200018: 90000803 adrp x3, .*
+ +20001c: 91007063 add x3, x3, #0x1c
+ +200020: d65f03c0 ret
diff --git a/ld/testsuite/ld-aarch64/adrp-add-relax-overlap.d b/ld/testsuite/ld-aarch64/adrp-add-relax-overlap.d
new file mode 100644
index 00000000000..c612300ee1c
--- /dev/null
+++ b/ld/testsuite/ld-aarch64/adrp-add-relax-overlap.d
@@ -0,0 +1,12 @@
+#source: adrp-add-relax-overlap.s
+#ld: -Ttext=0x10000 --section-start=.data=0x10880 --defsym=zero=0 -e _start
+#objdump: -dr
+
+.*: +file format .*
+
+Disassembly of section .text:
+
+0+10000 <_start>:
+ +10000: 90000004 adrp x4, .*
+ +10004: 91220084 add x4, x4, #0x880
+ +10008: d65f03c0 ret
diff --git a/ld/testsuite/ld-aarch64/adrp-add-relax-overlap.s b/ld/testsuite/ld-aarch64/adrp-add-relax-overlap.s
new file mode 100644
index 00000000000..cc3b3e3dd22
--- /dev/null
+++ b/ld/testsuite/ld-aarch64/adrp-add-relax-overlap.s
@@ -0,0 +1,19 @@
+ .text
+ .global _start
+ .type _start, %function
+ .global zero
+ .hidden zero
+_start:
+ adrp x4, target
+.Ladd:
+ add x4, x4, :lo12:target
+ .reloc .Ladd, R_AARCH64_ADD_ABS_LO12_NC, zero
+ ret
+ .size _start, .-_start
+
+ .data
+ .global target
+ .hidden target
+ .balign 8
+target:
+ .xword 0
diff --git a/ld/testsuite/ld-aarch64/adrp-add-relax-reject.d b/ld/testsuite/ld-aarch64/adrp-add-relax-reject.d
new file mode 100644
index 00000000000..16e4666672b
--- /dev/null
+++ b/ld/testsuite/ld-aarch64/adrp-add-relax-reject.d
@@ -0,0 +1,16 @@
+#source: adrp-add-relax-reject.s
+#ld: -Ttext=0x10000 --section-start=.data=0x10880 -e _start
+#objdump: -dr
+
+.*: +file format .*
+
+Disassembly of section .text:
+
+0+10000 <_start>:
+ +10000: 90000000 adrp x0, .*
+ +10004: 91220001 add x1, x0, #0x880
+ +10008: 90000002 adrp x2, .*
+ +1000c: 91222042 add x2, x2, #0x888
+ +10010: 90000003 adrp x3, .*
+ +10014: 91222063 add x3, x3, #0x888
+ +10018: d65f03c0 ret
diff --git a/ld/testsuite/ld-aarch64/adrp-add-relax-reject.s b/ld/testsuite/ld-aarch64/adrp-add-relax-reject.s
new file mode 100644
index 00000000000..4d139e865d5
--- /dev/null
+++ b/ld/testsuite/ld-aarch64/adrp-add-relax-reject.s
@@ -0,0 +1,28 @@
+ .text
+ .global _start
+ .type _start, %function
+_start:
+ /* Destination register mismatch. */
+ adrp x0, target
+ add x1, x0, :lo12:target
+
+ /* Symbol mismatch. */
+ adrp x2, target
+ add x2, x2, :lo12:other
+
+ /* Addend mismatch. */
+ adrp x3, target
+ add x3, x3, :lo12:target+8
+ ret
+ .size _start, .-_start
+
+ .data
+ .global target
+ .global other
+ .hidden target
+ .hidden other
+ .balign 8
+target:
+ .xword 0
+other:
+ .xword 0
diff --git a/ld/testsuite/ld-aarch64/adrp-add-relax.d b/ld/testsuite/ld-aarch64/adrp-add-relax.d
new file mode 100644
index 00000000000..9c519a313c2
--- /dev/null
+++ b/ld/testsuite/ld-aarch64/adrp-add-relax.d
@@ -0,0 +1,18 @@
+#source: adrp-add-relax.s
+#ld: -Ttext=0x200000 --defsym=neg_limit=0x100004 --defsym=neg_overflow=0x10000b --defsym=pos_limit=0x300013 --defsym=pos_overflow=0x30001c -e _start
+#objdump: -dr
+
+.*: +file format .*
+
+Disassembly of section .text:
+
+0+200000 <_start>:
+ +200000: d503201f nop
+ +200004: 10800000 adr x0, .*
+ +200008: 90fff801 adrp x1, .*
+ +20000c: 91002c21 add x1, x1, #0xb
+ +200010: d503201f nop
+ +200014: 707fffe2 adr x2, .*
+ +200018: 90000803 adrp x3, .*
+ +20001c: 91007063 add x3, x3, #0x1c
+ +200020: d65f03c0 ret
diff --git a/ld/testsuite/ld-aarch64/adrp-add-relax.s b/ld/testsuite/ld-aarch64/adrp-add-relax.s
new file mode 100644
index 00000000000..9002b655eca
--- /dev/null
+++ b/ld/testsuite/ld-aarch64/adrp-add-relax.s
@@ -0,0 +1,22 @@
+ .text
+ .global _start
+ .type _start, %function
+ .global neg_limit
+ .global neg_overflow
+ .global pos_limit
+ .global pos_overflow
+ .hidden neg_limit
+ .hidden neg_overflow
+ .hidden pos_limit
+ .hidden pos_overflow
+_start:
+ adrp x0, neg_limit
+ add x0, x0, :lo12:neg_limit
+ adrp x1, neg_overflow
+ add x1, x1, :lo12:neg_overflow
+ adrp x2, pos_limit
+ add x2, x2, :lo12:pos_limit
+ adrp x3, pos_overflow
+ add x3, x3, :lo12:pos_overflow
+ ret
+ .size _start, .-_start
diff --git a/ld/testsuite/ld-aarch64/emit-relocs-local-addend.d b/ld/testsuite/ld-aarch64/emit-relocs-local-addend.d
index a5ce63d6e0a..b2c82f7aa1a 100644
--- a/ld/testsuite/ld-aarch64/emit-relocs-local-addend.d
+++ b/ld/testsuite/ld-aarch64/emit-relocs-local-addend.d
@@ -5,10 +5,10 @@
Relocation section '\.rela\.text' at offset .* contains 4 entries:
Offset Info Type Sym\. Value Sym\. Name \+ Addend
-000000010000 000200000113 R_AARCH64_ADR_PRE 0000000000010018 \.rodata \+ 0
-000000010004 000200000115 R_AARCH64_ADD_ABS 0000000000010018 \.rodata \+ 0
-00000001000c 000200000113 R_AARCH64_ADR_PRE 0000000000010018 \.rodata \+ 10
-000000010010 000200000115 R_AARCH64_ADD_ABS 0000000000010018 \.rodata \+ 10
+000000010000 000000000000 R_AARCH64_NONE 0
+000000010004 000200000112 R_AARCH64_ADR_PRE 0000000000010018 \.rodata \+ 0
+00000001000c 000000000000 R_AARCH64_NONE 0
+000000010010 000200000112 R_AARCH64_ADR_PRE 0000000000010018 \.rodata \+ 10
Relocation section '\.rela\.rodata' at offset .* contains 2 entries:
Offset Info Type Sym\. Value Sym. Name \+ Addend
--
2.43.0
More information about the Binutils
mailing list