AVR: Prune vector table with --prune-vectab PR ld/34679

Message ID 0763b1cc-1a98-43dd-bb4b-72fed08ec5b3@gjlay.de
State New
Headers
Series AVR: Prune vector table with --prune-vectab PR ld/34679 |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_binutils_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_binutils_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_binutils_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_binutils_check--master-arm success Test passed

Commit Message

Georg-Johann Lay Sept. 27, 2026, 5:34 p.m. UTC
  Usually, a vector table has many unused entries which jump
to __bad_interrupt.  This patch adds --prune-vectab as a
new ld option.  It does two things:

* Prune the vector table in .vectors.  This runs prior to
   the ordinary relaxation that turns  JMP  into  RJMP+NOP.
   This simplifies pruning.  Most of the routine is checking
   whether the vector table has the expected shape, that is:
   All of .vectors is an array of RJMPs, or an array of JMPs,
   or an array of JMPs with one final CLH.  The latter can
   occur on AVR-SD where AVR-LibC adds a CLH after the vector
   table so that all executed instruction are followed by a
   valid opcode.

* During relaxation, allow to remove the last NOP in a row
   of RJMP+NOP vectab entries.

The patch adds some new tests.  It passes the avr-gcc testsuite
with -mrelax -Wl,--prune-vectab compared against a run where
only -mrelax is on; tested with ATmega128 and ATmega8.

Ok for trunk?

Johann

--

	PR ld/34679
bfd/
	* elf32-avr.c (avr_prune_vectab): New static variable.
	(elf32_avr_setup_params): Set it.
	(avr_irq_t, avr_vectab_t): New typedefs.
	(dprint, avr_global_symbol_address, avr_find_relocs_to_vectab)
	(avr_is_CLH, avr_reloc_global_symbol_name): New static functions.
	(avr_maybe_prune_vectab): New static function.
	(elf32_avr_relax_section): Call it.  Allow to prune the last
	vectab NOP when --prune-vectab is on.
	* elf32-avr.h (elf32_avr_setup_params): Adjust prototype.
ld/
	* emultempl/avrelf.em (avr_prune_vectab): New static variable.
	(avr_elf_set_global_bfd_parameters): Pass it.
	(PARSE_AND_LIST_LONGOPTS): Set it.  Add option --prune-vectab.
	* ldlex.h (OPTION_PRUNE_VECTAB): New enum option_values.
	* testsuite/ld-avr/vectab.h: New test source.
	* testsuite/ld-avr/prune-vtab-1.s: New test source.
	* testsuite/ld-avr/prune-vtab-2.s: New test source.
	* testsuite/ld-avr/vectab-rjmp.s: New test source.
	* testsuite/ld-avr/vectab-jmp.s: New test source.
	* testsuite/ld-avr/vectab-jmp-clh.s: New test source.
	* testsuite/ld-avr/prune-vtab-1a.d: New test.
	* testsuite/ld-avr/prune-vtab-1b.d: New test.
	* testsuite/ld-avr/prune-vtab-1c.d: New test.
	* testsuite/ld-avr/prune-vtab-2a.d: New test.
	* testsuite/ld-avr/prune-vtab-2b.d: New test.
	* testsuite/ld-avr/prune-vtab-2c.d: New test.
  

Comments

Georg-Johann Lay Oct. 2, 2026, 6:31 p.m. UTC | #1
Approved here: 
https://gcc.gnu.org/pipermail/gcc-patches/2026-October/733282.html

Johann

