Hi,

This patch tries to shrink the interrupt vector table by deleting unused 
entries at the end of the table as part of linker relaxation.

The motivation for this patch is that, currently, the full interrupt
vector table is being placed in the linked executable, regardless of the
number of interrupts actually used. This increases the size of the
executable for archs (XMEGAs, for example), that have lots of interrupts
(125 interrupts * 4 bytes for XMEGAs) if the user program only uses a
few of them.

At a high level, this patch figures out the size of the vector table and
the address of the last entry in the table that has a user defined
handler, and deletes everything in between.

It does it by hooking into linker relaxation for the .vectors section and
doing the following:

1. It goes through the global symbols in the bfd and looks for
those representing interrupt handlers.

2. For each such symbol found, it extracts the vector number from the
name. It also finds out whether there is a definition available for the
symbol from the symbol information available.

3. At the end of (2), symbol indices representing handlers for the last 
available interrupt for that arch, and the last used interrupt are
available - they are the symbols with the max vector number, and the max
vector number with a definition, respectively.

4. To find out the addresses of entries in the vector table for the last 
available interrupt and the last used interrupt, it walks the relocation 
entries for the section and looks for entries with symbol indices matching
what was found in (3). The corresponding relocation offsets represent
the addresses of the entries.

5. Bytes between the offsets obtained in (4) are deleted (excluding the
defined entry's size), as are relocation entries pointing into the deleted area.

Note that this ((1) and (2) relies on avr-libc's convention of naming interrupt
handlers __vector_<n>, with n representing the interrupt number. 

Also, the patch kicks in only if --shrink-ivt is passed along with --relax,
otherwise, the __vector_<n> symbols get defined to __bad_interrupt as
usual.

Thoughts?

Regards
Senthil


diff --git bfd/elf32-avr.c bfd/elf32-avr.c
index a2d4401..e957024 100644
--- bfd/elf32-avr.c
+++ bfd/elf32-avr.c
@@ -617,6 +617,11 @@ static bfd_vma avr_pc_wrap_around = 0x10000000;
    machine will try to optimize call/ret sequences by a single jump
    instruction. This option could be switched off by a linker switch.  */
 static int avr_replace_call_ret_sequences = 1;
+
+/* This variable decides whether the linker will attempt to reduce
+   the size of the interrupt vector table. Needs to be turned on by
+   a linker switch (--shrink-ivt). */
+static bfd_boolean avr_shrink_ivt = FALSE;
 
 /* Initialize an entry in the stub hash table.  */
 
@@ -1649,6 +1654,212 @@ elf32_avr_relax_delete_bytes (bfd *abfd,
   return TRUE;
 }
 
+static const char *vector_prefix = "__vector_";
+static int extract_vector_number (const char *symbol_name)
+{
+    const char *start = symbol_name + strlen (vector_prefix);
+    int vector_number = 0;
+
+    while (*start >= '0' && *start <= '9')
+      {
+        vector_number = vector_number * 10 + (*start - '0'); 
+        start++;
+      }
+
+    return vector_number;
+}
+
+
+/* Given symbol indices for symbols representing the max available and
+   max defined vectors, this function returns addresses for the corresponding
+   entries in the vector table, using the relocation offset for those symbol
+   references */
+static void get_vector_entry_addresses (bfd *abfd,
+                                    asection *sec,
+                                    int last_vector_sym_index,
+                                    int last_defined_vector_sym_index,
+                                    bfd_vma *last_vector_entry_address,
+                                    bfd_vma *last_defined_vector_entry_address,
+                                    unsigned int *vector_table_entry_size)
+{
+    Elf_Internal_Shdr *symtab_hdr = &elf_tdata (abfd)->symtab_hdr;
+    Elf_Internal_Rela *irel = _bfd_elf_link_read_relocs
+            (abfd, sec, NULL, NULL, TRUE);
+
+    Elf_Internal_Rela *irelend = irel + sec->reloc_count;
+
+    for (; irel < irelend; irel++)
+      {
+          int indx = ELF32_R_SYM (irel->r_info) - symtab_hdr->sh_info;
+          if (indx < 0)
+              continue;
+
+          if (indx == last_defined_vector_sym_index)
+            {
+              *last_defined_vector_entry_address = irel->r_offset;
+            }
+          if (indx == last_vector_sym_index)
+            {
+              *last_vector_entry_address = irel->r_offset;
+              *vector_table_entry_size = bfd_get_reloc_size(
+                                            elf_avr_howto_table + 
+                                            ELF32_R_TYPE (irel->r_info));
+            }
+      }
+}
+
+/* Filters the relocation array to only include entries that point into the
+   section AFTER deleting the extra bytes i.e. having offsets less than the
+   section size after deletion. */
+static void remove_extra_vector_entry_relocations (bfd *abfd,
+                                    asection *sec,
+                                    unsigned int section_size_after_deletion)
+{
+    Elf_Internal_Rela *irel = _bfd_elf_link_read_relocs
+            (abfd, sec, NULL, NULL, TRUE);
+    Elf_Internal_Rela *irelend = irel + sec->reloc_count;
+
+    Elf_Internal_Rela *updated_irel = irel;
+    unsigned int valid_reloc_count = 0;
+
+    for (; irel < irelend; irel++)
+      {
+        if (irel->r_offset < section_size_after_deletion)
+          {
+            *updated_irel++ = *irel;
+            valid_reloc_count++;
+          }
+      }
+
+    sec->reloc_count = valid_reloc_count;
+}
+                                        
+
+static void delete_unused_vector_table_bytes (bfd *abfd,
+                                        asection *sec,
+                                        int last_vector_sym_index,
+                                        int last_defined_vector_sym_index)
+{
+    bfd_vma last_vector_entry_address = 0;
+    bfd_vma last_defined_vector_entry_address = 0;
+    unsigned int vector_table_entry_size = 0;
+    
+    get_vector_entry_addresses (abfd, sec,
+            last_vector_sym_index,
+            last_defined_vector_sym_index,
+            &last_vector_entry_address,
+            &last_defined_vector_entry_address,
+            &vector_table_entry_size);
+
+    if (debug_relax)
+        printf ("Last defined vector address : %0x, Last vector address : %0x",
+                (int)last_defined_vector_entry_address,
+                (int)last_vector_entry_address);
+
+    if (last_defined_vector_entry_address < last_vector_entry_address)
+      {
+        if (debug_relax)
+          printf ("Deleting from %0x to %0x\n", 
+                  (unsigned int)last_defined_vector_entry_address + 
+                    vector_table_entry_size, 
+                  (unsigned int)(last_vector_entry_address 
+                    - last_defined_vector_entry_address));
+
+        /* Initialize contents pointer and cache contents, relax_delete_bytes 
expects
+           it to be populated */
+        if (elf_section_data (sec)->this_hdr.contents == NULL)
+          {
+            bfd_byte *contents = NULL;
+            if (! bfd_malloc_and_get_section (abfd, sec, &contents))
+                return;
+          }
+
+        unsigned int bytes_to_delete = last_vector_entry_address -
+                                         last_defined_vector_entry_address;
+        
+        /* Do this before calling avr_relax_delete_bytes, as that function 
+           adjusts relocation offsets by bytes_to_delete, and having
+           relocations pointing past the section confuses it. */
+        remove_extra_vector_entry_relocations (abfd, sec, 
+                                               sec->size - bytes_to_delete);
+
+        /* The vector entry address points to the start of the entry, so add 
the size
+           of an entry and then delete from there */
+        elf32_avr_relax_delete_bytes (abfd, sec, 
+                    last_defined_vector_entry_address + 
vector_table_entry_size,
+                    bytes_to_delete); 
+      }
+}
+
+
+/* Walks through the global symbols in the BFD, finds those that
+   represent interrupt handlers (i.e. those that start with __vector_*),
+   and finds the max available and max defined ones. This assumes the
+   avr-libc convention of defining vector table entries as jumps to 
+   __vector_<n>, with __vector_<n> weakly defined by default. */
+static void find_vector_symbols (bfd *abfd,
+                                 int *last_vector_sym_index,
+                                 int *last_defined_vector_sym_index)
+{
+    Elf_Internal_Shdr *symtab_hdr = &elf_tdata (abfd)->symtab_hdr;
+    struct elf_link_hash_entry **sym_hashes = elf_sym_hashes (abfd);
+    int symcount = (symtab_hdr->sh_size / sizeof (Elf32_External_Sym)
+                    - symtab_hdr->sh_info);
+    struct elf_link_hash_entry **end_hashes = sym_hashes + symcount;
+
+    int last_vector_number = 0;
+    int last_defined_vector_number = 0;
+    int sym_index = 0;
+
+    for (; sym_hashes < end_hashes; sym_hashes++,sym_index++)
+      {
+        struct elf_link_hash_entry *sym_hash = *sym_hashes;
+        const char *symbol_name = sym_hash->root.root.string;
+
+        /* If the symbol's name begins with __vector_ */
+        if (strstr (symbol_name, vector_prefix) == symbol_name)
+          {
+            int vector_number = extract_vector_number (symbol_name);
+
+            if (vector_number < 0)
+                continue;
+
+            if (vector_number > last_vector_number)
+            {
+                last_vector_number = vector_number;
+                *last_vector_sym_index = sym_index;
+            }
+
+            if (sym_hash->root.type == bfd_link_hash_defined)
+              {
+                if (vector_number > last_defined_vector_number)
+                  {
+                    last_defined_vector_number = vector_number;
+                    *last_defined_vector_sym_index = sym_index;
+                  }
+              }
+          }
+      }
+}
+
+static void shrink_vector_table (bfd *abfd,
+                                          asection *sec)
+{
+    int last_vector_sym_index = -1;
+    int last_defined_vector_sym_index = -1;
+
+    find_vector_symbols (abfd, &last_vector_sym_index,
+                               &last_defined_vector_sym_index);
+
+    /* Could not find interrupt vector handler symbol - quit */ 
+    if (last_vector_sym_index == -1)
+        return;
+
+    delete_unused_vector_table_bytes (abfd, sec,
+                                     last_vector_sym_index,
+                                     last_defined_vector_sym_index);
+}
+
 /* This function handles relaxing for the avr.
    Many important relaxing opportunities within functions are already
    realized by the compiler itself.
@@ -1673,7 +1884,9 @@ elf32_avr_relax_delete_bytes (bfd *abfd,
    ".jumptables" in order to maintain the position of the instructions.
    There, however, we substitute jmp/call by a sequence rjmp,nop/rcall,nop
    if possible. (In future one could possibly use the space of the nop
-   for the first instruction of the irq service function.
+   for the first instruction of the irq service function). In addition, the
+   if the shrink-ivt option is passed, the ".vectors" section contents are 
+   shrunk down to the last interrupt vector that has a handler.
 
    The .jumptables sections is meant to be used for a future tablejump variant
    for the devices with 3-byte program counter where the table itself
@@ -1699,6 +1912,11 @@ elf32_avr_relax_section (bfd *abfd,
      filled with nop instructions. */
   bfd_boolean shrinkable = TRUE;
 
+  if (avr_shrink_ivt && !strcmp (sec->name,".vectors"))
+  {
+      shrink_vector_table (abfd, sec);
+  }
+
   if (!strcmp (sec->name,".vectors")
       || !strcmp (sec->name,".jumptables"))
     shrinkable = FALSE;
@@ -2598,7 +2816,8 @@ elf32_avr_setup_params (struct bfd_link_info *info,
                         bfd_boolean deb_stubs,
                         bfd_boolean deb_relax,
                         bfd_vma pc_wrap_around,
-                        bfd_boolean call_ret_replacement)
+                        bfd_boolean call_ret_replacement,
+                        bfd_boolean shrink_ivt)
 {
   struct elf32_avr_link_hash_table *htab = avr_link_hash_table (info);
 
@@ -2612,6 +2831,7 @@ elf32_avr_setup_params (struct bfd_link_info *info,
   debug_stubs = deb_stubs;
   avr_pc_wrap_around = pc_wrap_around;
   avr_replace_call_ret_sequences = call_ret_replacement;
+  avr_shrink_ivt = shrink_ivt;
 }
 
 
diff --git bfd/elf32-avr.h bfd/elf32-avr.h
index 5eeca86..60bed13 100644
--- bfd/elf32-avr.h
+++ bfd/elf32-avr.h
@@ -26,7 +26,7 @@
 extern void
 elf32_avr_setup_params (struct bfd_link_info *, bfd *, asection *,
                         bfd_boolean, bfd_boolean, bfd_boolean,
-                        bfd_vma, bfd_boolean);
+                        bfd_vma, bfd_boolean, bfd_boolean);
 
 extern int
 elf32_avr_setup_section_lists (bfd *, struct bfd_link_info *);
diff --git ld/emultempl/avrelf.em ld/emultempl/avrelf.em
index 90894a1..781b612 100644
--- ld/emultempl/avrelf.em
+++ ld/emultempl/avrelf.em
@@ -44,6 +44,7 @@ static bfd_boolean avr_debug_relax = FALSE;
 static bfd_boolean avr_debug_stubs = FALSE;
 static bfd_boolean avr_replace_call_ret_sequences = TRUE;
 static bfd_vma avr_pc_wrap_around = 0x10000000;
+static bfd_boolean avr_shrink_ivt = FALSE;
 
 /* 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_shrink_ivt);
 }
 
 
@@ -186,6 +188,7 @@ PARSE_AND_LIST_PROLOGUE='
 #define OPTION_NO_STUBS                303
 #define OPTION_DEBUG_STUBS             304
 #define OPTION_DEBUG_RELAX             305
+#define OPTION_SHRINK_VECTOR_TABLE     306
 '
 
 PARSE_AND_LIST_LONGOPTS='
@@ -199,6 +202,8 @@ PARSE_AND_LIST_LONGOPTS='
     NULL, OPTION_DEBUG_STUBS},
   { "debug-relax", no_argument,
     NULL, OPTION_DEBUG_RELAX},
+  { "shrink-ivt", no_argument,
+    NULL, OPTION_SHRINK_VECTOR_TABLE},
 '
 
 PARSE_AND_LIST_OPTIONS='
@@ -228,6 +233,8 @@ PARSE_AND_LIST_OPTIONS='
                   "Used for debugging avr-ld.\n"));
   fprintf (file, _("  --debug-relax               "
                   "Used for debugging avr-ld.\n"));
+  fprintf (file, _("  --shrink-ivt                "
+                  "Shrinks the IVT down to the last handled interrupt.\n"));
 '
 
 PARSE_AND_LIST_ARGS_CASES='
@@ -260,6 +267,10 @@ PARSE_AND_LIST_ARGS_CASES='
       avr_no_stubs = TRUE;
       break;
 
+    case OPTION_SHRINK_VECTOR_TABLE:
+      avr_shrink_ivt = TRUE;
+      break;
+
     case OPTION_NO_CALL_RET_REPLACEMENT:
       {
         /* This variable is defined in the bfd library.  */
_______________________________________________
AVR-GCC-list mailing list
AVR-GCC-list@nongnu.org
https://lists.nongnu.org/mailman/listinfo/avr-gcc-list

Reply via email to