[binutils-gdb] AVR: Let --relax elide global RJMP .+0

Georg-Johann Lay gjl@sourceware.org
Fri Aug 21 11:13:40 GMT 2026


https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=c2c66c11d5bae80d44e91984264386cb3f63b3ca

commit c2c66c11d5bae80d44e91984264386cb3f63b3ca
Author: Georg-Johann Lay <avr@gjlay.de>
Date:   Sat Aug 15 21:08:06 2026 +0200

    AVR: Let --relax elide global RJMP .+0
    
    This patch extends the avr-specific linker relaxation machine to
    elide trivial RJMP instructions.  These are RJMP instructions
    with a jump offset of 0 that are targeting a global symbol.
    
    The use case is when a function is tail-calling some other function,
    and the latter happens to be located right after the former, like in:
    
       rjmp  func     ;; from module A
       .global func   ;; from module B
       func:
    
    Plain RJMP .+0 that don't target a global symbol are not touched
    since they are emit by __builtin_avr_delay_cycles (2) etc.
    
    The feature can be turned off with the new ld option --no-elide-rjmp0.
    
    The patch passes without new regressions when run in the AVR GCC
    testsuite with --tool_opts="-mrelax".
    
    bfd/
            * elf32-avr.c (avr_elide_rjmp0): New static variable.
            (elf32_avr_setup_params): Set it.
            (elf32_avr_relax_section) <sym_is_global>: New variable.
            [avr_elide_rjmp0 && avr_is_RJMP]: Elide global RJMPs with
            a jump offset of 0.
            * elf32-avr.h (elf32_avr_setup_params): Adjust interface.
    ld/
            * ldlex.h (enum option_values) [emultempl/avrelf.em]: Add
            OPTION_NO_ELIDE_RJMP0.
            * emultempl/avrelf.em (avr_elide_rjmp0): New static variable.
            (avr_elf_set_global_bfd_parameters): Pass it.
            (PARSE_AND_LIST_LONGOPTS) <--no-elide-rjmp0>: Add and document.
            (PARSE_AND_LIST_ARGS_CASES) [OPTION_NO_ELIDE_RJMP0]: Set
            avr_elide_rjmp0 to false.

Diff:
---
 bfd/elf32-avr.c        | 96 ++++++++++++++++++++++++++++++++++++++++++++++++--
 bfd/elf32-avr.h        |  2 +-
 ld/emultempl/avrelf.em | 21 ++++++++++-
 ld/ldlex.h             |  1 +
 4 files changed, 116 insertions(+), 4 deletions(-)

diff --git a/bfd/elf32-avr.c b/bfd/elf32-avr.c
index e8831578038..929a2fe2519 100644
--- a/bfd/elf32-avr.c
+++ b/bfd/elf32-avr.c
@@ -729,6 +729,12 @@ static bfd_vma avr_pc_wrap_around = 0x10000000;
    machine will try to optimize CALL/RET sequences by a single jump
    instruction. This can be switched off by --no-call-ret-replacement.  */
 static bool avr_replace_call_ret_sequences = true;
+
+/* If this variable holds true, the linker relaxation machine will
+   try to remove RJMP instructions that are void.  This does not
+   include plain RJMP .+0 which is used by GCC to delay 2 cycles.
+   This can be switched off by --no-elide-rjmp0.  */
+static bool avr_elide_rjmp0 = true;
 

 
 /* Per-section relaxation related information for avr.  */