Am 27.09.26 um 19:34 schrieb Georg-Johann Lay via Binutils:
> Usually, a vector table has many unused entries which jump
> to __bad_interrupt.  This patch adds --prune-vectab as a
> new ld option.  It does two things:
> 
> * Prune the vector table in .vectors.  This runs prior to
>    the ordinary relaxation that turns  JMP  into  RJMP+NOP.
>    This simplifies pruning.  Most of the routine is checking
>    whether the vector table has the expected shape, that is:
>    All of .vectors is an array of RJMPs, or an array of JMPs,
>    or an array of JMPs with one final CLH.  The latter can
>    occur on AVR-SD where AVR-LibC adds a CLH after the vector
>    table so that all executed instruction are followed by a
>    valid opcode.
> 
> * During relaxation, allow to remove the last NOP in a row
>    of RJMP+NOP vectab entries.
> 
> The patch adds some new tests.  It passes the avr-gcc testsuite
> with -mrelax -Wl,--prune-vectab compared against a run where
> only -mrelax is on; tested with ATmega128 and ATmega8.
> 
> Ok for trunk?
> 
> Johann
> 
> -- 
> 
>      PR ld/34679
> bfd/
>      * elf32-avr.c (avr_prune_vectab): New static variable.
>      (elf32_avr_setup_params): Set it.
>      (avr_irq_t, avr_vectab_t): New typedefs.
>      (dprint, avr_global_symbol_address, avr_find_relocs_to_vectab)
>      (avr_is_CLH, avr_reloc_global_symbol_name): New static functions.
>      (avr_maybe_prune_vectab): New static function.
>      (elf32_avr_relax_section): Call it.  Allow to prune the last
>      vectab NOP when --prune-vectab is on.
>      * elf32-avr.h (elf32_avr_setup_params): Adjust prototype.
> ld/
>      * emultempl/avrelf.em (avr_prune_vectab): New static variable.
>      (avr_elf_set_global_bfd_parameters): Pass it.
>      (PARSE_AND_LIST_LONGOPTS): Set it.  Add option --prune-vectab.
>      * ldlex.h (OPTION_PRUNE_VECTAB): New enum option_values.
>      * testsuite/ld-avr/vectab.h: New test source.
>      * testsuite/ld-avr/prune-vtab-1.s: New test source.
>      * testsuite/ld-avr/prune-vtab-2.s: New test source.
>      * testsuite/ld-avr/vectab-rjmp.s: New test source.
>      * testsuite/ld-avr/vectab-jmp.s: New test source.
>      * testsuite/ld-avr/vectab-jmp-clh.s: New test source.
>      * testsuite/ld-avr/prune-vtab-1a.d: New test.
>      * testsuite/ld-avr/prune-vtab-1b.d: New test.
>      * testsuite/ld-avr/prune-vtab-1c.d: New test.
>      * testsuite/ld-avr/prune-vtab-2a.d: New test.
>      * testsuite/ld-avr/prune-vtab-2b.d: New test.
>      * testsuite/ld-avr/prune-vtab-2c.d: New test.
  

Patch

diff --git a/bfd/elf32-avr.c b/bfd/elf32-avr.c
index 3cc79adec68..74c0295a022 100644
--- a/bfd/elf32-avr.c
+++ b/bfd/elf32-avr.c
@@ -34,6 +34,20 @@  static bool debug_relax = false;
 /* Enable debugging printout at stdout with this variable.  */
 static bool debug_stubs = false;
 
+static void dprint (const char *fmt, ...) ATTRIBUTE_PRINTF_1;
+static void dprint (const char *fmt, ...)
+{
+  va_list args;
+  va_start (args, fmt);
+  if (debug_relax)
+    {
+      vprintf (fmt, args);
+      fflush (stdout);
+    }
+  va_end (args);
+}
+
+
 static bfd_reloc_status_type
 bfd_elf_avr_diff_reloc (bfd *, arelent *, asymbol *, void *,
 			asection *, bfd *, char **);
@@ -735,6 +749,9 @@  static bool avr_replace_call_ret_sequences = true;
    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;
+
+/* Prune the vector table provided --relax.  */
+static bool avr_prune_vectab = false;
 
 
 /* Per-section relaxation related information for avr.  */
@@ -999,6 +1016,13 @@  avr_is_RJMP (uint16_t w)
   return 0xc000 == (w & 0xf000);
 }
 
+/* Return true iff W is a CLH instruction.  */
+
+static bool
+avr_is_CLH (uint16_t w)
+{
+  return 0x94d8 == w;
+}
 
 /* Return true iff W is a RET instruction.  */
 
@@ -2539,6 +2563,394 @@  avr_reloc_at (bfd *abfd, Elf_Internal_Shdr *symtab_hdr,
 }
 
 
