[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