From 7757ef4a8b03a2f9d12cc5fe0dc0b252a05b1ac5 Mon Sep 17 00:00:00 2001
From: Xi Ruoyao <xry111@xry111.site>
Date: Wed, 6 Aug 2025 12:19:22 +0800
Subject: [PATCH 18/23] LoongArch: Fix symbol size after relaxation

There's a logic error in loongarch_relax_perform_deletes: when there's
not any delete operation of which the start address is strictly smaller
than the symbol address, splay_tree_predecessor() will return nullptr
and the symbol size will be unchanged even if some bytes of it are
removed.

Make the logic more complete to fix this issue.  Also factor out the
symbol size adjustment logic into a function to avoid code bloating.

Tested-by: WANG Xuerui <git@xen0n.name>
Signed-off-by: Xi Ruoyao <xry111@xry111.site>
(cherry picked from commit 99f48156dd1d02193e3b550c85cc8925826752b7)
---
 bfd/elfnn-loongarch.c                         | 102 ++++++++++--------
 .../ld-loongarch-elf/ld-loongarch-elf.exp     |   2 +
 .../ld-loongarch-elf/relax-sym-size-1.d       |   7 ++
 .../ld-loongarch-elf/relax-sym-size-1.s       |   8 ++
 .../ld-loongarch-elf/relax-sym-size-2.d       |   7 ++
 .../ld-loongarch-elf/relax-sym-size-2.s       |  19 ++++
 6 files changed, 102 insertions(+), 43 deletions(-)
 create mode 100644 ld/testsuite/ld-loongarch-elf/relax-sym-size-1.d
 create mode 100644 ld/testsuite/ld-loongarch-elf/relax-sym-size-1.s
 create mode 100644 ld/testsuite/ld-loongarch-elf/relax-sym-size-2.d
 create mode 100644 ld/testsuite/ld-loongarch-elf/relax-sym-size-2.s

diff --git a/bfd/elfnn-loongarch.c b/bfd/elfnn-loongarch.c
index 46fafb332de..505f1196987 100644
--- a/bfd/elfnn-loongarch.c
+++ b/bfd/elfnn-loongarch.c
@@ -4902,6 +4902,60 @@ loongarch_relax_delete_or_nop (bfd *abfd,
     bfd_put (32, abfd, LARCH_NOP, contents + addr);
 }
 
+/* If some bytes in a symbol is deleted, we need to adjust its size.  */
+static void
+loongarch_relax_resize_symbol (bfd_size_type *size, bfd_vma orig_value,
+			       splay_tree pdops)
+{
+  splay_tree_key key = (splay_tree_key)orig_value;
+  bfd_vma orig_end = orig_value + *size;
+  splay_tree_node node = splay_tree_predecessor (pdops, key);
+
+  if (node)
+    {
+      bfd_vma addr = (bfd_vma)node->key;
+      struct pending_delete_op *op = (struct pending_delete_op *)node->value;
+
+      /* This shouldn't happen unless people write something really insane like
+	     .reloc ., R_LARCH_ALIGN, 60
+	     .rept 15
+	     1: nop
+	     .endr
+	     .set x, 1b
+	     .size x, . - 1b
+	 But let's just try to make it "work" anyway.  */
+      if (orig_value < addr + op->size)
+	{
+	  bfd_size_type n_deleted = op->size - (orig_value - addr);
+	  if (n_deleted >= *size)
+	    {
+	      *size = 0;
+	      return;
+	    }
+
+	  *size -= n_deleted;
+	}
+    }
+
+  node = splay_tree_lookup (pdops, key);
+  if (!node)
+    node = splay_tree_successor (pdops, key);
+
+  for (; node; node = splay_tree_successor (pdops, node->key))
+    {
+      bfd_vma addr = (bfd_vma)node->key;
+      struct pending_delete_op *op = (struct pending_delete_op *)node->value;
+
+      if (addr >= orig_end)
+	return;
+
+      if (orig_end < addr + op->size)
+	*size -= orig_end - addr;
+      else
+	*size -= op->size;
+    }
+}
+
 static void
 loongarch_relax_perform_deletes (bfd *abfd, asection *sec,
 				 struct bfd_link_info *link_info)
@@ -5000,30 +5054,8 @@ loongarch_relax_perform_deletes (bfd *abfd, asection *sec,
 	    sym->st_value
 		= loongarch_calc_relaxed_addr (link_info, orig_value);
 
-	  /* If the symbol *spans* some deleted bytes, that is its *end* is in
-	     the moved bytes but its *start* isn't, then we must adjust its
-	     size.
-
-	     This test needs to use the original value of st_value, otherwise
-	     we might accidentally decrease size when deleting bytes right
-	     before the symbol.  */
-	  bfd_vma sym_end = orig_value + sym->st_size;
-	  if (sym_end <= toaddr)
-	    {
-	      splay_tree_node node = splay_tree_predecessor (
-		  pdops, (splay_tree_key)orig_value);
-	      for (; node; node = splay_tree_successor (pdops, node->key))
-		{
-		  bfd_vma addr = (bfd_vma)node->key;
-		  struct pending_delete_op *op
-		      = (struct pending_delete_op *)node->value;
-
-		  if (addr >= sym_end)
-		    break;
-		  if (orig_value <= addr && sym_end > addr)
-		    sym->st_size -= op->size;
-		}
-	    }
+	  if (orig_value + sym->st_size <= toaddr)
+	    loongarch_relax_resize_symbol (&sym->st_size, orig_value, pdops);
 	}
     }
 