+/* Return the target address of a global symbol NAME, or -1 if not found.  */
+
+static int
+avr_global_symbol_address (struct bfd_link_info *link_info, const char *name)
+{
+  struct bfd_link_hash_entry *h;
+  h = bfd_link_hash_lookup (link_info->hash, name,
+			    false /*create*/, false /*copy*/, true /*follow*/);
+  if (h)
+    {
+      const bfd_vma value = h->u.def.value;
+      const asection *sec = h->u.def.section;
+      return (int) (value
+		    + sec->output_offset
+		    + sec->output_section->vma);
+    }
+
+  return -1;
+}
+
+
+/* Return NULL or the name of the global symbol used by reloc REL.  */
+
+static const char *
+avr_reloc_global_symbol_name (bfd *abfd, Elf_Internal_Rela *rel)
+{
+  Elf_Internal_Shdr *symtab_hdr = &elf_symtab_hdr (abfd);
+
+  if (ELF32_R_SYM (rel->r_info) < symtab_hdr->sh_info)
+    // This is a local symbol.
+    return NULL;
+
+  struct elf_link_hash_entry **sym_hashes = elf_sym_hashes (abfd);
+  struct elf_link_hash_entry *h;
+  h = sym_hashes[ELF32_R_SYM (rel->r_info) - symtab_hdr->sh_info];
+  if (h != NULL)
+    {
+      while (h->root.type == bfd_link_hash_indirect
+	     || h->root.type == bfd_link_hash_warning)
+	h = (struct elf_link_hash_entry *) h->root.u.i.link;
+
+      return h->root.root.string;
+    }
+
+  return NULL;
+}
+
+
+// AVR-LibC produces vectabs with up to 128 entries at most.
+#define N_IRQS 128
+
+// Symbol name of an unused vectab entry.  Vectab entries that target
+// the address of this global symbol are regarded as unused.
+#define S_BAD_IRQ "__bad_interrupt"
+
+typedef struct
+{
+  int idx;
+  // Targte address of jump.
+  unsigned symval;
+  Elf_Internal_Rela *reloc;
+  // Code address of the jump / vectab entry.
+  bfd_vma dot;
+  // Whether something is targeting the vectab entry.
+  bool has_label_or_reloc;
+} avr_irq_t;
+
+typedef struct
+{
+  // Size in bytes of .vectors without an optional trailing CLH.
+  unsigned size;
+  // The number of (expected) IRQs.
+  int n_irqs;
+  // The size of one entry is 2 bytes or 4 bytes.
+  int vec_size;
+  // Code address of __bad_interrupt.
+  int bad_irq;
+  // The very table.
+  avr_irq_t irqs[N_IRQS];
+  // Whether vectab pruning has been attempted.
+  bool pruned;
+} avr_vectab_t;
+
+
+/* Traverse all input bfds and find all CALL / JMP relocs that point
+   into the vectab.  This allows code like
+
+	JMP __vectors + 0x8
+
+   to work as expected, i.e. the respective IRQ entry won't be pruned.
+   Notice that  JMP 0x8  is *NOT SUPPORTED* since it doesn't have a
+   reloc, and hence we can't find it without scanning the entire code.
+   Set  vtab->irqs[].has_label_or_reloc  when such a reloc is found.  */
+
+static bool
+avr_find_relocs_to_vectab (struct bfd_link_info *link_info, avr_vectab_t *vtab)
+{
+  for (bfd *abfd = link_info->input_bfds; abfd; abfd = abfd->link.next)
+    {
+      Elf_Internal_Shdr *symtab_hdr = &elf_symtab_hdr (abfd);
+
+      for (struct bfd_section *sec = abfd->sections; sec; sec = sec->next)
+	{
+	  if (sec->reloc_count == 0
+	      || (sec->flags & SEC_RELOC) == 0
+	      || (sec->flags & SEC_HAS_CONTENTS) == 0
+	      || (sec->flags & SEC_CODE) == 0)
+	    continue;
+
+	  Elf_Internal_Rela *rel = elf_section_data (sec)->relocs;
+	  if (rel == NULL)
+	    rel = _bfd_elf_link_read_relocs (abfd, sec, NULL, NULL, true);
+
+	  Elf_Internal_Rela *relend = rel + sec->reloc_count;
+	  for (; rel && rel < relend; ++rel)
+	    {
+	      if (ELF32_R_SYM (rel->r_info) >= symtab_hdr->sh_info
+		  && (ELF32_R_TYPE (rel->r_info) == R_AVR_CALL
+		      || ELF32_R_TYPE (rel->r_info) == R_AVR_13_PCREL))
+		{
+		  // A global symbol.
+		  const char *sname = avr_reloc_global_symbol_name (abfd, rel);
+		  if (sname && !strcmp (sname, "__vectors"))
+		    {
+		      const int idx = rel->r_addend / vtab->vec_size;
+		      if (idx >= 0 && idx < vtab->n_irqs)
+			vtab->irqs[idx].has_label_or_reloc = true;
+		    }
+		} // Jump / call related
+	    } // Relocs
+	} // Input sections
+    } // BFDs
+
+  return false;
+}
+
+
+/* Try to prune a vector table from AVR-LibC.  When entries are unused, i.e.
+   they effectively jump to __bad_interrupt, then we can remove them provided
+   all later entries can also be removed.  The trigger is --prune-vectab,
+   which only takes effect when linker relaxation is on.
+   return 1:  Vectab has just been changed.
+   return 0:  Nothing changed and no internal error occurred.
+   return -1: An error occurred.  */
+
+static int
+avr_maybe_prune_vectab (bfd *abfd, struct bfd_link_info *link_info,
+			asection *sec, Elf_Internal_Rela *internal_relocs,
+			bfd_byte **pcontents)
+{
+  static avr_vectab_t avr_vectab;
+  avr_vectab_t *vtab = &avr_vectab;
+
+  if (!avr_prune_vectab || vtab->pruned || strcmp (sec->name, ".vectors"))
+    return 0;
+
+  vtab->pruned = true;
+  vtab->size = (unsigned) sec->size;
+
+  dprint ("== Pruning .vectors of size 0x%x\n", vtab->size);
+
+  Elf_Internal_Shdr *symtab_hdr = &elf_symtab_hdr (abfd);
+
+  // Get a cached copy of the contents if it exists.
+  if (elf_section_data (sec)->this_hdr.contents != NULL)
+    *pcontents = elf_section_data (sec)->this_hdr.contents;
+  else
+    // Go get them off disk.
+    if (! bfd_malloc_and_get_section (abfd, sec, pcontents))
+      {
+	dprint ("Prune failed: error reading contents\n");
+	return -1;
+      }
+
+  // Look up __bad_interrupt in the linker's global hash table.
+  vtab->bad_irq = avr_global_symbol_address (link_info, S_BAD_IRQ);
+  if (vtab->bad_irq < 0)
+    {
+      dprint ("Prune failed: %s not found\n", S_BAD_IRQ);
+      return 0;
+    }
+  dprint ("Prune: %s = 0x%x\n", S_BAD_IRQ, vtab->bad_irq);
+
+  // The very first word must be a JMP or a RJMP, and it heralds the
+  // sizes of all the following entries.
+  const uint16_t code_word0 = avr_word (abfd, *pcontents + 0);
+  vtab->vec_size = 0?0
+    : avr_is_RJMP (code_word0) ? 2
+    : avr_is_JMP (code_word0) ? 4
+    : 0;
+
+  if (vtab->vec_size == 0)
+    {
+      dprint ("Prune failed: vectab doesn't start with JMP nor RJMP\n");
+      return 0;
+    }
+
+  const uint16_t code_last = avr_word (abfd, *pcontents + (vtab->size - 2));
+
+  if (vtab->vec_size == 4
+      && vtab->size % vtab->vec_size == 2
+      && avr_is_CLH (code_last))
+    {
+      // This is the case for AVR-SD devices that need a valid insn after each
+      // executed instruction.  AVR-LibC inserts a CLH at the vectab end.
+      dprint ("Prune: vectab has a trailing CLH\n");
+      vtab->size -= 2;
+    }
+  else if (vtab->size % vtab->vec_size != 0)
+    {
+      dprint ("Prune failed: odd vectab layout\n");
+      return 0;
+    }
+
+  vtab->n_irqs = vtab->size / vtab->vec_size;
+
+  dprint ("Prune: expecting %d %sJMP entries\n", vtab->n_irqs,
+	  vtab->vec_size == 2 ? "R" : "");
+
+  if (vtab->n_irqs > N_IRQS)
+    {
+      dprint ("Prune failed: vectab has more than %d entries\n", N_IRQS);
+      return 0;
+    }
+
+  // As soon as we know the vec_size, find any relocs that point
+  // into the vectab.  Mark .has_label_or_reloc accordingly.
+  avr_find_relocs_to_vectab (link_info, vtab);
+
+  int n_irqs = 0;
+  Elf_Internal_Rela *irelend = internal_relocs + sec->reloc_count;
+  for (Elf_Internal_Rela *irel = internal_relocs; irel < irelend; ++irel)
+    {
+      dprint ("Prune: reloc #%d off=%x, add=%x", (int) (irel - internal_relocs),
+	      (int) irel->r_offset, (int) irel->r_addend);
+
+      if (irel->r_addend)
+	{
+	  dprint ("\nPrune failed: non-zero addend\n");
+	  return 0;
+	}
+
+      // The size in bytes of the entry.
+      const int sz = 0?0
+	: ELF32_R_TYPE (irel->r_info) == R_AVR_13_PCREL ? 2
+	: ELF32_R_TYPE (irel->r_info) == R_AVR_CALL ? 4
+	: 0;
+
+      // The sizes of all relocs must be the same, otherwise it's not
+      // an AVR-LibC vector table.
+      if (sz == 0
+	  || (vtab->vec_size != 0 && sz != vtab->vec_size))
+	{
+	  dprint ("\nPrune failed: unexpected reloc\n");
+	  return 0;
+	}
+
+      vtab->vec_size = sz;
+
+      if (irel->r_offset % sz != 0
+	  || irel->r_offset + sz > vtab->size)
+	{
+	  dprint ("\nPrune failed: unexpected reloc offset\n");
+	  return 0;
+	}
+
+      dprint (" %s", sz == 2 ? "R_AVR_13_PCREL" : "R_AVR_CALL");
+
+      const int idx = irel->r_offset / sz;
+      if (idx >= N_IRQS)
+	{
+	  dprint ("\nPrune failed: vectab has more than %d entries\n", N_IRQS);
+	  return 0;
+	}
+
+      avr_irq_t *irq = &vtab->irqs[idx];
+
+      if (irq->reloc != NULL)
+	{
+	  dprint ("\nPrune failed: overlapping relocs\n");
+	  return 0;
+	}
+
+      irq->idx = idx;
+      irq->reloc = irel;
+      n_irqs += 1;
+
+      if (ELF32_R_SYM (irel->r_info) < symtab_hdr->sh_info)
+	{
+	  dprint ("\nPrune failed: symbol is not global\n");
+	  return 0;
+	}
+      else
+	{
+	  // Symbol is external.  Get its value.
+	  unsigned long i = ELF32_R_SYM (irel->r_info) - symtab_hdr->sh_info;
+	  struct elf_link_hash_entry *h = elf_sym_hashes (abfd)[i];
+	  BFD_ASSERT (h != NULL);
+	  if (h->root.type != bfd_link_hash_defined
+	      && h->root.type != bfd_link_hash_defweak)
+	    {
+	      dprint ("\nPrune failed: symbol is not defined\n");
+	      return 0;
+	    }
+
+	  irq->symval = (h->root.u.def.value
+			 + h->root.u.def.section->output_section->vma
+			 + h->root.u.def.section->output_offset);
+	}
+
+      dprint (" symval=0x%x idx=%d", irq->symval, irq->idx);
+
+      const char *s_code = NULL;
+      const uint16_t code_word = avr_word (abfd, *pcontents + irel->r_offset);
+      if (vtab->vec_size == 2 && avr_is_RJMP (code_word))
+	s_code = "rjmp";
+      else if (vtab->vec_size == 4 && avr_is_JMP (code_word))
+	s_code = "jmp";
+      else
+	{
+	  dprint ("\nPrune failed: unexpected opcode 0x%04x\n", code_word);
+	  return 0;
+	}
+
+      // Get the address of this instruction.
+      irq->dot = sec->output_section->vma + sec->output_offset + irel->r_offset;
+
+      dprint (" %s .=0x%x", s_code, (unsigned) irq->dot);
+
+      if (idx == 0
+	  || avr_local_label_at (abfd, sec, symtab_hdr, irel->r_offset)
+	  || avr_global_label_at (abfd, sec, symtab_hdr, irel->r_offset))
+	irq->has_label_or_reloc = true;
+
+      if (irq->has_label_or_reloc)
+	dprint (" label_or_reloc");
+      if (irq->symval != (unsigned) vtab->bad_irq)
+	dprint (" required");
+
+      dprint ("\n");
+    } // Relocs
+
+  // Finally, we have all the checks done and data in place, so that
+  // we can perform the actual pruning.
+
+  if (n_irqs != vtab->n_irqs)
+    {
+      dprint ("Prune failed: incomplete vectab\n");
+      return 0;
+    }
+
+  int last_idx = 0;
+  for (int i = 0; i < n_irqs; ++i)
+    if (vtab->irqs[i].has_label_or_reloc
+	|| vtab->irqs[i].symval != (unsigned) vtab->bad_irq)
+      last_idx = i;
+
+  const int n_delete = (n_irqs - 1 - last_idx) * vtab->vec_size;
+  dprint ("Prune: last remaining irq:%d, ", last_idx);
+  dprint ("pruning 0x%x=%d bytes from %d irqs\n",
+	  n_delete, n_delete, n_delete / vtab->vec_size);
+  if (n_delete == 0)
+    return 0;
+
+  dprint ("Prune: remove irq");
+
+  elf_section_data (sec)->this_hdr.contents = *pcontents;
+
+  for (int i = n_irqs - 1; i > last_idx; --i)
+    {
+      const avr_irq_t *irq = &vtab->irqs[i];
+
+      dprint (" %d", irq->idx);
+      if (!elf32_avr_relax_delete_bytes (abfd, sec, irq->reloc->r_offset,
+					 vtab->vec_size, true))
+	return -1;
+      // Avoid relocs to point outside of the section.
+      // This is needed even for R_AVR_NONE.
+      irq->reloc->r_offset = 0;
+      irq->reloc->r_info = R_AVR_NONE;
+    }
+
+  dprint ("\nPrune vectab accomplished\n");
+
+  return 1;
+}
+
+
 /* This function handles relaxing for the avr.
    Many important relaxing opportunities within functions are already
    realized by the compiler itself.  Though when the back end emits
@@ -2662,9 +3074,16 @@  elf32_avr_relax_section (bfd *abfd,  asection *sec,
   if (internal_relocs == NULL)
     goto error_return;
 
+  const int mpv = avr_maybe_prune_vectab (abfd, link_info, sec,
+					  internal_relocs, &contents);
+  if (mpv < 0)
+    goto error_return;
+  else if (mpv > 0)
+    *again = true;
+
   /* Walk through the relocs looking for relaxing opportunities.  */
   irelend = internal_relocs + sec->reloc_count;
