This is the mail archive of the binutils@sourceware.org mailing list for the binutils project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[RFC] Add IFUNC support for MIPS


Hi,

This is the third attempt at MIPS IFUNC patch for binutils. If you want to
run objects generated by this patch you will need the companion glibc patch.

This patch targets mips32, mips64, micromips & mips16. I believe I have
addressed all the issues of the previous patch reviews, posted by Jack Carter.
Ref:
https://www.sourceware.org/ml/binutils/2013-08/msg00121.html
https://www.sourceware.org/ml/binutils/2013-11/msg00155.html

This iteration is significantly different from the previous ones. Detailed
specifications are in the attached text file. Major points to note:

- IRELATIVE fix-ups are generated for all IFUNC symbols.
- IGOT entry created only where necessary.
- IPLT stubs generated for all global IFUNCs, but used only where necessary.

Tests have been split out in to a separate patch and attached for review. 
These are carried forward from the previous iteration. I am working on translating 
more of my informal tests to the correct format.

Features explored in informal run-time testing include:
- mips32, mips64(n32/n64), mips16, micromips32
- PIC/PIE/no-PIC
- Local IFUNC declarations, non-default visibility (hidden/internal)
- Multi-got
- Pre-emption, lazy binding

Regards,
Faraz Shahbazker


bfd/ChangeLog:

	* bfd-in2.h (BFD_RELOC_MIPS_IRELATIVE): New relocation.
	* elf32-mips.c
	(elf_mips_eh_howto table): Howto for BFD_RELOC_MIPS_IRELATIVE.
	(bfd_elf32_bfd_reloc_type_lookup): Case for R_MIPS_IRELATIVE
	(bfd_elf32_bfd_reloc_name_lookup): Case for R_MIPS_IRELATIVE
	(mips_elf32_rtype_to_howto): Case for R_MIPS_IRELATIVE.
	* elfxx-mips.c
	(struct mips_elf_link_hash_entry): Offset indexes into iplt and igot.
	(mips_elf_link_hash_entry): Flag to indicate sym needs iplt or igot
	(mips_elf_link_hash_table): New fields for size, index and count of
	iplt stubs, hash-table for local IFUNC symbols.
	(MIPS16_P): New macro to check ASE flag.
	(mips16_exec_iplt_entry): Template for non-shared mips16 iplt stub.
	(mips16_dso_iplt_entry): Template for shared mips16 iplt stub.
	(mips32_exec_iplt_entry): Template for non-shared mips32 iplt stub.
	(mips32_dso_iplt_entry): Template for shared mips32 iplt stub.
	(micromips32_exec_iplt_entry): Template for non-shared micromips32 stub.
	(micromips32_dso_iplt_entry): Template for shared micromips32 stub.
	(mips64_exec_iplt_entry): Template for non-shared mips64 iplt stub.
	(mips64_dso_iplt_entry): Template for shared mips64 iplt stub.
	(mips_elf_link_hash_newfunc): Initialization of new
	mips_elf_link_hash_entry elements.
	(mips_elf_create_stub_symbol): Set ISA bit in address and ST_MIPS16
	type for mips16 iplt stubs.
	(mips_elf_rel_dyn_section): Moved up to avoid forward declaration.
	(mips_use_local_got_p): Likewise.
	(mips_get_irel_section): New function.
	(mips_elf_allocate_iplt): Likewise.
	(mips_elf_allocate_dynamic_relocations): Moved up to avoid forward
	declaration.
	(mips_elf_check_local_symbols): New function.
	(mips_elf_check_symbols): Allocate an iplt entry or dynamic
	relocation for an IFUNC symbols.
	(sort_dynamic_relocs): Change sort-order so that IRELATIVE relocs
	are applied after all others.
	(sort_dynamic_relocs_64): Likewise.
	(mips_elf_got16_entry): Add argument h and pass it to
	mips_elf_create_local_got_entry instead of NULL.
	(mips_elf_set_iplt_offsets): New function.
	(mips_elf_sort_hash_table_ifunc_f): Likewise.
	(mips_elf_sort_hash_table_noifunc_f): Likewise.
	(mips_elf_sort_hash_table): Sort all non-IFUNC symbols before IFUNCs
	and track first indices for IFUNCs.
	(mips_elf_count_local_got_symbols): New function.
	(mips_elf_create_ifunc_sections): Likewise.
	(get_local_sym_hash): Likewise.
        (mips_elf_calculate_relocation): Create hash-table for local IFUNC
	symbols and condition them to be accessed through GOT. Point IFUNC
	symbol value to iplt stub for non-shared objects.
        (_bfd_mips_elf_section_processing): Size .igot section.
	(_bfd_mips_elf_add_symbol_hook): Mark output as has_gnu_symbols.
	(_bfd_mips_elf_check_relocs): Check if we need to create iplt sections.
	If symbol is an IFUNC, don't convert it to an STT_FUNC. Allow local
	IFUNCs to be accessed via call16 reloc.
	(_bfd_mips_elf_adjust_dynamic_symbol): Add STT_GNU_IFUNC to sanity
	check.
	(_bfd_mips_elf_always_size_sections): Allocate relocs for local IFUNCs.
	(mips_elf_lay_out_got): Count GOT entries for local IFUNC symbols.
	(_bfd_mips_elf_size_dynamic_sections): Exclude iplt and igotplt.
	(_bfd_mips_elf_size_dynamic_section): New dynamic tags MIPS_IPLT,
	MIPS_IFUNC_INDX, MIPS_IPLTREL, MIPS_IFUNCREL_INDX.
	(mips_elf_create_iplt): New function.
	(mips_elf_multi_got_index): Likewise.
	(mips_elf_create_ireloc): Likewise.
	(_bfd_mips_elf_finish_dynamic_symbol): Create IRELATIVE reloc and
	(if needed) iplt stub for IFUNC symbols. Set IFUNC symbol value to
	the iplt entry address for non-shared objects.
	(_bfd_mips_elf_finish_local_dynamic_symbol): New function.
	(_bfd_mips_elf_finish_dynamic_sections): Call
	_bfd_mips_elf_finish_local dynamic_symbol for all local IFUNCs.
	Set values of dynamic tags - MIPS_IPLT, MIPS_IFUNC_INDX,
	MIPS_IPLTREL and MIPS_IFUNCREL_INDX.
	(local_htab_hash): New function, hash table for local IFUNCs.
	(loc_htab_eq): New comparison function for local IFUNC hash table.
	(_bfd_mips_elf_link_hash_table_free): New function.
	(_bfd_mips_elf_link_hash_table_create): Allocate a hash table for
	local IFUNCs.
	( _bfd_mips_elf_get_target_dtag): Add cases for dynamic tags -
	MIPS_IPLT, MIPS_IFUNC_INDX, MIPS_IPLTREL and MIPS_IFUNCREL_INDX.
	* libbfd.h
	(bfd_reloc_code_real_names): Entry for BFD_RELOC_MIPS_IRELATIVE.
	* reloc.c
	(ENUMDOC): BFD_RELOC_MIPS_IRELATIVE entry.

binutils/ChangeLog:

	* readelf.c: Add cases for dynamic tags - MIPS_IPLT,
	MIPS_IFUNC_INDX, MIPS_IPLTREL and MIPS_IFUNCREL_INDX.

elfcpp/ChangeLog:

	* elfcpp.h
	(enum DT): Add cases for dynamic tags - MIPS_IPLT, MIPS_IFUNC_INDX,
	MIPS_IPLTREL and MIPS_IFUNCREL_INDX.

include/elf/ChangeLog:

	* mips.h
	(START_RELOC_NUMBERS): Entry for R_MIPS_IRELATIVE.
	(DT_MIPS_IPLT): New dynamic tag.
	(DT_MIPS_IFUNC_INDX): New dynamic tag.
	(DT_MIPS_IPLTREL): New dynamic tag.
	(DT_MIPS_IFUNCREL_INDX): New dynamic tag.


---
 bfd/bfd-in2.h      |    3 +
 bfd/elf32-mips.c   |   22 +
 bfd/elfxx-mips.c   | 1183 +++++++++++++++++++++++++++++++++++++++++++++++-----
 bfd/libbfd.h       |    1 +
 bfd/reloc.c        |    5 +
 binutils/readelf.c |    5 +
 elfcpp/elfcpp.h    |    8 +
 include/elf/mips.h |   16 +
 8 files changed, 1139 insertions(+), 104 deletions(-)

diff --git a/bfd/bfd-in2.h b/bfd/bfd-in2.h
index 2d32c74..c25b900 100644
--- a/bfd/bfd-in2.h
+++ b/bfd/bfd-in2.h
@@ -3037,6 +3037,9 @@ to compensate for the borrow when the low bits are added.  */
   BFD_RELOC_MIPS_JUMP_SLOT,
 
 
+/* MIPS support for STT_GNU_IFUNC.  */
+  BFD_RELOC_MIPS_IRELATIVE,
+
 /* Moxie ELF relocations.  */
   BFD_RELOC_MOXIE_10_PCREL,
 
diff --git a/bfd/elf32-mips.c b/bfd/elf32-mips.c
index fff08e5..7f9c9b4 100644
--- a/bfd/elf32-mips.c
+++ b/bfd/elf32-mips.c
@@ -1646,6 +1646,22 @@ static reloc_howto_type elf_mips_eh_howto =
 	 0xffffffff,	        /* dst_mask */
 	 FALSE);		/* pcrel_offset */
 
+/* STT_GNU_IFUNC support: */
+static reloc_howto_type elf_mips_irelative_howto =
+  HOWTO (R_MIPS_IRELATIVE,	/* type */
+	 0,			/* rightshift */
+	 2,			/* size (0 = byte, 1 = short, 2 = long) */
+	 32,			/* bitsize */
+	 FALSE,			/* pc_relative */
+	 0,			/* bitpos */
+	 complain_overflow_bitfield,/* complain_on_overflow */
+	 bfd_elf_generic_reloc, /* special_function */
+	 "R_MIPS_IRELATIVE",	/* name */
+	 TRUE,			/* partial_inplace */
+	 0xffffffff,		/* src_mask */
+	 0xffffffff,		/* dst_mask */
+	 FALSE);		/* pcrel_offset */
+
 /* Set the GP value for OUTPUT_BFD.  Returns FALSE if this is a
    dangerous relocation.  */
 
@@ -2126,6 +2142,8 @@ bfd_elf32_bfd_reloc_type_lookup (bfd *abfd, bfd_reloc_code_real_type code)
       return &elf_mips_jump_slot_howto;
     case BFD_RELOC_MIPS_EH:
       return &elf_mips_eh_howto;
+    case BFD_RELOC_MIPS_IRELATIVE:
+      return &elf_mips_irelative_howto;
     }
 }
 
@@ -2173,6 +2191,8 @@ bfd_elf32_bfd_reloc_name_lookup (bfd *abfd ATTRIBUTE_UNUSED,
     return &elf_mips_jump_slot_howto;
   if (strcasecmp (elf_mips_eh_howto.name, r_name) == 0)
     return &elf_mips_eh_howto;
+  if (strcasecmp (elf_mips_irelative_howto.name, r_name) == 0)
+    return &elf_mips_irelative_howto;
 
   return NULL;
 }
@@ -2199,6 +2219,8 @@ mips_elf32_rtype_to_howto (unsigned int r_type,
       return &elf_mips_jump_slot_howto;
     case R_MIPS_EH:
       return &elf_mips_eh_howto;
+    case R_MIPS_IRELATIVE:
+      return &elf_mips_irelative_howto;
     default:
       if (r_type >= R_MICROMIPS_min && r_type < R_MICROMIPS_max)
 	return &elf_micromips_howto_table_rel[r_type - R_MICROMIPS_min];
diff --git a/bfd/elfxx-mips.c b/bfd/elfxx-mips.c
index a5e6453..65ae685 100644
--- a/bfd/elfxx-mips.c
+++ b/bfd/elfxx-mips.c
@@ -45,6 +45,7 @@
 #include "coff/mips.h"
 
 #include "hashtab.h"
+#include "objalloc.h"
 
 /* Types of TLS GOT entry.  */
 enum mips_got_tls_type {
@@ -375,6 +376,12 @@ struct mips_elf_link_hash_entry
      being called returns a floating point value.  */
   asection *call_fp_stub;
 
+  /* Offset into the IPLT table.  */
+  bfd_vma iplt_offset;
+
+  /* Offset into the IGOT table.  */
+  bfd_vma igot_offset;
+
   /* The highest GGA_* value that satisfies all references to this symbol.  */
   unsigned int global_got_area : 2;
 
@@ -413,6 +420,12 @@ struct mips_elf_link_hash_entry
 
   /* Does this symbol resolve to a PLT entry?  */
   unsigned int use_plt_entry : 1;
+
+  /* Does this symbol need an IPLT stub?  */
+  unsigned int needs_iplt : 1;
+
+  /* Does this ifunc symbol need an IGOT entry?  */
+  unsigned int needs_igot : 1;
 };
 
 /* MIPS ELF linker hash table.  */
@@ -485,6 +498,18 @@ struct mips_elf_link_hash_table
   /* The index of the next .got.plt entry to create.  */
   bfd_vma plt_got_index;
 
+  /* The size of an IPLT entry in bytes.  */
+  bfd_vma iplt_entry_size;
+
+  /* Index of first IFUNC in dynsym table.  */
+  int ifunc_dynindx;
+
+  /* Index of first reloc-only IFUNC in dynsym table.  */
+  int ifunc_rel_dynindx;
+
+  /* Number of GOT-referenced IFUNC symbols.  */
+  int ifunc_got_count;
+
   /* The number of functions that need a lazy-binding stub.  */
   bfd_vma lazy_stub_count;
 
@@ -516,6 +541,10 @@ struct mips_elf_link_hash_table
 
   /* Is the PLT header compressed?  */
   unsigned int plt_header_is_comp : 1;
+
+  /* Used by local STT_GNU_IFUNC symbols.  */
+  htab_t loc_hash_table;
+  void *loc_hash_memory;
 };
 
 /* Get the MIPS ELF linker hash table from a link_info structure.  */
@@ -800,6 +829,10 @@ static bfd *reldyn_sorting_bfd;
 #define MICROMIPS_P(abfd) \
   ((elf_elfheader (abfd)->e_flags & EF_MIPS_ARCH_ASE_MICROMIPS) != 0)
 
+/* Nonzero if ABFD has mips16 code.  */
+#define MIPS16_P(abfd) \
+  ((elf_elfheader (abfd)->e_flags & EF_MIPS_ARCH_ASE_M16) != 0)
+
 /* Nonzero if ABFD is MIPS R6.  */
 #define MIPSR6_P(abfd) \
   ((elf_elfheader (abfd)->e_flags & EF_MIPS_ARCH) == E_MIPS_ARCH_32R6 \
@@ -1191,6 +1224,97 @@ static const bfd_vma mips_vxworks_shared_plt_entry[] =
   0x10000000,	/* b .PLT_resolver	*/
   0x24180000	/* li t8, <pltindex>	*/
 };