@@ -2563,7 +2569,11 @@ avr_reloc_at (bfd *abfd, Elf_Internal_Shdr *symtab_hdr,
 
    The .jumptables section is meant to be used for a future tablejump variant
    for the devices with 3-byte program counter where the table itself contains
-   4-byte jump instructions whose relative offset must not be changed.  */
+   4-byte jump instructions whose relative offset must not be changed.
+
+   Finally, we elide RJMP instructions that are void and not a delay.
+   This may occur when a function is tailcalling some other function,
+   and the latter happens to be located right after the former.  */
 
 static bool
 elf32_avr_relax_section (bfd *abfd,  asection *sec,
@@ -2657,6 +2667,7 @@ elf32_avr_relax_section (bfd *abfd,  asection *sec,
   for (irel = internal_relocs; irel < irelend; irel++)
     {
       bfd_vma symval;
+      bool sym_is_global = false;
 
       if (ELF32_R_TYPE (irel->r_info) != R_AVR_13_PCREL
 	  && ELF32_R_TYPE (irel->r_info) != R_AVR_7_PCREL
@@ -2720,6 +2731,7 @@ elf32_avr_relax_section (bfd *abfd,  asection *sec,
 	  symval = (h->root.u.def.value
 		    + h->root.u.def.section->output_section->vma
 		    + h->root.u.def.section->output_offset);
+	  sym_is_global = true;
 	}
 
       /* For simplicity of coding, we are going to modify the section
@@ -2900,6 +2912,84 @@ elf32_avr_relax_section (bfd *abfd,  asection *sec,
 		*again = true;
 		break;
 	      }
+	    else if (avr_elide_rjmp0
+		     // Elide no-op RJMP tail calls like in
+		     //    RJMP func     ;; in module A
+		     //    .global func  ;; in module B
+		     //    func:
+		     && avr_is_RJMP (code_word)
+		     // Plain RJMP .+0 is used by GCC to delay 2 cycles, thus
+		     // we are only interested in global jump targets...
+		     && sym_is_global
+		     // ...without offset, and...
+		     && irel->r_addend == 0
+		     // ...where the RJMP targets the insn directly after it.
+		     && symval + irel->r_addend == dot + 2)
+	      {
+		if (debug_relax)
+		  printf ("found rjmp .+0 at address 0x%x in section %s\n",
+			  (int) dot, sec->name);
+
+		const bool has_prev = irel->r_offset >= 2;
+		const uint16_t prev_word = has_prev
+		  ? avr_word (abfd, contents + irel->r_offset - 2)
+		  : 0;
+
+		// The assumption in the following condition is that there is
+		// no dangling skip at the end of a section.  Note that a skip
+		// insn at that place doesn't make sense in a real program.
+		if (has_prev
+		    && avr_is_skip (prev_word))
+		  {
+		    if (debug_relax)
+		      printf ("skip insn prevents deletion of rjmp .+0 at "
+			      "address 0x%x\n", (int) dot);
+		    break;
+		  }
+
+		// Avoid the paranoid case where the RJMP is at the end of
+		// the program memory and jumps to 0x0.  We don't have the
+		// flash size handy, so assume a size of 0.5 KiB.
+		if ((dot + 2) % 0x200 == 0)
+		  {
+		    if (debug_relax)
+		      printf ("not deleting rjmp .+0 at address 0x%x that may "
+			      "be at the end of program memory\n", (int) dot);
+		    break;
+		  }
+
+		// Ditch the RJMP.
+		// Notice that labels or relocs at the RJMP are no issue.
+
+		if (debug_relax)
+		  printf ("deleted rjmp .+0 instruction at address 0x%x\n",
+			  (int) dot);
+
+		// Read this BFD's local symbols if we haven't done so already.
+		if (isymbuf == NULL && symtab_hdr->sh_info != 0)
+		  {
+		    isymbuf = avr_read_symbuf (abfd, symtab_hdr);
+		    if (isymbuf == NULL)
+		      break;
+		  }
+
+		elf_section_data (sec)->relocs = internal_relocs;
+		elf_section_data (sec)->this_hdr.contents = contents;
+		symtab_hdr->contents = (unsigned char *) isymbuf;
+
+		// Delete the two RJMP bytes, and...
+		if (!elf32_avr_relax_delete_bytes (abfd, sec,
+						   irel->r_offset, 2, true))
+		  goto error_return;
+
+		// ...decommission the reloc.
+		irel->r_info = R_AVR_NONE;
+
+		// That will change things, so we should relax again.
+		// Note that this is not required, and it may be slow.
+		*again = true;
+		break;
+	      }
 	    else if (avr_is_RJMP (code_word)
 		     || avr_is_JMP (code_word))
 	      {
@@ -3328,7 +3418,8 @@ void
 elf32_avr_setup_params (struct bfd_link_info *info, bfd *avr_stub_bfd,
 			asection *avr_stub_section,
 			bool no_stubs, bool deb_stubs, bool deb_relax,
-			bfd_vma pc_wrap_around, bool call_ret_replacement)
+			bfd_vma pc_wrap_around, bool call_ret_replacement,
+			bool elide_rjmp0)
 {
   elf32_avr_link_hash_table_t *htab = avr_link_hash_table (info);
 
@@ -3342,6 +3433,7 @@ elf32_avr_setup_params (struct bfd_link_info *info, bfd *avr_stub_bfd,
   debug_stubs = deb_stubs;
   avr_pc_wrap_around = pc_wrap_around;
   avr_replace_call_ret_sequences = call_ret_replacement;
+  avr_elide_rjmp0 = elide_rjmp0;
 }
 
 
diff --git a/bfd/elf32-avr.h b/bfd/elf32-avr.h
index 2b9079bc11e..1f06e608402 100644
--- a/bfd/elf32-avr.h
+++ b/bfd/elf32-avr.h
@@ -24,7 +24,7 @@
 /* These four functions will be called from the ld back end.  */
 
 extern void elf32_avr_setup_params (struct bfd_link_info *, bfd *, asection *,
-				    bool, bool, bool, bfd_vma, bool);
+				    bool, bool, bool, bfd_vma, bool, bool);
 extern int elf32_avr_setup_section_lists (bfd *, struct bfd_link_info *);
 extern bool elf32_avr_size_stubs (bfd *, struct bfd_link_info *, bool);
 extern bool elf32_avr_build_stubs (struct bfd_link_info *);
diff --git a/ld/emultempl/avrelf.em b/ld/emultempl/avrelf.em
index 71e035b74a1..cd3c66a3563 100644
--- a/ld/emultempl/avrelf.em
+++ b/ld/emultempl/avrelf.em
@@ -43,6 +43,7 @@ static bool avr_no_stubs = false;
 static bool avr_debug_relax = false;
 static bool avr_debug_stubs = false;
 static bool avr_replace_call_ret_sequences = true;
+static bool avr_elide_rjmp0 = true;
 static bfd_vma avr_pc_wrap_around = 0x10000000;
 
 /* Transfers information to the bfd frontend.  */
@@ -57,7 +58,8 @@ avr_elf_set_global_bfd_parameters (void)
 			  avr_debug_stubs,
 			  avr_debug_relax,
 			  avr_pc_wrap_around,
-			  avr_replace_call_ret_sequences);
+			  avr_replace_call_ret_sequences,
+			  avr_elide_rjmp0);
 }
 
 
@@ -235,6 +237,8 @@ EOF
 PARSE_AND_LIST_LONGOPTS='
   { "no-call-ret-replacement", no_argument,
     NULL, OPTION_NO_CALL_RET_REPLACEMENT},
+  { "no-elide-rjmp0", no_argument,
+    NULL, OPTION_NO_ELIDE_RJMP0},
   { "pmem-wrap-around", required_argument,
     NULL, OPTION_PMEM_WRAP_AROUND},
   { "no-stubs", no_argument,
@@ -260,6 +264,14 @@ PARSE_AND_LIST_OPTIONS='
 		   "  instructions by a single jump instruction.\n"
 		   "                              "
 		   "  This option disables this optimization.\n"));