@@ -5070,29 +5102,13 @@ loongarch_relax_perform_deletes (bfd *abfd, asection *sec,
 	{
 	  bfd_vma orig_value = sym_hash->root.u.def.value;
 
-	  /* As above, adjust the value.  */
+	  /* As above, adjust the value and size.  */
 	  if (orig_value <= toaddr)
 	    sym_hash->root.u.def.value
 		= loongarch_calc_relaxed_addr (link_info, orig_value);
 
-	  /* As above, adjust the size if needed.  */
-	  bfd_vma sym_end = orig_value + sym_hash->size;
-	  if (sym_end <= toaddr)
-	    {
-	      splay_tree_node node = splay_tree_predecessor (
-		  pdops, (splay_tree_key)orig_value);
-	      for (; node; node = splay_tree_successor (pdops, node->key))
-		{
-		  bfd_vma addr = (bfd_vma)node->key;
-		  struct pending_delete_op *op
-		      = (struct pending_delete_op *)node->value;
-
-		  if (addr >= sym_end)
-		    break;
-		  if (orig_value <= addr && sym_end > addr)
-		    sym_hash->size -= op->size;
-		}
-	    }
+	  if (orig_value + sym_hash->size <= toaddr)
+	    loongarch_relax_resize_symbol (&sym_hash->size, orig_value, pdops);
 	}
     }
 }
diff --git a/ld/testsuite/ld-loongarch-elf/ld-loongarch-elf.exp b/ld/testsuite/ld-loongarch-elf/ld-loongarch-elf.exp
index e23cdc87161..a33727f2efd 100644
--- a/ld/testsuite/ld-loongarch-elf/ld-loongarch-elf.exp
+++ b/ld/testsuite/ld-loongarch-elf/ld-loongarch-elf.exp
@@ -48,6 +48,8 @@ if [istarget "loongarch64-*-*"] {
     run_dump_test "relax-after-alignment"
     run_dump_test "relax-medium-call"
     run_dump_test "relax-medium-call-1"
+    run_dump_test "relax-sym-size-1"
+    run_dump_test "relax-sym-size-2"
     run_dump_test "check_got_relax"
 }
 
diff --git a/ld/testsuite/ld-loongarch-elf/relax-sym-size-1.d b/ld/testsuite/ld-loongarch-elf/relax-sym-size-1.d
new file mode 100644
index 00000000000..eec989f68be
--- /dev/null
+++ b/ld/testsuite/ld-loongarch-elf/relax-sym-size-1.d
@@ -0,0 +1,7 @@
+#source: relax-sym-size-1.s
+#ld:
+#readelf: -s
+
+#...
+ *[0-9]+: [0-9a-z]+ +8 .* _start
+#...
diff --git a/ld/testsuite/ld-loongarch-elf/relax-sym-size-1.s b/ld/testsuite/ld-loongarch-elf/relax-sym-size-1.s
new file mode 100644
index 00000000000..c70ac7176bd
--- /dev/null
+++ b/ld/testsuite/ld-loongarch-elf/relax-sym-size-1.s
@@ -0,0 +1,8 @@
+.p2align 2
+bar:
+  nop
+.globl _start
+_start:
+  la.pcrel $a0, bar
+  ret
+.size _start, . - _start
diff --git a/ld/testsuite/ld-loongarch-elf/relax-sym-size-2.d b/ld/testsuite/ld-loongarch-elf/relax-sym-size-2.d
new file mode 100644
index 00000000000..1388099d37f
--- /dev/null
+++ b/ld/testsuite/ld-loongarch-elf/relax-sym-size-2.d
@@ -0,0 +1,7 @@
+#source: relax-sym-size-2.s
+#ld:
+#readelf: -s
+
+#...
+ *[0-9]+: [0-9a-z]+ +64 .* _start
+#...
diff --git a/ld/testsuite/ld-loongarch-elf/relax-sym-size-2.s b/ld/testsuite/ld-loongarch-elf/relax-sym-size-2.s
new file mode 100644
index 00000000000..e02e16b7d26
--- /dev/null
+++ b/ld/testsuite/ld-loongarch-elf/relax-sym-size-2.s
@@ -0,0 +1,19 @@
+bar:
+  nop
+.p2align 6
+
+.reloc ., R_LARCH_ALIGN, 60
+.rept 15
+  1: nop
+.endr
+  la.pcrel $a0, bar
+  ret
+
+.reloc ., R_LARCH_ALIGN, 60
+.rept 15
+  2: nop
+.endr
+
+.globl _start
+.set _start, 1b
+.size _start, 2b - 1b
-- 
2.49.1