+
+/* The format of MIPS16 o32 IPLT entries.  We use v0 ($2)
+   and v1 ($3) as temporaries because t8 ($24) and t9 ($25) are not
+   directly addressable.  */
+static const bfd_vma mips16_exec_iplt_entry[] =
+{
+  0xb202,		/* lw 	 $2, 8($pc)       	*/
+  0x9a60,		/* lw 	 $3, 0($2)		*/
+  0xeb00,		/* jr 	 $3			*/
+  0x653b,		/* move  $25, $3		*/
+  0x0000, 0x0000	/* .word (.igot address)	*/
+};
+
+/* The format of MIPS16 o32 IPLT entries for PIC.  */
+static const bfd_vma mips16_so_iplt_entry[] =
+{
+  0x0b00,		/* addiu $3, $pc, 0		*/
+  0x9b43,		/* lw    $2, 12($3)		*/
+  0xe269,		/* addu  $2, $2, $3		*/
+  0x9a60,		/* lw    $3, 0($2)		*/
+  0xeb00,		/* jr    $3			*/
+  0x653b,		/* move  $25, $3		*/
+  0x0000, 0x0000	/* .word (.igot offset)	*/
+};
+
+/* The format of 32 bit IPLT entries.  */
+static const bfd_vma mips32_exec_iplt_entry[] =
+{
+  0x3c0f0000,   /* lui $15, %hi(.igot address)		*/
+  0x8df90000,   /* lw  $25, %lo(.igot address)($15)	*/
+  0x03200008,   /* jr  $25				*/
+  0x00000000    /* nop					*/
+};
+
+/* The format of MIPS32 IPLT entries for PIC.  */
+static const bfd_vma mips32_so_iplt_entry[] =
+{
+  0x3c170000,	/* lui  $23, %hi(.igot offset)		*/
+  0x02f9b821,	/* addu $23, $23, $25			*/
+  0x8ef90000,	/* lw   $25, %lo(.igot offset)($23)	*/
+  0x03200008,	/* jr   $25				*/
+  0x00000000,	/* nop					*/
+};
+
+/* The format of 32-bit micromips IPLT entries.  */
+static const bfd_vma micromips32_exec_iplt_entry[] =
+{
+  0x41a30000, 	/* lui $2, %hi(.igot address)		*/
+  0xff230000,	/* ld  $2, %lo(.igot address)($2) 	*/
+  0x4599,	/* jr  $25				*/
+  0x0c00,	/* nop					*/
+};
+
+/* The format of 32-bit micromips IPLT entries for PIC.  */
+static const bfd_vma micromips32_so_iplt_entry[] =
+{
+  0x79000000,	/* addiu   $2, pc, %lo(.igot offset)	*/
+  0x41a30000,	/* lui     $3, %hi(.igot offset)	*/
+  0x0534,	/* addu    $2, $2, $3			*/
+  0x69a0,	/* lw      $3, 0($2)			*/
+  0x4583,	/* jr      $3				*/
+  0x0f23	/* move    $25, $3			*/
+};
+
+/* The format of 64-bit IPLT entries.  */
+static const bfd_vma mips64_exec_iplt_entry[] =
+{
+  0x3c0f0000,   /* lui    $15, %highest(.igot address)		*/
+  0x65ef0000,   /* daddiu $15, $15, %higher(.igot address) 	*/
+  0x000f7c38,   /* dsll   $15,$15, 16				*/
+  0x65ef0000,   /* daddiu $15, $15, %hi(.igot address)		*/
+  0x000f7c38,   /* dsll   $15,$15, 16				*/
+  0xddf90000,   /* ld     $25, %lo(.igot address)($15)		*/
+  0x03200008,   /* jr     $25					*/
+  0x00000000	/* nop						*/
+};
+
+/* The format of 64-bit IPLT entries for PIC.  */
+static const bfd_vma mips64_so_iplt_entry[] =
+{
+  0x3c0f0000,   /* lui     $15, %highest(.igot offset)		*/
+  0x65ef0000,   /* daddiu  $15, $15, %higher(.igot offset)	*/
+  0x000f7c38,   /* dsll    $15, $15, 0x10			*/
+  0x65ef0000,   /* daddiu  $15, $15, %hi(.igot offset)		*/
+  0x000f7c38,   /* dsll    $15, $15, 0x10			*/
+  0x01f9782d,   /* daddu   $15, $15, $25			*/
+  0xddf90000,   /* ld      $25, %lo(.igot offset) ($15)		*/
+  0x03200008,   /* jr      $25					*/
+  0x00000000,   /* nop						*/
+};
+
 
 /* microMIPS 32-bit opcode helper installer.  */
 
@@ -1289,6 +1413,10 @@ mips_elf_link_hash_newfunc (struct bfd_hash_entry *entry,
       ret->has_nonpic_branches = FALSE;
       ret->needs_lazy_stub = FALSE;
       ret->use_plt_entry = FALSE;
+      ret->needs_iplt = FALSE;
+      ret->needs_igot = FALSE;
+      ret->iplt_offset = -1;
+      ret->igot_offset = 0;
     }
 
   return (struct bfd_hash_entry *) ret;
@@ -1588,8 +1716,9 @@ mips_elf_create_stub_symbol (struct bfd_link_info *info,
   struct elf_link_hash_entry *elfh;
   const char *name;
 
-  if (ELF_ST_IS_MICROMIPS (h->root.other))
-    value |= 1;
+  if (ELF_ST_IS_MICROMIPS (h->root.other)
+      || (ELF_ST_IS_MIPS16 (h->root.other) && h->root.type == STT_GNU_IFUNC))
+      value |= 1;
 
   /* Create a new symbol.  */
   name = ACONCAT ((prefix, h->root.root.root.string, NULL));
@@ -1604,6 +1733,10 @@ mips_elf_create_stub_symbol (struct bfd_link_info *info,
   elfh->type = ELF_ST_INFO (STB_LOCAL, STT_FUNC);
   elfh->size = size;
   elfh->forced_local = 1;
+
+  if (ELF_ST_IS_MIPS16 (h->root.other) && h->needs_iplt)
+    elfh->other = STO_MIPS16;
+
   return TRUE;
 }
 
@@ -1966,6 +2099,188 @@ mips_elf_add_la25_stub (struct bfd_link_info *info,
 	  : mips_elf_add_la25_intro (stub, info));
 }
 
+/* Return the dynamic relocation section.  If it doesn't exist, try to
+   create a new it if CREATE_P, otherwise return NULL.  Also return NULL
+   if creation fails.  */
+
+static asection *
+mips_elf_rel_dyn_section (struct bfd_link_info *info, bfd_boolean create_p)
+{
+  const char *dname;
+  asection *sreloc;
+  bfd *dynobj;
+
+  dname = MIPS_ELF_REL_DYN_NAME (info);
+  dynobj = elf_hash_table (info)->dynobj;
+  sreloc = bfd_get_linker_section (dynobj, dname);
+  if (sreloc == NULL && create_p)
+    {
+      sreloc = bfd_make_section_anyway_with_flags (dynobj, dname,
+						   (SEC_ALLOC
+						    | SEC_LOAD
+						    | SEC_HAS_CONTENTS
+						    | SEC_IN_MEMORY
+						    | SEC_LINKER_CREATED
+						    | SEC_READONLY));
+      if (sreloc == NULL
+	  || !bfd_set_section_alignment (dynobj, sreloc,
+					 MIPS_ELF_LOG_FILE_ALIGN (dynobj)))
+	return NULL;
+    }
+  return sreloc;
+}
+
+/* Return section for R_MIPS_IRELATIVE relocations.  If the link is
+   dynamic, the relocations should go in .dynrel, otherwise they should
+   go in the special .rel.iplt section.  */
+
+static asection *
+mips_get_irel_section (struct bfd_link_info *info,
+		       struct mips_elf_link_hash_table *htab)
+{
+  asection *srel = (elf_hash_table (info)->dynamic_sections_created)
+		    ? mips_elf_rel_dyn_section (info, FALSE)
+		    : htab->root.irelplt;
+  BFD_ASSERT (srel != NULL);
+  return srel;
+}
+
+/* Return true if a GOT entry for H should live in the local rather than
+   global GOT area.  */
+
+static bfd_boolean
+mips_use_local_got_p (struct bfd_link_info *info,
+		      struct mips_elf_link_hash_entry *h)
+{
+  /* Symbols that aren't in the dynamic symbol table must live in the
+     local GOT.  This includes symbols that are completely undefined
+     and which therefore don't bind locally.  We'll report undefined
+     symbols later if appropriate.  */
+  if (h->root.dynindx == -1)
+    return TRUE;
+
+  /* Symbols that bind locally can (and in the case of forced-local
+     symbols, must) live in the local GOT.  */
+  if (h->got_only_for_calls
+      ? SYMBOL_CALLS_LOCAL (info, &h->root)
+      : SYMBOL_REFERENCES_LOCAL (info, &h->root))
+    return TRUE;
+
+  /* If this is an executable that must provide a definition of the symbol,
+     either though PLTs or copy relocations, then that address should go in
+     the local rather than global GOT.  */
+  if (info->executable && h->has_static_relocs)
+    return TRUE;
+
+  return FALSE;
+}
+
+/* Reserve space in the iplt and igot tables for another ifunc entry.
+   Don't allocate igot entry if this is a shared object.  */
+
+static bfd_boolean
+mips_elf_allocate_iplt (bfd *abfd, struct mips_elf_link_hash_table *mhtab,
+			struct bfd_link_info *info,
+			struct mips_elf_link_hash_entry *mh)
+{
+  asection *s;
+  asection *srel;
+  bfd *dynobj;
+
+  BFD_ASSERT (!mh->needs_iplt);
+  BFD_ASSERT (mhtab->root.iplt != NULL);
+
+  s = mhtab->root.iplt;
+
+  /* Stub offset for shared object is not yet known. This will be
+     determined later by dynsym indices.  */
+  if (!info->shared)
+    mh->iplt_offset = s->size;
+
+  mips_elf_create_stub_symbol (info, mh, ".iplt.", mhtab->root.iplt,
+			       s->size, mhtab->iplt_entry_size);
+
+  s->size += mhtab->iplt_entry_size;
+
+  /* Create an IGOT entry, only if symbol doesn't already have a GOT entry.  */
+  if (!info->shared ||
+      (mh->global_got_area == GGA_NONE && !mips_use_local_got_p (info, mh)))
+    {
+      BFD_ASSERT (mhtab->root.igotplt != NULL);
+      mh->igot_offset = mhtab->root.igotplt->size;
+      mhtab->root.igotplt->size += MIPS_ELF_GOT_SIZE (abfd);
+      mh->needs_igot = TRUE;
+    }
+
+  srel = mips_get_irel_section (info, mhtab);
+  dynobj = elf_hash_table (info)->dynobj;
+
+  if (mhtab->root.splt)
+    srel->size += MIPS_ELF_RELA_SIZE (dynobj);
+  else
+    srel->size += MIPS_ELF_REL_SIZE (dynobj);
+
+  /* This should be the only place needs_iplt is set.  */
+  mh->needs_iplt = TRUE;
+  return TRUE;
+}
+
+/* Add room for N relocations to the .rel(a).dyn section in ABFD.  */
+
+static void
+mips_elf_allocate_dynamic_relocations (bfd *abfd, struct bfd_link_info *info,
+				       unsigned int n)
+{
+  asection *s;
+  struct mips_elf_link_hash_table *htab;
+
+  htab = mips_elf_hash_table (info);
+  BFD_ASSERT (htab != NULL);
+
+  s = mips_elf_rel_dyn_section (info, FALSE);
+  BFD_ASSERT (s != NULL);
+
+  if (htab->is_vxworks)
+    s->size += n * MIPS_ELF_RELA_SIZE (abfd);
+  else
+    {
+      if (s->size == 0)
+	{
+	  /* Make room for a null element.  */
+	  s->size += MIPS_ELF_REL_SIZE (abfd);
+	  ++s->reloc_count;
+	}
+      s->size += n * MIPS_ELF_REL_SIZE (abfd);
+    }
+}
+
+/* hash_traverse callback that is called before sizing sections.
+   DATA points to a mips_htab_traverse_info structure.  */
+static bfd_boolean
+mips_elf_check_local_symbols (void **slot, void *data)
+{
+  struct mips_htab_traverse_info *hti =
+    (struct mips_htab_traverse_info *) data;
+  struct mips_elf_link_hash_entry *h =
+    (struct mips_elf_link_hash_entry *) *slot;
+
+  /* If the referenced symbol is ifunc, allocate an iplt for it.  */
+  if (h && !h->needs_iplt && h->root.type == STT_GNU_IFUNC)
+    {
+      struct bfd_link_info *info = hti->info;
+      /* .iplt entry is needed for executable objects.
+	 IREL fixup is sufficient for shared objects.  */
+      if (info->shared)
+	mips_elf_allocate_dynamic_relocations (info->output_bfd, info, 1);
+      else
+	if (!mips_elf_allocate_iplt (info->output_bfd,
+				     mips_elf_hash_table (info), info, h))
+	  return FALSE;
+    }
+
+  return TRUE;
+}
+
 /* A mips_elf_link_hash_traverse callback that is called before sizing
    sections.  DATA points to a mips_htab_traverse_info structure.  */
 
@@ -1978,6 +2293,24 @@ mips_elf_check_symbols (struct mips_elf_link_hash_entry *h, void *data)
   if (!hti->info->relocatable)
     mips_elf_check_mips16_stubs (hti->info, h);
 
