[PATCH] LoongArch: Allow to relax instructions into NOPs after handling alignment

WANG Xuerui i.swmail@xen0n.name
Sun Jun 29 17:33:42 GMT 2025


From: WANG Xuerui <git@xen0n.name>

Right now, LoongArch linker relaxation is 2-pass, since after alignment
is done, byte deletion can no longer happen. However, as the alignment
pass also shrinks text sections, new relaxation chances may well be
created after alignment is done. Although at this point we can no longer
delete unused instructions without disturbing alignment, we can still
replace them with NOPs; popular LoongArch micro-architectures can
eliminate NOPs during execution, so we can expect a (very) slight
performance improvement from those late-created relaxation chances.

To achieve this, the number of relax passes is raised to 3 for
LoongArch, and every relaxation handler except loongarch_relax_align is
migrated to a new helper loongarch_relax_delete_or_nop, that either
deletes bytes or fills the bytes to be "deleted" with NOPs, depending on
whether the containing section already has undergone alignment. Also,
since no byte can be deleted during this relax pass, in the pass the
pending_delete_ops structure is no longer allocated, and
loongarch_calc_relaxed_addr(x) degrades to the trivial "return x" in
this case.

A simple way to roughly measure this change's effectiveness is to check
how many pcalau12i + addi.d pairs are relaxed into pcaddi's. Taking a
Firefox 140.0.2 test build of mine as an example:

Before: 47842 pcaddi's in libxul.so
After: 48080

This is a 0.5% increase, which is kind of acceptable for a peephole
optimization like this.

Signed-off-by: WANG Xuerui <git@xen0n.name>
---
 bfd/elfnn-loongarch.c                         | 97 ++++++++++++++-----
 ld/emultempl/loongarchelf.em                  |  2 +-
 .../ld-loongarch-elf/ld-loongarch-elf.exp     |  1 +
 .../ld-loongarch-elf/relax-after-alignment.d  | 31 ++++++
 .../ld-loongarch-elf/relax-after-alignment.s  | 40 ++++++++
 5 files changed, 146 insertions(+), 25 deletions(-)
 create mode 100644 ld/testsuite/ld-loongarch-elf/relax-after-alignment.d
 create mode 100644 ld/testsuite/ld-loongarch-elf/relax-after-alignment.s

diff --git a/bfd/elfnn-loongarch.c b/bfd/elfnn-loongarch.c
index a480a709897..c74aac77e31 100644
--- a/bfd/elfnn-loongarch.c
+++ b/bfd/elfnn-loongarch.c
@@ -173,6 +173,10 @@ loongarch_elf_new_section_hook (bfd *abfd, asection *sec)
 #define loongarch_elf_hash_table(p)					\
     ((struct loongarch_elf_link_hash_table *) ((p)->hash))		\
 
+/* During linker relaxation, indicates whether the section has already
+   undergone alignment processing and no more byte deletion is possible.  */
+#define loongarch_sec_closed_for_deletion(sec) ((sec)->sec_flg0)
+
 #define MINUS_ONE ((bfd_vma) 0 - 1)
 
 #define sec_addr(sec) ((sec)->output_section->vma + (sec)->output_offset)
@@ -4789,7 +4793,10 @@ loongarch_calc_relaxed_addr (struct bfd_link_info *info, bfd_vma offset)
   struct pending_delete_op *op;
   splay_tree_node node;
 
-  BFD_ASSERT (pdops != NULL);
+  if (!pdops)
+    /* Currently this means we are past the stages where byte deletion could
+       possibly happen.  */
+    return offset;
 
   /* Find the op that starts just before the given address.  */
   node = splay_tree_predecessor (pdops, (splay_tree_key)offset);
@@ -4814,9 +4821,9 @@ loongarch_calc_relaxed_addr (struct bfd_link_info *info, bfd_vma offset)
 
 static void
 loongarch_relax_delete_bytes (bfd *abfd,
-			  bfd_vma addr,
-			  size_t count,
-			  struct bfd_link_info *link_info)
+			      bfd_vma addr,
+			      size_t count,
+			      struct bfd_link_info *link_info)
 {
   struct loongarch_elf_link_hash_table *htab
       = loongarch_elf_hash_table (link_info);
@@ -4867,6 +4874,34 @@ loongarch_relax_delete_bytes (bfd *abfd,
     }
 }
 