-  for (irel = internal_relocs; irel < irelend; irel++)
+  for (irel = internal_relocs; irel < irelend && !mpv; irel++)
     {
       bfd_vma symval;
       bool sym_is_global = false;
@@ -2835,8 +3254,19 @@  elf32_avr_relax_section (bfd *abfd,  asection *sec,
 		irel->r_info = ELF32_R_INFO (ELF32_R_SYM (irel->r_info),
 					     R_AVR_13_PCREL);
 
+		// Allow to ditch the final NOP?
+		bfd_byte *pmaybe_clh = contents + irel->r_offset + 4;
+		const bool may_prune_last_vectab_entry
+		  = (avr_prune_vectab
+		     && !strcmp (sec->name, ".vectors")
+		     && (irel->r_offset + 4 == sec->size
+			 || (irel->r_offset + 6 == sec->size
+			     && avr_is_CLH (avr_word (abfd, pmaybe_clh)))));
+
 		// We should not modify the ordering if 'shrinkable' is FALSE.
-		if (!shrinkable)
+		if (!shrinkable
+		    // ...except --prune-vectab allows it for the last entry.
+		    && !may_prune_last_vectab_entry)
 		  {
 		    // Let's insert a NOP.
 		    bfd_put_8 (abfd, 0x00, contents + irel->r_offset + 2);
@@ -3419,7 +3849,7 @@  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,
-			bool elide_rjmp0)
+			bool elide_rjmp0, bool prune_vectab)
 {
   elf32_avr_link_hash_table_t *htab = avr_link_hash_table (info);
 
@@ -3434,6 +3864,7 @@  elf32_avr_setup_params (struct bfd_link_info *info, bfd *avr_stub_bfd,
   avr_pc_wrap_around = pc_wrap_around;
   avr_replace_call_ret_sequences = call_ret_replacement;
   avr_elide_rjmp0 = elide_rjmp0;
+  avr_prune_vectab = prune_vectab;
 }
 
 
diff --git a/bfd/elf32-avr.h b/bfd/elf32-avr.h
index 1f06e608402..028f9e1926f 100644
--- a/bfd/elf32-avr.h
+++ b/bfd/elf32-avr.h
@@ -24,7 +24,8 @@ 
 /* 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, bool, bfd_vma, bool, 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 cd3c66a3563..4a510b15d53 100644
--- a/ld/emultempl/avrelf.em
+++ b/ld/emultempl/avrelf.em
@@ -44,6 +44,7 @@  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 bool avr_prune_vectab = false;
 static bfd_vma avr_pc_wrap_around = 0x10000000;
 
 /* Transfers information to the bfd frontend.  */
@@ -59,7 +60,8 @@  avr_elf_set_global_bfd_parameters (void)
 			  avr_debug_relax,
 			  avr_pc_wrap_around,
 			  avr_replace_call_ret_sequences,
-			  avr_elide_rjmp0);
+			  avr_elide_rjmp0,
+			  avr_prune_vectab);
 }
 
 