+  if (h && !h->needs_iplt &&
+      h->root.type == STT_GNU_IFUNC)
+    {
+      struct bfd_link_info *info = hti->info;
+
+      /* If visibility is restricted(local), we only need reloc entry for IREL
+	 fixup, else we need IPLT stub for cross-object symbol resolution.  */
+      if (ELF_ST_VISIBILITY (h->root.other) == STV_HIDDEN
+	  || ELF_ST_VISIBILITY (h->root.other) == STV_INTERNAL)
+	mips_elf_allocate_dynamic_relocations (info->output_bfd, info, 1);
+      else
+	{
+	  if (!mips_elf_allocate_iplt (info->output_bfd,
+				       mips_elf_hash_table (info), info, h))
+	    return FALSE;
+	}
+    }
+
   if (mips_elf_local_pic_function_p (h))
     {
       /* PR 12845: If H is in a section that has been garbage
@@ -2756,6 +3089,14 @@ sort_dynamic_relocs (const void *arg1, const void *arg2)
   bfd_elf32_swap_reloc_in (reldyn_sorting_bfd, arg1, &int_reloc1);
   bfd_elf32_swap_reloc_in (reldyn_sorting_bfd, arg2, &int_reloc2);
 
+  /* Ensure that IRELATIVE relocs are applied after all others.  */
+  if (ELF32_R_TYPE (int_reloc1.r_info) == R_MIPS_IRELATIVE
+      && ELF32_R_TYPE (int_reloc2.r_info) != R_MIPS_IRELATIVE)
+    return 1;
+  else if (ELF32_R_TYPE (int_reloc2.r_info) == R_MIPS_IRELATIVE
+	   && ELF32_R_TYPE (int_reloc1.r_info) != R_MIPS_IRELATIVE)
+    return -1;
+
   diff = ELF32_R_SYM (int_reloc1.r_info) - ELF32_R_SYM (int_reloc2.r_info);
   if (diff != 0)
     return diff;
@@ -2782,6 +3123,14 @@ sort_dynamic_relocs_64 (const void *arg1 ATTRIBUTE_UNUSED,
   (*get_elf_backend_data (reldyn_sorting_bfd)->s->swap_reloc_in)
     (reldyn_sorting_bfd, arg2, int_reloc2);
 
+  /* Ensure that IRELATIVE relocs are applied after all others.  */
+  if (ELF64_R_TYPE (int_reloc1[0].r_info) == R_MIPS_IRELATIVE
+      && ELF64_R_TYPE (int_reloc2[0].r_info) != R_MIPS_IRELATIVE)
+    return 1;
+  else if (ELF64_R_TYPE (int_reloc2[0].r_info) == R_MIPS_IRELATIVE
+	   && ELF64_R_TYPE (int_reloc1[0].r_info) != R_MIPS_IRELATIVE)
+    return -1;
+
   if (ELF64_R_SYM (int_reloc1[0].r_info) < ELF64_R_SYM (int_reloc2[0].r_info))
     return -1;
   if (ELF64_R_SYM (int_reloc1[0].r_info) > ELF64_R_SYM (int_reloc2[0].r_info))
@@ -3145,37 +3494,6 @@ mips_elf_replace_bfd_got (bfd *abfd, struct mips_got_info *g)
   tdata->got = g;
 }
 
-/* Return the dynamic relocation section.  If it doesn't exist, try to
-   create a new it if CREATE_P, otherwise return NULL.  Also return NULL
-   if creation fails.  */
-
-static asection *
-mips_elf_rel_dyn_section (struct bfd_link_info *info, bfd_boolean create_p)
-{
-  const char *dname;
-  asection *sreloc;
-  bfd *dynobj;
-
-  dname = MIPS_ELF_REL_DYN_NAME (info);
-  dynobj = elf_hash_table (info)->dynobj;
-  sreloc = bfd_get_linker_section (dynobj, dname);
-  if (sreloc == NULL && create_p)
-    {
-      sreloc = bfd_make_section_anyway_with_flags (dynobj, dname,
-						   (SEC_ALLOC
-						    | SEC_LOAD
-						    | SEC_HAS_CONTENTS
-						    | SEC_IN_MEMORY
-						    | SEC_LINKER_CREATED
-						    | SEC_READONLY));
-      if (sreloc == NULL
-	  || ! bfd_set_section_alignment (dynobj, sreloc,
-					  MIPS_ELF_LOG_FILE_ALIGN (dynobj)))
-	return NULL;
-    }
-  return sreloc;
-}
-
 /* Return the GOT_TLS_* type required by relocation type R_TYPE.  */
 
 static int
@@ -3600,7 +3918,8 @@ mips_elf_got_page (bfd *abfd, bfd *ibfd, struct bfd_link_info *info,
 
 static bfd_vma
 mips_elf_got16_entry (bfd *abfd, bfd *ibfd, struct bfd_link_info *info,
-		      bfd_vma value, bfd_boolean external)
+		      bfd_vma value, bfd_boolean external,
+		      struct mips_elf_link_hash_entry *h)
 {
   struct mips_got_entry *entry;
 
@@ -3615,7 +3934,7 @@ mips_elf_got16_entry (bfd *abfd, bfd *ibfd, struct bfd_link_info *info,
      R_MIPS16_GOT16, R_MIPS_CALL16, etc.  The format of the entry is the
      same in all cases.  */
   entry = mips_elf_create_local_got_entry (abfd, info, ibfd, value, 0,
-					   NULL, R_MIPS_GOT16);
+					   h, R_MIPS_GOT16);
   if (entry)
     return entry->gotidx;
   else
@@ -3790,6 +4109,60 @@ count_section_dynsyms (bfd *output_bfd, struct bfd_link_info *info)
   return count;
 }
 
+/* Set iplt_offset and stub value according to sort order of IFUNC symbols.  */
+
+static bfd_boolean
+mips_elf_set_iplt_offsets (struct mips_elf_link_hash_entry *h, void *data)
+{
+  struct bfd_link_info *info = data;
+  struct mips_elf_link_hash_table *htab = mips_elf_hash_table (info);
+
+  /* .iplt stubs are assigned in ascending order of dynamic indices:
+     all GP-relative referenced IFUNC symbols, followed by by all reloc-only
+     IFUNC symbols.  */
+  if (h->root.type == STT_GNU_IFUNC
+      && info->shared && h->root.dynindx >= htab->ifunc_dynindx)
+    {
+      char *stubname =  ACONCAT ((".iplt.", h->root.root.root.string, NULL));
+      struct bfd_link_hash_entry *bh;
+
+      if (htab->ifunc_rel_dynindx
+	  && h->root.dynindx >= htab->ifunc_rel_dynindx)
+	h->iplt_offset = (htab->ifunc_got_count + h->root.dynindx
+			  - htab->ifunc_rel_dynindx) * htab->iplt_entry_size;
+      else
+	h->iplt_offset = (h->root.dynindx - htab->ifunc_dynindx)
+	  * htab->iplt_entry_size;
+
+      /* Find the stub symbol and update to its value.  */
+      bh = bfd_link_hash_lookup (info->hash, stubname, FALSE, FALSE, TRUE);
+      if (bh != NULL)
+	bh->u.def.value = h->iplt_offset;
+    }
+  return TRUE;
+}
+
+/* Sort hash table for IFUNC symbols only.  */
+static bfd_boolean
+mips_elf_sort_hash_table_ifunc_f (struct mips_elf_link_hash_entry *h, void *data)
+{
+  if (h->root.type == STT_GNU_IFUNC)
+    return mips_elf_sort_hash_table_f (h, data);
+  else
+    return TRUE;
+}
+
+/* Sort hash table for non-IFUNC symbols only.  */
+
+static bfd_boolean
+mips_elf_sort_hash_table_noifunc_f (struct mips_elf_link_hash_entry *h, void *data)
+{
+  if (h->root.type != STT_GNU_IFUNC)
+    return mips_elf_sort_hash_table_f (h, data);
+  else
+    return TRUE;
+}
+
 /* Sort the dynamic symbol table so that symbols that need GOT entries
    appear towards the end.  */
 
@@ -3815,10 +4188,23 @@ mips_elf_sort_hash_table (bfd *abfd, struct bfd_link_info *info)
     = hsd.min_got_dynindx
     = (elf_hash_table (info)->dynsymcount - g->reloc_only_gotno);
   hsd.max_non_got_dynindx = count_section_dynsyms (abfd, info) + 1;
-  mips_elf_link_hash_traverse (((struct mips_elf_link_hash_table *)
-				elf_hash_table (info)),
-			       mips_elf_sort_hash_table_f,
-			       &hsd);
+
+  /* Sort only regular (non-ifunc) symbols in first pass.  */
+  mips_elf_link_hash_traverse (htab, mips_elf_sort_hash_table_noifunc_f, &hsd);
+  /* Record first/lowest IFUNC indices of normal and reloc-only types.  */
+  htab->ifunc_dynindx = hsd.max_non_got_dynindx;
+  htab->ifunc_rel_dynindx = hsd.max_unref_got_dynindx;
+  /* Record number of referenced normal IFUNC symbols.  */
+  htab->ifunc_got_count = hsd.min_got_dynindx - hsd.max_non_got_dynindx;
+  /* Sort all ifunc symbols in 2nd pass so they get contiguous indices.  */
+  mips_elf_link_hash_traverse (htab, mips_elf_sort_hash_table_ifunc_f, &hsd);
+
+  /* No reloc-only IFUNC symbols found.  */
+  if (htab->ifunc_rel_dynindx == hsd.max_unref_got_dynindx)
+    htab->ifunc_rel_dynindx = 0;
+
+  /* Set iplt_offset for each IFUNC stub according to sort order.  */
+  mips_elf_link_hash_traverse (htab, mips_elf_set_iplt_offsets, info);
 
   /* There should have been enough room in the symbol table to
      accommodate both the GOT and non-GOT symbols.  */
@@ -4050,34 +4436,6 @@ mips_elf_record_got_page_ref (struct bfd_link_info *info, bfd *abfd,
   return TRUE;
 }
 
-/* Add room for N relocations to the .rel(a).dyn section in ABFD.  */
-
-static void
-mips_elf_allocate_dynamic_relocations (bfd *abfd, struct bfd_link_info *info,
-				       unsigned int n)
-{
-  asection *s;
-  struct mips_elf_link_hash_table *htab;
-
-  htab = mips_elf_hash_table (info);
-  BFD_ASSERT (htab != NULL);
-
-  s = mips_elf_rel_dyn_section (info, FALSE);
-  BFD_ASSERT (s != NULL);
-
-  if (htab->is_vxworks)
-    s->size += n * MIPS_ELF_RELA_SIZE (abfd);
-  else
-    {
-      if (s->size == 0)
-	{
-	  /* Make room for a null element.  */
-	  s->size += MIPS_ELF_REL_SIZE (abfd);
-	  ++s->reloc_count;
-	}
-      s->size += n * MIPS_ELF_REL_SIZE (abfd);
-    }
-}
 
 /* A htab_traverse callback for GOT entries, with DATA pointing to a
    mips_elf_traverse_got_arg structure.  Count the number of GOT
@@ -4385,39 +4743,9 @@ mips_elf_resolve_final_got_entries (struct bfd_link_info *info,
 
   tga.info = info;
   tga.g = g;
-  htab_traverse (g->got_page_refs, mips_elf_resolve_got_page_ref, &tga);
-
-  return TRUE;
-}
-
-/* Return true if a GOT entry for H should live in the local rather than
-   global GOT area.  */
-
-static bfd_boolean
-mips_use_local_got_p (struct bfd_link_info *info,
-		      struct mips_elf_link_hash_entry *h)
-{
-  /* Symbols that aren't in the dynamic symbol table must live in the
-     local GOT.  This includes symbols that are completely undefined
-     and which therefore don't bind locally.  We'll report undefined
-     symbols later if appropriate.  */
-  if (h->root.dynindx == -1)
-    return TRUE;
-
-  /* Symbols that bind locally can (and in the case of forced-local
-     symbols, must) live in the local GOT.  */
-  if (h->got_only_for_calls
-      ? SYMBOL_CALLS_LOCAL (info, &h->root)
-      : SYMBOL_REFERENCES_LOCAL (info, &h->root))
-    return TRUE;
-
-  /* If this is an executable that must provide a definition of the symbol,
-     either though PLTs or copy relocations, then that address should go in
-     the local rather than global GOT.  */
-  if (info->executable && h->has_static_relocs)
-    return TRUE;
+  htab_traverse (g->got_page_refs, mips_elf_resolve_got_page_ref, &tga);
 
-  return FALSE;
+  return TRUE;
 }
 
 /* A mips_elf_link_hash_traverse callback for which DATA points to the
@@ -4460,6 +4788,29 @@ mips_elf_count_got_symbols (struct mips_elf_link_hash_entry *h, void *data)
     }
   return 1;
 }
+
+/* A elf_link_hash_traverse callback for which INF points to the
+   link_info structure. Count the number of local IFUNC symbols
+   by iterating over the local hash table.  */
+
+static int
+mips_elf_count_local_got_symbols (void **slot, void *inf)
+{
+  struct mips_elf_link_hash_entry *h = (struct mips_elf_link_hash_entry *) *slot;
+  struct bfd_link_info *info;
+  struct mips_elf_link_hash_table *htab;
+  struct mips_got_info *g;
+
+  BFD_ASSERT (h != NULL && h->root.type == STT_GNU_IFUNC &&
+	      h->root.forced_local);
+
+  info = (struct bfd_link_info *) inf;
+  htab = mips_elf_hash_table (info);
+
+  g = htab->got_info;
+  g->local_gotno++;
+  return 1;
+}
 
 /* A htab_traverse callback for GOT entries.  Add each one to the GOT
    given in mips_elf_traverse_got_arg DATA.  Clear DATA->G on error.  */
@@ -5087,6 +5438,78 @@ mips_elf_create_compact_rel_section
   return TRUE;
 }
 
+/* Create the .iplt, .rel(a).iplt and .igot sections.  */
+
+static bfd_boolean
+mips_elf_create_ifunc_sections (struct bfd_link_info *info)
+{
+  struct mips_elf_link_hash_table * volatile htab;
+  const struct elf_backend_data *bed;
+  bfd *dynobj;
+  asection *s;
+  flagword flags;
+
+  htab = mips_elf_hash_table (info);
+  dynobj = htab->root.dynobj;
+  bed = get_elf_backend_data (dynobj);
+  flags = bed->dynamic_sec_flags;
+
+  s = bfd_make_section_anyway_with_flags (dynobj, ".iplt",
+					  flags | SEC_READONLY | SEC_CODE);
+  if (s == NULL || !bfd_set_section_alignment (dynobj, s, bed->plt_alignment))
+    return FALSE;
+
+  htab->root.iplt = s;
+
+  if (info->shared)
+    {
+      if (ABI_64_P (dynobj))
+	htab->iplt_entry_size = 4 * ARRAY_SIZE (mips64_so_iplt_entry);
+      else if (MIPS16_P (dynobj))
+	htab->iplt_entry_size = 2 * ARRAY_SIZE (mips16_so_iplt_entry);
+      else if (MICROMIPS_P (dynobj))
+	  /* Multiply by compression ratio for micromips.  */
+	htab->iplt_entry_size = 4 * (ARRAY_SIZE (micromips32_so_iplt_entry)
+					 * 2 / 3);
+      else
+	htab->iplt_entry_size = 4 * (ARRAY_SIZE (mips32_so_iplt_entry)
+				     + (LOAD_INTERLOCKS_P (dynobj)? 0 : 1));
+    }
+  else
+    {
+      if (ABI_64_P (dynobj))
+	htab->iplt_entry_size = 4 * ARRAY_SIZE (mips64_exec_iplt_entry);
+      else if (MIPS16_P (dynobj))
+	    htab->iplt_entry_size = 2 * ARRAY_SIZE (mips16_exec_iplt_entry);
+      else if (MICROMIPS_P (dynobj))
+	  /* Multiply by compression ratio for micromips.  */
+	    htab->iplt_entry_size = 4
+	      * (ARRAY_SIZE (micromips32_exec_iplt_entry) * 3 / 4);
+      else
+	htab->iplt_entry_size = 4 * (ARRAY_SIZE (mips32_exec_iplt_entry)
+				     + (LOAD_INTERLOCKS_P (dynobj)? 0 : 1));
+    }
+
+  BFD_ASSERT (htab->root.igotplt == NULL);
+
+  s = bfd_make_section_anyway_with_flags (dynobj, ".igot", flags);
+  if (s == NULL
+      || !bfd_set_section_alignment (dynobj, s, bed->s->log_file_align))
+    return FALSE;
+  htab->root.igotplt = s;
+  mips_elf_section_data (s)->elf.this_hdr.sh_flags |= SHF_ALLOC | SHF_WRITE;
+
+  BFD_ASSERT (htab->root.irelplt == NULL);
+
+  s = bfd_make_section_with_flags (dynobj, ".rel.iplt", flags | SEC_READONLY);
+  if (s == NULL
+      || !bfd_set_section_alignment (dynobj, s, bed->s->log_file_align))
+    return FALSE;
+  htab->root.irelplt = s;
+
+  return TRUE;
+}
+
 /* Create the .got section to hold the global offset table.  */
 
 static bfd_boolean
@@ -5203,6 +5626,74 @@ mips_elf_relocation_needs_la25_stub (bfd *input_bfd, int r_type,
       return FALSE;
     }
 }
+
+/* Find and/or create a hash entry for local symbol.  */
+
+static struct mips_elf_link_hash_entry *
+get_local_sym_hash (struct mips_elf_link_hash_table *htab,
+		    bfd *abfd, const Elf_Internal_Rela *rel)
+{
+  struct mips_elf_link_hash_entry e, *ret;
+  asection *sec;
+  hashval_t h;
+  void **slot;
+  Elf_Internal_Sym *isym;
+  Elf_Internal_Shdr *symtab_hdr;
+  char *namep;
+
+  sec = bfd_get_section_by_name (abfd, ".text");
+  h = ELF_LOCAL_SYMBOL_HASH (sec->id, ELF_R_SYM (abfd, rel->r_info));
+  isym = bfd_sym_from_r_symndx (&htab->sym_cache, abfd,
+				ELF_R_SYM (abfd, rel->r_info));
+  symtab_hdr =  &elf_tdata (abfd)->symtab_hdr;
+  namep = bfd_elf_string_from_elf_section (abfd, symtab_hdr->sh_link,
+					   isym->st_name);
+
+  if (ELF_ST_TYPE (isym->st_info) == STT_GNU_IFUNC)
+    elf_tdata (abfd)->has_gnu_symbols = TRUE;
+
+  e.root.indx = sec->id;
+  e.root.dynstr_index = ELF_R_SYM (abfd, rel->r_info);
+  e.root.root.root.string = namep;
+
+  slot = htab_find_slot_with_hash (htab->loc_hash_table, &e, h, INSERT);
+  if (!slot)
+    return NULL;
+
+  /* Found match */
+  if (*slot)
+    {
+      ret = (struct mips_elf_link_hash_entry *) *slot;
+      return ret;
+    }
+
+  /* Allocate new slot */
+  ret = (struct mips_elf_link_hash_entry *)
+    objalloc_alloc ((struct objalloc *) htab->loc_hash_memory,
+		    sizeof (struct mips_elf_link_hash_entry));
+
+  if (ret)
+    {
+      memset (ret, 0, sizeof (*ret));
+      ret->root.indx = sec->id;
+      ret->root.dynstr_index = ELF_R_SYM (abfd, rel->r_info);
+      ret->root.dynindx = -1;
+      ret->root.root.root.string = namep;
+      ret->root.root.u.def.section = sec;
+      ret->root.root.u.def.value = isym->st_value;
+      ret->root.got.offset = (bfd_vma) -1;
+      ret->global_got_area = GGA_NONE;
+      ret->root.type = STT_GNU_IFUNC;
+      ret->root.def_regular = 1;
+      ret->root.ref_regular = 1;
+      ret->root.forced_local = 1;
+      ret->root.root.type = bfd_link_hash_defined;
+
+      *slot = ret;
+    }
+
+  return ret;
+}
 
 /* Calculate the value produced by the RELOCATION (which comes from
    the INPUT_BFD).  The ADDEND is the addend to use for this
@@ -5253,6 +5744,8 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
   /* TRUE if the symbol referred to by this relocation is a local
      symbol.  */
   bfd_boolean local_p, was_local_p;
