+ /* Go hunt up a function and fix its line info if needed. */
+ stabp = stabcontents + irel->r_offset - 8;
+
+ /* Go pullout the stab entry. */
+ type = bfd_h_get_8 (abfd, stabp + TYPEOFF);
+ value = bfd_h_get_32 (abfd, stabp + VALOFF);
+
+ name = bfd_get_stab_name (type);
+
+ if (strcmp (name, "FUN") == 0)
+ {
+ int function_adjusted = 0;
+
+ if (symval > (baseaddr + addr))
+ /* Not in this function. */
+ continue;
+
+ /* Hey we got a function hit. */
+ stabp += STABSIZE;
+ for (;stabp < stabend; stabp += STABSIZE)
+ {
+ /* Go pullout the stab entry. */
+ type = bfd_h_get_8 (abfd, stabp + TYPEOFF);
+ value = bfd_h_get_32 (abfd, stabp + VALOFF);
+
+ name = bfd_get_stab_name (type);
+
+ if (strcmp (name, "FUN") == 0)
+ {
+ /* Hit another function entry. */
+ if (function_adjusted)
+ {
+ /* Adjust the value. */
+ value += count;
+
+ /* We need to put it back. */
+ bfd_h_put_32 (abfd, value,stabp + VALOFF);
+ }
+
+ /* And then bale out. */
+ break;
+ }
+
+ if (strcmp (name, "SLINE") == 0)
+ {
+ /* Got a line entry. */
+ if ((baseaddr + addr) <= (symval + value))
+ {
+ /* Adjust the line entry. */
+ value += count;
+
+ /* We need to put it back. */
+ bfd_h_put_32 (abfd, value,stabp + VALOFF);
+ function_adjusted = 1;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /* When adding an instruction back it is sometimes necessary to move any
+ global or local symbol that was referencing the first instruction of
+ the moved block to refer to the first instruction of the inserted block.
+
+ For example adding a PAGE instruction before a CALL or JMP requires
+ that any label on the CALL or JMP is moved to the PAGE insn. */
+ addr += noadj;
+
+ /* Adjust the local symbols defined in this section. */
+ isymend = isymbuf + symtab_hdr->sh_info;
+ for (isym = isymbuf; isym < isymend; isym++)
+ {
+ if (isym->st_shndx == shndx
+ && addr <= isym->st_value
+ && isym->st_value < endaddr)
+ isym->st_value += count;
+ }
+
+ /* Now adjust the global symbols defined in this section. */
+ symcount = (symtab_hdr->sh_size / sizeof (Elf32_External_Sym)
+ - symtab_hdr->sh_info);
+ sym_hashes = elf_sym_hashes (abfd);
+ end_hashes = sym_hashes + symcount;
+ for (; sym_hashes < end_hashes; sym_hashes++)
+ {
+ struct elf_link_hash_entry *sym_hash = *sym_hashes;
+
+ if ((sym_hash->root.type == bfd_link_hash_defined
+ || sym_hash->root.type == bfd_link_hash_defweak)
+ && sym_hash->root.u.def.section == sec)
+ {
+ if (addr <= sym_hash->root.u.def.value
+ && sym_hash->root.u.def.value < endaddr)
+ sym_hash->root.u.def.value += count;
+ }
+ }
+
+ return;
+}
+
+/* Delete some bytes from a section while relaxing. */
+
+static bfd_boolean
+ip2k_elf_relax_delete_bytes (bfd *abfd,
+ asection *sec,
+ bfd_vma addr,
+ int count)
+{
+ bfd_byte *contents = elf_section_data (sec)->this_hdr.contents;
+ bfd_vma endaddr = sec->size;
+
+ /* Actually delete the bytes. */
+ memmove (contents + addr, contents + addr + count,
+ endaddr - addr - count);
+
+ sec->size -= count;
+
+ adjust_all_relocations (abfd, sec, addr + count, endaddr, -count, 0);
+ return TRUE;
+}
+
+static bfd_boolean
+ip2k_delete_page_insn (bfd *abfd ATTRIBUTE_UNUSED,
+ asection *sec,
+ Elf_Internal_Rela *irel,
+ bfd_boolean *again,
+ struct misc *misc)
+{
+ /* Note that we've changed the relocs, section contents, etc. */
+ elf_section_data (sec)->relocs = misc->irelbase;
+ elf_section_data (sec)->this_hdr.contents = misc->contents;
+ misc->symtab_hdr->contents = (bfd_byte *) misc->isymbuf;
+
+ /* Fix the relocation's type. */
+ irel->r_info = ELF32_R_INFO (ELF32_R_SYM (irel->r_info), R_IP2K_NONE);
+
+ /* Delete the PAGE insn. */
+ if (!ip2k_elf_relax_delete_bytes (abfd, sec, irel->r_offset, 2))
+ return FALSE;
+
+ /* Modified => will need to iterate relaxation again. */
+ *again = TRUE;
+
+ return TRUE;
+}
+
+static bfd_boolean
+ip2k_relax_switch_table_128 (bfd *abfd ATTRIBUTE_UNUSED,
+ asection *sec,
+ Elf_Internal_Rela *irel,
+ bfd_boolean *again,
+ struct misc *misc)
+{
+ Elf_Internal_Rela *irelend = misc->irelbase + sec->reloc_count;
+ Elf_Internal_Rela *ireltest = irel;
+ bfd_byte code[4];
+ bfd_vma addr;
+
+ /* Test all page instructions. */
+ addr = irel->r_offset;
+ while (1)
+ {
+ if (addr + 4 > sec->size)
+ break;
+
+ ip2k_get_mem (abfd, misc->contents + addr, 4, code);
+ if ((! IS_PAGE_OPCODE (code + 0))
+ || (! IS_JMP_OPCODE (code + 2)))
+ break;
+
+ /* Validate relocation entry (every entry should have a matching
+ relocation entry). */
+ if (ireltest >= irelend)
+ {
+ _bfd_error_handler (_("ip2k relaxer: switch table without complete matching relocation information."));
+ return FALSE;
+ }
+
+ if (ireltest->r_offset != addr)
+ {
+ _bfd_error_handler (_("ip2k relaxer: switch table without complete matching relocation information."));
+ return FALSE;
+ }
+
+ if (! ip2k_test_page_insn (abfd, sec, ireltest, misc))
+ /* Un-removable page insn => nothing can be done. */
+ return TRUE;
+
+ addr += 4;
+ ireltest += 2;
+ }
+
+ /* Relaxable. Adjust table header. */
+ ip2k_get_mem (abfd, misc->contents + irel->r_offset - 4, 4, code);
+ if ((! IS_ADD_W_WREG_OPCODE (code + 0))
+ || (! IS_ADD_PCL_W_OPCODE (code + 2)))
+ {
+ _bfd_error_handler (_("ip2k relaxer: switch table header corrupt."));
+ return FALSE;
+ }
+
+ if (!ip2k_elf_relax_delete_bytes (abfd, sec, irel->r_offset - 4, 2))
+ return FALSE;
+
+ *again = TRUE;
+
+ /* Delete all page instructions in table. */
+ while (irel < ireltest)
+ {
+ if (!ip2k_delete_page_insn (abfd, sec, irel, again, misc))
+ return FALSE;
+ irel += 2;
+ }
+
+ return TRUE;
+}
+
+static bfd_boolean
+ip2k_relax_switch_table_256 (bfd *abfd ATTRIBUTE_UNUSED,
+ asection *sec,
+ Elf_Internal_Rela *irel,
+ bfd_boolean *again,
+ struct misc *misc)
+{
+ Elf_Internal_Rela *irelend = misc->irelbase + sec->reloc_count;
+ Elf_Internal_Rela *ireltest = irel;
+ bfd_byte code[12];
+ bfd_vma addr;
+
+ /* Test all page instructions. */
+ addr = irel->r_offset;
+
+ while (1)
+ {
+ if (addr + 4 > sec->size)
+ break;
+
+ ip2k_get_mem (abfd, misc->contents + addr, 4, code);
+
+ if ((! IS_PAGE_OPCODE (code + 0))
+ || (! IS_JMP_OPCODE (code + 2)))
+ break;
+
+ /* Validate relocation entry (every entry should have a matching
+ relocation entry). */
+ if (ireltest >= irelend)
+ {
+ _bfd_error_handler (_("ip2k relaxer: switch table without complete matching relocation information."));
+ return FALSE;
+ }
+
+ if (ireltest->r_offset != addr)
+ {
+ _bfd_error_handler (_("ip2k relaxer: switch table without complete matching relocation information."));
+ return FALSE;
+ }
+
+ if (!ip2k_test_page_insn (abfd, sec, ireltest, misc))
+ /* Un-removable page insn => nothing can be done. */
+ return TRUE;
+
+ addr += 4;
+ ireltest += 2;
+ }
+
+ /* Relaxable. Adjust table header. */
+ ip2k_get_mem (abfd, misc->contents + irel->r_offset - 4, 2, code);
+ if (IS_PAGE_OPCODE (code))
+ addr = irel->r_offset - 16;
+ else
+ addr = irel->r_offset - 14;
+
+ ip2k_get_mem (abfd, misc->contents + addr, 12, code);
+ if ((!IS_ADD_W_WREG_OPCODE (code + 0))
+ || (!IS_SNC_OPCODE (code + 2))
+ || (!IS_INC_1SP_OPCODE (code + 4))
+ || (!IS_ADD_2SP_W_OPCODE (code + 6))
+ || (!IS_SNC_OPCODE (code + 8))
+ || (!IS_INC_1SP_OPCODE (code + 10)))
+ {
+ _bfd_error_handler (_("ip2k relaxer: switch table header corrupt."));
+ return FALSE;
+ }
+
+ /* Delete first 3 opcodes. */
+ if (!ip2k_elf_relax_delete_bytes (abfd, sec, addr + 0, 6))
+ return FALSE;
+
+ *again = TRUE;
+
+ /* Delete all page instructions in table. */
+ while (irel < ireltest)
+ {
+ if (!ip2k_delete_page_insn (abfd, sec, irel, again, misc))
+ return FALSE;
+ irel += 2;
+ }
+
+ return TRUE;
+}
+
+/* This function handles relaxation of a section in a specific page. */
+
+static bfd_boolean
+ip2k_elf_relax_section_page (bfd *abfd,
+ asection *sec,
+ bfd_boolean *again,
+ struct misc *misc,
+ unsigned long page_start,
+ unsigned long page_end)
+{
+ Elf_Internal_Rela *irelend = misc->irelbase + sec->reloc_count;
+ Elf_Internal_Rela *irel;
+ int switch_table_128;
+ int switch_table_256;
+
+ /* Walk thru the section looking for relaxation opportunities. */
+ for (irel = misc->irelbase; irel < irelend; irel++)
+ {
+ if (ELF32_R_TYPE (irel->r_info) != (int) R_IP2K_PAGE3)
+ /* Ignore non page instructions. */
+ continue;
+
+ if (BASEADDR (sec) + irel->r_offset < page_start)
+ /* Ignore page instructions on earlier page - they have
+ already been processed. Remember that there is code flow
+ that crosses a page boundary. */
+ continue;
+
+ if (BASEADDR (sec) + irel->r_offset > page_end)
+ /* Flow beyond end of page => nothing more to do for this page. */
+ return TRUE;
+
+ /* Detect switch tables. */
+ switch_table_128 = ip2k_is_switch_table_128 (abfd, sec, irel->r_offset, misc->contents);
+ switch_table_256 = ip2k_is_switch_table_256 (abfd, sec, irel->r_offset, misc->contents);
+
+ if ((switch_table_128 > 0) || (switch_table_256 > 0))
+ /* If the index is greater than 0 then it has already been processed. */
+ continue;
+
+ if (switch_table_128 == 0)
+ {
+ if (!ip2k_relax_switch_table_128 (abfd, sec, irel, again, misc))
+ return FALSE;
+
+ continue;
+ }
+
+ if (switch_table_256 == 0)
+ {
+ if (!ip2k_relax_switch_table_256 (abfd, sec, irel, again, misc))
+ return FALSE;
+
+ continue;
+ }
+
+ /* Simple relax. */
+ if (ip2k_test_page_insn (abfd, sec, irel, misc))
+ {
+ if (!ip2k_delete_page_insn (abfd, sec, irel, again, misc))
+ return FALSE;
+
+ continue;
+ }
+ }