@@ -241,6 +243,8 @@  PARSE_AND_LIST_LONGOPTS='
     NULL, OPTION_NO_ELIDE_RJMP0},
   { "pmem-wrap-around", required_argument,
     NULL, OPTION_PMEM_WRAP_AROUND},
+  { "prune-vectab", no_argument,
+    NULL, OPTION_PRUNE_VECTAB },
   { "no-stubs", no_argument,
     NULL, OPTION_NO_STUBS},
   { "debug-stubs", no_argument,
@@ -264,7 +268,7 @@  PARSE_AND_LIST_OPTIONS='
 		   "  instructions by a single jump instruction.\n"
 		   "                              "
 		   "  This option disables this optimization.\n"));
-  fprintf (file, _("  --no-elide-rjmp0   "
+  fprintf (file, _("  --no-elide-rjmp0            "
 		   "The relaxation machine normally will\n"
 		   "                              "
 		   "  remove an rjmp instruction when it targets a\n"
@@ -272,6 +276,10 @@  PARSE_AND_LIST_OPTIONS='
 		   "  global symbol at a jump offset of 0.\n"
 		   "                              "
 		   "  This option disables this optimization.\n"));
+  fprintf (file, _("  --prune-vectab              "
+		   "Prune AVR-LibC vector table provided relaxation\n"
+		   "                              "
+		   "  is on.\n"));
   fprintf (file, _("  --no-stubs                  "
 		   "If the linker detects to attempt to access\n"
 		   "                              "
@@ -329,6 +337,11 @@  PARSE_AND_LIST_ARGS_CASES='
 	avr_elide_rjmp0 = false;
       }
       break;
+
+    case OPTION_PRUNE_VECTAB:
+      /* This variable is defined in the bfd library.  */
+      avr_prune_vectab = true;
+      break;
 '
 
 #
diff --git a/ld/ldlex.h b/ld/ldlex.h
index b4f1bffd2a7..1f6e6e5d387 100644
--- a/ld/ldlex.h
+++ b/ld/ldlex.h
@@ -272,6 +272,7 @@  enum option_values
   OPTION_NO_CALL_RET_REPLACEMENT,
   OPTION_NO_ELIDE_RJMP0,
   OPTION_PMEM_WRAP_AROUND,
+  OPTION_PRUNE_VECTAB,
   OPTION_NO_STUBS,
   OPTION_DEBUG_STUBS,
   OPTION_DEBUG_RELAX,
diff --git a/ld/testsuite/ld-avr/prune-vtab-1.s b/ld/testsuite/ld-avr/prune-vtab-1.s
new file mode 100644
index 00000000000..7164a2179c5
--- /dev/null
+++ b/ld/testsuite/ld-avr/prune-vtab-1.s
@@ -0,0 +1,2 @@ 
+;;; empty
+
diff --git a/ld/testsuite/ld-avr/prune-vtab-1a.d b/ld/testsuite/ld-avr/prune-vtab-1a.d
new file mode 100644
index 00000000000..1d889e979e4
--- /dev/null
+++ b/ld/testsuite/ld-avr/prune-vtab-1a.d
@@ -0,0 +1,19 @@ 
+#name: Prune RJMP vectab, no code
+#source: vectab-rjmp.s
+#source: prune-vtab-1.s
+#target: avr-*-*
+#as: -mavr4 -I$srcdir/$subdir
+#ld: -mavr4 --relax --prune-vectab
+#objdump: -d
+
+.*:     file format elf32-avr
+
+
+Disassembly of section .text:
+
+00000000 <__vectors>:
+   0:	f8 94       	cli
+
+00000002 <__bad_interrupt>:
+   2:	fe cf       	rjmp	.-4      	; 0x0 <__vectors>
+
diff --git a/ld/testsuite/ld-avr/prune-vtab-1b.d b/ld/testsuite/ld-avr/prune-vtab-1b.d
new file mode 100644
index 00000000000..e37fbec8c34
--- /dev/null
+++ b/ld/testsuite/ld-avr/prune-vtab-1b.d
@@ -0,0 +1,18 @@ 
+#name: Prune JMP vectab, no code
+#source: vectab-jmp.s
+#source: prune-vtab-1.s
+#target: avr-*-*
+#as: -mavr51 -I$srcdir/$subdir
+#ld: -mavr51 --relax --prune-vectab
+#objdump: -d
+
+.*:     file format elf32-avr
+
+
+Disassembly of section .text:
+
+00000000 <__vectors>:
+   0:	f8 94       	cli
+
+00000002 <__bad_interrupt>:
+   2:	fe cf       	rjmp	.-4      	; 0x0 <__vectors>
diff --git a/ld/testsuite/ld-avr/prune-vtab-1c.d b/ld/testsuite/ld-avr/prune-vtab-1c.d
new file mode 100644
index 00000000000..f5c3b5a196f
--- /dev/null
+++ b/ld/testsuite/ld-avr/prune-vtab-1c.d
@@ -0,0 +1,22 @@ 
+#name: Prune JMP+CLH vectab, no code
+#source: vectab-jmp-clh.s
+#source: prune-vtab-1.s
+#target: avr-*-*
+#as: -mavr51 -I$srcdir/$subdir
+#ld: -mavr51 --relax --prune-vectab
+#objdump: -d
+
+.*:     file format elf32-avr
+
+
+Disassembly of section .text:
+
+00000000 <__vectors>:
+   0:	01 c0       	rjmp	.+2      	; 0x4 <__ctors_end>
+   2:	d8 94       	clh
+
+00000004 <__ctors_end>:
+   4:	f8 94       	cli
+
+00000006 <__bad_interrupt>:
+   6:	fc cf       	rjmp	.-8      	; 0x0 <__vectors>
diff --git a/ld/testsuite/ld-avr/prune-vtab-2.s b/ld/testsuite/ld-avr/prune-vtab-2.s
new file mode 100644
index 00000000000..ca7d897bd03
--- /dev/null
+++ b/ld/testsuite/ld-avr/prune-vtab-2.s
@@ -0,0 +1,4 @@ 
+.global __vector_2
+.type __vector_2, @function
+__vector_2:
+    reti
diff --git a/ld/testsuite/ld-avr/prune-vtab-2a.d b/ld/testsuite/ld-avr/prune-vtab-2a.d
new file mode 100644
index 00000000000..540cdc49c88
--- /dev/null
+++ b/ld/testsuite/ld-avr/prune-vtab-2a.d
@@ -0,0 +1,26 @@ 
+#name: Prune RJMP vectab, one ISR
+#source: vectab-rjmp.s
+#source: prune-vtab-2.s
+#target: avr-*-*
+#as: -mavr4 -I$srcdir/$subdir
+#ld: -mavr4 --relax --prune-vectab
+#objdump: -d
+
+.*:     file format elf32-avr
+
+
+Disassembly of section .text:
+
+00000000 <__vectors>:
+   0:	02 c0       	rjmp	.+4      	; 0x6 <__ctors_end>
+   2:	02 c0       	rjmp	.+4      	; 0x8 <__bad_interrupt>
+   4:	02 c0       	rjmp	.+4      	; 0xa <__vector_2>
+
+00000006 <__ctors_end>:
+   6:	f8 94       	cli
+
+00000008 <__bad_interrupt>:
+   8:	fb cf       	rjmp	.-10     	; 0x0 <__vectors>
+
+0000000a <__vector_2>:
+   a:	18 95       	reti
diff --git a/ld/testsuite/ld-avr/prune-vtab-2b.d b/ld/testsuite/ld-avr/prune-vtab-2b.d
new file mode 100644
index 00000000000..c7c9359d040
--- /dev/null
+++ b/ld/testsuite/ld-avr/prune-vtab-2b.d
@@ -0,0 +1,28 @@ 
+#name: Prune JMP vectab, one ISR
+#source: vectab-jmp.s
+#source: prune-vtab-2.s
+#target: avr-*-*
+#as: -mavr51 -I$srcdir/$subdir
+#ld: -mavr51 --relax --prune-vectab
+#objdump: -d
+
+.*:     file format elf32-avr
+
+
+Disassembly of section .text:
+
+00000000 <__vectors>:
+   0:	04 c0       	rjmp	.+8      	; 0xa <__ctors_end>
+   2:	00 00       	nop
+   4:	03 c0       	rjmp	.+6      	; 0xc <__bad_interrupt>
+   6:	00 00       	nop
+   8:	02 c0       	rjmp	.+4      	; 0xe <__vector_2>
+
+0000000a <__ctors_end>:
+   a:	f8 94       	cli
+
+0000000c <__bad_interrupt>:
+   c:	f9 cf       	rjmp	.-14     	; 0x0 <__vectors>
+
+0000000e <__vector_2>:
+   e:	18 95       	reti
diff --git a/ld/testsuite/ld-avr/prune-vtab-2c.d b/ld/testsuite/ld-avr/prune-vtab-2c.d
new file mode 100644
index 00000000000..46cb1b76179
--- /dev/null
+++ b/ld/testsuite/ld-avr/prune-vtab-2c.d
@@ -0,0 +1,29 @@ 
+#name: Prune JMP+CLH vectab, one ISR
+#source: vectab-jmp-clh.s
+#source: prune-vtab-2.s
+#target: avr-*-*
+#as: -mavr51 -I$srcdir/$subdir
+#ld: -mavr51 --relax --prune-vectab
+#objdump: -d
+
+.*:     file format elf32-avr
+
+
+Disassembly of section .text:
+
+00000000 <__vectors>:
+   0:	05 c0       	rjmp	.+10     	; 0xc <__ctors_end>
+   2:	00 00       	nop
+   4:	04 c0       	rjmp	.+8      	; 0xe <__bad_interrupt>
+   6:	00 00       	nop
+   8:	03 c0       	rjmp	.+6      	; 0x10 <__vector_2>
+   a:	d8 94       	clh
+
+0000000c <__ctors_end>:
+   c:	f8 94       	cli
+
+0000000e <__bad_interrupt>:
+   e:	f8 cf       	rjmp	.-16     	; 0x0 <__vectors>
+
+00000010 <__vector_2>:
+  10:	18 95       	reti
diff --git a/ld/testsuite/ld-avr/vectab-jmp-clh.s b/ld/testsuite/ld-avr/vectab-jmp-clh.s
new file mode 100644
index 00000000000..62e4e1b2a5d
--- /dev/null
+++ b/ld/testsuite/ld-avr/vectab-jmp-clh.s
@@ -0,0 +1,9 @@ 
+.macro  XJMP x
+    jmp \x
+.endm
+
+.macro XCLH
+    clh
+.endm
+
+.include "vectab.h"
diff --git a/ld/testsuite/ld-avr/vectab-jmp.s b/ld/testsuite/ld-avr/vectab-jmp.s
new file mode 100644
index 00000000000..2d3e1ea9d64
--- /dev/null
+++ b/ld/testsuite/ld-avr/vectab-jmp.s
@@ -0,0 +1,9 @@ 
+.macro  XJMP x
+    jmp \x
+.endm
+
+.macro XCLH
+    ;; clh
+.endm
+
+.include "vectab.h"
diff --git a/ld/testsuite/ld-avr/vectab-rjmp.s b/ld/testsuite/ld-avr/vectab-rjmp.s
new file mode 100644
index 00000000000..546d8289e9e
--- /dev/null
+++ b/ld/testsuite/ld-avr/vectab-rjmp.s
@@ -0,0 +1,9 @@ 
+.macro  XJMP x
+    rjmp \x
+.endm
+
+.macro XCLH
+    ;; clh
+.endm
+
+.include "vectab.h"
diff --git a/ld/testsuite/ld-avr/vectab.h b/ld/testsuite/ld-avr/vectab.h
new file mode 100644
index 00000000000..d6dc79b64ba
--- /dev/null
+++ b/ld/testsuite/ld-avr/vectab.h
@@ -0,0 +1,32 @@ 
+.macro  vector name
+    .weak   \name
+    .set    \name, __bad_interrupt
+    XJMP    \name
+.endm
+
+    .section .vectors,"ax",@progbits
+    .global __vectors
+    .type __vectors,@function
+__vectors:
+    XJMP    __init
+    vector  __vector_1
+    vector  __vector_2
+	vector	__vector_3
+	vector	__vector_4
+	vector	__vector_5
+	vector	__vector_6
+    XCLH
+
+    .text
+    .global __bad_interrupt
+    .func   __bad_interrupt
+__bad_interrupt:
+    .weak   __vector_default
+    .set    __vector_default, __vectors
+    XJMP    __vector_default
+    .endfunc
+
+    .section .init0,"ax",@progbits
+    .weak   __init
+__init:
+    cli