+  /* TRUE if the symbol referred to by this relocation is a local IFUNC */
+  bfd_boolean local_gnu_ifunc_p = FALSE;
   /* TRUE if the symbol referred to by this relocation is "_gp_disp".  */
   bfd_boolean gp_disp_p = FALSE;
   /* TRUE if the symbol referred to by this relocation is
@@ -5333,6 +5826,12 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
 
       target_is_16_bit_code_p = ELF_ST_IS_MIPS16 (sym->st_other);
       target_is_micromips_code_p = ELF_ST_IS_MICROMIPS (sym->st_other);
+
+      if (sym->st_info == STT_GNU_IFUNC)
+	{
+	  h = get_local_sym_hash (mips_elf_hash_table (info), input_bfd, relocation);
+	  local_gnu_ifunc_p = TRUE;
+	}
     }
   else
     {
@@ -5557,6 +6056,17 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
       target_is_16_bit_code_p = !micromips_p;
       target_is_micromips_code_p = micromips_p;
     }
+  /* If this symbol is an ifunc, point to the iplt stub for it.  */
+  else if (h && h->needs_iplt && !info->shared)
+    {
+      BFD_ASSERT (htab->root.iplt != NULL);
+      symbol = (htab->root.iplt->output_section->vma
+		+ htab->root.iplt->output_offset
+		+ h->iplt_offset);
+      /* Set ISA bit in address for compressed code.  */
+      if (ELF_ST_IS_COMPRESSED (h->root.other))
+	symbol |= 1;
+    }
 
   /* Make sure MIPS16 and microMIPS are not used together.  */
   if ((r_type == R_MIPS16_26 && target_is_micromips_code_p)
@@ -5941,10 +6451,13 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
     case R_MICROMIPS_CALL16:
       /* VxWorks does not have separate local and global semantics for
 	 R_MIPS*_GOT16; every relocation evaluates to "G".  */
+      /* Local IFUNC symbols must be accessed through GOT, similar to global
+	 symbols, to allow for indirection */
       if (!htab->is_vxworks && local_p)
 	{
 	  value = mips_elf_got16_entry (abfd, input_bfd, info,
-					symbol + addend, !was_local_p);
+					symbol + addend,
+					!was_local_p || local_gnu_ifunc_p, h);
 	  if (value == MINUS_ONE)
 	    return bfd_reloc_outofrange;
 	  value
@@ -7019,6 +7532,8 @@ _bfd_mips_elf_section_processing (bfd *abfd, Elf_Internal_Shdr *hdr)
 		hdr->sh_size += hdr->sh_addralign - adjust;
 	    }
 	}
+      else if (strcmp (name, ".igot") == 0)
+	hdr->sh_entsize =  MIPS_ELF_GOT_SIZE (abfd);
     }
 
   return TRUE;