+  fprintf (file, _("  --no-elide-rjmp0   "
+		   "The relaxation machine normally will\n"
+		   "                              "
+		   "  remove an rjmp instruction when it targets a\n"
+		   "                              "
+		   "  global symbol at a jump offset of 0.\n"
+		   "                              "
+		   "  This option disables this optimization.\n"));
   fprintf (file, _("  --no-stubs                  "
 		   "If the linker detects to attempt to access\n"
 		   "                              "
@@ -310,6 +322,13 @@ PARSE_AND_LIST_ARGS_CASES='
 	avr_replace_call_ret_sequences = false;
       }
       break;
+
+    case OPTION_NO_ELIDE_RJMP0:
+      {
+	/* This variable is defined in the bfd library.  */
+	avr_elide_rjmp0 = false;
+      }
+      break;
 '
 
 #
diff --git a/ld/ldlex.h b/ld/ldlex.h
index 43b0d9d41d1..b4f1bffd2a7 100644
--- a/ld/ldlex.h
+++ b/ld/ldlex.h
@@ -270,6 +270,7 @@ enum option_values
   OPTION_IN_IMPLIB,
   /* Used by emultempl/avrelf.em.  */
   OPTION_NO_CALL_RET_REPLACEMENT,
+  OPTION_NO_ELIDE_RJMP0,
   OPTION_PMEM_WRAP_AROUND,
   OPTION_NO_STUBS,
   OPTION_DEBUG_STUBS,


More information about the Binutils-cvs mailing list