+static void
+loongarch_relax_delete_or_nop (bfd *abfd,
+			       asection *sec,
+			       bfd_vma addr,
+			       size_t count,
+			       struct bfd_link_info *link_info)
+{
+  struct bfd_elf_section_data *data = elf_section_data (sec);
+  bfd_byte *contents = data->this_hdr.contents;
+
+  BFD_ASSERT (count % 4 == 0);
+
+  if (!loongarch_sec_closed_for_deletion (sec))
+    {
+      /* Deletions are still possible within the section.  */
+      loongarch_relax_delete_bytes (abfd, addr, count, link_info);
+      return;
+    }
+
+  /* We can no longer delete bytes in the section after enforcing alignment.
+     But as the resulting shrinkage may open up a few more relaxation chances,
+     allowing unnecessary instructions to be replaced with NOPs instead of
+     being removed altogether may still benefit performance to a lesser
+     extent.  */
+  for (; count; addr += 4, count -= 4)
+    bfd_put (32, abfd, LARCH_NOP, contents + addr);
+}
+
 static void
 loongarch_relax_perform_deletes (bfd *abfd, asection *sec,
 				 struct bfd_link_info *link_info)
@@ -5135,7 +5170,7 @@ loongarch_tls_perform_trans (bfd *abfd, asection *sec,
 	bfd_put (32, abfd, LARCH_NOP, contents + rel->r_offset);
 	/* link with -relax option will delete NOP.  */
 	if (!info->disable_target_specific_optimizations)
-	  loongarch_relax_delete_bytes (abfd, rel->r_offset, 4, info);
+	  loongarch_relax_delete_or_nop (abfd, sec, rel->r_offset, 4, info);
 	return true;
 
       case R_LARCH_TLS_IE_PC_HI20:
@@ -5250,7 +5285,7 @@ loongarch_relax_tls_le (bfd *abfd, asection *sec, asection *sym_sec,
 	    if (symval < 0x800)
 	      {
 		rel->r_info = ELFNN_R_INFO (0, R_LARCH_NONE);
-		loongarch_relax_delete_bytes (abfd, rel->r_offset,
+		loongarch_relax_delete_or_nop (abfd, sec, rel->r_offset,
 		    4, link_info);
 	      }
 	    break;
@@ -5275,8 +5310,8 @@ loongarch_relax_tls_le (bfd *abfd, asection *sec, asection *sym_sec,
 	  case R_LARCH_TLS_LE64_LO20:
 	  case R_LARCH_TLS_LE64_HI12:
 	    rel->r_info = ELFNN_R_INFO (0, R_LARCH_NONE);
-	    loongarch_relax_delete_bytes (abfd, rel->r_offset,
-					  4, link_info);
+	    loongarch_relax_delete_or_nop (abfd, sec, rel->r_offset,
+					   4, link_info);
 	    break;
 
 	  case R_LARCH_TLS_LE_LO12:
@@ -5376,7 +5411,7 @@ loongarch_relax_pcala_addi (bfd *abfd, asection *sec, asection *sym_sec,
 				 R_LARCH_PCREL20_S2);
   rel_lo->r_info = ELFNN_R_INFO (0, R_LARCH_NONE);
 
-  loongarch_relax_delete_bytes (abfd, rel_lo->r_offset, 4, info);
+  loongarch_relax_delete_or_nop (abfd, sec, rel_lo->r_offset, 4, info);
 
   return true;
 }
@@ -5436,7 +5471,7 @@ loongarch_relax_call36 (bfd *abfd, asection *sec, asection *sym_sec,
   /* Adjust relocations.  */
   rel->r_info = ELFNN_R_INFO (ELFNN_R_SYM (rel->r_info), R_LARCH_B26);
   /* Delete jirl instruction.  */
-  loongarch_relax_delete_bytes (abfd, rel->r_offset + 4, 4, info);
+  loongarch_relax_delete_or_nop (abfd, sec, rel->r_offset + 4, 4, info);
   return true;
 }
 
@@ -5555,7 +5590,7 @@ loongarch_relax_align (bfd *abfd, asection *sec, asection *sym_sec,
 
   /* Once we've handled an R_LARCH_ALIGN in a section,
      we can't relax anything else in this section.  */
-  sec->sec_flg0 = true;
+  loongarch_sec_closed_for_deletion (sec) = true;
   rel->r_info = ELFNN_R_INFO (0, R_LARCH_NONE);
 
   /* If skipping more bytes than the specified maximum,
@@ -5653,7 +5688,7 @@ loongarch_relax_tls_ld_gd_desc (bfd *abfd, asection *sec, asection *sym_sec,
     }
   rel_lo->r_info = ELFNN_R_INFO (0, R_LARCH_NONE);
 
-  loongarch_relax_delete_bytes (abfd, rel_lo->r_offset, 4, info);
+  loongarch_relax_delete_or_nop (abfd, sec, rel_lo->r_offset, 4, info);
 
   return true;
 }
@@ -5697,15 +5732,21 @@ loongarch_elf_relax_section (bfd *abfd, asection *sec,
   if (htab->layout_mutating_for_relr)
     return true;
 
+  /* Definition of LoongArch linker relaxation passes:
+
+     - Pass 0: relaxes everything except R_LARCH_ALIGN, byte deletions are
+	       performed; skipped if disable_target_specific_optimizations.
+     - Pass 1: handles alignment, byte deletions are performed.
+     - Pass 2: same as Pass 0 but replacing relaxed insns with NOP, thus
+	       preserving alignment; skip condition same as Pass 0.  */
+  bool is_alignment_pass = info->relax_pass == 1;
   if (bfd_link_relocatable (info)
-      || sec->sec_flg0
       || sec->reloc_count == 0
       || (sec->flags & SEC_RELOC) == 0
       || (sec->flags & SEC_HAS_CONTENTS) == 0
       /* The exp_seg_relro_adjust is enum phase_enum (0x4).  */
       || *(htab->data_segment_phase) == 4
-      || (info->disable_target_specific_optimizations
-	  && info->relax_pass == 0))
+      || (info->disable_target_specific_optimizations && !is_alignment_pass))
     return true;
 
   struct bfd_elf_section_data *data = elf_section_data (sec);
@@ -5741,7 +5782,10 @@ loongarch_elf_relax_section (bfd *abfd, asection *sec,
       htab->max_alignment = max_alignment;
     }
 
-  splay_tree pdops = pending_delete_ops_new (abfd);
+  splay_tree pdops = NULL;
+  if (!loongarch_sec_closed_for_deletion (sec))
+    pdops = pending_delete_ops_new (abfd);
+
   htab->pending_delete_ops = pdops;
 
   for (unsigned int i = 0; i < sec->reloc_count; i++)
@@ -5783,7 +5827,13 @@ loongarch_elf_relax_section (bfd *abfd, asection *sec,
 	}
 
       relax_func_t relax_func = NULL;
-      if (info->relax_pass == 0)
+      if (is_alignment_pass)
+	{
+	  if (r_type != R_LARCH_ALIGN)
+	    continue;
+	  relax_func = loongarch_relax_align;
+	}
+      else
 	{
 	  switch (r_type)
 	    {
@@ -5837,10 +5887,6 @@ loongarch_elf_relax_section (bfd *abfd, asection *sec,
 		continue;
 	    }
 	}
-      else if (info->relax_pass == 1 && r_type == R_LARCH_ALIGN)
-	relax_func = loongarch_relax_align;
-      else
-	continue;
 
       /* Four kind of relocations:
 	 Normal: symval is the symbol address.
@@ -5979,9 +6025,12 @@ loongarch_elf_relax_section (bfd *abfd, asection *sec,
 				    info, again, max_alignment);
     }
 
-  loongarch_relax_perform_deletes (abfd, sec, info);
-  htab->pending_delete_ops = NULL;
-  splay_tree_delete (pdops);
+  if (pdops)
+    {
+      loongarch_relax_perform_deletes (abfd, sec, info);
+      htab->pending_delete_ops = NULL;
+      splay_tree_delete (pdops);
+    }
 
   return true;
 }
diff --git a/ld/emultempl/loongarchelf.em b/ld/emultempl/loongarchelf.em
index 928fd83ab15..517ece16105 100644
--- a/ld/emultempl/loongarchelf.em
+++ b/ld/emultempl/loongarchelf.em
@@ -58,7 +58,7 @@ larch_elf_before_allocation (void)
 	ENABLE_RELAXATION;
     }
 
-  link_info.relax_pass = 2;
+  link_info.relax_pass = 3;
 }
 
 static void
diff --git a/ld/testsuite/ld-loongarch-elf/ld-loongarch-elf.exp b/ld/testsuite/ld-loongarch-elf/ld-loongarch-elf.exp
index 2f09a69befb..e23cdc87161 100644
--- a/ld/testsuite/ld-loongarch-elf/ld-loongarch-elf.exp
+++ b/ld/testsuite/ld-loongarch-elf/ld-loongarch-elf.exp
@@ -45,6 +45,7 @@ if [istarget "loongarch64-*-*"] {
     run_dump_test "underflow_s_5_20"
     run_dump_test "tls-le-norelax"
     run_dump_test "tls-le-relax"
+    run_dump_test "relax-after-alignment"
     run_dump_test "relax-medium-call"
     run_dump_test "relax-medium-call-1"
     run_dump_test "check_got_relax"
diff --git a/ld/testsuite/ld-loongarch-elf/relax-after-alignment.d b/ld/testsuite/ld-loongarch-elf/relax-after-alignment.d
new file mode 100644
index 00000000000..6cf5f7052ee
--- /dev/null
+++ b/ld/testsuite/ld-loongarch-elf/relax-after-alignment.d
@@ -0,0 +1,31 @@
+#name: additional relaxation chances after alignment processing
+#as:
+#ld: --defsym _start=0
+#objdump: -d --no-show-raw-insn
+
+.*:\s+file format .*
+
+
+Disassembly of section \.text:
+
+0000000120000400 <before>:
+\s*120000400:\s+pcaddi\s+\$t0, 523777
+\s*[0-9a-f]+:\s+nop\s*
+\s*[0-9a-f]+:\s+pcaddi\s+\$t0, 523775
+\s*\.\.\.
+
+0000000120000800 <gap0>:
+\s*\.\.\.
+
+00000001201ffc04 <target>:
+\s*1201ffc04:\s+break\s+0x0
+
+00000001201ffc08 <gap1>:
+\s*\.\.\.
+
+00000001203ff000 <after>:
+\s*\.\.\.
+\s*1203ff800:\s+pcaddi\s+\$t0, -524031
+\s*[0-9a-f]+:\s+nop
+\s*[0-9a-f]+:\s+pcalau12i\s+\$t0, -511
+\s*[0-9a-f]+:\s+addi\.d\s+\$t0, \$t0, -1020
diff --git a/ld/testsuite/ld-loongarch-elf/relax-after-alignment.s b/ld/testsuite/ld-loongarch-elf/relax-after-alignment.s
new file mode 100644
index 00000000000..57945cb13fa
--- /dev/null
+++ b/ld/testsuite/ld-loongarch-elf/relax-after-alignment.s
@@ -0,0 +1,40 @@
+# 0x0 pre-relax, 0x400 post-relax
+# all addresses are additionally offset by 0x120000000 without `ld -shared`
+before:
+    la.pcrel $t0, target  # should become pcaddi + nop thanks to relax pass 2
+    la.pcrel $t0, target  # should become single pcaddi in relax pass 0
+.rept 253
+.word 0
+.endr
+
+# 0x404 pre-relax, 0x800 post-relax
+gap0:
+# 255 nops + R_LARCH_ALIGN before relaxation
+# none of the aligning nops should remain after relax pass 1
+.p2align 10
+# 0x800 {pre,post}-relax
+.rept 523521
+.word 0
+.endr
+
+# 0x1ffc04 {pre,post}-relax
+target:
+    break 0
+
+# 0x1ffc08 {pre,post}-relax
+gap1:
+.rept 523518
+.word 0
+.endr
+
+# 0x3ff000 {pre,post}-relax
+after:
+# same as above
+.p2align 10
+# 0x3ff3fc pre-relax, 0x3ff000 post-relax
+.rept 512
+.word 0
+.endr
+# 0x3ffbfc pre-repax, 0x3ff800 post-relax
+    la.pcrel $t0, target  # should become pcaddi + nop thanks to relax pass 2
+    la.pcrel $t0, target  # should stay as pcalau12i + addi.d
-- 
2.48.1



More information about the Binutils mailing list