@@ -7381,6 +7896,9 @@ _bfd_mips_elf_add_symbol_hook (bfd *abfd, struct bfd_link_info *info,
 			       flagword *flagsp ATTRIBUTE_UNUSED,
 			       asection **secp, bfd_vma *valp)
 {
+  if (ELF_ST_TYPE (sym->st_info) == STT_GNU_IFUNC)
+    elf_tdata (info->output_bfd)->has_gnu_symbols = TRUE;
+
   if (SGI_COMPAT (abfd)
       && (abfd->flags & DYNAMIC) != 0
       && strcmp (*namep, "_rld_new_interface") == 0)
@@ -7932,6 +8450,13 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
   bed = get_elf_backend_data (abfd);
   rel_end = relocs + sec->reloc_count * bed->s->int_rels_per_ext_rel;
 
+  /* This needs to happen early. If the sections aren't needed
+     they will not get generated.  */
+  if (htab->root.dynobj == NULL)
+    htab->root.dynobj = abfd;
+  if (!htab->root.iplt && !mips_elf_create_ifunc_sections (info))
+    return FALSE;
+
   /* Check for the mips16 stub sections.  */
 
   name = bfd_get_section_name (abfd, sec);
@@ -8193,12 +8718,30 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
       bfd_boolean can_make_dynamic_p;
       bfd_boolean call_reloc_p;
       bfd_boolean constrain_symbol_p;
+      bfd_boolean local_gnu_ifunc_p = FALSE;
 
       r_symndx = ELF_R_SYM (abfd, rel->r_info);
       r_type = ELF_R_TYPE (abfd, rel->r_info);
 
       if (r_symndx < extsymoff)
-	h = NULL;
+	{
+	  Elf_Internal_Sym *isym;
+	  isym = bfd_sym_from_r_symndx (&htab->sym_cache, abfd, r_symndx);
+
+	  if (isym == NULL)
+	    return FALSE;
+
+	  /* Check relocation for local STT_GNU_IFUNC symbol */
+	  if (isym->st_info == STT_GNU_IFUNC)
+	    {
+	      local_gnu_ifunc_p = TRUE;
+	      /* Add local IFUNC symbol to hash-table */
+	      if ( get_local_sym_hash (htab, abfd, rel) == NULL )
+		return FALSE;
+	    }
+
+	  h = NULL;
+       }
       else if (r_symndx >= extsymoff + NUM_SHDR_ENTRIES (symtab_hdr))
 	{
 	  (*_bfd_error_handler)
@@ -8410,7 +8953,8 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
 	case R_MIPS_CALL16:
 	case R_MIPS16_CALL16:
 	case R_MICROMIPS_CALL16:
-	  if (h == NULL)
+	  /* Exclude local IFUNCs from check */
+	  if (h == NULL && !local_gnu_ifunc_p)
 	    {
 	      (*_bfd_error_handler)
 		(_("%B: CALL16 reloc at 0x%lx not against global symbol"),
@@ -8435,9 +8979,11 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
 
 	      /* We need a stub, not a plt entry for the undefined
 		 function.  But we record it as if it needs plt.  See
-		 _bfd_elf_adjust_dynamic_symbol.  */
+		 _bfd_elf_adjust_dynamic_symbol.  If it is an ifunc
+		 symbol it will go into an iplt section and not plt.  */
 	      h->needs_plt = 1;
-	      h->type = STT_FUNC;
+	      if (h->type != STT_GNU_IFUNC)
+		h->type = STT_FUNC;
 	    }
 	  break;
 
@@ -8976,6 +9522,7 @@ _bfd_mips_elf_adjust_dynamic_symbol (struct bfd_link_info *info,
   /* Make sure we know what is going on here.  */
   BFD_ASSERT (dynobj != NULL
 	      && (h->needs_plt
+		  || h->type == STT_GNU_IFUNC
 		  || h->u.weakdef != NULL
 		  || (h->def_dynamic
 		      && h->ref_regular
@@ -9254,6 +9801,11 @@ _bfd_mips_elf_always_size_sections (bfd *output_bfd,
   hti.error = FALSE;
   mips_elf_link_hash_traverse (mips_elf_hash_table (info),
 			       mips_elf_check_symbols, &hti);
+
+  /* Allocate relocs for local IFUNC symbols.  */
+  htab_traverse (htab->loc_hash_table,
+		 mips_elf_check_local_symbols, &hti);
+
   if (hti.error)
     return FALSE;
 
@@ -9298,6 +9850,7 @@ mips_elf_lay_out_got (bfd *output_bfd, struct bfd_link_info *info)
      count the number of reloc-only GOT symbols.  */
   mips_elf_link_hash_traverse (htab, mips_elf_count_got_symbols, info);
 
+  htab_traverse (htab->loc_hash_table, mips_elf_count_local_got_symbols, info);
   if (!mips_elf_resolve_final_got_entries (info, g))
     return FALSE;
 
@@ -9742,6 +10295,8 @@ _bfd_mips_elf_size_dynamic_sections (bfd *output_bfd,
       else if (! CONST_STRNEQ (name, ".init")
 	       && s != htab->sgot
 	       && s != htab->sgotplt
+	       && s != htab->root.iplt
+	       && s != htab->root.igotplt
 	       && s != htab->sstubs
 	       && s != htab->sdynbss)
 	{
@@ -9883,6 +10438,22 @@ _bfd_mips_elf_size_dynamic_sections (bfd *output_bfd,
 	  if (! MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_PLTGOT, 0))
 	    return FALSE;
 	}
+      if (info->shared && htab->root.iplt && htab->root.iplt->size > 0)
+	{
+	  if (! MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_IPLT, 0))
+	    return FALSE;
+	  if (! MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_IFUNC_INDX, 0))
+	    return FALSE;
+	  /* These entries are only needed in multi-got scenario.  */
+	  if (htab->sgot->size > MIPS_ELF_GOT_MAX_SIZE (info))
+	    {
+	      if (! MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_IPLTREL, 0))
+		return FALSE;
+	      if (! MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_IFUNCREL_INDX, 0))
+		return FALSE;
+	    }
+
+	}
       if (htab->is_vxworks
 	  && !elf_vxworks_add_dynamic_entries (output_bfd, info))
 	return FALSE;
@@ -10475,6 +11046,281 @@ mips_elf_irix6_finish_dynamic_symbol (bfd *abfd ATTRIBUTE_UNUSED,
 	}
 }
 
+/* Create the contents of the iplt entry for an IFUNC symbol.  */
+
+static bfd_boolean
+mips_elf_create_iplt (bfd *output_bfd,
+		      struct mips_elf_link_hash_table *htab,
+		      struct mips_elf_link_hash_entry *hmips,
+		      struct bfd_link_info *info,
+		      bfd_vma igotplt_address)
+{
+  bfd_byte *loc;
+  const bfd_vma *iplt_entry;
+
+  /* Find out where the .iplt entry should go.  */
+  if (!htab->root.iplt->contents)
+    {
+      htab->root.iplt->contents = bfd_zalloc (output_bfd,
+					      htab->root.iplt->size);
+      if (!htab->root.iplt->contents)
+	return FALSE;
+    }
+  loc = htab->root.iplt->contents + hmips->iplt_offset;
+
+  /* Fill in the IPLT entry itself.  */
+  if (info->shared)
+    {
+      /* For position independant code, offset of igot entry from
+	 the start address of this iplt is embedded within the stub
+	 in various forms.  */
+      bfd_vma iplt_address = (htab->root.iplt->output_section->vma
+			      + htab->root.iplt->output_offset
+			      + hmips->iplt_offset);
+      bfd_vma igotplt_offset = igotplt_address - iplt_address;
+      bfd_vma high = mips_elf_high (igotplt_offset);
+      bfd_vma low = igotplt_offset & 0xffff;
+
+      if (ABI_64_P (output_bfd))
+	{
+	  bfd_vma highest = mips_elf_highest (igotplt_offset);
+	  bfd_vma higher = mips_elf_higher (igotplt_offset);
+
+	  iplt_entry = mips64_so_iplt_entry;
+	  bfd_put_32 (output_bfd, iplt_entry[0] | highest, loc);
+	  bfd_put_32 (output_bfd, iplt_entry[1] | higher, loc + 4);
+	  bfd_put_32 (output_bfd, iplt_entry[2], loc + 8);
+	  bfd_put_32 (output_bfd, iplt_entry[3] | high , loc + 12);
+	  bfd_put_32 (output_bfd, iplt_entry[4], loc + 16);
+	  bfd_put_32 (output_bfd, iplt_entry[5], loc + 20);
+	  bfd_put_32 (output_bfd, iplt_entry[6] | low, loc + 24);
+	  bfd_put_32 (output_bfd, iplt_entry[7], loc + 28);
+	  bfd_put_32 (output_bfd, iplt_entry[8], loc + 32);
+	}
+      else if (ELF_ST_IS_MIPS16 (hmips->root.other))
+	{
+	  iplt_entry = mips16_so_iplt_entry;
+	  bfd_put_16 (output_bfd, iplt_entry[0], loc);
+	  bfd_put_16 (output_bfd, iplt_entry[1], loc + 2);
+	  bfd_put_16 (output_bfd, iplt_entry[2], loc + 4);
+	  bfd_put_16 (output_bfd, iplt_entry[3], loc + 6);
+	  bfd_put_16 (output_bfd, iplt_entry[4], loc + 8);
+	  bfd_put_16 (output_bfd, iplt_entry[5], loc + 10);
+	  bfd_put_32 (output_bfd, igotplt_offset, loc + 12);
+	}
+      else if (ELF_ST_IS_MICROMIPS (hmips->root.other))
+	{
+	  /* Compensate for offset scale-up performed by addiupc.  */
+	  low = low >> 2;
+	  iplt_entry = micromips32_so_iplt_entry;
+	  bfd_put_micromips_32 (output_bfd, iplt_entry[0] | low, loc);
+	  bfd_put_micromips_32 (output_bfd, iplt_entry[1] | high, loc + 4);
+	  bfd_put_16 (output_bfd, iplt_entry[2], loc + 8);
+	  bfd_put_16 (output_bfd, iplt_entry[3], loc + 10);
+	  bfd_put_16 (output_bfd, iplt_entry[4], loc + 12);
+	  bfd_put_16 (output_bfd, iplt_entry[5], loc + 14);
+	}
+      else
+	{
+	  iplt_entry = mips32_so_iplt_entry;
+	  bfd_put_32 (output_bfd, iplt_entry[0] | high, loc);
+	  bfd_put_32 (output_bfd, iplt_entry[1], loc + 4);
+	  bfd_put_32 (output_bfd, iplt_entry[2] | low, loc + 8);
+	  if (LOAD_INTERLOCKS_P (output_bfd))
+	    {
+	      bfd_put_32 (output_bfd, iplt_entry[3], loc + 12);
+	      bfd_put_32 (output_bfd, iplt_entry[4], loc + 16);
+	    }
+	  else
+	    {
+	      bfd_put_32 (output_bfd, iplt_entry[4], loc + 12);
+	      bfd_put_32 (output_bfd, iplt_entry[3], loc + 16);
+	      bfd_put_32 (output_bfd, iplt_entry[4], loc + 20);
+	    }
+	}
+    }
+  else
+    {
+      bfd_vma high = mips_elf_high (igotplt_address);
+      bfd_vma low = igotplt_address & 0xffff;
+      if (ABI_64_P (output_bfd))
+	{
+	  bfd_vma highest = mips_elf_highest (igotplt_address);
+	  bfd_vma higher = mips_elf_higher (igotplt_address);
+
+	  iplt_entry = mips64_exec_iplt_entry;
+	  bfd_put_32 (output_bfd, iplt_entry[0] | highest, loc);
+	  bfd_put_32 (output_bfd, iplt_entry[1] | higher, loc + 4);
+	  bfd_put_32 (output_bfd, iplt_entry[2], loc + 8);
+	  bfd_put_32 (output_bfd, iplt_entry[3] | high , loc + 12);
+	  bfd_put_32 (output_bfd, iplt_entry[4], loc + 16);
+	  bfd_put_32 (output_bfd, iplt_entry[5] | low, loc + 20);
+	  bfd_put_32 (output_bfd, iplt_entry[6], loc + 24);
+	  bfd_put_32 (output_bfd, iplt_entry[7], loc + 28);
+	}
+      else if (ELF_ST_IS_MIPS16 (hmips->root.other))
+	{
+	  iplt_entry = mips16_exec_iplt_entry;
+	  bfd_put_16 (output_bfd, iplt_entry[0], loc);
+	  bfd_put_16 (output_bfd, iplt_entry[1], loc + 2);
+	  bfd_put_16 (output_bfd, iplt_entry[2], loc + 4);
+	  bfd_put_16 (output_bfd, iplt_entry[3], loc + 6);
+	  bfd_put_32 (output_bfd, igotplt_address, loc + 8);
+	}
+      else if (ELF_ST_IS_MICROMIPS (hmips->root.other))
+	{
+	  iplt_entry = micromips32_exec_iplt_entry;
+	  bfd_put_micromips_32 (output_bfd, iplt_entry[0] | high, loc);
+	  bfd_put_micromips_32 (output_bfd, iplt_entry[1] | low , loc + 4);
+
+	  bfd_put_16 (output_bfd, iplt_entry[2], loc + 8);
+	  bfd_put_16 (output_bfd, iplt_entry[3], loc + 10);
+	}
+      else
+	{
+	  iplt_entry = mips32_exec_iplt_entry;
+	  bfd_put_32 (output_bfd, iplt_entry[0] | high, loc);
+	  bfd_put_32 (output_bfd, iplt_entry[1] | low, loc + 4);
+	  if (LOAD_INTERLOCKS_P (output_bfd))
+	    {
+	      bfd_put_32 (output_bfd, iplt_entry[2], loc + 8);
+	      bfd_put_32 (output_bfd, iplt_entry[3], loc + 12);
+	    }
+	  else
+	    {
+	      bfd_put_32 (output_bfd, iplt_entry[3], loc + 8);
+	      bfd_put_32 (output_bfd, iplt_entry[2], loc + 12);
+	      bfd_put_32 (output_bfd, iplt_entry[3], loc + 16);
+	    }
+	}
+    }
+
+  return TRUE;
+}
+
+/* Return the GOT index of global symbol H in the secondary GOT.  */
+
+static bfd_vma
+mips_elf_multi_got_index (bfd *ibfd,
+				struct bfd_link_info *info,
+				struct elf_link_hash_entry *h)
+{
+  struct mips_got_entry e, *p;
+
+  struct mips_got_info *g, *gg;
+  struct mips_elf_link_hash_table *htab;
+  struct mips_elf_link_hash_entry *hmips;
+
+  htab = mips_elf_hash_table (info);
+  BFD_ASSERT (htab != NULL);
+  hmips = (struct mips_elf_link_hash_entry *) h;
+
+  g = htab->got_info;
+  gg = g;
+
+  e.abfd = ibfd;
+  e.symndx = -1;
+  e.d.h = hmips;
+  e.tls_type = GOT_TLS_NONE;
+
+  for (g = g->next; g->next != gg; g = g->next)
+    {
+      if (g->got_entries
+	  && (p = (struct mips_got_entry *) htab_find (g->got_entries, &e)))
+	      return p->gotidx;
+    }
+  return 0;
+}
+
+/* Create the IRELATIVE relocation for an IFUNC symbol.  */
+
+static bfd_boolean
+mips_elf_create_ireloc (bfd *output_bfd,
+		      bfd *dynobj,
+		      struct mips_elf_link_hash_table *htab,
+		      struct mips_elf_link_hash_entry *hmips,
+		      Elf_Internal_Sym *sym,
+		      struct bfd_link_info *info)
+{
+  bfd_byte *loc;
+  bfd_vma igotplt_address = 0;
+  bfd_vma igot_offset = 0;
+  asection *gotsect, *relsect;
+  int symindx;
+
+  if (!hmips->needs_igot)
+    {
+      /* Symbol already has local/global GOT entry, use it as IGOT entry.  */
+      gotsect = htab->sgot;
+      if (hmips->global_got_area != GGA_NONE)
+	{
+	  igot_offset = mips_elf_primary_global_got_index (output_bfd,
+							   info, &hmips->root);
+	  /* If offset exceeds 16 bits, lookup in secondary GOTs.  */
+	  if (igot_offset > MIPS_ELF_GOT_MAX_SIZE(info))
+	    igot_offset = mips_elf_multi_got_index (output_bfd, info,
+						    &hmips->root);
+	}
+      else if (mips_use_local_got_p (info, hmips))
+	  igot_offset = mips_elf_local_got_index (output_bfd, output_bfd,
+						  info, sym->st_value, 0,
+						  hmips, R_MIPS_32);
+    }
+  else
+    {
+      bfd_vma igot_index;
+      gotsect = htab->root.igotplt;
+      igot_offset = hmips->igot_offset;
+
+      /* Calculate the address of the IGOT entry.  */
+      igot_index = igot_offset / MIPS_ELF_GOT_SIZE (dynobj);
+
+      if (!gotsect->contents)
+	{
+	  gotsect->contents = bfd_zalloc (output_bfd, gotsect->size);
+	  if (!gotsect->contents)
+	    return FALSE;
+	}
+
+      /* Initially point the .igot entry at the IFUNC resolver routine.  */
+      loc = (bfd_byte *) gotsect->contents
+	+ igot_index * MIPS_ELF_GOT_SIZE (dynobj);
+
+      if (ABI_64_P (output_bfd))
+	bfd_put_64 (output_bfd, sym->st_value, loc);
+      else
+	bfd_put_32 (output_bfd, sym->st_value, loc);
+    }
+
+  igotplt_address = (gotsect->output_section->vma + gotsect->output_offset
+		     + igot_offset);
+
+  relsect = mips_get_irel_section (info, htab);
+  if (relsect->contents == NULL)
+    {
+      /* Allocate memory for the relocation section contents.  */
+      relsect->contents = bfd_zalloc (dynobj, relsect->size);
+      if (relsect->contents == NULL)
+	return FALSE;
+    }
+
+  symindx = hmips->root.dynindx;
+  if (symindx < 0)
+    symindx = 0;
+
+  /* Emit an R_MIPS_IRELATIVE relocation against the IGOT entry.  */
+  mips_elf_output_dynamic_relocation (output_bfd, relsect,
+				      relsect->reloc_count++, symindx,
+				      R_MIPS_IRELATIVE, igotplt_address);
+
+  /* If necessary, generate the corresponding .iplt entry.  */
+  if (hmips->needs_iplt)
+    return mips_elf_create_iplt (output_bfd, htab, hmips, info,
+				 igotplt_address);
+  else
+    return TRUE;
+}
+
 /* Finish up dynamic symbol handling.  We set the contents of various
    dynamic sections here.  */
 
@@ -10803,6 +11649,17 @@ _bfd_mips_elf_finish_dynamic_symbol (bfd *output_bfd,
       sym->st_other = other;
     }
 
+  if (hmips->root.type == STT_GNU_IFUNC)
+    {
+      if (!mips_elf_create_ireloc (output_bfd, dynobj, htab, hmips,
+				 sym, info))
+	return FALSE;
+      if (!elf_hash_table (info)->dynamic_sections_created)
+	return TRUE;
+      if (h->dynindx == -1  && !h->forced_local)
+	return TRUE;
+    }
+
   /* If we have a MIPS16 function with a stub, the dynamic symbol must
      refer to the stub, since only the stub uses the standard calling
      conventions.  */
@@ -10966,9 +11823,36 @@ _bfd_mips_elf_finish_dynamic_symbol (bfd *output_bfd,
       sym->st_other -= STO_MICROMIPS;
     }
 
+  if (hmips->needs_iplt && !info->shared)
+    {
+      /* Point at the iplt stub for this ifunc symbol.  */
+      sym->st_value = htab->root.iplt->output_section->vma
+	+ htab->root.iplt->output_offset + hmips->iplt_offset;
+      sym->st_info = ELF_ST_INFO (STB_GLOBAL, STT_FUNC);
+      if (ELF_ST_IS_COMPRESSED (hmips->root.other))
+	sym->st_value |= 1;
+    }
+
   return TRUE;
 }
 
+/* Finish up local dynamic symbol handling.  We set the contents of
+   various dynamic sections here.  */
+
+static bfd_boolean
+_bfd_mips_elf_finish_local_dynamic_symbol (void **slot, void *inf)
+{
+  struct mips_elf_link_hash_entry *h = (struct mips_elf_link_hash_entry *) *slot;
+  struct bfd_link_info *info  = (struct bfd_link_info *)inf;
+  Elf_Internal_Sym isym;
+
+  isym.st_value = h->root.root.u.def.section->output_section->vma
+    + h->root.root.u.def.section->output_offset + h->root.root.u.def.value;
+
+  return _bfd_mips_elf_finish_dynamic_symbol (info->output_bfd, info,
+					      &h->root, &isym);
+}
+
 /* Likewise, for VxWorks.  */
 
 bfd_boolean
@@ -11354,6 +12238,13 @@ _bfd_mips_elf_finish_dynamic_sections (bfd *output_bfd,
   sgot = htab->sgot;
   gg = htab->got_info;
 
+  if (htab_elements (htab->loc_hash_table) > 0)
+  {
+    /* Fill PLT and GOT entries for local STT_GNU_IFUNC symbols.  */
+    htab_traverse (htab->loc_hash_table,
+		   _bfd_mips_elf_finish_local_dynamic_symbol, info);
+  }
+
   if (elf_hash_table (info)->dynamic_sections_created)
     {
       bfd_byte *b;
@@ -11498,6 +12389,29 @@ _bfd_mips_elf_finish_dynamic_sections (bfd *output_bfd,
 	      dyn.d_un.d_ptr = s->vma;
 	      break;
 
+	    case DT_MIPS_IPLT:
+	      dyn.d_un.d_val = htab->root.iplt->output_section->vma;
+	      /* Compressed stubs start at odd addresses.  */
+	      if (MIPS16_P (output_bfd) || MICROMIPS_P (output_bfd))
+		dyn.d_un.d_val |= 0x1;
+	      break;
+
+	    case DT_MIPS_IFUNC_INDX:
+	      dyn.d_un.d_val = htab->ifunc_dynindx;
+	      break;
+
+	    case DT_MIPS_IPLTREL:
+	      dyn.d_un.d_val = htab->root.iplt->output_section->vma +
+		htab->ifunc_got_count * htab->iplt_entry_size;
+	      /* Compressed stubs start at odd addresses.  */
+	      if (MIPS16_P (output_bfd) || MICROMIPS_P (output_bfd))
+		dyn.d_un.d_val |= 0x1;
+	      break;
+
+	    case DT_MIPS_IFUNCREL_INDX:
+	      dyn.d_un.d_val = htab->ifunc_rel_dynindx;
+	      break;
+
 	    case DT_RELASZ:
 	      BFD_ASSERT (htab->is_vxworks);
 	      /* The count does not include the JUMP_SLOT relocations.  */
@@ -13832,6 +14746,48 @@ _bfd_mips_elf_relax_section (bfd *abfd, asection *sec,
   return FALSE;
 }
 
+/* Compute a hash of a local hash entry.  We use elf_link_hash_entry
+  for local symbol so that we can handle local STT_GNU_IFUNC symbols
+  as global symbol.  We reuse indx and dynstr_index for local symbol
+  hash since they aren't used by global symbols in this backend.  */
+
+static hashval_t
+local_htab_hash (const void *ptr)
+{
+  struct  mips_elf_link_hash_entry *h =
+    (struct  mips_elf_link_hash_entry *) ptr;
+  return ELF_LOCAL_SYMBOL_HASH (h->root.indx, h->root.dynstr_index);
+}
+
+/* Compare local hash entries.  */
+
+static int
+local_htab_eq (const void *ptr1, const void *ptr2)
+{
+  struct mips_elf_link_hash_entry *h1 =
+    (struct mips_elf_link_hash_entry *) ptr1;
+  struct  mips_elf_link_hash_entry *h2 =
+    (struct  mips_elf_link_hash_entry *) ptr2;
+
+  return h1->root.indx == h2->root.indx &&
+    h1->root.dynstr_index == h2->root.dynstr_index;
+}
+
+/* Destroy a MIPS ELF linker hash table.  */
+
+static void
+_bfd_mips_elf_link_hash_table_free (bfd *obfd)
+{
+  struct mips_elf_link_hash_table *htab;
+
+  htab = (struct mips_elf_link_hash_table *) obfd->link.hash;
+  if (htab->loc_hash_table)
+    htab_delete (htab->loc_hash_table);
+  if (htab->loc_hash_memory)
+    objalloc_free ((struct objalloc *) htab->loc_hash_memory);
+  _bfd_elf_link_hash_table_free (obfd);
+}
+
 /* Create a MIPS ELF linker hash table.  */
 
 struct bfd_link_hash_table *
@@ -13855,6 +14811,17 @@ _bfd_mips_elf_link_hash_table_create (bfd *abfd)
   ret->root.init_plt_refcount.plist = NULL;
   ret->root.init_plt_offset.plist = NULL;
 
+  /* Create hash table for local IFUNC symbols.  */
+  ret->loc_hash_table = htab_try_create (1024,
+					 local_htab_hash,
+					 local_htab_eq,
+					 NULL);
+  ret->loc_hash_memory = objalloc_create ();
+  if (!ret->loc_hash_table || !ret->loc_hash_memory)
+    {
+      _bfd_mips_elf_link_hash_table_free (abfd);
+      return NULL;
+    }
   return &ret->root.root;
 }
 
@@ -15477,6 +16444,14 @@ _bfd_mips_elf_get_target_dtag (bfd_vma dtag)
       return "DT_MIPS_PLTGOT";
     case DT_MIPS_RWPLT:
       return "DT_MIPS_RWPLT";
+    case DT_MIPS_IPLT:
+      return "DT_MIPS_IPLT";
+    case DT_MIPS_IFUNC_INDX:
+      return "DT_MIPS_IFUNC_INDX";
+    case DT_MIPS_IPLTREL:
+      return "DT_MIPS_IPLTREL";
+    case DT_MIPS_IFUNCREL_INDX:
+      return "DT_MIPS_IFUNCREL_INDX";
     }
 }
 
diff --git a/bfd/libbfd.h b/bfd/libbfd.h
index 711f08f..6010c06 100644
--- a/bfd/libbfd.h
+++ b/bfd/libbfd.h
@@ -1199,6 +1199,7 @@ static const char *const bfd_reloc_code_real_names[] = { "@@uninitialized@@",
   "BFD_RELOC_MIPS_COPY",
   "BFD_RELOC_MIPS_JUMP_SLOT",
 
+  "BFD_RELOC_MIPS_IRELATIVE",
   "BFD_RELOC_MOXIE_10_PCREL",
 
   "BFD_RELOC_FT32_10",
diff --git a/bfd/reloc.c b/bfd/reloc.c
index d312e48..21187c8 100644
--- a/bfd/reloc.c
+++ b/bfd/reloc.c
@@ -2450,6 +2450,11 @@ ENUMDOC
 COMMENT
 
 ENUM
+  BFD_RELOC_MIPS_IRELATIVE
+ENUMDOC
+  MIPS support for STT_GNU_IFUNC.
+
+ENUM
   BFD_RELOC_MOXIE_10_PCREL
 ENUMDOC
   Moxie ELF relocations.
diff --git a/binutils/readelf.c b/binutils/readelf.c
index a0c20a3..a57008b 100644
--- a/binutils/readelf.c
+++ b/binutils/readelf.c
@@ -1723,6 +1723,11 @@ get_mips_dynamic_type (unsigned long type)
     case DT_MIPS_AUX_DYNAMIC: return "MIPS_AUX_DYNAMIC";
     case DT_MIPS_PLTGOT: return "MIPS_PLTGOT";
     case DT_MIPS_RWPLT: return "MIPS_RWPLT";
+    case DT_MIPS_IPLT: return "MIPS_IPLT";
+    case DT_MIPS_IFUNC_INDX: return "MIPS_IFUNC_INDX";
+    case DT_MIPS_IPLTREL: return "MIPS_IPLTREL";
+    case DT_MIPS_IFUNCREL_INDX: return "MIPS_IFUNCREL_INDX";
+
     default:
       return NULL;
     }
diff --git a/elfcpp/elfcpp.h b/elfcpp/elfcpp.h
index 722984e..a5888ee 100644
--- a/elfcpp/elfcpp.h
+++ b/elfcpp/elfcpp.h
@@ -866,6 +866,14 @@ enum DT
   DT_MIPS_PLTGOT = 0x70000032,
   // Points to the base of a writable PLT.
   DT_MIPS_RWPLT = 0x70000034,
+  // Points to the start of the IFUNC PLT section
+  DT_MIPS_IPLT = 0x70000035,
+  // INDEX of the first IFUNC symbol in dynsym table
+  DT_MIPS_IFUNC_INDX = 0x70000036,
+  // Points to start of reloc-only IFUNC stubs in IPLT section
+  DT_MIPS_IPLTREL = 0x70000037
+  // INDEX of the first reloc-only IFUNC symbol in dynsym table
+  DT_MIPS_IFUNCREL_INDX = 0x70000038
 
   DT_AUXILIARY = 0x7ffffffd,
   DT_USED = 0x7ffffffe,
diff --git a/include/elf/mips.h b/include/elf/mips.h
index 23c21e2..b9adc4e 100644
--- a/include/elf/mips.h
+++ b/include/elf/mips.h
@@ -115,6 +115,9 @@ START_RELOC_NUMBERS (elf_mips_reloc_type)
   RELOC_NUMBER (R_MIPS_COPY, 126)
   RELOC_NUMBER (R_MIPS_JUMP_SLOT, 127)
 
+  /* STT_GNU_IFUNC support */
+  RELOC_NUMBER (R_MIPS_IRELATIVE, 128)
+
   /* These relocations are specific to microMIPS.  */
   FAKE_RELOC (R_MICROMIPS_min, 130)
   RELOC_NUMBER (R_MICROMIPS_26_S1, 133)
@@ -748,6 +751,19 @@ extern void bfd_mips_elf32_swap_reginfo_out
 
 /* Points to the base of a writable PLT.  */
 #define DT_MIPS_RWPLT          0x70000034
+
+/* Points to the start of the IFUNC PLT section */
+#define DT_MIPS_IPLT           0x70000035
+
+/* INDEX of the first IFUNC symbol in dynsym table */
+#define DT_MIPS_IFUNC_INDX  0x70000036
+
+/* Points to start of reloc-only IFUNC stubs in IPLT section */
+#define DT_MIPS_IPLTREL  0x70000037
+
+/* INDEX of the first reloc-only IFUNC symbol in dynsym table */
+#define DT_MIPS_IFUNCREL_INDX  0x70000038
+
 
 /* Flags which may appear in a DT_MIPS_FLAGS entry.  */
 
-- 
1.7.9.5

Attachment: Spec.txt
Description: Text document

ld/testsuite/ChangeLog:

	* ld-mips-elf/mips-ifunc.exp: Ifunc test script
	* ifunc-3-n32.r, ifunc-3-n32.sym, ifunc-3-n32.t, ifunc-3-n64.r,
	ifunc-3-n64.sym, ifunc-3-n64.t, ifunc-3-o32.r, ifunc-3-o32.sym,
	ifunc-3-o32.t, ifunc-4-n32.r, ifunc-4-n32.sym, ifunc-4-n32.t,
	ifunc-4-n64.r, ifunc-4-n64.sym, ifunc-4-n64.t, ifunc-4-o32.r,
	ifunc-4-o32.sym, ifunc-4-o32.t, ifunc-5-n32.g, ifunc-5-n32.r,
	ifunc-5-n32.sym, ifunc-5-n64.g, ifunc-5-n64.r, ifunc-5-n64.sym,
	ifunc-5-o32.g, ifunc-5-o32.r, ifunc-5-o32.sym, ifunc-6-n32.r,
	ifunc-6-n32.sym, ifunc-6-n64.r, ifunc-6-n64.sym, ifunc-6-o32.r,
	ifunc-6-o32.sym, ifunc-dyn-def.s, ifunc-dyn-main.s,
	ifunc-dyn-ref.s, ifunc-dyn.ld, ifunc-iplt.ld, ifunc-static-def.s,
	ifunc-static-main.s, ifunc-static-ref.s, ifunc-static.ld,
	libifunc-1-n32.sym, libifunc-1-n64.sym, libifunc-1-o32.sym,
	libifunc-2-n32.sym, libifunc-2-n64.sym, libifunc-2-o32.sym:
	New tests.

---
 ld/testsuite/ld-mips-elf/ifunc-3-n32.r       |    4 +
 ld/testsuite/ld-mips-elf/ifunc-3-n32.sym     |    6 +
 ld/testsuite/ld-mips-elf/ifunc-3-n32.t       |   10 ++
 ld/testsuite/ld-mips-elf/ifunc-3-n64.r       |    5 +
 ld/testsuite/ld-mips-elf/ifunc-3-n64.sym     |    6 +
 ld/testsuite/ld-mips-elf/ifunc-3-n64.t       |   14 +++
 ld/testsuite/ld-mips-elf/ifunc-3-o32.r       |    4 +
 ld/testsuite/ld-mips-elf/ifunc-3-o32.sym     |    6 +
 ld/testsuite/ld-mips-elf/ifunc-3-o32.t       |   11 ++
 ld/testsuite/ld-mips-elf/ifunc-4-n32.r       |    3 +
 ld/testsuite/ld-mips-elf/ifunc-4-n32.t       |   10 ++
 ld/testsuite/ld-mips-elf/ifunc-4-n64.r       |    5 +
 ld/testsuite/ld-mips-elf/ifunc-4-n64.t       |   14 +++
 ld/testsuite/ld-mips-elf/ifunc-4-o32.r       |    3 +
 ld/testsuite/ld-mips-elf/ifunc-4-o32.t       |   11 ++
 ld/testsuite/ld-mips-elf/ifunc-5-n32.r       |    3 +
 ld/testsuite/ld-mips-elf/ifunc-5-n32.sym     |    5 +
 ld/testsuite/ld-mips-elf/ifunc-5-n64.r       |    5 +
 ld/testsuite/ld-mips-elf/ifunc-5-n64.sym     |    5 +
 ld/testsuite/ld-mips-elf/ifunc-5-o32.r       |    3 +
 ld/testsuite/ld-mips-elf/ifunc-5-o32.sym     |    6 +
 ld/testsuite/ld-mips-elf/ifunc-6-n32.r       |    3 +
 ld/testsuite/ld-mips-elf/ifunc-6-n32.sym     |    6 +
 ld/testsuite/ld-mips-elf/ifunc-6-n64.r       |    5 +
 ld/testsuite/ld-mips-elf/ifunc-6-n64.sym     |    6 +
 ld/testsuite/ld-mips-elf/ifunc-6-o32.r       |    3 +
 ld/testsuite/ld-mips-elf/ifunc-6-o32.sym     |    6 +
 ld/testsuite/ld-mips-elf/ifunc-dyn-def.s     |   67 +++++++++++
 ld/testsuite/ld-mips-elf/ifunc-dyn-main.s    |   39 +++++++
 ld/testsuite/ld-mips-elf/ifunc-dyn-ref.s     |   43 +++++++
 ld/testsuite/ld-mips-elf/ifunc-dyn.ld        |   25 +++++
 ld/testsuite/ld-mips-elf/ifunc-iplt.ld       |   26 +++++
 ld/testsuite/ld-mips-elf/ifunc-static-def.s  |  155 ++++++++++++++++++++++++++
 ld/testsuite/ld-mips-elf/ifunc-static-main.s |   39 +++++++
 ld/testsuite/ld-mips-elf/ifunc-static-ref.s  |   39 +++++++
 ld/testsuite/ld-mips-elf/ifunc-static.ld     |   27 +++++
 ld/testsuite/ld-mips-elf/libifunc-1-n32.sym  |    4 +
 ld/testsuite/ld-mips-elf/libifunc-1-n64.sym  |    4 +
 ld/testsuite/ld-mips-elf/libifunc-1-o32.sym  |    4 +
 ld/testsuite/ld-mips-elf/libifunc-2-n32.sym  |    4 +
 ld/testsuite/ld-mips-elf/libifunc-2-n64.sym  |    4 +
 ld/testsuite/ld-mips-elf/libifunc-2-o32.sym  |    5 +
 ld/testsuite/ld-mips-elf/mips-ifunc.exp      |  130 +++++++++++++++++++++
 43 files changed, 783 insertions(+)
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-3-n32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-3-n32.sym
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-3-n32.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-3-n64.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-3-n64.sym
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-3-n64.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-3-o32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-3-o32.sym
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-3-o32.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-4-n32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-4-n32.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-4-n64.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-4-n64.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-4-o32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-4-o32.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-5-n32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-5-n32.sym
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-5-n64.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-5-n64.sym
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-5-o32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-5-o32.sym
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-6-n32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-6-n32.sym
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-6-n64.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-6-n64.sym
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-6-o32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-6-o32.sym
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-dyn-def.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-dyn-main.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-dyn-ref.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-dyn.ld
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt.ld
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-static-def.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-static-main.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-static-ref.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-static.ld
 create mode 100644 ld/testsuite/ld-mips-elf/libifunc-1-n32.sym
 create mode 100644 ld/testsuite/ld-mips-elf/libifunc-1-n64.sym
 create mode 100644 ld/testsuite/ld-mips-elf/libifunc-1-o32.sym
 create mode 100644 ld/testsuite/ld-mips-elf/libifunc-2-n32.sym
 create mode 100644 ld/testsuite/ld-mips-elf/libifunc-2-n64.sym
 create mode 100644 ld/testsuite/ld-mips-elf/libifunc-2-o32.sym
 create mode 100644 ld/testsuite/ld-mips-elf/mips-ifunc.exp

diff --git a/ld/testsuite/ld-mips-elf/ifunc-3-n32.r b/ld/testsuite/ld-mips-elf/ifunc-3-n32.r
new file mode 100644
index 0000000..805cd17
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-n32.r
@@ -0,0 +1,4 @@
+
+Relocation section '.rel.dyn' at offset 0x12000 contains 1 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00000800  00000480 R_MIPS_IRELATIVE  00000400   func1
diff --git a/ld/testsuite/ld-mips-elf/ifunc-3-n32.sym b/ld/testsuite/ld-mips-elf/ifunc-3-n32.sym
new file mode 100644
index 0000000..f7c4ddf
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-n32.sym
@@ -0,0 +1,6 @@
+Symbol table for image:
+  Num Buc:    Value  Size   Type   Bind Vis      Ndx Name
+    3   0: 00000000     0 FUNC    GLOBAL DEFAULT UND ref1
+    4   0: 00000400    28 FUNC    GLOBAL DEFAULT   1 func1
+    1   1: 00000001     0 SECTION GLOBAL DEFAULT ABS _DYNAMIC_LINKING
+    2   2: 0000100c     0 OBJECT  GLOBAL DEFAULT  14 __RLD_MAP
diff --git a/ld/testsuite/ld-mips-elf/ifunc-3-n32.t b/ld/testsuite/ld-mips-elf/ifunc-3-n32.t
new file mode 100644
index 0000000..edf2787
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-n32.t
@@ -0,0 +1,10 @@
+tmpdir/ifunc-3-n32:     file format elf32-ntradbigmips
+
+
+Disassembly of section .iplt:
+
+00000400 <.iplt.func1>:
+ 400:	3c0f0000 	lui	t3,0x0
+ 404:	8df90800 	lw	t9,2048\(t3\)
+ 408:	03200008 	jr	t9
+ 40c:	00000000 	nop
diff --git a/ld/testsuite/ld-mips-elf/ifunc-3-n64.r b/ld/testsuite/ld-mips-elf/ifunc-3-n64.r
new file mode 100644
index 0000000..8cfa0c4
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-n64.r
@@ -0,0 +1,5 @@
+Relocation section '.rel.dyn' at offset 0x12000 contains 1 entries:
+  Offset          Info           Type           Sym. Value    Sym. Name
+000000000800  000400000080 R_MIPS_IRELATIVE  0000000000000400 func1
+                    Type2: R_MIPS_NONE      
+                    Type3: R_MIPS_NONE      
diff --git a/ld/testsuite/ld-mips-elf/ifunc-3-n64.sym b/ld/testsuite/ld-mips-elf/ifunc-3-n64.sym
new file mode 100644
index 0000000..2d55cca
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-n64.sym
@@ -0,0 +1,6 @@
+Symbol table for image:
+  Num Buc:    Value          Size   Type   Bind Vis      Ndx Name
+    3   0: 0000000000000000     0 FUNC    GLOBAL DEFAULT UND ref1
+    4   0: 0000000000000400    28 FUNC    GLOBAL DEFAULT   1 func1
+    1   1: 0000000000000001     0 SECTION GLOBAL DEFAULT ABS _DYNAMIC_LINKING
+    2   2: 0000000000001018     0 OBJECT  GLOBAL DEFAULT  14 __RLD_MAP
diff --git a/ld/testsuite/ld-mips-elf/ifunc-3-n64.t b/ld/testsuite/ld-mips-elf/ifunc-3-n64.t
new file mode 100644
index 0000000..56d299c
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-n64.t
@@ -0,0 +1,14 @@
+tmpdir/ifunc-3-n64:     file format elf64-tradbigmips
+
+
+Disassembly of section .iplt:
+
+0000000000000400 <.iplt.func1>:
+ 400:	3c0f0000 	lui	t3,0x0
+ 404:	65ef0000 	daddiu	t3,t3,0
+ 408:	000f7c38 	dsll	t3,t3,0x10
+ 40c:	65ef0000 	daddiu	t3,t3,0
+ 410:	000f7c38 	dsll	t3,t3,0x10
+ 414:	ddf90800 	ld	t9,2048\(t3\)
+ 418:	03200008 	jr	t9
+ 41c:	00000000 	nop
diff --git a/ld/testsuite/ld-mips-elf/ifunc-3-o32.r b/ld/testsuite/ld-mips-elf/ifunc-3-o32.r
new file mode 100644
index 0000000..805cd17
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-o32.r
@@ -0,0 +1,4 @@
+
+Relocation section '.rel.dyn' at offset 0x12000 contains 1 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00000800  00000480 R_MIPS_IRELATIVE  00000400   func1
diff --git a/ld/testsuite/ld-mips-elf/ifunc-3-o32.sym b/ld/testsuite/ld-mips-elf/ifunc-3-o32.sym
new file mode 100644
index 0000000..c7f6343
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-o32.sym
@@ -0,0 +1,6 @@
+Symbol table for image:
+  Num Buc:    Value  Size   Type   Bind Vis      Ndx Name
+    3   0: 00000000     0 FUNC    GLOBAL DEFAULT UND ref1
+    4   0: 00000400    40 FUNC    GLOBAL DEFAULT   1 func1
+    1   1: 00000001     0 SECTION GLOBAL DEFAULT ABS _DYNAMIC_LINKING
+    2   2: 0000100c     0 OBJECT  GLOBAL DEFAULT  14 __RLD_MAP
diff --git a/ld/testsuite/ld-mips-elf/ifunc-3-o32.t b/ld/testsuite/ld-mips-elf/ifunc-3-o32.t
new file mode 100644
index 0000000..eba438f
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-o32.t
@@ -0,0 +1,11 @@
+tmpdir/ifunc-3-o32:     file format elf32-tradbigmips
+
+
+Disassembly of section .iplt:
+
+00000400 <.iplt.func1>:
+ 400:	3c0f0000 	lui	t7,0x0
+ 404:	8df90800 	lw	t9,2048\(t7\)
+ 408:	00000000 	nop
+ 40c:	03200008 	jr	t9
+ 410:	00000000 	nop
diff --git a/ld/testsuite/ld-mips-elf/ifunc-4-n32.r b/ld/testsuite/ld-mips-elf/ifunc-4-n32.r
new file mode 100644
index 0000000..e899ffc
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-n32.r
@@ -0,0 +1,3 @@
+Relocation section '.rel.dyn' at offset 0x11000 contains 1 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00080800  00000080 R_MIPS_IRELATIVE 
diff --git a/ld/testsuite/ld-mips-elf/ifunc-4-n32.t b/ld/testsuite/ld-mips-elf/ifunc-4-n32.t
new file mode 100644
index 0000000..a681706
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-n32.t
@@ -0,0 +1,10 @@
+tmpdir/ifunc-4-n32:     file format elf32-ntradbigmips
+
+
+Disassembly of section .iplt:
+
+00080000 <.iplt.func1>:
+   80000:	3c0f0008 	lui	t3,0x8
+   80004:	8df90800 	lw	t9,2048\(t3\)
+   80008:	03200008 	jr	t9
+   8000c:	00000000 	nop
diff --git a/ld/testsuite/ld-mips-elf/ifunc-4-n64.r b/ld/testsuite/ld-mips-elf/ifunc-4-n64.r
new file mode 100644
index 0000000..695742b
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-n64.r
@@ -0,0 +1,5 @@
+Relocation section '.rel.dyn' at offset 0x11000 contains 1 entries:
+  Offset          Info           Type           Sym. Value    Sym. Name
+000000080800  000000000080 R_MIPS_IRELATIVE 
+                    Type2: R_MIPS_NONE      
+                    Type3: R_MIPS_NONE      
diff --git a/ld/testsuite/ld-mips-elf/ifunc-4-n64.t b/ld/testsuite/ld-mips-elf/ifunc-4-n64.t
new file mode 100644
index 0000000..b602165
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-n64.t
@@ -0,0 +1,14 @@
+tmpdir/ifunc-4-n64:     file format elf64-tradbigmips
+
+
+Disassembly of section .iplt:
+
+0000000000080000 <.iplt.func1>:
+   80000:	3c0f0000 	lui	t3,0x0
+   80004:	65ef0000 	daddiu	t3,t3,0
+   80008:	000f7c38 	dsll	t3,t3,0x10
+   8000c:	65ef0008 	daddiu	t3,t3,8
+   80010:	000f7c38 	dsll	t3,t3,0x10
+   80014:	ddf90800 	ld	t9,2048\(t3\)
+   80018:	03200008 	jr	t9
+   8001c:	00000000 	nop
diff --git a/ld/testsuite/ld-mips-elf/ifunc-4-o32.r b/ld/testsuite/ld-mips-elf/ifunc-4-o32.r
new file mode 100644
index 0000000..e899ffc
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-o32.r
@@ -0,0 +1,3 @@
+Relocation section '.rel.dyn' at offset 0x11000 contains 1 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00080800  00000080 R_MIPS_IRELATIVE 
diff --git a/ld/testsuite/ld-mips-elf/ifunc-4-o32.t b/ld/testsuite/ld-mips-elf/ifunc-4-o32.t
new file mode 100644
index 0000000..bc71cb0
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-o32.t
@@ -0,0 +1,11 @@
+tmpdir/ifunc-4-o32:     file format elf32-tradbigmips
+
+
+Disassembly of section .iplt:
+
+00080000 <.iplt.func1>:
+   80000:	3c0f0008 	lui	t7,0x8
+   80004:	8df90800 	lw	t9,2048\(t7\)
+   80008:	00000000 	nop
+   8000c:	03200008 	jr	t9
+   80010:	00000000 	nop
diff --git a/ld/testsuite/ld-mips-elf/ifunc-5-n32.r b/ld/testsuite/ld-mips-elf/ifunc-5-n32.r
new file mode 100644
index 0000000..43819e5
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-n32.r
@@ -0,0 +1,3 @@
+Relocation section '.rel.dyn' at offset 0x11000 contains 1 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00000810  00000480 R_MIPS_IRELATIVE  func1\(\)    func1
diff --git a/ld/testsuite/ld-mips-elf/ifunc-5-n32.sym b/ld/testsuite/ld-mips-elf/ifunc-5-n32.sym
new file mode 100644
index 0000000..3756726
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-n32.sym
@@ -0,0 +1,5 @@
+Symbol table for image:
+  Num Buc:    Value  Size   Type   Bind Vis      Ndx Name
+    2   0: 00000030    60 FUNC    GLOBAL DEFAULT   1 ref1
+    4   0: 0000000c    28 IFUNC   GLOBAL DEFAULT   1 func1
+    3   1: 0000000c    28 FUNC    GLOBAL DEFAULT   1 func1_ifunc
diff --git a/ld/testsuite/ld-mips-elf/ifunc-5-n64.r b/ld/testsuite/ld-mips-elf/ifunc-5-n64.r
new file mode 100644
index 0000000..cf6b01a
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-n64.r
@@ -0,0 +1,5 @@
+Relocation section '.rel.dyn' at offset 0x11000 contains 1 entries:
+  Offset          Info           Type           Sym. Value    Sym. Name
+000000000820  000400000080 R_MIPS_IRELATIVE  func1\(\)          func1
+                    Type2: R_MIPS_NONE      
+                    Type3: R_MIPS_NONE      
diff --git a/ld/testsuite/ld-mips-elf/ifunc-5-n64.sym b/ld/testsuite/ld-mips-elf/ifunc-5-n64.sym
new file mode 100644
index 0000000..c28e09a
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-n64.sym
@@ -0,0 +1,5 @@
+Symbol table for image:
+  Num Buc:    Value          Size   Type   Bind Vis      Ndx Name
+    2   0: 0000000000000030    60 FUNC    GLOBAL DEFAULT   1 ref1
+    4   0: 000000000000000c    28 IFUNC   GLOBAL DEFAULT   1 func1
+    3   1: 000000000000000c    28 FUNC    GLOBAL DEFAULT   1 func1_ifunc
diff --git a/ld/testsuite/ld-mips-elf/ifunc-5-o32.r b/ld/testsuite/ld-mips-elf/ifunc-5-o32.r
new file mode 100644
index 0000000..61dc397
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-o32.r
@@ -0,0 +1,3 @@
+Relocation section '.rel.dyn' at offset 0x11000 contains 1 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00000810  00000580 R_MIPS_IRELATIVE  func1\(\)    func1
diff --git a/ld/testsuite/ld-mips-elf/ifunc-5-o32.sym b/ld/testsuite/ld-mips-elf/ifunc-5-o32.sym
new file mode 100644
index 0000000..66a60f7
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-o32.sym
@@ -0,0 +1,6 @@
+Symbol table for image:
+  Num Buc:    Value  Size   Type   Bind Vis      Ndx Name
+    3   0: 00000040    76 FUNC    GLOBAL DEFAULT   1 ref1
+    5   0: 0000000c    40 IFUNC   GLOBAL DEFAULT   1 func1
+    2   0: 00000000     0 SECTION GLOBAL DEFAULT ABS _gp_disp
+    4   1: 0000000c    40 FUNC    GLOBAL DEFAULT   1 func1_ifunc
diff --git a/ld/testsuite/ld-mips-elf/ifunc-6-n32.r b/ld/testsuite/ld-mips-elf/ifunc-6-n32.r
new file mode 100644
index 0000000..32cba8b
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-n32.r
@@ -0,0 +1,3 @@
+Relocation section '.rel.dyn' at offset 0x12000 contains 1 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00000800  00000480 R_MIPS_IRELATIVE  00000400   func1
diff --git a/ld/testsuite/ld-mips-elf/ifunc-6-n32.sym b/ld/testsuite/ld-mips-elf/ifunc-6-n32.sym
new file mode 100644
index 0000000..4fca883
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-n32.sym
@@ -0,0 +1,6 @@
+Symbol table for image:
+  Num Buc:    Value  Size   Type   Bind Vis      Ndx Name
+    3   0: 00000070    60 FUNC    GLOBAL DEFAULT   1 ref1
+    4   0: 00000400    28 FUNC    GLOBAL DEFAULT   1 func1
+    1   1: 00000001     0 SECTION GLOBAL DEFAULT ABS _DYNAMIC_LINKING
+    2   2: 00001000     0 OBJECT  GLOBAL DEFAULT  12 __RLD_MAP
diff --git a/ld/testsuite/ld-mips-elf/ifunc-6-n64.r b/ld/testsuite/ld-mips-elf/ifunc-6-n64.r
new file mode 100644
index 0000000..8cfa0c4
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-n64.r
@@ -0,0 +1,5 @@
+Relocation section '.rel.dyn' at offset 0x12000 contains 1 entries:
+  Offset          Info           Type           Sym. Value    Sym. Name
+000000000800  000400000080 R_MIPS_IRELATIVE  0000000000000400 func1
+                    Type2: R_MIPS_NONE      
+                    Type3: R_MIPS_NONE      
diff --git a/ld/testsuite/ld-mips-elf/ifunc-6-n64.sym b/ld/testsuite/ld-mips-elf/ifunc-6-n64.sym
new file mode 100644
index 0000000..2dc4ae8
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-n64.sym
@@ -0,0 +1,6 @@
+Symbol table for image:
+  Num Buc:    Value          Size   Type   Bind Vis      Ndx Name
+    3   0: 0000000000000070    60 FUNC    GLOBAL DEFAULT   1 ref1
+    4   0: 0000000000000400    28 FUNC    GLOBAL DEFAULT   1 func1
+    1   1: 0000000000000001     0 SECTION GLOBAL DEFAULT ABS _DYNAMIC_LINKING
+    2   2: 0000000000001000     0 OBJECT  GLOBAL DEFAULT  12 __RLD_MAP
diff --git a/ld/testsuite/ld-mips-elf/ifunc-6-o32.r b/ld/testsuite/ld-mips-elf/ifunc-6-o32.r
new file mode 100644
index 0000000..32cba8b
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-o32.r
@@ -0,0 +1,3 @@
+Relocation section '.rel.dyn' at offset 0x12000 contains 1 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00000800  00000480 R_MIPS_IRELATIVE  00000400   func1
diff --git a/ld/testsuite/ld-mips-elf/ifunc-6-o32.sym b/ld/testsuite/ld-mips-elf/ifunc-6-o32.sym
new file mode 100644
index 0000000..7320b3f
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-o32.sym
@@ -0,0 +1,6 @@
+Symbol table for image:
+  Num Buc:    Value  Size   Type   Bind Vis      Ndx Name
+    3   0: 00000080    76 FUNC    GLOBAL DEFAULT   1 ref1
+    4   0: 00000400    40 FUNC    GLOBAL DEFAULT   1 func1
+    1   1: 00000001     0 SECTION GLOBAL DEFAULT ABS _DYNAMIC_LINKING
+    2   2: 00001000     0 OBJECT  GLOBAL DEFAULT  12 __RLD_MAP
diff --git a/ld/testsuite/ld-mips-elf/ifunc-dyn-def.s b/ld/testsuite/ld-mips-elf/ifunc-dyn-def.s
new file mode 100644
index 0000000..9b5d2a8
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-dyn-def.s
@@ -0,0 +1,67 @@
+	.file	1 "ifunc.c"
+	.section .mdebug.abi32
+	.previous
+	.nan	legacy
+	.gnu_attribute 4, 1
+	.abicalls
+	.text
+	.align	2
+	.set	nomips16
+	.set	nomicromips
+	.ent	f1_a
+	.type	f1_a, @function
+f1_a:
+	nop
+
+	.set	macro
+	.set	reorder
+	.end	f1_a
+	.size	f1_a, .-f1_a
+	.align	2
+	.set	nomips16
+	.set	nomicromips
+	.ent	f1_b
+	.type	f1_b, @function
+f1_b:
+	nop
+
+	.set	macro
+	.set	reorder
+	.end	f1_b
+	.size	f1_b, .-f1_b
+	.align	2
+	.set	nomips16
+	.set	nomicromips
+	.ent	f1_c
+	.type	f1_c, @function
+f1_c:
+	.frame	$fp,8,$31		# vars= 0, regs= 1/0, args= 0, gp= 0
+	nop
+
+	.set	macro
+	.set	reorder
+	.end	f1_c
+	.size	f1_c, .-f1_c
+	.align	2
+	.globl	func1_ifunc
+	.set	nomips16
+	.set	nomicromips
+	.ent	func1_ifunc
+	.type	func1_ifunc, @function
+func1_ifunc:
+	lw	$2,%got(f1_a)($28)
+	addiu	$2,$2,%lo(f1_a)
+	lw	$2,%got(f1_b)($28)
+	addiu	$2,$2,%lo(f1_b)
+	lw	$2,%got(f1_c)($28)
+	addiu	$2,$2,%lo(f1_c)
+	nop
+
+	.set	macro
+	.set	reorder
+	.end	func1_ifunc
+	.size	func1_ifunc, .-func1_ifunc
+	.globl	func1
+	.type	func1, @gnu_indirect_function
+	func1 = func1_ifunc
+	.ident	"GCC: (GNU) 4.9.0 20130917 (experimental)"
diff --git a/ld/testsuite/ld-mips-elf/ifunc-dyn-main.s b/ld/testsuite/ld-mips-elf/ifunc-dyn-main.s
new file mode 100644
index 0000000..8c12176
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-dyn-main.s
@@ -0,0 +1,39 @@
+	.file	1 "ifunc_ref_main_2.c"
+	.section .mdebug.abi32
+	.previous
+	.nan	legacy
+	.gnu_attribute 4, 1
+	.abicalls
+	.option	pic0
+	.text
+	.align	2
+	.globl	main
+	.set	nomips16
+	.set	nomicromips
+	.ent	main
+	.type	main, @function
+main:
+	.frame	$fp,32,$31		# vars= 0, regs= 2/0, args= 16, gp= 8
+	.mask	0xc0000000,-4
+	.fmask	0x00000000,0
+	.set	noreorder
+	.set	nomacro
+	addiu	$sp,$sp,-32
+	sw	$31,28($sp)
+	sw	$fp,24($sp)
+	move	$fp,$sp
+	jal	ref1
+	nop
+
+	move	$sp,$fp
+	lw	$31,28($sp)
+	lw	$fp,24($sp)
+	addiu	$sp,$sp,32
+	j	$31
+	nop
+
+	.set	macro
+	.set	reorder
+	.end	main
+	.size	main, .-main
+	.ident	"GCC: (GNU) 4.9.0 20130930 (experimental)"
diff --git a/ld/testsuite/ld-mips-elf/ifunc-dyn-ref.s b/ld/testsuite/ld-mips-elf/ifunc-dyn-ref.s
new file mode 100644
index 0000000..d562096
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-dyn-ref.s
@@ -0,0 +1,43 @@
+	.file	1 "ifunc_ref.c"
+	.section .mdebug.abi32
+	.previous
+	.nan	legacy
+	.gnu_attribute 4, 1
+	.abicalls
+	.text
+	.align	2
+	.globl	ref1
+	.set	nomips16
+	.set	nomicromips
+	.ent	ref1
+	.type	ref1, @function
+ref1:
+	.frame	$fp,32,$31		# vars= 0, regs= 2/0, args= 16, gp= 8
+	.mask	0xc0000000,-4
+	.fmask	0x00000000,0
+	.set	noreorder
+	.cpload	$25
+	.set	nomacro
+	addiu	$sp,$sp,-32
+	sw	$31,28($sp)
+	sw	$fp,24($sp)
+	move	$fp,$sp
+	.cprestore	16
+	lw	$2,%call16(func1)($28)
+	move	$25,$2
+	jalr	$25
+	nop
+
+	lw	$28,16($fp)
+	move	$sp,$fp
+	lw	$31,28($sp)
+	lw	$fp,24($sp)
+	addiu	$sp,$sp,32
+	j	$31
+	nop
+
+	.set	macro
+	.set	reorder
+	.end	ref1
+	.size	ref1, .-ref1
+	.ident	"GCC: (GNU) 4.9.0 20130930 (experimental)"
diff --git a/ld/testsuite/ld-mips-elf/ifunc-dyn.ld b/ld/testsuite/ld-mips-elf/ifunc-dyn.ld
new file mode 100644
index 0000000..c82c8ce
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-dyn.ld
@@ -0,0 +1,25 @@
+ENTRY(_start)
+SECTIONS
+{
+  . = ALIGN (0x400);
+  .text : { *(.text) }
+
+  . = ALIGN (0x400);
+  .iplt : { *(.iplt) }
+
+  . = ALIGN (0x400);
+  .igot : { *(.igot) }
+
+  . = ALIGN (0x400);
+  .got : { *(.got) }
+
+  . = ALIGN (0x400);
+  .data : { *(.data) }
+
+  . = ALIGN (0x1000);
+  .rel.dyn        :
+    {
+      *(.rel.*)
+    }
+
+}
diff --git a/ld/testsuite/ld-mips-elf/ifunc-iplt.ld b/ld/testsuite/ld-mips-elf/ifunc-iplt.ld
new file mode 100644
index 0000000..a26b803
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt.ld
@@ -0,0 +1,26 @@
+ENTRY(_start)
+SECTIONS
+{
+  . = ALIGN (0x400);
+  .iplt : { *(.iplt) }
+
+  . = ALIGN (0x400);
+  .text : { *(.text) }
+
+  . = ALIGN (0x400);
+  .igot : { *(.igot) }
+
+  . = ALIGN (0x400);
+  .got : { *(.got) }
+
+  . = ALIGN (0x400);
+  .data : { *(.data) }
+
+  . = ALIGN (0x1000);
+  .rel.dyn        :
+    {
+      *(.rel.*)
+    }
+/*  /DISCARD/ : { *(*) } */
+
+}
diff --git a/ld/testsuite/ld-mips-elf/ifunc-static-def.s b/ld/testsuite/ld-mips-elf/ifunc-static-def.s
new file mode 100644
index 0000000..cf84696
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-static-def.s
@@ -0,0 +1,155 @@
+	.file	1 "ifunc.c"
+	.section .mdebug.abi32
+	.previous
+	.nan	legacy
+	.gnu_attribute 4, 1
+	.abicalls
+	.option	pic0
+	.text
+	.align	2
+	.set	nomips16
+	.set	nomicromips
+	.ent	f1_a
+	.type	f1_a, @function
+f1_a:
+	.frame	$fp,8,$31		# vars= 0, regs= 1/0, args= 0, gp= 0
+	.mask	0x40000000,-4
+	.fmask	0x00000000,0
+	.set	noreorder
+	.set	nomacro
+	addiu	$sp,$sp,-8
+	sw	$fp,4($sp)
+	move	$fp,$sp
+	li	$2,1			# 0x1
+	move	$sp,$fp
+	lw	$fp,4($sp)
+	addiu	$sp,$sp,8
+	j	$31
+	nop
+
+	.set	macro
+	.set	reorder
+	.end	f1_a
+	.size	f1_a, .-f1_a
+	.align	2
+	.set	nomips16
+	.set	nomicromips
+	.ent	f1_b
+	.type	f1_b, @function
+f1_b:
+	.frame	$fp,8,$31		# vars= 0, regs= 1/0, args= 0, gp= 0
+	.mask	0x40000000,-4
+	.fmask	0x00000000,0
+	.set	noreorder
+	.set	nomacro
+	addiu	$sp,$sp,-8
+	sw	$fp,4($sp)
+	move	$fp,$sp
+	li	$2,2			# 0x2
+	move	$sp,$fp
+	lw	$fp,4($sp)
+	addiu	$sp,$sp,8
+	j	$31
+	nop
+
+	.set	macro
+	.set	reorder
+	.end	f1_b
+	.size	f1_b, .-f1_b
+	.align	2
+	.set	nomips16
+	.set	nomicromips
+	.ent	f1_c
+	.type	f1_c, @function
+f1_c:
+	.frame	$fp,8,$31		# vars= 0, regs= 1/0, args= 0, gp= 0
+	.mask	0x40000000,-4
+	.fmask	0x00000000,0
+	.set	noreorder
+	.set	nomacro
+	addiu	$sp,$sp,-8
+	sw	$fp,4($sp)
+	move	$fp,$sp
+	li	$2,3			# 0x3
+	move	$sp,$fp
+	lw	$fp,4($sp)
+	addiu	$sp,$sp,8
+	j	$31
+	nop
+
+	.set	macro
+	.set	reorder
+	.end	f1_c
+	.size	f1_c, .-f1_c
+	.align	2
+	.globl	func1_ifunc
+	.set	nomips16
+	.set	nomicromips
+	.ent	func1_ifunc
+	.type	func1_ifunc, @function
+func1_ifunc:
+	.frame	$fp,432,$31		# vars= 400, regs= 2/0, args= 16, gp= 8
+	.mask	0xc0000000,-4
+	.fmask	0x00000000,0
+	.set	noreorder
+	.set	nomacro
+	addiu	$sp,$sp,-432
+	sw	$31,428($sp)
+	sw	$fp,424($sp)
+	move	$fp,$sp
+	addiu	$2,$fp,28
+	move	$4,$2
+	nop
+
+	beq	$2,$0,$L8
+	nop
+
+	li	$2,48			# 0x30
+	sw	$2,24($fp)
+	j	$L9
+	nop
+
+$L8:
+	li	$2,3			# 0x3
+	sw	$2,24($fp)
+$L9:
+	lw	$2,24($fp)
+	andi	$2,$2,0xf0
+	beq	$2,$0,$L10
+	nop
+
+	lui	$2,%hi(f1_a)
+	addiu	$2,$2,%lo(f1_a)
+	j	$L13
+	nop
+
+$L10:
+	lw	$2,24($fp)
+	andi	$2,$2,0xf
+	beq	$2,$0,$L12
+	nop
+
+	lui	$2,%hi(f1_b)
+	addiu	$2,$2,%lo(f1_b)
+	j	$L13
+	nop
+
+$L12:
+	lui	$2,%hi(f1_c)
+	addiu	$2,$2,%lo(f1_c)
+$L13:
+	move	$sp,$fp
+	lw	$31,428($sp)
+	lw	$fp,424($sp)
+	addiu	$sp,$sp,432
+	j	$31
+	nop
+
+	.set	macro
+	.set	reorder
+	.end	func1_ifunc
+	.size	func1_ifunc, .-func1_ifunc
+	.globl	func1
+	.type	func1, @gnu_indirect_function
+	func1 = func1_ifunc
+	.ident	"GCC: (GNU) 4.9.0 20130930 (experimental)"
diff --git a/ld/testsuite/ld-mips-elf/ifunc-static-main.s b/ld/testsuite/ld-mips-elf/ifunc-static-main.s
new file mode 100644
index 0000000..2093a42
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-static-main.s
@@ -0,0 +1,39 @@
+	.file	1 "ifunc_ref_main_1.c"
+	.section .mdebug.abi32
+	.previous
+	.nan	legacy
+	.gnu_attribute 4, 1
+	.abicalls
+	.option	pic0
+	.text
+	.align	2
+	.globl	main
+	.set	nomips16
+	.set	nomicromips
+	.ent	main
+	.type	main, @function
+main:
+	.frame	$fp,32,$31		# vars= 0, regs= 2/0, args= 16, gp= 8
+	.mask	0xc0000000,-4
+	.fmask	0x00000000,0
+	.set	noreorder
+	.set	nomacro
+	addiu	$sp,$sp,-32
+	sw	$31,28($sp)
+	sw	$fp,24($sp)
+	move	$fp,$sp
+	jal	func1
+	nop
+
+	move	$sp,$fp
+	lw	$31,28($sp)
+	lw	$fp,24($sp)
+	addiu	$sp,$sp,32
+	j	$31
+	nop
+
+	.set	macro
+	.set	reorder
+	.end	main
+	.size	main, .-main
+	.ident	"GCC: (GNU) 4.9.0 20130930 (experimental)"
diff --git a/ld/testsuite/ld-mips-elf/ifunc-static-ref.s b/ld/testsuite/ld-mips-elf/ifunc-static-ref.s
new file mode 100644
index 0000000..e17db14
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-static-ref.s
@@ -0,0 +1,39 @@
+	.file	1 "ifunc_ref.c"
+	.section .mdebug.abi32
+	.previous
+	.nan	legacy
+	.gnu_attribute 4, 1
+	.abicalls
+	.option	pic0
+	.text
+	.align	2
+	.globl	ref1
+	.set	nomips16
+	.set	nomicromips
+	.ent	ref1
+	.type	ref1, @function
+ref1:
+	.frame	$fp,32,$31		# vars= 0, regs= 2/0, args= 16, gp= 8
+	.mask	0xc0000000,-4
+	.fmask	0x00000000,0
+	.set	noreorder
+	.set	nomacro
+	addiu	$sp,$sp,-32
+	sw	$31,28($sp)
+	sw	$fp,24($sp)
+	move	$fp,$sp
+	jal	func1
+	nop
+
+	move	$sp,$fp
+	lw	$31,28($sp)
+	lw	$fp,24($sp)
+	addiu	$sp,$sp,32
+	j	$31
+	nop
+
+	.set	macro
+	.set	reorder
+	.end	ref1
+	.size	ref1, .-ref1
+	.ident	"GCC: (GNU) 4.9.0 20130930 (experimental)"
diff --git a/ld/testsuite/ld-mips-elf/ifunc-static.ld b/ld/testsuite/ld-mips-elf/ifunc-static.ld
new file mode 100644
index 0000000..ef07ec6
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-static.ld
@@ -0,0 +1,27 @@
+ENTRY(_start)
+SECTIONS
+{
+  . = 0x80000;
+  . = ALIGN (0x400);
+  .iplt : { *(.iplt) }
+
+  . = ALIGN (0x400);
+  .text : { *(.text) }
+
+  . = ALIGN (0x400);
+  .igot : { *(.igot) }
+
+  . = ALIGN (0x400);
+  .got : { *(.got) }
+
+  . = ALIGN (0x400);
+  .data : { *(.data) }
+
+  . = ALIGN (0x1000);
+  .rel.dyn        :
+    {
+      *(.rel.*)
+    }
+/*  /DISCARD/ : { *(*) } */
+
+}
diff --git a/ld/testsuite/ld-mips-elf/libifunc-1-n32.sym b/ld/testsuite/ld-mips-elf/libifunc-1-n32.sym
new file mode 100644
index 0000000..7886d26
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/libifunc-1-n32.sym
@@ -0,0 +1,4 @@
+Symbol table for image:
+  Num Buc:    Value  Size   Type   Bind Vis      Ndx Name
+    2   0: 0000000c    28 FUNC    GLOBAL DEFAULT   1 func1_ifunc
+    3   0: 0000000c    28 IFUNC   GLOBAL DEFAULT   1 func1
diff --git a/ld/testsuite/ld-mips-elf/libifunc-1-n64.sym b/ld/testsuite/ld-mips-elf/libifunc-1-n64.sym
new file mode 100644
index 0000000..192abe3
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/libifunc-1-n64.sym
@@ -0,0 +1,4 @@
+Symbol table for image:
+  Num Buc:    Value          Size   Type   Bind Vis      Ndx Name
+    2   0: 000000000000000c    28 FUNC    GLOBAL DEFAULT   1 func1_ifunc
+    3   0: 000000000000000c    28 IFUNC   GLOBAL DEFAULT   1 func1
diff --git a/ld/testsuite/ld-mips-elf/libifunc-1-o32.sym b/ld/testsuite/ld-mips-elf/libifunc-1-o32.sym
new file mode 100644
index 0000000..f0ff11f
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/libifunc-1-o32.sym
@@ -0,0 +1,4 @@
+Symbol table for image:
+  Num Buc:    Value  Size   Type   Bind Vis      Ndx Name
+    2   0: 0000000c    40 FUNC    GLOBAL DEFAULT   1 func1_ifunc
+    3   0: 0000000c    40 IFUNC   GLOBAL DEFAULT   1 func1
diff --git a/ld/testsuite/ld-mips-elf/libifunc-2-n32.sym b/ld/testsuite/ld-mips-elf/libifunc-2-n32.sym
new file mode 100644
index 0000000..2c1e84e
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/libifunc-2-n32.sym
@@ -0,0 +1,4 @@
+Symbol table for image:
+  Num Buc:    Value  Size   Type   Bind Vis      Ndx Name
+    2   0: 00000000    60 FUNC    GLOBAL DEFAULT   1 ref1
+    3   0: 00000040     0 FUNC    GLOBAL DEFAULT UND func1
diff --git a/ld/testsuite/ld-mips-elf/libifunc-2-n64.sym b/ld/testsuite/ld-mips-elf/libifunc-2-n64.sym
new file mode 100644
index 0000000..3bd53fd
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/libifunc-2-n64.sym
@@ -0,0 +1,4 @@
+Symbol table for image:
+  Num Buc:    Value          Size   Type   Bind Vis      Ndx Name
+    2   0: 0000000000000000    60 FUNC    GLOBAL DEFAULT   1 ref1
+    3   0: 0000000000000040     0 FUNC    GLOBAL DEFAULT UND func1
diff --git a/ld/testsuite/ld-mips-elf/libifunc-2-o32.sym b/ld/testsuite/ld-mips-elf/libifunc-2-o32.sym
new file mode 100644
index 0000000..b831edd
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/libifunc-2-o32.sym
@@ -0,0 +1,5 @@
+Symbol table for image:
+  Num Buc:    Value  Size   Type   Bind Vis      Ndx Name
+    3   0: 00000000    76 FUNC    GLOBAL DEFAULT   1 ref1
+    4   0: 00000050     0 FUNC    GLOBAL DEFAULT UND func1
+    2   0: 00000000     0 SECTION GLOBAL DEFAULT ABS _gp_disp
diff --git a/ld/testsuite/ld-mips-elf/mips-ifunc.exp b/ld/testsuite/ld-mips-elf/mips-ifunc.exp
new file mode 100644
index 0000000..5fe1cb2
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/mips-ifunc.exp
@@ -0,0 +1,130 @@
+# Expect script for MIPS IFUNC linker tests
+#   Copyright 2013
+#   Free Software Foundation, Inc.
+#
+# This file is part of the GNU Binutils.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
+# MA 02110-1301, USA.
+#
+
+if {![istarget mips*-*-*] || ![is_elf_format]} {
+    return
+}
+
+# General setup
+#############################################
+set has_newabi [expr [istarget *-*-irix6*] \
+		     || [istarget mips*-*-linux*] \
+		     || [istarget mips*-sde-elf*]]
+set linux_gnu [expr [istarget mips*-*-linux*]]
+set embedded_elf [expr [istarget mips*-*-elf]]
+
+# Set defaults.
+set abi_asflags(o32) ""
+set abi_asflags(n32) "-march=from-abi -n32 -EB"
+set abi_asflags(n64) "-march=from-abi -64 -EB"
+set abi_ldflags(o32) ""
+set abi_ldflags(n32) -melf32bmipn32
+set abi_ldflags(n64) -melf64bmip
+
+# Override as needed.
+if { [istarget *-*-irix6*] } {
+    set abi_asflags(o32) "-32 -EB"
+    set abi_ldflags(o32) -melf32bsmip
+} elseif { [istarget mips64*-linux*] } {
+    set abi_asflags(o32) "-32 -EB"
+    set abi_ldflags(o32) -melf32btsmip
+} elseif { [istarget mips64*-*freebsd*] } {
+    set abi_asflags(o32) "-32 -EB"
+    set abi_ldflags(o32) -melf32btsmip_fbsd
+}
+if { [istarget mips*-*-linux*] || [istarget mips*-sde-elf*] } {
+    set abi_ldflags(n32) -melf32btsmipn32
+    set abi_ldflags(n64) -melf64btsmip
+} elseif { [istarget mips64*-*freebsd*] } {
+    set abi_ldflags(n32) -melf32btsmipn32_fbsd
+    set abi_ldflags(n64) -melf64btsmip_fbsd
+}
+#############################################
+
+
+# STT_GNU_IFUNC testing:
+#
+#    1. Dso with ifunc defined code
+#    2. Dso that references external ifunc'ed routines
+#    3. Dynamic executable with ifunc defined code
+#    4. Static executable with ifunc defined and referenced code
+#    5. Dso with with ifunc defined and referenced code
+#    6. Dynamic executable with ifunc defined and referenced code
+# STT_GNU_IFUNC tests.
+set abis [concat o32 [expr {$has_newabi ? "n32 n64" : ""}]]
+foreach { abi } $abis {
+    run_ld_link_tests [list \
+	[list \
+	    "IFUNC 1 (Simple dso with def) ${abi}" \
+	    "$abi_ldflags($abi) -shared -T ifunc-dyn.ld" "" \
+	    "$abi_asflags($abi)" \
+	    [list ifunc-dyn-def.s] \
+	    [list "readelf -Ds libifunc-1-${abi}.sym"] \
+	    "libifunc-1-${abi}.so" \
+        ] \
+	[list \
+	    "IFUNC 2 (Simple dso with ref) ${abi}" \
+	    "$abi_ldflags($abi) -shared -T ifunc-dyn.ld" "" \
+	    "$abi_asflags($abi)" \
+	    [list ifunc-dyn-ref.s] \
+	    [list "readelf -Ds libifunc-2-${abi}.sym"] \
+	    "libifunc-2-${abi}.so" \
+        ] \
+	[list \
+	    "IFUNC 3 (Simple dynamic executable with def) ${abi}" \
+	    "$abi_ldflags($abi) -Bdynamic -L./tmpdir -lifunc-2-${abi} -T ifunc-dyn.ld" "" \
+	    "$abi_asflags($abi)" \
+	    [list ifunc-dyn-main.s ifunc-dyn-def.s] \
+	    [list "readelf -Ds ifunc-3-${abi}.sym" \
+                  "readelf -r ifunc-3-${abi}.r" \
+                  "objdump -dj.iplt ifunc-3-${abi}.t"] \
+	    "ifunc-3-${abi}" \
+        ] \
+	[list \
+	    "IFUNC 4 (Simple static executable with def and ref) ${abi}" \
+	    "$abi_ldflags($abi) -Bstatic -T ifunc-static.ld" "" \
+	    "$abi_asflags($abi) -non_shared" \
+	    [list ifunc-static-main.s ifunc-static-def.s ifunc-static-ref.s] \
+	    [list "readelf -r ifunc-4-${abi}.r" \
+                  "objdump -dj.iplt ifunc-4-${abi}.t"] \
+	    "ifunc-4-${abi}" \
+        ] \
+	[list \
+	    "IFUNC 5 (Dynamic shared object with def and ref) ${abi}" \
+	    "$abi_ldflags($abi) -shared -T ifunc-dyn.ld" "" \
+	    "$abi_asflags($abi) -KPIC" \
+	    [list ifunc-dyn-def.s ifunc-dyn-ref.s] \
+	    [list "readelf -Ds ifunc-5-${abi}.sym" \
+                  "readelf -r ifunc-5-${abi}.r"] \
+	    "ifunc-5-${abi}" \
+        ] \
+	[list \
+	    "IFUNC 6 (Dynamic executable with def and ref) ${abi}" \
+	    "$abi_ldflags($abi) -Bdynamic -L./tmpdir -lifunc-2-${abi} -T ifunc-dyn.ld" "" \
+	    "$abi_asflags($abi)" \
+	    [list ifunc-dyn-main.s ifunc-dyn-def.s ifunc-dyn-ref.s] \
+	    [list "readelf -Ds ifunc-6-${abi}.sym" \
+                  "readelf -r ifunc-6-${abi}.r"] \
+	    "ifunc-6-${abi}" \
+        ] \
+    ]
+}
-- 
1.7.9.5


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]