public inbox for binutils@sourceware.org
 help / color / mirror / Atom feed
* [RFC] Add IFUNC support for MIPS
@ 2015-06-18 23:28 Faraz Shahbazker
  2015-06-19 15:46 ` Matthew Fortune
  2015-06-22 20:40 ` Richard Sandiford
  0 siblings, 2 replies; 35+ messages in thread
From: Faraz Shahbazker @ 2015-06-18 23:28 UTC (permalink / raw)
  To: binutils

[-- Attachment #1: Type: text/plain, Size: 62623 bytes --]

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						*/
+};
+
 \f
 /* 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);
-    }
-}
 \f
 /* 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;
+}
 \f
 /* 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;
+}
 \f
 /* 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;
 }
 \f
+/* 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
+
 \f
 /* Flags which may appear in a DT_MIPS_FLAGS entry.  */
 
-- 
1.7.9.5

[-- Attachment #2: Spec.txt --]
[-- Type: text/plain, Size: 6957 bytes --]

====
MIPS IFUNC ABI specification
====

Introduction
----

This document describes the working of GNU indirect functions(IFUNC)
for MIPS.

Terminology
----
Shared file or shared object refers to any object file with e_type
ET_DYN. Unless otherwise distinguished, this includes dynamic shared libraries
(PIC) and position-independent executables (PIE).

Executable file or executable refers to any object file with e_type
ET_EXEC. Unless otherwise distinguished, this includes statically linked and
dynamically linked executables.

Relocations for IFUNC resolution
----

The relocation table may now contain a new relocation type generated by the
static linker:

R_MIPS_IRELATIVE(128)
This relocation marks the location of an IFUNC indirection that needs to be
resolved by the dynamic linker at load time. The resolution step calls the
IFUNC resolver function given at the relocation offset possibly with
additional processor-specific (hardware capability) arguments and writes the
address returned by the resolver back to the relocation offset.

An IRELATIVE fix-up involves execution of the resolver code and it may not be
safely applied until all other fix ups that affect the resolver code, have
been applied. Therefore, these fix-ups are scheduled after all other
relocations and they always occur at the end of the relocation table.

GOT and IGOT sections - the indirection table
----

An IRELATIVE fix-up will target the existing GOT entry of the symbol, except 
in the following cases:

1. Symbols defined in executable files have no GOT entries.
2. Symbols defined in a shared object, but not referenced within that object
have no GOT entries.

If the symbol has no GOT entry, an entry is allocated in the IGOT section
and used by the fix-up. This GOT/IGOT entry is initialized to point to the
resolver function at link time and modified to point to the resolved function
by the IRELATIVE fix-up.

IPLT section - indirection stubs
----

The IFUNC Procedure Linkage Table (IPLT) consists of a set of stubs generated
by the static linker to stand in for IFUNC references that can not be
resolved via a GOT/IGOT entry, for example:

1. Internal or external IFUNC references in an executable file where there is
   no GOT section.
2. External references in shared objects where the resolver function cannot
   be invoked at load time due to the specific order of loading.

Executable files contain non-PIC stubs and shared objects contain PIC-style
stubs. Non-PIC stubs can be called using absolute JAL instruction and then
redirect the call to the actual function via an absolute pointer in to the GOT
(or IGOT) section.  PIC stubs provide the same functionality, but they must be
called using the SVr4 PIC ABI [JALR $25]. The redirection is done via a full
32-bit (or 64-bit) relative offset from $25 in to the GOT (or IGOT)
section. In either case, the actual position of the GOT/IGOT relative to the
IPLT section or $gp is irrelevant.

For executable files, IFUNC calls are always routed through IPLT stubs. Every
IFUNC definition must have a corresponding stub irrespective of its declared
visibility. For shared objects, the dynamic linker must decide whether or not
to route through the stub for each external IFUNC references in each
object. Only externally visible IFUNC definitions needs stubs; no stubs are
generated for definitions with static or non-default (STV_HIDDEN/STV_INTERNAL)
visibility.

Issues specific to shared objects
====

External IFUNC resolution
----
If an external IFUNC reference binds to a resolver within another object which
has not been relocated at the time when the caller object is loaded, it is not
possible to invoke this IFUNC resolver to determine the resolved function. In
this case, the dynamic linker must fall back to binding that reference to the
appropriate IPLT stub. When the callee object is eventually loaded, its GOT
entry would be updated to the resolved function. When the IPLT stub is
executed, it will simply pick the resolved address from GOT and jump to it.

This scheme requires the ability to map the dynamic symbol of an IFUNC to
the address of its IPLT stub. This mapping is achieved by sorting all global
IFUNC symbols to be contiguous within the dynamic symbol table and using a
pair of dynamic tags:
MIPS_IPLT: address of the first IPLT stub
MIPS_IFUNC_INDX: index of the first dynamic IFUNC symbol.

For any dynamic IFUNC symbol, the difference of its index and
MIPS_IFUNC_INDEX, scaled by the size of an IPLT stub and added back to
MIPS_IPLT address yields that symbol's corresponding IPLT stub.

PIC stub size varies with architecture and ISA as follows:
- mips16/micromips32: 16 bytes
- mips32 o32: 20 bytes
- mips64 n32: 20 bytes
- mips64 n64: 36 bytes

** With further optimization, it may be possible to reduce the sizes of
o32/n32 stubs to 16 bytes and n64 stubs to 32 bytes.

dynsym table - dynamic symbol order
----
For mapping unresolvable IFUNC references to IPLT stubs, all IFUNC symbols
must be sorted to appear contiguous within the dynamic symbol table. However,
the order of symbols in the dynamic symbol table also controls the order of
entries in the GOT and that order is defined as:
- local GOT references
- normal global GOT entries
- reloc-only global GOT entries

To remain consistent with the internal order of entries in the GOT, the
dynamic symbol table is sorted in to 2 contiguous IFUNC sequences, so that
order of GOT entries is:
- local non-IFUNC GOT entries
- local IFUNC GOT entries
- normal global IFUNC GOT entries
- normal global non-IFUNC GOT entries
- reloc-only global non-IFUNC GOT entries
- reloc-only global IFUNC GOT entries

In this way, the relative meta-order of the 3 categories of GOT entries is
maintained. The first contiguous sequence of IFUNC symbols is marked by the
tag MIPS_IFUNC_INDX, with MIPS_IPLT pointing to the start address of the
corresponding IPLT stub sequence. The 2nd contiguous sequence is marked by
the tag MIPS_IFUNCREL_INDX, with MIPS_IPLTREL pointing to the start address of
the corresponding stub sequence. The 2nd sequence and its dynamic tags are
only needed in the multi-GOT case, where a single primary GOT is insufficient.

Dynamic Section
----

Dynamic section entries give information to the dynamic linker.  The following new
dynamic table entries are required for IFUNC resolution:

MIPS_IPLT(35)
	Address of the first IPLT stub. This tag is needed for all shared
	objects with global IFUNC symbols.

MIPS_IFUNC_INDX(36)
	Index of the first dynamic IFUNC symbol. This tag is needed for all
	shared objects with global IFUNC symbols.

MIPS_IPLTREL(37)
	Address of the IPLT stub of the first IFUNC symbol with a reloc-only
	GOT entry. This tag is needed for all shared objects with global
	IFUNC symbols and multiple GOTs.

MIPS_IFUNCREL_INDX(38)
	Index of the first dynamic IFUNC symbol with a reloc-only GOT
	entry. This tag is needed for all shared objects with global
	IFUNC symbols and multiple GOTs.

[-- Attachment #3: ifunc_tests.patch --]
[-- Type: text/x-patch, Size: 36317 bytes --]

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


^ permalink raw reply	[flat|nested] 35+ messages in thread

* RE: [RFC] Add IFUNC support for MIPS
  2015-06-18 23:28 [RFC] Add IFUNC support for MIPS Faraz Shahbazker
@ 2015-06-19 15:46 ` Matthew Fortune
  2015-06-19 16:26   ` Faraz Shahbazker
  2015-06-22 20:40 ` Richard Sandiford
  1 sibling, 1 reply; 35+ messages in thread
From: Matthew Fortune @ 2015-06-19 15:46 UTC (permalink / raw)
  To: Faraz Shahbazker, binutils

Faraz Shahbazker <Faraz.Shahbazker@imgtec.com> writes:
> 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.

Hi Faraz,

Please could you leave tag 0x70000035 free for the new debug tag? I
have already used this within Android in the hope that we will settle on
it as the solution for PIE debug. I don't believe there is any IFUNC
implementation in use anywhere yet so moving these tags should be OK.

Thanks,
Matthew

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC] Add IFUNC support for MIPS
  2015-06-19 15:46 ` Matthew Fortune
@ 2015-06-19 16:26   ` Faraz Shahbazker
  0 siblings, 0 replies; 35+ messages in thread
From: Faraz Shahbazker @ 2015-06-19 16:26 UTC (permalink / raw)
  To: Matthew Fortune, binutils

On 06/19/2015 08:46 AM, Matthew Fortune wrote:
> Faraz Shahbazker <Faraz.Shahbazker@imgtec.com> writes:
>> 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.
> 
> Hi Faraz,
> 
> Please could you leave tag 0x70000035 free for the new debug tag? I
> have already used this within Android in the hope that we will settle on
> it as the solution for PIE debug. I don't believe there is any IFUNC
> implementation in use anywhere yet so moving these tags should be OK.

No problem. I will move these tags up in the next review cycle.

Regards,
Faraz Shahbazker

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC] Add IFUNC support for MIPS
  2015-06-18 23:28 [RFC] Add IFUNC support for MIPS Faraz Shahbazker
  2015-06-19 15:46 ` Matthew Fortune
@ 2015-06-22 20:40 ` Richard Sandiford
  2015-06-23  6:10   ` Alan Modra
  2015-06-23 17:54   ` Faraz Shahbazker
  1 sibling, 2 replies; 35+ messages in thread
From: Richard Sandiford @ 2015-06-22 20:40 UTC (permalink / raw)
  To: Faraz Shahbazker; +Cc: binutils

Thanks for the patch.  I don't want to open old wounds (by which I mean
https://sourceware.org/ml/binutils/2013-12/msg00058.html :-)), but why
did you go for this scheme:

> dynsym table - dynamic symbol order
> ----
> For mapping unresolvable IFUNC references to IPLT stubs, all IFUNC symbols
> must be sorted to appear contiguous within the dynamic symbol table. However,
> the order of symbols in the dynamic symbol table also controls the order of
> entries in the GOT and that order is defined as:
> - local GOT references
> - normal global GOT entries
> - reloc-only global GOT entries
>
> To remain consistent with the internal order of entries in the GOT, the
> dynamic symbol table is sorted in to 2 contiguous IFUNC sequences, so that
> order of GOT entries is:
> - local non-IFUNC GOT entries
> - local IFUNC GOT entries
> - normal global IFUNC GOT entries
> - normal global non-IFUNC GOT entries
> - reloc-only global non-IFUNC GOT entries
> - reloc-only global IFUNC GOT entries
>
> In this way, the relative meta-order of the 3 categories of GOT entries is
> maintained. The first contiguous sequence of IFUNC symbols is marked by the
> tag MIPS_IFUNC_INDX, with MIPS_IPLT pointing to the start address of the
> corresponding IPLT stub sequence. The 2nd contiguous sequence is marked by
> the tag MIPS_IFUNCREL_INDX, with MIPS_IPLTREL pointing to the start address of
> the corresponding stub sequence. The 2nd sequence and its dynamic tags are
> only needed in the multi-GOT case, where a single primary GOT is insufficient.

The idea of allowing normal relocations in the GOT (as suggested in the
earlier threads) seems much simpler and could be applied easily to future
extensions.  I'm talking here about the new dynamic tag suggested in:

  https://sourceware.org/ml/binutils/2013-12/msg00061.html

E.g. it may be that in future someone wants to add priorities to ifuncs,
so that the user can control the order in which ifuncs are called.
If we have ifunc relocations being applied explicitly, we can always
sort them as required.  If instead we divide the ifuncs into three groups,
as above, we will always be limited by that classification.

Thanks,
Richard

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC] Add IFUNC support for MIPS
  2015-06-22 20:40 ` Richard Sandiford
@ 2015-06-23  6:10   ` Alan Modra
  2015-06-23 17:54   ` Faraz Shahbazker
  1 sibling, 0 replies; 35+ messages in thread
From: Alan Modra @ 2015-06-23  6:10 UTC (permalink / raw)
  To: Faraz Shahbazker, binutils, rdsandiford

On Mon, Jun 22, 2015 at 09:40:02PM +0100, Richard Sandiford wrote:
> > order of GOT entries is:
> > - local non-IFUNC GOT entries
> > - local IFUNC GOT entries
> > - normal global IFUNC GOT entries
> > - normal global non-IFUNC GOT entries
> > - reloc-only global non-IFUNC GOT entries
> > - reloc-only global IFUNC GOT entries

I recommend playing a little with ifunc resolvers in PIEs and shared
libraries that access a variable, with LD_BIND_NOW=1.  You'll be in a
world of pain if the GOT entry for the variable isn't relocated before
the ifunc.

-- 
Alan Modra
Australia Development Lab, IBM

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC] Add IFUNC support for MIPS
  2015-06-22 20:40 ` Richard Sandiford
  2015-06-23  6:10   ` Alan Modra
@ 2015-06-23 17:54   ` Faraz Shahbazker
  2015-06-23 18:20     ` Richard Sandiford
  1 sibling, 1 reply; 35+ messages in thread
From: Faraz Shahbazker @ 2015-06-23 17:54 UTC (permalink / raw)
  To: binutils, rdsandiford

Hi Richard,

On 06/22/2015 01:40 PM, Richard Sandiford wrote:
> Thanks for the patch.  I don't want to open old wounds (by which I mean
> https://sourceware.org/ml/binutils/2013-12/msg00058.html :-)), but why
> did you go for this scheme:

Although I went through the previous patch-review threads and all of Jack's notes, it seems I missed that particular discussion. Funnily, I came across it almost immediately after posting the patch. I am going through it now and I will rework the patch using some of those ideas. However :-) ...

I should point out that my approach was different. As far as IFUNCs are concerned, the process of GOT relocation is just vanilla re-basing. The actual IFUNC relocations are performed later in the main reloc loop by processing IRELATIVE relocs. That order is not controlled by the GOT order.

>> To remain consistent with the internal order of entries in the GOT, the
>> dynamic symbol table is sorted in to 2 contiguous IFUNC sequences, so that
>> order of GOT entries is:
>> - local non-IFUNC GOT entries
>> - local IFUNC GOT entries
>> - normal global IFUNC GOT entries
>> - normal global non-IFUNC GOT entries
>> - reloc-only global non-IFUNC GOT entries
>> - reloc-only global IFUNC GOT entries

Going through the above discussion, I see problems with this arrangement. I realize also that I haven't described it accurately. Please ignore this for now, I'll work on it some more.

> The idea of allowing normal relocations in the GOT (as suggested in the
> earlier threads) seems much simpler and could be applied easily to future
> extensions.  I'm talking here about the new dynamic tag suggested in:

Sorry that you have to go through this all over again, but I'd like to clarify this one point. Which is preferable?
- Earlier approach: perform IRELATIVE relocs implicitly for symbols in shared objects while performing the GOT relocation.
vs. 
- Current proposed: each IRELATIVE reloc is explicitly processed as a normal relocation, after all GOT relocations and all other relocations.

I tended towards this approach because of a multi-got test-case where the resolver function needs symbols that have REL32 relocations.

Regards,
Faraz Shahbazker

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC] Add IFUNC support for MIPS
  2015-06-23 17:54   ` Faraz Shahbazker
@ 2015-06-23 18:20     ` Richard Sandiford
  2015-06-23 19:13       ` Faraz Shahbazker
  0 siblings, 1 reply; 35+ messages in thread
From: Richard Sandiford @ 2015-06-23 18:20 UTC (permalink / raw)
  To: Faraz Shahbazker; +Cc: binutils

Faraz Shahbazker <faraz.shahbazker@imgtec.com> writes:
> On 06/22/2015 01:40 PM, Richard Sandiford wrote:
>> Thanks for the patch.  I don't want to open old wounds (by which I mean
>> https://sourceware.org/ml/binutils/2013-12/msg00058.html :-)), but why
>> did you go for this scheme:
>
> Although I went through the previous patch-review threads and all of
> Jack's notes, it seems I missed that particular discussion. Funnily, I
> came across it almost immediately after posting the patch. I am going
> through it now and I will rework the patch using some of those
> ideas. However :-) ...
>
> I should point out that my approach was different. As far as IFUNCs are
> concerned, the process of GOT relocation is just vanilla re-basing. The
> actual IFUNC relocations are performed later in the main reloc loop by
> processing IRELATIVE relocs. That order is not controlled by the GOT
> order.
>
>>> To remain consistent with the internal order of entries in the GOT, the
>>> dynamic symbol table is sorted in to 2 contiguous IFUNC sequences, so that
>>> order of GOT entries is:
>>> - local non-IFUNC GOT entries
>>> - local IFUNC GOT entries
>>> - normal global IFUNC GOT entries
>>> - normal global non-IFUNC GOT entries
>>> - reloc-only global non-IFUNC GOT entries
>>> - reloc-only global IFUNC GOT entries
>
> Going through the above discussion, I see problems with this
> arrangement. I realize also that I haven't described it
> accurately. Please ignore this for now, I'll work on it some more.
>
>> The idea of allowing normal relocations in the GOT (as suggested in the
>> earlier threads) seems much simpler and could be applied easily to future
>> extensions.  I'm talking here about the new dynamic tag suggested in:
>
> Sorry that you have to go through this all over again, but I'd like to
> clarify this one point. Which is preferable?
> - Earlier approach: perform IRELATIVE relocs implicitly for symbols in
> shared objects while performing the GOT relocation.
> vs. 
> - Current proposed: each IRELATIVE reloc is explicitly processed as a
> normal relocation, after all GOT relocations and all other relocations.
>
> I tended towards this approach because of a multi-got test-case where
> the resolver function needs symbols that have REL32 relocations.

Yeah, I agree performing them later is better than original implicit
approach.  But I don't think we gain anything by separating ifunc
relocations from other relocations and tying the layout to the original
ABI GOT scheme.

Thanks,
Richard

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC] Add IFUNC support for MIPS
  2015-06-23 18:20     ` Richard Sandiford
@ 2015-06-23 19:13       ` Faraz Shahbazker
  2015-07-21 12:08         ` Faraz Shahbazker
  2015-08-02 13:08         ` [RFC] Add IFUNC support for MIPS Richard Sandiford
  0 siblings, 2 replies; 35+ messages in thread
From: Faraz Shahbazker @ 2015-06-23 19:13 UTC (permalink / raw)
  To: binutils, rdsandiford

On 06/23/2015 11:19 AM, Richard Sandiford wrote:
>> Sorry that you have to go through this all over again, but I'd like to
>> clarify this one point. Which is preferable?
>> - Earlier approach: perform IRELATIVE relocs implicitly for symbols in
>> shared objects while performing the GOT relocation.
>> vs. 
>> - Current proposed: each IRELATIVE reloc is explicitly processed as a
>> normal relocation, after all GOT relocations and all other relocations.
>>
>> I tended towards this approach because of a multi-got test-case where
>> the resolver function needs symbols that have REL32 relocations.
> 
> Yeah, I agree performing them later is better than original implicit
> approach.  But I don't think we gain anything by separating ifunc
> relocations from other relocations and tying the layout to the original
> ABI GOT scheme.
Agreed.

A chunk of the patch tries to address the link-order problem, namely:
If an external reference binds to an IFUNC resolver within another object which
has not been relocated at the time when the referring object is loaded, it is not
possible to invoke this IFUNC resolver to determine the resolved function.

Is this problem worth solving or should I leave it alone?

Regards,
Faraz Shahbazker

^ permalink raw reply	[flat|nested] 35+ messages in thread

* RE: [RFC] Add IFUNC support for MIPS
  2015-06-23 19:13       ` Faraz Shahbazker
@ 2015-07-21 12:08         ` Faraz Shahbazker
  2015-07-23 21:44           ` Richard Sandiford
  2015-08-02 13:08         ` [RFC] Add IFUNC support for MIPS Richard Sandiford
  1 sibling, 1 reply; 35+ messages in thread
From: Faraz Shahbazker @ 2015-07-21 12:08 UTC (permalink / raw)
  To: binutils

[-- Attachment #1: Type: text/plain, Size: 58671 bytes --]

Hi,

I have revised the patch with more suggestions from the previous discussions. 
Detailed description is in the attached text file. I think I have got a better handle on
symbol preemption with this version, but I would really appreciate some input on that
aspect.

Previous discussions for reference:
https://www.sourceware.org/ml/binutils/2013-08/msg00121.html
https://www.sourceware.org/ml/binutils/2013-11/msg00155.html
https://sourceware.org/ml/binutils/2013-12/msg00058.html

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.

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_got_info): New fields general_gotno and
	assigned_general_gotno.
	(struct mips_elf_link_hash_entry): New fields for offset in to
	IPLT/IGOT and flags to indicate symbol needs IPLT or IRELOC.
	(mips_elf_link_hash_table): New fields for size of IPLT stubs
	and hash-table for local IFUNC symbols.
	(MIPS16_P): New macro to check ASE flag.
	(mips16_exec_iplt_entry): Template for mips16 IPLT stub.
	(mips32_exec_iplt_entry): Template for mips32 IPLT stub.
	(mips32r6_exec_iplt_entry): Template for mips32 R6 IPLT stub.
	(micromips32_exec_iplt_entry): Template for micromips32 stub.
	(mips64_exec_iplt_entry): Template for mips64 IPLT stub.
	(mips64_48b_exec_iplt_entry): Template for 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_get_irel_section): New function.
	(mips_elf_allocate_ireloc): Likewise.
	(mips_elf_allocate_iplt): Likewise.
	(mips_elf_check_local_symbols): Likewise.
	(mips_elf_check_symbols): Allocate an IPLT entry and/or dynamic
	relocation for IFUNC symbols.
	(mips_elf_count_got_entry): Count local IFUNCs under general
	GOT entries instead of local.
	(mips_elf_got16_entry): Add argument h and pass it to
	mips_elf_create_local_got_entry instead of NULL.
	(mips_elf_create_local_got_entry): Add check for sufficient general
	GOT entries. Assign local IFUNCs from general GOT entries pool.
	(mips_elf_count_general_got_symbols): New function.
	(mips_elf_deallocate_irelocs): Likewise.
	(mips_elf_multi_got): Traverse primary GOT entries and deallocate
	previously allocated IRELOCs for symbols marked RELOC_ONLY.
	(mips_elf_create_ifunc_sections): New function.
	(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 symbols that need IPLT.
	(mips_elf_create_dynamic_relocation): Emit IRELATIVE relocation
	instead of REL32 for local IFUNC reference.
        (_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 need to create IFUNC sections.
	If symbol is an IFUNC, don't convert it to an STT_FUNC. Relax error
	checking to 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 general GOT entries. Offset local entries
	to follow general entries.
	(_bfd_mips_elf_size_dynamic_sections): Exclude IPLT and IGOT.
	Create dynamic tag for DT_MIPS_GENERAL_GOTNO if needed.
	(_bfd_mips_elf_relocate_section): Relax error checking to allow
	local IFUNCs to be accessed via standalone got16 reloc.
	(mips_elf_create_iplt): New function.
	(misp_elf_check_local_got_index): Likewise.
	(mips_elf_create_ireloc): Likewise.
	(_bfd_mips_elf_finish_dynamic_symbol): Create IPLT stub/IREL
	relocation for IFUNC symbols as necessary. Set IFUNC symbol value
	to the IPLT entry address for executable 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 tag - DT_MIPS_GENERAL_GOTNO.
	(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 tag -
	DT_MIPS_GENERAL_GOTNO.
	* 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:
	(get_mips_dynamic_type): Add case for DT_MIPS_GENERAL_GOTNO.
	(dynamic_section_mips_val): Add case for DT_MIPS_GENERAL_GOTNO.

elfcpp/ChangeLog:

	* elfcpp.h
	(enum DT): Add cases for dynamic tag DT_MIPS_GENERAL_GOTNO.

include/elf/ChangeLog:

	* mips.h
	(START_RELOC_NUMBERS): Entry for R_MIPS_IRELATIVE.
	(DT_MIPS_GENERAL_GOTNO): New dynamic tag.
---
 bfd/bfd-in2.h      |    3 +
 bfd/elf32-mips.c   |   22 ++
 bfd/elfxx-mips.c   |  991 ++++++++++++++++++++++++++++++++++++++++++++++++----
 bfd/libbfd.h       |    1 +
 bfd/reloc.c        |    5 +
 binutils/readelf.c |    3 +
 elfcpp/elfcpp.h    |    2 +
 include/elf/mips.h |    7 +
 8 files changed, 964 insertions(+), 70 deletions(-)

diff --git a/bfd/bfd-in2.h b/bfd/bfd-in2.h
index c27db8d..7eec1b5 100644
--- a/bfd/bfd-in2.h
+++ b/bfd/bfd-in2.h
@@ -3038,6 +3038,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 9932453..521fcd6 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 {
@@ -165,10 +166,14 @@ struct mips_got_info
   unsigned int tls_assigned_gotno;
   /* The number of local .got entries, eventually including page entries.  */
   unsigned int local_gotno;
+  /* The number of explicitly relocated .got entries.  */
+  unsigned int general_gotno;
   /* The maximum number of page entries needed.  */
   unsigned int page_gotno;
   /* The number of relocations needed for the GOT entries.  */
   unsigned int relocs;
+  /* The first unused general .got entry.  */
+  unsigned int assigned_general_gotno;
   /* The first unused local .got entry.  */
   unsigned int assigned_low_gotno;
   /* The last unused local .got entry.  */
@@ -375,6 +380,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 +424,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_ireloc : 1;
 };
 
 /* MIPS ELF linker hash table.  */
@@ -485,6 +502,9 @@ 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;
+
   /* The number of functions that need a lazy-binding stub.  */
   bfd_vma lazy_stub_count;
 
@@ -516,6 +536,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 +824,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 +1219,69 @@ 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 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					*/
+};
+
+/* Format of 32 bit IPLT entries for R6. JR encoding differs.  */
+static const bfd_vma mips32r6_exec_iplt_entry[] =
+{
+  0x3c0f0000,   /* lui $15, %hi(.igot address)		*/
+  0x8df90000,   /* lw  $25, %lo(.igot address)($15)	*/
+  0x03200009,   /* 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 64-bit IPLT entries.  */
+static const bfd_vma mips64_exec_iplt_entry[] =
+{
+  0x3c0f0000,	/* lui $15, %highest(.got.iplt entry)        */
+  0x3c0e0000,	/* lui $14, %hi(.got.iplt entry)             */
+  0x25ef0000,	/* addiu $15, $15, %higher(.got.iplt entry)  */
+  0x000f783c,	/* dsll32 $15, $15, 0x0                      */
+  0x01ee782d,	/* daddu $15, $15, $14                       */
+  0xddf90000,	/* ld $25, %lo(.got.iplt entry)($15)         */
+  0x03200008,	/* jr $25                                    */
+  0x00000000,	/* nop                                       */
+};
+
+static const bfd_vma mips64_48b_exec_iplt_entry[] =
+{
+  0x3c0f0000,	/* lui $15, %higher(.got.iplt entry)         */
+  0x25ef0000,	/* addiu $15, $15, %high(.got.iplt entry)    */
+  0x000f7c38,	/* dsll $15, $15, 16  			     */
+  0xddf90000,	/* ld $25, %lo(.got.iplt entry)($15)         */
+  0x03200008,	/* jr $25                                    */
+  0x00000000,	/* nop                                       */
+};
+
 ?
 /* microMIPS 32-bit opcode helper installer.  */
 
@@ -1289,6 +1380,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_ireloc = FALSE;
+      ret->iplt_offset = -1;
+      ret->igot_offset = 0;
     }
 
   return (struct bfd_hash_entry *) ret;
@@ -1588,8 +1683,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 +1700,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 +2066,137 @@ 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;
+}
+
+/* Reserve space in the rel.iplt section for IRELATIVE relocation.  */
+
+static bfd_boolean
+mips_elf_allocate_ireloc (struct bfd_link_info *info,
+			  struct mips_elf_link_hash_table *mhtab,
+			  struct mips_elf_link_hash_entry *mh)
+{
+  asection *srel;
+  bfd *dynobj;
+
+  if (mh->needs_iplt || mh->global_got_area == GGA_NORMAL
+      || mh->root.forced_local)
+    {
+      srel = mips_get_irel_section (info, mhtab);
+      dynobj = elf_hash_table (info)->dynobj;
+      if (srel != mhtab->root.irelplt && srel->size == 0)
+	{
+	  /* Make room for a null element.  */
+	  srel->size += MIPS_ELF_REL_SIZE (dynobj);
+	  ++srel->reloc_count;
+	}
+      srel->size += MIPS_ELF_REL_SIZE (dynobj);
+      mh->needs_ireloc = TRUE;
+    }
+
+  return TRUE;
+}
+
+/* Reserve space in the iplt and igot tables for an ifunc entry
+and allocate space for an IRELATIVE relocation.  */
+
+static bfd_boolean
+mips_elf_allocate_iplt (struct bfd_link_info *info,
+			struct mips_elf_link_hash_table *mhtab,
+			struct mips_elf_link_hash_entry *mh)
+{
+  asection *s;
+  bfd *abfd = info->output_bfd;
+
+  BFD_ASSERT (!mh->needs_iplt);
+  BFD_ASSERT (mhtab->root.iplt != NULL);
+
+  s = mhtab->root.iplt;
+
+  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;
+
+  BFD_ASSERT (mhtab->root.igotplt != NULL);
+  mh->igot_offset = mhtab->root.igotplt->size;
+  mhtab->root.igotplt->size += MIPS_ELF_GOT_SIZE (abfd);
+  mh->needs_iplt = TRUE;
+
+  return TRUE;
+}
+
+/* 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 only for executable objects.  */
+      if (!info->shared &&
+	  !mips_elf_allocate_iplt (info, mips_elf_hash_table (info), h))
+	return FALSE;
+
+      /* IRELATIVE fixup will be needed for each local IFUNC.  */
+      if (!mips_elf_allocate_ireloc (info, mips_elf_hash_table (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 +2209,19 @@ 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;
+      /* .iplt entry is needed only for executable objects.  */
+      if (!info->shared
+	  && !mips_elf_allocate_iplt (info, mips_elf_hash_table (info), h))
+	return FALSE;
+      /* Allocate an extra relocation for this IFUNC, if needed.  */
+      if (!mips_elf_allocate_ireloc (info, mips_elf_hash_table (info), h))
+	return FALSE;
+    }
+
   if (mips_elf_local_pic_function_p (h))
     {
       /* PR 12845: If H is in a section that has been garbage
@@ -3145,37 +3389,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
@@ -3270,7 +3483,14 @@ mips_elf_count_got_entry (struct bfd_link_info *info,
 					? &entry->d.h->root : NULL);
     }
   else if (entry->symndx >= 0 || entry->d.h->global_got_area == GGA_NONE)
-    g->local_gotno += 1;
+    {
+      /* Count IFUNCs as general GOT entries since they will need
+	 explicit IRELATIVE relocations.  */
+      if (entry->symndx < 0 && entry->d.h->root.type == STT_GNU_IFUNC)
+	g->general_gotno += 1;
+      else
+	g->local_gotno += 1;
+    }
   else
     g->global_gotno += 1;
 }
@@ -3600,7 +3820,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 +3836,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
@@ -3715,7 +3936,8 @@ mips_elf_create_local_got_entry (bfd *abfd, struct bfd_link_info *info,
   if (entry)
     return entry;
 
-  if (g->assigned_low_gotno > g->assigned_high_gotno)
+  if (g->assigned_low_gotno > g->assigned_high_gotno ||
+      g->assigned_general_gotno > g->assigned_low_gotno)
     {
       /* We didn't allocate enough space in the GOT.  */
       (*_bfd_error_handler)
@@ -3728,7 +3950,11 @@ mips_elf_create_local_got_entry (bfd *abfd, struct bfd_link_info *info,
   if (!entry)
     return NULL;
 
-  if (got16_reloc_p (r_type)
+  if (h && h->root.type == STT_GNU_IFUNC)
+    /* Allocate IFUNC slots in the general GOT region since they
+       will need explicit IRELATIVE relocations.  */
+    lookup.gotidx = MIPS_ELF_GOT_SIZE (abfd) * g->assigned_general_gotno++;
+  else if (got16_reloc_p (r_type)
       || call16_reloc_p (r_type)
       || got_page_reloc_p (r_type)
       || got_disp_reloc_p (r_type))
@@ -4460,6 +4686,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 GOT entries that need
+   explicit relocations by iterating over the local hash table.  */
+
+static int
+mips_elf_count_general_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;
+
+  info = (struct bfd_link_info *) inf;
+  htab = mips_elf_hash_table (info);
+
+  g = htab->got_info;
+  if (h != NULL && h->root.type == STT_GNU_IFUNC &&
+      (h->root.forced_local || h->global_got_area == GGA_NONE))
+    g->general_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.  */
@@ -4698,6 +4947,31 @@ mips_elf_set_global_got_area (void **entryp, void *data)
   return 1;
 }
 
+/* A htab_traverse callback for GOT entries, where DATA points to a
+   mips_elf_traverse_got_arg. Deallocate dedicated REL32 relocations
+   for global IFUNCs that are marked RELOC_ONLY.  */
+
+static int
+mips_elf_deallocate_irelocs (void **entryp, void *data)
+{
+  struct mips_got_entry *entry;
+
+  entry = (struct mips_got_entry *) *entryp;
+  if (entry->abfd != NULL
+      && entry->symndx == -1
+      && entry->d.h->global_got_area == GGA_RELOC_ONLY
+      && entry->d.h->root.type == STT_GNU_IFUNC
+      && entry->d.h->needs_ireloc)
+    {
+      struct mips_elf_traverse_got_arg *arg
+	= (struct mips_elf_traverse_got_arg *) data;
+      asection *srel = mips_elf_rel_dyn_section (arg->info, FALSE);
+      srel->size -= MIPS_ELF_REL_SIZE (elf_hash_table (arg->info)->dynobj);
+      entry->d.h->needs_ireloc = FALSE;
+    }
+  return 1;
+}
+
 /* A htab_traverse callback for secondary GOT entries, where DATA points
    to a mips_elf_traverse_got_arg.  Assign GOT indices to global entries
    and record the number of relocations they require.  DATA->value is
@@ -4852,6 +5126,11 @@ mips_elf_multi_got (bfd *abfd, struct bfd_link_info *info,
   tga.value = GGA_NORMAL;
   htab_traverse (g->got_entries, mips_elf_set_global_got_area, &tga);
 
+  /* Deallocate extra relocation intended for IFUNCs that have been marked
+     as RELOC_ONLY. These entries will get explicit relocs due to multi-got
+     which will work just as well for IFUNC resolution.  */
+  htab_traverse (gg->got_entries, mips_elf_deallocate_irelocs, &tga);
+
   /* Now go through the GOTs assigning them offset ranges.
      [assigned_low_gotno, local_gotno[ will be set to the range of local
      entries in each GOT.  We can then compute the end of a GOT by
@@ -5087,6 +5366,64 @@ 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_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 +5540,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 +5658,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 +5740,13 @@ 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 +5971,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)
+    {
+      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 +6366,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
@@ -6448,31 +6876,41 @@ mips_elf_create_dynamic_relocation (bfd *output_bfd,
   if (defined_p && r_type != R_MIPS_REL32)
     *addendp += symbol;
 
-  if (htab->is_vxworks)
-    /* VxWorks uses non-relative relocations for this.  */
-    outrel[0].r_info = ELF32_R_INFO (indx, R_MIPS_32);
+  if (h && !indx && h->root.type == STT_GNU_IFUNC)
+    {
+      outrel[0].r_info = ELF_R_INFO (output_bfd, (unsigned long) indx,
+				     R_MIPS_IRELATIVE);
+      outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
+				     R_MIPS_NONE);
+    }
   else
-    /* The relocation is always an REL32 relocation because we don't
-       know where the shared library will wind up at load-time.  */
-    outrel[0].r_info = ELF_R_INFO (output_bfd, (unsigned long) indx,
-				   R_MIPS_REL32);
-
-  /* For strict adherence to the ABI specification, we should
-     generate a R_MIPS_64 relocation record by itself before the
-     _REL32/_64 record as well, such that the addend is read in as
-     a 64-bit value (REL32 is a 32-bit relocation, after all).
-     However, since none of the existing ELF64 MIPS dynamic
-     loaders seems to care, we don't waste space with these
-     artificial relocations.  If this turns out to not be true,
-     mips_elf_allocate_dynamic_relocation() should be tweaked so
-     as to make room for a pair of dynamic relocations per
-     invocation if ABI_64_P, and here we should generate an
-     additional relocation record with R_MIPS_64 by itself for a
-     NULL symbol before this relocation record.  */
-  outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
-				 ABI_64_P (output_bfd)
-				 ? R_MIPS_64
-				 : R_MIPS_NONE);
+    {
+      if (htab->is_vxworks)
+	/* VxWorks uses non-relative relocations for this.  */
+	outrel[0].r_info = ELF32_R_INFO (indx, R_MIPS_32);
+      else
+	/* The relocation is always an REL32 relocation because we don't
+	   know where the shared library will wind up at load-time.  */
+	outrel[0].r_info = ELF_R_INFO (output_bfd, (unsigned long) indx,
+				       R_MIPS_REL32);
+
+      /* For strict adherence to the ABI specification, we should
+	 generate a R_MIPS_64 relocation record by itself before the
+	 _REL32/_64 record as well, such that the addend is read in as
+	 a 64-bit value (REL32 is a 32-bit relocation, after all).
+	 However, since none of the existing ELF64 MIPS dynamic
+	 loaders seems to care, we don't waste space with these
+	 artificial relocations.  If this turns out to not be true,
+	 mips_elf_allocate_dynamic_relocation() should be tweaked so
+	 as to make room for a pair of dynamic relocations per
+	 invocation if ABI_64_P, and here we should generate an
+	 additional relocation record with R_MIPS_64 by itself for a
+	 NULL symbol before this relocation record.  */
+      outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
+				     ABI_64_P (output_bfd)
+				     ? R_MIPS_64
+				     : R_MIPS_NONE);
+    }
   outrel[2].r_info = ELF_R_INFO (output_bfd, 0, R_MIPS_NONE);
 
   /* Adjust the output offset of the relocation to reference the
@@ -7019,6 +7457,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 +7821,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 +8375,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 +8643,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;
+
+	  /* Relocation is for local STT_GNU_IFUNC symbol.  */
+	  if (isym->st_info == STT_GNU_IFUNC)
+	    {
+	      /* Ensure that we have a hash entry for this symbol.  */
+	      if (get_local_sym_hash (htab, abfd, rel) == NULL)
+		return FALSE;
+	      local_gnu_ifunc_p = TRUE;
+	    }
+
+	  h = NULL;
+       }
       else if (r_symndx >= extsymoff + NUM_SHDR_ENTRIES (symtab_hdr))
 	{
 	  (*_bfd_error_handler)
@@ -8410,7 +8878,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 +8904,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 +9447,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 +9726,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,9 +9775,17 @@ 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);
 
+  /* Count local IFUNC symbols. These need general GOT entries that
+     are fixed-up by explicit IRELATIVE relocations.  */
+  htab_traverse (htab->loc_hash_table, mips_elf_count_general_got_symbols, info);
+
   if (!mips_elf_resolve_final_got_entries (info, g))
     return FALSE;
 
+  g->assigned_general_gotno = htab->reserved_gotno;
+  g->local_gotno += g->general_gotno;
+  g->assigned_low_gotno += g->general_gotno;
+
   /* Calculate the total loadable size of the output.  That
      will give us the maximum number of GOT_PAGE entries
      required.  */
@@ -9742,6 +10227,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)
 	{
@@ -9887,6 +10374,13 @@ _bfd_mips_elf_size_dynamic_sections (bfd *output_bfd,
 	  if (! MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_PLTGOT, 0))
 	    return FALSE;
 	}
+
+      if (htab->got_info->general_gotno > 0)
+	{
+	  if (! MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_GENERAL_GOTNO, 0))
+	    return FALSE;
+	}
+
       if (htab->is_vxworks
 	  && !elf_vxworks_add_dynamic_entries (output_bfd, info))
 	return FALSE;
@@ -10018,6 +10512,7 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
       Elf_Internal_Shdr *symtab_hdr;
       struct elf_link_hash_entry *h;
       bfd_boolean rel_reloc;
+      bfd_boolean local_gnu_ifunc_p = FALSE;
 
       rel_reloc = (NEWABI_P (input_bfd)
 		   && mips_elf_rel_relocation_p (input_bfd, input_section,
@@ -10031,6 +10526,23 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
 	{
 	  sec = local_sections[r_symndx];
 	  h = NULL;
+
+	  Elf_Internal_Sym *isym;
+	  struct mips_elf_link_hash_table *htab;
+	  htab = mips_elf_hash_table (info);
+	  isym = bfd_sym_from_r_symndx (&htab->sym_cache, input_bfd, r_symndx);
+
+	  if (isym == NULL)
+	    return FALSE;
+
+	  /* Relocation is for local STT_GNU_IFUNC symbol.  */
+	  if (isym->st_info == STT_GNU_IFUNC)
+	    {
+	      /* Ensure that we have a hash entry for this symbol.  */
+	      if (get_local_sym_hash (htab, input_bfd, rel) == NULL)
+		return FALSE;
+	      local_gnu_ifunc_p = TRUE;
+	    }
 	}
       else
 	{
@@ -10091,8 +10603,9 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
 		      && mips_elf_local_relocation_p (input_bfd, rel,
 						      local_sections)))
 		{
-		  if (!mips_elf_add_lo16_rel_addend (input_bfd, rel, relend,
-						     contents, &addend))
+		  if (!local_gnu_ifunc_p
+		      && !mips_elf_add_lo16_rel_addend (input_bfd, rel, relend,
+							contents, &addend))
 		    {
 		      if (h)
 			name = h->root.root.string;
@@ -10479,6 +10992,237 @@ 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,
+		      bfd_vma igotplt_address)
+{
+  bfd_byte *loc;
+  const bfd_vma *iplt_entry;
+  bfd_vma high = mips_elf_high (igotplt_address);
+  bfd_vma low = igotplt_address & 0xffff;
+
+  /* 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 (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;
+
+      if (highest)
+	{
+	  /* Full 64-bit address space */
+	  bfd_put_32 (output_bfd, iplt_entry[0] | highest, loc);
+	  bfd_put_32 (output_bfd, iplt_entry[1] | high, loc + 4);
+	  bfd_put_32 (output_bfd, iplt_entry[2] | higher, loc + 8);
+	  bfd_put_32 (output_bfd, iplt_entry[3], 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 (higher)
+	{
+	  /* 48-bit address space */
+	  iplt_entry = mips64_48b_exec_iplt_entry;
+	  bfd_put_32 (output_bfd, iplt_entry[0] | higher, loc);
+	  bfd_put_32 (output_bfd, iplt_entry[1] | high, loc + 4);
+	  bfd_put_32 (output_bfd, iplt_entry[2], loc + 8);
+	  bfd_put_32 (output_bfd, iplt_entry[3] | low, loc + 12);
+	  bfd_put_32 (output_bfd, iplt_entry[4], loc + 16);
+	  bfd_put_32 (output_bfd, iplt_entry[5], loc + 20);
+	}
+      else
+	{
+	  /* 32-bit address space */
+	  bfd_put_32 (output_bfd, iplt_entry[0] | high, loc);
+	  bfd_put_32 (output_bfd, iplt_entry[5] | low, loc + 4);
+	  bfd_put_32 (output_bfd, iplt_entry[6], loc + 8);
+	  bfd_put_32 (output_bfd, iplt_entry[7], loc + 12);
+	}
+    }
+  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
+    {
+      if (MIPSR6_P(output_bfd))
+	iplt_entry = mips32r6_exec_iplt_entry;
+      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;
+}
+
+/* Find local GOT index for VALUE. Return -1 if no GOT slot is found.  */
+
+static bfd_vma
+mips_elf_check_local_got_index (bfd *abfd, struct bfd_link_info *info,
+				 bfd_vma value)
+{
+  struct mips_got_entry lookup, *entry;
+  void **loc;
+  struct mips_got_info *g;
+  struct mips_elf_link_hash_table *htab;
+
+  htab = mips_elf_hash_table (info);
+  BFD_ASSERT (htab != NULL);
+
+  g = mips_elf_bfd_got (abfd, FALSE);
+  BFD_ASSERT (g != NULL);
+
+  lookup.abfd = NULL;
+  lookup.symndx = -1;
+  lookup.d.address = value;
+  lookup.tls_type = GOT_TLS_NONE;
+  loc = htab_find_slot (g->got_entries, &lookup, NO_INSERT);
+  if (!loc)
+    return -1;
+
+  entry = (struct mips_got_entry *) *loc;
+  if (entry)
+    return entry->gotidx;
+  else
+    return -1;
+}
+
+/* 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_vma igotplt_address = 0;
+  int igot_offset = -1;
+  asection *gotsect, *relsect;
+
+  if (!hmips->needs_iplt)
+    {
+      gotsect = htab->sgot;
+      if (hmips->global_got_area == GGA_NORMAL)
+	  igot_offset = mips_elf_primary_global_got_index (output_bfd,
+							   info, &hmips->root);
+      else if (hmips->global_got_area == GGA_NONE)
+	  igot_offset = mips_elf_check_local_got_index (output_bfd, info,
+							sym->st_value);
+    }
+  else
+    {
+      bfd_byte *loc;
+      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 (igot_offset >= 0)
+    {
+      if (hmips->needs_iplt && relsect->contents == NULL)
+	{
+	  /* Allocate memory for the relocation section contents.  */
+	  relsect->contents = bfd_zalloc (dynobj, relsect->size);
+	  if (relsect->contents == NULL)
+	    return FALSE;
+	}
+
+      if (hmips->needs_iplt || hmips->global_got_area == GGA_NONE)
+	/* Emit an R_MIPS_IRELATIVE relocation against the [I]GOT entry.  */
+	mips_elf_output_dynamic_relocation (output_bfd, relsect,
+					    relsect->reloc_count++, 0,
+					    R_MIPS_IRELATIVE, igotplt_address);
+      else if (hmips->global_got_area == GGA_NORMAL)
+	{
+	  /* Emit an R_MIPS_REL32 relocation against global GOT entry for
+	     a preemptible symbol.  */
+	  asection *sec = hmips->root.root.u.def.section;
+	  Elf_Internal_Rela rel[3];
+
+	  memset (rel, 0, sizeof (rel));
+	  rel[0].r_info = ELF_R_INFO (output_bfd, 0, R_MIPS_REL32);
+	  rel[0].r_offset = rel[1].r_offset = rel[2].r_offset = igot_offset;
+
+	  mips_elf_create_dynamic_relocation (output_bfd, info, rel, hmips,
+					      sec, sym->st_value, NULL,
+					      gotsect);
+	}
+    }
+  /* If necessary, generate the corresponding .iplt entry.  */
+  if (hmips->needs_iplt
+      && !mips_elf_create_iplt (output_bfd, htab, hmips, igotplt_address))
+    return FALSE;
+
+  return TRUE;
+}
+
 /* Finish up dynamic symbol handling.  We set the contents of various
    dynamic sections here.  */
 
@@ -10807,6 +11551,19 @@ _bfd_mips_elf_finish_dynamic_symbol (bfd *output_bfd,
       sym->st_other = other;
     }
 
+  if (hmips->root.type == STT_GNU_IFUNC)
+    {
+      if (hmips->needs_ireloc
+	  && !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.  */
@@ -10970,9 +11727,37 @@ _bfd_mips_elf_finish_dynamic_symbol (bfd *output_bfd,
       sym->st_other -= STO_MICROMIPS;
     }
 
+  if (hmips->needs_iplt)
+    {
+      /* 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;
+  isym.st_other = h->root.other;
+
+  return _bfd_mips_elf_finish_dynamic_symbol (info->output_bfd, info,
+					      &h->root, &isym);
+}
+
 /* Likewise, for VxWorks.  */
 
 bfd_boolean
@@ -11358,6 +12143,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;
@@ -11528,6 +12320,10 @@ _bfd_mips_elf_finish_dynamic_sections (bfd *output_bfd,
 	      dyn.d_un.d_ptr = s->vma;
 	      break;
 
+	    case DT_MIPS_GENERAL_GOTNO:
+	      dyn.d_un.d_val = htab->reserved_gotno + gg->general_gotno;
+	      break;
+
 	    case DT_RELASZ:
 	      BFD_ASSERT (htab->is_vxworks);
 	      /* The count does not include the JUMP_SLOT relocations.  */
@@ -13862,6 +14658,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 *
@@ -13885,6 +14723,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;
 }
 
@@ -15509,7 +16358,9 @@ _bfd_mips_elf_get_target_dtag (bfd_vma dtag)
       return "DT_MIPS_PLTGOT";
     case DT_MIPS_RWPLT:
       return "DT_MIPS_RWPLT";
-    }
+    case DT_MIPS_GENERAL_GOTNO:
+      return "DT_MIPS_GENERAL_GOTNO";
+   }
 }
 
 /* Return the meaning of Tag_GNU_MIPS_ABI_FP value FP, or null if
diff --git a/bfd/libbfd.h b/bfd/libbfd.h
index 18e3c40..2d0742d 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 3478006..18ed890 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 c313db4..7f97758 100644
--- a/binutils/readelf.c
+++ b/binutils/readelf.c
@@ -1724,6 +1724,8 @@ 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_GENERAL_GOTNO: return "MIPS_GENERAL_GOTNO";
+
     default:
       return NULL;
     }
@@ -8514,6 +8516,7 @@ dynamic_section_mips_val (Elf_Internal_Dyn * entry)
     case DT_MIPS_DELTA_SYM_NO:
     case DT_MIPS_DELTA_CLASSSYM_NO:
     case DT_MIPS_COMPACT_SIZE:
+    case DT_MIPS_GENERAL_GOTNO:
       print_vma (entry->d_un.d_ptr, DEC);
       break;
 
diff --git a/elfcpp/elfcpp.h b/elfcpp/elfcpp.h
index 722984e..f919fb8 100644
--- a/elfcpp/elfcpp.h
+++ b/elfcpp/elfcpp.h
@@ -866,6 +866,8 @@ enum DT
   DT_MIPS_PLTGOT = 0x70000032,
   // Points to the base of a writable PLT.
   DT_MIPS_RWPLT = 0x70000034,
+  // The GOT index of the first implicitly relocated GOT entry.
+  DT_MIPS_GENERAL_GOTNO = 0x70000036,
 
   DT_AUXILIARY = 0x7ffffffd,
   DT_USED = 0x7ffffffe,
diff --git a/include/elf/mips.h b/include/elf/mips.h
index 57de3bc..4005047 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)
@@ -751,6 +754,10 @@ extern void bfd_mips_elf32_swap_reginfo_out
 
 /* Relative offset of run time loader map, used for debugging.  */
 #define DT_MIPS_RLD_MAP_REL    0x70000035
+
+/* The GOT index of the first implicitly relocated GOT entry.  */
+#define DT_MIPS_GENERAL_GOTNO  0x70000036
+
 ?
 /* Flags which may appear in a DT_MIPS_FLAGS entry.  */
 
-- 
1.7.9.5

[-- Attachment #2: Spec.txt --]
[-- Type: text/plain, Size: 7223 bytes --]

====
MIPS IFUNC ABI specification
====

Introduction
----

This document describes the working of GNU indirect functions(IFUNC)
for MIPS.

Terminology
----
Shared file or shared object refers to any object file with e_type
ET_DYN. Unless otherwise distinguished, this includes dynamic shared libraries
(PIC) and position-independent executables (PIE).

Executable file or executable refers to any object file with e_type
ET_EXEC. Unless otherwise distinguished, this includes statically linked and
dynamically linked executables.

Relocations for IFUNC resolution
----

The relocation table may now contain a new relocation type generated by the
static linker:

R_MIPS_IRELATIVE(128)
This relocation marks the location of an IFUNC indirection that needs to be
resolved by the dynamic linker at load time. The relocation step calls the
IFUNC resolver function given at the relocation offset possibly with
additional processor-specific (hardware capability) arguments and writes the
address returned by the resolver back to the relocation offset. For
shared objects, the relocation step may add a base address to the
relocation offset to calculate the run-time address of the resolver
function.

The operation can be described as:
[B + O] <= ((ifunc_proto) B + O) (<hwcaps>)
where O = relocation offset
      B = relative relocation bias; =0 for executable files
      ifunc_proto = void *(*)(void)

IFUNCs in executable files
----
Code/data references to IFUNCs in executable files are resolved by
means of indirection stubs and an indirection table.

IGOT sections - the indirection table
----
Symbols defined in executable files have no GOT entries, so the
indirection is acheived by creating an entry in the new IGOT
section. Each entry in IGOT has a corresponding IRELATIVE
relocation. This IGOT entry is initialized to point to the resolver
function at link time and modified to point to the resolved function
by the IRELATIVE fix-up. Executable files are not relocatable, so the
relocation bias is always zero.

IPLT section - indirection stubs
----

The IFUNC Procedure Linkage Table (IPLT) consists of a set of stubs
generated by the static linker to stand in for IFUNC references in
executable file that can not be resolved via a GOT entry. For
executable files, both IFUNC calls and references are always routed
through IPLT stubs. Every IFUNC definition must have a corresponding
stub irrespective of its declared visibility.

Call to an IFUNC is performed by an absolute JAL instruction to the
IPLT stub. The stub redirects the call to the actual function by
reading a pointer from the IGOT section. Data references use the
absolute address of the IPLT stub in place of the IFUNC.

IFUNCs in shared objects
----

In the case of shared objects, data references to IFUNCs use explicit
dynamic relocations, where as calls or code-references are routed
through the GOT which itself is relocated implicitly by the dynamic
linker. Morever, global symbols can be pre-empted by the dynamic
linker at load time. There are 4 scenarios to consider:

Data relocations for local IFUNC symbols
----

The static linker replaces each R_MIPS_REL32 dynamic relocation for a
local IFUNC reference with an IRELATIVE relocation. The IRELATIVE
resolution step above includes an implicit relative relocation. Shared
objects have a non-zero bias element provided by the dynamic linker.

Code relocations for local IFUNC symbols
----

Non-preemptible IFUNC symbols have entries in the local GOT region.
Call or reference in code is resolved by reading from the
corresponding GOT entry. The local GOT can be considered as a series
of implicit REL32 relocations. For IFUNC resolution, we must
replace each implicit REL32 relocation with an explicit IRELATIVE
relocation, just as we did above for explicit REL32 relocations in the
case of data relocations.

Thus, each IFUNC entry in the local IGOT gets a corresponding
IRELATIVE relocation. To prevent these entries from being relocated
twice (once implicitly as part of GOT relocation and again
by the IRELATIVE relocation), a part of the GOT is marked as
containing only explictly relocated entries. This part is skipped over
during normal GOT relocation and only modified by explicit
relocations. This part is marked off from the implicitly relocated
local GOT region by the dynamic tag MIPS_GENERAL_GOTNO.

Data relocations for global IFUNC symbols
----

For a global preemptible symbol, the dynamic linker must determine at
load-time, whether the IFUNC is preempted by an external
symbol. R_MIPS_REL32 relocations against such symbols cannot simply be
replaced with IRELATIVE ones.  The REL32 relocation step for such
references must be modified to include an implicit IRELATIVE
relocation, to be performed only if the resolved symbol is also an
IFUNC. The relocations produced remain exactly the same in this case,
but the relocation process employed by the dynamic linker changes to 
account for symbol preemption.

Code relocations for global IFUNC symbols
----

Preemptible global IFUNC symbols have entries in the global GOT
region. A call or reference in code is resolved by reading from the
corresponding GOT entry. The global GOT can be considered as a
series of implicit REL32 relocations. The order of symbols
in the global GOT region is defined by the ABI as a one-to-one
correspondence with entries in the dynamic symbol table. These
entries cannot be repositioned or partitioned without significatly
hampering the ABI.

Moreover, the global GOT is relocated implicitly before the local GOT
and all explicit relocations. If an IFUNC resolver function depends on
any of the latter stages, it cannot be invoked safely as part of the
implicitly global GOT relocation.

The static linker generates an explicit R_MIPS_REL32 relocation
against the GOT entry of each global IFUNC as a means of delaying the
IFUNC resolution step. These R_MIPS_REL32 relocations for code behave
similar to explicit data relocations described above, encapsulating
both symbol preemption and an optional IRELATIVE relocation. The only
additional precaution is to skip these GOT entries from the implicit
GOT relocation step to prevent them from being relocated twice. This
can be acheived trivially, without having to partition the global GOT
with dynamic tags, because symbol information indication type as
STT_GNU_IFUNC is readily available for such symbols from the dynamic
symbol table.

Dynamic Section
----

Dynamic section entries give information to the dynamic linker.  The following new
dynamic table entries are required for IFUNC resolution:

MIPS_GENERAL_GOTNO(36)
        Number of explicitly relocated GOT entries. Alternatively, start of
        the normally relocated local GOT region. This entry is optional. If not
	present, its value is assumed by the linker to be 1(non-GNU) or 2(GNU).

Revised GOT layout
----

GOT0: Reserved for dynamic linker
GOT1: Reserved, GNU-specific, optional

GOT2 ...
MIPS_GENERAL_GOTNO - 1: Explicitly relocated GOT entries

MIPS_GENERAL_GOTNO ...
MIPS_LOCAL_GOTNO - 1: Implicitly relocated local GOT entries

MIPS_LOCAL_GOTNO ...
MIPS_SYMTABNO - 1: Global GOT entries, of which only those not
corresponding to IFUNC symbols are implictly relocated.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0002-RFC-Add-IFUNC-tests-for-MIPS.patch --]
[-- Type: text/x-patch; name="0002-RFC-Add-IFUNC-tests-for-MIPS.patch", Size: 45869 bytes --]

From a2c0aab6fb7bb35134a7d404add3301cc99569d0 Mon Sep 17 00:00:00 2001
From: Faraz Shahbazker <faraz.shahbazker@imgtec.com>
Date: Tue, 21 Jul 2015 04:15:25 -0700
Subject: [PATCH 2/2] [RFC] Add IFUNC tests for MIPS

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-iplt-0x400000.t, ifunc-iplt-0x400000000.t,
	ifunc-iplt-0x4000000000000.t, 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             |    8 +
 ld/testsuite/ld-mips-elf/ifunc-3-n64.sym           |    6 +
 ld/testsuite/ld-mips-elf/ifunc-3-n64.t             |   11 ++
 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.sym           |   29 ++++
 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.sym           |   29 ++++
 ld/testsuite/ld-mips-elf/ifunc-4-n64.t             |   11 ++
 ld/testsuite/ld-mips-elf/ifunc-4-o32.r             |    3 +
 ld/testsuite/ld-mips-elf/ifunc-4-o32.sym           |   24 +++
 ld/testsuite/ld-mips-elf/ifunc-4-o32.t             |   11 ++
 ld/testsuite/ld-mips-elf/ifunc-5-n32.r             |    4 +
 ld/testsuite/ld-mips-elf/ifunc-5-n32.sym           |    5 +
 ld/testsuite/ld-mips-elf/ifunc-5-n64.r             |    8 +
 ld/testsuite/ld-mips-elf/ifunc-5-n64.sym           |    5 +
 ld/testsuite/ld-mips-elf/ifunc-5-o32.r             |    4 +
 ld/testsuite/ld-mips-elf/ifunc-5-o32.sym           |    6 +
 ld/testsuite/ld-mips-elf/ifunc-6-n32.r             |    4 +
 ld/testsuite/ld-mips-elf/ifunc-6-n32.sym           |    6 +
 ld/testsuite/ld-mips-elf/ifunc-6-n64.r             |    8 +
 ld/testsuite/ld-mips-elf/ifunc-6-n64.sym           |    6 +
 ld/testsuite/ld-mips-elf/ifunc-6-o32.r             |    4 +
 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-0x400000.t     |   11 ++
 ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000000.t  |   13 ++
 .../ld-mips-elf/ifunc-iplt-0x4000000000000.t       |   14 ++
 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            |  149 +++++++++++++++++++
 49 files changed, 929 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.sym
 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.sym
 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.sym
 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-0x400000.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000000.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt-0x4000000000000.t
 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..b14c140
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-n32.r
@@ -0,0 +1,4 @@
+Relocation section '.rel.dyn' at offset 0x12000 contains 2 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00000000  00000000 R_MIPS_NONE      
+00000800  00000080 R_MIPS_IRELATIVE 
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..6068a87
--- /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
+    4   0: 00000000     0 FUNC    GLOBAL DEFAULT UND ref1
+    2   0: 00000400    28 FUNC    GLOBAL DEFAULT   1 func1
+    1   1: 00000001     0 SECTION GLOBAL DEFAULT ABS _DYNAMIC_LINKING
+    3   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..53b6455
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-n64.r
@@ -0,0 +1,8 @@
+Relocation section '.rel.dyn' at offset 0x12000 contains 2 entries:
+  Offset          Info           Type           Sym. Value    Sym. Name
+000000000000  000000000000 R_MIPS_NONE      
+                    Type2: R_MIPS_NONE      
+                    Type3: R_MIPS_NONE      
+000000000800  000000000080 R_MIPS_IRELATIVE 
+                    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..bc9c09f
--- /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
+    4   0: 0000000000000000     0 FUNC    GLOBAL DEFAULT UND ref1
+    2   0: 0000000000000400    28 FUNC    GLOBAL DEFAULT   1 func1
+    1   1: 0000000000000001     0 SECTION GLOBAL DEFAULT ABS _DYNAMIC_LINKING
+    3   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..0df1db2
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-n64.t
@@ -0,0 +1,11 @@
+tmpdir/ifunc-3-n64:     file format elf64-tradbigmips
+
+
+Disassembly of section .iplt:
+
+0000000000000400 <.iplt.func1>:
+ 400:	3c0f0000 	lui	t3,0x0
+ 404:	ddf90800 	ld	t9,2048\(t3\)
+ 408:	03200008 	jr	t9
+ 40c:	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..b14c140
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-o32.r
@@ -0,0 +1,4 @@
+Relocation section '.rel.dyn' at offset 0x12000 contains 2 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00000000  00000000 R_MIPS_NONE      
+00000800  00000080 R_MIPS_IRELATIVE 
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..a6c92d1
--- /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
+    4   0: 00000000     0 FUNC    GLOBAL DEFAULT UND ref1
+    2   0: 00000400    40 FUNC    GLOBAL DEFAULT   1 func1
+    1   1: 00000001     0 SECTION GLOBAL DEFAULT ABS _DYNAMIC_LINKING
+    3   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.sym b/ld/testsuite/ld-mips-elf/ifunc-4-n32.sym
new file mode 100644
index 0000000..4761ae5
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-n32.sym
@@ -0,0 +1,29 @@
+Symbol table '.symtab' contains 27 entries:
+   Num:    Value  Size Type    Bind   Vis      Ndx Name
+     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
+     1: 00080000     0 SECTION LOCAL  DEFAULT    1 
+     2: 00080400     0 SECTION LOCAL  DEFAULT    2 
+     3: 00080800     0 SECTION LOCAL  DEFAULT    3 
+     4: 00081000     0 SECTION LOCAL  DEFAULT    4 
+     5: 00081008     0 SECTION LOCAL  DEFAULT    5 
+     6: 00081020     0 SECTION LOCAL  DEFAULT    6 
+     7: 00000000     0 SECTION LOCAL  DEFAULT    7 
+     8: 00000000     0 SECTION LOCAL  DEFAULT    8 
+     9: 00000000     0 SECTION LOCAL  DEFAULT    9 
+    10: 00000000     0 FILE    LOCAL  DEFAULT  ABS ifunc_ref_main_1.c
+    11: 00000000     0 FILE    LOCAL  DEFAULT  ABS ifunc.c
+    12: 00080430    36 FUNC    LOCAL  DEFAULT    2 f1_a
+    13: 00080454    36 FUNC    LOCAL  DEFAULT    2 f1_b
+    14: 00080478    36 FUNC    LOCAL  DEFAULT    2 f1_c
+    15: 000804d0     0 NOTYPE  LOCAL  DEFAULT    2 \$L8
+    16: 000804d8     0 NOTYPE  LOCAL  DEFAULT    2 \$L9
+    17: 000804f8     0 NOTYPE  LOCAL  DEFAULT    2 \$L10
+    18: 00080520     0 NOTYPE  LOCAL  DEFAULT    2 \$L13
+    19: 00080518     0 NOTYPE  LOCAL  DEFAULT    2 \$L12
+    20: 00000000     0 FILE    LOCAL  DEFAULT  ABS ifunc_ref.c
+    21: 00000000     0 FILE    LOCAL  DEFAULT  ABS 
+    22: 00080000    16 FUNC    LOCAL  DEFAULT    1 .iplt.func1
+    23: 0008049c   156 IFUNC   GLOBAL DEFAULT    2 func1
+    24: 00080400    48 FUNC    GLOBAL DEFAULT    2 main
+    25: 00080540    48 FUNC    GLOBAL DEFAULT    2 ref1
+    26: 0008049c   156 FUNC    GLOBAL DEFAULT    2 func1_ifunc
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.sym b/ld/testsuite/ld-mips-elf/ifunc-4-n64.sym
new file mode 100644
index 0000000..0d044f5
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-n64.sym
@@ -0,0 +1,29 @@
+Symbol table '.symtab' contains 27 entries:
+   Num:    Value          Size Type    Bind   Vis      Ndx Name
+     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
+     1: 0000000000080000     0 SECTION LOCAL  DEFAULT    1 
+     2: 0000000000080400     0 SECTION LOCAL  DEFAULT    2 
+     3: 0000000000080800     0 SECTION LOCAL  DEFAULT    3 
+     4: 0000000000081000     0 SECTION LOCAL  DEFAULT    4 
+     5: 0000000000081010     0 SECTION LOCAL  DEFAULT    5 
+     6: 0000000000081088     0 SECTION LOCAL  DEFAULT    6 
+     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
+     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 
+     9: 0000000000000000     0 SECTION LOCAL  DEFAULT    9 
+    10: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS ifunc_ref_main_1.c
+    11: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS ifunc.c
+    12: 0000000000080430    36 FUNC    LOCAL  DEFAULT    2 f1_a
+    13: 0000000000080454    36 FUNC    LOCAL  DEFAULT    2 f1_b
+    14: 0000000000080478    36 FUNC    LOCAL  DEFAULT    2 f1_c
+    15: 00000000000804d0     0 NOTYPE  LOCAL  DEFAULT    2 \$L8
+    16: 00000000000804d8     0 NOTYPE  LOCAL  DEFAULT    2 \$L9
+    17: 00000000000804f8     0 NOTYPE  LOCAL  DEFAULT    2 \$L10
+    18: 0000000000080520     0 NOTYPE  LOCAL  DEFAULT    2 \$L13
+    19: 0000000000080518     0 NOTYPE  LOCAL  DEFAULT    2 \$L12
+    20: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS ifunc_ref.c
+    21: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS 
+    22: 0000000000080000    32 FUNC    LOCAL  DEFAULT    1 .iplt.func1
+    23: 000000000008049c   156 IFUNC   GLOBAL DEFAULT    2 func1
+    24: 0000000000080400    48 FUNC    GLOBAL DEFAULT    2 main
+    25: 0000000000080540    48 FUNC    GLOBAL DEFAULT    2 ref1
+    26: 000000000008049c   156 FUNC    GLOBAL DEFAULT    2 func1_ifunc
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..bf5a2d2
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-n64.t
@@ -0,0 +1,11 @@
+tmpdir/ifunc-4-n64:     file format elf64-tradbigmips
+
+
+Disassembly of section .iplt:
+
+0000000000080000 <.iplt.func1>:
+   80000:	3c0f0008 	lui	t3,0x8
+   80004:	ddf90800 	ld	t9,2048\(t3\)
+   80008:	03200008 	jr	t9
+   8000c:	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.sym b/ld/testsuite/ld-mips-elf/ifunc-4-o32.sym
new file mode 100644
index 0000000..4f6e13e
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-o32.sym
@@ -0,0 +1,24 @@
+Symbol table '.symtab' contains 22 entries:
+   Num:    Value  Size Type    Bind   Vis      Ndx Name
+     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
+     1: 00080000     0 SECTION LOCAL  DEFAULT    1 
+     2: 00080400     0 SECTION LOCAL  DEFAULT    2 
+     3: 00080800     0 SECTION LOCAL  DEFAULT    3 
+     4: 00081000     0 SECTION LOCAL  DEFAULT    4 
+     5: 00081008     0 SECTION LOCAL  DEFAULT    5 
+     6: 00081020     0 SECTION LOCAL  DEFAULT    6 
+     7: 00000000     0 SECTION LOCAL  DEFAULT    7 
+     8: 00000000     0 SECTION LOCAL  DEFAULT    8 
+     9: 00000000     0 SECTION LOCAL  DEFAULT    9 
+    10: 00000000     0 FILE    LOCAL  DEFAULT  ABS ifunc_ref_main_1.c
+    11: 00000000     0 FILE    LOCAL  DEFAULT  ABS ifunc.c
+    12: 00080430    36 FUNC    LOCAL  DEFAULT    2 f1_a
+    13: 00080454    36 FUNC    LOCAL  DEFAULT    2 f1_b
+    14: 00080478    36 FUNC    LOCAL  DEFAULT    2 f1_c
+    15: 00000000     0 FILE    LOCAL  DEFAULT  ABS ifunc_ref.c
+    16: 00000000     0 FILE    LOCAL  DEFAULT  ABS 
+    17: 00080000    20 FUNC    LOCAL  DEFAULT    1 .iplt.func1
+    18: 0008049c   156 IFUNC   GLOBAL DEFAULT    2 func1
+    19: 00080400    48 FUNC    GLOBAL DEFAULT    2 main
+    20: 00080540    48 FUNC    GLOBAL DEFAULT    2 ref1
+    21: 0008049c   156 FUNC    GLOBAL DEFAULT    2 func1_ifunc
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..fb0ef4a
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-n32.r
@@ -0,0 +1,4 @@
+Relocation section '.rel.dyn' at offset 0x11000 contains 2 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00000000  00000000 R_MIPS_NONE      
+00000410  00000403 R_MIPS_REL32      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..718d84a
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-n64.r
@@ -0,0 +1,8 @@
+Relocation section '.rel.dyn' at offset 0x11000 contains 2 entries:
+  Offset          Info           Type           Sym. Value    Sym. Name
+000000000000  000000000000 R_MIPS_NONE      
+                    Type2: R_MIPS_NONE      
+                    Type3: R_MIPS_NONE      
+000000000420  000400001203 R_MIPS_REL32      func1\(\)          func1
+                    Type2: R_MIPS_64        
+                    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..dbb3325
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-o32.r
@@ -0,0 +1,4 @@
+Relocation section '.rel.dyn' at offset 0x11000 contains 2 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00000000  00000000 R_MIPS_NONE      
+00000410  00000503 R_MIPS_REL32      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..b14c140
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-n32.r
@@ -0,0 +1,4 @@
+Relocation section '.rel.dyn' at offset 0x12000 contains 2 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00000000  00000000 R_MIPS_NONE      
+00000800  00000080 R_MIPS_IRELATIVE 
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..2c822d9
--- /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
+    4   0: 00000070    60 FUNC    GLOBAL DEFAULT   1 ref1
+    2   0: 00000400    28 FUNC    GLOBAL DEFAULT   1 func1
+    1   1: 00000001     0 SECTION GLOBAL DEFAULT ABS _DYNAMIC_LINKING
+    3   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..53b6455
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-n64.r
@@ -0,0 +1,8 @@
+Relocation section '.rel.dyn' at offset 0x12000 contains 2 entries:
+  Offset          Info           Type           Sym. Value    Sym. Name
+000000000000  000000000000 R_MIPS_NONE      
+                    Type2: R_MIPS_NONE      
+                    Type3: R_MIPS_NONE      
+000000000800  000000000080 R_MIPS_IRELATIVE 
+                    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..500aada
--- /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
+    4   0: 0000000000000070    60 FUNC    GLOBAL DEFAULT   1 ref1
+    2   0: 0000000000000400    28 FUNC    GLOBAL DEFAULT   1 func1
+    1   1: 0000000000000001     0 SECTION GLOBAL DEFAULT ABS _DYNAMIC_LINKING
+    3   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..b14c140
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-o32.r
@@ -0,0 +1,4 @@
+Relocation section '.rel.dyn' at offset 0x12000 contains 2 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00000000  00000000 R_MIPS_NONE      
+00000800  00000080 R_MIPS_IRELATIVE 
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..a39cb03
--- /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
+    4   0: 00000080    76 FUNC    GLOBAL DEFAULT   1 ref1
+    2   0: 00000400    40 FUNC    GLOBAL DEFAULT   1 func1
+    1   1: 00000001     0 SECTION GLOBAL DEFAULT ABS _DYNAMIC_LINKING
+    3   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-0x400000.t b/ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000.t
new file mode 100644
index 0000000..c46a85d
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000.t
@@ -0,0 +1,11 @@
+tmpdir/ifunc-iplt-0x400000:     file format elf64-tradbigmips
+
+
+Disassembly of section .iplt:
+
+0000000000400188 <.iplt.func1>:
+  400188:	3c0f0041 	lui	t3,0x41
+  40018c:	ddf90320 	ld	t9,800\(t3\)
+  400190:	03200008 	jr	t9
+  400194:	00000000 	nop
+	...
diff --git a/ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000000.t b/ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000000.t
new file mode 100644
index 0000000..3562b23
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000000.t
@@ -0,0 +1,13 @@
+tmpdir/ifunc-iplt-0x400000000:     file format elf64-tradbigmips
+
+
+Disassembly of section .iplt:
+
+0000000400000188 <.iplt.func1>:
+   400000188:	3c0f0004 	lui	t3,0x4
+   40000018c:	25ef0001 	addiu	t3,t3,1
+   400000190:	000f7c38 	dsll	t3,t3,0x10
+   400000194:	ddf90320 	ld	t9,800\(t3\)
+   400000198:	03200008 	jr	t9
+   40000019c:	00000000 	nop
+	...
diff --git a/ld/testsuite/ld-mips-elf/ifunc-iplt-0x4000000000000.t b/ld/testsuite/ld-mips-elf/ifunc-iplt-0x4000000000000.t
new file mode 100644
index 0000000..90e8306
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-0x4000000000000.t
@@ -0,0 +1,14 @@
+tmpdir/ifunc-iplt-0x4000000000000:     file format elf64-tradbigmips
+
+
+Disassembly of section .iplt:
+
+0004000000000188 <.iplt.func1>:
+   4000000000188:	3c0f0004 	lui	t3,0x4
+   400000000018c:	3c0e0001 	lui	t2,0x1
+   4000000000190:	25ef0000 	addiu	t3,t3,0
+   4000000000194:	000f783c 	dsll32	t3,t3,0x0
+   4000000000198:	01ee782d 	daddu	t3,t3,t2
+   400000000019c:	ddf90320 	ld	t9,800\(t3\)
+   40000000001a0:	03200008 	jr	t9
+   40000000001a4:	00000000 	nop
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..88d7e5d
--- /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
+    3   0: 0000000c    28 FUNC    GLOBAL DEFAULT   1 func1_ifunc
+    2   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..7236950
--- /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
+    3   0: 000000000000000c    28 FUNC    GLOBAL DEFAULT   1 func1_ifunc
+    2   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..b5fff26
--- /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
+    3   0: 0000000c    40 FUNC    GLOBAL DEFAULT   1 func1_ifunc
+    2   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..ae389cf
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/mips-ifunc.exp
@@ -0,0 +1,149 @@
+# 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 -s ifunc-4-${abi}.sym" \
+		  "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}" \
+        ] \
+    ]
+}
+
+# IPLT sequences change based on how big the address of the
+# .igot.plt section is based on Mips loading immediate values.
+#
+set addrs { "0x400000" "0x400000000" "0x4000000000000" }
+foreach { addr } $addrs {
+    run_ld_link_tests [list \
+	[list \
+	    "IFUNC IPLT (Simple static executable with def and ref) ${addr}" \
+	    "$abi_ldflags(n64) -Bstatic -Ttext-segment ${addr}" "" \
+	    "$abi_asflags(n64) -non_shared" \
+	    [list ifunc-static-main.s ifunc-static-def.s ifunc-static-ref.s] \
+	    [list "objdump -dj.iplt ifunc-iplt-${addr}.t"] \
+	    "ifunc-iplt-${addr}" \
+        ] \
+    ]
+}
+
-- 
1.7.9.5


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC] Add IFUNC support for MIPS
  2015-07-21 12:08         ` Faraz Shahbazker
@ 2015-07-23 21:44           ` Richard Sandiford
  2015-07-31 12:57             ` Faraz Shahbazker
  2015-09-21 18:50             ` [RFC] Add IFUNC support for MIPS (v3) Faraz Shahbazker
  0 siblings, 2 replies; 35+ messages in thread
From: Richard Sandiford @ 2015-07-23 21:44 UTC (permalink / raw)
  To: Faraz Shahbazker; +Cc: binutils

Faraz Shahbazker <Faraz.Shahbazker@imgtec.com> writes:
> I have revised the patch with more suggestions from the previous discussions. 
> Detailed description is in the attached text file. I think I have got a better handle on
> symbol preemption with this version, but I would really appreciate some input on that
> aspect.
>
> Previous discussions for reference:
> https://www.sourceware.org/ml/binutils/2013-08/msg00121.html
> https://www.sourceware.org/ml/binutils/2013-11/msg00155.html
> https://sourceware.org/ml/binutils/2013-12/msg00058.html
>
> 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.
>
> Regards,
> Faraz Shahbazker

Thanks for the updated patch.  FWIW I'll try to look at them next week
(or the following weekend).

Could you also post or link to the glibc patch?  That might help clear
up some things.

Thanks,
Richard

^ permalink raw reply	[flat|nested] 35+ messages in thread

* RE: [RFC] Add IFUNC support for MIPS
  2015-07-23 21:44           ` Richard Sandiford
@ 2015-07-31 12:57             ` Faraz Shahbazker
  2015-09-21 18:50             ` [RFC] Add IFUNC support for MIPS (v3) Faraz Shahbazker
  1 sibling, 0 replies; 35+ messages in thread
From: Faraz Shahbazker @ 2015-07-31 12:57 UTC (permalink / raw)
  To: Richard Sandiford; +Cc: binutils

Richard Sandiford [rdsandiford@googlemail.com] writes:
> Could you also post or link to the glibc patch?  That might help clear
> up some things.

glibc patch posted here:
https://sourceware.org/ml/libc-alpha/2015-07/msg01005.html

Thanks,
Faraz Shahbazker

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC] Add IFUNC support for MIPS
  2015-06-23 19:13       ` Faraz Shahbazker
  2015-07-21 12:08         ` Faraz Shahbazker
@ 2015-08-02 13:08         ` Richard Sandiford
  2015-08-03 15:29           ` Faraz Shahbazker
  1 sibling, 1 reply; 35+ messages in thread
From: Richard Sandiford @ 2015-08-02 13:08 UTC (permalink / raw)
  To: Faraz Shahbazker; +Cc: binutils

[-- Attachment #1: Type: text/plain, Size: 1905 bytes --]

Faraz Shahbazker <faraz.shahbazker@imgtec.com> writes:
> On 06/23/2015 11:19 AM, Richard Sandiford wrote:
>>> Sorry that you have to go through this all over again, but I'd like
>>> to clarify this one point. Which is preferable?  - Earlier approach:
>>> perform IRELATIVE relocs implicitly for symbols in shared objects
>>> while performing the GOT relocation.  vs.  - Current proposed: each
>>> IRELATIVE reloc is explicitly processed as a normal relocation, after
>>> all GOT relocations and all other relocations.
>>> I tended towards this approach because of a multi-got test-case where
>>> the resolver function needs symbols that have REL32 relocations.
>>  Yeah, I agree performing them later is better than original implicit
>> approach.  But I don't think we gain anything by separating ifunc
>> relocations from other relocations and tying the layout to the
>> original ABI GOT scheme.
> Agreed.
>
> A chunk of the patch tries to address the link-order problem, namely: If
> an external reference binds to an IFUNC resolver within another object
> which has not been relocated at the time when the referring object is
> loaded, it is not possible to invoke this IFUNC resolver to determine
> the resolved function.
>
> Is this problem worth solving or should I leave it alone?

I don't think we can reasonably support that in the general case.
E.g. in the attached testcase b.c has a pointer reference to foo,
which in a.c is defined as an ifunc.  The ifunc resolver refers
to relocated data in a.c.  That doesn't work on x86_64-linux-gnu:

Test 1:
foo.sh: line 41:  5234 Segmentation fault      (core dumped) ./test1
Test 2:
foo.sh: line 42:  5236 Segmentation fault      (core dumped) ./test2
Test 3:
Don't call me!
Don't call me!
Test 4:
Hello, world!
Hello, world!

Of course, removing ptr2 allows the reference to foo in b.c to be lazily
bound and then everything works.

Thanks,
Richard


[-- Attachment #2: test.sh --]
[-- Type: application/x-sh, Size: 1005 bytes --]

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC] Add IFUNC support for MIPS
  2015-08-02 13:08         ` [RFC] Add IFUNC support for MIPS Richard Sandiford
@ 2015-08-03 15:29           ` Faraz Shahbazker
  0 siblings, 0 replies; 35+ messages in thread
From: Faraz Shahbazker @ 2015-08-03 15:29 UTC (permalink / raw)
  To: binutils, rdsandiford

On 08/02/2015 06:08 AM, Richard Sandiford wrote:
> Faraz Shahbazker <faraz.shahbazker@imgtec.com> writes:
>> A chunk of the patch tries to address the link-order problem, namely: If
>> an external reference binds to an IFUNC resolver within another object
>> which has not been relocated at the time when the referring object is
>> loaded, it is not possible to invoke this IFUNC resolver to determine
>> the resolved function.
>>
>> Is this problem worth solving or should I leave it alone?
> 
> I don't think we can reasonably support that in the general case.
> E.g. in the attached testcase b.c has a pointer reference to foo,
> which in a.c is defined as an ifunc.  The ifunc resolver refers
> to relocated data in a.c.  That doesn't work on x86_64-linux-gnu:

I have dropped this completely from the recent patch. Thanks for the explanation though.

Regards,
Faraz

^ permalink raw reply	[flat|nested] 35+ messages in thread

* [RFC] Add IFUNC support for MIPS (v3)
  2015-07-23 21:44           ` Richard Sandiford
  2015-07-31 12:57             ` Faraz Shahbazker
@ 2015-09-21 18:50             ` Faraz Shahbazker
  2015-09-27 11:23               ` Richard Sandiford
  1 sibling, 1 reply; 35+ messages in thread
From: Faraz Shahbazker @ 2015-09-21 18:50 UTC (permalink / raw)
  To: binutils; +Cc: rdsandiford

Hi,

glibc patch review is close to completion. Requesting fresh comments on the binutils patch.

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_got_info): New fields general_gotno and
	assigned_general_gotno.
	(struct mips_elf_link_hash_entry): New fields for offset in to
	IPLT/IGOT and flags to indicate symbol needs IPLT or IRELOC.
	(mips_elf_link_hash_table): New fields for size of IPLT stubs
	and hash-table for local IFUNC symbols.
	(MIPS16_P): New macro to check ASE flag.
	(mips16_exec_iplt_entry): Template for mips16 IPLT stub.
	(mips32_exec_iplt_entry): Template for mips32 IPLT stub.
	(mips32r6_exec_iplt_entry): Template for mips32 R6 IPLT stub.
	(micromips32_exec_iplt_entry): Template for micromips32 stub.
	(mips64_exec_iplt_entry): Template for mips64 IPLT stub.
	(mips64_48b_exec_iplt_entry): Template for 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_get_irel_section): New function.
	(mips_elf_allocate_ireloc): Likewise.
	(mips_elf_allocate_iplt): Likewise.
	(mips_elf_check_local_symbols): Likewise.
	(mips_elf_check_symbols): Allocate an IPLT entry and/or dynamic
	relocation for defined IFUNC symbols and set has_gnu_symbols.
	(mips_elf_count_got_entry): Skip counting of IFUNC entries.
	(mips_elf_got16_entry): Add argument h and pass it to
	mips_elf_create_local_got_entry instead of NULL.
	(mips_elf_create_local_got_entry): Add check for sufficient general
	GOT entries. Assign local IFUNCs from general GOT entries pool.
	(mips_elf_record_global_got_symbol):  Keep global IFUNC symbols
	in the general GOT region instead of moving them to global GOT.
	(mips_use_local_got_p): Return TRUE for all IFUNC symbol definitions.
	(mips_elf_count_got_symbols): Count global IFUNCs under general
	GOT entries instead of global.
	(mips_elf_count_general_got_symbols): New function.
	(mips_elf_create_ifunc_sections): New function.
	(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 symbols that need IPLT.
	Force global IFUNC to be allocated from local GOT region.
	(mips_elf_create_dynamic_relocation): Emit IRELATIVE relocation
	instead of REL32 for local IFUNC reference. Relax assertion for
	IFUNC symbols in explicit GOT region to have dynamic relocations.
        (_bfd_mips_elf_section_processing): Size .igot section.
	(_bfd_mips_elf_check_relocs): Check need to create IFUNC sections.
	If symbol is an IFUNC, don't convert it to an STT_FUNC. Relax error
	checking to 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 general GOT entries. Offset local entries
	to follow general entries.
	(_bfd_mips_elf_size_dynamic_sections): Exclude IPLT and IGOT.
	Create dynamic tag for DT_MIPS_GENERAL_GOTNO if needed.
	(_bfd_mips_elf_relocate_section): Relax error checking to allow
	local IFUNCs to be accessed via standalone got16 reloc.
	(mips_elf_create_iplt): New function.
	(misp_elf_check_local_got_index): Likewise.
	(mips_elf_create_ireloc): Likewise.
	(_bfd_mips_elf_finish_dynamic_symbol): Create IPLT stub/IREL
	relocation for IFUNC symbols as necessary. Set IFUNC symbol value
	to the IPLT entry address for executable 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 tag - DT_MIPS_GENERAL_GOTNO.
	(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 tag -
	DT_MIPS_GENERAL_GOTNO.
	(_bfd_mips_post_process_headers): If ELF uses GNU IFUNCs, set
	ABIVERSION to 4.
	* 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:
	(get_mips_dynamic_type): Add case for DT_MIPS_GENERAL_GOTNO.
	(dynamic_section_mips_val): Add case for DT_MIPS_GENERAL_GOTNO.

elfcpp/ChangeLog:

	* elfcpp.h
	(enum DT): Add cases for dynamic tag DT_MIPS_GENERAL_GOTNO.

include/elf/ChangeLog:

	* mips.h
	(START_RELOC_NUMBERS): Entry for R_MIPS_IRELATIVE.
	(DT_MIPS_GENERAL_GOTNO): New dynamic tag.
---
 bfd/bfd-in2.h      |    3 +
 bfd/elf32-mips.c   |   22 ++
 bfd/elfxx-mips.c   | 1016 ++++++++++++++++++++++++++++++++++++++++++++++++----
 bfd/libbfd.h       |    1 +
 bfd/reloc.c        |    5 +
 binutils/readelf.c |    3 +
 elfcpp/elfcpp.h    |    2 +
 include/elf/mips.h |    7 +
 8 files changed, 986 insertions(+), 73 deletions(-)

diff --git a/bfd/bfd-in2.h b/bfd/bfd-in2.h
index 51fa54f..99e0061 100644
--- a/bfd/bfd-in2.h
+++ b/bfd/bfd-in2.h
@@ -3040,6 +3040,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..429febc 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 5ece52d..0dd1331 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 {
@@ -165,10 +166,14 @@ struct mips_got_info
   unsigned int tls_assigned_gotno;
   /* The number of local .got entries, eventually including page entries.  */
   unsigned int local_gotno;
+  /* The number of explicitly relocated .got entries.  */
+  unsigned int general_gotno;
   /* The maximum number of page entries needed.  */
   unsigned int page_gotno;
   /* The number of relocations needed for the GOT entries.  */
   unsigned int relocs;
+  /* The first unused general .got entry.  */
+  unsigned int assigned_general_gotno;
   /* The first unused local .got entry.  */
   unsigned int assigned_low_gotno;
   /* The last unused local .got entry.  */
@@ -375,6 +380,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 +424,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 IRELATIVE relocation?  */
+  unsigned int needs_ireloc : 1;
 };
 
 /* MIPS ELF linker hash table.  */
@@ -485,6 +502,9 @@ 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;
+
   /* The number of functions that need a lazy-binding stub.  */
   bfd_vma lazy_stub_count;
 
@@ -516,6 +536,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 +824,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 \
@@ -1185,6 +1213,70 @@ 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 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					*/
+};
+
+/* Format of 32 bit IPLT entries for R6. JR encoding differs.  */
+static const bfd_vma mips32r6_exec_iplt_entry[] =
+{
+  0x3c0f0000,   /* lui $15, %hi(.igot address)		*/
+  0x8df90000,   /* lw  $25, %lo(.igot address)($15)	*/
+  0x03200009,   /* 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 64-bit IPLT entries.  */
+static const bfd_vma mips64_exec_iplt_entry[] =
+{
+  0x3c0f0000,	/* lui $15, %highest(.got.iplt entry)        */
+  0x3c0e0000,	/* lui $14, %hi(.got.iplt entry)             */
+  0x25ef0000,	/* addiu $15, $15, %higher(.got.iplt entry)  */
+  0x000f783c,	/* dsll32 $15, $15, 0x0                      */
+  0x01ee782d,	/* daddu $15, $15, $14                       */
+  0xddf90000,	/* ld $25, %lo(.got.iplt entry)($15)         */
+  0x03200008,	/* jr $25                                    */
+  0x00000000,	/* nop                                       */
+};
+
+/* The format of 64-bit IPLT entries for 48bit address.  */
+static const bfd_vma mips64_48b_exec_iplt_entry[] =
+{
+  0x3c0f0000,	/* lui $15, %higher(.got.iplt entry)         */
+  0x25ef0000,	/* addiu $15, $15, %high(.got.iplt entry)    */
+  0x000f7c38,	/* dsll $15, $15, 16  			     */
+  0xddf90000,	/* ld $25, %lo(.got.iplt entry)($15)         */
+  0x03200008,	/* jr $25                                    */
+  0x00000000,	/* nop                                       */
+};
+
 \f
 /* microMIPS 32-bit opcode helper installer.  */
 
@@ -1283,6 +1375,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_ireloc = FALSE;
+      ret->iplt_offset = -1;
+      ret->igot_offset = 0;
     }
 
   return (struct bfd_hash_entry *) ret;
@@ -1582,7 +1678,8 @@ 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))
+  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.  */
@@ -1598,6 +1695,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;
 }
 
@@ -1960,6 +2061,136 @@ 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 one 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;
+}
+
+/* Reserve space in the rel.iplt section for IRELATIVE relocation.  */
+
+static bfd_boolean
+mips_elf_allocate_ireloc (struct bfd_link_info *info,
+			  struct mips_elf_link_hash_table *mhtab,
+			  struct mips_elf_link_hash_entry *mh)
+{
+  asection *srel;
+  bfd *dynobj;
+
+  srel = mips_get_irel_section (info, mhtab);
+  dynobj = elf_hash_table (info)->dynobj;
+  if (srel != mhtab->root.irelplt && srel->size == 0)
+    {
+      /* Make room for a null element.  */
+      srel->size += MIPS_ELF_REL_SIZE (dynobj);
+      ++srel->reloc_count;
+    }
+  srel->size += MIPS_ELF_REL_SIZE (dynobj);
+  mh->needs_ireloc = TRUE;
+
+  return TRUE;
+}
+
+/* Reserve space in the iplt and igot tables for an ifunc entry
+   and allocate space for an IRELATIVE relocation.  */
+
+static bfd_boolean
+mips_elf_allocate_iplt (struct bfd_link_info *info,
+			struct mips_elf_link_hash_table *mhtab,
+			struct mips_elf_link_hash_entry *mh)
+{
+  asection *s;
+  bfd *abfd = info->output_bfd;
+
+  BFD_ASSERT (!mh->needs_iplt);
+  BFD_ASSERT (mhtab->root.iplt != NULL);
+
+  s = mhtab->root.iplt;
+
+  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;
+
+  BFD_ASSERT (mhtab->root.igotplt != NULL);
+  mh->igot_offset = mhtab->root.igotplt->size;
+  mhtab->root.igotplt->size += MIPS_ELF_GOT_SIZE (abfd);
+  mh->needs_iplt = TRUE;
+
+  return TRUE;
+}
+
+/* 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
+      && h->root.def_regular)
+    {
+      struct bfd_link_info *info = hti->info;
+      elf_tdata (info->output_bfd)->has_gnu_symbols |= elf_gnu_symbol_ifunc;
+
+      /* .iplt entry is needed only for executable objects.  */
+      if (!bfd_link_pic (info) &&
+	  !mips_elf_allocate_iplt (info, mips_elf_hash_table (info), h))
+	return FALSE;
+
+      /* IRELATIVE fixup will be needed for each local IFUNC.  */
+      if (!mips_elf_allocate_ireloc (info, mips_elf_hash_table (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.  */
 
@@ -1972,6 +2203,21 @@ mips_elf_check_symbols (struct mips_elf_link_hash_entry *h, void *data)
   if (!bfd_link_relocatable (hti->info))
     mips_elf_check_mips16_stubs (hti->info, h);
 
+  if (h && !h->needs_iplt && h->root.type == STT_GNU_IFUNC
+      && h->root.def_regular)
+    {
+      struct bfd_link_info *info = hti->info;
+      elf_tdata (info->output_bfd)->has_gnu_symbols |= elf_gnu_symbol_ifunc;
+
+      /* .iplt entry is needed only for executable objects.  */
+      if (!bfd_link_pic (info)
+	  && !mips_elf_allocate_iplt (info, mips_elf_hash_table (info), h))
+	return FALSE;
+      /* IREL or REL32 fixup is needed for each global IFUNC.  */
+      if (!mips_elf_allocate_ireloc (info, mips_elf_hash_table (info), h))
+	return FALSE;
+    }
+
   if (mips_elf_local_pic_function_p (h))
     {
       /* PR 12845: If H is in a section that has been garbage
@@ -3139,37 +3385,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
@@ -3263,9 +3478,12 @@ mips_elf_count_got_entry (struct bfd_link_info *info,
 					entry->symndx < 0
 					? &entry->d.h->root : NULL);
     }
-  else if (entry->symndx >= 0 || entry->d.h->global_got_area == GGA_NONE)
+  /* Skip IFUNCs from local/global GOT, they are already counted as general
+     GOT entries with explicit relocations.  */
+  else if (entry->symndx >= 0 || (entry->d.h->global_got_area == GGA_NONE
+				  && !entry->d.h->needs_ireloc))
     g->local_gotno += 1;
-  else
+  else if (!entry->d.h->needs_ireloc)
     g->global_gotno += 1;
 }
 
@@ -3596,7 +3814,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;
 
@@ -3611,7 +3830,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
@@ -3711,7 +3930,8 @@ mips_elf_create_local_got_entry (bfd *abfd, struct bfd_link_info *info,
   if (entry)
     return entry;
 
-  if (g->assigned_low_gotno > g->assigned_high_gotno)
+  if (g->assigned_low_gotno > g->assigned_high_gotno ||
+      g->assigned_general_gotno > g->assigned_low_gotno)
     {
       /* We didn't allocate enough space in the GOT.  */
       (*_bfd_error_handler)
@@ -3724,7 +3944,11 @@ mips_elf_create_local_got_entry (bfd *abfd, struct bfd_link_info *info,
   if (!entry)
     return NULL;
 
-  if (got16_reloc_p (r_type)
+  if (h && h->needs_ireloc)
+    /* Allocate IFUNC slots in the general GOT region since they
+       will need explicit IRELATIVE relocations.  */
+    lookup.gotidx = MIPS_ELF_GOT_SIZE (abfd) * g->assigned_general_gotno++;
+  else if (got16_reloc_p (r_type)
       || call16_reloc_p (r_type)
       || got_page_reloc_p (r_type)
       || got_disp_reloc_p (r_type))
@@ -3952,7 +4176,11 @@ mips_elf_record_global_got_symbol (struct elf_link_hash_entry *h,
     }
 
   tls_type = mips_elf_reloc_tls_type (r_type);
-  if (tls_type == GOT_TLS_NONE && hmips->global_got_area > GGA_NORMAL)
+  /* IFUNC symbols use explicitly relocated GOT region, instead of the ABI
+     global GOT, but we don't distinguish these from the local GOT region
+     just yet.  */
+  if (tls_type == GOT_TLS_NONE && hmips->global_got_area > GGA_NORMAL
+      && !hmips->needs_ireloc)
     hmips->global_got_area = GGA_NORMAL;
 
   entry.abfd = abfd;
@@ -4398,7 +4626,9 @@ mips_use_local_got_p (struct bfd_link_info *info,
      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)
+  /* Both global & local IFUNC symbols actually use the explicitly relocated
+     GOT region, but we don't distinguish it from local GOT just yet.  */
+  if (h->root.dynindx == -1 || h->needs_ireloc)
     return TRUE;
 
   /* Symbols that bind locally can (and in the case of forced-local
@@ -4455,6 +4685,33 @@ mips_elf_count_got_symbols (struct mips_elf_link_hash_entry *h, void *data)
 	  g->global_gotno++;
 	}
     }
+
+  if (h->needs_ireloc)
+    /* Count IFUNCs towards explicitly relocated GOT.  */
+    g->general_gotno++;
+
+  return 1;
+}
+
+/* A elf_link_hash_traverse callback for which INF points to the
+   link_info structure.  Count the number of GOT entries that need
+   explicit relocations by iterating over the local hash table.  */
+
+static int
+mips_elf_count_general_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;
+
+  info = (struct bfd_link_info *) inf;
+  htab = mips_elf_hash_table (info);
+
+  g = htab->got_info;
+  if (h != NULL && h->needs_ireloc)
+    g->general_gotno++;
   return 1;
 }
 \f
@@ -5084,6 +5341,64 @@ 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 (!bfd_link_pic (info))
+    {
+      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
@@ -5200,6 +5515,75 @@ 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);
+
+  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;
+      if (MIPS16_P (abfd))
+	ret->root.other = STO_MIPS16;
+      if (MICROMIPS_P (abfd))
+	ret->root.other = STO_MICROMIPS;
+
+      *slot = ret;
+    }
+
+  return ret;
+}
 \f
 /* Calculate the value produced by the RELOCATION (which comes from
    the INPUT_BFD).  The ADDEND is the addend to use for this
@@ -5250,6 +5634,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
@@ -5330,6 +5716,13 @@ 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
     {
@@ -5554,6 +5947,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)
+    {
+      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)
@@ -5649,6 +6053,16 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
 	      BFD_ASSERT (h->root.needs_plt);
 	      g = mips_elf_gotplt_index (info, &h->root);
 	    }
+	  /* IFUNCs use explicit GOT, however we don't distinguish it
+	     from local GOT at this stage.  */
+	  else if (h && h->needs_ireloc && !h->needs_iplt)
+	    {
+	      g = mips_elf_local_got_index (abfd, input_bfd, info,
+					    symbol + addend, r_symndx,
+					    h, r_type);
+	      if (g == MINUS_ONE)
+		return bfd_reloc_outofrange;
+	    }
 	  else
 	    {
 	      BFD_ASSERT (addend == 0);
@@ -5940,8 +6354,11 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
 	 R_MIPS*_GOT16; every relocation evaluates to "G".  */
       if (!htab->is_vxworks && local_p)
 	{
+	  /* Local IFUNC symbols must be accessed through GOT, similar to
+	     global symbols, to allow for indirection.  */
 	  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
@@ -6386,7 +6803,8 @@ mips_elf_create_dynamic_relocation (bfd *output_bfd,
      in the relocation.  */
   if (h != NULL && ! SYMBOL_REFERENCES_LOCAL (info, &h->root))
     {
-      BFD_ASSERT (htab->is_vxworks || h->global_got_area != GGA_NONE);
+      BFD_ASSERT (htab->is_vxworks || h->global_got_area != GGA_NONE
+		  || h->root.type == STT_GNU_IFUNC);
       indx = h->root.dynindx;
       if (SGI_COMPAT (output_bfd))
 	defined_p = h->root.def_regular;
@@ -6445,31 +6863,42 @@ mips_elf_create_dynamic_relocation (bfd *output_bfd,
   if (defined_p && r_type != R_MIPS_REL32)
     *addendp += symbol;
 
-  if (htab->is_vxworks)
-    /* VxWorks uses non-relative relocations for this.  */
-    outrel[0].r_info = ELF32_R_INFO (indx, R_MIPS_32);
+  /* Morph REL32 in to IRELATIVE fix-up for local IFUNC reference.  */
+  if (h && !indx && h->root.type == STT_GNU_IFUNC)
+    {
+      outrel[0].r_info = ELF_R_INFO (output_bfd, (unsigned long) indx,
+				     R_MIPS_IRELATIVE);
+      outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
+				     R_MIPS_NONE);
+    }
   else
-    /* The relocation is always an REL32 relocation because we don't
-       know where the shared library will wind up at load-time.  */
-    outrel[0].r_info = ELF_R_INFO (output_bfd, (unsigned long) indx,
-				   R_MIPS_REL32);
-
-  /* For strict adherence to the ABI specification, we should
-     generate a R_MIPS_64 relocation record by itself before the
-     _REL32/_64 record as well, such that the addend is read in as
-     a 64-bit value (REL32 is a 32-bit relocation, after all).
-     However, since none of the existing ELF64 MIPS dynamic
-     loaders seems to care, we don't waste space with these
-     artificial relocations.  If this turns out to not be true,
-     mips_elf_allocate_dynamic_relocation() should be tweaked so
-     as to make room for a pair of dynamic relocations per
-     invocation if ABI_64_P, and here we should generate an
-     additional relocation record with R_MIPS_64 by itself for a
-     NULL symbol before this relocation record.  */
-  outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
-				 ABI_64_P (output_bfd)
-				 ? R_MIPS_64
-				 : R_MIPS_NONE);
+    {
+      if (htab->is_vxworks)
+	/* VxWorks uses non-relative relocations for this.  */
+	outrel[0].r_info = ELF32_R_INFO (indx, R_MIPS_32);
+      else
+	/* The relocation is always an REL32 relocation because we don't
+	   know where the shared library will wind up at load-time.  */
+	outrel[0].r_info = ELF_R_INFO (output_bfd, (unsigned long) indx,
+				       R_MIPS_REL32);
+
+      /* For strict adherence to the ABI specification, we should
+	 generate a R_MIPS_64 relocation record by itself before the
+	 _REL32/_64 record as well, such that the addend is read in as
+	 a 64-bit value (REL32 is a 32-bit relocation, after all).
+	 However, since none of the existing ELF64 MIPS dynamic
+	 loaders seems to care, we don't waste space with these
+	 artificial relocations.  If this turns out to not be true,
+	 mips_elf_allocate_dynamic_relocation() should be tweaked so
+	 as to make room for a pair of dynamic relocations per
+	 invocation if ABI_64_P, and here we should generate an
+	 additional relocation record with R_MIPS_64 by itself for a
+	 NULL symbol before this relocation record.  */
+      outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
+				     ABI_64_P (output_bfd)
+				     ? R_MIPS_64
+				     : R_MIPS_NONE);
+    }
   outrel[2].r_info = ELF_R_INFO (output_bfd, 0, R_MIPS_NONE);
 
   /* Adjust the output offset of the relocation to reference the
@@ -7016,6 +7445,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;
@@ -7929,6 +8360,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);
@@ -8190,12 +8628,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;
+
+	  /* Relocation is for local STT_GNU_IFUNC symbol.  */
+	  if (isym->st_info == STT_GNU_IFUNC)
+	    {
+	      /* Ensure that we have a hash entry for this symbol.  */
+	      if (get_local_sym_hash (htab, abfd, rel) == NULL)
+		return FALSE;
+	      local_gnu_ifunc_p = TRUE;
+	    }
+
+	  h = NULL;
+       }
       else if (r_symndx >= extsymoff + NUM_SHDR_ENTRIES (symtab_hdr))
 	{
 	  (*_bfd_error_handler)
@@ -8407,7 +8863,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"),
@@ -8432,9 +8889,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;
 
@@ -8973,6 +9432,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
@@ -9251,6 +9711,10 @@ _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;
 
@@ -9295,9 +9759,17 @@ 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);
 
+  /* Count local IFUNC symbols.  These need general GOT entries that
+     are fixed-up by explicit IRELATIVE relocations.  */
+  htab_traverse (htab->loc_hash_table, mips_elf_count_general_got_symbols, info);
+
   if (!mips_elf_resolve_final_got_entries (info, g))
     return FALSE;
 
+  g->assigned_general_gotno = htab->reserved_gotno;
+  g->local_gotno += g->general_gotno;
+  g->assigned_low_gotno += g->general_gotno;
+
   /* Calculate the total loadable size of the output.  That
      will give us the maximum number of GOT_PAGE entries
      required.  */
@@ -9739,6 +10211,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)
 	{
@@ -9884,6 +10358,13 @@ _bfd_mips_elf_size_dynamic_sections (bfd *output_bfd,
 	  if (! MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_PLTGOT, 0))
 	    return FALSE;
 	}
+
+      if (htab->got_info->general_gotno > 0)
+	{
+	  if (! MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_GENERAL_GOTNO, 0))
+	    return FALSE;
+	}
+
       if (htab->is_vxworks
 	  && !elf_vxworks_add_dynamic_entries (output_bfd, info))
 	return FALSE;
@@ -10015,6 +10496,7 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
       Elf_Internal_Shdr *symtab_hdr;
       struct elf_link_hash_entry *h;
       bfd_boolean rel_reloc;
+      bfd_boolean local_gnu_ifunc_p = FALSE;
 
       rel_reloc = (NEWABI_P (input_bfd)
 		   && mips_elf_rel_relocation_p (input_bfd, input_section,
@@ -10028,6 +10510,23 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
 	{
 	  sec = local_sections[r_symndx];
 	  h = NULL;
+
+	  Elf_Internal_Sym *isym;
+	  struct mips_elf_link_hash_table *htab;
+	  htab = mips_elf_hash_table (info);
+	  isym = bfd_sym_from_r_symndx (&htab->sym_cache, input_bfd, r_symndx);
+
+	  if (isym == NULL)
+	    return FALSE;
+
+	  /* Relocation is for local STT_GNU_IFUNC symbol.  */
+	  if (isym->st_info == STT_GNU_IFUNC)
+	    {
+	      /* Ensure that we have a hash entry for this symbol.  */
+	      if (get_local_sym_hash (htab, input_bfd, rel) == NULL)
+		return FALSE;
+	      local_gnu_ifunc_p = TRUE;
+	    }
 	}
       else
 	{
@@ -10088,8 +10587,9 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
 		      && mips_elf_local_relocation_p (input_bfd, rel,
 						      local_sections)))
 		{
-		  if (!mips_elf_add_lo16_rel_addend (input_bfd, rel, relend,
-						     contents, &addend))
+		  if (!local_gnu_ifunc_p
+		      && !mips_elf_add_lo16_rel_addend (input_bfd, rel, relend,
+							contents, &addend))
 		    {
 		      if (h)
 			name = h->root.root.string;
@@ -10476,6 +10976,265 @@ 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,
+		      bfd_vma igotplt_address)
+{
+  bfd_byte *loc;
+  const bfd_vma *iplt_entry;
+  bfd_vma high = mips_elf_high (igotplt_address);
+  bfd_vma low = igotplt_address & 0xffff;
+
+  /* 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 (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;
+
+      if (highest)
+	{
+	  /* Full 64-bit address space.  */
+	  bfd_put_32 (output_bfd, iplt_entry[0] | highest, loc);
+	  bfd_put_32 (output_bfd, iplt_entry[1] | high, loc + 4);
+	  bfd_put_32 (output_bfd, iplt_entry[2] | higher, loc + 8);
+	  bfd_put_32 (output_bfd, iplt_entry[3], 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 (higher)
+	{
+	  /* 48-bit address space.  */
+	  iplt_entry = mips64_48b_exec_iplt_entry;
+	  bfd_put_32 (output_bfd, iplt_entry[0] | higher, loc);
+	  bfd_put_32 (output_bfd, iplt_entry[1] | high, loc + 4);
+	  bfd_put_32 (output_bfd, iplt_entry[2], loc + 8);
+	  bfd_put_32 (output_bfd, iplt_entry[3] | low, loc + 12);
+	  bfd_put_32 (output_bfd, iplt_entry[4], loc + 16);
+	  bfd_put_32 (output_bfd, iplt_entry[5], loc + 20);
+	}
+      else
+	{
+	  /* 32-bit address space.  */
+	  bfd_put_32 (output_bfd, iplt_entry[0] | high, loc);
+	  bfd_put_32 (output_bfd, iplt_entry[5] | low, loc + 4);
+	  bfd_put_32 (output_bfd, iplt_entry[6], loc + 8);
+	  bfd_put_32 (output_bfd, iplt_entry[7], loc + 12);
+	}
+    }
+  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
+    {
+      if (MIPSR6_P(output_bfd))
+	iplt_entry = mips32r6_exec_iplt_entry;
+      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;
+}
+
+/* Find local GOT index for VALUE. Return -1 if no GOT slot is found.  */
+
+static bfd_vma
+mips_elf_check_local_got_index (bfd *abfd, struct bfd_link_info *info,
+				struct mips_elf_link_hash_entry *h,
+				bfd_vma value)
+{
+  struct mips_got_entry lookup, *entry;
+  void **loc;
+  struct mips_got_info *g;
+  struct mips_elf_link_hash_table *htab;
+
+  htab = mips_elf_hash_table (info);
+  BFD_ASSERT (htab != NULL);
+
+  g = mips_elf_bfd_got (abfd, FALSE);
+  BFD_ASSERT (g != NULL);
+
+  /* Check for existing local GOT entry.  */
+  lookup.abfd = NULL;
+  lookup.symndx = -1;
+  lookup.d.address = value;
+  lookup.tls_type = GOT_TLS_NONE;
+  loc = htab_find_slot (g->got_entries, &lookup, NO_INSERT);
+
+  if (loc && *loc)
+    entry = (struct mips_got_entry *) *loc;
+  else
+    {
+      /* Need to create and initialize a new GOT entry.  We only get here
+	 global IFUNC symbol and we need distinct hashes entries for aliased
+	 symbols.  */
+      lookup.symndx = h->root.dynindx;
+      lookup.abfd = abfd;
+      if (h->root.dynindx < 0)
+	lookup.d.h = h;
+      loc = htab_find_slot (g->got_entries, &lookup, INSERT);
+
+      if (!loc)
+	return -1;
+
+      entry = (struct mips_got_entry *) bfd_alloc (abfd, sizeof (*entry));
+      if (!entry)
+	return -1;
+
+      lookup.gotidx = MIPS_ELF_GOT_SIZE (abfd) * g->assigned_general_gotno++;
+      *entry = lookup;
+      *loc = entry;
+      MIPS_ELF_PUT_WORD (abfd, value, htab->sgot->contents + entry->gotidx);
+    }
+
+  return entry->gotidx;
+}
+
+/* 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_vma igotplt_address = 0;
+  int igot_offset = -1;
+  asection *gotsect, *relsect;
+  bfd_vma value = sym->st_value;
+
+  if (MIPS16_P (output_bfd) || MICROMIPS_P (output_bfd))
+    value |= 1;
+
+  if (!hmips->needs_iplt)
+    {
+      gotsect = htab->sgot;
+      /* Check if IFUNC symbol already has an assigned GOT slots; assign
+	 a new slot if necessary.  */
+      igot_offset = mips_elf_check_local_got_index (output_bfd, info,
+						    hmips, value);
+      if (igot_offset < 0)
+	igot_offset = mips_elf_local_got_index (output_bfd, output_bfd, info,
+						value, hmips->root.dynindx,
+						hmips, R_MIPS_REL32);
+    }
+  else
+    {
+      bfd_byte *loc;
+      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, value, loc);
+      else
+	bfd_put_32 (output_bfd, value, loc);
+    }
+
+  igotplt_address = (gotsect->output_section->vma + gotsect->output_offset
+		     + igot_offset);
+
+  relsect = mips_get_irel_section (info, htab);
+
+  if (igot_offset >= 0)
+    {
+      if (hmips->needs_iplt && relsect->contents == NULL)
+	{
+	  /* Allocate memory for the relocation section contents.  */
+	  relsect->contents = bfd_zalloc (dynobj, relsect->size);
+	  if (relsect->contents == NULL)
+	    return FALSE;
+	}
+
+      if (hmips->needs_iplt || hmips->root.dynindx < 0)
+	/* Emit an R_MIPS_IRELATIVE relocation against the [I]GOT entry.  */
+	mips_elf_output_dynamic_relocation (output_bfd, relsect,
+					    relsect->reloc_count++, 0,
+					    R_MIPS_IRELATIVE, igotplt_address);
+      else
+	{
+	  /* Emit an R_MIPS_REL32 relocation against global GOT entry for
+	     a preemptible symbol.  */
+	  asection *sec = hmips->root.root.u.def.section;
+	  Elf_Internal_Rela rel[3];
+
+	  memset (rel, 0, sizeof (rel));
+	  rel[0].r_info = ELF_R_INFO (output_bfd, 0, R_MIPS_REL32);
+	  rel[0].r_offset = rel[1].r_offset = rel[2].r_offset = igot_offset;
+
+	  mips_elf_create_dynamic_relocation (output_bfd, info, rel, hmips,
+					      sec, value, NULL,
+					      gotsect);
+	}
+    }
+  /* If necessary, generate the corresponding .iplt entry.  */
+  if (hmips->needs_iplt
+      && !mips_elf_create_iplt (output_bfd, htab, hmips, igotplt_address))
+    return FALSE;
+
+  return TRUE;
+}
+
 /* Finish up dynamic symbol handling.  We set the contents of various
    dynamic sections here.  */
 
@@ -10803,6 +11562,19 @@ _bfd_mips_elf_finish_dynamic_symbol (bfd *output_bfd,
       sym->st_other = other;
     }
 
+  if (hmips->root.type == STT_GNU_IFUNC)
+    {
+      if (hmips->needs_ireloc
+	  && !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 +11738,39 @@ _bfd_mips_elf_finish_dynamic_symbol (bfd *output_bfd,
       sym->st_other -= STO_MICROMIPS;
     }
 
+  if (hmips->needs_iplt)
+    {
+      /* 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;
+  isym.st_other = h->root.other;
+  if (ELF_ST_IS_COMPRESSED (isym.st_other))
+    isym.st_value |= 1;
+
+  return _bfd_mips_elf_finish_dynamic_symbol (info->output_bfd, info,
+					      &h->root, &isym);
+}
+
 /* Likewise, for VxWorks.  */
 
 bfd_boolean
@@ -11354,6 +12156,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;
@@ -11524,6 +12333,10 @@ _bfd_mips_elf_finish_dynamic_sections (bfd *output_bfd,
 	      dyn.d_un.d_ptr = s->vma;
 	      break;
 
+	    case DT_MIPS_GENERAL_GOTNO:
+	      dyn.d_un.d_val = htab->reserved_gotno + gg->general_gotno;
+	      break;
+
 	    case DT_RELASZ:
 	      BFD_ASSERT (htab->is_vxworks);
 	      /* The count does not include the JUMP_SLOT relocations.  */
@@ -13858,6 +14671,48 @@ _bfd_mips_elf_relax_section (bfd *abfd, asection *sec,
   return FALSE;
 }
 \f
+/* 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 *
@@ -13881,6 +14736,16 @@ _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;
 }
 
@@ -15498,7 +16363,9 @@ _bfd_mips_elf_get_target_dtag (bfd_vma dtag)
       return "DT_MIPS_PLTGOT";
     case DT_MIPS_RWPLT:
       return "DT_MIPS_RWPLT";
-    }
+    case DT_MIPS_GENERAL_GOTNO:
+      return "DT_MIPS_GENERAL_GOTNO";
+   }
 }
 
 /* Return the meaning of Tag_GNU_MIPS_ABI_FP value FP, or null if
@@ -16130,6 +16997,9 @@ _bfd_mips_post_process_headers (bfd *abfd, struct bfd_link_info *link_info)
   if (mips_elf_tdata (abfd)->abiflags.fp_abi == Val_GNU_MIPS_ABI_FP_64
       || mips_elf_tdata (abfd)->abiflags.fp_abi == Val_GNU_MIPS_ABI_FP_64A)
     i_ehdrp->e_ident[EI_ABIVERSION] = 3;
+
+  if (elf_tdata (abfd)->has_gnu_symbols & elf_gnu_symbol_ifunc)
+    i_ehdrp->e_ident[EI_ABIVERSION] = 4;
 }
 
 int
diff --git a/bfd/libbfd.h b/bfd/libbfd.h
index fc70e29..2960a14 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 caa6fb4..d47427e 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 6298f1e..ce05ac3 100644
--- a/binutils/readelf.c
+++ b/binutils/readelf.c
@@ -1718,6 +1718,8 @@ 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_GENERAL_GOTNO: return "MIPS_GENERAL_GOTNO";
+
     default:
       return NULL;
     }
@@ -8508,6 +8510,7 @@ dynamic_section_mips_val (Elf_Internal_Dyn * entry)
     case DT_MIPS_DELTA_SYM_NO:
     case DT_MIPS_DELTA_CLASSSYM_NO:
     case DT_MIPS_COMPACT_SIZE:
+    case DT_MIPS_GENERAL_GOTNO:
       print_vma (entry->d_un.d_ptr, DEC);
       break;
 
diff --git a/elfcpp/elfcpp.h b/elfcpp/elfcpp.h
index 722984e..f919fb8 100644
--- a/elfcpp/elfcpp.h
+++ b/elfcpp/elfcpp.h
@@ -866,6 +866,8 @@ enum DT
   DT_MIPS_PLTGOT = 0x70000032,
   // Points to the base of a writable PLT.
   DT_MIPS_RWPLT = 0x70000034,
+  // The GOT index of the first implicitly relocated GOT entry.
+  DT_MIPS_GENERAL_GOTNO = 0x70000036,
 
   DT_AUXILIARY = 0x7ffffffd,
   DT_USED = 0x7ffffffe,
diff --git a/include/elf/mips.h b/include/elf/mips.h
index 57de3bc..962d4d0 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)
@@ -751,6 +754,10 @@ extern void bfd_mips_elf32_swap_reginfo_out
 
 /* Relative offset of run time loader map, used for debugging.  */
 #define DT_MIPS_RLD_MAP_REL    0x70000035
+
+/* The GOT index of the first implicitly relocated GOT entry.  */
+#define DT_MIPS_GENERAL_GOTNO  0x70000036
+
 \f
 /* Flags which may appear in a DT_MIPS_FLAGS entry.  */
 
-- 
1.7.9.5

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC] Add IFUNC support for MIPS (v3)
  2015-09-21 18:50             ` [RFC] Add IFUNC support for MIPS (v3) Faraz Shahbazker
@ 2015-09-27 11:23               ` Richard Sandiford
  2015-10-13 15:50                 ` Faraz Shahbazker
  0 siblings, 1 reply; 35+ messages in thread
From: Richard Sandiford @ 2015-09-27 11:23 UTC (permalink / raw)
  To: Faraz Shahbazker; +Cc: binutils

Hi Faraz,

Thanks for the patch.  Some general comments first:

I know you borrowed it from the x86 port, but I'm not really a fan of
(ab?)using link_hash_entry for local symbols.  It just seems likely
to create confusion when so many of the fields don't apply or don't
carry useful information, especially with the hack:

   We reuse indx and dynstr_index for local symbol hash since they
   aren't used by global symbols in this backend.

I prefer the way the powerpc port handles it (which I borrowed for
AArch32).  I won't ask you to change it though.

I don't think this handles multigot correctly, but you probably
alreadly know that :-)

Faraz Shahbazker <faraz.shahbazker@imgtec.com> writes:
> +/* Format of 32 bit IPLT entries for R6. JR encoding differs.  */
> +static const bfd_vma mips32r6_exec_iplt_entry[] =
> +{
> +  0x3c0f0000,   /* lui $15, %hi(.igot address)		*/
> +  0x8df90000,   /* lw  $25, %lo(.igot address)($15)	*/
> +  0x03200009,   /* jr  $25				*/
> +  0x00000000    /* nop					*/
> +};

No need to change, just curious: why not use JRC here?

> +/* 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					*/
> +};

"lw" rather than "ld".

> @@ -1582,7 +1678,8 @@ 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))
> +  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.  */
> @@ -1598,6 +1695,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;
>  }
>  

This isn't really a property of the hash table symbol but of the kind of
stub being created by the calling code.  I think it would be better to
pass an "st_other" parameter alongside the other stub properties.

> +/* Return section for R_MIPS_IRELATIVE relocations.  If the link is
[...]
> +/* Reserve space in the rel.iplt section for IRELATIVE relocation.  */

Would be good to be consistent about whether we use R_MIPS_ prefix in
comments or not.  I don't have a preference for which.

s/for/for an/ in the second comment.

> +/* 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
> +      && h->root.def_regular)
> +    {
> +      struct bfd_link_info *info = hti->info;
> +      elf_tdata (info->output_bfd)->has_gnu_symbols |= elf_gnu_symbol_ifunc;
> +
> +      /* .iplt entry is needed only for executable objects.  */
> +      if (!bfd_link_pic (info) &&
> +	  !mips_elf_allocate_iplt (info, mips_elf_hash_table (info), h))
> +	return FALSE;
> +
> +      /* IRELATIVE fixup will be needed for each local IFUNC.  */
> +      if (!mips_elf_allocate_ireloc (info, mips_elf_hash_table (info), h))
> +	return FALSE;
> +    }
> +
> +  return TRUE;
> +}

There are two copies of this, so please split it out into a subroutine.

Formatting: && should go on the start of the next line.  (The other copy
was correct.)

I might be missing something, but I think for local symbols the
condition for whether an IPLT is needed should be h->has_static_relocs
rather than !bfd_link_pic.  E.g., if an executable uses GOT and CALL
relocs for all references to a symbol, it would be more efficient not
to have an IPLT and simply use an entry in the general GOT area
with an IRELATIVE relocation against it.

I think this routine should only allocate a relocation for .igot,
i.e. only in cases where an iplt entry is also allocated.  In other
cases the code is effectively allocating a relocation for the general
GOT area, but there's no guarantee at this stage that the primary GOT
will need such a relocation.  (All references end up in secondary GOTs.)
Instead I think we should allocate GOT relocations at the same time as
counting them, like we do for TLS GOT entries.

Also, returning false on error from a traversal callback isn't enough,
because that just stops the traversal.  We need to indicate to the caller
that a fatal error has occurred so that the caller can return false too.
In mips_elf_check_symbols this is done by setting hti->error.

> @@ -3263,9 +3478,12 @@ mips_elf_count_got_entry (struct bfd_link_info *info,
>  					entry->symndx < 0
>  					? &entry->d.h->root : NULL);
>      }
> -  else if (entry->symndx >= 0 || entry->d.h->global_got_area == GGA_NONE)
> +  /* Skip IFUNCs from local/global GOT, they are already counted as general
> +     GOT entries with explicit relocations.  */
> +  else if (entry->symndx >= 0 || (entry->d.h->global_got_area == GGA_NONE
> +				  && !entry->d.h->needs_ireloc))
>      g->local_gotno += 1;
> -  else
> +  else if (!entry->d.h->needs_ireloc)
>      g->global_gotno += 1;
>  }

I think this is the place where we should be setting general_gotno,
rather than mips_elf_count_got_symbols.  mips_elf_count_got_symbols
is supposed to:

   Count the number of global symbols that are in the primary GOT only
   because they have relocations against them (reloc_only_gotno).

and shouldn't be counting explicit GOT entries.

> @@ -3711,7 +3930,8 @@ mips_elf_create_local_got_entry (bfd *abfd, struct bfd_link_info *info,
>    if (entry)
>      return entry;
>  
> -  if (g->assigned_low_gotno > g->assigned_high_gotno)
> +  if (g->assigned_low_gotno > g->assigned_high_gotno ||
> +      g->assigned_general_gotno > g->assigned_low_gotno)
>      {
>        /* We didn't allocate enough space in the GOT.  */
>        (*_bfd_error_handler)

Formatting: || should be on the next line.  I don't think this works
as intended though.  The existing check is OK because we allocate locals
upwards and globals downwards, so checking for overlap is a good test.
General GOT entries and local GOT entries are both allocated upwards,
so checking for overlap there isn't strong enough.

> @@ -3952,7 +4176,11 @@ mips_elf_record_global_got_symbol (struct elf_link_hash_entry *h,
>      }
>  
>    tls_type = mips_elf_reloc_tls_type (r_type);
> -  if (tls_type == GOT_TLS_NONE && hmips->global_got_area > GGA_NORMAL)
> +  /* IFUNC symbols use explicitly relocated GOT region, instead of the ABI
> +     global GOT, but we don't distinguish these from the local GOT region
> +     just yet.  */
> +  if (tls_type == GOT_TLS_NONE && hmips->global_got_area > GGA_NORMAL
> +      && !hmips->needs_ireloc)
>      hmips->global_got_area = GGA_NORMAL;

Formatting: one condition to a line once it gets too long for a single line.
(Yeah, not always followed...)

I don't see how this can trigger though.  mips_elf_record_global_got_symbol
is only called while checking the relocations in the input bfds, to record
things that _might_ need a global GOT entry.  needs_ireloc is only set after
that.

Not needing to change this function would be a good sign.  When looking
at a global symbol in one input bfd, we don't know whether some later bfd
that we haven't analysed yet defines that symbol as an ifunc or not.

> @@ -4398,7 +4626,9 @@ mips_use_local_got_p (struct bfd_link_info *info,
>       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)
> +  /* Both global & local IFUNC symbols actually use the explicitly relocated
> +     GOT region, but we don't distinguish it from local GOT just yet.  */
> +  if (h->root.dynindx == -1 || h->needs_ireloc)
>      return TRUE;
>  
>    /* Symbols that bind locally can (and in the case of forced-local

s/from local GOT/from the local GOT/.

> @@ -4455,6 +4685,33 @@ mips_elf_count_got_symbols (struct mips_elf_link_hash_entry *h, void *data)
> +/* A elf_link_hash_traverse callback for which INF points to the
> +   link_info structure.  Count the number of GOT entries that need
> +   explicit relocations by iterating over the local hash table.  */
> +
> +static int
> +mips_elf_count_general_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;
> +
> +  info = (struct bfd_link_info *) inf;
> +  htab = mips_elf_hash_table (info);
> +
> +  g = htab->got_info;
> +  if (h != NULL && h->needs_ireloc)
> +    g->general_gotno++;
>    return 1;
>  }

Here too I think we should be relying on mips_elf_count_got_entry and
counting GOT entries, rather than counting based on symbols.

> +/* 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 (!bfd_link_pic (info))
> +    {
> +      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));

Space before "?".

> +    }
> +
> +  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;

Do we need to create .iplt and .igot unconditionally when they won't
be used for PIC objects?  If so, please add a comment, otherwise I think
the creation of those sections should be protected by !bfd_link_pic too.

The MIPS16_ and MICROMIPS_p assignments should be indented by a single tab,
like for the other two.

> @@ -5200,6 +5515,75 @@ 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");

I'm not sure it's valid to assume that every input has a .text section.
At least we should have some error code if it doesn't.

But I suppose this comes back to my objection to the "local hash" stuff.
It just seems wrong to pick an arbitrary section index, regardless of
where the symbol is actually defined.

> +  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;

Double space.

> +  namep = bfd_elf_string_from_elf_section (abfd, symtab_hdr->sh_link,
> +					   isym->st_name);
> +
> +  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;
> +      if (MIPS16_P (abfd))
> +	ret->root.other = STO_MIPS16;
> +      if (MICROMIPS_P (abfd))
> +	ret->root.other = STO_MICROMIPS;

I don't think this is a safe assumption.  Why not just set root.other
from st_other?

(Remember that abfd is the input here.)

> @@ -5554,6 +5947,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)
> +    {
> +      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;
> +    }

I think you need to set target_is_16_bit_code_p and
target_is_micromips_code_p too (based on the equivalent conditions
for abfd).

I'm a bit nervous about how this interacts with the other redirections,
e.g. for la25 and mips16 hard-float stubs.

> @@ -5649,6 +6053,16 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
>  	      BFD_ASSERT (h->root.needs_plt);
>  	      g = mips_elf_gotplt_index (info, &h->root);
>  	    }
> +	  /* IFUNCs use explicit GOT, however we don't distinguish it
> +	     from local GOT at this stage.  */

s/from local GOT/from the local GOT/.  "the explicitly-relocated GOT"
that you used elsewhere seems more descriptive.

> @@ -5940,8 +6354,11 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
>  	 R_MIPS*_GOT16; every relocation evaluates to "G".  */
>        if (!htab->is_vxworks && local_p)
>  	{
> +	  /* Local IFUNC symbols must be accessed through GOT, similar to
> +	     global symbols, to allow for indirection.  */
>  	  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

I agree this is correct, but I think it should be specified explicitly
in the ABI document that R_MIPS_GOT16 relocations against local IFUNC
symbols are treated in this way.  Using the usual R_MIPS_GOT16/R_MIPS_LO16
pair for local symbols would give the wrong result for ifuncs.

> @@ -7016,6 +7445,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;

Double space.

> @@ -7929,6 +8360,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);

After the above comment about not creating .iplt and .igot unnecessarily,
we could use irelplt as the key instead.

> @@ -10088,8 +10587,9 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
>  		      && mips_elf_local_relocation_p (input_bfd, rel,
>  						      local_sections)))
>  		{
> -		  if (!mips_elf_add_lo16_rel_addend (input_bfd, rel, relend,
> -						     contents, &addend))
> +		  if (!local_gnu_ifunc_p
> +		      && !mips_elf_add_lo16_rel_addend (input_bfd, rel, relend,
> +							contents, &addend))
>  		    {
>  		      if (h)
>  			name = h->root.root.string;

I think conceptually this applies only to the got16_reloc_p case:

	      if (hi16_reloc_p (r_type)
		  || (got16_reloc_p (r_type)
		      && !local_gnu_ifunc_p
		      && mips_elf_local_relocation_p (input_bfd, rel,
						      local_sections)))

Obviously addends don't make much sense for ifuncs, but the LO16 addend
for a HI16 does logically still exist.  We would need to read it if we did
ever add code to raise an error for nonzero addends in future.

> @@ -10476,6 +10976,265 @@ 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,
> +		      bfd_vma igotplt_address)
> +{
> [...]
> +  else
> +    {
> +      if (MIPSR6_P(output_bfd))

Space before "(".

> +/* Find local GOT index for VALUE. Return -1 if no GOT slot is found.  */
> +
> +static bfd_vma
> +mips_elf_check_local_got_index (bfd *abfd, struct bfd_link_info *info,
> +				struct mips_elf_link_hash_entry *h,
> +				bfd_vma value)
> +{
> +  struct mips_got_entry lookup, *entry;
> +  void **loc;
> +  struct mips_got_info *g;
> +  struct mips_elf_link_hash_table *htab;
> +
> +  htab = mips_elf_hash_table (info);
> +  BFD_ASSERT (htab != NULL);
> +
> +  g = mips_elf_bfd_got (abfd, FALSE);
> +  BFD_ASSERT (g != NULL);
> +
> +  /* Check for existing local GOT entry.  */
> +  lookup.abfd = NULL;
> +  lookup.symndx = -1;
> +  lookup.d.address = value;
> +  lookup.tls_type = GOT_TLS_NONE;
> +  loc = htab_find_slot (g->got_entries, &lookup, NO_INSERT);
> +
> +  if (loc && *loc)
> +    entry = (struct mips_got_entry *) *loc;
> +  else
> +    {
> +      /* Need to create and initialize a new GOT entry.  We only get here
> +	 global IFUNC symbol and we need distinct hashes entries for aliased
> +	 symbols.  */
> +      lookup.symndx = h->root.dynindx;
> +      lookup.abfd = abfd;
> +      if (h->root.dynindx < 0)
> +	lookup.d.h = h;
> +      loc = htab_find_slot (g->got_entries, &lookup, INSERT);
> +
> +      if (!loc)
> +	return -1;
> +
> +      entry = (struct mips_got_entry *) bfd_alloc (abfd, sizeof (*entry));
> +      if (!entry)
> +	return -1;
> +
> +      lookup.gotidx = MIPS_ELF_GOT_SIZE (abfd) * g->assigned_general_gotno++;
> +      *entry = lookup;
> +      *loc = entry;
> +      MIPS_ELF_PUT_WORD (abfd, value, htab->sgot->contents + entry->gotidx);
> +    }
> +
> +  return entry->gotidx;
> +}
> +
> +/* 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_vma igotplt_address = 0;
> +  int igot_offset = -1;
> +  asection *gotsect, *relsect;
> +  bfd_vma value = sym->st_value;
> +
> +  if (MIPS16_P (output_bfd) || MICROMIPS_P (output_bfd))
> +    value |= 1;
> +
> +  if (!hmips->needs_iplt)
> +    {
> +      gotsect = htab->sgot;
> +      /* Check if IFUNC symbol already has an assigned GOT slots; assign
> +	 a new slot if necessary.  */
> +      igot_offset = mips_elf_check_local_got_index (output_bfd, info,
> +						    hmips, value);
> +      if (igot_offset < 0)
> +	igot_offset = mips_elf_local_got_index (output_bfd, output_bfd, info,
> +						value, hmips->root.dynindx,
> +						hmips, R_MIPS_REL32);
> +    }

I don't think we should be handling this case here.  It's logically
per got entry rather than per symbol, i.e. for multigot we might need
several IRELATIVE GOT relocations.

This is in any case too late to be allocating new GOT entries.
We've already laid out the output object at this point.

> +  else
> +    {
> +      bfd_byte *loc;
> +      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);

Formatting:

      loc = ((bfd_byte *) gotsect->contents
	     + igot_index * MIPS_ELF_GOT_SIZE (dynobj));


> +
> +      if (ABI_64_P (output_bfd))
> +	bfd_put_64 (output_bfd, value, loc);
> +      else
> +	bfd_put_32 (output_bfd, value, loc);
> +    }
> +
> +  igotplt_address = (gotsect->output_section->vma + gotsect->output_offset
> +		     + igot_offset);
> +
> +  relsect = mips_get_irel_section (info, htab);
> +
> +  if (igot_offset >= 0)
> +    {
> +      if (hmips->needs_iplt && relsect->contents == NULL)
> +	{

Isn't needs_iplt always true in this case?

> +	  /* Allocate memory for the relocation section contents.  */
> +	  relsect->contents = bfd_zalloc (dynobj, relsect->size);
> +	  if (relsect->contents == NULL)
> +	    return FALSE;
> +	}
> +
> +      if (hmips->needs_iplt || hmips->root.dynindx < 0)

Same here.  Unless I'm missing something, the preemptible case should
never actually be used for MIPS, since we only have iplts for executables.

> +	/* Emit an R_MIPS_IRELATIVE relocation against the [I]GOT entry.  */
> +	mips_elf_output_dynamic_relocation (output_bfd, relsect,
> +					    relsect->reloc_count++, 0,
> +					    R_MIPS_IRELATIVE, igotplt_address);
> +      else
> +	{
> +	  /* Emit an R_MIPS_REL32 relocation against global GOT entry for
> +	     a preemptible symbol.  */
> +	  asection *sec = hmips->root.root.u.def.section;
> +	  Elf_Internal_Rela rel[3];
> +
> +	  memset (rel, 0, sizeof (rel));
> +	  rel[0].r_info = ELF_R_INFO (output_bfd, 0, R_MIPS_REL32);
> +	  rel[0].r_offset = rel[1].r_offset = rel[2].r_offset = igot_offset;
> +
> +	  mips_elf_create_dynamic_relocation (output_bfd, info, rel, hmips,
> +					      sec, value, NULL,
> +					      gotsect);
> +	}
> +    }
> +  /* If necessary, generate the corresponding .iplt entry.  */
> +  if (hmips->needs_iplt
> +      && !mips_elf_create_iplt (output_bfd, htab, hmips, igotplt_address))
> +    return FALSE;
> +
> +  return TRUE;
> +}
> +
>  /* Finish up dynamic symbol handling.  We set the contents of various
>     dynamic sections here.  */
>  
> @@ -10803,6 +11562,19 @@ _bfd_mips_elf_finish_dynamic_symbol (bfd *output_bfd,
>        sym->st_other = other;
>      }
>  
> +  if (hmips->root.type == STT_GNU_IFUNC)
> +    {
> +      if (hmips->needs_ireloc
> +	  && !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)

Double space.

> +	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 +11738,39 @@ _bfd_mips_elf_finish_dynamic_symbol (bfd *output_bfd,
>        sym->st_other -= STO_MICROMIPS;
>      }
>  
> +  if (hmips->needs_iplt)
> +    {
> +      /* 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;

Formatting:

      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;

Formatting:

  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);

> +  isym.st_other = h->root.other;
> +  if (ELF_ST_IS_COMPRESSED (isym.st_other))
> +    isym.st_value |= 1;
> +
> +  return _bfd_mips_elf_finish_dynamic_symbol (info->output_bfd, info,
> +					      &h->root, &isym);
> +}
> +
>  /* Likewise, for VxWorks.  */
>  
>  bfd_boolean
> @@ -11354,6 +12156,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);
> +  }
> +

Formatting: "{...}" block needs to be indented two more spaces.

> @@ -13858,6 +14671,48 @@ _bfd_mips_elf_relax_section (bfd *abfd, asection *sec,
>    return FALSE;
>  }
>  \f
> +/* 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.  */

/* Compute a hash of a local symbol table entry.  We use elf_link_hash_entry
   for local symbols so that we can handle local STT_GNU_IFUNC symbols
   as global symbols.  We reuse indx and dynstr_index for local symbol
   hash since they aren't used by global symbols in this backend.  */

(note indentation)

> +
> +static hashval_t
> +local_htab_hash (const void *ptr)
> +{
> +  struct  mips_elf_link_hash_entry *h =
> +    (struct  mips_elf_link_hash_entry *) ptr;

Stray space after both "struct"s.

> +  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;

Same here.

> +
> +  return h1->root.indx == h2->root.indx &&
> +    h1->root.dynstr_index == h2->root.dynstr_index;

Formatting, should be:

  return (h1->root.indx == h2->root.indx
	  && h1->root.dynstr_index == h2->root.dynstr_index);

> @@ -16130,6 +16997,9 @@ _bfd_mips_post_process_headers (bfd *abfd, struct bfd_link_info *link_info)
>    if (mips_elf_tdata (abfd)->abiflags.fp_abi == Val_GNU_MIPS_ABI_FP_64
>        || mips_elf_tdata (abfd)->abiflags.fp_abi == Val_GNU_MIPS_ABI_FP_64A)
>      i_ehdrp->e_ident[EI_ABIVERSION] = 3;
> +
> +  if (elf_tdata (abfd)->has_gnu_symbols & elf_gnu_symbol_ifunc)
> +    i_ehdrp->e_ident[EI_ABIVERSION] = 4;

Is it worth also making this explicitly dependent on whether
DT_MIPS_GENERAL_GOTNO is present?  It doesn't make a difference now,
since it's a subcondition of whether ifuncs are used, but it might
be more future-proof.

Thanks,
Richard

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC] Add IFUNC support for MIPS (v3)
  2015-09-27 11:23               ` Richard Sandiford
@ 2015-10-13 15:50                 ` Faraz Shahbazker
  2015-11-02 20:40                   ` Faraz Shahbazker
  2015-11-08 12:47                   ` Richard Sandiford
  0 siblings, 2 replies; 35+ messages in thread
From: Faraz Shahbazker @ 2015-10-13 15:50 UTC (permalink / raw)
  To: binutils, rdsandiford

Hi Richard,

Thank you for the detailed review. I've incorporated most of your suggestions.
I need clarification on a few points, before I re-post the patch.


On 09/27/2015 04:23 AM, Richard Sandiford wrote:
> I know you borrowed it from the x86 port, but I'm not really a fan of
> (ab?)using link_hash_entry for local symbols.  It just seems likely
> to create confusion when so many of the fields don't apply or don't
> carry useful information, especially with the hack:
> 
>    We reuse indx and dynstr_index for local symbol hash since they
>    aren't used by global symbols in this backend.
> 
> I prefer the way the powerpc port handles it (which I borrowed for
> AArch32).  I won't ask you to change it though.

I did look around a bit, but could relate best to x86. There were too many
unrelated [to IFUNC] differences in the other architectures.

> I don't think this handles multigot correctly, but you probably
> alreadly know that :-)

Yes.

> Faraz Shahbazker <faraz.shahbazker@imgtec.com> writes:
>> +/* Format of 32 bit IPLT entries for R6. JR encoding differs.  */
>> +static const bfd_vma mips32r6_exec_iplt_entry[] =
>> +{
>> +  0x3c0f0000,   /* lui $15, %hi(.igot address)		*/
>> +  0x8df90000,   /* lw  $25, %lo(.igot address)($15)	*/
>> +  0x03200009,   /* jr  $25				*/
>> +  0x00000000    /* nop					*/
>> +};
> No need to change, just curious: why not use JRC here?

I assume you meant this for the micromips32 stub? Will modify it.

>> +/* 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
>> +      && h->root.def_regular)
>> +    {
>> +      struct bfd_link_info *info = hti->info;
>> +      elf_tdata (info->output_bfd)->has_gnu_symbols |= elf_gnu_symbol_ifunc;
>> +
>> +      /* .iplt entry is needed only for executable objects.  */
>> +      if (!bfd_link_pic (info) &&
>> +	  !mips_elf_allocate_iplt (info, mips_elf_hash_table (info), h))
>> +	return FALSE;
>> +
>> +      /* IRELATIVE fixup will be needed for each local IFUNC.  */
>> +      if (!mips_elf_allocate_ireloc (info, mips_elf_hash_table (info), h))
>> +	return FALSE;
>> +    }
>> +
>> +  return TRUE;
>> +}
>
> I might be missing something, but I think for local symbols the
> condition for whether an IPLT is needed should be h->has_static_relocs
> rather than !bfd_link_pic.  E.g., if an executable uses GOT and CALL
> relocs for all references to a symbol, it would be more efficient not
> to have an IPLT and simply use an entry in the general GOT area
> with an IRELATIVE relocation against it.

has_static_relocs does not currently track local IFUNCs or relocations in 
data sections. The first seems easy to fix, but I couldn't figure out the latter.

>> @@ -3263,9 +3478,12 @@ mips_elf_count_got_entry (struct bfd_link_info *info,
>>  					entry->symndx < 0
>>  					? &entry->d.h->root : NULL);
>>      }
>> -  else if (entry->symndx >= 0 || entry->d.h->global_got_area == GGA_NONE)
>> +  /* Skip IFUNCs from local/global GOT, they are already counted as general
>> +     GOT entries with explicit relocations.  */
>> +  else if (entry->symndx >= 0 || (entry->d.h->global_got_area == GGA_NONE
>> +				  && !entry->d.h->needs_ireloc))
>>      g->local_gotno += 1;
>> -  else
>> +  else if (!entry->d.h->needs_ireloc)
>>      g->global_gotno += 1;
>>  }
> 
> I think this is the place where we should be setting general_gotno,
> rather than mips_elf_count_got_symbols.  mips_elf_count_got_symbols
> is supposed to:
> 
>    Count the number of global symbols that are in the primary GOT only
>    because they have relocations against them (reloc_only_gotno).
> 
> and shouldn't be counting explicit GOT entries.

Are we not expected to have a GOT entry for a global IFUNC symbol if  there are
no references to it (say within a shared library)? Something in your discussions
with jcarter led me to believe that this was expected. If not it can removed.
As it is, these entries do not factor in to the ABI global GOT mechanism.

>> @@ -5200,6 +5515,75 @@ 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");
> 
> I'm not sure it's valid to assume that every input has a .text section.
> At least we should have some error code if it doesn't.
> 
> But I suppose this comes back to my objection to the "local hash" stuff.
> It just seems wrong to pick an arbitrary section index, regardless of
> where the symbol is actually defined.

I see the pitfall of ".text". I think I'll stick to the i386 method, which is 
to pick the first section id.

>> @@ -5554,6 +5947,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)
>> +    {
>> +      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;
>> +    }
> 
> I think you need to set target_is_16_bit_code_p and
> target_is_micromips_code_p too (based on the equivalent conditions
> for abfd).
> 
> I'm a bit nervous about how this interacts with the other redirections,
> e.g. for la25 and mips16 hard-float stubs.

I  will dig further in to this. Any hints on what I should look for?

>> @@ -5940,8 +6354,11 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
>>  	 R_MIPS*_GOT16; every relocation evaluates to "G".  */
>>        if (!htab->is_vxworks && local_p)
>>  	{
>> +	  /* Local IFUNC symbols must be accessed through GOT, similar to
>> +	     global symbols, to allow for indirection.  */
>>  	  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
> 
> I agree this is correct, but I think it should be specified explicitly
> in the ABI document that R_MIPS_GOT16 relocations against local IFUNC
> symbols are treated in this way.  Using the usual R_MIPS_GOT16/R_MIPS_LO16
> pair for local symbols would give the wrong result for ifuncs.

I am not quite sure which ABI document you mean, but we are planning a change
in gcc to reflect this. It shouldn't be generating GOT+LO sequences for IFUNCs.
In theory, LO16(ifunc) is nonsensical for PIC, but we can force it to zero 
just to be defensive.

> 
>> +  else
>> +    {
>> +      bfd_byte *loc;
>> +      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, value, loc);
>> +      else
>> +	bfd_put_32 (output_bfd, value, loc);
>> +    }
>> +
>> +  igotplt_address = (gotsect->output_section->vma + gotsect->output_offset
>> +		     + igot_offset);
>> +
>> +  relsect = mips_get_irel_section (info, htab);
>> +
>> +  if (igot_offset >= 0)
>> +    {
>> +      if (hmips->needs_iplt && relsect->contents == NULL)
>> +	    {
> 
> Isn't needs_iplt always true in this case?

No. The code is generalized so that igot_offset could mean got_offset or 
igot_offset. For the former, needs_plt is false. In other words, needs_iplt implies 
(igot_offset >= 0), but not the converse. OTOH, memory for contents is allocated
as usual in _bfd_mips_elf_size_dynamic_sections so I could just replace this block
with an assertion.

>> +	  /* Allocate memory for the relocation section contents.  */
>> +	  relsect->contents = bfd_zalloc (dynobj, relsect->size);
>> +	  if (relsect->contents == NULL)
>> +	    return FALSE;
>> +	}
>> +
>> +      if (hmips->needs_iplt || hmips->root.dynindx < 0)
> 
> Same here.  Unless I'm missing something, the preemptible case should
> never actually be used for MIPS, since we only have iplts for executables.

As above. igot_offset is actually [i]got_offset. Pre-emption is needed for
global IFUNC which is defined and referenced in a shared libraries. In that
case there would be a general GOT entry with a symbolic REL32 fixup against it.

>> @@ -16130,6 +16997,9 @@ _bfd_mips_post_process_headers (bfd *abfd, struct bfd_link_info *link_info)
>>    if (mips_elf_tdata (abfd)->abiflags.fp_abi == Val_GNU_MIPS_ABI_FP_64
>>        || mips_elf_tdata (abfd)->abiflags.fp_abi == Val_GNU_MIPS_ABI_FP_64A)
>>      i_ehdrp->e_ident[EI_ABIVERSION] = 3;
>> +
>> +  if (elf_tdata (abfd)->has_gnu_symbols & elf_gnu_symbol_ifunc)
>> +    i_ehdrp->e_ident[EI_ABIVERSION] = 4;
> 
> Is it worth also making this explicitly dependent on whether
> DT_MIPS_GENERAL_GOTNO is present?  It doesn't make a difference now,
> since it's a subcondition of whether ifuncs are used, but it might
> be more future-proof.

Yes, I think that would be good. As things stand, the higher ABI version also
gets attached to static executables where we don't really care about it.

Regards,
Faraz Shahbazker

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC] Add IFUNC support for MIPS (v3)
  2015-10-13 15:50                 ` Faraz Shahbazker
@ 2015-11-02 20:40                   ` Faraz Shahbazker
  2015-11-08 12:47                   ` Richard Sandiford
  1 sibling, 0 replies; 35+ messages in thread
From: Faraz Shahbazker @ 2015-11-02 20:40 UTC (permalink / raw)
  To: binutils, rdsandiford

Bump.

On 10/13/2015 08:49 AM, Faraz Shahbazker wrote:
> Hi Richard,
> 
> Thank you for the detailed review. I've incorporated most of your suggestions.
> I need clarification on a few points, before I re-post the patch.
> 
> 
> On 09/27/2015 04:23 AM, Richard Sandiford wrote:
>> I know you borrowed it from the x86 port, but I'm not really a fan of
>> (ab?)using link_hash_entry for local symbols.  It just seems likely
>> to create confusion when so many of the fields don't apply or don't
>> carry useful information, especially with the hack:
>>
>>    We reuse indx and dynstr_index for local symbol hash since they
>>    aren't used by global symbols in this backend.
>>
>> I prefer the way the powerpc port handles it (which I borrowed for
>> AArch32).  I won't ask you to change it though.
> 
> I did look around a bit, but could relate best to x86. There were too many
> unrelated [to IFUNC] differences in the other architectures.
> 
>> I don't think this handles multigot correctly, but you probably
>> alreadly know that :-)
> 
> Yes.
> 
>> Faraz Shahbazker <faraz.shahbazker@imgtec.com> writes:
>>> +/* Format of 32 bit IPLT entries for R6. JR encoding differs.  */
>>> +static const bfd_vma mips32r6_exec_iplt_entry[] =
>>> +{
>>> +  0x3c0f0000,   /* lui $15, %hi(.igot address)		*/
>>> +  0x8df90000,   /* lw  $25, %lo(.igot address)($15)	*/
>>> +  0x03200009,   /* jr  $25				*/
>>> +  0x00000000    /* nop					*/
>>> +};
>> No need to change, just curious: why not use JRC here?
> 
> I assume you meant this for the micromips32 stub? Will modify it.
> 
>>> +/* 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
>>> +      && h->root.def_regular)
>>> +    {
>>> +      struct bfd_link_info *info = hti->info;
>>> +      elf_tdata (info->output_bfd)->has_gnu_symbols |= elf_gnu_symbol_ifunc;
>>> +
>>> +      /* .iplt entry is needed only for executable objects.  */
>>> +      if (!bfd_link_pic (info) &&
>>> +	  !mips_elf_allocate_iplt (info, mips_elf_hash_table (info), h))
>>> +	return FALSE;
>>> +
>>> +      /* IRELATIVE fixup will be needed for each local IFUNC.  */
>>> +      if (!mips_elf_allocate_ireloc (info, mips_elf_hash_table (info), h))
>>> +	return FALSE;
>>> +    }
>>> +
>>> +  return TRUE;
>>> +}
>>
>> I might be missing something, but I think for local symbols the
>> condition for whether an IPLT is needed should be h->has_static_relocs
>> rather than !bfd_link_pic.  E.g., if an executable uses GOT and CALL
>> relocs for all references to a symbol, it would be more efficient not
>> to have an IPLT and simply use an entry in the general GOT area
>> with an IRELATIVE relocation against it.
> 
> has_static_relocs does not currently track local IFUNCs or relocations in 
> data sections. The first seems easy to fix, but I couldn't figure out the latter.
> 
>>> @@ -3263,9 +3478,12 @@ mips_elf_count_got_entry (struct bfd_link_info *info,
>>>  					entry->symndx < 0
>>>  					? &entry->d.h->root : NULL);
>>>      }
>>> -  else if (entry->symndx >= 0 || entry->d.h->global_got_area == GGA_NONE)
>>> +  /* Skip IFUNCs from local/global GOT, they are already counted as general
>>> +     GOT entries with explicit relocations.  */
>>> +  else if (entry->symndx >= 0 || (entry->d.h->global_got_area == GGA_NONE
>>> +				  && !entry->d.h->needs_ireloc))
>>>      g->local_gotno += 1;
>>> -  else
>>> +  else if (!entry->d.h->needs_ireloc)
>>>      g->global_gotno += 1;
>>>  }
>>
>> I think this is the place where we should be setting general_gotno,
>> rather than mips_elf_count_got_symbols.  mips_elf_count_got_symbols
>> is supposed to:
>>
>>    Count the number of global symbols that are in the primary GOT only
>>    because they have relocations against them (reloc_only_gotno).
>>
>> and shouldn't be counting explicit GOT entries.
> 
> Are we not expected to have a GOT entry for a global IFUNC symbol if  there are
> no references to it (say within a shared library)? Something in your discussions
> with jcarter led me to believe that this was expected. If not it can removed.
> As it is, these entries do not factor in to the ABI global GOT mechanism.
> 
>>> @@ -5200,6 +5515,75 @@ 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");
>>
>> I'm not sure it's valid to assume that every input has a .text section.
>> At least we should have some error code if it doesn't.
>>
>> But I suppose this comes back to my objection to the "local hash" stuff.
>> It just seems wrong to pick an arbitrary section index, regardless of
>> where the symbol is actually defined.
> 
> I see the pitfall of ".text". I think I'll stick to the i386 method, which is 
> to pick the first section id.
> 
>>> @@ -5554,6 +5947,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)
>>> +    {
>>> +      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;
>>> +    }
>>
>> I think you need to set target_is_16_bit_code_p and
>> target_is_micromips_code_p too (based on the equivalent conditions
>> for abfd).
>>
>> I'm a bit nervous about how this interacts with the other redirections,
>> e.g. for la25 and mips16 hard-float stubs.
> 
> I  will dig further in to this. Any hints on what I should look for?
> 
>>> @@ -5940,8 +6354,11 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
>>>  	 R_MIPS*_GOT16; every relocation evaluates to "G".  */
>>>        if (!htab->is_vxworks && local_p)
>>>  	{
>>> +	  /* Local IFUNC symbols must be accessed through GOT, similar to
>>> +	     global symbols, to allow for indirection.  */
>>>  	  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
>>
>> I agree this is correct, but I think it should be specified explicitly
>> in the ABI document that R_MIPS_GOT16 relocations against local IFUNC
>> symbols are treated in this way.  Using the usual R_MIPS_GOT16/R_MIPS_LO16
>> pair for local symbols would give the wrong result for ifuncs.
> 
> I am not quite sure which ABI document you mean, but we are planning a change
> in gcc to reflect this. It shouldn't be generating GOT+LO sequences for IFUNCs.
> In theory, LO16(ifunc) is nonsensical for PIC, but we can force it to zero 
> just to be defensive.
> 
>>
>>> +  else
>>> +    {
>>> +      bfd_byte *loc;
>>> +      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, value, loc);
>>> +      else
>>> +	bfd_put_32 (output_bfd, value, loc);
>>> +    }
>>> +
>>> +  igotplt_address = (gotsect->output_section->vma + gotsect->output_offset
>>> +		     + igot_offset);
>>> +
>>> +  relsect = mips_get_irel_section (info, htab);
>>> +
>>> +  if (igot_offset >= 0)
>>> +    {
>>> +      if (hmips->needs_iplt && relsect->contents == NULL)
>>> +	    {
>>
>> Isn't needs_iplt always true in this case?
> 
> No. The code is generalized so that igot_offset could mean got_offset or 
> igot_offset. For the former, needs_plt is false. In other words, needs_iplt implies 
> (igot_offset >= 0), but not the converse. OTOH, memory for contents is allocated
> as usual in _bfd_mips_elf_size_dynamic_sections so I could just replace this block
> with an assertion.
> 
>>> +	  /* Allocate memory for the relocation section contents.  */
>>> +	  relsect->contents = bfd_zalloc (dynobj, relsect->size);
>>> +	  if (relsect->contents == NULL)
>>> +	    return FALSE;
>>> +	}
>>> +
>>> +      if (hmips->needs_iplt || hmips->root.dynindx < 0)
>>
>> Same here.  Unless I'm missing something, the preemptible case should
>> never actually be used for MIPS, since we only have iplts for executables.
> 
> As above. igot_offset is actually [i]got_offset. Pre-emption is needed for
> global IFUNC which is defined and referenced in a shared libraries. In that
> case there would be a general GOT entry with a symbolic REL32 fixup against it.
> 
>>> @@ -16130,6 +16997,9 @@ _bfd_mips_post_process_headers (bfd *abfd, struct bfd_link_info *link_info)
>>>    if (mips_elf_tdata (abfd)->abiflags.fp_abi == Val_GNU_MIPS_ABI_FP_64
>>>        || mips_elf_tdata (abfd)->abiflags.fp_abi == Val_GNU_MIPS_ABI_FP_64A)
>>>      i_ehdrp->e_ident[EI_ABIVERSION] = 3;
>>> +
>>> +  if (elf_tdata (abfd)->has_gnu_symbols & elf_gnu_symbol_ifunc)
>>> +    i_ehdrp->e_ident[EI_ABIVERSION] = 4;
>>
>> Is it worth also making this explicitly dependent on whether
>> DT_MIPS_GENERAL_GOTNO is present?  It doesn't make a difference now,
>> since it's a subcondition of whether ifuncs are used, but it might
>> be more future-proof.
> 
> Yes, I think that would be good. As things stand, the higher ABI version also
> gets attached to static executables where we don't really care about it.
> 
> Regards,
> Faraz Shahbazker
> 

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC] Add IFUNC support for MIPS (v3)
  2015-10-13 15:50                 ` Faraz Shahbazker
  2015-11-02 20:40                   ` Faraz Shahbazker
@ 2015-11-08 12:47                   ` Richard Sandiford
  2015-12-21 22:52                     ` Faraz Shahbazker
  1 sibling, 1 reply; 35+ messages in thread
From: Richard Sandiford @ 2015-11-08 12:47 UTC (permalink / raw)
  To: Faraz Shahbazker; +Cc: binutils

Faraz Shahbazker <faraz.shahbazker@imgtec.com> writes:
>>> +/* 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
>>> +      && h->root.def_regular)
>>> +    {
>>> +      struct bfd_link_info *info = hti->info;
>>> +      elf_tdata (info->output_bfd)->has_gnu_symbols |= elf_gnu_symbol_ifunc;
>>> +
>>> +      /* .iplt entry is needed only for executable objects.  */
>>> +      if (!bfd_link_pic (info) &&
>>> +	  !mips_elf_allocate_iplt (info, mips_elf_hash_table (info), h))
>>> +	return FALSE;
>>> +
>>> +      /* IRELATIVE fixup will be needed for each local IFUNC.  */
>>> +      if (!mips_elf_allocate_ireloc (info, mips_elf_hash_table (info), h))
>>> +	return FALSE;
>>> +    }
>>> +
>>> +  return TRUE;
>>> +}
>>
>> I might be missing something, but I think for local symbols the
>> condition for whether an IPLT is needed should be h->has_static_relocs
>> rather than !bfd_link_pic.  E.g., if an executable uses GOT and CALL
>> relocs for all references to a symbol, it would be more efficient not
>> to have an IPLT and simply use an entry in the general GOT area
>> with an IRELATIVE relocation against it.
>
> has_static_relocs does not currently track local IFUNCs or relocations in 
> data sections. The first seems easy to fix, but I couldn't figure out
> the latter.

I didn't follow this, sorry.  Why are relocations in data sections special
in this context?  At face value the logic:

      if (h)
	{
	  if (constrain_symbol_p)
	    {
	      if (!can_make_dynamic_p)
		((struct mips_elf_link_hash_entry *) h)->has_static_relocs = 1;

	      if (!call_reloc_p)
		h->pointer_equality_needed = 1;

	      /* We must not create a stub for a symbol that has
		 relocations related to taking the function's address.
		 This doesn't apply to VxWorks, where CALL relocs refer
		 to a .got.plt entry instead of a normal .got entry.  */
	      if (!htab->is_vxworks && (!can_make_dynamic_p || !call_reloc_p))
		((struct mips_elf_link_hash_entry *) h)->no_fn_stub = TRUE;
	    }

applies equally to local ifuncs, and check_relocs should see all relocations
against local ifuncs.

>>> @@ -3263,9 +3478,12 @@ mips_elf_count_got_entry (struct bfd_link_info *info,
>>>  					entry->symndx < 0
>>>  					? &entry->d.h->root : NULL);
>>>      }
>>> -  else if (entry->symndx >= 0 || entry->d.h->global_got_area == GGA_NONE)
>>> +  /* Skip IFUNCs from local/global GOT, they are already counted as general
>>> +     GOT entries with explicit relocations.  */
>>> +  else if (entry->symndx >= 0 || (entry->d.h->global_got_area == GGA_NONE
>>> +				  && !entry->d.h->needs_ireloc))
>>>      g->local_gotno += 1;
>>> -  else
>>> +  else if (!entry->d.h->needs_ireloc)
>>>      g->global_gotno += 1;
>>>  }
>> 
>> I think this is the place where we should be setting general_gotno,
>> rather than mips_elf_count_got_symbols.  mips_elf_count_got_symbols
>> is supposed to:
>> 
>>    Count the number of global symbols that are in the primary GOT only
>>    because they have relocations against them (reloc_only_gotno).
>> 
>> and shouldn't be counting explicit GOT entries.
>
> Are we not expected to have a GOT entry for a global IFUNC symbol if
> there are no references to it (say within a shared library)? Something
> in your discussions with jcarter led me to believe that this was
> expected. If not it can removed.

No, I don't think we need that.  The psABI normally requires this for
general global symbols because of the way that the GOT is resolved,
but the point of the new GOT area is to avoid that.

>>> @@ -5200,6 +5515,75 @@ 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");
>> 
>> I'm not sure it's valid to assume that every input has a .text section.
>> At least we should have some error code if it doesn't.
>> 
>> But I suppose this comes back to my objection to the "local hash" stuff.
>> It just seems wrong to pick an arbitrary section index, regardless of
>> where the symbol is actually defined.
>
> I see the pitfall of ".text". I think I'll stick to the i386 method, which is 
> to pick the first section id.

OK.

>>> @@ -5554,6 +5947,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)
>>> +    {
>>> +      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;
>>> +    }
>> 
>> I think you need to set target_is_16_bit_code_p and
>> target_is_micromips_code_p too (based on the equivalent conditions
>> for abfd).
>> 
>> I'm a bit nervous about how this interacts with the other redirections,
>> e.g. for la25 and mips16 hard-float stubs.
>
> I  will dig further in to this. Any hints on what I should look for?

Well, it's a question of whether the earlier redirections in the
if-else are mutually exclusive with the new one or not, and whether
we handle redirections correctly for the ifunc resolver.  (The second
part isn't really related to this code.)  E.g.:

  - if a MIPS16 hard float function calls an ifunc, do we correctly
    redirect the call to a stub that calls the iplt (or, if the stub
    uses a CALL16, indirectly by loading the resolved ifunc from the
    GOT)?

  - if there are MIPS16 and non-MIPS16 calls to the same iplt,
    what happens?

  - if a PIC ifunc F (i.e. one that needs $25 to be valid on entry)
    is used in a non-PIC executable, does the IRELATIVE relocation
    refer to the "la $25" stub for F, or to the original F?

  - if we have a MIPS16 hard float ifunc F, does the IRELATIVE
    resolve the stub for F, or to F itself?

It might well be that all this just works, it just wasn't obvious.

>>> @@ -5940,8 +6354,11 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
>>>  	 R_MIPS*_GOT16; every relocation evaluates to "G".  */
>>>        if (!htab->is_vxworks && local_p)
>>>  	{
>>> +	  /* Local IFUNC symbols must be accessed through GOT, similar to
>>> +	     global symbols, to allow for indirection.  */
>>>  	  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
>> 
>> I agree this is correct, but I think it should be specified explicitly
>> in the ABI document that R_MIPS_GOT16 relocations against local IFUNC
>> symbols are treated in this way.  Using the usual R_MIPS_GOT16/R_MIPS_LO16
>> pair for local symbols would give the wrong result for ifuncs.
>
> I am not quite sure which ABI document you mean,

The document you attached earlier where you described the new relocations.

> but we are planning a change in gcc to reflect this. It shouldn't be
> generating GOT+LO sequences for IFUNCs.  In theory, LO16(ifunc) is
> nonsensical for PIC, but we can force it to zero just to be defensive.

I don't think ignoring the addend is a good idea.  Seems better to make
it a hard error or make it work like global symbols (as the above does).

>>> +  else
>>> +    {
>>> +      bfd_byte *loc;
>>> +      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, value, loc);
>>> +      else
>>> +	bfd_put_32 (output_bfd, value, loc);
>>> +    }
>>> +
>>> +  igotplt_address = (gotsect->output_section->vma + gotsect->output_offset
>>> +		     + igot_offset);
>>> +
>>> +  relsect = mips_get_irel_section (info, htab);
>>> +
>>> +  if (igot_offset >= 0)
>>> +    {
>>> +      if (hmips->needs_iplt && relsect->contents == NULL)
>>> +	    {
>> 
>> Isn't needs_iplt always true in this case?
>
> No. The code is generalized so that igot_offset could mean got_offset
> or igot_offset. For the former, needs_plt is false. In other words,
> needs_iplt implies (igot_offset >= 0), but not the converse. OTOH,
> memory for contents is allocated as usual in
> _bfd_mips_elf_size_dynamic_sections so I could just replace this block
> with an assertion.

Hmm, OK, I'd missed the bit about igot_offset meaning got_offset in some
cases, sorry.  But let's step back a bit.

(A) When the symbol doesn't bind locally we should treat the ifunc
    like any other symbol, except that we use an R_MIPS_REL32 in the
    new global GOT area to ensure it is resolved safely.  (Normal global
    GOT relocation is too early if we do end up using the local definition.)

(B) When the symbol binds locally I think there are 6 cases:

    PIC relocs?  non-PIC relocs?  all call-only?   variant
    n            y                y                (1)
    y            n                y                (2)
    y            y                y                (3)
    n            y                n                (4)
    y            n                n                (2) or (5) (*)
    y            y                n                (5)

    (1) Need an IPLT.  Nothing other than the IPLT needs the associated
        IRELATIVE GOT entry, so the entry can go in .igot.

    (2) No need for an IPLT, we can just have a .got entry with an IRELATIVE
        relocation.

    (3) Need an IPLT.  The associated IRELATIVE GOT entry should go in .got
        so that it can be used by the PIC references.

    (4) Same as (1), using the IPLT as the canonical function address.

    (5) Need an IPLT, which acts as the canonical function address.
        Nothing other than the IPLT needs the associated IRELATIVE GOT entry,
        so that entry can go in .igot.  However, we need a separate
        .got entry to satisfy the PIC references.  This can be a normal
        local GOT entry (i.e. not use the new area).

    (*) Could be (2) assuming that the ifunc always returns the same pointer,
        otherwise (5).  Should be rare so maybe (5) is safer.

    If the symbol is exported then for (1)-(3) the symbol definition
    is the IFUNC.  For (4) and (5) it's the IPLT.

We should know by size_dynamic_sections which case applies.

The only cases that don't need an IPLT are (2) (where all references to
the ifunc are via the GOT) and (A).  So it seems strange to have this code:

+  if (!hmips->needs_iplt)
+    {
+      gotsect = htab->sgot;
+      /* Check if IFUNC symbol already has an assigned GOT slots; assign
+	 a new slot if necessary.  */
+      igot_offset = mips_elf_check_local_got_index (output_bfd, info,
+						    hmips, value);
+      if (igot_offset < 0)
+	igot_offset = mips_elf_local_got_index (output_bfd, output_bfd, info,
+						value, hmips->root.dynindx,
+						hmips, R_MIPS_REL32);
+    }

in a function called by finish_dynamic_symbol.  The GOT layout should
already be final at this point and we should already have created the
GOT entry needed by the PIC references to the symbol.

Also in:

+      if (hmips->needs_iplt || hmips->root.dynindx < 0)
+	/* Emit an R_MIPS_IRELATIVE relocation against the [I]GOT entry.  */
+	mips_elf_output_dynamic_relocation (output_bfd, relsect,
+					    relsect->reloc_count++, 0,
+					    R_MIPS_IRELATIVE, igotplt_address);
+      else
+	{
+	  /* Emit an R_MIPS_REL32 relocation against global GOT entry for
+	     a preemptible symbol.  */
+	  asection *sec = hmips->root.root.u.def.section;
+	  Elf_Internal_Rela rel[3];
+
+	  memset (rel, 0, sizeof (rel));
+	  rel[0].r_info = ELF_R_INFO (output_bfd, 0, R_MIPS_REL32);
+	  rel[0].r_offset = rel[1].r_offset = rel[2].r_offset = igot_offset;
+
+	  mips_elf_create_dynamic_relocation (output_bfd, info, rel, hmips,
+					      sec, value, NULL,
+					      gotsect);

if the else is for (A) then "hmips->root.dynindx < 0" doesn't seem
strong enough, since it would include (2) in cases where the symbol
is also exported.

Thanks,
Richard

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC] Add IFUNC support for MIPS (v3)
  2015-11-08 12:47                   ` Richard Sandiford
@ 2015-12-21 22:52                     ` Faraz Shahbazker
  2015-12-22 22:29                       ` Richard Sandiford
  0 siblings, 1 reply; 35+ messages in thread
From: Faraz Shahbazker @ 2015-12-21 22:52 UTC (permalink / raw)
  To: binutils, rdsandiford

On 11/08/2015 04:46 AM, Richard Sandiford wrote:
>>>> @@ -5554,6 +5947,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)
>>>> +    {
>>>> +      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;
>>>> +    }
>>>
>>> I think you need to set target_is_16_bit_code_p and
>>> target_is_micromips_code_p too (based on the equivalent conditions
>>> for abfd).
>>>
>>> I'm a bit nervous about how this interacts with the other redirections,`
>>> e.g. for la25 and mips16 hard-float stubs.
>>
>> I  will dig further in to this. Any hints on what I should look for?
> 
> Well, it's a question of whether the earlier redirections in the
> if-else are mutually exclusive with the new one or not, and whether
> we handle redirections correctly for the ifunc resolver.  (The second
> part isn't really related to this code.)  E.g.:

I've added few test cases to cover these interactions - I don't think
these are exhaustive, but at least in theory all mixed call redirections
must take priority over IPLT redirection because the IPLT stub does not
address mix-call issues. The ifunc redirection will happen either before 
(loading the resolved ifunc from GOT) or within the stub by redirection 
to an IPLT stub.

>   - if a MIPS16 hard float function calls an ifunc, do we correctly
>     redirect the call to a stub that calls the iplt (or, if the stub
>     uses a CALL16, indirectly by loading the resolved ifunc from the
>     GOT)?
Yes for both.

>   - if there are MIPS16 and non-MIPS16 calls to the same iplt,
>     what happens?
Since the IPLT redirection is logically 'behind' the MIPS16 stub
redirection, it should not matter beyond the selection of the correct
instruction (jal vs. jalx) depending on the iplt type.

>   - if we have a MIPS16 hard float ifunc F, does the IRELATIVE
>     resolve the stub for F, or to F itself?
F itself. IRELATIVE is only concerned with the resolver function.
The stub in turn will refer to the IFUNC through either the iplt
or a fixed-up GOT entry.

>   - if a PIC ifunc F (i.e. one that needs $25 to be valid on entry)
>     is used in a non-PIC executable, does the IRELATIVE relocation
>     refer to the "la $25" stub for F, or to the original F?
This is the only case where there is a potential overlap in redirections.
Since all the iplt stubs load $25, the extra la25 stub is not needed.
I've chosen to modified mips_elf_check_symbols for the next patch, to 
not create the redundant la25 stub for functions that have iplt stubs.

>>> I agree this is correct, but I think it should be specified explicitly
>>> in the ABI document that R_MIPS_GOT16 relocations against local IFUNC
>>> symbols are treated in this way.  Using the usual R_MIPS_GOT16/R_MIPS_LO16
>>> pair for local symbols would give the wrong result for ifuncs.
>>
>> I am not quite sure which ABI document you mean,
> 
> The document you attached earlier where you described the new relocations.

This was included towards the end of the last spec document I had attached. 
But I will restructure and publish with the next patch.

> Hmm, OK, I'd missed the bit about igot_offset meaning got_offset in some
> cases, sorry.  But let's step back a bit.
> ...
> in a function called by finish_dynamic_symbol.  The GOT layout should
> already be final at this point and we should already have created the
> GOT entry needed by the PIC references to the symbol.

Got it.

> Also in:
> 
> +      if (hmips->needs_iplt || hmips->root.dynindx < 0)
> +	/* Emit an R_MIPS_IRELATIVE relocation against the [I]GOT entry.  */
> +	mips_elf_output_dynamic_relocation (output_bfd, relsect,
> +					    relsect->reloc_count++, 0,
> +					    R_MIPS_IRELATIVE, igotplt_address);
> +      else
> +	{
> +	  /* Emit an R_MIPS_REL32 relocation against global GOT entry for
> +	     a preemptible symbol.  */
> +	  asection *sec = hmips->root.root.u.def.section;
> +	  Elf_Internal_Rela rel[3];
> +
> +	  memset (rel, 0, sizeof (rel));
> +	  rel[0].r_info = ELF_R_INFO (output_bfd, 0, R_MIPS_REL32);
> +	  rel[0].r_offset = rel[1].r_offset = rel[2].r_offset = igot_offset;
> +
> +	  mips_elf_create_dynamic_relocation (output_bfd, info, rel, hmips,
> +					      sec, value, NULL,
> +					      gotsect);
> 
> if the else is for (A) then "hmips->root.dynindx < 0" doesn't seem
> strong enough, since it would include (2) in cases where the symbol
> is also exported.

As I understood, if the symbol is also exported, it will have an entry in 
the dynamic symbol table and an assigned (> 0) dynamic index. Am I wrong?

Regards,
Faraz Shahbazker

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC] Add IFUNC support for MIPS (v3)
  2015-12-21 22:52                     ` Faraz Shahbazker
@ 2015-12-22 22:29                       ` Richard Sandiford
  2016-01-08  0:10                         ` [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v4) Faraz Shahbazker
  2016-01-08  0:11                         ` [PATCH 2/2] " Faraz Shahbazker
  0 siblings, 2 replies; 35+ messages in thread
From: Richard Sandiford @ 2015-12-22 22:29 UTC (permalink / raw)
  To: Faraz Shahbazker; +Cc: binutils

Faraz Shahbazker <faraz.shahbazker@imgtec.com> writes:
> On 11/08/2015 04:46 AM, Richard Sandiford wrote:
>>>>> @@ -5554,6 +5947,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)
>>>>> +    {
>>>>> +      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;
>>>>> +    }
>>>>
>>>> I think you need to set target_is_16_bit_code_p and
>>>> target_is_micromips_code_p too (based on the equivalent conditions
>>>> for abfd).
>>>>
>>>> I'm a bit nervous about how this interacts with the other redirections,`
>>>> e.g. for la25 and mips16 hard-float stubs.
>>>
>>> I  will dig further in to this. Any hints on what I should look for?
>> 
>> Well, it's a question of whether the earlier redirections in the
>> if-else are mutually exclusive with the new one or not, and whether
>> we handle redirections correctly for the ifunc resolver.  (The second
>> part isn't really related to this code.)  E.g.:
>
> I've added few test cases to cover these interactions - I don't think
> these are exhaustive, but at least in theory all mixed call redirections
> must take priority over IPLT redirection because the IPLT stub does not
> address mix-call issues. The ifunc redirection will happen either before 
> (loading the resolved ifunc from GOT) or within the stub by redirection 
> to an IPLT stub.
>
>>   - if a MIPS16 hard float function calls an ifunc, do we correctly
>>     redirect the call to a stub that calls the iplt (or, if the stub
>>     uses a CALL16, indirectly by loading the resolved ifunc from the
>>     GOT)?
> Yes for both.
>
>>   - if there are MIPS16 and non-MIPS16 calls to the same iplt,
>>     what happens?
> Since the IPLT redirection is logically 'behind' the MIPS16 stub
> redirection, it should not matter beyond the selection of the correct
> instruction (jal vs. jalx) depending on the iplt type.
>
>>   - if we have a MIPS16 hard float ifunc F, does the IRELATIVE
>>     resolve the stub for F, or to F itself?
> F itself. IRELATIVE is only concerned with the resolver function.
> The stub in turn will refer to the IFUNC through either the iplt
> or a fixed-up GOT entry.
>
>>   - if a PIC ifunc F (i.e. one that needs $25 to be valid on entry)
>>     is used in a non-PIC executable, does the IRELATIVE relocation
>>     refer to the "la $25" stub for F, or to the original F?
> This is the only case where there is a potential overlap in redirections.
> Since all the iplt stubs load $25, the extra la25 stub is not needed.
> I've chosen to modified mips_elf_check_symbols for the next patch, to 
> not create the redundant la25 stub for functions that have iplt stubs.

Thanks for looking at this.

>>>> I agree this is correct, but I think it should be specified explicitly
>>>> in the ABI document that R_MIPS_GOT16 relocations against local IFUNC
>>>> symbols are treated in this way.  Using the usual R_MIPS_GOT16/R_MIPS_LO16
>>>> pair for local symbols would give the wrong result for ifuncs.
>>>
>>> I am not quite sure which ABI document you mean,
>> 
>> The document you attached earlier where you described the new relocations.
>
> This was included towards the end of the last spec document I had attached. 
> But I will restructure and publish with the next patch.

Ah, missed that, sorry.

>> Also in:
>> 
>> +      if (hmips->needs_iplt || hmips->root.dynindx < 0)
>> +	/* Emit an R_MIPS_IRELATIVE relocation against the [I]GOT entry.  */
>> +	mips_elf_output_dynamic_relocation (output_bfd, relsect,
>> +					    relsect->reloc_count++, 0,
>> +					    R_MIPS_IRELATIVE, igotplt_address);
>> +      else
>> +	{
>> +	  /* Emit an R_MIPS_REL32 relocation against global GOT entry for
>> +	     a preemptible symbol.  */
>> +	  asection *sec = hmips->root.root.u.def.section;
>> +	  Elf_Internal_Rela rel[3];
>> +
>> +	  memset (rel, 0, sizeof (rel));
>> +	  rel[0].r_info = ELF_R_INFO (output_bfd, 0, R_MIPS_REL32);
>> +	  rel[0].r_offset = rel[1].r_offset = rel[2].r_offset = igot_offset;
>> +
>> +	  mips_elf_create_dynamic_relocation (output_bfd, info, rel, hmips,
>> +					      sec, value, NULL,
>> +					      gotsect);
>> 
>> if the else is for (A) then "hmips->root.dynindx < 0" doesn't seem
>> strong enough, since it would include (2) in cases where the symbol
>> is also exported.
>
> As I understood, if the symbol is also exported, it will have an entry in 
> the dynamic symbol table and an assigned (> 0) dynamic index. Am I wrong?

Sorry, that was an unfortunate use of "it".  What I meant was that the
else would trigger for (2) with exported symbols (as wall as for (A)),
for exactly the reason you say.  In other words, it looks like we'll
end up with an R_MIPS_REL32 in a case where we should have an
R_MIPS_IRELATIVE.

Thanks,
Richard

^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v4)
  2015-12-22 22:29                       ` Richard Sandiford
@ 2016-01-08  0:10                         ` Faraz Shahbazker
  2016-01-21 20:02                           ` Faraz Shahbazker
  2016-03-06  7:14                           ` Maciej W. Rozycki
  2016-01-08  0:11                         ` [PATCH 2/2] " Faraz Shahbazker
  1 sibling, 2 replies; 35+ messages in thread
From: Faraz Shahbazker @ 2016-01-08  0:10 UTC (permalink / raw)
  To: binutils, rdsandiford; +Cc: macro

[-- Attachment #1: Type: text/plain, Size: 65069 bytes --]

Hi,

Spec (attached) updated to reflect creation of IPLT/IGOT entries for symbols that 
bind locally. Test cases in following patch.

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_got_info): New fields general_gotno and
	assigned_general_gotno.
	(struct mips_elf_link_hash_entry): New fields for offset in to
	IPLT/IGOT, flags to indicate if symbol needs IPLT/IRELOC/IGOT
	and if it has normal GOT-based relocations.
	(mips_elf_link_hash_table): New fields for size of IPLT stubs
	and hash-table for local IFUNC symbols.
	(MIPS16_P): New macro to check ASE flag.
	(mips16_exec_iplt_entry): Template for mips16 IPLT stub.
	(mips32_exec_iplt_entry): Template for mips32 IPLT stub.
	(mips32r6_exec_iplt_entry): Template for mips32 R6 IPLT stub.
	(micromips32_exec_iplt_entry): Template for micromips32 stub.
	(mips64_exec_iplt_entry): Template for mips64 IPLT stub.
	(mips64_48b_exec_iplt_entry): Template for 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 for micromips
	& mips16 IPLT stubs. New argument to set st_other value of stub.
	(mips_elf_add_la25_intro): Add argument other to the call to
	mips_elf_create_stub_symbol.
	(mips_elf_add_la25_trampoline): Add argument other to the call to
	mips_elf_create_stub_symbol.
	(mips_elf_rel_dyn_section): Moved up to avoid forward declaration.
	(mips_get_irel_section): New function.
	(mips_elf_allocate_ireloc): Likewise.
	(mips_elf_allocate_iplt): Likewise.
	(mips_elf_check_ifunc_symbols): Likewise.
	(mips_elf_check_symbols): Call mips_elf_check_ifunc_symbols to
	allocate an IPLT entry for an IFUNC symbol and set has_gnu_symbols.
	Skip creation of la25 stubs for IFUNCs having IPLT stubs.
	(mips_elf_count_got_entry): Count GOT entries for IFUNCs that do
	not have an IPLT stub under the general GOT region and allocate
	space for an IRELATIVE relocation for each.
	(mips_elf_got16_entry): Add argument h and pass it to
	mips_elf_create_local_got_entry instead of NULL.
	(mips_elf_create_local_got_entry): Change hash-lookup for IFUNCs
	to use non-NULL input BFD and a pointer to the
	mips_elf_link_hash_entry.  Add check for sufficient general GOT
	entries.  Assign local IFUNCs from general GOT entries pool.
 	Allow early return for existing hash entry only if gotidx has
	been assigned. 	Allocate general GOT only if symbol does not
	have an IGOT entry.
	(mips_elf_record_local_got_symbol): New argument for pointer to
	mips_elf_link_hash_entry of the local symbol.  If non-NULL, this
	is used to set d.h in the GOT has entry instead of d.addend.
	(mips_use_local_got_p): Return TRUE for all IFUNC symbol definitions.
	(mips_elf_create_ifunc_sections): New function.
	(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 symbols that need IPLT.
	Force global IFUNC to be allocated from local GOT region.
	(mips_elf_perform_relocation): New argument flag indication relocation
	targets IFUNC. Disable JALR to BAL optimization for IFUNC symobls.
	(mips_elf_create_dynamic_relocation): Emit IRELATIVE relocation
	instead of REL32 for local IFUNC reference. Relax assertion for
	IFUNC symbols in explicit GOT region to have dynamic relocations.
        (_bfd_mips_elf_section_processing): Size .igot section.
	(_bfd_mips_elf_check_relocs): Check need to create IFUNC sections.
	If symbol is an IFUNC, don't convert it to an STT_FUNC. Relax error
	checking to allow local IFUNCs to be accessed via call16 reloc.
	Record calls to a local IFUNC symbols and pre-allocate GOT entries
	for call16 and got16 relocations.
	(_bfd_mips_elf_adjust_dynamic_symbol): Add STT_GNU_IFUNC to sanity
	check.
	(_bfd_mips_elf_always_size_sections): Allocate IPLTs for local IFUNCs.
	(mips_elf_lay_out_got): Offset local GOT entries to follow general
	GOT entries.
	(_bfd_mips_elf_size_dynamic_sections): Exclude IPLT and IGOT.
	Create dynamic tag for DT_MIPS_GENERAL_GOTNO if needed.
	(_bfd_mips_elf_relocate_section): Relax error checking to allow
	local IFUNCs to be accessed via standalone got16 reloc.  Pass
	gnu_ifunc_p flag when calling mips_elf_perform_relocation.
	(mips_elf_create_iplt): New function.
	(mips_elf_check_local_got_index): Likewise.
	(mips_elf_create_ireloc): Likewise.
	(_bfd_mips_elf_finish_dynamic_symbol): Create IPLT stub/IRELATIVE
	relocation for IFUNC symbols as necessary. Set IFUNC symbol value
	to the IPLT entry address for executable 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 tag - DT_MIPS_GENERAL_GOTNO.
	(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 tag -
	DT_MIPS_GENERAL_GOTNO.
	(_bfd_mips_post_process_headers): If ELF uses GNU IFUNCs, increase
	ABIVERSION to 4.
	* 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:
	(get_mips_dynamic_type): Add case for DT_MIPS_GENERAL_GOTNO.
	(dynamic_section_mips_val): Add case for DT_MIPS_GENERAL_GOTNO.

elfcpp/ChangeLog:

	* elfcpp.h
	(enum DT): Add cases for dynamic tag DT_MIPS_GENERAL_GOTNO.

include/elf/ChangeLog:

	* mips.h
	(START_RELOC_NUMBERS): Entry for R_MIPS_IRELATIVE.
	(DT_MIPS_GENERAL_GOTNO): New dynamic tag.
---
 bfd/bfd-in2.h      |    3 +
 bfd/elf32-mips.c   |   22 ++
 bfd/elfxx-mips.c   | 1076 +++++++++++++++++++++++++++++++++++++++++++++++-----
 bfd/libbfd.h       |    1 +
 bfd/reloc.c        |    5 +
 binutils/readelf.c |    3 +
 elfcpp/elfcpp.h    |    2 +
 include/elf/mips.h |    7 +
 8 files changed, 1030 insertions(+), 89 deletions(-)

diff --git a/bfd/bfd-in2.h b/bfd/bfd-in2.h
index fb4858c..0218164 100644
--- a/bfd/bfd-in2.h
+++ b/bfd/bfd-in2.h
@@ -3062,6 +3062,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 752f386..e8f1079 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 4ece819..6f23387 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 {
@@ -165,10 +166,14 @@ struct mips_got_info
   unsigned int tls_assigned_gotno;
   /* The number of local .got entries, eventually including page entries.  */
   unsigned int local_gotno;
+  /* The number of explicitly relocated .got entries.  */
+  unsigned int general_gotno;
   /* The maximum number of page entries needed.  */
   unsigned int page_gotno;
   /* The number of relocations needed for the GOT entries.  */
   unsigned int relocs;
+  /* The first unused general .got entry.  */
+  unsigned int assigned_general_gotno;
   /* The first unused local .got entry.  */
   unsigned int assigned_low_gotno;
   /* The last unused local .got entry.  */
@@ -375,6 +380,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.  */
+  int igot_offset;
+
   /* The highest GGA_* value that satisfies all references to this symbol.  */
   unsigned int global_got_area : 2;
 
@@ -392,6 +403,9 @@ struct mips_elf_link_hash_entry
      cannot possibly be made dynamic).  */
   unsigned int has_static_relocs : 1;
 
+  /* True if there is a got16 or call16 relocation against this symbol.  */
+  unsigned int has_got_relocs : 1;
+
   /* True if we must not create a .MIPS.stubs entry for this symbol.
      This is set, for example, if there are relocations related to
      taking the function's address, i.e. any but R_MIPS_CALL*16 ones.
@@ -413,6 +427,15 @@ 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 symbol need an IPLT stub?  */
+  unsigned int needs_igot : 1;
+
+  /* Does this ifunc symbol need an IRELATIVE relocation?  */
+  unsigned int needs_ireloc : 1;
 };
 
 /* MIPS ELF linker hash table.  */
@@ -485,6 +508,9 @@ 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;
+
   /* The number of functions that need a lazy-binding stub.  */
   bfd_vma lazy_stub_count;
 
@@ -516,6 +542,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 +830,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 \
@@ -1185,6 +1219,69 @@ 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 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					*/
+};
+
+/* Format of 32 bit IPLT entries for R6. JR encoding differs.  */
+static const bfd_vma mips32r6_exec_iplt_entry[] =
+{
+  0x3c0f0000,   /* lui $15, %hi(.igot address)		*/
+  0x8df90000,   /* lw  $25, %lo(.igot address)($15)	*/
+  0x03200009,   /* 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,	/* lw  $2, %lo(.igot address)($2) 	*/
+  0x45b9,	/* jrc $25				*/
+};
+
+/* The format of 64-bit IPLT entries.  */
+static const bfd_vma mips64_exec_iplt_entry[] =
+{
+  0x3c0f0000,	/* lui $15, %highest(.got.iplt entry)        */
+  0x3c0e0000,	/* lui $14, %hi(.got.iplt entry)             */
+  0x25ef0000,	/* addiu $15, $15, %higher(.got.iplt entry)  */
+  0x000f783c,	/* dsll32 $15, $15, 0x0                      */
+  0x01ee782d,	/* daddu $15, $15, $14                       */
+  0xddf90000,	/* ld $25, %lo(.got.iplt entry)($15)         */
+  0x03200008,	/* jr $25                                    */
+  0x00000000,	/* nop                                       */
+};
+
+/* The format of 64-bit IPLT entries for 48bit address.  */
+static const bfd_vma mips64_48b_exec_iplt_entry[] =
+{
+  0x3c0f0000,	/* lui $15, %higher(.got.iplt entry)         */
+  0x25ef0000,	/* addiu $15, $15, %high(.got.iplt entry)    */
+  0x000f7c38,	/* dsll $15, $15, 16  			     */
+  0xddf90000,	/* ld $25, %lo(.got.iplt entry)($15)         */
+  0x03200008,	/* jr $25                                    */
+  0x00000000,	/* nop                                       */
+};
+
 \f
 /* microMIPS 32-bit opcode helper installer.  */
 
@@ -1278,11 +1375,17 @@ mips_elf_link_hash_newfunc (struct bfd_hash_entry *entry,
       ret->got_only_for_calls = TRUE;
       ret->readonly_reloc = FALSE;
       ret->has_static_relocs = FALSE;
+      ret->has_got_relocs = FALSE;
       ret->no_fn_stub = FALSE;
       ret->need_fn_stub = FALSE;
       ret->has_nonpic_branches = FALSE;
       ret->needs_lazy_stub = FALSE;
       ret->use_plt_entry = FALSE;
+      ret->needs_iplt = FALSE;
+      ret->needs_igot = FALSE;
+      ret->needs_ireloc = FALSE;
+      ret->iplt_offset = -1;
+      ret->igot_offset = -1;
     }
 
   return (struct bfd_hash_entry *) ret;
@@ -1576,13 +1679,14 @@ static bfd_boolean
 mips_elf_create_stub_symbol (struct bfd_link_info *info,
 			     struct mips_elf_link_hash_entry *h,
 			     const char *prefix, asection *s, bfd_vma value,
-			     bfd_vma size)
+			     bfd_vma size, unsigned int other)
 {
   struct bfd_link_hash_entry *bh;
   struct elf_link_hash_entry *elfh;
   const char *name;
 
-  if (ELF_ST_IS_MICROMIPS (h->root.other))
+  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.  */
@@ -1598,6 +1702,8 @@ 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;
+  elfh->other = other;
+
   return TRUE;
 }
 
@@ -1863,7 +1969,7 @@ mips_elf_add_la25_intro (struct mips_elf_la25_stub *stub,
     s->size = (1 << align) - 8;
 
   /* Create a symbol for the stub.  */
-  mips_elf_create_stub_symbol (info, stub->h, ".pic.", s, s->size, 8);
+  mips_elf_create_stub_symbol (info, stub->h, ".pic.", s, s->size, 8, 0);
   stub->stub_section = s;
   stub->offset = s->size;
 
@@ -1900,7 +2006,7 @@ mips_elf_add_la25_trampoline (struct mips_elf_la25_stub *stub,
     }
 
   /* Create a symbol for the stub.  */
-  mips_elf_create_stub_symbol (info, stub->h, ".pic.", s, s->size, 16);
+  mips_elf_create_stub_symbol (info, stub->h, ".pic.", s, s->size, 16, 0);
   stub->stub_section = s;
   stub->offset = s->size;
 
@@ -1963,6 +2069,156 @@ 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 one 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 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;
+}
+
+/* Reserve space in the rel.iplt section for an IRELATIVE relocation.  */
+
+static bfd_boolean
+mips_elf_allocate_ireloc (struct bfd_link_info *info,
+			  struct mips_elf_link_hash_table *mhtab,
+			  struct mips_elf_link_hash_entry *mh)
+{
+  asection *srel;
+  bfd *dynobj;
+
+  srel = mips_get_irel_section (info, mhtab);
+  dynobj = elf_hash_table (info)->dynobj;
+  if (srel != mhtab->root.irelplt && srel->size == 0)
+    {
+      /* Make room for a null element.  */
+      srel->size += MIPS_ELF_REL_SIZE (dynobj);
+      ++srel->reloc_count;
+    }
+  srel->size += MIPS_ELF_REL_SIZE (dynobj);
+  mh->needs_ireloc = TRUE;
+
+  return TRUE;
+}
+
+/* Reserve space in the iplt and igot tables for an ifunc entry
+   and allocate space for an IRELATIVE relocation.  */
+
+static bfd_boolean
+mips_elf_allocate_iplt (struct bfd_link_info *info,
+			struct mips_elf_link_hash_table *mhtab,
+			struct mips_elf_link_hash_entry *mh)
+{
+  asection *s;
+  bfd *abfd = info->output_bfd;
+  unsigned int other = 0;
+
+  BFD_ASSERT (!mh->needs_iplt);
+  BFD_ASSERT (mhtab->root.iplt != NULL);
+
+  s = mhtab->root.iplt;
+  if (ELF_ST_IS_MIPS16 (mh->root.other))
+    other = STO_MIPS16;
+
+  mh->iplt_offset = s->size;
+  mips_elf_create_stub_symbol (info, mh, ".iplt.", mhtab->root.iplt,
+			       s->size, mhtab->iplt_entry_size, other);
+  s->size += mhtab->iplt_entry_size;
+
+  BFD_ASSERT (mhtab->root.igotplt != NULL);
+
+  /* Only create IGOT entry if there are no GOT relocations, or when
+     there are non-CALL references to the symbol.  In the latter case,
+     existing GOT entry must point to IPLT, so an IGOT entry is needed
+     to catch the result of the IRELATIVE relocation resolution.  */
+  if (!mh->has_got_relocs || mh->root.pointer_equality_needed)
+    {
+      mh->igot_offset = mhtab->root.igotplt->size;
+      mhtab->root.igotplt->size += MIPS_ELF_GOT_SIZE (abfd);
+      mh->needs_igot = TRUE;
+    }
+
+  mh->needs_iplt = TRUE;
+
+  /* IRELATIVE fixup will be needed for each local IFUNC.  */
+  if (!mips_elf_allocate_ireloc (info, mips_elf_hash_table (info), mh))
+    return FALSE;
+
+  return TRUE;
+}
+
+/* hash_traverse callback that is called before sizing sections.
+   DATA points to a mips_htab_traverse_info structure.  */
+
+static bfd_boolean
+mips_elf_check_ifunc_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 (h
+      && !h->needs_iplt
+      && h->root.type == STT_GNU_IFUNC
+      && h->root.def_regular)
+    {
+      struct bfd_link_info *info = hti->info;
+      elf_tdata (info->output_bfd)->has_gnu_symbols |= elf_gnu_symbol_ifunc;
+
+      /* For global symbols, .iplt entry is needed only for all non-shared-
+	 objects.  For local symbols, it is needed only if the symbol has
+	 static relocations.  */
+      if (((h->root.forced_local && h->has_static_relocs)
+	   || (!h->root.forced_local && !bfd_link_pic (info)))
+	  && !mips_elf_allocate_iplt (info, mips_elf_hash_table (info), h))
+	{
+	  hti->error = TRUE;
+	  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.  */
 
@@ -1975,6 +2231,12 @@ mips_elf_check_symbols (struct mips_elf_link_hash_entry *h, void *data)
   if (!bfd_link_relocatable (hti->info))
     mips_elf_check_mips16_stubs (hti->info, h);
 
+  /* Create stubs and relocations for IFUNC symbols.  */
+  if (h
+      && h->root.type == STT_GNU_IFUNC
+      && !mips_elf_check_ifunc_symbols ((void **)&h, hti))
+    return FALSE;
+
   if (mips_elf_local_pic_function_p (h))
     {
       /* PR 12845: If H is in a section that has been garbage
@@ -1986,13 +2248,15 @@ mips_elf_check_symbols (struct mips_elf_link_hash_entry *h, void *data)
 	 If we're creating a non-PIC relocatable object, mark H as
 	 being PIC.  If we're creating a non-relocatable object with
 	 non-PIC branches and jumps to H, make sure that H has an la25
-	 stub.  */
+	 stub.  IFUNCs with IPLT stubs don't need an la25 stub.  */
       if (bfd_link_relocatable (hti->info))
 	{
 	  if (!PIC_OBJECT_P (hti->output_bfd))
 	    h->root.other = ELF_ST_SET_MIPS_PIC (h->root.other);
 	}
-      else if (h->has_nonpic_branches && !mips_elf_add_la25_stub (hti->info, h))
+      else if (h->has_nonpic_branches
+	       && (h->root.type != STT_GNU_IFUNC || !h->needs_iplt)
+	       && !mips_elf_add_la25_stub (hti->info, h))
 	{
 	  hti->error = TRUE;
 	  return FALSE;
@@ -3130,37 +3394,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
@@ -3254,8 +3487,21 @@ mips_elf_count_got_entry (struct bfd_link_info *info,
 					entry->symndx < 0
 					? &entry->d.h->root : NULL);
     }
-  else if (entry->symndx >= 0 || entry->d.h->global_got_area == GGA_NONE)
-    g->local_gotno += 1;
+  else if (entry->symndx >= 0 || (entry->d.h->global_got_area == GGA_NONE))
+    {
+      /* Count IFUNCs as general GOT entries with explicit relocations.  */
+      if (entry->symndx < 0
+	  && entry->d.h->root.type == STT_GNU_IFUNC
+	  && entry->d.h->root.def_regular
+	  && !entry->d.h->needs_igot)
+	{
+	  g->general_gotno += 1;
+	  mips_elf_allocate_ireloc (info, mips_elf_hash_table (info),
+				    entry->d.h);
+	}
+      else
+	g->local_gotno += 1;
+    }
   else
     g->global_gotno += 1;
 }
@@ -3587,7 +3833,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;
 
@@ -3602,7 +3849,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
@@ -3691,18 +3938,28 @@ mips_elf_create_local_got_entry (bfd *abfd, struct bfd_link_info *info,
       return entry;
     }
 
-  lookup.abfd = NULL;
   lookup.symndx = -1;
-  lookup.d.address = value;
+  if (h && h->root.type == STT_GNU_IFUNC)
+    {
+      lookup.abfd = ibfd;
+      lookup.d.h = h;
+    }
+  else
+    {
+      lookup.abfd = NULL;
+      lookup.d.address = value;
+    }
+
   loc = htab_find_slot (g->got_entries, &lookup, INSERT);
   if (!loc)
     return NULL;
 
   entry = (struct mips_got_entry *) *loc;
-  if (entry)
+  if (entry && entry->gotidx >= 0)
     return entry;
 
-  if (g->assigned_low_gotno > g->assigned_high_gotno)
+  if (g->assigned_low_gotno > g->assigned_high_gotno
+      || g->assigned_general_gotno > g->local_gotno)
     {
       /* We didn't allocate enough space in the GOT.  */
       (*_bfd_error_handler)
@@ -3715,7 +3972,15 @@ mips_elf_create_local_got_entry (bfd *abfd, struct bfd_link_info *info,
   if (!entry)
     return NULL;
 
-  if (got16_reloc_p (r_type)
+  if (h && h->needs_ireloc && !h->needs_igot)
+    /* Allocate IFUNC slots in the general GOT region since they
+       will need explicit IRELATIVE relocations.  */
+    {
+      lookup.gotidx = MIPS_ELF_GOT_SIZE (abfd) * g->assigned_general_gotno++;
+      if (h->needs_iplt)
+	h->igot_offset = lookup.gotidx;
+    }
+  else if (got16_reloc_p (r_type)
       || call16_reloc_p (r_type)
       || got_page_reloc_p (r_type)
       || got_disp_reloc_p (r_type))
@@ -3958,7 +4223,8 @@ mips_elf_record_global_got_symbol (struct elf_link_hash_entry *h,
 
 static bfd_boolean
 mips_elf_record_local_got_symbol (bfd *abfd, long symndx, bfd_vma addend,
-				  struct bfd_link_info *info, int r_type)
+				  struct bfd_link_info *info, int r_type,
+				  struct mips_elf_link_hash_entry *h)
 {
   struct mips_elf_link_hash_table *htab;
   struct mips_got_info *g;
@@ -3972,7 +4238,10 @@ mips_elf_record_local_got_symbol (bfd *abfd, long symndx, bfd_vma addend,
 
   entry.abfd = abfd;
   entry.symndx = symndx;
-  entry.d.addend = addend;
+  if (h)
+    entry.d.h = h;
+  else
+    entry.d.addend = addend;
   entry.tls_type = mips_elf_reloc_tls_type (r_type);
   return mips_elf_record_got_entry (info, abfd, &entry);
 }
@@ -4389,7 +4658,11 @@ mips_use_local_got_p (struct bfd_link_info *info,
      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)
+
+  /* Both global & local IFUNC symbols actually use the explicitly relocated
+     GOT region, but we don't distinguish it from the local GOT just yet.  */
+  if (h->root.dynindx == -1
+      || (h->root.type == STT_GNU_IFUNC && h->root.def_regular))
     return TRUE;
 
   /* Symbols that bind locally can (and in the case of forced-local
@@ -5075,6 +5348,65 @@ 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;
+
+  if (!bfd_link_pic (info))
+    {
+      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))
+	htab->iplt_entry_size = (4 * ARRAY_SIZE (micromips32_exec_iplt_entry)
+				 - 2);
+      else
+	htab->iplt_entry_size = 4 * (ARRAY_SIZE (mips32_exec_iplt_entry)
+				     + (LOAD_INTERLOCKS_P (dynobj) ? 0 : 1));
+
+      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;
+
+      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
@@ -5191,6 +5523,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 = abfd->sections;
+  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);
+
+  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;
+      ret->igot_offset = -1;
+      ret->root.other = isym->st_other;
+      ret->got_only_for_calls = TRUE;
+
+      *slot = ret;
+    }
+
+  return ret;
+}
 \f
 /* Calculate the value produced by the RELOCATION (which comes from
    the INPUT_BFD).  The ADDEND is the addend to use for this
@@ -5241,6 +5641,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
@@ -5321,6 +5723,13 @@ 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
     {
@@ -5545,6 +5954,21 @@ 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
+	   && (h->needs_igot
+	       || (!call16_reloc_p (r_type)
+		   && !got16_reloc_p (r_type))))
+    {
+      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)
@@ -5640,6 +6064,16 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
 	      BFD_ASSERT (h->root.needs_plt);
 	      g = mips_elf_gotplt_index (info, &h->root);
 	    }
+	  /* IFUNCs use the explicitly-relocated GOT region, however we don't
+	     distinguish it from the local GOT at this stage.  */
+	  else if (h && h->needs_ireloc && !h->needs_igot)
+	    {
+	      g = mips_elf_local_got_index (abfd, input_bfd, info,
+					    symbol + addend, r_symndx,
+					    h, r_type);
+	      if (g == MINUS_ONE)
+		return bfd_reloc_outofrange;
+	    }
 	  else
 	    {
 	      BFD_ASSERT (addend == 0);
@@ -5931,8 +6365,11 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
 	 R_MIPS*_GOT16; every relocation evaluates to "G".  */
       if (!htab->is_vxworks && local_p)
 	{
+	  /* Local IFUNC symbols must be accessed through GOT, similar to
+	     global symbols, to allow for indirection.  */
 	  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
@@ -6209,7 +6646,8 @@ mips_elf_perform_relocation (struct bfd_link_info *info,
 			     const Elf_Internal_Rela *relocation,
 			     bfd_vma value, bfd *input_bfd,
 			     asection *input_section, bfd_byte *contents,
-			     bfd_boolean cross_mode_jump_p)
+			     bfd_boolean cross_mode_jump_p,
+			     bfd_boolean ifunc_p)
 {
   bfd_vma x;
   bfd_byte *location;
@@ -6279,6 +6717,7 @@ mips_elf_perform_relocation (struct bfd_link_info *info,
 	   && r_type == R_MIPS_26
 	   && (x >> 26) == 0x3)		/* jal addr */
 	  || (JALR_TO_BAL_P (input_bfd)
+	      && !ifunc_p
 	      && r_type == R_MIPS_JALR
 	      && x == 0x0320f809)	/* jalr t9 */
 	  || (JR_TO_B_P (input_bfd)
@@ -6377,7 +6816,8 @@ mips_elf_create_dynamic_relocation (bfd *output_bfd,
      in the relocation.  */
   if (h != NULL && ! SYMBOL_REFERENCES_LOCAL (info, &h->root))
     {
-      BFD_ASSERT (htab->is_vxworks || h->global_got_area != GGA_NONE);
+      BFD_ASSERT (htab->is_vxworks || h->global_got_area != GGA_NONE
+		  || h->root.type == STT_GNU_IFUNC);
       indx = h->root.dynindx;
       if (SGI_COMPAT (output_bfd))
 	defined_p = h->root.def_regular;
@@ -6436,31 +6876,44 @@ mips_elf_create_dynamic_relocation (bfd *output_bfd,
   if (defined_p && r_type != R_MIPS_REL32)
     *addendp += symbol;
 
-  if (htab->is_vxworks)
-    /* VxWorks uses non-relative relocations for this.  */
-    outrel[0].r_info = ELF32_R_INFO (indx, R_MIPS_32);
+  /* Morph REL32 in to IRELATIVE fix-up for local IFUNC reference.  */
+  if (h
+      && h->root.type == STT_GNU_IFUNC
+      && SYMBOL_REFERENCES_LOCAL (info, &h->root))
+    {
+      outrel[0].r_info = ELF_R_INFO (output_bfd, 0,
+				     R_MIPS_IRELATIVE);
+      outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
+				     R_MIPS_NONE);
+    }
   else
-    /* The relocation is always an REL32 relocation because we don't
-       know where the shared library will wind up at load-time.  */
-    outrel[0].r_info = ELF_R_INFO (output_bfd, (unsigned long) indx,
-				   R_MIPS_REL32);
-
-  /* For strict adherence to the ABI specification, we should
-     generate a R_MIPS_64 relocation record by itself before the
-     _REL32/_64 record as well, such that the addend is read in as
-     a 64-bit value (REL32 is a 32-bit relocation, after all).
-     However, since none of the existing ELF64 MIPS dynamic
-     loaders seems to care, we don't waste space with these
-     artificial relocations.  If this turns out to not be true,
-     mips_elf_allocate_dynamic_relocation() should be tweaked so
-     as to make room for a pair of dynamic relocations per
-     invocation if ABI_64_P, and here we should generate an
-     additional relocation record with R_MIPS_64 by itself for a
-     NULL symbol before this relocation record.  */
-  outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
-				 ABI_64_P (output_bfd)
-				 ? R_MIPS_64
-				 : R_MIPS_NONE);
+    {
+      if (htab->is_vxworks)
+	/* VxWorks uses non-relative relocations for this.  */
+	outrel[0].r_info = ELF32_R_INFO (indx, R_MIPS_32);
+      else
+	/* The relocation is always an REL32 relocation because we don't
+	   know where the shared library will wind up at load-time.  */
+	outrel[0].r_info = ELF_R_INFO (output_bfd, (unsigned long) indx,
+				       R_MIPS_REL32);
+
+      /* For strict adherence to the ABI specification, we should
+	 generate a R_MIPS_64 relocation record by itself before the
+	 _REL32/_64 record as well, such that the addend is read in as
+	 a 64-bit value (REL32 is a 32-bit relocation, after all).
+	 However, since none of the existing ELF64 MIPS dynamic
+	 loaders seems to care, we don't waste space with these
+	 artificial relocations.  If this turns out to not be true,
+	 mips_elf_allocate_dynamic_relocation() should be tweaked so
+	 as to make room for a pair of dynamic relocations per
+	 invocation if ABI_64_P, and here we should generate an
+	 additional relocation record with R_MIPS_64 by itself for a
+	 NULL symbol before this relocation record.  */
+      outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
+				     ABI_64_P (output_bfd)
+				     ? R_MIPS_64
+				     : R_MIPS_NONE);
+    }
   outrel[2].r_info = ELF_R_INFO (output_bfd, 0, R_MIPS_NONE);
 
   /* Adjust the output offset of the relocation to reference the
@@ -7007,6 +7460,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;
@@ -7920,6 +8375,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.irelplt && !mips_elf_create_ifunc_sections (info))
+    return FALSE;
+
   /* Check for the mips16 stub sections.  */
 
   name = bfd_get_section_name (abfd, sec);
@@ -8178,6 +8640,7 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
       unsigned long r_symndx;
       unsigned int r_type;
       struct elf_link_hash_entry *h;
+      struct mips_elf_link_hash_entry *ih = NULL;
       bfd_boolean can_make_dynamic_p;
       bfd_boolean call_reloc_p;
       bfd_boolean constrain_symbol_p;
@@ -8186,7 +8649,23 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *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;
+
+	  /* Relocation is for local STT_GNU_IFUNC symbol.  */
+	  if (isym->st_info == STT_GNU_IFUNC)
+	    {
+	      /* Ensure that we have a hash entry for this symbol.  */
+	      if ((ih = 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)
@@ -8370,6 +8849,13 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
 		info->flags |= DF_TEXTREL;
 	    }
 	}
+      else if (ih)
+	{
+	  if (!bfd_link_pic (info) && !can_make_dynamic_p)
+	    ih->has_static_relocs = 1;
+	  if (!call_reloc_p)
+	    ih->root.pointer_equality_needed = 1;
+	}
       else if (call_lo16_reloc_p (r_type)
 	       || got_lo16_reloc_p (r_type)
 	       || got_disp_reloc_p (r_type)
@@ -8384,7 +8870,8 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
 	     R_MIPS_CALL_HI16 because these are always followed by an
 	     R_MIPS_GOT_LO16 or R_MIPS_CALL_LO16.  */
 	  if (!mips_elf_record_local_got_symbol (abfd, r_symndx,
-						 rel->r_addend, info, r_type))
+						 rel->r_addend, info,
+						 r_type, NULL))
 	    return FALSE;
 	}
 
@@ -8398,7 +8885,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 && ih == NULL)
 	    {
 	      (*_bfd_error_handler)
 		(_("%B: CALL16 reloc at 0x%lx not against global symbol"),
@@ -8406,6 +8894,10 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
 	      bfd_set_error (bfd_error_bad_value);
 	      return FALSE;
 	    }
+	  if (h && h->type == STT_GNU_IFUNC)
+	    ((struct mips_elf_link_hash_entry *)h)->has_got_relocs = TRUE;
+	  else if (ih)
+	    ih->has_got_relocs = TRUE;
 	  /* Fall through.  */
 
 	case R_MIPS_CALL_HI16:
@@ -8423,10 +8915,17 @@ _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;
 	    }
+	  else
+	    if (ih &&
+		!mips_elf_record_local_got_symbol (abfd, -1, rel->r_addend,
+						   info, r_type, ih))
+	      return FALSE;
 	  break;
 
 	case R_MIPS_GOT_PAGE:
@@ -8459,9 +8958,18 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
 		}
 	      else
 		addend = rel->r_addend;
-	      if (!mips_elf_record_got_page_ref (info, abfd, r_symndx,
-						 h, addend))
+	      if (ih &&
+		  !mips_elf_record_local_got_symbol (abfd, -1, rel->r_addend,
+						     info, r_type, ih))
 		return FALSE;
+	      else if (!mips_elf_record_got_page_ref (info, abfd, r_symndx,
+						   h, addend))
+		  return FALSE;
+
+	      if (h && h->type == STT_GNU_IFUNC)
+		((struct mips_elf_link_hash_entry *)h)->has_got_relocs = TRUE;
+	      else if (ih)
+		ih->has_got_relocs = TRUE;
 
 	      if (h)
 		{
@@ -8518,7 +9026,7 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
 	    {
 	      if (!mips_elf_record_local_got_symbol (abfd, r_symndx,
 						     rel->r_addend,
-						     info, r_type))
+						     info, r_type, NULL))
 		return FALSE;
 	    }
 	  break;
@@ -8964,6 +9472,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
@@ -9242,6 +9751,10 @@ _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_ifunc_symbols, &hti);
+
   if (hti.error)
     return FALSE;
 
@@ -9289,6 +9802,10 @@ mips_elf_lay_out_got (bfd *output_bfd, struct bfd_link_info *info)
   if (!mips_elf_resolve_final_got_entries (info, g))
     return FALSE;
 
+  g->assigned_general_gotno = htab->reserved_gotno;
+  g->local_gotno += g->general_gotno;
+  g->assigned_low_gotno += g->general_gotno;
+
   /* Calculate the total loadable size of the output.  That
      will give us the maximum number of GOT_PAGE entries
      required.  */
@@ -9730,6 +10247,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)
 	{
@@ -9875,6 +10394,13 @@ _bfd_mips_elf_size_dynamic_sections (bfd *output_bfd,
 	  if (! MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_PLTGOT, 0))
 	    return FALSE;
 	}
+
+      if (elf_tdata (output_bfd)->has_gnu_symbols & elf_gnu_symbol_ifunc)
+	{
+	  if (! MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_GENERAL_GOTNO, 0))
+	    return FALSE;
+	}
+
       if (htab->is_vxworks
 	  && !elf_vxworks_add_dynamic_entries (output_bfd, info))
 	return FALSE;
@@ -10006,6 +10532,8 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
       Elf_Internal_Shdr *symtab_hdr;
       struct elf_link_hash_entry *h;
       bfd_boolean rel_reloc;
+      bfd_boolean local_gnu_ifunc_p = FALSE;
+      bfd_boolean gnu_ifunc_p = FALSE;
 
       rel_reloc = (NEWABI_P (input_bfd)
 		   && mips_elf_rel_relocation_p (input_bfd, input_section,
@@ -10019,6 +10547,23 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
 	{
 	  sec = local_sections[r_symndx];
 	  h = NULL;
+
+	  Elf_Internal_Sym *isym;
+	  struct mips_elf_link_hash_table *htab;
+	  htab = mips_elf_hash_table (info);
+	  isym = bfd_sym_from_r_symndx (&htab->sym_cache, input_bfd, r_symndx);
+
+	  if (isym == NULL)
+	    return FALSE;
+
+	  /* Relocation is for local STT_GNU_IFUNC symbol.  */
+	  if (isym->st_info == STT_GNU_IFUNC)
+	    {
+	      /* Ensure that we have a hash entry for this symbol.  */
+	      if (get_local_sym_hash (htab, input_bfd, rel) == NULL)
+		return FALSE;
+	      local_gnu_ifunc_p = TRUE;
+	    }
 	}
       else
 	{
@@ -10046,6 +10591,8 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
 	  continue;
 	}
 
+      gnu_ifunc_p = (local_gnu_ifunc_p || (h && h->type == STT_GNU_IFUNC));
+
       if (r_type == R_MIPS_64 && ! NEWABI_P (input_bfd))
 	{
 	  /* Some 32-bit code uses R_MIPS_64.  In particular, people use
@@ -10077,7 +10624,8 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
 	      if (hi16_reloc_p (r_type)
 		  || (got16_reloc_p (r_type)
 		      && mips_elf_local_relocation_p (input_bfd, rel,
-						      local_sections)))
+						      local_sections)
+		      && !local_gnu_ifunc_p))
 		{
 		  if (!mips_elf_add_lo16_rel_addend (input_bfd, rel, relend,
 						     contents, &addend))
@@ -10168,7 +10716,7 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
 
 	      if (! mips_elf_perform_relocation (info, howto, rel, addend,
 						 input_bfd, input_section,
-						 contents, FALSE))
+						 contents, FALSE, gnu_ifunc_p))
 		return FALSE;
 	    }
 
@@ -10322,7 +10870,8 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
       /* Actually perform the relocation.  */
       if (! mips_elf_perform_relocation (info, howto, rel, value,
 					 input_bfd, input_section,
-					 contents, cross_mode_jump_p))
+					 contents, cross_mode_jump_p,
+					 gnu_ifunc_p))
 	return FALSE;
     }
 
@@ -10467,6 +11016,242 @@ 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,
+		      bfd_vma igotplt_address)
+{
+  bfd_byte *loc;
+  const bfd_vma *iplt_entry;
+  bfd_vma high = mips_elf_high (igotplt_address);
+  bfd_vma low = igotplt_address & 0xffff;
+
+  /* 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 (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;
+
+      if (highest)
+	{
+	  /* Full 64-bit address space.  */
+	  bfd_put_32 (output_bfd, iplt_entry[0] | highest, loc);
+	  bfd_put_32 (output_bfd, iplt_entry[1] | high, loc + 4);
+	  bfd_put_32 (output_bfd, iplt_entry[2] | higher, loc + 8);
+	  bfd_put_32 (output_bfd, iplt_entry[3], 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 (higher)
+	{
+	  /* 48-bit address space.  */
+	  iplt_entry = mips64_48b_exec_iplt_entry;
+	  bfd_put_32 (output_bfd, iplt_entry[0] | higher, loc);
+	  bfd_put_32 (output_bfd, iplt_entry[1] | high, loc + 4);
+	  bfd_put_32 (output_bfd, iplt_entry[2], loc + 8);
+	  bfd_put_32 (output_bfd, iplt_entry[3] | low, loc + 12);
+	  bfd_put_32 (output_bfd, iplt_entry[4], loc + 16);
+	  bfd_put_32 (output_bfd, iplt_entry[5], loc + 20);
+	}
+      else
+	{
+	  /* 32-bit address space.  */
+	  bfd_put_32 (output_bfd, iplt_entry[0] | high, loc);
+	  bfd_put_32 (output_bfd, iplt_entry[5] | low, loc + 4);
+	  bfd_put_32 (output_bfd, iplt_entry[6], loc + 8);
+	  bfd_put_32 (output_bfd, iplt_entry[7], loc + 12);
+	}
+    }
+  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
+    {
+      if (MIPSR6_P (output_bfd))
+	iplt_entry = mips32r6_exec_iplt_entry;
+      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;
+}
+
+/* Find local GOT index for VALUE.  Return -1 if no GOT slot is found.  */
+
+static bfd_vma
+mips_elf_check_local_got_index (bfd *abfd, struct bfd_link_info *info,
+				struct mips_elf_link_hash_entry *h)
+{
+  struct mips_got_entry lookup, *entry;
+  void **loc;
+  struct mips_got_info *g;
+  struct mips_elf_link_hash_table *htab;
+
+  htab = mips_elf_hash_table (info);
+  BFD_ASSERT (htab != NULL);
+
+  g = mips_elf_bfd_got (abfd, FALSE);
+
+  /* Check for existing local GOT entry.  */
+  if (g != NULL)
+    {
+      lookup.abfd = abfd;
+      lookup.symndx = -1;
+      lookup.d.h = h;
+      lookup.tls_type = GOT_TLS_NONE;
+      loc = htab_find_slot (g->got_entries, &lookup, NO_INSERT);
+    }
+  else
+    return -1;
+
+  if (loc && *loc)
+    {
+      entry = (struct mips_got_entry *) *loc;
+      return entry->gotidx;
+    }
+  else
+    return -1;
+}
+
+/* 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_vma igotplt_address = 0;
+  int igot_offset = -1;
+  asection *gotsect, *relsect;
+  bfd_vma value = sym->st_value;
+
+  if (MIPS16_P (output_bfd) || MICROMIPS_P (output_bfd))
+    value |= 1;
+
+  if (!hmips->needs_igot)
+    {
+      gotsect = htab->sgot;
+      /* Check if IFUNC symbol already has an assigned GOT slot.  */
+      igot_offset = mips_elf_check_local_got_index (output_bfd, info, hmips);
+    }
+  else
+    {
+      bfd_byte *loc;
+      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, value, loc);
+      else
+	bfd_put_32 (output_bfd, value, loc);
+    }
+
+  igotplt_address = (gotsect->output_section->vma + gotsect->output_offset
+		     + igot_offset);
+
+  relsect = mips_get_irel_section (info, htab);
+
+  if (igot_offset >= 0)
+    {
+      if (hmips->needs_iplt && relsect->contents == NULL)
+	{
+	  /* Allocate memory for the relocation section contents.  */
+	  relsect->contents = bfd_zalloc (dynobj, relsect->size);
+	  if (relsect->contents == NULL)
+	    return FALSE;
+	}
+
+      if (hmips->needs_iplt || SYMBOL_REFERENCES_LOCAL (info, &hmips->root))
+	/* Emit an IRELATIVE relocation against the [I]GOT entry.  */
+	mips_elf_output_dynamic_relocation (output_bfd, relsect,
+					    relsect->reloc_count++, 0,
+					    R_MIPS_IRELATIVE, igotplt_address);
+      else
+	{
+	  /* Emit an R_MIPS_REL32 relocation against global GOT entry for
+	     a preemptible symbol.  */
+	  asection *sec = hmips->root.root.u.def.section;
+	  Elf_Internal_Rela rel[3];
+
+	  memset (rel, 0, sizeof (rel));
+	  rel[0].r_info = ELF_R_INFO (output_bfd, 0, R_MIPS_REL32);
+	  rel[0].r_offset = rel[1].r_offset = rel[2].r_offset = igot_offset;
+
+	  mips_elf_create_dynamic_relocation (output_bfd, info, rel, hmips,
+					      sec, value, NULL,
+					      gotsect);
+	}
+    }
+  /* If necessary, generate the corresponding .iplt entry.  */
+  if (hmips->needs_iplt
+      && !mips_elf_create_iplt (output_bfd, htab, hmips, igotplt_address))
+    return FALSE;
+
+  return TRUE;
+}
+
 /* Finish up dynamic symbol handling.  We set the contents of various
    dynamic sections here.  */
 
@@ -10794,6 +11579,19 @@ _bfd_mips_elf_finish_dynamic_symbol (bfd *output_bfd,
       sym->st_other = other;
     }
 
+  if (hmips->root.type == STT_GNU_IFUNC)
+    {
+      if (hmips->needs_ireloc
+	  && !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.  */
@@ -10957,9 +11755,41 @@ _bfd_mips_elf_finish_dynamic_symbol (bfd *output_bfd,
       sym->st_other -= STO_MICROMIPS;
     }
 
+  if (hmips->needs_iplt)
+    {
+      /* 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);
+  isym.st_other = h->root.other;
+  if (ELF_ST_IS_COMPRESSED (isym.st_other))
+    isym.st_value |= 1;
+
+  return _bfd_mips_elf_finish_dynamic_symbol (info->output_bfd, info,
+					      &h->root, &isym);
+}
+
 /* Likewise, for VxWorks.  */
 
 bfd_boolean
@@ -11345,6 +12175,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;
@@ -11515,6 +12352,10 @@ _bfd_mips_elf_finish_dynamic_sections (bfd *output_bfd,
 	      dyn.d_un.d_ptr = s->vma;
 	      break;
 
+	    case DT_MIPS_GENERAL_GOTNO:
+	      dyn.d_un.d_val = htab->reserved_gotno + gg->general_gotno;
+	      break;
+
 	    case DT_RELASZ:
 	      BFD_ASSERT (htab->is_vxworks);
 	      /* The count does not include the JUMP_SLOT relocations.  */
@@ -13849,6 +14690,48 @@ _bfd_mips_elf_relax_section (bfd *abfd, asection *sec,
   return FALSE;
 }
 \f
+/* 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 *
@@ -13872,6 +14755,16 @@ _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;
 }
 
@@ -15509,7 +16402,9 @@ _bfd_mips_elf_get_target_dtag (bfd_vma dtag)
       return "DT_MIPS_PLTGOT";
     case DT_MIPS_RWPLT:
       return "DT_MIPS_RWPLT";
-    }
+    case DT_MIPS_GENERAL_GOTNO:
+      return "DT_MIPS_GENERAL_GOTNO";
+   }
 }
 
 /* Return the meaning of Tag_GNU_MIPS_ABI_FP value FP, or null if
@@ -16123,7 +17018,7 @@ _bfd_mips_elf_get_synthetic_symtab (bfd *abfd,
 void
 _bfd_mips_post_process_headers (bfd *abfd, struct bfd_link_info *link_info)
 {
-  struct mips_elf_link_hash_table *htab;
+  struct mips_elf_link_hash_table *htab = NULL;
   Elf_Internal_Ehdr *i_ehdrp;
 
   i_ehdrp = elf_elfheader (abfd);
@@ -16141,6 +17036,9 @@ _bfd_mips_post_process_headers (bfd *abfd, struct bfd_link_info *link_info)
   if (mips_elf_tdata (abfd)->abiflags.fp_abi == Val_GNU_MIPS_ABI_FP_64
       || mips_elf_tdata (abfd)->abiflags.fp_abi == Val_GNU_MIPS_ABI_FP_64A)
     i_ehdrp->e_ident[EI_ABIVERSION] = 3;
+
+  if (elf_tdata (abfd)->has_gnu_symbols & elf_gnu_symbol_ifunc)
+    i_ehdrp->e_ident[EI_ABIVERSION] = 4;
 }
 
 int
diff --git a/bfd/libbfd.h b/bfd/libbfd.h
index d7183d3..aa9b0c7 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 3ef704f..496c465 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 47ac1ad..0091539 100644
--- a/binutils/readelf.c
+++ b/binutils/readelf.c
@@ -1729,6 +1729,8 @@ 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_GENERAL_GOTNO: return "MIPS_GENERAL_GOTNO";
+
     default:
       return NULL;
     }
@@ -8612,6 +8614,7 @@ dynamic_section_mips_val (Elf_Internal_Dyn * entry)
     case DT_MIPS_DELTA_SYM_NO:
     case DT_MIPS_DELTA_CLASSSYM_NO:
     case DT_MIPS_COMPACT_SIZE:
+    case DT_MIPS_GENERAL_GOTNO:
       print_vma (entry->d_un.d_ptr, DEC);
       break;
 
diff --git a/elfcpp/elfcpp.h b/elfcpp/elfcpp.h
index e0eae42..c77b86e 100644
--- a/elfcpp/elfcpp.h
+++ b/elfcpp/elfcpp.h
@@ -866,6 +866,8 @@ enum DT
   DT_MIPS_PLTGOT = 0x70000032,
   // Points to the base of a writable PLT.
   DT_MIPS_RWPLT = 0x70000034,
+  // The GOT index of the first implicitly relocated GOT entry.
+  DT_MIPS_GENERAL_GOTNO = 0x70000036,
 
   DT_AUXILIARY = 0x7ffffffd,
   DT_USED = 0x7ffffffe,
diff --git a/include/elf/mips.h b/include/elf/mips.h
index 46f63fe..0034884 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)
@@ -751,6 +754,10 @@ extern void bfd_mips_elf32_swap_reginfo_out
 
 /* Relative offset of run time loader map, used for debugging.  */
 #define DT_MIPS_RLD_MAP_REL    0x70000035
+
+/* The GOT index of the first implicitly relocated GOT entry.  */
+#define DT_MIPS_GENERAL_GOTNO  0x70000036
+
 \f
 /* Flags which may appear in a DT_MIPS_FLAGS entry.  */
 
-- 
1.7.9.5

[-- Attachment #2: Spec.txt --]
[-- Type: text/plain, Size: 8584 bytes --]

====
MIPS IFUNC ABI specification
====

Introduction
----

This document describes the working of GNU indirect functions(IFUNC)
for MIPS.

Terminology
----
Shared file or shared object refers to any object file with e_type
ET_DYN. Unless otherwise distinguished, this includes dynamic shared libraries
(PIC) and position-independent executables (PIE).

Executable file or executable refers to any object file with e_type
ET_EXEC. Unless otherwise distinguished, this includes statically linked and
dynamically linked executables.

Relocations for IFUNC resolution
----

The relocation table may now contain a new relocation type generated by the
static linker:

R_MIPS_IRELATIVE(128)
This relocation marks the location of an IFUNC indirection that needs to be
resolved by the dynamic linker at load time. The relocation step calls the
IFUNC resolver function given at the relocation offset possibly with
additional processor-specific (hardware capability) arguments and writes the
address returned by the resolver back to the relocation offset. For
shared objects, the relocation step may add a base address to the
relocation offset to calculate the run-time address of the resolver
function.

The operation can be described as:
[B + O] <= ((ifunc_proto) B + O) (<hwcaps>)
where O = relocation offset
      B = relative relocation bias; =0 for executable files

IFUNCs in executable files
----
Code/data references to IFUNCs in executable files are resolved by
means of indirection stubs and an indirection table, except where
all references are GOT-based calls.

IGOT sections - the indirection table
----
If symbol has no GOT entry ,indirection is acheived by creating an entry in
the new IGOT section. Each entry in IGOT has a corresponding IRELATIVE
relocation. This IGOT entry is initialized to point to the resolver function
at link time and modified to point to the resolved function by the IRELATIVE
fix-up. Executable files are not relocatable, so the relocation bias is always
zero.

IPLT section - indirection stubs
----

The IFUNC Procedure Linkage Table (IPLT) consists of a set of stubs generated
by the static linker to stand in for IFUNC references in executable file that
can not be resolved via a GOT entry.

If an IPLT stub is required, the IFUNC call is performed by an absolute JAL
instruction to the IPLT stub. The stub redirects the call to the actual
function by reading a pointer from the IGOT section. Data references use the
absolute address of the IPLT stub in place of the IFUNC. IPLT Stubs are
required for all IFUNC symbols which are not bound locally.

When an IFUNC symbol binds locally there are 6 cases:

    PIC relocs?  non-PIC relocs?  all call-only?   variant
    n            y                y                (1)
    y            n                y                (2)
    y            y                y                (3)
    n            y                n                (4)
    y            n                n                (5)
    y            y                n                (5)

    (1) Need an IPLT.  Nothing other than the IPLT needs the associated
        IRELATIVE GOT entry, so the entry can go in .igot.

    (2) No need for an IPLT, we can just have a .got entry with an IRELATIVE
        relocation.

    (3) Need an IPLT.  The associated IRELATIVE GOT entry should go in .got
        so that it can be used by the PIC references.

    (4) Same as (1), using the IPLT as the canonical function address.

    (5) Need an IPLT, which acts as the canonical function address.
        Nothing other than the IPLT needs the associated IRELATIVE GOT entry,
        so that entry can go in .igot.  However, we need a separate
        .got entry to satisfy the PIC references.  This can be a normal
        local GOT entry (i.e. not use the new area).

    If the symbol is exported then for (1)-(3) the symbol definition
    is the IFUNC.  For (4) and (5) it's the IPLT.


IFUNCs in shared objects
----

In the case of shared objects, data references to IFUNCs use explicit
dynamic relocations, where as calls or code-references are routed
through the GOT which itself is relocated implicitly by the dynamic
linker. Morever, global symbols can be pre-empted by the dynamic
linker at load time. There are 4 scenarios to consider:

Data relocations for local IFUNC symbols
----

The static linker replaces each R_MIPS_REL32 dynamic relocation for a
local IFUNC reference with an IRELATIVE relocation. The IRELATIVE
resolution step above includes an implicit relative relocation. Shared
objects have a non-zero bias element provided by the dynamic linker.

Code relocations for local IFUNC symbols
----

Non-preemptible IFUNC symbols have entries in the local GOT region.
Call or reference in code is resolved by reading from the
corresponding GOT entry. The local GOT can be considered as a series
of implicit REL32 relocations. For IFUNC resolution, we must
replace each implicit REL32 relocation with an explicit IRELATIVE
relocation, just as we did above for explicit REL32 relocations in the
case of data relocations.

Thus, each IFUNC entry in the local IGOT gets a corresponding
IRELATIVE relocation. To prevent these entries from being relocated
twice (once implicitly as part of GOT relocation and again
by the IRELATIVE relocation), a part of the GOT is marked as
containing only explictly relocated entries. This part is skipped over
during normal GOT relocation and only modified by explicit
relocations. This part is marked off from the implicitly relocated
local GOT region by the dynamic tag MIPS_GENERAL_GOTNO.

Data relocations for global IFUNC symbols
----

For a global preemptible symbol, the dynamic linker must determine at
load-time, whether the IFUNC is preempted by an external
symbol. R_MIPS_REL32 relocations against such symbols cannot simply be
replaced with IRELATIVE ones.  The REL32 relocation step for such
references must be modified to include an implicit IRELATIVE
relocation, to be performed only if the resolved symbol is also an
IFUNC. The relocations produced remain exactly the same in this case,
but the relocation process employed by the dynamic linker changes to
account for symbol preemption.

Code relocations for global IFUNC symbols
----

Preemptible global IFUNC symbols have entries in the global GOT
region. A call or reference in code is resolved by reading from the
corresponding GOT entry. The global GOT can be considered as a
series of implicit REL32 relocations.

The static linker generates an explicit R_MIPS_REL32 relocation
against the GOT entry of each global IFUNC as a means of delaying the
IFUNC resolution step. These R_MIPS_REL32 relocations for code behave
similar to explicit data relocations described above, encapsulating
both symbol preemption and an optional IRELATIVE relocation. Since
these GOT entries are explicitly relocated, they too are allocated with 
the general GOT region as described above.

Dynamic Section
----

Dynamic section entries give information to the dynamic linker.  The following
new dynamic table entries are required for IFUNC resolution:

MIPS_GENERAL_GOTNO(36)
        Number of explicitly relocated GOT entries. Alternatively, start of
        the normally relocated local GOT region. This entry is optional. If not
	present, its value is assumed by the linker to be 1(non-GNU) or 2(GNU).

Revised GOT layout
----

GOT0: Reserved for dynamic linker
GOT1: Reserved, GNU-specific, optional

GOT2 ...
MIPS_GENERAL_GOTNO - 1: Explicitly relocated GOT entries (local & global)

MIPS_GENERAL_GOTNO ...
MIPS_LOCAL_GOTNO - 1: Implicitly relocated local GOT entries

MIPS_LOCAL_GOTNO ...
MIPS_SYMTABNO - 1: Implicitly relocated Global GOT entries

IFUNC address in assembly code
----

In shared objects, IFUNC references must always be made through GOT, for both
local and global symbols. Each IFUNC symbol will always have a corresponding
GOT entry within a reachable offset of $gp.

For 32-bit, address of IFUNC symbol foo can be obtained as:
   lw      <reg>,%got16(foo)($28)

For 64-bit, address of IFUNC symbol foo can be obtained as:
   ld      <reg>,%got_disp(foo)($28)

It is not necessary to do a 2-step calculation by adding the %lo16
bits of the symbol for local IFUNC symbols. Doing so will result in
a spurious address.

Non-PIC calls to PIC IFUNC
----
A call from non-PIC code to PIC function needs to be routed through a stub
that initializes $25 for the PIC function. Since all IPLT stubs initialize
$25, an extra PIC stub is not needed and will not be generated for such calls
where the IPLT stub has already been generated.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH 2/2] [RFC] Add IFUNC support for MIPS (v4)
  2015-12-22 22:29                       ` Richard Sandiford
  2016-01-08  0:10                         ` [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v4) Faraz Shahbazker
@ 2016-01-08  0:11                         ` Faraz Shahbazker
  2016-03-06  7:18                           ` Maciej W. Rozycki
  1 sibling, 1 reply; 35+ messages in thread
From: Faraz Shahbazker @ 2016-01-08  0:11 UTC (permalink / raw)
  To: binutils, rdsandiford; +Cc: macro

Test cases

ld/testsuite/ChangeLog:

	* ld-ifunc/ifunc.exp: Enable IFUNC tests for MIPS targets
	* 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-7-o32.r, ifunc-7-o32.sec, ifunc-8-o32.g,
	ifunc-8-o32.r, ifunc-8-o32.sec, ifunc-9-o32.g, ifunc-9-o32.r,
	ifunc-9-o32.sec ifunc-9-o32.t, ifunc-10-o32.r, ifunc-10-o32.secm,
	ifunc-11-o32.g, ifunc-11-o32.r, ifunc-11-o32.sec, ifunc-12-o32.g,
	ifunc-12-o32.r, ifunc-12-o32.sec, ifunc-13-o32.r,
	ifunc-13-o32.sec, ifunc-13-o32.t, ifunc-local-1.s,
	ifunc-local-2.s, ifunc-local-3.s, ifunc-local-4.s,
	ifunc-local-5.s, ifunc-local-6.s, ifunc-dyn-def.s, ifunc-dyn-main.s,
	ifunc-dyn-ref.s, ifunc-dyn.ld, ifunc-iplt.ld,
	ifunc-iplt-0x400000.t, ifunc-iplt-0x400000000.t,
	ifunc-iplt-0x4000000000000.t, ifunc-iplt-micromips.t,
	ifunc-iplt-micromips.igot, ifunc-iplt-mips16.t,
	ifunc-iplt-mips16.igot, ifunc-iplt-mips32r6.t, ifunc-static-def.s,
	ifunc-static-def-mips16.s, ifunc-static-main.s,
	ifunc-static-main-mips16.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-ifunc/ifunc.exp                    |    3 +-
 ld/testsuite/ld-mips-elf/ifunc-10-o32.r            |    3 +
 ld/testsuite/ld-mips-elf/ifunc-10-o32.sec          |    5 +
 ld/testsuite/ld-mips-elf/ifunc-11-o32.g            |    7 +
 ld/testsuite/ld-mips-elf/ifunc-11-o32.r            |    3 +
 ld/testsuite/ld-mips-elf/ifunc-11-o32.sec          |    5 +
 ld/testsuite/ld-mips-elf/ifunc-12-o32.g            |    7 +
 ld/testsuite/ld-mips-elf/ifunc-12-o32.r            |    3 +
 ld/testsuite/ld-mips-elf/ifunc-12-o32.sec          |    5 +
 ld/testsuite/ld-mips-elf/ifunc-13-o32.r            |    4 +
 ld/testsuite/ld-mips-elf/ifunc-13-o32.sec          |    3 +
 ld/testsuite/ld-mips-elf/ifunc-13-o32.t            |    5 +
 ld/testsuite/ld-mips-elf/ifunc-3-n32.r             |    4 +
 ld/testsuite/ld-mips-elf/ifunc-3-n32.sym           |    3 +
 ld/testsuite/ld-mips-elf/ifunc-3-n32.t             |   10 +
 ld/testsuite/ld-mips-elf/ifunc-3-n64.r             |    8 +
 ld/testsuite/ld-mips-elf/ifunc-3-n64.sym           |    3 +
 ld/testsuite/ld-mips-elf/ifunc-3-n64.t             |   11 +
 ld/testsuite/ld-mips-elf/ifunc-3-o32.r             |    4 +
 ld/testsuite/ld-mips-elf/ifunc-3-o32.sym           |    3 +
 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.sym           |    4 +
 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.sym           |    4 +
 ld/testsuite/ld-mips-elf/ifunc-4-n64.t             |   11 +
 ld/testsuite/ld-mips-elf/ifunc-4-o32.r             |    3 +
 ld/testsuite/ld-mips-elf/ifunc-4-o32.sym           |    4 +
 ld/testsuite/ld-mips-elf/ifunc-4-o32.t             |   11 +
 ld/testsuite/ld-mips-elf/ifunc-5-n32.g             |   11 +
 ld/testsuite/ld-mips-elf/ifunc-5-n32.r             |    4 +
 ld/testsuite/ld-mips-elf/ifunc-5-n32.sym           |    4 +
 ld/testsuite/ld-mips-elf/ifunc-5-n64.g             |   12 +
 ld/testsuite/ld-mips-elf/ifunc-5-n64.r             |    8 +
 ld/testsuite/ld-mips-elf/ifunc-5-n64.sym           |    4 +
 ld/testsuite/ld-mips-elf/ifunc-5-o32.g             |   11 +
 ld/testsuite/ld-mips-elf/ifunc-5-o32.r             |    4 +
 ld/testsuite/ld-mips-elf/ifunc-5-o32.sym           |    3 +
 ld/testsuite/ld-mips-elf/ifunc-5.dyn               |    3 +
 ld/testsuite/ld-mips-elf/ifunc-6-n32.r             |    4 +
 ld/testsuite/ld-mips-elf/ifunc-6-n32.sym           |    3 +
 ld/testsuite/ld-mips-elf/ifunc-6-n64.r             |    8 +
 ld/testsuite/ld-mips-elf/ifunc-6-n64.sym           |    3 +
 ld/testsuite/ld-mips-elf/ifunc-6-o32.r             |    4 +
 ld/testsuite/ld-mips-elf/ifunc-6-o32.sym           |    3 +
 ld/testsuite/ld-mips-elf/ifunc-7-o32.r             |    3 +
 ld/testsuite/ld-mips-elf/ifunc-7-o32.sec           |    4 +
 ld/testsuite/ld-mips-elf/ifunc-8-o32.g             |    6 +
 ld/testsuite/ld-mips-elf/ifunc-8-o32.r             |    3 +
 ld/testsuite/ld-mips-elf/ifunc-8-o32.sec           |   17 +
 ld/testsuite/ld-mips-elf/ifunc-9-o32.g             |    6 +
 ld/testsuite/ld-mips-elf/ifunc-9-o32.r             |    4 +
 ld/testsuite/ld-mips-elf/ifunc-9-o32.sec           |   17 +
 ld/testsuite/ld-mips-elf/ifunc-9-o32.t             |    8 +
 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-0x400000.t     |   11 +
 ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000000.t  |   13 +
 .../ld-mips-elf/ifunc-iplt-0x4000000000000.t       |   14 +
 ld/testsuite/ld-mips-elf/ifunc-iplt-micromips.igot |    7 +
 ld/testsuite/ld-mips-elf/ifunc-iplt-micromips.t    |    9 +
 ld/testsuite/ld-mips-elf/ifunc-iplt-mips16.igot    |    8 +
 ld/testsuite/ld-mips-elf/ifunc-iplt-mips16.t       |   12 +
 ld/testsuite/ld-mips-elf/ifunc-iplt-mips32r6.t     |   11 +
 ld/testsuite/ld-mips-elf/ifunc-iplt.ld             |   26 ++
 ld/testsuite/ld-mips-elf/ifunc-local-1.s           |  176 ++++++++
 ld/testsuite/ld-mips-elf/ifunc-local-2.s           |   99 +++++
 ld/testsuite/ld-mips-elf/ifunc-local-3.s           |  135 ++++++
 ld/testsuite/ld-mips-elf/ifunc-local-4.s           |  183 +++++++++
 ld/testsuite/ld-mips-elf/ifunc-local-5.s           |  106 +++++
 ld/testsuite/ld-mips-elf/ifunc-local-6.s           |  112 +++++
 ld/testsuite/ld-mips-elf/ifunc-static-def-mips16.s |  434 ++++++++++++++++++++
 ld/testsuite/ld-mips-elf/ifunc-static-def.s        |  145 +++++++
 .../ld-mips-elf/ifunc-static-main-mips16.s         |  157 +++++++
 ld/testsuite/ld-mips-elf/ifunc-static-main.s       |   35 ++
 ld/testsuite/ld-mips-elf/ifunc-static-ref.s        |   36 ++
 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            |  282 +++++++++++++
 87 files changed, 2558 insertions(+), 1 deletion(-)
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-10-o32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-10-o32.sec
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-11-o32.g
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-11-o32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-11-o32.sec
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-12-o32.g
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-12-o32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-12-o32.sec
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-13-o32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-13-o32.sec
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-13-o32.t
 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.sym
 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.sym
 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.sym
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-4-o32.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-5-n32.g
 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.g
 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.g
 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-5.dyn
 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-7-o32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-7-o32.sec
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-8-o32.g
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-8-o32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-8-o32.sec
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-9-o32.g
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-9-o32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-9-o32.sec
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-9-o32.t
 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-0x400000.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000000.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt-0x4000000000000.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt-micromips.igot
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt-micromips.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt-mips16.igot
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt-mips16.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt-mips32r6.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt.ld
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-local-1.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-local-2.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-local-3.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-local-4.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-local-5.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-local-6.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-static-def-mips16.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-static-def.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-static-main-mips16.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-ifunc/ifunc.exp b/ld/testsuite/ld-ifunc/ifunc.exp
index 5a5bf72..25b18b1 100644
--- a/ld/testsuite/ld-ifunc/ifunc.exp
+++ b/ld/testsuite/ld-ifunc/ifunc.exp
@@ -30,7 +30,8 @@ if {!(([istarget "i?86-*-*"]
        || [istarget "powerpc*-*-*"]
        || [istarget "aarch64*-*-*"]
        || [istarget "sparc*-*-*"]
-       || [istarget "s390*-*-*"])
+       || [istarget "s390*-*-*"]
+       || [istarget "mips*-*-*"])
       && ([istarget "*-*-elf*"]
 	  || [istarget "*-*-nacl*"]
 	  || (([istarget "*-*-linux*"]
diff --git a/ld/testsuite/ld-mips-elf/ifunc-10-o32.r b/ld/testsuite/ld-mips-elf/ifunc-10-o32.r
new file mode 100644
index 0000000..789b7d8
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-10-o32.r
@@ -0,0 +1,3 @@
+Relocation section '.rel.dyn' at offset 0xe8 contains 1 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00410254  00000080 R_MIPS_IRELATIVE 
diff --git a/ld/testsuite/ld-mips-elf/ifunc-10-o32.sec b/ld/testsuite/ld-mips-elf/ifunc-10-o32.sec
new file mode 100644
index 0000000..8f68890
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-10-o32.sec
@@ -0,0 +1,5 @@
+#...
+.* \.iplt .*
+.*
+.* \.igot .*
+#pass
\ No newline at end of file
diff --git a/ld/testsuite/ld-mips-elf/ifunc-11-o32.g b/ld/testsuite/ld-mips-elf/ifunc-11-o32.g
new file mode 100644
index 0000000..2d42af5
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-11-o32.g
@@ -0,0 +1,7 @@
+#...
+004101a0 <_GLOBAL_OFFSET_TABLE_>:
+  4101a0:	00000000 	nop
+  4101a4:	80000000 	lb	zero,0\(zero\)
+  4101a8:	00400000 	0x400000
+  4101ac:	00400170 	0x400170
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-11-o32.r b/ld/testsuite/ld-mips-elf/ifunc-11-o32.r
new file mode 100644
index 0000000..6803275
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-11-o32.r
@@ -0,0 +1,3 @@
+Relocation section '.rel.dyn' at offset 0xe8 contains 1 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00410194  00000080 R_MIPS_IRELATIVE 
diff --git a/ld/testsuite/ld-mips-elf/ifunc-11-o32.sec b/ld/testsuite/ld-mips-elf/ifunc-11-o32.sec
new file mode 100644
index 0000000..8f68890
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-11-o32.sec
@@ -0,0 +1,5 @@
+#...
+.* \.iplt .*
+.*
+.* \.igot .*
+#pass
\ No newline at end of file
diff --git a/ld/testsuite/ld-mips-elf/ifunc-12-o32.g b/ld/testsuite/ld-mips-elf/ifunc-12-o32.g
new file mode 100644
index 0000000..1fe54c1
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-12-o32.g
@@ -0,0 +1,7 @@
+#...
+004101b0 <_GLOBAL_OFFSET_TABLE_>:
+  4101b0:	00000000 	nop
+  4101b4:	80000000 	lb	zero,0\(zero\)
+  4101b8:	00400000 	0x400000
+  4101bc:	00400180 	0x400180
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-12-o32.r b/ld/testsuite/ld-mips-elf/ifunc-12-o32.r
new file mode 100644
index 0000000..f4a3a8b
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-12-o32.r
@@ -0,0 +1,3 @@
+Relocation section '.rel.dyn' at offset 0xe8 contains 1 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+004101a4  00000080 R_MIPS_IRELATIVE 
diff --git a/ld/testsuite/ld-mips-elf/ifunc-12-o32.sec b/ld/testsuite/ld-mips-elf/ifunc-12-o32.sec
new file mode 100644
index 0000000..8f68890
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-12-o32.sec
@@ -0,0 +1,5 @@
+#...
+.* \.iplt .*
+.*
+.* \.igot .*
+#pass
\ No newline at end of file
diff --git a/ld/testsuite/ld-mips-elf/ifunc-13-o32.r b/ld/testsuite/ld-mips-elf/ifunc-13-o32.r
new file mode 100644
index 0000000..9101767
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-13-o32.r
@@ -0,0 +1,4 @@
+Relocation section '.rel.dyn' at offset 0xe8 contains 2 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+004101e8  00000080 R_MIPS_IRELATIVE 
+00000000  00000000 R_MIPS_NONE      
diff --git a/ld/testsuite/ld-mips-elf/ifunc-13-o32.sec b/ld/testsuite/ld-mips-elf/ifunc-13-o32.sec
new file mode 100644
index 0000000..da8594e
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-13-o32.sec
@@ -0,0 +1,3 @@
+#...
+.* \.iplt .*
+#pass
\ No newline at end of file
diff --git a/ld/testsuite/ld-mips-elf/ifunc-13-o32.t b/ld/testsuite/ld-mips-elf/ifunc-13-o32.t
new file mode 100644
index 0000000..f13cfb4
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-13-o32.t
@@ -0,0 +1,5 @@
+#...
+00400190 <main>:
+#...
+  4001a0:	0c100070 	jal	4001c0 <.iplt.func1>
+#pass
\ No newline at end of file
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..b14c140
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-n32.r
@@ -0,0 +1,4 @@
+Relocation section '.rel.dyn' at offset 0x12000 contains 2 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00000000  00000000 R_MIPS_NONE      
+00000800  00000080 R_MIPS_IRELATIVE 
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..a0a1c12
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-n32.sym
@@ -0,0 +1,3 @@
+#...
+    2   0: 00000400    28 FUNC    GLOBAL DEFAULT   1 func1
+#pass
\ No newline at end of file
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..53b6455
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-n64.r
@@ -0,0 +1,8 @@
+Relocation section '.rel.dyn' at offset 0x12000 contains 2 entries:
+  Offset          Info           Type           Sym. Value    Sym. Name
+000000000000  000000000000 R_MIPS_NONE      
+                    Type2: R_MIPS_NONE      
+                    Type3: R_MIPS_NONE      
+000000000800  000000000080 R_MIPS_IRELATIVE 
+                    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..ea4760a
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-n64.sym
@@ -0,0 +1,3 @@
+#...
+    2   0: 0000000000000400    28 FUNC    GLOBAL DEFAULT   1 func1
+#pass
\ No newline at end of file
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..0df1db2
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-n64.t
@@ -0,0 +1,11 @@
+tmpdir/ifunc-3-n64:     file format elf64-tradbigmips
+
+
+Disassembly of section .iplt:
+
+0000000000000400 <.iplt.func1>:
+ 400:	3c0f0000 	lui	t3,0x0
+ 404:	ddf90800 	ld	t9,2048\(t3\)
+ 408:	03200008 	jr	t9
+ 40c:	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..b14c140
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-o32.r
@@ -0,0 +1,4 @@
+Relocation section '.rel.dyn' at offset 0x12000 contains 2 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00000000  00000000 R_MIPS_NONE      
+00000800  00000080 R_MIPS_IRELATIVE 
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..034b158
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-o32.sym
@@ -0,0 +1,3 @@
+#...
+    2   0: 00000400    40 FUNC    GLOBAL DEFAULT   1 func1
+#pass
\ No newline at end of file
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.sym b/ld/testsuite/ld-mips-elf/ifunc-4-n32.sym
new file mode 100644
index 0000000..1a8f307
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-n32.sym
@@ -0,0 +1,4 @@
+#...
+    22: 00080000    16 FUNC    LOCAL  DEFAULT    1 .iplt.func1
+    23: 0008049c   156 IFUNC   GLOBAL DEFAULT    2 func1
+#pass
\ No newline at end of file
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.sym b/ld/testsuite/ld-mips-elf/ifunc-4-n64.sym
new file mode 100644
index 0000000..ed469d8
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-n64.sym
@@ -0,0 +1,4 @@
+#...
+    22: 0000000000080000    32 FUNC    LOCAL  DEFAULT    1 .iplt.func1
+    23: 000000000008049c   156 IFUNC   GLOBAL DEFAULT    2 func1
+#pass
\ No newline at end of file
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..bf5a2d2
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-n64.t
@@ -0,0 +1,11 @@
+tmpdir/ifunc-4-n64:     file format elf64-tradbigmips
+
+
+Disassembly of section .iplt:
+
+0000000000080000 <.iplt.func1>:
+   80000:	3c0f0008 	lui	t3,0x8
+   80004:	ddf90800 	ld	t9,2048\(t3\)
+   80008:	03200008 	jr	t9
+   8000c:	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.sym b/ld/testsuite/ld-mips-elf/ifunc-4-o32.sym
new file mode 100644
index 0000000..4e3ed8c
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-o32.sym
@@ -0,0 +1,4 @@
+#...
+    17: 00080000    20 FUNC    LOCAL  DEFAULT    1 .iplt.func1
+    18: 0008049c   156 IFUNC   GLOBAL DEFAULT    2 func1
+#pass
\ No newline at end of file
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.g b/ld/testsuite/ld-mips-elf/ifunc-5-n32.g
new file mode 100644
index 0000000..884ca55
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-n32.g
@@ -0,0 +1,11 @@
+
+tmpdir/ifunc-5-n32:     file format elf32-ntradbigmips
+
+
+Disassembly of section .got:
+
+00000400 <.got>:
+ 400:	00000000 	nop
+ 404:	80000000 	lb	zero,0\(zero\)
+ 408:	0000000c 	syscall
+	...
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..aba5cb2
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-n32.r
@@ -0,0 +1,4 @@
+Relocation section '.rel.dyn' at offset 0x11000 contains 2 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00000000  00000000 R_MIPS_NONE      
+00000408  00000203 R_MIPS_REL32      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..3dc0342
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-n32.sym
@@ -0,0 +1,4 @@
+#...
+    2   0: 0000000c    28 IFUNC   GLOBAL DEFAULT   1 func1
+    4   1: 0000000c    28 FUNC    GLOBAL DEFAULT   1 func1_ifunc
+#pass
\ No newline at end of file
diff --git a/ld/testsuite/ld-mips-elf/ifunc-5-n64.g b/ld/testsuite/ld-mips-elf/ifunc-5-n64.g
new file mode 100644
index 0000000..70911f2
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-n64.g
@@ -0,0 +1,12 @@
+
+tmpdir/ifunc-5-n64:     file format elf64-tradbigmips
+
+
+Disassembly of section .got:
+
+0000000000000400 <.got>:
+	...
+ 408:	80000000 	lb	zero,0\(zero\)
+	...
+ 414:	0000000c 	syscall
+	...
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..49db321
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-n64.r
@@ -0,0 +1,8 @@
+Relocation section '.rel.dyn' at offset 0x11000 contains 2 entries:
+  Offset          Info           Type           Sym. Value    Sym. Name
+000000000000  000000000000 R_MIPS_NONE      
+                    Type2: R_MIPS_NONE      
+                    Type3: R_MIPS_NONE      
+000000000410  000200001203 R_MIPS_REL32      func1\(\)          func1
+                    Type2: R_MIPS_64        
+                    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..483b13d
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-n64.sym
@@ -0,0 +1,4 @@
+#...
+    2   0: 000000000000000c    28 IFUNC   GLOBAL DEFAULT   1 func1
+    4   1: 000000000000000c    28 FUNC    GLOBAL DEFAULT   1 func1_ifunc
+#pass
\ No newline at end of file
diff --git a/ld/testsuite/ld-mips-elf/ifunc-5-o32.g b/ld/testsuite/ld-mips-elf/ifunc-5-o32.g
new file mode 100644
index 0000000..cf80f02
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-o32.g
@@ -0,0 +1,11 @@
+
+tmpdir/ifunc-5-o32:     file format elf32-tradbigmips
+
+
+Disassembly of section .got:
+
+00000400 <.got>:
+ 400:	00000000 	nop
+ 404:	80000000 	lb	zero,0\(zero\)
+ 408:	0000000c 	syscall
+	...
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..18bdec1
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-o32.r
@@ -0,0 +1,4 @@
+Relocation section '.rel.dyn' at offset 0x11000 contains 2 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00000000  00000000 R_MIPS_NONE      
+00000408  00000303 R_MIPS_REL32      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..dd5ada4
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-o32.sym
@@ -0,0 +1,3 @@
+#...
+    3   0: 0000000c    40 IFUNC   GLOBAL DEFAULT   1 func1
+#pass
\ No newline at end of file
diff --git a/ld/testsuite/ld-mips-elf/ifunc-5.dyn b/ld/testsuite/ld-mips-elf/ifunc-5.dyn
new file mode 100644
index 0000000..4730fb9
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5.dyn
@@ -0,0 +1,3 @@
+#...
+ 0x0*70000036 \(MIPS_GENERAL_GOTNO\).* 3
+#pass
\ No newline at end of file
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..ad53c7e
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-n32.r
@@ -0,0 +1,4 @@
+Relocation section '.rel.dyn' at offset 0x11000 contains 2 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00000000  00000000 R_MIPS_NONE      
+00000808  00000080 R_MIPS_IRELATIVE 
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..a0a1c12
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-n32.sym
@@ -0,0 +1,3 @@
+#...
+    2   0: 00000400    28 FUNC    GLOBAL DEFAULT   1 func1
+#pass
\ No newline at end of file
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..7321380
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-n64.r
@@ -0,0 +1,8 @@
+Relocation section '.rel.dyn' at offset 0x11000 contains 2 entries:
+  Offset          Info           Type           Sym. Value    Sym. Name
+000000000000  000000000000 R_MIPS_NONE      
+                    Type2: R_MIPS_NONE      
+                    Type3: R_MIPS_NONE      
+000000000810  000000000080 R_MIPS_IRELATIVE 
+                    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..ea4760a
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-n64.sym
@@ -0,0 +1,3 @@
+#...
+    2   0: 0000000000000400    28 FUNC    GLOBAL DEFAULT   1 func1
+#pass
\ No newline at end of file
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..ad53c7e
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-o32.r
@@ -0,0 +1,4 @@
+Relocation section '.rel.dyn' at offset 0x11000 contains 2 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00000000  00000000 R_MIPS_NONE      
+00000808  00000080 R_MIPS_IRELATIVE 
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..034b158
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-o32.sym
@@ -0,0 +1,3 @@
+#...
+    2   0: 00000400    40 FUNC    GLOBAL DEFAULT   1 func1
+#pass
\ No newline at end of file
diff --git a/ld/testsuite/ld-mips-elf/ifunc-7-o32.r b/ld/testsuite/ld-mips-elf/ifunc-7-o32.r
new file mode 100644
index 0000000..761c7ee
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-7-o32.r
@@ -0,0 +1,3 @@
+Relocation section '.rel.dyn' at offset 0xe8 contains 1 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00410250  00000080 R_MIPS_IRELATIVE 
diff --git a/ld/testsuite/ld-mips-elf/ifunc-7-o32.sec b/ld/testsuite/ld-mips-elf/ifunc-7-o32.sec
new file mode 100644
index 0000000..c904010
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-7-o32.sec
@@ -0,0 +1,4 @@
+#...
+.* \.iplt .*
+.* \.igot .*
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-8-o32.g b/ld/testsuite/ld-mips-elf/ifunc-8-o32.g
new file mode 100644
index 0000000..6961a7f
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-8-o32.g
@@ -0,0 +1,6 @@
+#...
+00410170 <_GLOBAL_OFFSET_TABLE_>:
+  410170:	00000000 	nop
+  410174:	80000000 	lb	zero,0\(zero\)
+  410178:	004000fc 	0x4000fc
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-8-o32.r b/ld/testsuite/ld-mips-elf/ifunc-8-o32.r
new file mode 100644
index 0000000..ba4c7d6
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-8-o32.r
@@ -0,0 +1,3 @@
+Relocation section '.rel.dyn' at offset 0xe8 contains 1 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+00410178  00000080 R_MIPS_IRELATIVE 
diff --git a/ld/testsuite/ld-mips-elf/ifunc-8-o32.sec b/ld/testsuite/ld-mips-elf/ifunc-8-o32.sec
new file mode 100644
index 0000000..a0dfae2
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-8-o32.sec
@@ -0,0 +1,17 @@
+#...
+Section Headers:
+!.* \.iplt .*
+!.* \.iplt .*
+!.* \.iplt .*
+!.* \.iplt .*
+!.* \.iplt .*
+!.* \.iplt .*
+.* \.got .*
+!.* \.iplt .*
+!.* \.iplt .*
+!.* \.iplt .*
+!.* \.iplt .*
+!.* \.iplt .*
+!.* \.iplt .*
+!.* \.iplt .*
+#pass
\ No newline at end of file
diff --git a/ld/testsuite/ld-mips-elf/ifunc-9-o32.g b/ld/testsuite/ld-mips-elf/ifunc-9-o32.g
new file mode 100644
index 0000000..c470055
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-9-o32.g
@@ -0,0 +1,6 @@
+#...
+004101b0 <_GLOBAL_OFFSET_TABLE_>:
+  4101b0:	00000000 	nop
+  4101b4:	80000000 	lb	zero,0\(zero\)
+  4101b8:	0040010c 	syscall	0x10004
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-9-o32.r b/ld/testsuite/ld-mips-elf/ifunc-9-o32.r
new file mode 100644
index 0000000..a2cedc8
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-9-o32.r
@@ -0,0 +1,4 @@
+Relocation section '.rel.dyn' at offset 0xe8 contains 2 entries:
+ Offset     Info    Type            Sym.Value  Sym. Name
+004101d8  00000080 R_MIPS_IRELATIVE 
+00000000  00000000 R_MIPS_NONE      
diff --git a/ld/testsuite/ld-mips-elf/ifunc-9-o32.sec b/ld/testsuite/ld-mips-elf/ifunc-9-o32.sec
new file mode 100644
index 0000000..39c7712
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-9-o32.sec
@@ -0,0 +1,17 @@
+#...
+Section Headers:
+!.* \.igot .*
+!.* \.igot .*
+!.* \.igot .*
+!.* \.igot .*
+!.* \.igot .*
+!.* \.igot .*
+.* \.iplt .*
+.* \.got .*
+!.* \.igot .*
+!.* \.igot .*
+!.* \.igot .*
+!.* \.igot .*
+!.* \.igot .*
+!.* \.igot .*
+#pass
\ No newline at end of file
diff --git a/ld/testsuite/ld-mips-elf/ifunc-9-o32.t b/ld/testsuite/ld-mips-elf/ifunc-9-o32.t
new file mode 100644
index 0000000..ca3f48b
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-9-o32.t
@@ -0,0 +1,8 @@
+#...
+00400190 <.iplt.func1>:
+  400190:	3c0f0041 	lui	t7,0x41
+  400194:	8df901b8 	lw	t9,440\(t7\)
+  400198:	00000000 	nop
+  40019c:	03200008 	jr	t9
+  4001a0:	00000000 	nop
+#pass
\ No newline at end of file
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-0x400000.t b/ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000.t
new file mode 100644
index 0000000..c46a85d
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000.t
@@ -0,0 +1,11 @@
+tmpdir/ifunc-iplt-0x400000:     file format elf64-tradbigmips
+
+
+Disassembly of section .iplt:
+
+0000000000400188 <.iplt.func1>:
+  400188:	3c0f0041 	lui	t3,0x41
+  40018c:	ddf90320 	ld	t9,800\(t3\)
+  400190:	03200008 	jr	t9
+  400194:	00000000 	nop
+	...
diff --git a/ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000000.t b/ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000000.t
new file mode 100644
index 0000000..3562b23
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000000.t
@@ -0,0 +1,13 @@
+tmpdir/ifunc-iplt-0x400000000:     file format elf64-tradbigmips
+
+
+Disassembly of section .iplt:
+
+0000000400000188 <.iplt.func1>:
+   400000188:	3c0f0004 	lui	t3,0x4
+   40000018c:	25ef0001 	addiu	t3,t3,1
+   400000190:	000f7c38 	dsll	t3,t3,0x10
+   400000194:	ddf90320 	ld	t9,800\(t3\)
+   400000198:	03200008 	jr	t9
+   40000019c:	00000000 	nop
+	...
diff --git a/ld/testsuite/ld-mips-elf/ifunc-iplt-0x4000000000000.t b/ld/testsuite/ld-mips-elf/ifunc-iplt-0x4000000000000.t
new file mode 100644
index 0000000..90e8306
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-0x4000000000000.t
@@ -0,0 +1,14 @@
+tmpdir/ifunc-iplt-0x4000000000000:     file format elf64-tradbigmips
+
+
+Disassembly of section .iplt:
+
+0004000000000188 <.iplt.func1>:
+   4000000000188:	3c0f0004 	lui	t3,0x4
+   400000000018c:	3c0e0001 	lui	t2,0x1
+   4000000000190:	25ef0000 	addiu	t3,t3,0
+   4000000000194:	000f783c 	dsll32	t3,t3,0x0
+   4000000000198:	01ee782d 	daddu	t3,t3,t2
+   400000000019c:	ddf90320 	ld	t9,800\(t3\)
+   40000000001a0:	03200008 	jr	t9
+   40000000001a4:	00000000 	nop
diff --git a/ld/testsuite/ld-mips-elf/ifunc-iplt-micromips.igot b/ld/testsuite/ld-mips-elf/ifunc-iplt-micromips.igot
new file mode 100644
index 0000000..b9844dc
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-micromips.igot
@@ -0,0 +1,7 @@
+tmpdir/ifunc-iplt-micromips:     file format elf32-tradbigmips
+
+
+Disassembly of section .igot:
+
+004101d0 <_fdata>:
+  4101d0:	0040014d 	break	0x40,0x5
diff --git a/ld/testsuite/ld-mips-elf/ifunc-iplt-micromips.t b/ld/testsuite/ld-mips-elf/ifunc-iplt-micromips.t
new file mode 100644
index 0000000..bc2f2a9
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-micromips.t
@@ -0,0 +1,9 @@
+tmpdir/ifunc-iplt-micromips:     file format elf32-tradbigmips
+
+
+Disassembly of section .iplt:
+
+004001c0 <.iplt.func1>:
+  4001c0:	41a3 0041 	lui	v1,0x41
+  4001c4:	ff23 01d0 	lw	t9,464\(v1\)
+  4001c8:	45b9      	jrc	t9
diff --git a/ld/testsuite/ld-mips-elf/ifunc-iplt-mips16.igot b/ld/testsuite/ld-mips-elf/ifunc-iplt-mips16.igot
new file mode 100644
index 0000000..78f2988
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-mips16.igot
@@ -0,0 +1,8 @@
+
+tmpdir/ifunc-iplt-mips16:     file format elf32-tradbigmips
+
+
+Disassembly of section .igot:
+
+00410190 <.igot>:
+  410190:	00400125 	0x400125
diff --git a/ld/testsuite/ld-mips-elf/ifunc-iplt-mips16.t b/ld/testsuite/ld-mips-elf/ifunc-iplt-mips16.t
new file mode 100644
index 0000000..564083d
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-mips16.t
@@ -0,0 +1,12 @@
+tmpdir/ifunc-iplt-mips16:     file format elf32-tradbigmips
+
+
+Disassembly of section .iplt:
+
+00400170 <.iplt.func1>:
+  400170:	b202      	lw	v0,400178 <.iplt.func1\+0x8>
+  400172:	9a60      	lw	v1,0\(v0\)
+  400174:	eb00      	jr	v1
+  400176:	653b      	move	t9,v1
+  400178:	0041      	addiu	s0,sp,260
+  40017a:	0190      	addiu	s1,sp,576
diff --git a/ld/testsuite/ld-mips-elf/ifunc-iplt-mips32r6.t b/ld/testsuite/ld-mips-elf/ifunc-iplt-mips32r6.t
new file mode 100644
index 0000000..5250ffe
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-mips32r6.t
@@ -0,0 +1,11 @@
+
+tmpdir/ifunc-iplt-mips32r6:     file format elf32-tradbigmips
+
+
+Disassembly of section .iplt:
+
+00400230 <.iplt.func1>:
+  400230:	3c0f0041 	lui	t7,0x41
+  400234:	8df90240 	lw	t9,576\(t7\)
+  400238:	03200009 	jr	t9
+  40023c:	00000000 	nop
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-local-1.s b/ld/testsuite/ld-mips-elf/ifunc-local-1.s
new file mode 100644
index 0000000..3b839d1
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-local-1.s
@@ -0,0 +1,176 @@
+	.section .mdebug.abi32
+	.previous
+	.abicalls
+	.option	pic0
+	.text
+	.align	2
+	.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
+	.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
+	.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
+	.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
+	.type	func1, @gnu_indirect_function
+	func1 = func1_ifunc
+
+	
+	.align	2
+	.globl	main
+	.set	nomips16
+	.set	nomicromips
+	.ent	main
+	.option	pic0
+	.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-local-2.s b/ld/testsuite/ld-mips-elf/ifunc-local-2.s
new file mode 100644
index 0000000..5872e3b
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-local-2.s
@@ -0,0 +1,99 @@
+	.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
+	.type	func1, @gnu_indirect_function
+	func1 = func1_ifunc
+
+	.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
+	.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
+1:	jalr	$25
+	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-local-3.s b/ld/testsuite/ld-mips-elf/ifunc-local-3.s
new file mode 100644
index 0000000..779c3ce
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-local-3.s
@@ -0,0 +1,135 @@
+	.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
+	.type	func1, @gnu_indirect_function
+	func1 = func1_ifunc
+
+	.option	pic0
+	.text
+	.align	2
+	.globl	ref1
+	.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
+	
+	.align	2
+	.globl	main
+	.set	nomips16
+	.set	nomicromips
+	.globl	main
+	.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
+	.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
+
+	jal	ref1
+	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	main
+	.size	main, .-main
diff --git a/ld/testsuite/ld-mips-elf/ifunc-local-4.s b/ld/testsuite/ld-mips-elf/ifunc-local-4.s
new file mode 100644
index 0000000..c57a32f
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-local-4.s
@@ -0,0 +1,183 @@
+	.section .mdebug.abi32
+	.previous
+	.abicalls
+	.option	pic0
+	.text
+	.align	2
+	.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
+	.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
+	.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
+	.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
+	.type	func1, @gnu_indirect_function
+	func1 = func1_ifunc
+
+	
+	.align	2
+	.globl	main
+	.set	nomips16
+	.set	nomicromips
+	.ent	main
+	.option	pic0
+	.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
+
+        .section        .data.rel,"aw",@progbits
+        .align  2
+        .type   fptr, @object
+        .size   fptr, 4
+fptr:
+        .word   func1
+	.ident	"GCC: (GNU) 4.9.0 20130930 (experimental)"
diff --git a/ld/testsuite/ld-mips-elf/ifunc-local-5.s b/ld/testsuite/ld-mips-elf/ifunc-local-5.s
new file mode 100644
index 0000000..684303f
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-local-5.s
@@ -0,0 +1,106 @@
+	.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
+	.type	func1, @gnu_indirect_function
+	func1 = func1_ifunc
+
+	.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
+	.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
+	.reloc	1f,R_MIPS_JALR,func1
+1:	jalr	$25
+	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
+	.section	.data.rel,"aw",@progbits
+	.align	2
+	.type	fptr, @object
+	.size	fptr, 4
+fptr:
+	.word	func1
+	.ident	"GCC: (GNU) 4.9.0 20130930 (experimental)"
diff --git a/ld/testsuite/ld-mips-elf/ifunc-local-6.s b/ld/testsuite/ld-mips-elf/ifunc-local-6.s
new file mode 100644
index 0000000..6cf12c3
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-local-6.s
@@ -0,0 +1,112 @@
+	.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
+	.type	func1, @gnu_indirect_function
+	func1 = func1_ifunc
+
+	.align	2
+	.globl	main
+	.set	nomips16
+	.set	nomicromips
+	.globl	main
+	.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
+	.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
+
+	lui	$2,%hi(func1)
+	addiu	$2, $2, %lo(func1)
+	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	main
+	.size	main, .-main
+	.section	.data.rel,"aw",@progbits
+	.align	2
+	.type	fptr, @object
+	.size	fptr, 4
+fptr:
+	.word	func1
+	.ident	"GCC: (GNU) 4.9.0 20130930 (experimental)"
diff --git a/ld/testsuite/ld-mips-elf/ifunc-static-def-mips16.s b/ld/testsuite/ld-mips-elf/ifunc-static-def-mips16.s
new file mode 100644
index 0000000..31c8c7c
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-static-def-mips16.s
@@ -0,0 +1,434 @@
+	.section .mdebug.abi32
+	.previous
+	.nan	legacy
+	.module	fp=xx
+	.module	nooddspreg
+	.abicalls
+	.option	pic0
+	.text
+$Ltext0:
+	.cfi_sections	.debug_frame
+	.align	2
+$LFB0 = .
+	.file 1 "ifunc-static-def.c"
+	.loc 1 3 0
+	.cfi_startproc
+	.set	mips16
+	.set	nomicromips
+	.ent	f1_a
+	.type	f1_a, @function
+f1_a:
+	.frame	$17,8,$31		# vars= 0, regs= 1/0, args= 0, gp= 0
+	.mask	0x00020000,-4
+	.fmask	0x00000000,0
+	save	8,$17
+	.cfi_def_cfa_offset 8
+	.cfi_offset 17, -4
+	move	$17,$sp
+	.cfi_def_cfa_register 17
+	.loc 1 3 0
+	li	$2,1
+	move	$sp,$17
+	.cfi_def_cfa_register 29
+	restore	8,$17
+	.cfi_restore 17
+	.cfi_def_cfa_offset 0
+	j	$31
+	.end	f1_a
+	.cfi_endproc
+$LFE0:
+	.size	f1_a, .-f1_a
+	.align	2
+$LFB1 = .
+	.loc 1 4 0
+	.cfi_startproc
+	.set	mips16
+	.set	nomicromips
+	.ent	f1_b
+	.type	f1_b, @function
+f1_b:
+	.frame	$17,8,$31		# vars= 0, regs= 1/0, args= 0, gp= 0
+	.mask	0x00020000,-4
+	.fmask	0x00000000,0
+	save	8,$17
+	.cfi_def_cfa_offset 8
+	.cfi_offset 17, -4
+	move	$17,$sp
+	.cfi_def_cfa_register 17
+	.loc 1 4 0
+	li	$2,2
+	move	$sp,$17
+	.cfi_def_cfa_register 29
+	restore	8,$17
+	.cfi_restore 17
+	.cfi_def_cfa_offset 0
+	j	$31
+	.end	f1_b
+	.cfi_endproc
+$LFE1:
+	.size	f1_b, .-f1_b
+	.align	2
+$LFB2 = .
+	.loc 1 5 0
+	.cfi_startproc
+	.set	mips16
+	.set	nomicromips
+	.ent	f1_c
+	.type	f1_c, @function
+f1_c:
+	.frame	$17,8,$31		# vars= 0, regs= 1/0, args= 0, gp= 0
+	.mask	0x00020000,-4
+	.fmask	0x00000000,0
+	save	8,$17
+	.cfi_def_cfa_offset 8
+	.cfi_offset 17, -4
+	move	$17,$sp
+	.cfi_def_cfa_register 17
+	.loc 1 5 0
+	li	$2,3
+	move	$sp,$17
+	.cfi_def_cfa_register 29
+	restore	8,$17
+	.cfi_restore 17
+	.cfi_def_cfa_offset 0
+	j	$31
+	.end	f1_c
+	.cfi_endproc
+$LFE2:
+	.size	f1_c, .-f1_c
+
+	.comm	global,4,4
+	.globl	result
+	.data
+	.align	2
+	.type	result, @object
+	.size	result, 4
+result:
+	.word	6
+	.text
+	.align	2
+	.globl	func1_ifunc
+$LFB3 = .
+	.loc 1 12 0
+	.cfi_startproc
+	.set	mips16
+	.set	nomicromips
+	.ent	func1_ifunc
+	.type	func1_ifunc, @function
+func1_ifunc:
+	.frame	$17,24,$31		# vars= 8, regs= 1/0, args= 0, gp= 8
+	.mask	0x00020000,-4
+	.fmask	0x00000000,0
+	save	24,$17
+	.cfi_def_cfa_offset 24
+	.cfi_offset 17, -4
+	move	$17,$sp
+	.cfi_def_cfa_register 17
+	.loc 1 15 0
+	lw	$2,$L13
+	lw	$2,0($2)
+	beqz	$2,$L8
+	.loc 1 16 0
+	li	$2,48
+	sw	$2,8($17)
+	b	$L9
+$L8:
+	.loc 1 18 0
+	li	$2,3
+	sw	$2,8($17)
+$L9:
+	.loc 1 20 0
+	lw	$3,8($17)
+	li	$2,240
+	and	$2,$3
+	beqz	$2,$L10
+	.loc 1 21 0
+	lw	$2,$L14
+	b	$L11
+$L10:
+	.loc 1 22 0
+	lw	$3,8($17)
+	li	$2,15
+	and	$2,$3
+	beqz	$2,$L12
+	.loc 1 23 0
+	lw	$2,$L15
+	b	$L11
+$L12:
+	.loc 1 25 0
+	lw	$2,$L16
+$L11:
+	.loc 1 26 0
+	move	$sp,$17
+	.cfi_def_cfa_register 29
+	restore	24,$17
+	.cfi_restore 17
+	.cfi_def_cfa_offset 0
+	j	$31
+	.align	2
+$L13:
+	.word	global
+$L14:
+	.word	f1_a
+$L15:
+	.word	f1_b
+$L16:
+	.word	f1_c
+	.end	func1_ifunc
+	.cfi_endproc
+$LFE3:
+	.size	func1_ifunc, .-func1_ifunc
+	.globl	func1
+	.type	func1, @gnu_indirect_function
+	func1 = func1_ifunc
+$Letext0:
+	.section	.debug_info,"",@progbits
+$Ldebug_info0:
+	.4byte	0xb9
+	.2byte	0x4
+	.4byte	$Ldebug_abbrev0
+	.byte	0x4
+	.uleb128 0x1
+	.4byte	$LASF5
+	.byte	0xc
+	.4byte	$LASF6
+	.4byte	$LASF7
+	.4byte	$Ltext0
+	.4byte	$Letext0-$Ltext0
+	.4byte	$Ldebug_line0
+	.uleb128 0x2
+	.4byte	$LASF0
+	.byte	0x1
+	.byte	0x3
+	.4byte	0x3a
+	.4byte	$LFB0
+	.4byte	$LFE0-$LFB0
+	.uleb128 0x1
+	.byte	0x9c
+	.uleb128 0x3
+	.byte	0x4
+	.byte	0x5
+	.ascii	"int\000"
+	.uleb128 0x2
+	.4byte	$LASF1
+	.byte	0x1
+	.byte	0x4
+	.4byte	0x3a
+	.4byte	$LFB1
+	.4byte	$LFE1-$LFB1
+	.uleb128 0x1
+	.byte	0x9c
+	.uleb128 0x2
+	.4byte	$LASF2
+	.byte	0x1
+	.byte	0x5
+	.4byte	0x3a
+	.4byte	$LFB2
+	.4byte	$LFE2-$LFB2
+	.uleb128 0x1
+	.byte	0x9c
+	.uleb128 0x4
+	.4byte	$LASF8
+	.byte	0x1
+	.byte	0xb
+	.4byte	0x93
+	.4byte	$LFB3
+	.4byte	$LFE3-$LFB3
+	.uleb128 0x1
+	.byte	0x9c
+	.4byte	0x93
+	.uleb128 0x5
+	.4byte	$LASF4
+	.byte	0x1
+	.byte	0xd
+	.4byte	0x95
+	.uleb128 0x2
+	.byte	0x91
+	.sleb128 -16
+	.byte	0
+	.uleb128 0x6
+	.byte	0x4
+	.uleb128 0x7
+	.4byte	0x3a
+	.uleb128 0x8
+	.4byte	$LASF3
+	.byte	0x1
+	.byte	0x7
+	.4byte	0x3a
+	.uleb128 0x5
+	.byte	0x3
+	.4byte	global
+	.uleb128 0x8
+	.4byte	$LASF4
+	.byte	0x1
+	.byte	0x8
+	.4byte	0x95
+	.uleb128 0x5
+	.byte	0x3
+	.4byte	result
+	.byte	0
+	.section	.debug_abbrev,"",@progbits
+$Ldebug_abbrev0:
+	.uleb128 0x1
+	.uleb128 0x11
+	.byte	0x1
+	.uleb128 0x25
+	.uleb128 0xe
+	.uleb128 0x13
+	.uleb128 0xb
+	.uleb128 0x3
+	.uleb128 0xe
+	.uleb128 0x1b
+	.uleb128 0xe
+	.uleb128 0x11
+	.uleb128 0x1
+	.uleb128 0x12
+	.uleb128 0x6
+	.uleb128 0x10
+	.uleb128 0x17
+	.byte	0
+	.byte	0
+	.uleb128 0x2
+	.uleb128 0x2e
+	.byte	0
+	.uleb128 0x3
+	.uleb128 0xe
+	.uleb128 0x3a
+	.uleb128 0xb
+	.uleb128 0x3b
+	.uleb128 0xb
+	.uleb128 0x27
+	.uleb128 0x19
+	.uleb128 0x49
+	.uleb128 0x13
+	.uleb128 0x11
+	.uleb128 0x1
+	.uleb128 0x12
+	.uleb128 0x6
+	.uleb128 0x40
+	.uleb128 0x18
+	.uleb128 0x2117
+	.uleb128 0x19
+	.byte	0
+	.byte	0
+	.uleb128 0x3
+	.uleb128 0x24
+	.byte	0
+	.uleb128 0xb
+	.uleb128 0xb
+	.uleb128 0x3e
+	.uleb128 0xb
+	.uleb128 0x3
+	.uleb128 0x8
+	.byte	0
+	.byte	0
+	.uleb128 0x4
+	.uleb128 0x2e
+	.byte	0x1
+	.uleb128 0x3f
+	.uleb128 0x19
+	.uleb128 0x3
+	.uleb128 0xe
+	.uleb128 0x3a
+	.uleb128 0xb
+	.uleb128 0x3b
+	.uleb128 0xb
+	.uleb128 0x27
+	.uleb128 0x19
+	.uleb128 0x49
+	.uleb128 0x13
+	.uleb128 0x11
+	.uleb128 0x1
+	.uleb128 0x12
+	.uleb128 0x6
+	.uleb128 0x40
+	.uleb128 0x18
+	.uleb128 0x2117
+	.uleb128 0x19
+	.uleb128 0x1
+	.uleb128 0x13
+	.byte	0
+	.byte	0
+	.uleb128 0x5
+	.uleb128 0x34
+	.byte	0
+	.uleb128 0x3
+	.uleb128 0xe
+	.uleb128 0x3a
+	.uleb128 0xb
+	.uleb128 0x3b
+	.uleb128 0xb
+	.uleb128 0x49
+	.uleb128 0x13
+	.uleb128 0x2
+	.uleb128 0x18
+	.byte	0
+	.byte	0
+	.uleb128 0x6
+	.uleb128 0xf
+	.byte	0
+	.uleb128 0xb
+	.uleb128 0xb
+	.byte	0
+	.byte	0
+	.uleb128 0x7
+	.uleb128 0x35
+	.byte	0
+	.uleb128 0x49
+	.uleb128 0x13
+	.byte	0
+	.byte	0
+	.uleb128 0x8
+	.uleb128 0x34
+	.byte	0
+	.uleb128 0x3
+	.uleb128 0xe
+	.uleb128 0x3a
+	.uleb128 0xb
+	.uleb128 0x3b
+	.uleb128 0xb
+	.uleb128 0x49
+	.uleb128 0x13
+	.uleb128 0x3f
+	.uleb128 0x19
+	.uleb128 0x2
+	.uleb128 0x18
+	.byte	0
+	.byte	0
+	.byte	0
+	.section	.debug_aranges,"",@progbits
+	.4byte	0x1c
+	.2byte	0x2
+	.4byte	$Ldebug_info0
+	.byte	0x4
+	.byte	0
+	.2byte	0
+	.2byte	0
+	.4byte	$Ltext0
+	.4byte	$Letext0-$Ltext0
+	.4byte	0
+	.4byte	0
+	.section	.debug_line,"",@progbits
+$Ldebug_line0:
+	.section	.debug_str,"MS",@progbits,1
+$LASF8:
+	.ascii	"func1_ifunc\000"
+$LASF5:
+	.ascii	"GNU C11 5.0.0 20150302 (experimental) -mel -mno-shared -"
+	.ascii	"mips16 -mel -mno-relax-pic-calls -mllsc -mplt -mips32r2 "
+	.ascii	"-msynci -mabi=32 -mfpxx -g\000"
+$LASF4:
+	.ascii	"result\000"
+$LASF6:
+	.ascii	"ifunc-static-def.c\000"
+$LASF7:
+	.ascii	"/home/frs/tmp/ifunc/test\000"
+$LASF3:
+	.ascii	"global\000"
+$LASF0:
+	.ascii	"f1_a\000"
+$LASF1:
+	.ascii	"f1_b\000"
+$LASF2:
+	.ascii	"f1_c\000"
+	.ident	"GCC: (GNU) 5.0.0 20150302 (experimental)"
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..55f36b7
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-static-def.s
@@ -0,0 +1,145 @@
+	.file	1 "ifunc.c"
+	.section .mdebug.abi32
+	.previous
+	.abicalls
+	.option	pic0
+	.text
+	.align	2
+	.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
+	.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
+	.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
+	.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-mips16.s b/ld/testsuite/ld-mips-elf/ifunc-static-main-mips16.s
new file mode 100644
index 0000000..43f4abe
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-static-main-mips16.s
@@ -0,0 +1,157 @@
+	.section .mdebug.abi32
+	.previous
+	.nan	legacy
+	.module	fp=xx
+	.module	nooddspreg
+	.abicalls
+	.option	pic0
+	.text
+$Ltext0:
+	.cfi_sections	.debug_frame
+	.align	2
+	.globl	main
+$LFB0 = .
+	.file 1 "ifunc-static-main.c"
+	.loc 1 4 0
+	.cfi_startproc
+	.set	mips16
+	.set	nomicromips
+	.ent	main
+	.type	main, @function
+main:
+	.frame	$17,16,$31		# vars= 0, regs= 2/0, args= 16, gp= 8
+	.mask	0x80020000,-4
+	.fmask	0x00000000,0
+	save	32,$17,$31
+	.cfi_def_cfa_offset 32
+	.cfi_offset 31, -4
+	.cfi_offset 17, -8
+	addiu	$17,$sp,16
+	.cfi_def_cfa 17, 16
+	.loc 1 5 0
+	jal	func1
+	.loc 1 6 0
+	move	$sp,$17
+	.cfi_def_cfa_register 29
+	restore	16,$17,$31
+	.cfi_restore 17
+	.cfi_restore 31
+	.cfi_def_cfa_offset 0
+	j	$31
+	.end	main
+	.cfi_endproc
+$LFE0:
+	.size	main, .-main
+$Letext0:
+	.section	.debug_info,"",@progbits
+$Ldebug_info0:
+	.4byte	0x3e
+	.2byte	0x4
+	.4byte	$Ldebug_abbrev0
+	.byte	0x4
+	.uleb128 0x1
+	.4byte	$LASF0
+	.byte	0xc
+	.4byte	$LASF1
+	.4byte	$LASF2
+	.4byte	$Ltext0
+	.4byte	$Letext0-$Ltext0
+	.4byte	$Ldebug_line0
+	.uleb128 0x2
+	.4byte	$LASF3
+	.byte	0x1
+	.byte	0x3
+	.4byte	0x3a
+	.4byte	$LFB0
+	.4byte	$LFE0-$LFB0
+	.uleb128 0x1
+	.byte	0x9c
+	.uleb128 0x3
+	.byte	0x4
+	.byte	0x5
+	.ascii	"int\000"
+	.byte	0
+	.section	.debug_abbrev,"",@progbits
+$Ldebug_abbrev0:
+	.uleb128 0x1
+	.uleb128 0x11
+	.byte	0x1
+	.uleb128 0x25
+	.uleb128 0xe
+	.uleb128 0x13
+	.uleb128 0xb
+	.uleb128 0x3
+	.uleb128 0xe
+	.uleb128 0x1b
+	.uleb128 0xe
+	.uleb128 0x11
+	.uleb128 0x1
+	.uleb128 0x12
+	.uleb128 0x6
+	.uleb128 0x10
+	.uleb128 0x17
+	.byte	0
+	.byte	0
+	.uleb128 0x2
+	.uleb128 0x2e
+	.byte	0
+	.uleb128 0x3f
+	.uleb128 0x19
+	.uleb128 0x3
+	.uleb128 0xe
+	.uleb128 0x3a
+	.uleb128 0xb
+	.uleb128 0x3b
+	.uleb128 0xb
+	.uleb128 0x27
+	.uleb128 0x19
+	.uleb128 0x49
+	.uleb128 0x13
+	.uleb128 0x11
+	.uleb128 0x1
+	.uleb128 0x12
+	.uleb128 0x6
+	.uleb128 0x40
+	.uleb128 0x18
+	.uleb128 0x2116
+	.uleb128 0x19
+	.byte	0
+	.byte	0
+	.uleb128 0x3
+	.uleb128 0x24
+	.byte	0
+	.uleb128 0xb
+	.uleb128 0xb
+	.uleb128 0x3e
+	.uleb128 0xb
+	.uleb128 0x3
+	.uleb128 0x8
+	.byte	0
+	.byte	0
+	.byte	0
+	.section	.debug_aranges,"",@progbits
+	.4byte	0x1c
+	.2byte	0x2
+	.4byte	$Ldebug_info0
+	.byte	0x4
+	.byte	0
+	.2byte	0
+	.2byte	0
+	.4byte	$Ltext0
+	.4byte	$Letext0-$Ltext0
+	.4byte	0
+	.4byte	0
+	.section	.debug_line,"",@progbits
+$Ldebug_line0:
+	.section	.debug_str,"MS",@progbits,1
+$LASF1:
+	.ascii	"ifunc-static-main.c\000"
+$LASF0:
+	.ascii	"GNU C11 5.0.0 20150302 (experimental) -mel -mno-shared -"
+	.ascii	"mips16 -mel -mllsc -mplt -mips32r2 "
+	.ascii	"-msynci -mabi=32 -mfpxx -g\000"
+$LASF2:
+	.ascii	"/home/frs/tmp/ifunc/test\000"
+$LASF3:
+	.ascii	"main\000"
+	.ident	"GCC: (GNU) 5.0.0 20150302 (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..4a6349e
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-static-main.s
@@ -0,0 +1,35 @@
+	.file	1 "ifunc_ref_main_1.c"
+	.section .mdebug.abi32
+	.previous
+	.abicalls
+	.option	pic0
+	.text
+	.align	2
+	.globl	main
+	.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..76de6cd
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-static-ref.s
@@ -0,0 +1,36 @@
+	.file	1 "ifunc_ref.c"
+	.section .mdebug.abi32
+	.previous
+	.abicalls
+	.option	pic0
+	.text
+	.align	2
+	.globl	ref1
+	.set 	mips32r2
+	.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..88d7e5d
--- /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
+    3   0: 0000000c    28 FUNC    GLOBAL DEFAULT   1 func1_ifunc
+    2   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..7236950
--- /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
+    3   0: 000000000000000c    28 FUNC    GLOBAL DEFAULT   1 func1_ifunc
+    2   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..b5fff26
--- /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
+    3   0: 0000000c    40 FUNC    GLOBAL DEFAULT   1 func1_ifunc
+    2   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..320fbc9
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/mips-ifunc.exp
@@ -0,0 +1,282 @@
+# 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 -s ifunc-4-${abi}.sym" \
+		  "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" \
+		  "readelf -d ifunc-5.dyn" \
+                  "objdump -dj.got ifunc-5-${abi}.g"] \
+	    "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 -d ifunc-6.dyn" \
+                  "readelf -r ifunc-6-${abi}.r"] \
+	    "ifunc-6-${abi}" \
+        ] \
+    ]
+}
+
+# IPLT sequences change based on how big the address of the
+# .igot.plt section is based on Mips loading immediate values.
+#
+set addrs { "0x400000" "0x400000000" "0x4000000000000" }
+foreach { addr } $addrs {
+    run_ld_link_tests [list \
+	[list \
+	    "IFUNC IPLT (Simple static executable with def and ref) ${addr}" \
+	    "$abi_ldflags(n64) -Bstatic -Ttext-segment ${addr}" "" \
+	    "$abi_asflags(n64) -non_shared" \
+	    [list ifunc-static-main.s ifunc-static-def.s ifunc-static-ref.s] \
+	    [list "objdump -dj.iplt ifunc-iplt-${addr}.t"] \
+	    "ifunc-iplt-${addr}" \
+        ] \
+    ]
+}
+
+# Check creation of IPLT/IGOT entries for locally bound IFUNC symbols.
+# When an IFUNC symbol binds locally there are 6 cases:
+#   PIC relocs?  non-PIC relocs?  all call-only?   variant	test
+#   n            y                y                (1)		IFUNC 7
+#   y            n                y                (2)		IFUNC 8
+#   y            y                y                (3)		IFUNC 9
+#   n            y                n                (4)		IFUNC 10
+#   y            n                n                (5)		IFUNC 11
+#   y            y                n                (5)		IFUNC 12
+# (1) Need .iplt and .igot.
+# (2) No .iplt; .got entry with an IRELATIVE relocation.
+# (3) Need .iplt but no .igot; IRELATIVE entry should in .got
+# (4) Same as (1)
+# (5) Need .iplt and .igot; Separate regular .got entry to satisfy the PIC 
+# references
+set abi o32
+run_ld_link_tests [list \
+	[list \
+	    "IFUNC 7 (Simple static executable with def) ${abi}" \
+	    "$abi_ldflags($abi) -static -Bstatic" "" \
+	    "$abi_asflags($abi)" \
+	     [list ifunc-local-1.s] \
+	     [list "readelf -r ifunc-7-${abi}.r" \
+		  "readelf -S ifunc-7-${abi}.sec"] \
+	    "ifunc-7-${abi}" \
+	] \
+	[list \
+	    "IFUNC 8 (Simple dynamic executable with def) ${abi}" \
+	    "$abi_ldflags($abi) -static" "" \
+	    "$abi_asflags($abi)" \
+	     [list ifunc-local-2.s] \
+ 	     [list "readelf -r ifunc-8-${abi}.r" \
+ 		  "readelf -S ifunc-8-${abi}.sec" \
+ 		  "objdump -Dj.got ifunc-8-${abi}.g"] \
+	    "ifunc-8-${abi}" \
+	] \
+	[list \
+	    "IFUNC 9 (Simple dynamic executable with def & PIC/non-PIC refs) ${abi}" \
+	    "$abi_ldflags($abi) -static -Bstatic" "" \
+	    "$abi_asflags($abi) -non_shared" \
+	     [list ifunc-local-3.s] \
+	     [list "readelf -r ifunc-9-${abi}.r" \
+		  "readelf -S ifunc-9-${abi}.sec" \
+		  "readelf -S ifunc-9-${abi}-x.sec" \
+		  "objdump -Dj.got ifunc-9-${abi}.g" \
+		  "objdump -dj.iplt ifunc-9-${abi}.t"] \
+	    "ifunc-9-${abi}" \
+	] \
+	[list \
+	    "IFUNC 10 (Simple static executable with def & non-call ref) ${abi}" \
+	    "$abi_ldflags($abi) -static -Bstatic" "" \
+	    "$abi_asflags($abi) -non_shared" \
+	     [list ifunc-local-4.s] \
+	     [list "readelf -r ifunc-10-${abi}.r" \
+		  "readelf -S ifunc-10-${abi}.sec"] \
+	    "ifunc-10-${abi}" \
+	] \
+	[list \
+	    "IFUNC 11 (Simple dynamic executable with def & non-call ref) ${abi}" \
+	    "$abi_ldflags($abi) -static" "" \
+	    "$abi_asflags($abi)" \
+	     [list ifunc-local-5.s] \
+ 	     [list "readelf -r ifunc-11-${abi}.r" \
+ 		  "readelf -S ifunc-11-${abi}.sec" \
+ 		  "objdump -Dj.got ifunc-11-${abi}.g"] \
+	    "ifunc-11-${abi}" \
+	] \
+	[list \
+	    "IFUNC 12 (Simple static executable with def, PIC/non-PIC, call/non-call refs) ${abi}" \
+	    "$abi_ldflags($abi) -static -Bstatic" "" \
+	    "$abi_asflags($abi) -non_shared" \
+	     [list ifunc-local-6.s] \
+ 	     [list "readelf -r ifunc-12-${abi}.r" \
+ 		  "readelf -S ifunc-12-${abi}.sec" \
+ 		  "objdump -Dj.got ifunc-12-${abi}.g"] \
+	    "ifunc-12-${abi}" \
+	] \
+]
+
+# Check that no PIC stub is used when IPLT stub is present.
+run_ld_link_tests [list \
+	[list \
+	    "IFUNC 13 non-PIC calls to PIC IFUNC ${abi}" \
+	    "$abi_ldflags($abi) -static -Bstatic" "" \
+	    "$abi_asflags($abi) -non_shared" \
+	     [list ifunc-dyn-def.s ifunc-dyn-ref.s ifunc-static-main.s] \
+ 	     [list "readelf -r ifunc-13-${abi}.r" \
+ 		  "readelf -S ifunc-13-${abi}.sec" \
+ 		  "objdump -Dj.text ifunc-13-${abi}.t"] \
+	    "ifunc-13-${abi}" \
+	] \
+]
+
+# Check generation of mips32r6 stubs
+run_ld_link_tests [list \
+	[list \
+	    "IFUNC IPLT (Simple static executable with def and ref) mips32r6" \
+	    "" "" \
+	    "$abi_asflags(o32) -mips32r6" \
+	    [list ifunc-static-main.s ifunc-static-def.s] \
+	    [list "objdump -dj.iplt ifunc-iplt-mips32r6.t"] \
+	    "ifunc-iplt-mips32r6" \
+    ] \
+]
+
+# Check generation of micromips stubs
+run_ld_link_tests [list \
+	[list \
+	    "IFUNC IPLT (Simple static executable with def and ref) micromips" \
+	    "" "" \
+	    "$abi_asflags(o32) -mmicromips" \
+	    [list ifunc-static-main.s ifunc-static-def.s] \
+	    [list "objdump -dj.iplt ifunc-iplt-micromips.t" \
+		 "objdump -dj.igot ifunc-iplt-micromips.igot"] \
+	    "ifunc-iplt-micromips" \
+    ] \
+]
+
+# Check generation of mips16 stubs
+run_ld_link_tests [list \
+	[list \
+	    "IFUNC IPLT (Simple static executable with def and ref) mips16" \
+	    "" "" \
+	    "$abi_asflags(o32) -mips32r2 -mips16" \
+	    [list ifunc-static-main-mips16.s ifunc-static-def-mips16.s] \
+	    [list "objdump -dj.iplt ifunc-iplt-mips16.t" \
+		 "objdump -dj.igot ifunc-iplt-mips16.igot"] \
+	    "ifunc-iplt-mips16" \
+    ] \
+]
-- 
1.7.9.5

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v4)
  2016-01-08  0:10                         ` [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v4) Faraz Shahbazker
@ 2016-01-21 20:02                           ` Faraz Shahbazker
  2016-03-06  7:14                           ` Maciej W. Rozycki
  1 sibling, 0 replies; 35+ messages in thread
From: Faraz Shahbazker @ 2016-01-21 20:02 UTC (permalink / raw)
  To: binutils, rdsandiford; +Cc: macro

Bump.

On 01/07/2016 04:10 PM, Faraz Shahbazker wrote:
> Hi,
> 
> Spec (attached) updated to reflect creation of IPLT/IGOT entries for symbols that 
> bind locally. Test cases in following patch.
> 
> 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_got_info): New fields general_gotno and
> 	assigned_general_gotno.
> 	(struct mips_elf_link_hash_entry): New fields for offset in to
> 	IPLT/IGOT, flags to indicate if symbol needs IPLT/IRELOC/IGOT
> 	and if it has normal GOT-based relocations.
> 	(mips_elf_link_hash_table): New fields for size of IPLT stubs
> 	and hash-table for local IFUNC symbols.
> 	(MIPS16_P): New macro to check ASE flag.
> 	(mips16_exec_iplt_entry): Template for mips16 IPLT stub.
> 	(mips32_exec_iplt_entry): Template for mips32 IPLT stub.
> 	(mips32r6_exec_iplt_entry): Template for mips32 R6 IPLT stub.
> 	(micromips32_exec_iplt_entry): Template for micromips32 stub.
> 	(mips64_exec_iplt_entry): Template for mips64 IPLT stub.
> 	(mips64_48b_exec_iplt_entry): Template for 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 for micromips
> 	& mips16 IPLT stubs. New argument to set st_other value of stub.
> 	(mips_elf_add_la25_intro): Add argument other to the call to
> 	mips_elf_create_stub_symbol.
> 	(mips_elf_add_la25_trampoline): Add argument other to the call to
> 	mips_elf_create_stub_symbol.
> 	(mips_elf_rel_dyn_section): Moved up to avoid forward declaration.
> 	(mips_get_irel_section): New function.
> 	(mips_elf_allocate_ireloc): Likewise.
> 	(mips_elf_allocate_iplt): Likewise.
> 	(mips_elf_check_ifunc_symbols): Likewise.
> 	(mips_elf_check_symbols): Call mips_elf_check_ifunc_symbols to
> 	allocate an IPLT entry for an IFUNC symbol and set has_gnu_symbols.
> 	Skip creation of la25 stubs for IFUNCs having IPLT stubs.
> 	(mips_elf_count_got_entry): Count GOT entries for IFUNCs that do
> 	not have an IPLT stub under the general GOT region and allocate
> 	space for an IRELATIVE relocation for each.
> 	(mips_elf_got16_entry): Add argument h and pass it to
> 	mips_elf_create_local_got_entry instead of NULL.
> 	(mips_elf_create_local_got_entry): Change hash-lookup for IFUNCs
> 	to use non-NULL input BFD and a pointer to the
> 	mips_elf_link_hash_entry.  Add check for sufficient general GOT
> 	entries.  Assign local IFUNCs from general GOT entries pool.
>  	Allow early return for existing hash entry only if gotidx has
> 	been assigned. 	Allocate general GOT only if symbol does not
> 	have an IGOT entry.
> 	(mips_elf_record_local_got_symbol): New argument for pointer to
> 	mips_elf_link_hash_entry of the local symbol.  If non-NULL, this
> 	is used to set d.h in the GOT has entry instead of d.addend.
> 	(mips_use_local_got_p): Return TRUE for all IFUNC symbol definitions.
> 	(mips_elf_create_ifunc_sections): New function.
> 	(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 symbols that need IPLT.
> 	Force global IFUNC to be allocated from local GOT region.
> 	(mips_elf_perform_relocation): New argument flag indication relocation
> 	targets IFUNC. Disable JALR to BAL optimization for IFUNC symobls.
> 	(mips_elf_create_dynamic_relocation): Emit IRELATIVE relocation
> 	instead of REL32 for local IFUNC reference. Relax assertion for
> 	IFUNC symbols in explicit GOT region to have dynamic relocations.
>         (_bfd_mips_elf_section_processing): Size .igot section.
> 	(_bfd_mips_elf_check_relocs): Check need to create IFUNC sections.
> 	If symbol is an IFUNC, don't convert it to an STT_FUNC. Relax error
> 	checking to allow local IFUNCs to be accessed via call16 reloc.
> 	Record calls to a local IFUNC symbols and pre-allocate GOT entries
> 	for call16 and got16 relocations.
> 	(_bfd_mips_elf_adjust_dynamic_symbol): Add STT_GNU_IFUNC to sanity
> 	check.
> 	(_bfd_mips_elf_always_size_sections): Allocate IPLTs for local IFUNCs.
> 	(mips_elf_lay_out_got): Offset local GOT entries to follow general
> 	GOT entries.
> 	(_bfd_mips_elf_size_dynamic_sections): Exclude IPLT and IGOT.
> 	Create dynamic tag for DT_MIPS_GENERAL_GOTNO if needed.
> 	(_bfd_mips_elf_relocate_section): Relax error checking to allow
> 	local IFUNCs to be accessed via standalone got16 reloc.  Pass
> 	gnu_ifunc_p flag when calling mips_elf_perform_relocation.
> 	(mips_elf_create_iplt): New function.
> 	(mips_elf_check_local_got_index): Likewise.
> 	(mips_elf_create_ireloc): Likewise.
> 	(_bfd_mips_elf_finish_dynamic_symbol): Create IPLT stub/IRELATIVE
> 	relocation for IFUNC symbols as necessary. Set IFUNC symbol value
> 	to the IPLT entry address for executable 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 tag - DT_MIPS_GENERAL_GOTNO.
> 	(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 tag -
> 	DT_MIPS_GENERAL_GOTNO.
> 	(_bfd_mips_post_process_headers): If ELF uses GNU IFUNCs, increase
> 	ABIVERSION to 4.
> 	* 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:
> 	(get_mips_dynamic_type): Add case for DT_MIPS_GENERAL_GOTNO.
> 	(dynamic_section_mips_val): Add case for DT_MIPS_GENERAL_GOTNO.
> 
> elfcpp/ChangeLog:
> 
> 	* elfcpp.h
> 	(enum DT): Add cases for dynamic tag DT_MIPS_GENERAL_GOTNO.
> 
> include/elf/ChangeLog:
> 
> 	* mips.h
> 	(START_RELOC_NUMBERS): Entry for R_MIPS_IRELATIVE.
> 	(DT_MIPS_GENERAL_GOTNO): New dynamic tag.
> ---
>  bfd/bfd-in2.h      |    3 +
>  bfd/elf32-mips.c   |   22 ++
>  bfd/elfxx-mips.c   | 1076 +++++++++++++++++++++++++++++++++++++++++++++++-----
>  bfd/libbfd.h       |    1 +
>  bfd/reloc.c        |    5 +
>  binutils/readelf.c |    3 +
>  elfcpp/elfcpp.h    |    2 +
>  include/elf/mips.h |    7 +
>  8 files changed, 1030 insertions(+), 89 deletions(-)
> 
> diff --git a/bfd/bfd-in2.h b/bfd/bfd-in2.h
> index fb4858c..0218164 100644
> --- a/bfd/bfd-in2.h
> +++ b/bfd/bfd-in2.h
> @@ -3062,6 +3062,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 752f386..e8f1079 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 4ece819..6f23387 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 {
> @@ -165,10 +166,14 @@ struct mips_got_info
>    unsigned int tls_assigned_gotno;
>    /* The number of local .got entries, eventually including page entries.  */
>    unsigned int local_gotno;
> +  /* The number of explicitly relocated .got entries.  */
> +  unsigned int general_gotno;
>    /* The maximum number of page entries needed.  */
>    unsigned int page_gotno;
>    /* The number of relocations needed for the GOT entries.  */
>    unsigned int relocs;
> +  /* The first unused general .got entry.  */
> +  unsigned int assigned_general_gotno;
>    /* The first unused local .got entry.  */
>    unsigned int assigned_low_gotno;
>    /* The last unused local .got entry.  */
> @@ -375,6 +380,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.  */
> +  int igot_offset;
> +
>    /* The highest GGA_* value that satisfies all references to this symbol.  */
>    unsigned int global_got_area : 2;
>  
> @@ -392,6 +403,9 @@ struct mips_elf_link_hash_entry
>       cannot possibly be made dynamic).  */
>    unsigned int has_static_relocs : 1;
>  
> +  /* True if there is a got16 or call16 relocation against this symbol.  */
> +  unsigned int has_got_relocs : 1;
> +
>    /* True if we must not create a .MIPS.stubs entry for this symbol.
>       This is set, for example, if there are relocations related to
>       taking the function's address, i.e. any but R_MIPS_CALL*16 ones.
> @@ -413,6 +427,15 @@ 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 symbol need an IPLT stub?  */
> +  unsigned int needs_igot : 1;
> +
> +  /* Does this ifunc symbol need an IRELATIVE relocation?  */
> +  unsigned int needs_ireloc : 1;
>  };
>  
>  /* MIPS ELF linker hash table.  */
> @@ -485,6 +508,9 @@ 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;
> +
>    /* The number of functions that need a lazy-binding stub.  */
>    bfd_vma lazy_stub_count;
>  
> @@ -516,6 +542,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 +830,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 \
> @@ -1185,6 +1219,69 @@ 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 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					*/
> +};
> +
> +/* Format of 32 bit IPLT entries for R6. JR encoding differs.  */
> +static const bfd_vma mips32r6_exec_iplt_entry[] =
> +{
> +  0x3c0f0000,   /* lui $15, %hi(.igot address)		*/
> +  0x8df90000,   /* lw  $25, %lo(.igot address)($15)	*/
> +  0x03200009,   /* 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,	/* lw  $2, %lo(.igot address)($2) 	*/
> +  0x45b9,	/* jrc $25				*/
> +};
> +
> +/* The format of 64-bit IPLT entries.  */
> +static const bfd_vma mips64_exec_iplt_entry[] =
> +{
> +  0x3c0f0000,	/* lui $15, %highest(.got.iplt entry)        */
> +  0x3c0e0000,	/* lui $14, %hi(.got.iplt entry)             */
> +  0x25ef0000,	/* addiu $15, $15, %higher(.got.iplt entry)  */
> +  0x000f783c,	/* dsll32 $15, $15, 0x0                      */
> +  0x01ee782d,	/* daddu $15, $15, $14                       */
> +  0xddf90000,	/* ld $25, %lo(.got.iplt entry)($15)         */
> +  0x03200008,	/* jr $25                                    */
> +  0x00000000,	/* nop                                       */
> +};
> +
> +/* The format of 64-bit IPLT entries for 48bit address.  */
> +static const bfd_vma mips64_48b_exec_iplt_entry[] =
> +{
> +  0x3c0f0000,	/* lui $15, %higher(.got.iplt entry)         */
> +  0x25ef0000,	/* addiu $15, $15, %high(.got.iplt entry)    */
> +  0x000f7c38,	/* dsll $15, $15, 16  			     */
> +  0xddf90000,	/* ld $25, %lo(.got.iplt entry)($15)         */
> +  0x03200008,	/* jr $25                                    */
> +  0x00000000,	/* nop                                       */
> +};
> +
>  \f
>  /* microMIPS 32-bit opcode helper installer.  */
>  
> @@ -1278,11 +1375,17 @@ mips_elf_link_hash_newfunc (struct bfd_hash_entry *entry,
>        ret->got_only_for_calls = TRUE;
>        ret->readonly_reloc = FALSE;
>        ret->has_static_relocs = FALSE;
> +      ret->has_got_relocs = FALSE;
>        ret->no_fn_stub = FALSE;
>        ret->need_fn_stub = FALSE;
>        ret->has_nonpic_branches = FALSE;
>        ret->needs_lazy_stub = FALSE;
>        ret->use_plt_entry = FALSE;
> +      ret->needs_iplt = FALSE;
> +      ret->needs_igot = FALSE;
> +      ret->needs_ireloc = FALSE;
> +      ret->iplt_offset = -1;
> +      ret->igot_offset = -1;
>      }
>  
>    return (struct bfd_hash_entry *) ret;
> @@ -1576,13 +1679,14 @@ static bfd_boolean
>  mips_elf_create_stub_symbol (struct bfd_link_info *info,
>  			     struct mips_elf_link_hash_entry *h,
>  			     const char *prefix, asection *s, bfd_vma value,
> -			     bfd_vma size)
> +			     bfd_vma size, unsigned int other)
>  {
>    struct bfd_link_hash_entry *bh;
>    struct elf_link_hash_entry *elfh;
>    const char *name;
>  
> -  if (ELF_ST_IS_MICROMIPS (h->root.other))
> +  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.  */
> @@ -1598,6 +1702,8 @@ 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;
> +  elfh->other = other;
> +
>    return TRUE;
>  }
>  
> @@ -1863,7 +1969,7 @@ mips_elf_add_la25_intro (struct mips_elf_la25_stub *stub,
>      s->size = (1 << align) - 8;
>  
>    /* Create a symbol for the stub.  */
> -  mips_elf_create_stub_symbol (info, stub->h, ".pic.", s, s->size, 8);
> +  mips_elf_create_stub_symbol (info, stub->h, ".pic.", s, s->size, 8, 0);
>    stub->stub_section = s;
>    stub->offset = s->size;
>  
> @@ -1900,7 +2006,7 @@ mips_elf_add_la25_trampoline (struct mips_elf_la25_stub *stub,
>      }
>  
>    /* Create a symbol for the stub.  */
> -  mips_elf_create_stub_symbol (info, stub->h, ".pic.", s, s->size, 16);
> +  mips_elf_create_stub_symbol (info, stub->h, ".pic.", s, s->size, 16, 0);
>    stub->stub_section = s;
>    stub->offset = s->size;
>  
> @@ -1963,6 +2069,156 @@ 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 one 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 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;
> +}
> +
> +/* Reserve space in the rel.iplt section for an IRELATIVE relocation.  */
> +
> +static bfd_boolean
> +mips_elf_allocate_ireloc (struct bfd_link_info *info,
> +			  struct mips_elf_link_hash_table *mhtab,
> +			  struct mips_elf_link_hash_entry *mh)
> +{
> +  asection *srel;
> +  bfd *dynobj;
> +
> +  srel = mips_get_irel_section (info, mhtab);
> +  dynobj = elf_hash_table (info)->dynobj;
> +  if (srel != mhtab->root.irelplt && srel->size == 0)
> +    {
> +      /* Make room for a null element.  */
> +      srel->size += MIPS_ELF_REL_SIZE (dynobj);
> +      ++srel->reloc_count;
> +    }
> +  srel->size += MIPS_ELF_REL_SIZE (dynobj);
> +  mh->needs_ireloc = TRUE;
> +
> +  return TRUE;
> +}
> +
> +/* Reserve space in the iplt and igot tables for an ifunc entry
> +   and allocate space for an IRELATIVE relocation.  */
> +
> +static bfd_boolean
> +mips_elf_allocate_iplt (struct bfd_link_info *info,
> +			struct mips_elf_link_hash_table *mhtab,
> +			struct mips_elf_link_hash_entry *mh)
> +{
> +  asection *s;
> +  bfd *abfd = info->output_bfd;
> +  unsigned int other = 0;
> +
> +  BFD_ASSERT (!mh->needs_iplt);
> +  BFD_ASSERT (mhtab->root.iplt != NULL);
> +
> +  s = mhtab->root.iplt;
> +  if (ELF_ST_IS_MIPS16 (mh->root.other))
> +    other = STO_MIPS16;
> +
> +  mh->iplt_offset = s->size;
> +  mips_elf_create_stub_symbol (info, mh, ".iplt.", mhtab->root.iplt,
> +			       s->size, mhtab->iplt_entry_size, other);
> +  s->size += mhtab->iplt_entry_size;
> +
> +  BFD_ASSERT (mhtab->root.igotplt != NULL);
> +
> +  /* Only create IGOT entry if there are no GOT relocations, or when
> +     there are non-CALL references to the symbol.  In the latter case,
> +     existing GOT entry must point to IPLT, so an IGOT entry is needed
> +     to catch the result of the IRELATIVE relocation resolution.  */
> +  if (!mh->has_got_relocs || mh->root.pointer_equality_needed)
> +    {
> +      mh->igot_offset = mhtab->root.igotplt->size;
> +      mhtab->root.igotplt->size += MIPS_ELF_GOT_SIZE (abfd);
> +      mh->needs_igot = TRUE;
> +    }
> +
> +  mh->needs_iplt = TRUE;
> +
> +  /* IRELATIVE fixup will be needed for each local IFUNC.  */
> +  if (!mips_elf_allocate_ireloc (info, mips_elf_hash_table (info), mh))
> +    return FALSE;
> +
> +  return TRUE;
> +}
> +
> +/* hash_traverse callback that is called before sizing sections.
> +   DATA points to a mips_htab_traverse_info structure.  */
> +
> +static bfd_boolean
> +mips_elf_check_ifunc_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 (h
> +      && !h->needs_iplt
> +      && h->root.type == STT_GNU_IFUNC
> +      && h->root.def_regular)
> +    {
> +      struct bfd_link_info *info = hti->info;
> +      elf_tdata (info->output_bfd)->has_gnu_symbols |= elf_gnu_symbol_ifunc;
> +
> +      /* For global symbols, .iplt entry is needed only for all non-shared-
> +	 objects.  For local symbols, it is needed only if the symbol has
> +	 static relocations.  */
> +      if (((h->root.forced_local && h->has_static_relocs)
> +	   || (!h->root.forced_local && !bfd_link_pic (info)))
> +	  && !mips_elf_allocate_iplt (info, mips_elf_hash_table (info), h))
> +	{
> +	  hti->error = TRUE;
> +	  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.  */
>  
> @@ -1975,6 +2231,12 @@ mips_elf_check_symbols (struct mips_elf_link_hash_entry *h, void *data)
>    if (!bfd_link_relocatable (hti->info))
>      mips_elf_check_mips16_stubs (hti->info, h);
>  
> +  /* Create stubs and relocations for IFUNC symbols.  */
> +  if (h
> +      && h->root.type == STT_GNU_IFUNC
> +      && !mips_elf_check_ifunc_symbols ((void **)&h, hti))
> +    return FALSE;
> +
>    if (mips_elf_local_pic_function_p (h))
>      {
>        /* PR 12845: If H is in a section that has been garbage
> @@ -1986,13 +2248,15 @@ mips_elf_check_symbols (struct mips_elf_link_hash_entry *h, void *data)
>  	 If we're creating a non-PIC relocatable object, mark H as
>  	 being PIC.  If we're creating a non-relocatable object with
>  	 non-PIC branches and jumps to H, make sure that H has an la25
> -	 stub.  */
> +	 stub.  IFUNCs with IPLT stubs don't need an la25 stub.  */
>        if (bfd_link_relocatable (hti->info))
>  	{
>  	  if (!PIC_OBJECT_P (hti->output_bfd))
>  	    h->root.other = ELF_ST_SET_MIPS_PIC (h->root.other);
>  	}
> -      else if (h->has_nonpic_branches && !mips_elf_add_la25_stub (hti->info, h))
> +      else if (h->has_nonpic_branches
> +	       && (h->root.type != STT_GNU_IFUNC || !h->needs_iplt)
> +	       && !mips_elf_add_la25_stub (hti->info, h))
>  	{
>  	  hti->error = TRUE;
>  	  return FALSE;
> @@ -3130,37 +3394,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
> @@ -3254,8 +3487,21 @@ mips_elf_count_got_entry (struct bfd_link_info *info,
>  					entry->symndx < 0
>  					? &entry->d.h->root : NULL);
>      }
> -  else if (entry->symndx >= 0 || entry->d.h->global_got_area == GGA_NONE)
> -    g->local_gotno += 1;
> +  else if (entry->symndx >= 0 || (entry->d.h->global_got_area == GGA_NONE))
> +    {
> +      /* Count IFUNCs as general GOT entries with explicit relocations.  */
> +      if (entry->symndx < 0
> +	  && entry->d.h->root.type == STT_GNU_IFUNC
> +	  && entry->d.h->root.def_regular
> +	  && !entry->d.h->needs_igot)
> +	{
> +	  g->general_gotno += 1;
> +	  mips_elf_allocate_ireloc (info, mips_elf_hash_table (info),
> +				    entry->d.h);
> +	}
> +      else
> +	g->local_gotno += 1;
> +    }
>    else
>      g->global_gotno += 1;
>  }
> @@ -3587,7 +3833,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;
>  
> @@ -3602,7 +3849,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
> @@ -3691,18 +3938,28 @@ mips_elf_create_local_got_entry (bfd *abfd, struct bfd_link_info *info,
>        return entry;
>      }
>  
> -  lookup.abfd = NULL;
>    lookup.symndx = -1;
> -  lookup.d.address = value;
> +  if (h && h->root.type == STT_GNU_IFUNC)
> +    {
> +      lookup.abfd = ibfd;
> +      lookup.d.h = h;
> +    }
> +  else
> +    {
> +      lookup.abfd = NULL;
> +      lookup.d.address = value;
> +    }
> +
>    loc = htab_find_slot (g->got_entries, &lookup, INSERT);
>    if (!loc)
>      return NULL;
>  
>    entry = (struct mips_got_entry *) *loc;
> -  if (entry)
> +  if (entry && entry->gotidx >= 0)
>      return entry;
>  
> -  if (g->assigned_low_gotno > g->assigned_high_gotno)
> +  if (g->assigned_low_gotno > g->assigned_high_gotno
> +      || g->assigned_general_gotno > g->local_gotno)
>      {
>        /* We didn't allocate enough space in the GOT.  */
>        (*_bfd_error_handler)
> @@ -3715,7 +3972,15 @@ mips_elf_create_local_got_entry (bfd *abfd, struct bfd_link_info *info,
>    if (!entry)
>      return NULL;
>  
> -  if (got16_reloc_p (r_type)
> +  if (h && h->needs_ireloc && !h->needs_igot)
> +    /* Allocate IFUNC slots in the general GOT region since they
> +       will need explicit IRELATIVE relocations.  */
> +    {
> +      lookup.gotidx = MIPS_ELF_GOT_SIZE (abfd) * g->assigned_general_gotno++;
> +      if (h->needs_iplt)
> +	h->igot_offset = lookup.gotidx;
> +    }
> +  else if (got16_reloc_p (r_type)
>        || call16_reloc_p (r_type)
>        || got_page_reloc_p (r_type)
>        || got_disp_reloc_p (r_type))
> @@ -3958,7 +4223,8 @@ mips_elf_record_global_got_symbol (struct elf_link_hash_entry *h,
>  
>  static bfd_boolean
>  mips_elf_record_local_got_symbol (bfd *abfd, long symndx, bfd_vma addend,
> -				  struct bfd_link_info *info, int r_type)
> +				  struct bfd_link_info *info, int r_type,
> +				  struct mips_elf_link_hash_entry *h)
>  {
>    struct mips_elf_link_hash_table *htab;
>    struct mips_got_info *g;
> @@ -3972,7 +4238,10 @@ mips_elf_record_local_got_symbol (bfd *abfd, long symndx, bfd_vma addend,
>  
>    entry.abfd = abfd;
>    entry.symndx = symndx;
> -  entry.d.addend = addend;
> +  if (h)
> +    entry.d.h = h;
> +  else
> +    entry.d.addend = addend;
>    entry.tls_type = mips_elf_reloc_tls_type (r_type);
>    return mips_elf_record_got_entry (info, abfd, &entry);
>  }
> @@ -4389,7 +4658,11 @@ mips_use_local_got_p (struct bfd_link_info *info,
>       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)
> +
> +  /* Both global & local IFUNC symbols actually use the explicitly relocated
> +     GOT region, but we don't distinguish it from the local GOT just yet.  */
> +  if (h->root.dynindx == -1
> +      || (h->root.type == STT_GNU_IFUNC && h->root.def_regular))
>      return TRUE;
>  
>    /* Symbols that bind locally can (and in the case of forced-local
> @@ -5075,6 +5348,65 @@ 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;
> +
> +  if (!bfd_link_pic (info))
> +    {
> +      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))
> +	htab->iplt_entry_size = (4 * ARRAY_SIZE (micromips32_exec_iplt_entry)
> +				 - 2);
> +      else
> +	htab->iplt_entry_size = 4 * (ARRAY_SIZE (mips32_exec_iplt_entry)
> +				     + (LOAD_INTERLOCKS_P (dynobj) ? 0 : 1));
> +
> +      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;
> +
> +      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
> @@ -5191,6 +5523,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 = abfd->sections;
> +  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);
> +
> +  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;
> +      ret->igot_offset = -1;
> +      ret->root.other = isym->st_other;
> +      ret->got_only_for_calls = TRUE;
> +
> +      *slot = ret;
> +    }
> +
> +  return ret;
> +}
>  \f
>  /* Calculate the value produced by the RELOCATION (which comes from
>     the INPUT_BFD).  The ADDEND is the addend to use for this
> @@ -5241,6 +5641,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
> @@ -5321,6 +5723,13 @@ 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
>      {
> @@ -5545,6 +5954,21 @@ 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
> +	   && (h->needs_igot
> +	       || (!call16_reloc_p (r_type)
> +		   && !got16_reloc_p (r_type))))
> +    {
> +      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)
> @@ -5640,6 +6064,16 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
>  	      BFD_ASSERT (h->root.needs_plt);
>  	      g = mips_elf_gotplt_index (info, &h->root);
>  	    }
> +	  /* IFUNCs use the explicitly-relocated GOT region, however we don't
> +	     distinguish it from the local GOT at this stage.  */
> +	  else if (h && h->needs_ireloc && !h->needs_igot)
> +	    {
> +	      g = mips_elf_local_got_index (abfd, input_bfd, info,
> +					    symbol + addend, r_symndx,
> +					    h, r_type);
> +	      if (g == MINUS_ONE)
> +		return bfd_reloc_outofrange;
> +	    }
>  	  else
>  	    {
>  	      BFD_ASSERT (addend == 0);
> @@ -5931,8 +6365,11 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
>  	 R_MIPS*_GOT16; every relocation evaluates to "G".  */
>        if (!htab->is_vxworks && local_p)
>  	{
> +	  /* Local IFUNC symbols must be accessed through GOT, similar to
> +	     global symbols, to allow for indirection.  */
>  	  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
> @@ -6209,7 +6646,8 @@ mips_elf_perform_relocation (struct bfd_link_info *info,
>  			     const Elf_Internal_Rela *relocation,
>  			     bfd_vma value, bfd *input_bfd,
>  			     asection *input_section, bfd_byte *contents,
> -			     bfd_boolean cross_mode_jump_p)
> +			     bfd_boolean cross_mode_jump_p,
> +			     bfd_boolean ifunc_p)
>  {
>    bfd_vma x;
>    bfd_byte *location;
> @@ -6279,6 +6717,7 @@ mips_elf_perform_relocation (struct bfd_link_info *info,
>  	   && r_type == R_MIPS_26
>  	   && (x >> 26) == 0x3)		/* jal addr */
>  	  || (JALR_TO_BAL_P (input_bfd)
> +	      && !ifunc_p
>  	      && r_type == R_MIPS_JALR
>  	      && x == 0x0320f809)	/* jalr t9 */
>  	  || (JR_TO_B_P (input_bfd)
> @@ -6377,7 +6816,8 @@ mips_elf_create_dynamic_relocation (bfd *output_bfd,
>       in the relocation.  */
>    if (h != NULL && ! SYMBOL_REFERENCES_LOCAL (info, &h->root))
>      {
> -      BFD_ASSERT (htab->is_vxworks || h->global_got_area != GGA_NONE);
> +      BFD_ASSERT (htab->is_vxworks || h->global_got_area != GGA_NONE
> +		  || h->root.type == STT_GNU_IFUNC);
>        indx = h->root.dynindx;
>        if (SGI_COMPAT (output_bfd))
>  	defined_p = h->root.def_regular;
> @@ -6436,31 +6876,44 @@ mips_elf_create_dynamic_relocation (bfd *output_bfd,
>    if (defined_p && r_type != R_MIPS_REL32)
>      *addendp += symbol;
>  
> -  if (htab->is_vxworks)
> -    /* VxWorks uses non-relative relocations for this.  */
> -    outrel[0].r_info = ELF32_R_INFO (indx, R_MIPS_32);
> +  /* Morph REL32 in to IRELATIVE fix-up for local IFUNC reference.  */
> +  if (h
> +      && h->root.type == STT_GNU_IFUNC
> +      && SYMBOL_REFERENCES_LOCAL (info, &h->root))
> +    {
> +      outrel[0].r_info = ELF_R_INFO (output_bfd, 0,
> +				     R_MIPS_IRELATIVE);
> +      outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
> +				     R_MIPS_NONE);
> +    }
>    else
> -    /* The relocation is always an REL32 relocation because we don't
> -       know where the shared library will wind up at load-time.  */
> -    outrel[0].r_info = ELF_R_INFO (output_bfd, (unsigned long) indx,
> -				   R_MIPS_REL32);
> -
> -  /* For strict adherence to the ABI specification, we should
> -     generate a R_MIPS_64 relocation record by itself before the
> -     _REL32/_64 record as well, such that the addend is read in as
> -     a 64-bit value (REL32 is a 32-bit relocation, after all).
> -     However, since none of the existing ELF64 MIPS dynamic
> -     loaders seems to care, we don't waste space with these
> -     artificial relocations.  If this turns out to not be true,
> -     mips_elf_allocate_dynamic_relocation() should be tweaked so
> -     as to make room for a pair of dynamic relocations per
> -     invocation if ABI_64_P, and here we should generate an
> -     additional relocation record with R_MIPS_64 by itself for a
> -     NULL symbol before this relocation record.  */
> -  outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
> -				 ABI_64_P (output_bfd)
> -				 ? R_MIPS_64
> -				 : R_MIPS_NONE);
> +    {
> +      if (htab->is_vxworks)
> +	/* VxWorks uses non-relative relocations for this.  */
> +	outrel[0].r_info = ELF32_R_INFO (indx, R_MIPS_32);
> +      else
> +	/* The relocation is always an REL32 relocation because we don't
> +	   know where the shared library will wind up at load-time.  */
> +	outrel[0].r_info = ELF_R_INFO (output_bfd, (unsigned long) indx,
> +				       R_MIPS_REL32);
> +
> +      /* For strict adherence to the ABI specification, we should
> +	 generate a R_MIPS_64 relocation record by itself before the
> +	 _REL32/_64 record as well, such that the addend is read in as
> +	 a 64-bit value (REL32 is a 32-bit relocation, after all).
> +	 However, since none of the existing ELF64 MIPS dynamic
> +	 loaders seems to care, we don't waste space with these
> +	 artificial relocations.  If this turns out to not be true,
> +	 mips_elf_allocate_dynamic_relocation() should be tweaked so
> +	 as to make room for a pair of dynamic relocations per
> +	 invocation if ABI_64_P, and here we should generate an
> +	 additional relocation record with R_MIPS_64 by itself for a
> +	 NULL symbol before this relocation record.  */
> +      outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
> +				     ABI_64_P (output_bfd)
> +				     ? R_MIPS_64
> +				     : R_MIPS_NONE);
> +    }
>    outrel[2].r_info = ELF_R_INFO (output_bfd, 0, R_MIPS_NONE);
>  
>    /* Adjust the output offset of the relocation to reference the
> @@ -7007,6 +7460,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;
> @@ -7920,6 +8375,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.irelplt && !mips_elf_create_ifunc_sections (info))
> +    return FALSE;
> +
>    /* Check for the mips16 stub sections.  */
>  
>    name = bfd_get_section_name (abfd, sec);
> @@ -8178,6 +8640,7 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
>        unsigned long r_symndx;
>        unsigned int r_type;
>        struct elf_link_hash_entry *h;
> +      struct mips_elf_link_hash_entry *ih = NULL;
>        bfd_boolean can_make_dynamic_p;
>        bfd_boolean call_reloc_p;
>        bfd_boolean constrain_symbol_p;
> @@ -8186,7 +8649,23 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *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;
> +
> +	  /* Relocation is for local STT_GNU_IFUNC symbol.  */
> +	  if (isym->st_info == STT_GNU_IFUNC)
> +	    {
> +	      /* Ensure that we have a hash entry for this symbol.  */
> +	      if ((ih = 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)
> @@ -8370,6 +8849,13 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
>  		info->flags |= DF_TEXTREL;
>  	    }
>  	}
> +      else if (ih)
> +	{
> +	  if (!bfd_link_pic (info) && !can_make_dynamic_p)
> +	    ih->has_static_relocs = 1;
> +	  if (!call_reloc_p)
> +	    ih->root.pointer_equality_needed = 1;
> +	}
>        else if (call_lo16_reloc_p (r_type)
>  	       || got_lo16_reloc_p (r_type)
>  	       || got_disp_reloc_p (r_type)
> @@ -8384,7 +8870,8 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
>  	     R_MIPS_CALL_HI16 because these are always followed by an
>  	     R_MIPS_GOT_LO16 or R_MIPS_CALL_LO16.  */
>  	  if (!mips_elf_record_local_got_symbol (abfd, r_symndx,
> -						 rel->r_addend, info, r_type))
> +						 rel->r_addend, info,
> +						 r_type, NULL))
>  	    return FALSE;
>  	}
>  
> @@ -8398,7 +8885,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 && ih == NULL)
>  	    {
>  	      (*_bfd_error_handler)
>  		(_("%B: CALL16 reloc at 0x%lx not against global symbol"),
> @@ -8406,6 +8894,10 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
>  	      bfd_set_error (bfd_error_bad_value);
>  	      return FALSE;
>  	    }
> +	  if (h && h->type == STT_GNU_IFUNC)
> +	    ((struct mips_elf_link_hash_entry *)h)->has_got_relocs = TRUE;
> +	  else if (ih)
> +	    ih->has_got_relocs = TRUE;
>  	  /* Fall through.  */
>  
>  	case R_MIPS_CALL_HI16:
> @@ -8423,10 +8915,17 @@ _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;
>  	    }
> +	  else
> +	    if (ih &&
> +		!mips_elf_record_local_got_symbol (abfd, -1, rel->r_addend,
> +						   info, r_type, ih))
> +	      return FALSE;
>  	  break;
>  
>  	case R_MIPS_GOT_PAGE:
> @@ -8459,9 +8958,18 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
>  		}
>  	      else
>  		addend = rel->r_addend;
> -	      if (!mips_elf_record_got_page_ref (info, abfd, r_symndx,
> -						 h, addend))
> +	      if (ih &&
> +		  !mips_elf_record_local_got_symbol (abfd, -1, rel->r_addend,
> +						     info, r_type, ih))
>  		return FALSE;
> +	      else if (!mips_elf_record_got_page_ref (info, abfd, r_symndx,
> +						   h, addend))
> +		  return FALSE;
> +
> +	      if (h && h->type == STT_GNU_IFUNC)
> +		((struct mips_elf_link_hash_entry *)h)->has_got_relocs = TRUE;
> +	      else if (ih)
> +		ih->has_got_relocs = TRUE;
>  
>  	      if (h)
>  		{
> @@ -8518,7 +9026,7 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
>  	    {
>  	      if (!mips_elf_record_local_got_symbol (abfd, r_symndx,
>  						     rel->r_addend,
> -						     info, r_type))
> +						     info, r_type, NULL))
>  		return FALSE;
>  	    }
>  	  break;
> @@ -8964,6 +9472,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
> @@ -9242,6 +9751,10 @@ _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_ifunc_symbols, &hti);
> +
>    if (hti.error)
>      return FALSE;
>  
> @@ -9289,6 +9802,10 @@ mips_elf_lay_out_got (bfd *output_bfd, struct bfd_link_info *info)
>    if (!mips_elf_resolve_final_got_entries (info, g))
>      return FALSE;
>  
> +  g->assigned_general_gotno = htab->reserved_gotno;
> +  g->local_gotno += g->general_gotno;
> +  g->assigned_low_gotno += g->general_gotno;
> +
>    /* Calculate the total loadable size of the output.  That
>       will give us the maximum number of GOT_PAGE entries
>       required.  */
> @@ -9730,6 +10247,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)
>  	{
> @@ -9875,6 +10394,13 @@ _bfd_mips_elf_size_dynamic_sections (bfd *output_bfd,
>  	  if (! MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_PLTGOT, 0))
>  	    return FALSE;
>  	}
> +
> +      if (elf_tdata (output_bfd)->has_gnu_symbols & elf_gnu_symbol_ifunc)
> +	{
> +	  if (! MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_GENERAL_GOTNO, 0))
> +	    return FALSE;
> +	}
> +
>        if (htab->is_vxworks
>  	  && !elf_vxworks_add_dynamic_entries (output_bfd, info))
>  	return FALSE;
> @@ -10006,6 +10532,8 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
>        Elf_Internal_Shdr *symtab_hdr;
>        struct elf_link_hash_entry *h;
>        bfd_boolean rel_reloc;
> +      bfd_boolean local_gnu_ifunc_p = FALSE;
> +      bfd_boolean gnu_ifunc_p = FALSE;
>  
>        rel_reloc = (NEWABI_P (input_bfd)
>  		   && mips_elf_rel_relocation_p (input_bfd, input_section,
> @@ -10019,6 +10547,23 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
>  	{
>  	  sec = local_sections[r_symndx];
>  	  h = NULL;
> +
> +	  Elf_Internal_Sym *isym;
> +	  struct mips_elf_link_hash_table *htab;
> +	  htab = mips_elf_hash_table (info);
> +	  isym = bfd_sym_from_r_symndx (&htab->sym_cache, input_bfd, r_symndx);
> +
> +	  if (isym == NULL)
> +	    return FALSE;
> +
> +	  /* Relocation is for local STT_GNU_IFUNC symbol.  */
> +	  if (isym->st_info == STT_GNU_IFUNC)
> +	    {
> +	      /* Ensure that we have a hash entry for this symbol.  */
> +	      if (get_local_sym_hash (htab, input_bfd, rel) == NULL)
> +		return FALSE;
> +	      local_gnu_ifunc_p = TRUE;
> +	    }
>  	}
>        else
>  	{
> @@ -10046,6 +10591,8 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
>  	  continue;
>  	}
>  
> +      gnu_ifunc_p = (local_gnu_ifunc_p || (h && h->type == STT_GNU_IFUNC));
> +
>        if (r_type == R_MIPS_64 && ! NEWABI_P (input_bfd))
>  	{
>  	  /* Some 32-bit code uses R_MIPS_64.  In particular, people use
> @@ -10077,7 +10624,8 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
>  	      if (hi16_reloc_p (r_type)
>  		  || (got16_reloc_p (r_type)
>  		      && mips_elf_local_relocation_p (input_bfd, rel,
> -						      local_sections)))
> +						      local_sections)
> +		      && !local_gnu_ifunc_p))
>  		{
>  		  if (!mips_elf_add_lo16_rel_addend (input_bfd, rel, relend,
>  						     contents, &addend))
> @@ -10168,7 +10716,7 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
>  
>  	      if (! mips_elf_perform_relocation (info, howto, rel, addend,
>  						 input_bfd, input_section,
> -						 contents, FALSE))
> +						 contents, FALSE, gnu_ifunc_p))
>  		return FALSE;
>  	    }
>  
> @@ -10322,7 +10870,8 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
>        /* Actually perform the relocation.  */
>        if (! mips_elf_perform_relocation (info, howto, rel, value,
>  					 input_bfd, input_section,
> -					 contents, cross_mode_jump_p))
> +					 contents, cross_mode_jump_p,
> +					 gnu_ifunc_p))
>  	return FALSE;
>      }
>  
> @@ -10467,6 +11016,242 @@ 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,
> +		      bfd_vma igotplt_address)
> +{
> +  bfd_byte *loc;
> +  const bfd_vma *iplt_entry;
> +  bfd_vma high = mips_elf_high (igotplt_address);
> +  bfd_vma low = igotplt_address & 0xffff;
> +
> +  /* 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 (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;
> +
> +      if (highest)
> +	{
> +	  /* Full 64-bit address space.  */
> +	  bfd_put_32 (output_bfd, iplt_entry[0] | highest, loc);
> +	  bfd_put_32 (output_bfd, iplt_entry[1] | high, loc + 4);
> +	  bfd_put_32 (output_bfd, iplt_entry[2] | higher, loc + 8);
> +	  bfd_put_32 (output_bfd, iplt_entry[3], 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 (higher)
> +	{
> +	  /* 48-bit address space.  */
> +	  iplt_entry = mips64_48b_exec_iplt_entry;
> +	  bfd_put_32 (output_bfd, iplt_entry[0] | higher, loc);
> +	  bfd_put_32 (output_bfd, iplt_entry[1] | high, loc + 4);
> +	  bfd_put_32 (output_bfd, iplt_entry[2], loc + 8);
> +	  bfd_put_32 (output_bfd, iplt_entry[3] | low, loc + 12);
> +	  bfd_put_32 (output_bfd, iplt_entry[4], loc + 16);
> +	  bfd_put_32 (output_bfd, iplt_entry[5], loc + 20);
> +	}
> +      else
> +	{
> +	  /* 32-bit address space.  */
> +	  bfd_put_32 (output_bfd, iplt_entry[0] | high, loc);
> +	  bfd_put_32 (output_bfd, iplt_entry[5] | low, loc + 4);
> +	  bfd_put_32 (output_bfd, iplt_entry[6], loc + 8);
> +	  bfd_put_32 (output_bfd, iplt_entry[7], loc + 12);
> +	}
> +    }
> +  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
> +    {
> +      if (MIPSR6_P (output_bfd))
> +	iplt_entry = mips32r6_exec_iplt_entry;
> +      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;
> +}
> +
> +/* Find local GOT index for VALUE.  Return -1 if no GOT slot is found.  */
> +
> +static bfd_vma
> +mips_elf_check_local_got_index (bfd *abfd, struct bfd_link_info *info,
> +				struct mips_elf_link_hash_entry *h)
> +{
> +  struct mips_got_entry lookup, *entry;
> +  void **loc;
> +  struct mips_got_info *g;
> +  struct mips_elf_link_hash_table *htab;
> +
> +  htab = mips_elf_hash_table (info);
> +  BFD_ASSERT (htab != NULL);
> +
> +  g = mips_elf_bfd_got (abfd, FALSE);
> +
> +  /* Check for existing local GOT entry.  */
> +  if (g != NULL)
> +    {
> +      lookup.abfd = abfd;
> +      lookup.symndx = -1;
> +      lookup.d.h = h;
> +      lookup.tls_type = GOT_TLS_NONE;
> +      loc = htab_find_slot (g->got_entries, &lookup, NO_INSERT);
> +    }
> +  else
> +    return -1;
> +
> +  if (loc && *loc)
> +    {
> +      entry = (struct mips_got_entry *) *loc;
> +      return entry->gotidx;
> +    }
> +  else
> +    return -1;
> +}
> +
> +/* 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_vma igotplt_address = 0;
> +  int igot_offset = -1;
> +  asection *gotsect, *relsect;
> +  bfd_vma value = sym->st_value;
> +
> +  if (MIPS16_P (output_bfd) || MICROMIPS_P (output_bfd))
> +    value |= 1;
> +
> +  if (!hmips->needs_igot)
> +    {
> +      gotsect = htab->sgot;
> +      /* Check if IFUNC symbol already has an assigned GOT slot.  */
> +      igot_offset = mips_elf_check_local_got_index (output_bfd, info, hmips);
> +    }
> +  else
> +    {
> +      bfd_byte *loc;
> +      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, value, loc);
> +      else
> +	bfd_put_32 (output_bfd, value, loc);
> +    }
> +
> +  igotplt_address = (gotsect->output_section->vma + gotsect->output_offset
> +		     + igot_offset);
> +
> +  relsect = mips_get_irel_section (info, htab);
> +
> +  if (igot_offset >= 0)
> +    {
> +      if (hmips->needs_iplt && relsect->contents == NULL)
> +	{
> +	  /* Allocate memory for the relocation section contents.  */
> +	  relsect->contents = bfd_zalloc (dynobj, relsect->size);
> +	  if (relsect->contents == NULL)
> +	    return FALSE;
> +	}
> +
> +      if (hmips->needs_iplt || SYMBOL_REFERENCES_LOCAL (info, &hmips->root))
> +	/* Emit an IRELATIVE relocation against the [I]GOT entry.  */
> +	mips_elf_output_dynamic_relocation (output_bfd, relsect,
> +					    relsect->reloc_count++, 0,
> +					    R_MIPS_IRELATIVE, igotplt_address);
> +      else
> +	{
> +	  /* Emit an R_MIPS_REL32 relocation against global GOT entry for
> +	     a preemptible symbol.  */
> +	  asection *sec = hmips->root.root.u.def.section;
> +	  Elf_Internal_Rela rel[3];
> +
> +	  memset (rel, 0, sizeof (rel));
> +	  rel[0].r_info = ELF_R_INFO (output_bfd, 0, R_MIPS_REL32);
> +	  rel[0].r_offset = rel[1].r_offset = rel[2].r_offset = igot_offset;
> +
> +	  mips_elf_create_dynamic_relocation (output_bfd, info, rel, hmips,
> +					      sec, value, NULL,
> +					      gotsect);
> +	}
> +    }
> +  /* If necessary, generate the corresponding .iplt entry.  */
> +  if (hmips->needs_iplt
> +      && !mips_elf_create_iplt (output_bfd, htab, hmips, igotplt_address))
> +    return FALSE;
> +
> +  return TRUE;
> +}
> +
>  /* Finish up dynamic symbol handling.  We set the contents of various
>     dynamic sections here.  */
>  
> @@ -10794,6 +11579,19 @@ _bfd_mips_elf_finish_dynamic_symbol (bfd *output_bfd,
>        sym->st_other = other;
>      }
>  
> +  if (hmips->root.type == STT_GNU_IFUNC)
> +    {
> +      if (hmips->needs_ireloc
> +	  && !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.  */
> @@ -10957,9 +11755,41 @@ _bfd_mips_elf_finish_dynamic_symbol (bfd *output_bfd,
>        sym->st_other -= STO_MICROMIPS;
>      }
>  
> +  if (hmips->needs_iplt)
> +    {
> +      /* 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);
> +  isym.st_other = h->root.other;
> +  if (ELF_ST_IS_COMPRESSED (isym.st_other))
> +    isym.st_value |= 1;
> +
> +  return _bfd_mips_elf_finish_dynamic_symbol (info->output_bfd, info,
> +					      &h->root, &isym);
> +}
> +
>  /* Likewise, for VxWorks.  */
>  
>  bfd_boolean
> @@ -11345,6 +12175,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;
> @@ -11515,6 +12352,10 @@ _bfd_mips_elf_finish_dynamic_sections (bfd *output_bfd,
>  	      dyn.d_un.d_ptr = s->vma;
>  	      break;
>  
> +	    case DT_MIPS_GENERAL_GOTNO:
> +	      dyn.d_un.d_val = htab->reserved_gotno + gg->general_gotno;
> +	      break;
> +
>  	    case DT_RELASZ:
>  	      BFD_ASSERT (htab->is_vxworks);
>  	      /* The count does not include the JUMP_SLOT relocations.  */
> @@ -13849,6 +14690,48 @@ _bfd_mips_elf_relax_section (bfd *abfd, asection *sec,
>    return FALSE;
>  }
>  \f
> +/* 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 *
> @@ -13872,6 +14755,16 @@ _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;
>  }
>  
> @@ -15509,7 +16402,9 @@ _bfd_mips_elf_get_target_dtag (bfd_vma dtag)
>        return "DT_MIPS_PLTGOT";
>      case DT_MIPS_RWPLT:
>        return "DT_MIPS_RWPLT";
> -    }
> +    case DT_MIPS_GENERAL_GOTNO:
> +      return "DT_MIPS_GENERAL_GOTNO";
> +   }
>  }
>  
>  /* Return the meaning of Tag_GNU_MIPS_ABI_FP value FP, or null if
> @@ -16123,7 +17018,7 @@ _bfd_mips_elf_get_synthetic_symtab (bfd *abfd,
>  void
>  _bfd_mips_post_process_headers (bfd *abfd, struct bfd_link_info *link_info)
>  {
> -  struct mips_elf_link_hash_table *htab;
> +  struct mips_elf_link_hash_table *htab = NULL;
>    Elf_Internal_Ehdr *i_ehdrp;
>  
>    i_ehdrp = elf_elfheader (abfd);
> @@ -16141,6 +17036,9 @@ _bfd_mips_post_process_headers (bfd *abfd, struct bfd_link_info *link_info)
>    if (mips_elf_tdata (abfd)->abiflags.fp_abi == Val_GNU_MIPS_ABI_FP_64
>        || mips_elf_tdata (abfd)->abiflags.fp_abi == Val_GNU_MIPS_ABI_FP_64A)
>      i_ehdrp->e_ident[EI_ABIVERSION] = 3;
> +
> +  if (elf_tdata (abfd)->has_gnu_symbols & elf_gnu_symbol_ifunc)
> +    i_ehdrp->e_ident[EI_ABIVERSION] = 4;
>  }
>  
>  int
> diff --git a/bfd/libbfd.h b/bfd/libbfd.h
> index d7183d3..aa9b0c7 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 3ef704f..496c465 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 47ac1ad..0091539 100644
> --- a/binutils/readelf.c
> +++ b/binutils/readelf.c
> @@ -1729,6 +1729,8 @@ 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_GENERAL_GOTNO: return "MIPS_GENERAL_GOTNO";
> +
>      default:
>        return NULL;
>      }
> @@ -8612,6 +8614,7 @@ dynamic_section_mips_val (Elf_Internal_Dyn * entry)
>      case DT_MIPS_DELTA_SYM_NO:
>      case DT_MIPS_DELTA_CLASSSYM_NO:
>      case DT_MIPS_COMPACT_SIZE:
> +    case DT_MIPS_GENERAL_GOTNO:
>        print_vma (entry->d_un.d_ptr, DEC);
>        break;
>  
> diff --git a/elfcpp/elfcpp.h b/elfcpp/elfcpp.h
> index e0eae42..c77b86e 100644
> --- a/elfcpp/elfcpp.h
> +++ b/elfcpp/elfcpp.h
> @@ -866,6 +866,8 @@ enum DT
>    DT_MIPS_PLTGOT = 0x70000032,
>    // Points to the base of a writable PLT.
>    DT_MIPS_RWPLT = 0x70000034,
> +  // The GOT index of the first implicitly relocated GOT entry.
> +  DT_MIPS_GENERAL_GOTNO = 0x70000036,
>  
>    DT_AUXILIARY = 0x7ffffffd,
>    DT_USED = 0x7ffffffe,
> diff --git a/include/elf/mips.h b/include/elf/mips.h
> index 46f63fe..0034884 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)
> @@ -751,6 +754,10 @@ extern void bfd_mips_elf32_swap_reginfo_out
>  
>  /* Relative offset of run time loader map, used for debugging.  */
>  #define DT_MIPS_RLD_MAP_REL    0x70000035
> +
> +/* The GOT index of the first implicitly relocated GOT entry.  */
> +#define DT_MIPS_GENERAL_GOTNO  0x70000036
> +
>  \f
>  /* Flags which may appear in a DT_MIPS_FLAGS entry.  */
>  
> 

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v4)
  2016-01-08  0:10                         ` [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v4) Faraz Shahbazker
  2016-01-21 20:02                           ` Faraz Shahbazker
@ 2016-03-06  7:14                           ` Maciej W. Rozycki
  2016-03-16 21:20                             ` Faraz Shahbazker
  2016-04-29 13:58                             ` [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v4) Maciej W. Rozycki
  1 sibling, 2 replies; 35+ messages in thread
From: Maciej W. Rozycki @ 2016-03-06  7:14 UTC (permalink / raw)
  To: Faraz Shahbazker; +Cc: binutils, Richard Sandiford

Hi Faraz,

 This is a partial review only to let you proceed as I'm heading off for 
two weeks now, for a holiday/vacation.  The pieces I have already deleted 
are fine and these which are left here uncommented may or may not require 
a further action -- I'll handle them when I'm back.  Apologies for the 
slow progress.

> Spec (attached) updated to reflect creation of IPLT/IGOT entries for symbols that 
> bind locally. Test cases in following patch.
> 
> Regards,
> Faraz Shahbazker
> 
> bfd/ChangeLog:
> 
> 	* bfd-in2.h (BFD_RELOC_MIPS_IRELATIVE): New relocation.

 This is a generated file, so please list it at the end and say: 
"Regenerate." only, i.e.:

	* bfd-in2.h: Regenerate.

You did regenerate it according to the instructions at the top rather than 
hand-editing, right?

> 	* elf32-mips.c
> 	(elf_mips_eh_howto table): Howto for BFD_RELOC_MIPS_IRELATIVE.

 Please put the object name on the same line as the file name, i.e.:

	* elf32-mips.c (elf_mips_eh_howto table): Howto for 
	BFD_RELOC_MIPS_IRELATIVE.

Likewise throughout.

> 	(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_got_info): New fields general_gotno and
> 	assigned_general_gotno.
> 	(struct mips_elf_link_hash_entry): New fields for offset in to
> 	IPLT/IGOT, flags to indicate if symbol needs IPLT/IRELOC/IGOT
> 	and if it has normal GOT-based relocations.
> 	(mips_elf_link_hash_table): New fields for size of IPLT stubs
> 	and hash-table for local IFUNC symbols.
> 	(MIPS16_P): New macro to check ASE flag.
> 	(mips16_exec_iplt_entry): Template for mips16 IPLT stub.
> 	(mips32_exec_iplt_entry): Template for mips32 IPLT stub.
> 	(mips32r6_exec_iplt_entry): Template for mips32 R6 IPLT stub.
> 	(micromips32_exec_iplt_entry): Template for micromips32 stub.
> 	(mips64_exec_iplt_entry): Template for mips64 IPLT stub.
> 	(mips64_48b_exec_iplt_entry): Template for 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 for micromips
> 	& mips16 IPLT stubs. New argument to set st_other value of stub.
> 	(mips_elf_add_la25_intro): Add argument other to the call to
> 	mips_elf_create_stub_symbol.
> 	(mips_elf_add_la25_trampoline): Add argument other to the call to
> 	mips_elf_create_stub_symbol.
> 	(mips_elf_rel_dyn_section): Moved up to avoid forward declaration.
> 	(mips_get_irel_section): New function.
> 	(mips_elf_allocate_ireloc): Likewise.
> 	(mips_elf_allocate_iplt): Likewise.
> 	(mips_elf_check_ifunc_symbols): Likewise.
> 	(mips_elf_check_symbols): Call mips_elf_check_ifunc_symbols to
> 	allocate an IPLT entry for an IFUNC symbol and set has_gnu_symbols.
> 	Skip creation of la25 stubs for IFUNCs having IPLT stubs.
> 	(mips_elf_count_got_entry): Count GOT entries for IFUNCs that do
> 	not have an IPLT stub under the general GOT region and allocate
> 	space for an IRELATIVE relocation for each.
> 	(mips_elf_got16_entry): Add argument h and pass it to
> 	mips_elf_create_local_got_entry instead of NULL.
> 	(mips_elf_create_local_got_entry): Change hash-lookup for IFUNCs
> 	to use non-NULL input BFD and a pointer to the
> 	mips_elf_link_hash_entry.  Add check for sufficient general GOT
> 	entries.  Assign local IFUNCs from general GOT entries pool.
>  	Allow early return for existing hash entry only if gotidx has
> 	been assigned. 	Allocate general GOT only if symbol does not
> 	have an IGOT entry.
> 	(mips_elf_record_local_got_symbol): New argument for pointer to
> 	mips_elf_link_hash_entry of the local symbol.  If non-NULL, this
> 	is used to set d.h in the GOT has entry instead of d.addend.
> 	(mips_use_local_got_p): Return TRUE for all IFUNC symbol definitions.

 Please limit line lengths in your ChangeLog entries to fit within 74 
columns.

 Rationale: 
<https://sourceware.org/gdb/wiki/ContributionChecklist#Properly_Formatted_GNU_ChangeLog> 
-- there's a `fill-column' setting to enforce this for GNU Emacs users at 
the bottom of bfd/ChangeLog and our other ChangeLog files in case you 
wondered whether the GDB contribution checklist applied to binutils as 
well; in any case I won't complain whenever you do comply with that 
checklist wherever applicable with your binutils submissions related to my 
area of responsibility even though other maintainers might let changes 
through regardless.

> 	(mips_elf_create_ifunc_sections): New function.
> 	(get_local_sym_hash): Likewise.
>         (mips_elf_calculate_relocation): Create hash-table for local IFUNC

 Formatting here, please use a leading tab rather than spaces (I take it 
as an oversight; if you have tools like I do to catch cases like this and 
eliminate them at the commit time, then I won't speak up on these again).

> 	symbols and condition them to be accessed through GOT.
> 	Point IFUNC symbol value to IPLT stub for symbols that need IPLT.
> 	Force global IFUNC to be allocated from local GOT region.
> 	(mips_elf_perform_relocation): New argument flag indication relocation
> 	targets IFUNC. Disable JALR to BAL optimization for IFUNC symobls.
> 	(mips_elf_create_dynamic_relocation): Emit IRELATIVE relocation
> 	instead of REL32 for local IFUNC reference. Relax assertion for

 Two spaces after a full stop please.

> 	IFUNC symbols in explicit GOT region to have dynamic relocations.
>         (_bfd_mips_elf_section_processing): Size .igot section.

 Leading tab vs spaces too.

> 	(_bfd_mips_elf_check_relocs): Check need to create IFUNC sections.
> 	If symbol is an IFUNC, don't convert it to an STT_FUNC. Relax error
> 	checking to allow local IFUNCs to be accessed via call16 reloc.
> 	Record calls to a local IFUNC symbols and pre-allocate GOT entries
> 	for call16 and got16 relocations.
> 	(_bfd_mips_elf_adjust_dynamic_symbol): Add STT_GNU_IFUNC to sanity
> 	check.
> 	(_bfd_mips_elf_always_size_sections): Allocate IPLTs for local IFUNCs.
> 	(mips_elf_lay_out_got): Offset local GOT entries to follow general
> 	GOT entries.
> 	(_bfd_mips_elf_size_dynamic_sections): Exclude IPLT and IGOT.
> 	Create dynamic tag for DT_MIPS_GENERAL_GOTNO if needed.
> 	(_bfd_mips_elf_relocate_section): Relax error checking to allow
> 	local IFUNCs to be accessed via standalone got16 reloc.  Pass
> 	gnu_ifunc_p flag when calling mips_elf_perform_relocation.
> 	(mips_elf_create_iplt): New function.
> 	(mips_elf_check_local_got_index): Likewise.
> 	(mips_elf_create_ireloc): Likewise.
> 	(_bfd_mips_elf_finish_dynamic_symbol): Create IPLT stub/IRELATIVE
> 	relocation for IFUNC symbols as necessary. Set IFUNC symbol value
> 	to the IPLT entry address for executable 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 tag - DT_MIPS_GENERAL_GOTNO.
> 	(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 tag -
> 	DT_MIPS_GENERAL_GOTNO.
> 	(_bfd_mips_post_process_headers): If ELF uses GNU IFUNCs, increase
> 	ABIVERSION to 4.
> 	* 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:
> 	(get_mips_dynamic_type): Add case for DT_MIPS_GENERAL_GOTNO.
> 	(dynamic_section_mips_val): Add case for DT_MIPS_GENERAL_GOTNO.
> 
> elfcpp/ChangeLog:
> 
> 	* elfcpp.h
> 	(enum DT): Add cases for dynamic tag DT_MIPS_GENERAL_GOTNO.
> 
> include/elf/ChangeLog:
> 
> 	* mips.h
> 	(START_RELOC_NUMBERS): Entry for R_MIPS_IRELATIVE.
> 	(DT_MIPS_GENERAL_GOTNO): New dynamic tag.
> ---
>  bfd/bfd-in2.h      |    3 +
>  bfd/elf32-mips.c   |   22 ++
>  bfd/elfxx-mips.c   | 1076 +++++++++++++++++++++++++++++++++++++++++++++++-----
>  bfd/libbfd.h       |    1 +
>  bfd/reloc.c        |    5 +
>  binutils/readelf.c |    3 +
>  elfcpp/elfcpp.h    |    2 +
>  include/elf/mips.h |    7 +
>  8 files changed, 1030 insertions(+), 89 deletions(-)
> 
> diff --git a/bfd/bfd-in2.h b/bfd/bfd-in2.h
> index fb4858c..0218164 100644
> --- a/bfd/bfd-in2.h
> +++ b/bfd/bfd-in2.h
> @@ -3062,6 +3062,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,

 Please group the reloc with MIPS rather than moxie relocations, i.e. one 
newline before it and two afterwards.

> diff --git a/bfd/elf32-mips.c b/bfd/elf32-mips.c
> index 752f386..e8f1079 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 4ece819..6f23387 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 {
> @@ -165,10 +166,14 @@ struct mips_got_info
>    unsigned int tls_assigned_gotno;
>    /* The number of local .got entries, eventually including page entries.  */
>    unsigned int local_gotno;
> +  /* The number of explicitly relocated .got entries.  */
> +  unsigned int general_gotno;
>    /* The maximum number of page entries needed.  */
>    unsigned int page_gotno;
>    /* The number of relocations needed for the GOT entries.  */
>    unsigned int relocs;
> +  /* The first unused general .got entry.  */
> +  unsigned int assigned_general_gotno;
>    /* The first unused local .got entry.  */
>    unsigned int assigned_low_gotno;
>    /* The last unused local .got entry.  */
> @@ -375,6 +380,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.  */
> +  int igot_offset;
> +
>    /* The highest GGA_* value that satisfies all references to this symbol.  */
>    unsigned int global_got_area : 2;
>  
> @@ -392,6 +403,9 @@ struct mips_elf_link_hash_entry
>       cannot possibly be made dynamic).  */
>    unsigned int has_static_relocs : 1;
>  
> +  /* True if there is a got16 or call16 relocation against this symbol.  */
> +  unsigned int has_got_relocs : 1;
> +
>    /* True if we must not create a .MIPS.stubs entry for this symbol.
>       This is set, for example, if there are relocations related to
>       taking the function's address, i.e. any but R_MIPS_CALL*16 ones.
> @@ -413,6 +427,15 @@ 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 symbol need an IPLT stub?  */
> +  unsigned int needs_igot : 1;
> +
> +  /* Does this ifunc symbol need an IRELATIVE relocation?  */
> +  unsigned int needs_ireloc : 1;
>  };
>  
>  /* MIPS ELF linker hash table.  */
> @@ -485,6 +508,9 @@ 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;
> +
>    /* The number of functions that need a lazy-binding stub.  */
>    bfd_vma lazy_stub_count;
>  
> @@ -516,6 +542,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 +830,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)
> +

 This is unreliable, objects produced with older tools won't have the flag 
set even if they contain MIPS16 code.  You may end up with a mixture of 
old MIPS16 objects built with pre-IFUNC tools and new MIPS32 objects with 
IFUNC definitions causing those old MIPS16 objects calling the IPLT.

 You shouldn't need to check this flag though, see below.

> @@ -1185,6 +1219,69 @@ 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)	*/
> +};

 No need to use both $2 and $3 here, please reencode to use $2 only.

> +
> +/* The format of 32 bit IPLT entries.  */
> +static const bfd_vma mips32_exec_iplt_entry[] =
> +{
> +  0x3c0f0000,   /* lui $15, %hi(.igot address)		*/

 Please use a tab rather than spaces to separate the comment from code.  
Likewise throughout these entries.

> +  0x8df90000,   /* lw  $25, %lo(.igot address)($15)	*/
> +  0x03200008,   /* jr  $25				*/
> +  0x00000000    /* nop					*/
> +};
> +
> +/* Format of 32 bit IPLT entries for R6. JR encoding differs.  */
> +static const bfd_vma mips32r6_exec_iplt_entry[] =
> +{
> +  0x3c0f0000,   /* lui $15, %hi(.igot address)		*/
> +  0x8df90000,   /* lw  $25, %lo(.igot address)($15)	*/
> +  0x03200009,   /* jr  $25				*/
> +  0x00000000    /* nop					*/
> +};

 Please do use a compact R6 jump encoding here, as Richard originally 
advised, i.e.:

  0xd8190000,	/* jrc $25				*/

There's no need for a trailing NOP then as jumps have no forbidden slot.

> +
> +/* The format of 32-bit micromips IPLT entries.  */
> +static const bfd_vma micromips32_exec_iplt_entry[] =
> +{
> +  0x41a30000, 	/* lui $2, %hi(.igot address)		*/
> +  0xff230000,	/* lw  $2, %lo(.igot address)($2) 	*/

 NB you've got $3 encoded here rather than $2 as the temporary.  Please 
use $15 hovever, which is standard here, as there's no specific need to be 
unusual here.  Also the load target is $25 rather than $2, but you've got 
this encoded right, so just adjust the comment.

 And please use separate halfwords for 32-bit instructions like with other 
microMIPS entries.  The use of `bfd_put_micromips_32' is attractive and we 
can consider it later on as a small optimisation, but your choice breaks 
automatic array size calculation in `mips_elf_create_ifunc_sections' by 
requiring an implicit assumption (subtracting 2).  Besides, we use such an 
arrangement for all the other microMIPS entries.

> +  0x45b9,	/* jrc $25				*/
> +};

 The compact jump is OK here, but please do retain the original NOP, for 
alignment (even though it won't be executed), as there's an inevitable 
performance penalty when a 32-bit instruction jumped to from elsewhere 
spans a cache line boundary, because in this case the processor has to 
fill two cache lines before it can even fetch the whole instruction.

 See the existing MIPS16 and microMIPS entries for examples.

> +
> +/* The format of 64-bit IPLT entries.  */
> +static const bfd_vma mips64_exec_iplt_entry[] =
> +{
> +  0x3c0f0000,	/* lui $15, %highest(.got.iplt entry)        */
> +  0x3c0e0000,	/* lui $14, %hi(.got.iplt entry)             */
> +  0x25ef0000,	/* addiu $15, $15, %higher(.got.iplt entry)  */
> +  0x000f783c,	/* dsll32 $15, $15, 0x0                      */
> +  0x01ee782d,	/* daddu $15, $15, $14                       */
> +  0xddf90000,	/* ld $25, %lo(.got.iplt entry)($15)         */
> +  0x03200008,	/* jr $25                                    */
> +  0x00000000,	/* nop                                       */
> +};
> +
> +/* The format of 64-bit IPLT entries for 48bit address.  */
> +static const bfd_vma mips64_48b_exec_iplt_entry[] =
> +{
> +  0x3c0f0000,	/* lui $15, %higher(.got.iplt entry)         */
> +  0x25ef0000,	/* addiu $15, $15, %high(.got.iplt entry)    */
> +  0x000f7c38,	/* dsll $15, $15, 16  			     */
> +  0xddf90000,	/* ld $25, %lo(.got.iplt entry)($15)         */
> +  0x03200008,	/* jr $25                                    */
> +  0x00000000,	/* nop                                       */
> +};
> +
>  \f
>  /* microMIPS 32-bit opcode helper installer.  */
>  
> @@ -1278,11 +1375,17 @@ mips_elf_link_hash_newfunc (struct bfd_hash_entry *entry,
>        ret->got_only_for_calls = TRUE;
>        ret->readonly_reloc = FALSE;
>        ret->has_static_relocs = FALSE;
> +      ret->has_got_relocs = FALSE;
>        ret->no_fn_stub = FALSE;
>        ret->need_fn_stub = FALSE;
>        ret->has_nonpic_branches = FALSE;
>        ret->needs_lazy_stub = FALSE;
>        ret->use_plt_entry = FALSE;
> +      ret->needs_iplt = FALSE;
> +      ret->needs_igot = FALSE;
> +      ret->needs_ireloc = FALSE;
> +      ret->iplt_offset = -1;
> +      ret->igot_offset = -1;
>      }
>  
>    return (struct bfd_hash_entry *) ret;
> @@ -1576,13 +1679,14 @@ static bfd_boolean
>  mips_elf_create_stub_symbol (struct bfd_link_info *info,
>  			     struct mips_elf_link_hash_entry *h,
>  			     const char *prefix, asection *s, bfd_vma value,
> -			     bfd_vma size)
> +			     bfd_vma size, unsigned int other)
>  {
>    struct bfd_link_hash_entry *bh;
>    struct elf_link_hash_entry *elfh;
>    const char *name;
>  
> -  if (ELF_ST_IS_MICROMIPS (h->root.other))
> +  if (ELF_ST_IS_MICROMIPS (h->root.other)
> +      || (ELF_ST_IS_MIPS16 (h->root.other) && h->root.type == STT_GNU_IFUNC))
>      value |= 1;

 Why do you need to check for STT_GNU_IFUNC, is checking ELF_ST_IS_MIPS16 
not enough here?  NB existing users won't call this function because 
MIPS16 functions never need LA25 stubs.

> @@ -1598,6 +1702,8 @@ 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;
> +  elfh->other = other;
> +

 Why do you need to set `other' while you have already set the ISA bit in 
`value'?  Our rule for handling is to set either rather than both at a 
time, why do you need an exception here?

>    return TRUE;
>  }
>  
> @@ -1863,7 +1969,7 @@ mips_elf_add_la25_intro (struct mips_elf_la25_stub *stub,
>      s->size = (1 << align) - 8;
>  
>    /* Create a symbol for the stub.  */
> -  mips_elf_create_stub_symbol (info, stub->h, ".pic.", s, s->size, 8);
> +  mips_elf_create_stub_symbol (info, stub->h, ".pic.", s, s->size, 8, 0);
>    stub->stub_section = s;
>    stub->offset = s->size;
>  
> @@ -1900,7 +2006,7 @@ mips_elf_add_la25_trampoline (struct mips_elf_la25_stub *stub,
>      }
>  
>    /* Create a symbol for the stub.  */
> -  mips_elf_create_stub_symbol (info, stub->h, ".pic.", s, s->size, 16);
> +  mips_elf_create_stub_symbol (info, stub->h, ".pic.", s, s->size, 16, 0);
>    stub->stub_section = s;
>    stub->offset = s->size;
>  
> @@ -1963,6 +2069,156 @@ 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 one 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 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;
> +}
> +
> +/* Reserve space in the rel.iplt section for an IRELATIVE relocation.  */
> +
> +static bfd_boolean
> +mips_elf_allocate_ireloc (struct bfd_link_info *info,
> +			  struct mips_elf_link_hash_table *mhtab,
> +			  struct mips_elf_link_hash_entry *mh)
> +{
> +  asection *srel;
> +  bfd *dynobj;
> +
> +  srel = mips_get_irel_section (info, mhtab);
> +  dynobj = elf_hash_table (info)->dynobj;
> +  if (srel != mhtab->root.irelplt && srel->size == 0)
> +    {
> +      /* Make room for a null element.  */
> +      srel->size += MIPS_ELF_REL_SIZE (dynobj);
> +      ++srel->reloc_count;
> +    }
> +  srel->size += MIPS_ELF_REL_SIZE (dynobj);
> +  mh->needs_ireloc = TRUE;
> +
> +  return TRUE;
> +}
> +
> +/* Reserve space in the iplt and igot tables for an ifunc entry
> +   and allocate space for an IRELATIVE relocation.  */
> +
> +static bfd_boolean
> +mips_elf_allocate_iplt (struct bfd_link_info *info,
> +			struct mips_elf_link_hash_table *mhtab,
> +			struct mips_elf_link_hash_entry *mh)
> +{
> +  asection *s;
> +  bfd *abfd = info->output_bfd;
> +  unsigned int other = 0;
> +
> +  BFD_ASSERT (!mh->needs_iplt);
> +  BFD_ASSERT (mhtab->root.iplt != NULL);
> +
> +  s = mhtab->root.iplt;
> +  if (ELF_ST_IS_MIPS16 (mh->root.other))
> +    other = STO_MIPS16;
> +
> +  mh->iplt_offset = s->size;
> +  mips_elf_create_stub_symbol (info, mh, ".iplt.", mhtab->root.iplt,
> +			       s->size, mhtab->iplt_entry_size, other);
> +  s->size += mhtab->iplt_entry_size;
> +
> +  BFD_ASSERT (mhtab->root.igotplt != NULL);
> +
> +  /* Only create IGOT entry if there are no GOT relocations, or when
> +     there are non-CALL references to the symbol.  In the latter case,
> +     existing GOT entry must point to IPLT, so an IGOT entry is needed
> +     to catch the result of the IRELATIVE relocation resolution.  */
> +  if (!mh->has_got_relocs || mh->root.pointer_equality_needed)
> +    {
> +      mh->igot_offset = mhtab->root.igotplt->size;
> +      mhtab->root.igotplt->size += MIPS_ELF_GOT_SIZE (abfd);
> +      mh->needs_igot = TRUE;
> +    }
> +
> +  mh->needs_iplt = TRUE;
> +
> +  /* IRELATIVE fixup will be needed for each local IFUNC.  */
> +  if (!mips_elf_allocate_ireloc (info, mips_elf_hash_table (info), mh))
> +    return FALSE;
> +
> +  return TRUE;
> +}
> +
> +/* hash_traverse callback that is called before sizing sections.
> +   DATA points to a mips_htab_traverse_info structure.  */
> +
> +static bfd_boolean
> +mips_elf_check_ifunc_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 (h
> +      && !h->needs_iplt
> +      && h->root.type == STT_GNU_IFUNC
> +      && h->root.def_regular)
> +    {
> +      struct bfd_link_info *info = hti->info;
> +      elf_tdata (info->output_bfd)->has_gnu_symbols |= elf_gnu_symbol_ifunc;
> +
> +      /* For global symbols, .iplt entry is needed only for all non-shared-
> +	 objects.  For local symbols, it is needed only if the symbol has
> +	 static relocations.  */
> +      if (((h->root.forced_local && h->has_static_relocs)
> +	   || (!h->root.forced_local && !bfd_link_pic (info)))
> +	  && !mips_elf_allocate_iplt (info, mips_elf_hash_table (info), h))
> +	{
> +	  hti->error = TRUE;
> +	  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.  */
>  
> @@ -1975,6 +2231,12 @@ mips_elf_check_symbols (struct mips_elf_link_hash_entry *h, void *data)
>    if (!bfd_link_relocatable (hti->info))
>      mips_elf_check_mips16_stubs (hti->info, h);
>  
> +  /* Create stubs and relocations for IFUNC symbols.  */
> +  if (h
> +      && h->root.type == STT_GNU_IFUNC
> +      && !mips_elf_check_ifunc_symbols ((void **)&h, hti))
> +    return FALSE;
> +
>    if (mips_elf_local_pic_function_p (h))
>      {
>        /* PR 12845: If H is in a section that has been garbage
> @@ -1986,13 +2248,15 @@ mips_elf_check_symbols (struct mips_elf_link_hash_entry *h, void *data)
>  	 If we're creating a non-PIC relocatable object, mark H as
>  	 being PIC.  If we're creating a non-relocatable object with
>  	 non-PIC branches and jumps to H, make sure that H has an la25
> -	 stub.  */
> +	 stub.  IFUNCs with IPLT stubs don't need an la25 stub.  */
>        if (bfd_link_relocatable (hti->info))
>  	{
>  	  if (!PIC_OBJECT_P (hti->output_bfd))
>  	    h->root.other = ELF_ST_SET_MIPS_PIC (h->root.other);
>  	}
> -      else if (h->has_nonpic_branches && !mips_elf_add_la25_stub (hti->info, h))
> +      else if (h->has_nonpic_branches
> +	       && (h->root.type != STT_GNU_IFUNC || !h->needs_iplt)
> +	       && !mips_elf_add_la25_stub (hti->info, h))
>  	{
>  	  hti->error = TRUE;
>  	  return FALSE;
> @@ -3130,37 +3394,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
> @@ -3254,8 +3487,21 @@ mips_elf_count_got_entry (struct bfd_link_info *info,
>  					entry->symndx < 0
>  					? &entry->d.h->root : NULL);
>      }
> -  else if (entry->symndx >= 0 || entry->d.h->global_got_area == GGA_NONE)
> -    g->local_gotno += 1;
> +  else if (entry->symndx >= 0 || (entry->d.h->global_got_area == GGA_NONE))
> +    {
> +      /* Count IFUNCs as general GOT entries with explicit relocations.  */
> +      if (entry->symndx < 0
> +	  && entry->d.h->root.type == STT_GNU_IFUNC
> +	  && entry->d.h->root.def_regular
> +	  && !entry->d.h->needs_igot)
> +	{
> +	  g->general_gotno += 1;
> +	  mips_elf_allocate_ireloc (info, mips_elf_hash_table (info),
> +				    entry->d.h);
> +	}
> +      else
> +	g->local_gotno += 1;
> +    }
>    else
>      g->global_gotno += 1;
>  }
> @@ -3587,7 +3833,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;
>  
> @@ -3602,7 +3849,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
> @@ -3691,18 +3938,28 @@ mips_elf_create_local_got_entry (bfd *abfd, struct bfd_link_info *info,
>        return entry;
>      }
>  
> -  lookup.abfd = NULL;
>    lookup.symndx = -1;
> -  lookup.d.address = value;
> +  if (h && h->root.type == STT_GNU_IFUNC)
> +    {
> +      lookup.abfd = ibfd;
> +      lookup.d.h = h;
> +    }
> +  else
> +    {
> +      lookup.abfd = NULL;
> +      lookup.d.address = value;
> +    }
> +
>    loc = htab_find_slot (g->got_entries, &lookup, INSERT);
>    if (!loc)
>      return NULL;
>  
>    entry = (struct mips_got_entry *) *loc;
> -  if (entry)
> +  if (entry && entry->gotidx >= 0)
>      return entry;
>  
> -  if (g->assigned_low_gotno > g->assigned_high_gotno)
> +  if (g->assigned_low_gotno > g->assigned_high_gotno
> +      || g->assigned_general_gotno > g->local_gotno)
>      {
>        /* We didn't allocate enough space in the GOT.  */
>        (*_bfd_error_handler)
> @@ -3715,7 +3972,15 @@ mips_elf_create_local_got_entry (bfd *abfd, struct bfd_link_info *info,
>    if (!entry)
>      return NULL;
>  
> -  if (got16_reloc_p (r_type)
> +  if (h && h->needs_ireloc && !h->needs_igot)
> +    /* Allocate IFUNC slots in the general GOT region since they
> +       will need explicit IRELATIVE relocations.  */
> +    {
> +      lookup.gotidx = MIPS_ELF_GOT_SIZE (abfd) * g->assigned_general_gotno++;
> +      if (h->needs_iplt)
> +	h->igot_offset = lookup.gotidx;
> +    }
> +  else if (got16_reloc_p (r_type)
>        || call16_reloc_p (r_type)
>        || got_page_reloc_p (r_type)
>        || got_disp_reloc_p (r_type))
> @@ -3958,7 +4223,8 @@ mips_elf_record_global_got_symbol (struct elf_link_hash_entry *h,
>  
>  static bfd_boolean
>  mips_elf_record_local_got_symbol (bfd *abfd, long symndx, bfd_vma addend,
> -				  struct bfd_link_info *info, int r_type)
> +				  struct bfd_link_info *info, int r_type,
> +				  struct mips_elf_link_hash_entry *h)
>  {
>    struct mips_elf_link_hash_table *htab;
>    struct mips_got_info *g;
> @@ -3972,7 +4238,10 @@ mips_elf_record_local_got_symbol (bfd *abfd, long symndx, bfd_vma addend,
>  
>    entry.abfd = abfd;
>    entry.symndx = symndx;
> -  entry.d.addend = addend;
> +  if (h)
> +    entry.d.h = h;
> +  else
> +    entry.d.addend = addend;
>    entry.tls_type = mips_elf_reloc_tls_type (r_type);
>    return mips_elf_record_got_entry (info, abfd, &entry);
>  }
> @@ -4389,7 +4658,11 @@ mips_use_local_got_p (struct bfd_link_info *info,
>       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)
> +
> +  /* Both global & local IFUNC symbols actually use the explicitly relocated
> +     GOT region, but we don't distinguish it from the local GOT just yet.  */
> +  if (h->root.dynindx == -1
> +      || (h->root.type == STT_GNU_IFUNC && h->root.def_regular))
>      return TRUE;
>  
>    /* Symbols that bind locally can (and in the case of forced-local
> @@ -5075,6 +5348,65 @@ 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;
> +
> +  if (!bfd_link_pic (info))
> +    {
> +      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))
> +	htab->iplt_entry_size = (4 * ARRAY_SIZE (micromips32_exec_iplt_entry)
> +				 - 2);

 You don't need to subtract 2 here if using 16-bit words in 
`micromips32_exec_iplt_entry'.

 Also you cannot fix IPLT entries based on global MIPS16/microMIPS object 
flags as you may have to create mixed IPLT entries in a single output 
binary if mixed regular/compressed code is present in input.  You may even 
have to create two IPLT entries for a single function symbol.  Otherwise 
you'll have issues with tail calls and short delay slots and even if you 
escape them you may cause unnecessary mode switches that are costly.  So 
you need to decide based on the referring reloc type.

 Fortunately all the necessary details will have already been collected in 
case ordinary PLT is to be produced.  So you can just reuse that stuff for 
your purpose as well.

> +      else
> +	htab->iplt_entry_size = 4 * (ARRAY_SIZE (mips32_exec_iplt_entry)
> +				     + (LOAD_INTERLOCKS_P (dynobj) ? 0 : 1));
> +
> +      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;
> +
> +      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
> @@ -5191,6 +5523,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 = abfd->sections;
> +  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);
> +
> +  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;
> +      ret->igot_offset = -1;
> +      ret->root.other = isym->st_other;
> +      ret->got_only_for_calls = TRUE;
> +
> +      *slot = ret;
> +    }
> +
> +  return ret;
> +}
>  \f
>  /* Calculate the value produced by the RELOCATION (which comes from
>     the INPUT_BFD).  The ADDEND is the addend to use for this
> @@ -5241,6 +5641,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
> @@ -5321,6 +5723,13 @@ 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
>      {
> @@ -5545,6 +5954,21 @@ 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
> +	   && (h->needs_igot
> +	       || (!call16_reloc_p (r_type)
> +		   && !got16_reloc_p (r_type))))
> +    {
> +      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)
> @@ -5640,6 +6064,16 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
>  	      BFD_ASSERT (h->root.needs_plt);
>  	      g = mips_elf_gotplt_index (info, &h->root);
>  	    }
> +	  /* IFUNCs use the explicitly-relocated GOT region, however we don't
> +	     distinguish it from the local GOT at this stage.  */
> +	  else if (h && h->needs_ireloc && !h->needs_igot)
> +	    {
> +	      g = mips_elf_local_got_index (abfd, input_bfd, info,
> +					    symbol + addend, r_symndx,
> +					    h, r_type);
> +	      if (g == MINUS_ONE)
> +		return bfd_reloc_outofrange;
> +	    }
>  	  else
>  	    {
>  	      BFD_ASSERT (addend == 0);
> @@ -5931,8 +6365,11 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
>  	 R_MIPS*_GOT16; every relocation evaluates to "G".  */
>        if (!htab->is_vxworks && local_p)
>  	{
> +	  /* Local IFUNC symbols must be accessed through GOT, similar to
> +	     global symbols, to allow for indirection.  */
>  	  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
> @@ -6209,7 +6646,8 @@ mips_elf_perform_relocation (struct bfd_link_info *info,
>  			     const Elf_Internal_Rela *relocation,
>  			     bfd_vma value, bfd *input_bfd,
>  			     asection *input_section, bfd_byte *contents,
> -			     bfd_boolean cross_mode_jump_p)
> +			     bfd_boolean cross_mode_jump_p,
> +			     bfd_boolean ifunc_p)
>  {
>    bfd_vma x;
>    bfd_byte *location;
> @@ -6279,6 +6717,7 @@ mips_elf_perform_relocation (struct bfd_link_info *info,
>  	   && r_type == R_MIPS_26
>  	   && (x >> 26) == 0x3)		/* jal addr */
>  	  || (JALR_TO_BAL_P (input_bfd)
> +	      && !ifunc_p
>  	      && r_type == R_MIPS_JALR
>  	      && x == 0x0320f809)	/* jalr t9 */
>  	  || (JR_TO_B_P (input_bfd)
> @@ -6377,7 +6816,8 @@ mips_elf_create_dynamic_relocation (bfd *output_bfd,
>       in the relocation.  */
>    if (h != NULL && ! SYMBOL_REFERENCES_LOCAL (info, &h->root))
>      {
> -      BFD_ASSERT (htab->is_vxworks || h->global_got_area != GGA_NONE);
> +      BFD_ASSERT (htab->is_vxworks || h->global_got_area != GGA_NONE
> +		  || h->root.type == STT_GNU_IFUNC);
>        indx = h->root.dynindx;
>        if (SGI_COMPAT (output_bfd))
>  	defined_p = h->root.def_regular;
> @@ -6436,31 +6876,44 @@ mips_elf_create_dynamic_relocation (bfd *output_bfd,
>    if (defined_p && r_type != R_MIPS_REL32)
>      *addendp += symbol;
>  
> -  if (htab->is_vxworks)
> -    /* VxWorks uses non-relative relocations for this.  */
> -    outrel[0].r_info = ELF32_R_INFO (indx, R_MIPS_32);
> +  /* Morph REL32 in to IRELATIVE fix-up for local IFUNC reference.  */
> +  if (h
> +      && h->root.type == STT_GNU_IFUNC
> +      && SYMBOL_REFERENCES_LOCAL (info, &h->root))
> +    {
> +      outrel[0].r_info = ELF_R_INFO (output_bfd, 0,
> +				     R_MIPS_IRELATIVE);
> +      outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
> +				     R_MIPS_NONE);
> +    }
>    else
> -    /* The relocation is always an REL32 relocation because we don't
> -       know where the shared library will wind up at load-time.  */
> -    outrel[0].r_info = ELF_R_INFO (output_bfd, (unsigned long) indx,
> -				   R_MIPS_REL32);
> -
> -  /* For strict adherence to the ABI specification, we should
> -     generate a R_MIPS_64 relocation record by itself before the
> -     _REL32/_64 record as well, such that the addend is read in as
> -     a 64-bit value (REL32 is a 32-bit relocation, after all).
> -     However, since none of the existing ELF64 MIPS dynamic
> -     loaders seems to care, we don't waste space with these
> -     artificial relocations.  If this turns out to not be true,
> -     mips_elf_allocate_dynamic_relocation() should be tweaked so
> -     as to make room for a pair of dynamic relocations per
> -     invocation if ABI_64_P, and here we should generate an
> -     additional relocation record with R_MIPS_64 by itself for a
> -     NULL symbol before this relocation record.  */
> -  outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
> -				 ABI_64_P (output_bfd)
> -				 ? R_MIPS_64
> -				 : R_MIPS_NONE);
> +    {
> +      if (htab->is_vxworks)
> +	/* VxWorks uses non-relative relocations for this.  */
> +	outrel[0].r_info = ELF32_R_INFO (indx, R_MIPS_32);
> +      else
> +	/* The relocation is always an REL32 relocation because we don't
> +	   know where the shared library will wind up at load-time.  */
> +	outrel[0].r_info = ELF_R_INFO (output_bfd, (unsigned long) indx,
> +				       R_MIPS_REL32);
> +
> +      /* For strict adherence to the ABI specification, we should
> +	 generate a R_MIPS_64 relocation record by itself before the
> +	 _REL32/_64 record as well, such that the addend is read in as
> +	 a 64-bit value (REL32 is a 32-bit relocation, after all).
> +	 However, since none of the existing ELF64 MIPS dynamic
> +	 loaders seems to care, we don't waste space with these
> +	 artificial relocations.  If this turns out to not be true,
> +	 mips_elf_allocate_dynamic_relocation() should be tweaked so
> +	 as to make room for a pair of dynamic relocations per
> +	 invocation if ABI_64_P, and here we should generate an
> +	 additional relocation record with R_MIPS_64 by itself for a
> +	 NULL symbol before this relocation record.  */
> +      outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
> +				     ABI_64_P (output_bfd)
> +				     ? R_MIPS_64
> +				     : R_MIPS_NONE);
> +    }
>    outrel[2].r_info = ELF_R_INFO (output_bfd, 0, R_MIPS_NONE);
>  
>    /* Adjust the output offset of the relocation to reference the
> @@ -7007,6 +7460,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;
> @@ -7920,6 +8375,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.irelplt && !mips_elf_create_ifunc_sections (info))
> +    return FALSE;
> +
>    /* Check for the mips16 stub sections.  */
>  
>    name = bfd_get_section_name (abfd, sec);
> @@ -8178,6 +8640,7 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
>        unsigned long r_symndx;
>        unsigned int r_type;
>        struct elf_link_hash_entry *h;
> +      struct mips_elf_link_hash_entry *ih = NULL;
>        bfd_boolean can_make_dynamic_p;
>        bfd_boolean call_reloc_p;
>        bfd_boolean constrain_symbol_p;
> @@ -8186,7 +8649,23 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *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;
> +
> +	  /* Relocation is for local STT_GNU_IFUNC symbol.  */
> +	  if (isym->st_info == STT_GNU_IFUNC)
> +	    {
> +	      /* Ensure that we have a hash entry for this symbol.  */
> +	      if ((ih = 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)
> @@ -8370,6 +8849,13 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
>  		info->flags |= DF_TEXTREL;
>  	    }
>  	}
> +      else if (ih)
> +	{
> +	  if (!bfd_link_pic (info) && !can_make_dynamic_p)
> +	    ih->has_static_relocs = 1;
> +	  if (!call_reloc_p)
> +	    ih->root.pointer_equality_needed = 1;
> +	}
>        else if (call_lo16_reloc_p (r_type)
>  	       || got_lo16_reloc_p (r_type)
>  	       || got_disp_reloc_p (r_type)
> @@ -8384,7 +8870,8 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
>  	     R_MIPS_CALL_HI16 because these are always followed by an
>  	     R_MIPS_GOT_LO16 or R_MIPS_CALL_LO16.  */
>  	  if (!mips_elf_record_local_got_symbol (abfd, r_symndx,
> -						 rel->r_addend, info, r_type))
> +						 rel->r_addend, info,
> +						 r_type, NULL))
>  	    return FALSE;
>  	}
>  
> @@ -8398,7 +8885,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 && ih == NULL)
>  	    {
>  	      (*_bfd_error_handler)
>  		(_("%B: CALL16 reloc at 0x%lx not against global symbol"),
> @@ -8406,6 +8894,10 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
>  	      bfd_set_error (bfd_error_bad_value);
>  	      return FALSE;
>  	    }
> +	  if (h && h->type == STT_GNU_IFUNC)
> +	    ((struct mips_elf_link_hash_entry *)h)->has_got_relocs = TRUE;
> +	  else if (ih)
> +	    ih->has_got_relocs = TRUE;
>  	  /* Fall through.  */
>  
>  	case R_MIPS_CALL_HI16:
> @@ -8423,10 +8915,17 @@ _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;
>  	    }
> +	  else
> +	    if (ih &&
> +		!mips_elf_record_local_got_symbol (abfd, -1, rel->r_addend,
> +						   info, r_type, ih))
> +	      return FALSE;
>  	  break;
>  
>  	case R_MIPS_GOT_PAGE:
> @@ -8459,9 +8958,18 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
>  		}
>  	      else
>  		addend = rel->r_addend;
> -	      if (!mips_elf_record_got_page_ref (info, abfd, r_symndx,
> -						 h, addend))
> +	      if (ih &&
> +		  !mips_elf_record_local_got_symbol (abfd, -1, rel->r_addend,
> +						     info, r_type, ih))
>  		return FALSE;
> +	      else if (!mips_elf_record_got_page_ref (info, abfd, r_symndx,
> +						   h, addend))
> +		  return FALSE;
> +
> +	      if (h && h->type == STT_GNU_IFUNC)
> +		((struct mips_elf_link_hash_entry *)h)->has_got_relocs = TRUE;
> +	      else if (ih)
> +		ih->has_got_relocs = TRUE;
>  
>  	      if (h)
>  		{
> @@ -8518,7 +9026,7 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
>  	    {
>  	      if (!mips_elf_record_local_got_symbol (abfd, r_symndx,
>  						     rel->r_addend,
> -						     info, r_type))
> +						     info, r_type, NULL))
>  		return FALSE;
>  	    }
>  	  break;
> @@ -8964,6 +9472,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
> @@ -9242,6 +9751,10 @@ _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_ifunc_symbols, &hti);
> +
>    if (hti.error)
>      return FALSE;
>  
> @@ -9289,6 +9802,10 @@ mips_elf_lay_out_got (bfd *output_bfd, struct bfd_link_info *info)
>    if (!mips_elf_resolve_final_got_entries (info, g))
>      return FALSE;
>  
> +  g->assigned_general_gotno = htab->reserved_gotno;
> +  g->local_gotno += g->general_gotno;
> +  g->assigned_low_gotno += g->general_gotno;
> +
>    /* Calculate the total loadable size of the output.  That
>       will give us the maximum number of GOT_PAGE entries
>       required.  */
> @@ -9730,6 +10247,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)
>  	{
> @@ -9875,6 +10394,13 @@ _bfd_mips_elf_size_dynamic_sections (bfd *output_bfd,
>  	  if (! MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_PLTGOT, 0))
>  	    return FALSE;
>  	}
> +
> +      if (elf_tdata (output_bfd)->has_gnu_symbols & elf_gnu_symbol_ifunc)
> +	{
> +	  if (! MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_GENERAL_GOTNO, 0))
> +	    return FALSE;
> +	}
> +
>        if (htab->is_vxworks
>  	  && !elf_vxworks_add_dynamic_entries (output_bfd, info))
>  	return FALSE;
> @@ -10006,6 +10532,8 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
>        Elf_Internal_Shdr *symtab_hdr;
>        struct elf_link_hash_entry *h;
>        bfd_boolean rel_reloc;
> +      bfd_boolean local_gnu_ifunc_p = FALSE;
> +      bfd_boolean gnu_ifunc_p = FALSE;
>  
>        rel_reloc = (NEWABI_P (input_bfd)
>  		   && mips_elf_rel_relocation_p (input_bfd, input_section,
> @@ -10019,6 +10547,23 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
>  	{
>  	  sec = local_sections[r_symndx];
>  	  h = NULL;
> +
> +	  Elf_Internal_Sym *isym;
> +	  struct mips_elf_link_hash_table *htab;
> +	  htab = mips_elf_hash_table (info);
> +	  isym = bfd_sym_from_r_symndx (&htab->sym_cache, input_bfd, r_symndx);
> +
> +	  if (isym == NULL)
> +	    return FALSE;
> +
> +	  /* Relocation is for local STT_GNU_IFUNC symbol.  */
> +	  if (isym->st_info == STT_GNU_IFUNC)
> +	    {
> +	      /* Ensure that we have a hash entry for this symbol.  */
> +	      if (get_local_sym_hash (htab, input_bfd, rel) == NULL)
> +		return FALSE;
> +	      local_gnu_ifunc_p = TRUE;
> +	    }
>  	}
>        else
>  	{
> @@ -10046,6 +10591,8 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
>  	  continue;
>  	}
>  
> +      gnu_ifunc_p = (local_gnu_ifunc_p || (h && h->type == STT_GNU_IFUNC));
> +
>        if (r_type == R_MIPS_64 && ! NEWABI_P (input_bfd))
>  	{
>  	  /* Some 32-bit code uses R_MIPS_64.  In particular, people use
> @@ -10077,7 +10624,8 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
>  	      if (hi16_reloc_p (r_type)
>  		  || (got16_reloc_p (r_type)
>  		      && mips_elf_local_relocation_p (input_bfd, rel,
> -						      local_sections)))
> +						      local_sections)
> +		      && !local_gnu_ifunc_p))
>  		{
>  		  if (!mips_elf_add_lo16_rel_addend (input_bfd, rel, relend,
>  						     contents, &addend))
> @@ -10168,7 +10716,7 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
>  
>  	      if (! mips_elf_perform_relocation (info, howto, rel, addend,
>  						 input_bfd, input_section,
> -						 contents, FALSE))
> +						 contents, FALSE, gnu_ifunc_p))
>  		return FALSE;
>  	    }
>  
> @@ -10322,7 +10870,8 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
>        /* Actually perform the relocation.  */
>        if (! mips_elf_perform_relocation (info, howto, rel, value,
>  					 input_bfd, input_section,
> -					 contents, cross_mode_jump_p))
> +					 contents, cross_mode_jump_p,
> +					 gnu_ifunc_p))
>  	return FALSE;
>      }
>  
> @@ -10467,6 +11016,242 @@ 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,
> +		      bfd_vma igotplt_address)
> +{
> +  bfd_byte *loc;
> +  const bfd_vma *iplt_entry;
> +  bfd_vma high = mips_elf_high (igotplt_address);
> +  bfd_vma low = igotplt_address & 0xffff;
> +
> +  /* 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 (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;
> +
> +      if (highest)
> +	{
> +	  /* Full 64-bit address space.  */
> +	  bfd_put_32 (output_bfd, iplt_entry[0] | highest, loc);
> +	  bfd_put_32 (output_bfd, iplt_entry[1] | high, loc + 4);
> +	  bfd_put_32 (output_bfd, iplt_entry[2] | higher, loc + 8);
> +	  bfd_put_32 (output_bfd, iplt_entry[3], 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 (higher)
> +	{
> +	  /* 48-bit address space.  */
> +	  iplt_entry = mips64_48b_exec_iplt_entry;
> +	  bfd_put_32 (output_bfd, iplt_entry[0] | higher, loc);
> +	  bfd_put_32 (output_bfd, iplt_entry[1] | high, loc + 4);
> +	  bfd_put_32 (output_bfd, iplt_entry[2], loc + 8);
> +	  bfd_put_32 (output_bfd, iplt_entry[3] | low, loc + 12);
> +	  bfd_put_32 (output_bfd, iplt_entry[4], loc + 16);
> +	  bfd_put_32 (output_bfd, iplt_entry[5], loc + 20);
> +	}
> +      else
> +	{
> +	  /* 32-bit address space.  */
> +	  bfd_put_32 (output_bfd, iplt_entry[0] | high, loc);
> +	  bfd_put_32 (output_bfd, iplt_entry[5] | low, loc + 4);
> +	  bfd_put_32 (output_bfd, iplt_entry[6], loc + 8);
> +	  bfd_put_32 (output_bfd, iplt_entry[7], loc + 12);
> +	}
> +    }
> +  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
> +    {
> +      if (MIPSR6_P (output_bfd))
> +	iplt_entry = mips32r6_exec_iplt_entry;
> +      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;
> +}
> +
> +/* Find local GOT index for VALUE.  Return -1 if no GOT slot is found.  */
> +
> +static bfd_vma
> +mips_elf_check_local_got_index (bfd *abfd, struct bfd_link_info *info,
> +				struct mips_elf_link_hash_entry *h)
> +{
> +  struct mips_got_entry lookup, *entry;
> +  void **loc;
> +  struct mips_got_info *g;
> +  struct mips_elf_link_hash_table *htab;
> +
> +  htab = mips_elf_hash_table (info);
> +  BFD_ASSERT (htab != NULL);
> +
> +  g = mips_elf_bfd_got (abfd, FALSE);
> +
> +  /* Check for existing local GOT entry.  */
> +  if (g != NULL)
> +    {
> +      lookup.abfd = abfd;
> +      lookup.symndx = -1;
> +      lookup.d.h = h;
> +      lookup.tls_type = GOT_TLS_NONE;
> +      loc = htab_find_slot (g->got_entries, &lookup, NO_INSERT);
> +    }
> +  else
> +    return -1;
> +
> +  if (loc && *loc)
> +    {
> +      entry = (struct mips_got_entry *) *loc;
> +      return entry->gotidx;
> +    }
> +  else
> +    return -1;
> +}
> +
> +/* 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_vma igotplt_address = 0;
> +  int igot_offset = -1;
> +  asection *gotsect, *relsect;
> +  bfd_vma value = sym->st_value;
> +
> +  if (MIPS16_P (output_bfd) || MICROMIPS_P (output_bfd))
> +    value |= 1;
> +
> +  if (!hmips->needs_igot)
> +    {
> +      gotsect = htab->sgot;
> +      /* Check if IFUNC symbol already has an assigned GOT slot.  */
> +      igot_offset = mips_elf_check_local_got_index (output_bfd, info, hmips);
> +    }
> +  else
> +    {
> +      bfd_byte *loc;
> +      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, value, loc);
> +      else
> +	bfd_put_32 (output_bfd, value, loc);
> +    }
> +
> +  igotplt_address = (gotsect->output_section->vma + gotsect->output_offset
> +		     + igot_offset);
> +
> +  relsect = mips_get_irel_section (info, htab);
> +
> +  if (igot_offset >= 0)
> +    {
> +      if (hmips->needs_iplt && relsect->contents == NULL)
> +	{
> +	  /* Allocate memory for the relocation section contents.  */
> +	  relsect->contents = bfd_zalloc (dynobj, relsect->size);
> +	  if (relsect->contents == NULL)
> +	    return FALSE;
> +	}
> +
> +      if (hmips->needs_iplt || SYMBOL_REFERENCES_LOCAL (info, &hmips->root))
> +	/* Emit an IRELATIVE relocation against the [I]GOT entry.  */
> +	mips_elf_output_dynamic_relocation (output_bfd, relsect,
> +					    relsect->reloc_count++, 0,
> +					    R_MIPS_IRELATIVE, igotplt_address);
> +      else
> +	{
> +	  /* Emit an R_MIPS_REL32 relocation against global GOT entry for
> +	     a preemptible symbol.  */
> +	  asection *sec = hmips->root.root.u.def.section;
> +	  Elf_Internal_Rela rel[3];
> +
> +	  memset (rel, 0, sizeof (rel));
> +	  rel[0].r_info = ELF_R_INFO (output_bfd, 0, R_MIPS_REL32);
> +	  rel[0].r_offset = rel[1].r_offset = rel[2].r_offset = igot_offset;
> +
> +	  mips_elf_create_dynamic_relocation (output_bfd, info, rel, hmips,
> +					      sec, value, NULL,
> +					      gotsect);
> +	}
> +    }
> +  /* If necessary, generate the corresponding .iplt entry.  */
> +  if (hmips->needs_iplt
> +      && !mips_elf_create_iplt (output_bfd, htab, hmips, igotplt_address))
> +    return FALSE;
> +
> +  return TRUE;
> +}
> +
>  /* Finish up dynamic symbol handling.  We set the contents of various
>     dynamic sections here.  */
>  
> @@ -10794,6 +11579,19 @@ _bfd_mips_elf_finish_dynamic_symbol (bfd *output_bfd,
>        sym->st_other = other;
>      }
>  
> +  if (hmips->root.type == STT_GNU_IFUNC)
> +    {
> +      if (hmips->needs_ireloc
> +	  && !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.  */
> @@ -10957,9 +11755,41 @@ _bfd_mips_elf_finish_dynamic_symbol (bfd *output_bfd,
>        sym->st_other -= STO_MICROMIPS;
>      }
>  
> +  if (hmips->needs_iplt)
> +    {
> +      /* 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);
> +  isym.st_other = h->root.other;
> +  if (ELF_ST_IS_COMPRESSED (isym.st_other))
> +    isym.st_value |= 1;
> +
> +  return _bfd_mips_elf_finish_dynamic_symbol (info->output_bfd, info,
> +					      &h->root, &isym);
> +}
> +
>  /* Likewise, for VxWorks.  */
>  
>  bfd_boolean
> @@ -11345,6 +12175,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;
> @@ -11515,6 +12352,10 @@ _bfd_mips_elf_finish_dynamic_sections (bfd *output_bfd,
>  	      dyn.d_un.d_ptr = s->vma;
>  	      break;
>  
> +	    case DT_MIPS_GENERAL_GOTNO:
> +	      dyn.d_un.d_val = htab->reserved_gotno + gg->general_gotno;
> +	      break;
> +
>  	    case DT_RELASZ:
>  	      BFD_ASSERT (htab->is_vxworks);
>  	      /* The count does not include the JUMP_SLOT relocations.  */
> @@ -13849,6 +14690,48 @@ _bfd_mips_elf_relax_section (bfd *abfd, asection *sec,
>    return FALSE;
>  }
>  \f
> +/* 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 *
> @@ -13872,6 +14755,16 @@ _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;
>  }
>  
> @@ -15509,7 +16402,9 @@ _bfd_mips_elf_get_target_dtag (bfd_vma dtag)
>        return "DT_MIPS_PLTGOT";
>      case DT_MIPS_RWPLT:
>        return "DT_MIPS_RWPLT";
> -    }
> +    case DT_MIPS_GENERAL_GOTNO:
> +      return "DT_MIPS_GENERAL_GOTNO";
> +   }
>  }
>  
>  /* Return the meaning of Tag_GNU_MIPS_ABI_FP value FP, or null if
> @@ -16123,7 +17018,7 @@ _bfd_mips_elf_get_synthetic_symtab (bfd *abfd,
>  void
>  _bfd_mips_post_process_headers (bfd *abfd, struct bfd_link_info *link_info)
>  {
> -  struct mips_elf_link_hash_table *htab;
> +  struct mips_elf_link_hash_table *htab = NULL;
>    Elf_Internal_Ehdr *i_ehdrp;
>  
>    i_ehdrp = elf_elfheader (abfd);
> @@ -16141,6 +17036,9 @@ _bfd_mips_post_process_headers (bfd *abfd, struct bfd_link_info *link_info)
>    if (mips_elf_tdata (abfd)->abiflags.fp_abi == Val_GNU_MIPS_ABI_FP_64
>        || mips_elf_tdata (abfd)->abiflags.fp_abi == Val_GNU_MIPS_ABI_FP_64A)
>      i_ehdrp->e_ident[EI_ABIVERSION] = 3;
> +
> +  if (elf_tdata (abfd)->has_gnu_symbols & elf_gnu_symbol_ifunc)
> +    i_ehdrp->e_ident[EI_ABIVERSION] = 4;
>  }
>  
>  int
> diff --git a/bfd/reloc.c b/bfd/reloc.c
> index 3ef704f..496c465 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.
> +

 Missing COMMENT here.

> diff --git a/binutils/readelf.c b/binutils/readelf.c
> index 47ac1ad..0091539 100644
> --- a/binutils/readelf.c
> +++ b/binutils/readelf.c
> @@ -1729,6 +1729,8 @@ 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_GENERAL_GOTNO: return "MIPS_GENERAL_GOTNO";
> +
>      default:
>        return NULL;
>      }

 Please don't add this extra newline here.

> @@ -8612,6 +8614,7 @@ dynamic_section_mips_val (Elf_Internal_Dyn * entry)
>      case DT_MIPS_DELTA_SYM_NO:
>      case DT_MIPS_DELTA_CLASSSYM_NO:
>      case DT_MIPS_COMPACT_SIZE:
> +    case DT_MIPS_GENERAL_GOTNO:
>        print_vma (entry->d_un.d_ptr, DEC);
>        break;

 Hmm, I wonder why this is using `d_ptr' rather than `d_val' as the value 
associated with this tag is clearly not an address, but likewise does 
DT_MIPS_LOCAL_GOTNO and a number of other tags which the MIPS psABI 
specifies as using `d_val', so let's defer it to a follow-up cleanup.

  Maciej

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 2/2] [RFC] Add IFUNC support for MIPS (v4)
  2016-01-08  0:11                         ` [PATCH 2/2] " Faraz Shahbazker
@ 2016-03-06  7:18                           ` Maciej W. Rozycki
  0 siblings, 0 replies; 35+ messages in thread
From: Maciej W. Rozycki @ 2016-03-06  7:18 UTC (permalink / raw)
  To: Faraz Shahbazker; +Cc: binutils, rdsandiford

Hi Faraz,

> Test cases
> 
> ld/testsuite/ChangeLog:
> 
> 	* ld-ifunc/ifunc.exp: Enable IFUNC tests for MIPS targets
> 	* ld-mips-elf/mips-ifunc.exp: IFUNC test script

 Please use full stops at the end of sentences.

> 	* ifunc-3-n32.r, ifunc-3-n32.sym, ifunc-3-n32.t, ifunc-3-n64.r,

... and so on, and so on -- you're mixing test case sources and dumps.  
Please separate them and group functionally.  Please include subdirectory 
names and keep one file per line.  Using a shell script to generate such a 
listing is probably the easiest way.  See the existing examples.

> diff --git a/ld/testsuite/ld-mips-elf/ifunc-10-o32.sec b/ld/testsuite/ld-mips-elf/ifunc-10-o32.sec
> new file mode 100644
> index 0000000..8f68890
> --- /dev/null
> +++ b/ld/testsuite/ld-mips-elf/ifunc-10-o32.sec
> @@ -0,0 +1,5 @@
> +#...
> +.* \.iplt .*
> +.*
> +.* \.igot .*
> +#pass
> \ No newline at end of file

 Missing new line here, please fix this up.  Lots of cases like this 
throughout, I won't be listing them all.

 If your editor does not automatically add a new-line character at the end 
by default, then I suggest that you review your patches manually before 
submitting -- it may be easier for you to strip these lines with `sed' on 
an otherwise ready patch file actually.

> diff --git a/ld/testsuite/ld-mips-elf/ifunc-11-o32.g b/ld/testsuite/ld-mips-elf/ifunc-11-o32.g
> new file mode 100644
> index 0000000..2d42af5
> --- /dev/null
> +++ b/ld/testsuite/ld-mips-elf/ifunc-11-o32.g
> @@ -0,0 +1,7 @@
> +#...
> +004101a0 <_GLOBAL_OFFSET_TABLE_>:
> +  4101a0:	00000000 	nop
> +  4101a4:	80000000 	lb	zero,0\(zero\)
> +  4101a8:	00400000 	0x400000
> +  4101ac:	00400170 	0x400170
> +#pass

 This dump does not make sense to me, you are disassembling data.  If you 
want to verify GOT contents, then use `readelf -A'; it'll add extra 
information like symbol names to verify too.  Again, lots of other cases 
like this.

 Please resubmit with these global corrections; I haven't gone through the 
rest of your proposal as especially the last change will make your patch 
substantially different.

 Thanks,

  Maciej

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v4)
  2016-03-06  7:14                           ` Maciej W. Rozycki
@ 2016-03-16 21:20                             ` Faraz Shahbazker
  2016-04-29 14:19                               ` Maciej W. Rozycki
  2016-04-29 13:58                             ` [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v4) Maciej W. Rozycki
  1 sibling, 1 reply; 35+ messages in thread
From: Faraz Shahbazker @ 2016-03-16 21:20 UTC (permalink / raw)
  To: binutils, macro; +Cc: rdsandiford

Hi Maciej,

On 03/05/2016 11:13 PM, Maciej W. Rozycki wrote:
>> 	* bfd-in2.h (BFD_RELOC_MIPS_IRELATIVE): New relocation.
> 
>  This is a generated file, so please list it at the end and say: 
> "Regenerate." only, i.e.:
> 
> 	* bfd-in2.h: Regenerate.
> 
> You did regenerate it according to the instructions at the top rather than 
> hand-editing, right?
Yes. That's how I got the group spacing wrong.

>> @@ -1598,6 +1702,8 @@ 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;
>> +  elfh->other = other;
>> +
> 
>  Why do you need to set `other' while you have already set the ISA bit in 
> `value'?  Our rule for handling is to set either rather than both at a 
> time, why do you need an exception here?

I added `other' based on a suggestion from Richard that it was the preferred
method. The ISA bit setting for MIPS16 was just carried forward from an earlier
patch and can be removed.

>  Also you cannot fix IPLT entries based on global MIPS16/microMIPS object 
> flags as you may have to create mixed IPLT entries in a single output 
> binary if mixed regular/compressed code is present in input.  You may even 
> have to create two IPLT entries for a single function symbol.  Otherwise 
> you'll have issues with tail calls and short delay slots and even if you 
> escape them you may cause unnecessary mode switches that are costly.  So 
> you need to decide based on the referring reloc type.

If the type of the stub matches the IFUNC, then there shouldn't be any
unnecessary mode switches. I don't quite see the pitfalls of tails calls
that would require both compressed and regular IPLT for an IFUNC symbol.
Could you point me to an approximate test-case?

I have added mixed-mode handling for IPLT entries and related test-cases 
for the next review. Do you see any advantage in having the regular and 
compressed entries grouped within the IPLT section, versus just occurring
intermixed as they are encountered? For ordinary PLTs we create all regular
entries before all compressed entries, but I don't see any problem with
mixing them up, as long as the offset and symbol type information is correct.

>  Fortunately all the necessary details will have already been collected in 
> case ordinary PLT is to be produced.  So you can just reuse that stuff for 
> your purpose as well.

I don't see much opportunity for reuse, since the conditions that mandate
creation of IPLT entries are distinct from ordinary PLTs, so that code is not
guaranteed to execute in all cases where IPLTs are needed. But I have
restructured this condition code to look similar to PLT handling, including
support for a micromips-insn32 mode. N64/mips16/micromips/insn32 will still be
determined at the global level, but regular vs. compressed will be selected
individually for each entry.

Regards,
Faraz Shahbazker

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v4)
  2016-03-06  7:14                           ` Maciej W. Rozycki
  2016-03-16 21:20                             ` Faraz Shahbazker
@ 2016-04-29 13:58                             ` Maciej W. Rozycki
  2016-05-21  2:01                               ` Faraz Shahbazker
  1 sibling, 1 reply; 35+ messages in thread
From: Maciej W. Rozycki @ 2016-04-29 13:58 UTC (permalink / raw)
  To: Faraz Shahbazker; +Cc: binutils, Richard Sandiford

Hi Faraz,

 Second part of the review.  Apologies about the long RTT.

> bfd/ChangeLog:
> 
> 	* elf32-mips.c
> 	(elf_mips_eh_howto table): Howto for BFD_RELOC_MIPS_IRELATIVE.

 This does not correspond to the change made, you're not adding 
`elf_mips_eh_howto table'.  Also the convention is to make it unambiguous 
that this is a new object, e.g.:

	* elf32-mips.c (elf_mips_irelative_howto): New variable.

> 	(bfd_elf32_bfd_reloc_type_lookup): Case for R_MIPS_IRELATIVE.

 This is 
<http://www.gnu.org/prep/standards/html_node/Indicating-the-Part-Changed.html> 
from The GNU Coding Standard, so:

	(bfd_elf32_bfd_reloc_type_lookup) <R_MIPS_IRELATIVE>: New case.

> 	(bfd_elf32_bfd_reloc_name_lookup): Case for R_MIPS_IRELATIVE.

 No switch statement here and no mention of R_MIPS_IRELATIVE, so you need 
to describe the change a bit.  Please remember to use the imperative mood, 
as if you were giving orders to code.  So e.g.:

	(bfd_elf32_bfd_reloc_name_lookup): Handle R_MIPS_IRELATIVE.

> 	(mips_elf32_rtype_to_howto): Case for R_MIPS_IRELATIVE

 Here we have a switch again.  Also missing full stop.  So:

	(mips_elf32_rtype_to_howto) <R_MIPS_IRELATIVE>: New case.

> 	* elfxx-mips.c
> 	(struct mips_got_info): New fields general_gotno and
> 	assigned_general_gotno.

 No `struct' in ChangeLog entries + imperative mood, e.g.:

	* elfxx-mips.c (mips_got_info): Add fields general_gotno and 
	assigned_general_gotno.

> 	(struct mips_elf_link_hash_entry): New fields for offset in to
> 	IPLT/IGOT, flags to indicate if symbol needs IPLT/IRELOC/IGOT
> 	and if it has normal GOT-based relocations.

 Likewise.  No need to describe the purpose of the change (which is out of 
scope for ChangeLog), but please list the fields added.

> 	(mips_elf_link_hash_table): New fields for size of IPLT stubs
> 	and hash-table for local IFUNC symbols.

 Likewise.

> 	(MIPS16_P): New macro to check ASE flag.

 Just:

	(MIPS16_P): New macro.

> 	(mips16_exec_iplt_entry): Template for mips16 IPLT stub.

	(mips16_exec_iplt_entry): New variable.

> 	(mips32_exec_iplt_entry): Template for mips32 IPLT stub.
> 	(mips32r6_exec_iplt_entry): Template for mips32 R6 IPLT stub.
> 	(micromips32_exec_iplt_entry): Template for micromips32 stub.
> 	(mips64_exec_iplt_entry): Template for mips64 IPLT stub.
> 	(mips64_48b_exec_iplt_entry): Template for mips64 IPLT stub.

 Likewise.

> 	(mips_elf_link_hash_newfunc): Initialization of new
> 	mips_elf_link_hash_entry elements.

 Use the imperative mood.

> 	(mips_elf_create_stub_symbol): Set ISA bit in address for micromips
> 	& mips16 IPLT stubs. New argument to set st_other value of stub.

 Likewise (in the second sentence).

> 	(mips_elf_rel_dyn_section): Moved up to avoid forward declaration.

 Likewise.

> 	(mips_elf_record_local_got_symbol): New argument for pointer to
> 	mips_elf_link_hash_entry of the local symbol.  If non-NULL, this
> 	is used to set d.h in the GOT has entry instead of d.addend.

 No need for the explanation, i.e. keep the first sentence only.

 NB all such explanations belong to comments within code and/or the commit 
description (as applicable).

> 	(_bfd_mips_elf_finish_dynamic_symbol): Create IPLT stub/IRELATIVE
> 	relocation for IFUNC symbols as necessary. Set IFUNC symbol value
> 	to the IPLT entry address for executable objects.

 Two spaces after a full stop.  Check elsewhere too in case I missed a 
place.

> 	(_bfd_mips_elf_get_target_dtag): Add cases for dynamic tag -
> 	DT_MIPS_GENERAL_GOTNO.

 Again:

	(_bfd_mips_elf_get_target_dtag) <DT_MIPS_GENERAL_GOTNO>: New case.

> binutils/ChangeLog:
> 
> 	* readelf.c:
> 	(get_mips_dynamic_type): Add case for DT_MIPS_GENERAL_GOTNO.
> 	(dynamic_section_mips_val): Add case for DT_MIPS_GENERAL_GOTNO.

 Likewise.

> elfcpp/ChangeLog:
> 
> 	* elfcpp.h
> 	(enum DT): Add cases for dynamic tag DT_MIPS_GENERAL_GOTNO.

 No `enum' in ChangeLog entries + typo (cases vs case).

> include/elf/ChangeLog:
> 
> 	* mips.h
> 	(START_RELOC_NUMBERS): Entry for R_MIPS_IRELATIVE.

 START_RELOC_NUMBERS is not an object containing relocations:

	* mips.h (R_MIPS_IRELATIVE) New relocation.

> 	(DT_MIPS_GENERAL_GOTNO): New dynamic tag.

	(DT_MIPS_GENERAL_GOTNO): New macro.

> diff --git a/bfd/elf32-mips.c b/bfd/elf32-mips.c
> index 752f386..e8f1079 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 */

 Missing space after the comma here.

> +	 bfd_elf_generic_reloc, /* special_function */

 Use a tab here after the comma.

 Also why `bfd_elf_generic_reloc' and not `_bfd_mips_elf_generic_reloc'?  
This is a genuine question, perhaps you're right.  See also however:
<https://sourceware.org/ml/binutils/2003-05/msg00779.html>,
<https://sourceware.org/ml/binutils/2003-06/msg00338.html>,
<https://sourceware.org/ml/binutils/2003-12/msg00239.html>,
<https://sourceware.org/ml/binutils/2013-05/msg00060.html>.

> diff --git a/bfd/elfxx-mips.c b/bfd/elfxx-mips.c
> index 4ece819..6f23387 100644
> --- a/bfd/elfxx-mips.c
> +++ b/bfd/elfxx-mips.c
> @@ -392,6 +403,9 @@ struct mips_elf_link_hash_entry
>       cannot possibly be made dynamic).  */
>    unsigned int has_static_relocs : 1;
>  
> +  /* True if there is a got16 or call16 relocation against this symbol.  */
> +  unsigned int has_got_relocs : 1;

 Hmm, the comment doesn't cover all the relocations possible, perhaps 
just: "True if there is a GOT relocation..."?

> @@ -413,6 +427,15 @@ 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 symbol need an IPLT stub?  */
> +  unsigned int needs_igot : 1;

 "... an IGOT entry?"?

> @@ -1185,6 +1219,69 @@ 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 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					*/
> +};
> +
> +/* Format of 32 bit IPLT entries for R6. JR encoding differs.  */
> +static const bfd_vma mips32r6_exec_iplt_entry[] =
> +{
> +  0x3c0f0000,   /* lui $15, %hi(.igot address)		*/
> +  0x8df90000,   /* lw  $25, %lo(.igot address)($15)	*/
> +  0x03200009,   /* 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,	/* lw  $2, %lo(.igot address)($2) 	*/
> +  0x45b9,	/* jrc $25				*/
> +};
> +
> +/* The format of 64-bit IPLT entries.  */
> +static const bfd_vma mips64_exec_iplt_entry[] =
> +{
> +  0x3c0f0000,	/* lui $15, %highest(.got.iplt entry)        */
> +  0x3c0e0000,	/* lui $14, %hi(.got.iplt entry)             */
> +  0x25ef0000,	/* addiu $15, $15, %higher(.got.iplt entry)  */
> +  0x000f783c,	/* dsll32 $15, $15, 0x0                      */
> +  0x01ee782d,	/* daddu $15, $15, $14                       */
> +  0xddf90000,	/* ld $25, %lo(.got.iplt entry)($15)         */
> +  0x03200008,	/* jr $25                                    */
> +  0x00000000,	/* nop                                       */
> +};
> +
> +/* The format of 64-bit IPLT entries for 48bit address.  */
> +static const bfd_vma mips64_48b_exec_iplt_entry[] =
> +{
> +  0x3c0f0000,	/* lui $15, %higher(.got.iplt entry)         */
> +  0x25ef0000,	/* addiu $15, $15, %high(.got.iplt entry)    */
> +  0x000f7c38,	/* dsll $15, $15, 16  			     */
> +  0xddf90000,	/* ld $25, %lo(.got.iplt entry)($15)         */
> +  0x03200008,	/* jr $25                                    */
> +  0x00000000,	/* nop                                       */
> +};
> +

 You're adding 64-bit IPLT entries, but where's complementing NewABI 
support which would come in bfd/elf64-mips.c and bfd/elfn32-mips.c?  Also 
what's the difference between (.igot address) (used in 32-bit code) and 
(.got.iplt entry) used here?

> @@ -1963,6 +2069,156 @@ 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 one 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 IRELATIVE relocations.  If the link is dynamic, the
> +   relocations should go in .dynrel, otherwise they should go in the special

 Not `.rel.dyn'?

> +   .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;

 Formatting mistake, close the paren at the end.  Also add an empty line 
before the declaration and the rest of the function.

> +  BFD_ASSERT (srel != NULL);
> +  return srel;
> +}
> +
> +/* Reserve space in the rel.iplt section for an IRELATIVE relocation.  */
> +
> +static bfd_boolean
> +mips_elf_allocate_ireloc (struct bfd_link_info *info,
> +			  struct mips_elf_link_hash_table *mhtab,
> +			  struct mips_elf_link_hash_entry *mh)
> +{
> +  asection *srel;
> +  bfd *dynobj;
> +
> +  srel = mips_get_irel_section (info, mhtab);
> +  dynobj = elf_hash_table (info)->dynobj;
> +  if (srel != mhtab->root.irelplt && srel->size == 0)
> +    {
> +      /* Make room for a null element.  */
> +      srel->size += MIPS_ELF_REL_SIZE (dynobj);
> +      ++srel->reloc_count;
> +    }
> +  srel->size += MIPS_ELF_REL_SIZE (dynobj);
> +  mh->needs_ireloc = TRUE;
> +
> +  return TRUE;
> +}
> +
> +/* Reserve space in the iplt and igot tables for an ifunc entry
> +   and allocate space for an IRELATIVE relocation.  */
> +
> +static bfd_boolean
> +mips_elf_allocate_iplt (struct bfd_link_info *info,
> +			struct mips_elf_link_hash_table *mhtab,
> +			struct mips_elf_link_hash_entry *mh)
> +{
> +  asection *s;
> +  bfd *abfd = info->output_bfd;
> +  unsigned int other = 0;
> +
> +  BFD_ASSERT (!mh->needs_iplt);
> +  BFD_ASSERT (mhtab->root.iplt != NULL);
> +
> +  s = mhtab->root.iplt;
> +  if (ELF_ST_IS_MIPS16 (mh->root.other))
> +    other = STO_MIPS16;

 No need to handle microMIPS annotation here?  However you're fetching it 
from the ifunc symbol, which as I explained elsewhere, looks wrong to me.

> +
> +  mh->iplt_offset = s->size;
> +  mips_elf_create_stub_symbol (info, mh, ".iplt.", mhtab->root.iplt,
> +			       s->size, mhtab->iplt_entry_size, other);
> +  s->size += mhtab->iplt_entry_size;
> +
> +  BFD_ASSERT (mhtab->root.igotplt != NULL);
> +
> +  /* Only create IGOT entry if there are no GOT relocations, or when
> +     there are non-CALL references to the symbol.  In the latter case,
> +     existing GOT entry must point to IPLT, so an IGOT entry is needed
> +     to catch the result of the IRELATIVE relocation resolution.  */
> +  if (!mh->has_got_relocs || mh->root.pointer_equality_needed)
> +    {
> +      mh->igot_offset = mhtab->root.igotplt->size;
> +      mhtab->root.igotplt->size += MIPS_ELF_GOT_SIZE (abfd);
> +      mh->needs_igot = TRUE;
> +    }
> +
> +  mh->needs_iplt = TRUE;
> +
> +  /* IRELATIVE fixup will be needed for each local IFUNC.  */
> +  if (!mips_elf_allocate_ireloc (info, mips_elf_hash_table (info), mh))
> +    return FALSE;
> +
> +  return TRUE;
> +}
> +
> +/* hash_traverse callback that is called before sizing sections.
> +   DATA points to a mips_htab_traverse_info structure.  */

 This is `htab_traverse', not `hash_traverse'.

> +
> +static bfd_boolean
> +mips_elf_check_ifunc_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 (h
> +      && !h->needs_iplt
> +      && h->root.type == STT_GNU_IFUNC
> +      && h->root.def_regular)
> +    {
> +      struct bfd_link_info *info = hti->info;
> +      elf_tdata (info->output_bfd)->has_gnu_symbols |= elf_gnu_symbol_ifunc;

 Separate declaration from the rest of the block with a new line.

> +
> +      /* For global symbols, .iplt entry is needed only for all non-shared-
> +	 objects.  For local symbols, it is needed only if the symbol has
> +	 static relocations.  */
> +      if (((h->root.forced_local && h->has_static_relocs)
> +	   || (!h->root.forced_local && !bfd_link_pic (info)))
> +	  && !mips_elf_allocate_iplt (info, mips_elf_hash_table (info), h))

 I'm not sure what you mean by "all non-shared- objects" here.  Perhaps:

	/* For global symbols, an .iplt entry is needed in non-PIC 
	   binaries.  For local symbols, it is needed only if the symbol 
	   has static relocations.  */
	
-- is that right (as always please try to observe the 74-column limit for 
comments)?  Also I think the condition will be more readable as:

      if ((h->root.forced_local ? h->has_static_relocs : !bfd_link_pic (info))
	  && !mips_elf_allocate_iplt (info, mips_elf_hash_table (info), h))

> @@ -1975,6 +2231,12 @@ mips_elf_check_symbols (struct mips_elf_link_hash_entry *h, void *data)
>    if (!bfd_link_relocatable (hti->info))
>      mips_elf_check_mips16_stubs (hti->info, h);
>  
> +  /* Create stubs and relocations for IFUNC symbols.  */
> +  if (h
> +      && h->root.type == STT_GNU_IFUNC
> +      && !mips_elf_check_ifunc_symbols ((void **)&h, hti))

 Missing space after the cast here.

> @@ -1986,13 +2248,15 @@ mips_elf_check_symbols (struct mips_elf_link_hash_entry *h, void *data)
>  	 If we're creating a non-PIC relocatable object, mark H as
>  	 being PIC.  If we're creating a non-relocatable object with
>  	 non-PIC branches and jumps to H, make sure that H has an la25
> -	 stub.  */
> +	 stub.  IFUNCs with IPLT stubs don't need an la25 stub.  */
>        if (bfd_link_relocatable (hti->info))
>  	{
>  	  if (!PIC_OBJECT_P (hti->output_bfd))
>  	    h->root.other = ELF_ST_SET_MIPS_PIC (h->root.other);
>  	}
> -      else if (h->has_nonpic_branches && !mips_elf_add_la25_stub (hti->info, h))
> +      else if (h->has_nonpic_branches
> +	       && (h->root.type != STT_GNU_IFUNC || !h->needs_iplt)

 I think this will be easier to parse without the double negation:

	       && !(h->root.type == STT_GNU_IFUNC && h->needs_iplt)

> @@ -3254,8 +3487,21 @@ mips_elf_count_got_entry (struct bfd_link_info *info,
>  					entry->symndx < 0
>  					? &entry->d.h->root : NULL);
>      }
> -  else if (entry->symndx >= 0 || entry->d.h->global_got_area == GGA_NONE)
> -    g->local_gotno += 1;
> +  else if (entry->symndx >= 0 || (entry->d.h->global_got_area == GGA_NONE))

 Gratuitous change, no need for the extra parentheses.

> +    {
> +      /* Count IFUNCs as general GOT entries with explicit relocations.  */
> +      if (entry->symndx < 0
> +	  && entry->d.h->root.type == STT_GNU_IFUNC
> +	  && entry->d.h->root.def_regular
> +	  && !entry->d.h->needs_igot)
> +	{
> +	  g->general_gotno += 1;
> +	  mips_elf_allocate_ireloc (info, mips_elf_hash_table (info),
> +				    entry->d.h);
> +	}

 No error checking here?

> +      else
> +	g->local_gotno += 1;
> +    }
>    else
>      g->global_gotno += 1;

 Can you flatten the conditional:

  else if (entry->symndx < 0 && entry->d.h->global_got_area != GGA_NONE)
    g->global_gotno += 1;
  else if (entry->symndx < 0
	   && entry->d.h->root.type == STT_GNU_IFUNC
[...]
  else
    g->local_gotno += 1;

?

> @@ -3587,7 +3833,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;
>  
> @@ -3602,7 +3849,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
> @@ -3691,18 +3938,28 @@ mips_elf_create_local_got_entry (bfd *abfd, struct bfd_link_info *info,
>        return entry;
>      }
>  
> -  lookup.abfd = NULL;
>    lookup.symndx = -1;
> -  lookup.d.address = value;
> +  if (h && h->root.type == STT_GNU_IFUNC)
> +    {
> +      lookup.abfd = ibfd;
> +      lookup.d.h = h;
> +    }
> +  else
> +    {
> +      lookup.abfd = NULL;
> +      lookup.d.address = value;
> +    }
> +
>    loc = htab_find_slot (g->got_entries, &lookup, INSERT);
>    if (!loc)
>      return NULL;
>  
>    entry = (struct mips_got_entry *) *loc;
> -  if (entry)
> +  if (entry && entry->gotidx >= 0)
>      return entry;

 Why do you need to check `entry->gotidx' now?

> @@ -3715,7 +3972,15 @@ mips_elf_create_local_got_entry (bfd *abfd, struct bfd_link_info *info,
>    if (!entry)
>      return NULL;
>  
> -  if (got16_reloc_p (r_type)
> +  if (h && h->needs_ireloc && !h->needs_igot)
> +    /* Allocate IFUNC slots in the general GOT region since they
> +       will need explicit IRELATIVE relocations.  */
> +    {
> +      lookup.gotidx = MIPS_ELF_GOT_SIZE (abfd) * g->assigned_general_gotno++;

 Move the comment into the block.

> @@ -5075,6 +5348,65 @@ 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;
> +
> +  if (!bfd_link_pic (info))
> +    {
> +      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))
> +	htab->iplt_entry_size = (4 * ARRAY_SIZE (micromips32_exec_iplt_entry)
> +				 - 2);
> +      else
> +	htab->iplt_entry_size = 4 * (ARRAY_SIZE (mips32_exec_iplt_entry)
> +				     + (LOAD_INTERLOCKS_P (dynobj) ? 0 : 1));

 Don't bump the slot size for `!LOAD_INTERLOCKS_P' processors, just bump 
the size of `.iplt' by one instruction instead, as with regular `.plt'.  
It's safe to have LUI $15, ... from the following IPLT entry in the delay 
slot of the trailing jump of the previous one, so we just need an extra 
NOP at the end of the section for the last entry only.  There's no worry 
about the beginning of a MIPS16 entry landing in a delay slot because no 
`!LOAD_INTERLOCKS_P' processor supports MIPS16 execution, to say nothing 
about the uMIPS instruction set.

> +
> +      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;
> +
> +      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);

 Why do you need to set these flags explicitly?  They are supposed to be 
set automagically from BFD flags by the generic ELF backend.

> @@ -5191,6 +5523,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 = abfd->sections;
> +  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));

 No error checking here?

> +  symtab_hdr = &elf_tdata (abfd)->symtab_hdr;
> +  namep = bfd_elf_string_from_elf_section (abfd, symtab_hdr->sh_link,
> +					   isym->st_name);
> +
> +  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;
> +      ret->igot_offset = -1;
> +      ret->root.other = isym->st_other;
> +      ret->got_only_for_calls = TRUE;
> +
> +      *slot = ret;
> +    }
> +
> +  return ret;
> +}
>  \f

 This is called by `mips_elf_calculate_relocation' immediately below, so 
please keep the FF (^L) character separating the parts of the file above 
(I'm not sure if these codes still have any value, but let's not mess them 
up while we still have them).

> @@ -5321,6 +5723,13 @@ 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);

 No error checking here?

> +	  local_gnu_ifunc_p = TRUE;
> +	}

 Keep the `target_is_*' settings last in the block, for consistency.

 But does MIPS16/uMIPS annotation in `st_other' have the right meaning 
here, if any at all?  How is it set in GAS for an IFUNC symbol?

> @@ -5545,6 +5954,21 @@ 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
> +	   && (h->needs_igot
> +	       || (!call16_reloc_p (r_type)
> +		   && !got16_reloc_p (r_type))))
> +    {
> +      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;
> +    }

 Need to set `target_is_*' here too?  Need to update `sec' accordingly for 
later use (e.g. with R_MIPS_32)? -- all other branches of this conditional 
do.

> @@ -5640,6 +6064,16 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
>  	      BFD_ASSERT (h->root.needs_plt);
>  	      g = mips_elf_gotplt_index (info, &h->root);
>  	    }
> +	  /* IFUNCs use the explicitly-relocated GOT region, however we don't
> +	     distinguish it from the local GOT at this stage.  */
> +	  else if (h && h->needs_ireloc && !h->needs_igot)
> +	    {
> +	      g = mips_elf_local_got_index (abfd, input_bfd, info,
> +					    symbol + addend, r_symndx,
> +					    h, r_type);
> +	      if (g == MINUS_ONE)
> +		return bfd_reloc_outofrange;
> +	    }

 This block exactly duplicates one in the final `else' statement below.  
Can you rewrite code this such as to avoid the duplication?  E.g. I think:

      else if (local_p
	       && !htab->is_vxworks
[...]
      else if (!local_p
	       && !(h && h->needs_ireloc && !h->needs_igot))
[...]
      else

will do.

> @@ -5931,8 +6365,11 @@ mips_elf_calculate_relocation (bfd *abfd, bfd *input_bfd,
>  	 R_MIPS*_GOT16; every relocation evaluates to "G".  */
>        if (!htab->is_vxworks && local_p)
>  	{
> +	  /* Local IFUNC symbols must be accessed through GOT, similar to
> +	     global symbols, to allow for indirection.  */
>  	  value = mips_elf_got16_entry (abfd, input_bfd, info,
> -					symbol + addend, !was_local_p);
> +					symbol + addend,
> +					!was_local_p || local_gnu_ifunc_p, h);

 Please merge `symbol + addend' with the first line.

> @@ -6279,6 +6717,7 @@ mips_elf_perform_relocation (struct bfd_link_info *info,
>  	   && r_type == R_MIPS_26
>  	   && (x >> 26) == 0x3)		/* jal addr */
>  	  || (JALR_TO_BAL_P (input_bfd)
> +	      && !ifunc_p
>  	      && r_type == R_MIPS_JALR
>  	      && x == 0x0320f809)	/* jalr t9 */
>  	  || (JR_TO_B_P (input_bfd)

 Why for JALR only and not JR?

 I think it'll be properly handled in `mips_elf_calculate_relocation' 
instead, by discarding R_MIPS_JALR (and R_MICROMIPS_JALR, the handling of 
which is currently incomplete, but should be fixed) relocations in the 
IFUNC case rather than making them reach this point and only noticing they 
shouldn't be here in the first place.  So please arrange for this to 
happen along the case already handled there, rather than here.

> @@ -6436,31 +6876,44 @@ mips_elf_create_dynamic_relocation (bfd *output_bfd,
>    if (defined_p && r_type != R_MIPS_REL32)
>      *addendp += symbol;
>  
> -  if (htab->is_vxworks)
> -    /* VxWorks uses non-relative relocations for this.  */
> -    outrel[0].r_info = ELF32_R_INFO (indx, R_MIPS_32);
> +  /* Morph REL32 in to IRELATIVE fix-up for local IFUNC reference.  */
> +  if (h
> +      && h->root.type == STT_GNU_IFUNC
> +      && SYMBOL_REFERENCES_LOCAL (info, &h->root))
> +    {
> +      outrel[0].r_info = ELF_R_INFO (output_bfd, 0,
> +				     R_MIPS_IRELATIVE);
> +      outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
> +				     R_MIPS_NONE);
> +    }
>    else
> -    /* The relocation is always an REL32 relocation because we don't
> -       know where the shared library will wind up at load-time.  */
> -    outrel[0].r_info = ELF_R_INFO (output_bfd, (unsigned long) indx,
> -				   R_MIPS_REL32);
> -
> -  /* For strict adherence to the ABI specification, we should
> -     generate a R_MIPS_64 relocation record by itself before the
> -     _REL32/_64 record as well, such that the addend is read in as
> -     a 64-bit value (REL32 is a 32-bit relocation, after all).
> -     However, since none of the existing ELF64 MIPS dynamic
> -     loaders seems to care, we don't waste space with these
> -     artificial relocations.  If this turns out to not be true,
> -     mips_elf_allocate_dynamic_relocation() should be tweaked so
> -     as to make room for a pair of dynamic relocations per
> -     invocation if ABI_64_P, and here we should generate an
> -     additional relocation record with R_MIPS_64 by itself for a
> -     NULL symbol before this relocation record.  */
> -  outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
> -				 ABI_64_P (output_bfd)
> -				 ? R_MIPS_64
> -				 : R_MIPS_NONE);
> +    {
> +      if (htab->is_vxworks)
> +	/* VxWorks uses non-relative relocations for this.  */
> +	outrel[0].r_info = ELF32_R_INFO (indx, R_MIPS_32);
> +      else
> +	/* The relocation is always an REL32 relocation because we don't
> +	   know where the shared library will wind up at load-time.  */
> +	outrel[0].r_info = ELF_R_INFO (output_bfd, (unsigned long) indx,
> +				       R_MIPS_REL32);
> +
> +      /* For strict adherence to the ABI specification, we should
> +	 generate a R_MIPS_64 relocation record by itself before the
> +	 _REL32/_64 record as well, such that the addend is read in as
> +	 a 64-bit value (REL32 is a 32-bit relocation, after all).
> +	 However, since none of the existing ELF64 MIPS dynamic
> +	 loaders seems to care, we don't waste space with these
> +	 artificial relocations.  If this turns out to not be true,
> +	 mips_elf_allocate_dynamic_relocation() should be tweaked so
> +	 as to make room for a pair of dynamic relocations per
> +	 invocation if ABI_64_P, and here we should generate an
> +	 additional relocation record with R_MIPS_64 by itself for a
> +	 NULL symbol before this relocation record.  */
> +      outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
> +				     ABI_64_P (output_bfd)
> +				     ? R_MIPS_64
> +				     : R_MIPS_NONE);
> +    }

 No R_MIPS_IRELATIVE/R_MIPS_64 composition for n64?

 As a side note, the comment is wrong -- whoever wrote it misunderstood 
the ABI as it clearly states that R_MIPS_REL32/R_MIPS_64 by definition 
*is* R_MIPS_REL64, it's not like R_MIPS_64 just sign-extends the addend 
passed from R_MIPS_REL32:

"R_MIPS_REL64
          This can be produced by composing R_MIPS_REL with
          R_MIPS_64."

(R_MIPS_REL is an alias to R_MIPS_REL32).  I'll see if I can get with a 
better explanation here.

> @@ -7007,6 +7460,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);

 Why here rather than in `_bfd_mips_elf_fake_sections'?

> @@ -7920,6 +8375,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;

 This sets `elf_hash_table (info)->dynobj' in a disguised way.  Please 
sort out the assignment to `dynobj' immediately above then, and take care 
of code later on in this function which has become dead now.

> @@ -8186,7 +8649,23 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *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;

 Separate the declaration from the rest of the block with a new line, and 
remove the new line between the assignment and the error check, we don't 
normally put one in such places so that the fragment is seen as a whole.

> +
> +	  /* Relocation is for local STT_GNU_IFUNC symbol.  */
> +	  if (isym->st_info == STT_GNU_IFUNC)
> +	    {
> +	      /* Ensure that we have a hash entry for this symbol.  */
> +	      if ((ih = get_local_sym_hash (htab, abfd, rel)) == NULL)

 No assigments in selection or iteration statements' conditions please.

> @@ -8384,7 +8870,8 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
>  	     R_MIPS_CALL_HI16 because these are always followed by an
>  	     R_MIPS_GOT_LO16 or R_MIPS_CALL_LO16.  */
>  	  if (!mips_elf_record_local_got_symbol (abfd, r_symndx,
> -						 rel->r_addend, info, r_type))
> +						 rel->r_addend, info,
> +						 r_type, NULL))

 Please reformat to fit in two lines.

> @@ -8398,7 +8885,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 && ih == NULL)
>  	    {
>  	      (*_bfd_error_handler)
>  		(_("%B: CALL16 reloc at 0x%lx not against global symbol"),

 Under which circumstances does this condition trigger for local ifuncs?  
CALL16 relocations are used for lazy binding and are explicitly defined in 
the MIPS psABI for external symbols only.  Can the resolver for a local 
ifunc be external and bind lazily?

> @@ -8406,6 +8894,10 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
>  	      bfd_set_error (bfd_error_bad_value);
>  	      return FALSE;
>  	    }
> +	  if (h && h->type == STT_GNU_IFUNC)
> +	    ((struct mips_elf_link_hash_entry *)h)->has_got_relocs = TRUE;

 Missing space after the cast.

> @@ -8459,9 +8958,18 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
>  		}
>  	      else
>  		addend = rel->r_addend;
> -	      if (!mips_elf_record_got_page_ref (info, abfd, r_symndx,
> -						 h, addend))
> +	      if (ih &&
> +		  !mips_elf_record_local_got_symbol (abfd, -1, rel->r_addend,
> +						     info, r_type, ih))
>  		return FALSE;
> +	      else if (!mips_elf_record_got_page_ref (info, abfd, r_symndx,
> +						   h, addend))
> +		  return FALSE;

 Too much indentation here.

> +
> +	      if (h && h->type == STT_GNU_IFUNC)
> +		((struct mips_elf_link_hash_entry *)h)->has_got_relocs = TRUE;

 Missing space after the cast.

> +	      else if (ih)
> +		ih->has_got_relocs = TRUE;
>  
>  	      if (h)
>  		{

 Don't you need to handle R_MIPS_GOT_DISP in a similar manner?  You've got 
it mentioned in your specification even.  It's the NewABI's equivalent of 
R_MIPS_GOT16, though valid for both local and external symbols.

> @@ -9242,6 +9751,10 @@ _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_ifunc_symbols, &hti);
> +
>    if (hti.error)
>      return FALSE;

 Please bail out separately on each step.  Remove a newline between a call 
and its error check.

> @@ -9730,6 +10247,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)

 Hmm, it's interesting to note that we have duplicate `sgot' and `sgotplt' 
members in the backend -- how odd...  The duplication came with x86 ifunc 
support: <https://sourceware.org/ml/binutils/2009-06/msg00213.html>, 
<https://sourceware.org/ml/binutils/2009-06/msg00228.html> and was missed 
or something and in any case not fixed up.  This will have to be sorted 
out separately of course.

> @@ -9875,6 +10394,13 @@ _bfd_mips_elf_size_dynamic_sections (bfd *output_bfd,
>  	  if (! MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_PLTGOT, 0))
>  	    return FALSE;
>  	}
> +
> +      if (elf_tdata (output_bfd)->has_gnu_symbols & elf_gnu_symbol_ifunc)

 This looks wrong to me, it will return true for `elf_gnu_symbol_unique' 
too (which we don't set, but should; I'll fix it up).

> +	{
> +	  if (! MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_GENERAL_GOTNO, 0))

 No space after `!' please.

> @@ -10019,6 +10547,23 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
>  	{
>  	  sec = local_sections[r_symndx];
>  	  h = NULL;
> +
> +	  Elf_Internal_Sym *isym;
> +	  struct mips_elf_link_hash_table *htab;

 No declarations in the middle of a block please.

> @@ -10046,6 +10591,8 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
>  	  continue;
>  	}
>  
> +      gnu_ifunc_p = (local_gnu_ifunc_p || (h && h->type == STT_GNU_IFUNC));

 No need for the outer parens.

> @@ -10467,6 +11016,242 @@ 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,
> +		      bfd_vma igotplt_address)
> +{
> +  bfd_byte *loc;
> +  const bfd_vma *iplt_entry;
> +  bfd_vma high = mips_elf_high (igotplt_address);
> +  bfd_vma low = igotplt_address & 0xffff;
> +
> +  /* 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 (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;
> +
> +      if (highest)
> +	{
> +	  /* Full 64-bit address space.  */
> +	  bfd_put_32 (output_bfd, iplt_entry[0] | highest, loc);
> +	  bfd_put_32 (output_bfd, iplt_entry[1] | high, loc + 4);
> +	  bfd_put_32 (output_bfd, iplt_entry[2] | higher, loc + 8);
> +	  bfd_put_32 (output_bfd, iplt_entry[3], 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 (higher)
> +	{
> +	  /* 48-bit address space.  */
> +	  iplt_entry = mips64_48b_exec_iplt_entry;
> +	  bfd_put_32 (output_bfd, iplt_entry[0] | higher, loc);
> +	  bfd_put_32 (output_bfd, iplt_entry[1] | high, loc + 4);
> +	  bfd_put_32 (output_bfd, iplt_entry[2], loc + 8);
> +	  bfd_put_32 (output_bfd, iplt_entry[3] | low, loc + 12);
> +	  bfd_put_32 (output_bfd, iplt_entry[4], loc + 16);
> +	  bfd_put_32 (output_bfd, iplt_entry[5], loc + 20);
> +	}
> +      else
> +	{
> +	  /* 32-bit address space.  */
> +	  bfd_put_32 (output_bfd, iplt_entry[0] | high, loc);
> +	  bfd_put_32 (output_bfd, iplt_entry[5] | low, loc + 4);
> +	  bfd_put_32 (output_bfd, iplt_entry[6], loc + 8);
> +	  bfd_put_32 (output_bfd, iplt_entry[7], loc + 12);
> +	}
> +    }
> +  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);

 Extraneous space before last comma.

> +
> +      bfd_put_16 (output_bfd, iplt_entry[2], loc + 8);
> +      bfd_put_16 (output_bfd, iplt_entry[3], loc + 10);
> +    }
> +  else
> +    {
> +      if (MIPSR6_P (output_bfd))
> +	iplt_entry = mips32r6_exec_iplt_entry;
> +      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;
> +}
> +
> +/* Find local GOT index for VALUE.  Return -1 if no GOT slot is found.  */
> +
> +static bfd_vma
> +mips_elf_check_local_got_index (bfd *abfd, struct bfd_link_info *info,
> +				struct mips_elf_link_hash_entry *h)
> +{
> +  struct mips_got_entry lookup, *entry;
> +  void **loc;
> +  struct mips_got_info *g;
> +  struct mips_elf_link_hash_table *htab;
> +
> +  htab = mips_elf_hash_table (info);
> +  BFD_ASSERT (htab != NULL);
> +
> +  g = mips_elf_bfd_got (abfd, FALSE);
> +
> +  /* Check for existing local GOT entry.  */
> +  if (g != NULL)
> +    {
> +      lookup.abfd = abfd;
> +      lookup.symndx = -1;
> +      lookup.d.h = h;
> +      lookup.tls_type = GOT_TLS_NONE;
> +      loc = htab_find_slot (g->got_entries, &lookup, NO_INSERT);
> +    }
> +  else
> +    return -1;
> +
> +  if (loc && *loc)
> +    {
> +      entry = (struct mips_got_entry *) *loc;
> +      return entry->gotidx;
> +    }
> +  else
> +    return -1;
> +}
> +
> +/* 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_vma igotplt_address = 0;
> +  int igot_offset = -1;
> +  asection *gotsect, *relsect;
> +  bfd_vma value = sym->st_value;
> +
> +  if (MIPS16_P (output_bfd) || MICROMIPS_P (output_bfd))
> +    value |= 1;

 As I previously noted this must not be set globally, but I take it you've 
taken care of it already with the update.

> +
> +  if (!hmips->needs_igot)
> +    {
> +      gotsect = htab->sgot;
> +      /* Check if IFUNC symbol already has an assigned GOT slot.  */
> +      igot_offset = mips_elf_check_local_got_index (output_bfd, info, hmips);
> +    }
> +  else
> +    {
> +      bfd_byte *loc;
> +      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, value, loc);
> +      else
> +	bfd_put_32 (output_bfd, value, loc);
> +    }
> +
> +  igotplt_address = (gotsect->output_section->vma + gotsect->output_offset
> +		     + igot_offset);
> +
> +  relsect = mips_get_irel_section (info, htab);
> +
> +  if (igot_offset >= 0)
> +    {
> +      if (hmips->needs_iplt && relsect->contents == NULL)
> +	{
> +	  /* Allocate memory for the relocation section contents.  */
> +	  relsect->contents = bfd_zalloc (dynobj, relsect->size);
> +	  if (relsect->contents == NULL)
> +	    return FALSE;
> +	}
> +
> +      if (hmips->needs_iplt || SYMBOL_REFERENCES_LOCAL (info, &hmips->root))
> +	/* Emit an IRELATIVE relocation against the [I]GOT entry.  */
> +	mips_elf_output_dynamic_relocation (output_bfd, relsect,
> +					    relsect->reloc_count++, 0,
> +					    R_MIPS_IRELATIVE, igotplt_address);
> +      else
> +	{
> +	  /* Emit an R_MIPS_REL32 relocation against global GOT entry for
> +	     a preemptible symbol.  */
> +	  asection *sec = hmips->root.root.u.def.section;
> +	  Elf_Internal_Rela rel[3];
> +
> +	  memset (rel, 0, sizeof (rel));
> +	  rel[0].r_info = ELF_R_INFO (output_bfd, 0, R_MIPS_REL32);
> +	  rel[0].r_offset = rel[1].r_offset = rel[2].r_offset = igot_offset;
> +
> +	  mips_elf_create_dynamic_relocation (output_bfd, info, rel, hmips,
> +					      sec, value, NULL,
> +					      gotsect);

 This needs to be composed with R_MIPS_64 for n64, doesn't it?

> @@ -10957,9 +11755,41 @@ _bfd_mips_elf_finish_dynamic_symbol (bfd *output_bfd,
>        sym->st_other -= STO_MICROMIPS;
>      }
>  
> +  if (hmips->needs_iplt)
> +    {
> +      /* 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.  */

 Make a note that this is another `htab_traverse' callback.

> +
> +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;

 Wrap this line please.

> +  struct bfd_link_info *info  = (struct bfd_link_info *)inf;

 Missing space after the cast.

> @@ -13849,6 +14690,48 @@ _bfd_mips_elf_relax_section (bfd *abfd, asection *sec,
>    return FALSE;
>  }
>  \f
> +/* 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);

 Add an empty line between the declaration and the rest of the function.

> @@ -15509,7 +16402,9 @@ _bfd_mips_elf_get_target_dtag (bfd_vma dtag)
>        return "DT_MIPS_PLTGOT";
>      case DT_MIPS_RWPLT:
>        return "DT_MIPS_RWPLT";
> -    }
> +    case DT_MIPS_GENERAL_GOTNO:
> +      return "DT_MIPS_GENERAL_GOTNO";
> +   }

 Don't move the brace.

> @@ -16123,7 +17018,7 @@ _bfd_mips_elf_get_synthetic_symtab (bfd *abfd,
>  void
>  _bfd_mips_post_process_headers (bfd *abfd, struct bfd_link_info *link_info)
>  {
> -  struct mips_elf_link_hash_table *htab;
> +  struct mips_elf_link_hash_table *htab = NULL;

 Why is this change needed?

> @@ -16141,6 +17036,9 @@ _bfd_mips_post_process_headers (bfd *abfd, struct bfd_link_info *link_info)
>    if (mips_elf_tdata (abfd)->abiflags.fp_abi == Val_GNU_MIPS_ABI_FP_64
>        || mips_elf_tdata (abfd)->abiflags.fp_abi == Val_GNU_MIPS_ABI_FP_64A)
>      i_ehdrp->e_ident[EI_ABIVERSION] = 3;
> +
> +  if (elf_tdata (abfd)->has_gnu_symbols & elf_gnu_symbol_ifunc)

 Same note about `elf_gnu_symbol_unique' as above.

  Maciej

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v4)
  2016-03-16 21:20                             ` Faraz Shahbazker
@ 2016-04-29 14:19                               ` Maciej W. Rozycki
  2016-05-21  3:18                                 ` Faraz Shahbazker
  0 siblings, 1 reply; 35+ messages in thread
From: Maciej W. Rozycki @ 2016-04-29 14:19 UTC (permalink / raw)
  To: Faraz Shahbazker; +Cc: binutils, Richard Sandiford

Hi Faraz,

> >> @@ -1598,6 +1702,8 @@ 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;
> >> +  elfh->other = other;
> >> +
> > 
> >  Why do you need to set `other' while you have already set the ISA bit in 
> > `value'?  Our rule for handling is to set either rather than both at a 
> > time, why do you need an exception here?
> 
> I added `other' based on a suggestion from Richard that it was the preferred
> method. The ISA bit setting for MIPS16 was just carried forward from an earlier
> patch and can be removed.

 Would you please give me a reference?  All I came across is this:

> This isn't really a property of the hash table symbol but of the kind of
> stub being created by the calling code.  I think it would be better to
> pass an "st_other" parameter alongside the other stub properties.

-- which only comments on *how* to set `other' rather than whether to set 
it at all in the first place.

 Also please answer my original question, that is: what is the intent for 
this setting -- or IOW what changes if you remove it?

> >  Also you cannot fix IPLT entries based on global MIPS16/microMIPS object 
> > flags as you may have to create mixed IPLT entries in a single output 
> > binary if mixed regular/compressed code is present in input.  You may even 
> > have to create two IPLT entries for a single function symbol.  Otherwise 
> > you'll have issues with tail calls and short delay slots and even if you 
> > escape them you may cause unnecessary mode switches that are costly.  So 
> > you need to decide based on the referring reloc type.
> 
> If the type of the stub matches the IFUNC, then there shouldn't be any
> unnecessary mode switches.

 How do you know that the stub matches the IFUNC given that it is only 
resolved at the run time?  You could have some regular and some compressed 
IFUNC variants provided to choose from, perhaps deliberately.

> I don't quite see the pitfalls of tails calls
> that would require both compressed and regular IPLT for an IFUNC symbol.
> Could you point me to an approximate test-case?

 If you have a tail call in an non-PIC executable, it'll be made with a J 
(or sometimes B) instruction.  This instruction cannot be patched by LD to 
make a mode switch, because there is no suitable instruction in the ISA.  
Consequently the target of the jump (an IPLT stub in this case) and the 
jump itself have to match each other as far as the ISA mode is concerned.
So it's not even a performance issue, but it's a code correctness issue.

 There's no J instruction in the MIPS16 instruction set, so you won't 
encounter MIPS16 tail calls, but still avoiding a mode switch is 
preferable, to prevent a long pipeline stall.

 This is not an issue with an IPLT stub jumping to the IFUNC itself, 
because it's an indirect (register) jump, so the ISA mode can be switched 
if necessary.  Also, as I noted above, you don't know if a switch will be 
necessary up until the run time.  However by optimising to avoid a switch 
on jumping to an IPLT stub you're minimising the number of mode switches 
required to at most one.

> I have added mixed-mode handling for IPLT entries and related test-cases 
> for the next review. Do you see any advantage in having the regular and 
> compressed entries grouped within the IPLT section, versus just occurring
> intermixed as they are encountered? For ordinary PLTs we create all regular
> entries before all compressed entries, but I don't see any problem with
> mixing them up, as long as the offset and symbol type information is correct.

 There are two reasons for regular PLT to have entries sorted.

 First is cache line alignment -- regular PLT entries are 16-byte long 
making them align to cache lines of up to 16 bytes in size, while 
microMIPS entries are 12-byte long (unless `--insn32' has been used) 
guaranteeing alignment to cache lines of up to 4 bytes only.  By placing 
all regular entries first they have the guaranteed 16-byte alignment.

 Second is the handling of synthetic symbols which is much easier with 
such an arrangement -- see `_bfd_mips_elf_get_synthetic_symtab'.

 If neither were an issue with IPLT, then you could leave entries in a 
random order.  But the IPLT entries also have different lengths, even the 
regular ones, e.g. with n64, so there will be performance implications 
from different ordering.

 Can you see if you can do something about it then?  The best would be 
sorting entries in the decreasing alignment order and aligning the section 
according to the largest one.  So e.g. given the sizes of 32, 24, 20, 16, 
the order would be: 32, 16, 24, 20, and the section alignment -- 5.

> >  Fortunately all the necessary details will have already been collected in 
> > case ordinary PLT is to be produced.  So you can just reuse that stuff for 
> > your purpose as well.
> 
> I don't see much opportunity for reuse, since the conditions that mandate
> creation of IPLT entries are distinct from ordinary PLTs, so that code is not
> guaranteed to execute in all cases where IPLTs are needed. But I have
> restructured this condition code to look similar to PLT handling, including
> support for a micromips-insn32 mode. N64/mips16/micromips/insn32 will still be
> determined at the global level, but regular vs. compressed will be selected
> individually for each entry.

 The creation conditions are handled elsewhere, but the ISA modes of the 
callers are recorded as relocations are seen.  This information is then 
used to choose the correct kind of PLT entries in one place, and you can 
also use it to choose the correct kind of IPLT entries where appropriate 
in your code.  For why you need it -- please see my note above.

 Let me know if you have find anything unclear or have any other questions 
or comments.

  Maciej

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v4)
  2016-04-29 13:58                             ` [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v4) Maciej W. Rozycki
@ 2016-05-21  2:01                               ` Faraz Shahbazker
  2016-05-27 22:17                                 ` Faraz Shahbazker
  0 siblings, 1 reply; 35+ messages in thread
From: Faraz Shahbazker @ 2016-05-21  2:01 UTC (permalink / raw)
  To: Maciej W. Rozycki; +Cc: binutils, Richard Sandiford

On 04/29/16 06:57, Maciej W. Rozycki wrote:
>> diff --git a/bfd/elf32-mips.c b/bfd/elf32-mips.c
>> index 752f386..e8f1079 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 */> 
>  Also why `bfd_elf_generic_reloc' and not `_bfd_mips_elf_generic_reloc'?  

I copied this bit directly from the previous series of MIPS IFUNC patches,
published a couple of years back. My guess is that it was selected based
on whatever architecture was used as a reference, without considering the 
prevalent convention in MIPS-specific code. Will fix for the next iteration.

>> @@ -3691,18 +3938,28 @@ mips_elf_create_local_got_entry (bfd *abfd, struct bfd_link_info *info,
>>        return entry;
>>      }
>>  
>> -  lookup.abfd = NULL;
>>    lookup.symndx = -1;
>> -  lookup.d.address = value;
>> +  if (h && h->root.type == STT_GNU_IFUNC)
>> +    {
>> +      lookup.abfd = ibfd;
>> +      lookup.d.h = h;
>> +    }
>> +  else
>> +    {
>> +      lookup.abfd = NULL;
>> +      lookup.d.address = value;
>> +    }
>> +
>>    loc = htab_find_slot (g->got_entries, &lookup, INSERT);
>>    if (!loc)
>>      return NULL;
>>  
>>    entry = (struct mips_got_entry *) *loc;
>> -  if (entry)
>> +  if (entry && entry->gotidx >= 0)
>>      return entry;
> 
>  Why do you need to check `entry->gotidx' now?

The local GOT entry could have been created earlier by 
mips_elf_record_[global|local]_got_symbols(), but the gotidx is only assigned
from the general GOT pool, when mips_elf_create_local_got_entry() is called.
The major change here is that IFUNC GOT entries are always tracked uniquely
by the symbol's hash-table entry (d.h), as against other local symbols which
could by tracked by d.addend.

>> @@ -6279,6 +6717,7 @@ mips_elf_perform_relocation (struct bfd_link_info *info,
>>  	   && r_type == R_MIPS_26
>>  	   && (x >> 26) == 0x3)		/* jal addr */
>>  	  || (JALR_TO_BAL_P (input_bfd)
>> +	      && !ifunc_p
>>  	      && r_type == R_MIPS_JALR
>>  	      && x == 0x0320f809)	/* jalr t9 */
>>  	  || (JR_TO_B_P (input_bfd)
> 
>  Why for JALR only and not JR?
> 
>  I think it'll be properly handled in `mips_elf_calculate_relocation' 
> instead, by discarding R_MIPS_JALR (and R_MICROMIPS_JALR, the handling of 
> which is currently incomplete, but should be fixed) relocations in the 
> IFUNC case rather than making them reach this point and only noticing they 
> shouldn't be here in the first place.  So please arrange for this to 
> happen along the case already handled there, rather than here.

We can in fact discard these relocs for all IFUNCs that do not have IPLTs,
since there is no canonical address to use for the optimization in that case.
A related problem to tackle -- these relocs count as static relocs and cause
IPLT stubs to be generated for locally bound IFUNCs, even when they are not
otherwise needed (that is, when all references are GOT-based).

>> @@ -6436,31 +6876,44 @@ mips_elf_create_dynamic_relocation (bfd *output_bfd,
> <snip>
>  No R_MIPS_IRELATIVE/R_MIPS_64 composition for n64?

I defined IRELATIVE to just operate on natural width, since it is operating on GOT
entries. Now that you've mentioned it, I agree that composition would keep things
closer to the ABI spec. The glibc patch will need to be modified as well.

>> @@ -7920,6 +8375,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;
> 
>  This sets `elf_hash_table (info)->dynobj' in a disguised way.  Please 
> sort out the assignment to `dynobj' immediately above then, and take care 
> of code later on in this function which has become dead now. 

I've moved this assignment of ->dynobj in to create_ifunc_sections() and arranged
for that function to be called at the end of _bfd_mips_elf_check_relocs(),
and only if an input object declares IFUNCs. This arrangement makes the
'not-generated-if-not-needed' comment above more accurate.
 
>> @@ -8398,7 +8885,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 && ih == NULL)
>>  	    {
>>  	      (*_bfd_error_handler)
>>  		(_("%B: CALL16 reloc at 0x%lx not against global symbol"),
> 
>  Under which circumstances does this condition trigger for local ifuncs?  
> CALL16 relocations are used for lazy binding and are explicitly defined in 
> the MIPS psABI for external symbols only.  Can the resolver for a local 
> ifunc be external and bind lazily?

Local IFUNCs are treated like global symbols in that they get a full GOT 
(or IGOT) entry each and all references must go through that GOT entry.
This means that call16/got16 references can theoretically work for local
IFUNCs, although not explicitly sanctioned by the ABI.

I just followed gcc's behaviour instead of referring to the ABI on this
point. gcc does not consider IFUNCs to be locally bound, since the 
resolved function could be external. The mips backend, using this criteria,
considers all IFUNCs to be candidates for lazy evaluation and emits call16
for all PIC IFUNC calls without caring about local vs. global binding.
This trips up the error condition above.

Since you pointed it out, I tinkered around with gcc to force got16 to be
generated for local IFUNCs instead of call16, but ran up against instances 
where it is impossible to know that the symbol is a local IFUNC. For example,
the glibc testsuite has IFUNC declarations like this:

	extern int foo (void);
	static void * foo_ifunc (void) __asm__ ("foo");
	__asm__(".type foo, %gnu_indirect_function");

Both `static' and `ifunc' aspects of foo are hidden in inline assembly
and hence not available to the compiler. I don't see a way around this.
We can modify gcc to generate the right reloc for the canonical
local IFUNC declaration and let ^^those glibc tests fail, till such time
when they are modified to use the canonical form (not an easy sell, given
the pretzel declaration above seems to work for other architectures).

This reminds me, another issue with gcc is that it generates GOT+LO16
sequence for non-call references to local IFUNC instead of just GOT().
The GOT load already gives the address of the IFUNC and the subsequent
addiu corrupts it. I'll upload a gcc patch to address both issues and
copy you on it for comments.

Considering that this is an `extension', I think it would be okay to
allow CALL16 on local IFUNCs. But I'll follow your cue on what to
do about it.

Regards,
Faraz Shahbazker

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v4)
  2016-04-29 14:19                               ` Maciej W. Rozycki
@ 2016-05-21  3:18                                 ` Faraz Shahbazker
  2016-12-05 14:44                                   ` Maciej W. Rozycki
  0 siblings, 1 reply; 35+ messages in thread
From: Faraz Shahbazker @ 2016-05-21  3:18 UTC (permalink / raw)
  To: Maciej W. Rozycki; +Cc: binutils, Richard Sandiford

On 04/29/2016 07:19 AM, Maciej W. Rozycki wrote:
> 
>>>> @@ -1598,6 +1702,8 @@ 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;
>>>> +  elfh->other = other;
>>>> +
>>>
>>>  Why do you need to set `other' while you have already set the ISA bit in 
>>> `value'?  Our rule for handling is to set either rather than both at a 
>>> time, why do you need an exception here?
>>
>> I added `other' based on a suggestion from Richard that it was the preferred
>> method. The ISA bit setting for MIPS16 was just carried forward from an earlier
>> patch and can be removed.
> 
>  Would you please give me a reference?  All I came across is this:
> 
>> This isn't really a property of the hash table symbol but of the kind of
>> stub being created by the calling code.  I think it would be better to
>> pass an "st_other" parameter alongside the other stub properties.
> 
> -- which only comments on *how* to set `other' rather than whether to set 
> it at all in the first place.
>  Also please answer my original question, that is: what is the intent for 
> this setting -- or IOW what changes if you remove it?

Nothing. FWIW, even if both are set here, _bfd_mips_elf_link_output_symbol_hook
takes care of resetting the ISA bit. A similar thing happens with 
mips_elf_set_plt_sym_value, for example.

Before this patch, mips_elf_create_stub_symbol was only being used for
la25 stubs for MIPS or micromips and the pre-existing usage prefers to set 
the ISA bit. On one hand, I am inclined to stick with that and remove the symbol
annotation. On the other hand, the behaviour of output_symbol_hook() suggests 
that annotation is the preferred way of indicating ISA, since it retains that
setting over the ISA bit, when both are set. What would you recommend?
 
>>>  Also you cannot fix IPLT entries based on global MIPS16/microMIPS object 
>>> flags as you may have to create mixed IPLT entries in a single output 
>>> binary if mixed regular/compressed code is present in input.  You may even 
>>> have to create two IPLT entries for a single function symbol.  Otherwise 
>>> you'll have issues with tail calls and short delay slots and even if you 
>>> escape them you may cause unnecessary mode switches that are costly.  So 
>>> you need to decide based on the referring reloc type.

Finally got your point! Using the referring reloc to choose between regular
& compressed stub makes sense for locally bound IFUNCs. For an exported IFUNC
with external references and non-call relocs, the exported symbol has to 
be bound to a specific IPLT stub. Okay to pick the stub from the ISA type
of the resolver in that case?

This change takes the number of IFUNC-related fields in the struct
mips_elf_link_hash_entry to 7 (4 bit-flags and 3 offsets). Would it be better
to pack them all in to a struct which we allocate only for IFUNCs? Should
definitely save memory with large links, especially on 64-bit hosts.

>  There are two reasons for regular PLT to have entries sorted.
>  First is cache line alignment -- regular PLT entries are 16-byte long 
> making them align to cache lines of up to 16 bytes in size, while 
> microMIPS entries are 12-byte long (unless `--insn32' has been used) 
> guaranteeing alignment to cache lines of up to 4 bytes only.  By placing 
> all regular entries first they have the guaranteed 16-byte alignment.
> 
>  Second is the handling of synthetic symbols which is much easier with 
> such an arrangement -- see `_bfd_mips_elf_get_synthetic_symtab'.
> 
>  If neither were an issue with IPLT, then you could leave entries in a 
> random order.  But the IPLT entries also have different lengths, even the 
> regular ones, e.g. with n64, so there will be performance implications 
> from different ordering.
>
>  Can you see if you can do something about it then?  The best would be 
> sorting entries in the decreasing alignment order and aligning the section 
> according to the largest one.  So e.g. given the sizes of 32, 24, 20, 16, 
> the order would be: 32, 16, 24, 20, and the section alignment -- 5.

I get the general idea. But given that we're selecting 32 vs. 64/
micromips vs. mips16 and those combinations cannot be inter-linked, I
don't see how we can have more than 2 stub sizes in a given link. Besides
after performing the delay slot optimization that you suggested elsewhere,
all variations of mips32 will be 12 bytes. All variations of mips64 will
be 28-bytes, except mips16/64 which could be 16-bytes.

Also, note that although we have 32/48-bit variants for 64-bit, the IPLT stub
size is still the same as the regular entry. Only the # of instructions that
need to be executed is fewer. The selection is made based on the actual
address bits needed to get to the GOT/IGOT entry of that symbol. I should
probably be filling the tail part of these stubs with NOPs.

The delay slot optimization itself requires all regular entries to be
grouped together. Beyond that, stub sizes would only matter for mips16/64
combination. Okay then, to just sort on regular vs. compressed and keep
all compressed stubs before all regular ones? Compressed stubs generally
don't have the lagging delay-slot problem, except for the micromips_insn32
variant.

Related point: Should I go ahead and add all mips64 variants? Right now we
only support regular mips64 - no compressed stubs.

Regards,
Faraz Shahbazker

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v4)
  2016-05-21  2:01                               ` Faraz Shahbazker
@ 2016-05-27 22:17                                 ` Faraz Shahbazker
  0 siblings, 0 replies; 35+ messages in thread
From: Faraz Shahbazker @ 2016-05-27 22:17 UTC (permalink / raw)
  To: Maciej W. Rozycki; +Cc: binutils, Richard Sandiford

On 05/20/2016 07:01 PM, Faraz Shahbazker wrote:
>>> @@ -8398,7 +8885,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 && ih == NULL)
>>>  	    {
>>>  	      (*_bfd_error_handler)
>>>  		(_("%B: CALL16 reloc at 0x%lx not against global symbol"),
>>
>>  Under which circumstances does this condition trigger for local ifuncs?  
>> CALL16 relocations are used for lazy binding and are explicitly defined in 
>> the MIPS psABI for external symbols only.  Can the resolver for a local 
>> ifunc be external and bind lazily?
> 
> Local IFUNCs are treated like global symbols in that they get a full GOT 
> (or IGOT) entry each and all references must go through that GOT entry.
> This means that call16/got16 references can theoretically work for local
> IFUNCs, although not explicitly sanctioned by the ABI.
> 
> I just followed gcc's behaviour instead of referring to the ABI on this
> point. gcc does not consider IFUNCs to be locally bound, since the 
> resolved function could be external. The mips backend, using this criteria,
> considers all IFUNCs to be candidates for lazy evaluation and emits call16
> for all PIC IFUNC calls without caring about local vs. global binding.
> This trips up the error condition above.
> 
> Since you pointed it out, I tinkered around with gcc to force got16 to be
> generated for local IFUNCs instead of call16, but ran up against instances 
> where it is impossible to know that the symbol is a local IFUNC. For example,
> the glibc testsuite has IFUNC declarations like this:
> 
> 	extern int foo (void);
> 	static void * foo_ifunc (void) __asm__ ("foo");
> 	__asm__(".type foo, %gnu_indirect_function");
> 
> Both `static' and `ifunc' aspects of foo are hidden in inline assembly
> and hence not available to the compiler. I don't see a way around this.

Another problem with using got() instead of call16() is that we can't
distinguish call v. non-call PIC references in this case. In effect, all PIC
calls end up going through an IPLT stub, when they could have otherwise
directly used the GOT slot that the stub uses to get the resolved function.
This is referred to as case 3 in the spec document (excerpt below),
for locally bound IFUNCs.

==========
When an IFUNC symbol binds locally there are 6 cases:

    PIC relocs?  non-PIC relocs?  all call-only?   variant
    y            y                y                (3)

    (3) Need an IPLT.  The associated IRELATIVE GOT entry should go in .got
        so that it can be used by the PIC references.
==========

Regards,
Faraz Shahbazker

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v4)
  2016-05-21  3:18                                 ` Faraz Shahbazker
@ 2016-12-05 14:44                                   ` Maciej W. Rozycki
  2017-01-09 21:10                                     ` Faraz Shahbazker
                                                       ` (2 more replies)
  0 siblings, 3 replies; 35+ messages in thread
From: Maciej W. Rozycki @ 2016-12-05 14:44 UTC (permalink / raw)
  To: Faraz Shahbazker; +Cc: binutils, Richard Sandiford

On Sat, 21 May 2016, Faraz Shahbazker wrote:

> >>>> @@ -1598,6 +1702,8 @@ 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;
> >>>> +  elfh->other = other;
> >>>> +
> >>>
> >>>  Why do you need to set `other' while you have already set the ISA bit in 
> >>> `value'?  Our rule for handling is to set either rather than both at a 
> >>> time, why do you need an exception here?
> >>
> >> I added `other' based on a suggestion from Richard that it was the preferred
> >> method. The ISA bit setting for MIPS16 was just carried forward from an earlier
> >> patch and can be removed.
> > 
> >  Would you please give me a reference?  All I came across is this:
> > 
> >> This isn't really a property of the hash table symbol but of the kind of
> >> stub being created by the calling code.  I think it would be better to
> >> pass an "st_other" parameter alongside the other stub properties.
> > 
> > -- which only comments on *how* to set `other' rather than whether to set 
> > it at all in the first place.
> >  Also please answer my original question, that is: what is the intent for 
> > this setting -- or IOW what changes if you remove it?
> 
> Nothing. FWIW, even if both are set here, _bfd_mips_elf_link_output_symbol_hook
> takes care of resetting the ISA bit. A similar thing happens with 
> mips_elf_set_plt_sym_value, for example.
> 
> Before this patch, mips_elf_create_stub_symbol was only being used for
> la25 stubs for MIPS or micromips and the pre-existing usage prefers to set 
> the ISA bit. On one hand, I am inclined to stick with that and remove the symbol
> annotation. On the other hand, the behaviour of output_symbol_hook() suggests 
> that annotation is the preferred way of indicating ISA, since it retains that
> setting over the ISA bit, when both are set. What would you recommend?

 I take back my original concern.  You need the ISA annotation for correct 
final symbol table contents as shown with commit a848a2271b9b ("MIPS/BFD: 
Add microMIPS annotation to LA25 stub symbols").  And you need the ISA bit 
set as well for the relocation checks in `mips_elf_calculate_relocation' 
added with commit 9d862524f6ae ("MIPS: Verify the ISA mode and alignment 
of branch and jump targets").  The ISA bit will be stripped from the final 
symbol table produced with the ISA annotation remaining only, by code in 
`_bfd_mips_elf_link_output_symbol_hook'.  This does not affect any dynsym 
table produced where the ISA bit does remain.

> >>>  Also you cannot fix IPLT entries based on global MIPS16/microMIPS object 
> >>> flags as you may have to create mixed IPLT entries in a single output 
> >>> binary if mixed regular/compressed code is present in input.  You may even 
> >>> have to create two IPLT entries for a single function symbol.  Otherwise 
> >>> you'll have issues with tail calls and short delay slots and even if you 
> >>> escape them you may cause unnecessary mode switches that are costly.  So 
> >>> you need to decide based on the referring reloc type.
> 
> Finally got your point! Using the referring reloc to choose between regular
> & compressed stub makes sense for locally bound IFUNCs. For an exported IFUNC
> with external references and non-call relocs, the exported symbol has to 
> be bound to a specific IPLT stub. Okay to pick the stub from the ISA type
> of the resolver in that case?

 Yes, that sounds reasonable to me.

> This change takes the number of IFUNC-related fields in the struct
> mips_elf_link_hash_entry to 7 (4 bit-flags and 3 offsets). Would it be better
> to pack them all in to a struct which we allocate only for IFUNCs? Should
> definitely save memory with large links, especially on 64-bit hosts.

 Why would it save more memory on 64-bit hosts than on 32-bit ones?  I 
would have thought the reverse is the case, owing to the host pointer size 
difference only, as the size of all the offsets is determined by the 
target ABI.  Also the bit flags are free, being merged with the existing 
ones in one 32-bit integer (and we're far from overrunning it) and I can 
only see 2 offsets added in your change, `iplt_offset' and `igot_offset' 
(why is the former of the `bfd_vma' type while the latter is plain `int' 
BTW?).

 So it looks to me like with both offsets using 32-bit types there'd be no 
saving at all on a 64-bit host and a saving of only 4 bytes per symbol on 
a 32-bit host.  I'm not convinced it's worth the extra complication.

> >  There are two reasons for regular PLT to have entries sorted.
> >  First is cache line alignment -- regular PLT entries are 16-byte long 
> > making them align to cache lines of up to 16 bytes in size, while 
> > microMIPS entries are 12-byte long (unless `--insn32' has been used) 
> > guaranteeing alignment to cache lines of up to 4 bytes only.  By placing 
> > all regular entries first they have the guaranteed 16-byte alignment.
> > 
> >  Second is the handling of synthetic symbols which is much easier with 
> > such an arrangement -- see `_bfd_mips_elf_get_synthetic_symtab'.
> > 
> >  If neither were an issue with IPLT, then you could leave entries in a 
> > random order.  But the IPLT entries also have different lengths, even the 
> > regular ones, e.g. with n64, so there will be performance implications 
> > from different ordering.
> >
> >  Can you see if you can do something about it then?  The best would be 
> > sorting entries in the decreasing alignment order and aligning the section 
> > according to the largest one.  So e.g. given the sizes of 32, 24, 20, 16, 
> > the order would be: 32, 16, 24, 20, and the section alignment -- 5.
> 
> I get the general idea. But given that we're selecting 32 vs. 64/
> micromips vs. mips16 and those combinations cannot be inter-linked, I
> don't see how we can have more than 2 stub sizes in a given link.

 I just gave a single general illustration so as not to multiply examples, 
regardless of how many sizes at a time we'll end up with.

> Besides
> after performing the delay slot optimization that you suggested elsewhere,
> all variations of mips32 will be 12 bytes. All variations of mips64 will
> be 28-bytes, except mips16/64 which could be 16-bytes.

 Or 20 bytes in the 48-bit n64 case.  Also we don't support n64 MIPS16 
with dynamic loading, so no MIPS16 n64 mixing is needed.

> Also, note that although we have 32/48-bit variants for 64-bit, the IPLT stub
> size is still the same as the regular entry. Only the # of instructions that
> need to be executed is fewer. The selection is made based on the actual
> address bits needed to get to the GOT/IGOT entry of that symbol. I should
> probably be filling the tail part of these stubs with NOPs.

 Is that to simplify processing?  We should be able to handle all exact 
sizes rather than just 2 of them at a time in a given link.

> The delay slot optimization itself requires all regular entries to be
> grouped together. Beyond that, stub sizes would only matter for mips16/64
> combination. Okay then, to just sort on regular vs. compressed and keep
> all compressed stubs before all regular ones? Compressed stubs generally
> don't have the lagging delay-slot problem, except for the micromips_insn32
> variant.

 Fine with me as long as the alignment constraints I outlined above are 
met and you can handle it all with `_bfd_mips_elf_get_synthetic_symtab' 
too (if applicable; I haven't checked).

> Related point: Should I go ahead and add all mips64 variants? Right now we
> only support regular mips64 - no compressed stubs.

 By MIPS64 do you mean both n64 and n32?

 We have no pure-microMIPS PLT support for the n32/n64 ABI implemented, 
although you should be able to create pure-microMIPS SVR4 n32/n64 
executables.  It also looks to me like o32 microMIPS PLT entries should 
work for n32 executables unchanged -- it's just that we don't have them 
wired for such use right now.  For n64 executables new PLT entries would 
have to be encoded to use LD rather than LW, and ADDIUPC can be retained 
for GOT references because n64 PLT only supports `-msym32' executables.

 Based on these observations I think it will only be reasonable to have 
n64/n32 compressed stub support implemented if the effort to do so is low.  
So use your judgement in deciding whether you want to do that or not.

 Please let me know if you have further questions or comments, and 
apologies for the long RTT.

  Maciej

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v4)
  2016-12-05 14:44                                   ` Maciej W. Rozycki
@ 2017-01-09 21:10                                     ` Faraz Shahbazker
  2017-01-24 20:41                                     ` [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v5) Faraz Shahbazker
  2017-01-24 20:41                                     ` [PATCH 2/2] " Faraz Shahbazker
  2 siblings, 0 replies; 35+ messages in thread
From: Faraz Shahbazker @ 2017-01-09 21:10 UTC (permalink / raw)
  To: Maciej W. Rozycki; +Cc: binutils, Richard Sandiford

Hi Maciej,

On 12/05/2016 06:43 AM, Maciej W. Rozycki wrote:
>> Also, note that although we have 32/48-bit variants for 64-bit, the IPLT stub
>> size is still the same as the regular entry. Only the # of instructions that
>> need to be executed is fewer. The selection is made based on the actual
>> address bits needed to get to the GOT/IGOT entry of that symbol. I should
>> probably be filling the tail part of these stubs with NOPs.
> 
>  Is that to simplify processing?  We should be able to handle all exact 
> sizes rather than just 2 of them at a time in a given link.

It might be possible - squeezing out the unused bytes, like say
relaxation would. The output address is not know at the time of initial
allocation/layout so we leave enough space for the largest possible
64-bit stub.

>> The delay slot optimization itself requires all regular entries to be
>> grouped together. Beyond that, stub sizes would only matter for mips16/64
>> combination. Okay then, to just sort on regular vs. compressed and keep
>> all compressed stubs before all regular ones? Compressed stubs generally
>> don't have the lagging delay-slot problem, except for the micromips_insn32
>> variant.
> 
>  Fine with me as long as the alignment constraints I outlined above are 
> met and you can handle it all with `_bfd_mips_elf_get_synthetic_symtab' 
> too (if applicable; I haven't checked).

Excluding all unsupported ISA combinations, and with the trailing
delay-slot optimization, we'd have 3 possible IPLT sizes:
12 bytes for all things mips32 (except mips1)
28 bytes for all things mips64
16 bytes for mips1

Even if we further optimize the 64-bit case for address range, but we
get one size for all entries within an IPLT section - be it 20 or 12
bytes. Can we thus say cache alignments are irrelevant for IPLT stubs?

Currently IPLTs are treated like stubs instead of PLT entries. As far as
I can make out, they do not fit neatly within the general paradigm of
_bfd_mips_elf_get_synthetic_symtab. For one, the original
(non-annotated) IFUNC symbol is present and defined within the object -
for regular PLT entry this is an undefined external reference.

Secondly, there aren't any relocations to connects a symbol name with an
IPLT entry. Each PLT entry has a JUMP_SLOT relocation which refers to
the symbol. IRELATIVE relocations OTOH, operate directly on entries in
the GOT/IGOT and do not map to specific IFUNC symbols. To be clear, an
IPLT symbol name has no functional significance beyond disassembly, but
if it must have a connection to the IFUNC symbol it targets, then it
must be anointed by the linker with an explicit stub name. This
connection cannot be simply synthesized from other information.

The only disadvantage of not having synthetic symbols I can see is the
inability to disassemble the address field embedded in the last word of
the mips16 IPLT entries. Is there anything else I am missing?

Note that I am still sorting on the basis of compressed vs. regular
entries even when they have the same size, because we can't have a
compressed entry follow a regular entry with its trailing delay slot.

Regards,
Faraz Shahbazker

^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH 2/2] [RFC] Add IFUNC support for MIPS (v5)
  2016-12-05 14:44                                   ` Maciej W. Rozycki
  2017-01-09 21:10                                     ` Faraz Shahbazker
  2017-01-24 20:41                                     ` [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v5) Faraz Shahbazker
@ 2017-01-24 20:41                                     ` Faraz Shahbazker
  2 siblings, 0 replies; 35+ messages in thread
From: Faraz Shahbazker @ 2017-01-24 20:41 UTC (permalink / raw)
  To: Maciej W. Rozycki; +Cc: binutils, Richard Sandiford

Test-cases.

ld/testsuite/ChangeLog:

	* ld-ifunc/ifunc.exp: Enable IFUNC tests for MIPS targets.
	* ld-mips-elf/mips-ifunc.exp: New IFUNC test script.
	* ld-mips-elf/ifunc-dyn.ld: New.
	* ld-mips-elf/ifunc-iplt.ld: New.
	* ld-mips-elf/ifunc-static.ld: New.
	* ld-mips-elf/ifunc-dyn-def.s: New.
	* ld-mips-elf/ifunc-dyn-main.s: New.
	* ld-mips-elf/ifunc-dyn-ref.s: New.
	* ld-mips-elf/ifunc-local-1.s: New.
	* ld-mips-elf/ifunc-local-2.s: New.
	* ld-mips-elf/ifunc-local-3.s: New.
	* ld-mips-elf/ifunc-local-4.s: New.
	* ld-mips-elf/ifunc-local-5.s: New.
	* ld-mips-elf/ifunc-local-6.s: New.
	* ld-mips-elf/ifunc-mix-1.s: New.
	* ld-mips-elf/ifunc-mix-2.s: New.
	* ld-mips-elf/ifunc-static-def-mips16.s: New.
	* ld-mips-elf/ifunc-static-def.s: New.
	* ld-mips-elf/ifunc-static-main-mips16.s: New.
	* ld-mips-elf/ifunc-static-main.s: New.
	* ld-mips-elf/ifunc-static-ref.s: New.
	* ld-mips-elf/ifunc-10-o32.r: New.
	* ld-mips-elf/ifunc-11-o32.r: New.
	* ld-mips-elf/ifunc-12-o32.r: New.
	* ld-mips-elf/ifunc-13-o32.r: New.
	* ld-mips-elf/ifunc-3-n32.r: New.
	* ld-mips-elf/ifunc-3-n64.r: New.
	* ld-mips-elf/ifunc-3-o32.r: New.
	* ld-mips-elf/ifunc-4-n32.r: New.
	* ld-mips-elf/ifunc-4-n64.r: New.
	* ld-mips-elf/ifunc-4-o32.r: New.
	* ld-mips-elf/ifunc-5-n32.r: New.
	* ld-mips-elf/ifunc-5-n64.r: New.
	* ld-mips-elf/ifunc-5-o32.r: New.
	* ld-mips-elf/ifunc-6-n32.r: New.
	* ld-mips-elf/ifunc-6-n64.r: New.
	* ld-mips-elf/ifunc-6-o32.r: New.
	* ld-mips-elf/ifunc-7-o32.r: New.
	* ld-mips-elf/ifunc-8-o32.r: New.
	* ld-mips-elf/ifunc-9-o32.r: New.
	* ld-mips-elf/ifunc-mix-1.sym: New.
	* ld-mips-elf/ifunc-mix-2.sym: New.
	* ld-mips-elf/ifunc-3-n32.sym: New.
	* ld-mips-elf/ifunc-3-n64.sym: New.
	* ld-mips-elf/ifunc-3-o32.sym: New.
	* ld-mips-elf/ifunc-4-n32.sym: New.
	* ld-mips-elf/ifunc-4-n64.sym: New.
	* ld-mips-elf/ifunc-4-o32.sym: New.
	* ld-mips-elf/ifunc-5-n32.sym: New.
	* ld-mips-elf/ifunc-5-n64.sym: New.
	* ld-mips-elf/ifunc-5-o32.sym: New.
	* ld-mips-elf/ifunc-6-n32.sym: New.
	* ld-mips-elf/ifunc-6-n64.sym: New.
	* ld-mips-elf/ifunc-6-o32.sym: New.
	* ld-mips-elf/libifunc-1-n32.sym: New.
	* ld-mips-elf/libifunc-1-n64.sym: New.
	* ld-mips-elf/libifunc-1-o32.sym: New.
	* ld-mips-elf/libifunc-2-n32.sym: New.
	* ld-mips-elf/libifunc-2-n64.sym: New.
	* ld-mips-elf/libifunc-2-o32.sym: New.
	* ld-mips-elf/ifunc-mix-1.t: New.
	* ld-mips-elf/ifunc-mix-2.t: New.
	* ld-mips-elf/ifunc-13-o32.t: New.
	* ld-mips-elf/ifunc-3-n32.t: New.
	* ld-mips-elf/ifunc-3-n64.t: New.
	* ld-mips-elf/ifunc-3-o32.t: New.
	* ld-mips-elf/ifunc-4-n32.t: New.
	* ld-mips-elf/ifunc-4-n64.t: New.
	* ld-mips-elf/ifunc-4-o32.t: New.
	* ld-mips-elf/ifunc-9-o32.t: New.
	* ld-mips-elf/ifunc-iplt-0x4000000000000.t: New.
	* ld-mips-elf/ifunc-iplt-0x400000000.t: New.
	* ld-mips-elf/ifunc-iplt-0x400000.t: New.
	* ld-mips-elf/ifunc-iplt-micromips.t: New.
	* ld-mips-elf/ifunc-iplt-mips16.t: New.
	* ld-mips-elf/ifunc-iplt-mips32r6.t: New.
	* ld-mips-elf/ifunc-iplt-mix-1.t: New.
	* ld-mips-elf/ifunc-iplt-mix-2.t: New.
	* ld-mips-elf/ifunc-5-n32.g: New.
	* ld-mips-elf/ifunc-5-n64.g: New.
	* ld-mips-elf/ifunc-5-o32.g: New.
	* ld-mips-elf/ifunc-iplt-micromips.igot: New.
	* ld-mips-elf/ifunc-iplt-mips16.igot: New.
	* ld-mips-elf/ifunc-mix-1.igot: New.
	* ld-mips-elf/ifunc-mix-2.igot: New.
---
 ld/testsuite/ld-ifunc/ifunc.exp                    |    5 +-
 ld/testsuite/ld-mips-elf/ifunc-10-o32.r            |    3 +
 ld/testsuite/ld-mips-elf/ifunc-10-o32.sec          |    5 +
 ld/testsuite/ld-mips-elf/ifunc-10.s                |  130 ++++++++++
 ld/testsuite/ld-mips-elf/ifunc-11-o32.got          |    4 +
 ld/testsuite/ld-mips-elf/ifunc-11-o32.r            |    3 +
 ld/testsuite/ld-mips-elf/ifunc-11-o32.sec          |    5 +
 ld/testsuite/ld-mips-elf/ifunc-11.s                |   69 +++++
 ld/testsuite/ld-mips-elf/ifunc-12-o32.got          |    4 +
 ld/testsuite/ld-mips-elf/ifunc-12-o32.r            |    3 +
 ld/testsuite/ld-mips-elf/ifunc-12-o32.sec          |    5 +
 ld/testsuite/ld-mips-elf/ifunc-12.s                |   74 ++++++
 ld/testsuite/ld-mips-elf/ifunc-13-o32.r            |    3 +
 ld/testsuite/ld-mips-elf/ifunc-13-o32.sec          |    3 +
 ld/testsuite/ld-mips-elf/ifunc-13-o32.t            |    7 +
 ld/testsuite/ld-mips-elf/ifunc-3-n32.r             |    3 +
 ld/testsuite/ld-mips-elf/ifunc-3-n32.sym           |    3 +
 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           |    3 +
 ld/testsuite/ld-mips-elf/ifunc-3-n64.t             |   11 +
 ld/testsuite/ld-mips-elf/ifunc-3-o32.r             |    3 +
 ld/testsuite/ld-mips-elf/ifunc-3-o32.sym           |    3 +
 ld/testsuite/ld-mips-elf/ifunc-3-o32.t             |   10 +
 ld/testsuite/ld-mips-elf/ifunc-4-n32.r             |    3 +
 ld/testsuite/ld-mips-elf/ifunc-4-n32.sym           |    4 +
 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.sym           |    4 +
 ld/testsuite/ld-mips-elf/ifunc-4-n64.t             |   11 +
 ld/testsuite/ld-mips-elf/ifunc-4-o32.r             |    3 +
 ld/testsuite/ld-mips-elf/ifunc-4-o32.sym           |    4 +
 ld/testsuite/ld-mips-elf/ifunc-4-o32.t             |   10 +
 ld/testsuite/ld-mips-elf/ifunc-5-n32.got           |    5 +
 ld/testsuite/ld-mips-elf/ifunc-5-n32.r             |    3 +
 ld/testsuite/ld-mips-elf/ifunc-5-n32.sym           |    3 +
 ld/testsuite/ld-mips-elf/ifunc-5-n64.got           |    5 +
 ld/testsuite/ld-mips-elf/ifunc-5-n64.r             |    5 +
 ld/testsuite/ld-mips-elf/ifunc-5-n64.sym           |    3 +
 ld/testsuite/ld-mips-elf/ifunc-5-o32.got           |    5 +
 ld/testsuite/ld-mips-elf/ifunc-5-o32.r             |    3 +
 ld/testsuite/ld-mips-elf/ifunc-5-o32.sym           |    3 +
 ld/testsuite/ld-mips-elf/ifunc-5.dyn               |    3 +
 ld/testsuite/ld-mips-elf/ifunc-6-n32.r             |    3 +
 ld/testsuite/ld-mips-elf/ifunc-6-n32.sym           |    3 +
 ld/testsuite/ld-mips-elf/ifunc-6-n64.r             |    5 +
 ld/testsuite/ld-mips-elf/ifunc-6-n64.sym           |    3 +
 ld/testsuite/ld-mips-elf/ifunc-6-o32.r             |    3 +
 ld/testsuite/ld-mips-elf/ifunc-6-o32.sym           |    3 +
 ld/testsuite/ld-mips-elf/ifunc-6.dyn               |    3 +
 ld/testsuite/ld-mips-elf/ifunc-7-o32.r             |    3 +
 ld/testsuite/ld-mips-elf/ifunc-7-o32.sec           |    4 +
 ld/testsuite/ld-mips-elf/ifunc-7.s                 |  115 +++++++++
 ld/testsuite/ld-mips-elf/ifunc-8-o32.got           |    4 +
 ld/testsuite/ld-mips-elf/ifunc-8-o32.r             |    3 +
 ld/testsuite/ld-mips-elf/ifunc-8-o32.sec           |   17 ++
 ld/testsuite/ld-mips-elf/ifunc-8.s                 |   64 +++++
 ld/testsuite/ld-mips-elf/ifunc-9-o32.got           |    4 +
 ld/testsuite/ld-mips-elf/ifunc-9-o32.r             |    3 +
 ld/testsuite/ld-mips-elf/ifunc-9-o32.sec           |   17 ++
 ld/testsuite/ld-mips-elf/ifunc-9-o32.t             |    7 +
 ld/testsuite/ld-mips-elf/ifunc-9.s                 |   94 +++++++
 ld/testsuite/ld-mips-elf/ifunc-dyn-def.s           |   40 +++
 ld/testsuite/ld-mips-elf/ifunc-dyn-main.s          |   21 ++
 ld/testsuite/ld-mips-elf/ifunc-dyn-ref.s           |   24 ++
 ld/testsuite/ld-mips-elf/ifunc-dyn.ld              |   27 ++
 ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000.t     |   11 +
 ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000000.t  |   13 +
 .../ld-mips-elf/ifunc-iplt-0x4000000000000.t       |   14 +
 ld/testsuite/ld-mips-elf/ifunc-iplt-micromips.igot |    4 +
 ld/testsuite/ld-mips-elf/ifunc-iplt-micromips.t    |   10 +
 ld/testsuite/ld-mips-elf/ifunc-iplt-mips16.igot    |    4 +
 ld/testsuite/ld-mips-elf/ifunc-iplt-mips16.t       |   10 +
 ld/testsuite/ld-mips-elf/ifunc-iplt-mips32r6.t     |   10 +
 ld/testsuite/ld-mips-elf/ifunc-iplt-mix-1.t        |   29 +++
 ld/testsuite/ld-mips-elf/ifunc-iplt-mix-2.t        |   27 ++
 ld/testsuite/ld-mips-elf/ifunc-iplt.ld             |   27 ++
 ld/testsuite/ld-mips-elf/ifunc-mix-1.igot          |    3 +
 ld/testsuite/ld-mips-elf/ifunc-mix-1.s             |   53 ++++
 ld/testsuite/ld-mips-elf/ifunc-mix-1.sym           |   15 ++
 ld/testsuite/ld-mips-elf/ifunc-mix-1.t             |   18 ++
 ld/testsuite/ld-mips-elf/ifunc-mix-2.igot          |    3 +
 ld/testsuite/ld-mips-elf/ifunc-mix-2.s             |   55 ++++
 ld/testsuite/ld-mips-elf/ifunc-mix-2.sym           |   15 ++
 ld/testsuite/ld-mips-elf/ifunc-mix-2.t             |   19 ++
 ld/testsuite/ld-mips-elf/ifunc-static-def-mips16.s |  124 +++++++++
 ld/testsuite/ld-mips-elf/ifunc-static-def.s        |   98 +++++++
 .../ld-mips-elf/ifunc-static-main-mips16.s         |   18 ++
 ld/testsuite/ld-mips-elf/ifunc-static-main.s       |   21 ++
 ld/testsuite/ld-mips-elf/ifunc-static-ref.s        |   12 +
 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        |    4 +
 ld/testsuite/ld-mips-elf/mips-ifunc.exp            |  273
++++++++++++++++++++
 98 files changed, 1871 insertions(+), 2 deletions(-)
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-10-o32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-10-o32.sec
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-10.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-11-o32.got
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-11-o32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-11-o32.sec
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-11.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-12-o32.got
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-12-o32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-12-o32.sec
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-12.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-13-o32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-13-o32.sec
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-13-o32.t
 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.sym
 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.sym
 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.sym
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-4-o32.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-5-n32.got
 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.got
 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.got
 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-5.dyn
 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-6.dyn
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-7-o32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-7-o32.sec
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-7.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-8-o32.got
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-8-o32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-8-o32.sec
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-8.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-9-o32.got
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-9-o32.r
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-9-o32.sec
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-9-o32.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-9.s
 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-0x400000.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000000.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt-0x4000000000000.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt-micromips.igot
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt-micromips.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt-mips16.igot
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt-mips16.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt-mips32r6.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt-mix-1.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt-mix-2.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-iplt.ld
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-mix-1.igot
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-mix-1.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-mix-1.sym
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-mix-1.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-mix-2.igot
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-mix-2.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-mix-2.sym
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-mix-2.t
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-static-def-mips16.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-static-def.s
 create mode 100644 ld/testsuite/ld-mips-elf/ifunc-static-main-mips16.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-ifunc/ifunc.exp
b/ld/testsuite/ld-ifunc/ifunc.exp
index ffff2f1..86baa93 100644
--- a/ld/testsuite/ld-ifunc/ifunc.exp
+++ b/ld/testsuite/ld-ifunc/ifunc.exp
@@ -24,13 +24,14 @@


 # IFUNC support has only been implemented for the ix86, x86_64, powerpc,
-# aarch64, sparc, and S/390 so far.
+# aarch64, sparc, S/390 and mips so far.
 if {!(([istarget "i?86-*-*"]
        || [istarget "x86_64-*-*"]
        || [istarget "powerpc*-*-*"]
        || [istarget "aarch64*-*-*"]
        || [istarget "sparc*-*-*"]
-       || [istarget "s390*-*-*"])
+       || [istarget "s390*-*-*"]
+       || [istarget "mips*-*-*"])
       && ([istarget "*-*-elf*"]
 	  || [istarget "*-*-nacl*"]
 	  || (([istarget "*-*-linux*"]
diff --git a/ld/testsuite/ld-mips-elf/ifunc-10-o32.r
b/ld/testsuite/ld-mips-elf/ifunc-10-o32.r
new file mode 100644
index 0000000..97e99c4
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-10-o32.r
@@ -0,0 +1,3 @@
+#...
+00410214  00000080 R_MIPS_IRELATIVE *
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-10-o32.sec
b/ld/testsuite/ld-mips-elf/ifunc-10-o32.sec
new file mode 100644
index 0000000..1c06e46
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-10-o32.sec
@@ -0,0 +1,5 @@
+#...
+.* \.iplt .*
+.*
+.* \.igot .*
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-10.s
b/ld/testsuite/ld-mips-elf/ifunc-10.s
new file mode 100644
index 0000000..f3d58ce
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-10.s
@@ -0,0 +1,130 @@
+	.abicalls
+	.option	pic0
+	.text
+	.align	2
+	.ent	f1_a
+	.type	f1_a, @function
+f1_a:
+	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
+
+	.end	f1_a
+	.size	f1_a, .-f1_a
+	.align	2
+	.ent	f1_b
+	.type	f1_b, @function
+f1_b:
+	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
+
+	.end	f1_b
+	.size	f1_b, .-f1_b
+	.align	2
+	.ent	f1_c
+	.type	f1_c, @function
+f1_c:
+	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
+
+	.end	f1_c
+	.size	f1_c, .-f1_c
+	.align	2
+	.globl	func1_ifunc
+	.ent	func1_ifunc
+	.type	func1_ifunc, @function
+func1_ifunc:
+	addiu	$sp,$sp,-432
+	sw	$31,428($sp)
+	sw	$fp,424($sp)
+	move	$fp,$sp
+	addiu	$2,$fp,28
+	move	$4,$2
+	beq	$2,$0,$L8
+
+	li	$2,48			# 0x30
+	sw	$2,24($fp)
+	j	$L9
+
+$L8:
+	li	$2,3			# 0x3
+	sw	$2,24($fp)
+$L9:
+	lw	$2,24($fp)
+	andi	$2,$2,0xf0
+	beq	$2,$0,$L10
+
+	lui	$2,%hi(f1_a)
+	addiu	$2,$2,%lo(f1_a)
+	j	$L13
+
+$L10:
+	lw	$2,24($fp)
+	andi	$2,$2,0xf
+	beq	$2,$0,$L12
+
+	lui	$2,%hi(f1_b)
+	addiu	$2,$2,%lo(f1_b)
+	j	$L13
+
+$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
+
+	.end	func1_ifunc
+	.size	func1_ifunc, .-func1_ifunc
+	.type	func1, @gnu_indirect_function
+	func1 = func1_ifunc
+
+	.align	2
+	.globl	main
+	.set	nomips16
+	.set	nomicromips
+	.ent	main
+	.option	pic0
+	.type	main, @function
+main:
+	addiu	$sp,$sp,-32
+	sw	$31,28($sp)
+	sw	$fp,24($sp)
+	move	$fp,$sp
+	jal	func1
+
+	move	$sp,$fp
+	lw	$31,28($sp)
+	lw	$fp,24($sp)
+	addiu	$sp,$sp,32
+	j	$31
+
+	.end	main
+	.size	main, .-main
+
+	.section        .data.rel,"aw",@progbits
+	.align  2
+	.type   fptr, @object
+	.size   fptr, 4
+fptr:
+	.word   func1
diff --git a/ld/testsuite/ld-mips-elf/ifunc-11-o32.got
b/ld/testsuite/ld-mips-elf/ifunc-11-o32.got
new file mode 100644
index 0000000..c307e1f
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-11-o32.got
@@ -0,0 +1,4 @@
+#...
+Contents of section \.got:
+ 410180 00000000 80000000 00400000 00400160 .*
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-11-o32.r
b/ld/testsuite/ld-mips-elf/ifunc-11-o32.r
new file mode 100644
index 0000000..deeb023
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-11-o32.r
@@ -0,0 +1,3 @@
+#...
+00410174  00000080 R_MIPS_IRELATIVE *
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-11-o32.sec
b/ld/testsuite/ld-mips-elf/ifunc-11-o32.sec
new file mode 100644
index 0000000..1c06e46
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-11-o32.sec
@@ -0,0 +1,5 @@
+#...
+.* \.iplt .*
+.*
+.* \.igot .*
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-11.s
b/ld/testsuite/ld-mips-elf/ifunc-11.s
new file mode 100644
index 0000000..609837f
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-11.s
@@ -0,0 +1,69 @@
+	.gnu_attribute 4, 1
+	.abicalls
+	.text
+	.ent	f1_a
+	.type	f1_a, @function
+f1_a:
+	nop
+
+	.end	f1_a
+	.size	f1_a, .-f1_a
+	.ent	f1_b
+	.type	f1_b, @function
+f1_b:
+	nop
+
+	.end	f1_b
+	.size	f1_b, .-f1_b
+	.ent	f1_c
+	.type	f1_c, @function
+f1_c:
+	.frame	$fp,8,$31		# vars= 0, regs= 1/0, args= 0, gp= 0
+	nop
+
+	.end	f1_c
+	.size	f1_c, .-f1_c
+	.globl	func1_ifunc
+	.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)
+
+	.end	func1_ifunc
+	.size	func1_ifunc, .-func1_ifunc
+	.type	func1, @gnu_indirect_function
+	func1 = func1_ifunc
+
+	.globl	main
+	.ent	main
+	.type	main, @function
+main:
+	.cpload	$25
+	addiu	$sp,$sp,-32
+	sw	$31,28($sp)
+	sw	$fp,24($sp)
+	move	$fp,$sp
+	.cprestore	16
+	lw	$2,%got(func1)($28)
+	move	$25,$2
+	.reloc	1f,R_MIPS_JALR,func1
+1:	jalr	$25
+	move	$sp,$fp
+	lw	$31,28($sp)
+	lw	$fp,24($sp)
+	addiu	$sp,$sp,32
+	j	$31
+
+	.end	main
+	.size	main, .-main
+	.section	.data.rel,"aw",@progbits
+	.align	2
+	.type	fptr, @object
+	.size	fptr, 4
+fptr:
+	.word	func1
diff --git a/ld/testsuite/ld-mips-elf/ifunc-12-o32.got
b/ld/testsuite/ld-mips-elf/ifunc-12-o32.got
new file mode 100644
index 0000000..797f5ed
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-12-o32.got
@@ -0,0 +1,4 @@
+#...
+Contents of section \.got:
+ 410190 00000000 80000000 00400000 00400170  .*
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-12-o32.r
b/ld/testsuite/ld-mips-elf/ifunc-12-o32.r
new file mode 100644
index 0000000..9d52278
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-12-o32.r
@@ -0,0 +1,3 @@
+#...
+00410184  00000080 R_MIPS_IRELATIVE *
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-12-o32.sec
b/ld/testsuite/ld-mips-elf/ifunc-12-o32.sec
new file mode 100644
index 0000000..1c06e46
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-12-o32.sec
@@ -0,0 +1,5 @@
+#...
+.* \.iplt .*
+.*
+.* \.igot .*
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-12.s
b/ld/testsuite/ld-mips-elf/ifunc-12.s
new file mode 100644
index 0000000..779e8b2
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-12.s
@@ -0,0 +1,74 @@
+	.gnu_attribute 4, 1
+	.abicalls
+	.text
+	.ent	f1_a
+	.type	f1_a, @function
+f1_a:
+	nop
+	.end	f1_a
+	.size	f1_a, .-f1_a
+
+	.ent	f1_b
+	.type	f1_b, @function
+f1_b:
+	nop
+	.end	f1_b
+	.size	f1_b, .-f1_b
+
+	.ent	f1_c
+	.type	f1_c, @function
+f1_c:
+	.frame	$fp,8,$31		# vars= 0, regs= 1/0, args= 0, gp= 0
+	nop
+	.end	f1_c
+	.size	f1_c, .-f1_c
+
+	.globl	func1_ifunc
+	.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)
+	.end	func1_ifunc
+	.size	func1_ifunc, .-func1_ifunc
+
+	.type	func1, @gnu_indirect_function
+	func1 = func1_ifunc
+
+	.globl	main
+	.ent	main
+	.type	main, @function
+main:
+	.cpload	$25
+	addiu	$sp,$sp,-32
+	sw	$31,28($sp)
+	sw	$fp,24($sp)
+	move	$fp,$sp
+	.cprestore	16
+	lw	$2,%got(func1)($28)
+	move	$25,$2
+	jalr	$25
+
+	lui	$2,%hi(func1)
+	addiu	$2, $2, %lo(func1)
+
+	lw	$28,16($fp)
+	move	$sp,$fp
+	lw	$31,28($sp)
+	lw	$fp,24($sp)
+	addiu	$sp,$sp,32
+	j	$31
+
+	.end	main
+	.size	main, .-main
+
+	.section	.data.rel,"aw",@progbits
+	.align	2
+	.type	fptr, @object
+	.size	fptr, 4
+fptr:
+	.word	func1
diff --git a/ld/testsuite/ld-mips-elf/ifunc-13-o32.r
b/ld/testsuite/ld-mips-elf/ifunc-13-o32.r
new file mode 100644
index 0000000..9f31964
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-13-o32.r
@@ -0,0 +1,3 @@
+#...
+004101a8  00000080 R_MIPS_IRELATIVE *
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-13-o32.sec
b/ld/testsuite/ld-mips-elf/ifunc-13-o32.sec
new file mode 100644
index 0000000..77fa4da
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-13-o32.sec
@@ -0,0 +1,3 @@
+#...
+.* \.iplt .*
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-13-o32.t
b/ld/testsuite/ld-mips-elf/ifunc-13-o32.t
new file mode 100644
index 0000000..5e7e527
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-13-o32.t
@@ -0,0 +1,7 @@
+#...
+00400160 <main>:
+.*
+.*
+.*
+  40016c:	0c100064 	jal	400190 <\.iplt\.func1>
+#pass
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..c0c5188
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-n32.r
@@ -0,0 +1,3 @@
+#...
+00000c00  00000080 R_MIPS_IRELATIVE *
+#pass
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..ef58b9f
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-n32.sym
@@ -0,0 +1,3 @@
+#...
+    2   0: 00000800    24 FUNC    GLOBAL DEFAULT   1 func1
+#pass
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..96cd6d6
--- /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:
+
+00000800 <.iplt.func1>:
+ 800:	3c0f0000 	lui	t3,0x0
+ 804:	8df90c00 	lw	t9,3072\(t3\)
+ 808:	03200008 	jr	t9
+ 80c:	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..531a6a1
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-n64.r
@@ -0,0 +1,5 @@
+#...
+000000000c00  000000001280 R_MIPS_IRELATIVE *
+                    Type2: R_MIPS_64 *
+                    Type3: R_MIPS_NONE *
+#pass
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..64a1bc7
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-n64.sym
@@ -0,0 +1,3 @@
+#...
+    2   0: 0000000000000800    24 FUNC    GLOBAL DEFAULT   1 func1
+#pass
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..8bdab3b
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-n64.t
@@ -0,0 +1,11 @@
+tmpdir/ifunc-3-n64:     file format elf64-tradbigmips
+
+
+Disassembly of section .iplt:
+
+0000000000000800 <.iplt.func1>:
+ 800:	3c0f0000 	lui	t3,0x0
+ 804:	ddf90c00 	ld	t9,3072\(t3\)
+ 808:	03200008 	jr	t9
+ 80c:	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..c0c5188
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-o32.r
@@ -0,0 +1,3 @@
+#...
+00000c00  00000080 R_MIPS_IRELATIVE *
+#pass
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..ef58b9f
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-o32.sym
@@ -0,0 +1,3 @@
+#...
+    2   0: 00000800    24 FUNC    GLOBAL DEFAULT   1 func1
+#pass
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..f2e9ec3
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-3-o32.t
@@ -0,0 +1,10 @@
+tmpdir/ifunc-3-o32:     file format elf32-tradbigmips
+
+
+Disassembly of section .iplt:
+
+00000800 <.iplt.func1>:
+ 800:	3c0f0000 	lui	t7,0x0
+ 804:	8df90c00 	lw	t9,3072\(t7\)
+ 808:	03200008 	jr	t9
+ 80c:	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..b3cfa8a
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-n32.r
@@ -0,0 +1,3 @@
+#...
+00080800  00000080 R_MIPS_IRELATIVE *
+#pass
\ No newline at end of file
diff --git a/ld/testsuite/ld-mips-elf/ifunc-4-n32.sym
b/ld/testsuite/ld-mips-elf/ifunc-4-n32.sym
new file mode 100644
index 0000000..8803279
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-n32.sym
@@ -0,0 +1,4 @@
+#...
+    19: 00080000    16 FUNC    LOCAL  DEFAULT    1 .iplt.func1
+    20: 00080490   132 IFUNC   GLOBAL DEFAULT    2 func1
+#pass
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..da8be1d
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-n64.r
@@ -0,0 +1,5 @@
+#...
+000000080800  000000001280 R_MIPS_IRELATIVE *
+                    Type2: R_MIPS_64 *
+                    Type3: R_MIPS_NONE *
+#pass
\ No newline at end of file
diff --git a/ld/testsuite/ld-mips-elf/ifunc-4-n64.sym
b/ld/testsuite/ld-mips-elf/ifunc-4-n64.sym
new file mode 100644
index 0000000..8061f9a
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-n64.sym
@@ -0,0 +1,4 @@
+#...
+    19: 0000000000080000    32 FUNC    LOCAL  DEFAULT    1 .iplt.func1
+    20: 0000000000080490   132 IFUNC   GLOBAL DEFAULT    2 func1
+#pass
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..bf5a2d2
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-n64.t
@@ -0,0 +1,11 @@
+tmpdir/ifunc-4-n64:     file format elf64-tradbigmips
+
+
+Disassembly of section .iplt:
+
+0000000000080000 <.iplt.func1>:
+   80000:	3c0f0008 	lui	t3,0x8
+   80004:	ddf90800 	ld	t9,2048\(t3\)
+   80008:	03200008 	jr	t9
+   8000c:	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..81eb2e4
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-o32.r
@@ -0,0 +1,3 @@
+#...
+00080800  00000080 R_MIPS_IRELATIVE *
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-4-o32.sym
b/ld/testsuite/ld-mips-elf/ifunc-4-o32.sym
new file mode 100644
index 0000000..9bdc7d6
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-o32.sym
@@ -0,0 +1,4 @@
+#...
+    14: 00080000    16 FUNC    LOCAL  DEFAULT    1 .iplt.func1
+    15: 00080490   132 IFUNC   GLOBAL DEFAULT    2 func1
+#pass
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..1ae0031
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-4-o32.t
@@ -0,0 +1,10 @@
+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:	03200008 	jr	t9
+   8000c:	00000000 	nop
diff --git a/ld/testsuite/ld-mips-elf/ifunc-5-n32.got
b/ld/testsuite/ld-mips-elf/ifunc-5-n32.got
new file mode 100644
index 0000000..cc20d56
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-n32.got
@@ -0,0 +1,5 @@
+#...
+ General entries:
+   Address     Access  Initial
+  00000808 -32744\(gp\) 0000040c
+#pass
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..72a1f0c
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-n32.r
@@ -0,0 +1,3 @@
+#...
+00000808  00000203 R_MIPS_REL32      func1\(\)    func1
+#pass
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..33f34cc
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-n32.sym
@@ -0,0 +1,3 @@
+#...
+    2   0: 0000040c    24 IFUNC   GLOBAL DEFAULT   1 func1
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-5-n64.got
b/ld/testsuite/ld-mips-elf/ifunc-5-n64.got
new file mode 100644
index 0000000..63fe9d5
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-n64.got
@@ -0,0 +1,5 @@
+#...
+ General entries:
+           Address     Access          Initial
+  0000000000000810 -32736\(gp\) 000000000000040c
+#pass
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..54dd021
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-n64.r
@@ -0,0 +1,5 @@
+#...
+000000000810  000200001203 R_MIPS_REL32      func1\(\)          func1
+                    Type2: R_MIPS_64 *
+                    Type3: R_MIPS_NONE *
+#pass
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..61b491e
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-n64.sym
@@ -0,0 +1,3 @@
+#...
+    2   0: 000000000000040c    24 IFUNC   GLOBAL DEFAULT   1 func1
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-5-o32.got
b/ld/testsuite/ld-mips-elf/ifunc-5-o32.got
new file mode 100644
index 0000000..cc20d56
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-o32.got
@@ -0,0 +1,5 @@
+#...
+ General entries:
+   Address     Access  Initial
+  00000808 -32744\(gp\) 0000040c
+#pass
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..72a1f0c
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-o32.r
@@ -0,0 +1,3 @@
+#...
+00000808  00000203 R_MIPS_REL32      func1\(\)    func1
+#pass
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..33f34cc
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5-o32.sym
@@ -0,0 +1,3 @@
+#...
+    2   0: 0000040c    24 IFUNC   GLOBAL DEFAULT   1 func1
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-5.dyn
b/ld/testsuite/ld-mips-elf/ifunc-5.dyn
new file mode 100644
index 0000000..582a8d4
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-5.dyn
@@ -0,0 +1,3 @@
+#...
+ 0x0*70000036 \(MIPS_GENERAL_GOTNO\).* 3
+#pass
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..ea30a44
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-n32.r
@@ -0,0 +1,3 @@
+#...
+00000c08  00000080 R_MIPS_IRELATIVE *
+#pass
\ No newline at end of file
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..ef58b9f
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-n32.sym
@@ -0,0 +1,3 @@
+#...
+    2   0: 00000800    24 FUNC    GLOBAL DEFAULT   1 func1
+#pass
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..6155b9b
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-n64.r
@@ -0,0 +1,5 @@
+#...
+000000000c10  000000001280 R_MIPS_IRELATIVE *
+                    Type2: R_MIPS_64 *
+                    Type3: R_MIPS_NONE *
+#pass
\ No newline at end of file
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..64a1bc7
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-n64.sym
@@ -0,0 +1,3 @@
+#...
+    2   0: 0000000000000800    24 FUNC    GLOBAL DEFAULT   1 func1
+#pass
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..ea30a44
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-o32.r
@@ -0,0 +1,3 @@
+#...
+00000c08  00000080 R_MIPS_IRELATIVE *
+#pass
\ No newline at end of file
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..ef58b9f
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6-o32.sym
@@ -0,0 +1,3 @@
+#...
+    2   0: 00000800    24 FUNC    GLOBAL DEFAULT   1 func1
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-6.dyn
b/ld/testsuite/ld-mips-elf/ifunc-6.dyn
new file mode 100644
index 0000000..582a8d4
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-6.dyn
@@ -0,0 +1,3 @@
+#...
+ 0x0*70000036 \(MIPS_GENERAL_GOTNO\).* 3
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-7-o32.r
b/ld/testsuite/ld-mips-elf/ifunc-7-o32.r
new file mode 100644
index 0000000..1b3e164
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-7-o32.r
@@ -0,0 +1,3 @@
+#...
+00410210  00000080 R_MIPS_IRELATIVE *
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-7-o32.sec
b/ld/testsuite/ld-mips-elf/ifunc-7-o32.sec
new file mode 100644
index 0000000..c904010
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-7-o32.sec
@@ -0,0 +1,4 @@
+#...
+.* \.iplt .*
+.* \.igot .*
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-7.s
b/ld/testsuite/ld-mips-elf/ifunc-7.s
new file mode 100644
index 0000000..993bf08
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-7.s
@@ -0,0 +1,115 @@
+	.abicalls
+	.option	pic0
+	.text
+	.ent	f1_a
+	.type	f1_a, @function
+f1_a:
+	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
+	.end	f1_a
+	.size	f1_a, .-f1_a
+
+	.ent	f1_b
+	.type	f1_b, @function
+f1_b:
+	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
+	.end	f1_b
+	.size	f1_b, .-f1_b
+
+	.ent	f1_c
+	.type	f1_c, @function
+f1_c:
+	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
+	.end	f1_c
+	.size	f1_c, .-f1_c
+
+	.globl	func1_ifunc
+	.ent	func1_ifunc
+	.type	func1_ifunc, @function
+func1_ifunc:
+	addiu	$sp,$sp,-432
+	sw	$31,428($sp)
+	sw	$fp,424($sp)
+	move	$fp,$sp
+	addiu	$2,$fp,28
+	move	$4,$2
+	beq	$2,$0,$L8
+	li	$2,48			# 0x30
+	sw	$2,24($fp)
+	j	$L9
+
+$L8:
+	li	$2,3			# 0x3
+	sw	$2,24($fp)
+$L9:
+	lw	$2,24($fp)
+	andi	$2,$2,0xf0
+	beq	$2,$0,$L10
+
+	lui	$2,%hi(f1_a)
+	addiu	$2,$2,%lo(f1_a)
+	j	$L13
+
+$L10:
+	lw	$2,24($fp)
+	andi	$2,$2,0xf
+	beq	$2,$0,$L12
+
+	lui	$2,%hi(f1_b)
+	addiu	$2,$2,%lo(f1_b)
+	j	$L13
+
+$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
+	.end	func1_ifunc
+	.size	func1_ifunc, .-func1_ifunc
+
+	.type	func1, @gnu_indirect_function
+	func1 = func1_ifunc
+
+	.globl	main
+	.ent	main
+	.option	pic0
+	.type	main, @function
+main:
+	addiu	$sp,$sp,-32
+	sw	$31,28($sp)
+	sw	$fp,24($sp)
+	move	$fp,$sp
+	jal	func1
+
+	move	$sp,$fp
+	lw	$31,28($sp)
+	lw	$fp,24($sp)
+	addiu	$sp,$sp,32
+	j	$31
+
+	.end	main
+	.size	main, .-main
diff --git a/ld/testsuite/ld-mips-elf/ifunc-8-o32.got
b/ld/testsuite/ld-mips-elf/ifunc-8-o32.got
new file mode 100644
index 0000000..0a1b3df
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-8-o32.got
@@ -0,0 +1,4 @@
+#...
+Contents of section \.got:
+ 410160 00000000 80000000 004000fc .*
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-8-o32.r
b/ld/testsuite/ld-mips-elf/ifunc-8-o32.r
new file mode 100644
index 0000000..75ea935
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-8-o32.r
@@ -0,0 +1,3 @@
+#...
+00410168  00000080 R_MIPS_IRELATIVE *
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-8-o32.sec
b/ld/testsuite/ld-mips-elf/ifunc-8-o32.sec
new file mode 100644
index 0000000..fe3d6ba
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-8-o32.sec
@@ -0,0 +1,17 @@
+#...
+Section Headers:
+!.* \.iplt .*
+!.* \.iplt .*
+!.* \.iplt .*
+!.* \.iplt .*
+!.* \.iplt .*
+!.* \.iplt .*
+.* \.got .*
+!.* \.iplt .*
+!.* \.iplt .*
+!.* \.iplt .*
+!.* \.iplt .*
+!.* \.iplt .*
+!.* \.iplt .*
+!.* \.iplt .*
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-8.s
b/ld/testsuite/ld-mips-elf/ifunc-8.s
new file mode 100644
index 0000000..667b0cd
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-8.s
@@ -0,0 +1,64 @@
+	.gnu_attribute 4, 1
+	.abicalls
+	.text
+	.ent	f1_a
+	.type	f1_a, @function
+f1_a:
+	nop
+	.end	f1_a
+	.size	f1_a, .-f1_a
+
+	.ent	f1_b
+	.type	f1_b, @function
+f1_b:
+	nop
+	.end	f1_b
+	.size	f1_b, .-f1_b
+
+	.ent	f1_c
+	.type	f1_c, @function
+f1_c:
+	nop
+	.end	f1_c
+	.size	f1_c, .-f1_c
+
+	.globl	func1_ifunc
+	.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)
+	.end	func1_ifunc
+	.size	func1_ifunc, .-func1_ifunc
+
+	.type	func1, @gnu_indirect_function
+	func1 = func1_ifunc
+
+	.globl	main
+	.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
+	.cpload	$25
+	addiu	$sp,$sp,-32
+	sw	$31,28($sp)
+	sw	$fp,24($sp)
+	move	$fp,$sp
+	.cprestore	16
+	lw	$2,%got(func1)($28)
+	move	$25,$2
+1:	jalr	$25
+	move	$sp,$fp
+	lw	$31,28($sp)
+	lw	$fp,24($sp)
+	addiu	$sp,$sp,32
+	j	$31
+
+	.end	main
+	.size	main, .-main
diff --git a/ld/testsuite/ld-mips-elf/ifunc-9-o32.got
b/ld/testsuite/ld-mips-elf/ifunc-9-o32.got
new file mode 100644
index 0000000..7be3b66
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-9-o32.got
@@ -0,0 +1,4 @@
+#...
+Contents of section \.got:
+ 410190 00000000 80000000 004000fc .*
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-9-o32.r
b/ld/testsuite/ld-mips-elf/ifunc-9-o32.r
new file mode 100644
index 0000000..a28c20b
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-9-o32.r
@@ -0,0 +1,3 @@
+#...
+00410198  00000080 R_MIPS_IRELATIVE *
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-9-o32.sec
b/ld/testsuite/ld-mips-elf/ifunc-9-o32.sec
new file mode 100644
index 0000000..345a261
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-9-o32.sec
@@ -0,0 +1,17 @@
+#...
+Section Headers:
+!.* \.igot .*
+!.* \.igot .*
+!.* \.igot .*
+!.* \.igot .*
+!.* \.igot .*
+!.* \.igot .*
+.* \.iplt .*
+.* \.got .*
+!.* \.igot .*
+!.* \.igot .*
+!.* \.igot .*
+!.* \.igot .*
+!.* \.igot .*
+!.* \.igot .*
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-9-o32.t
b/ld/testsuite/ld-mips-elf/ifunc-9-o32.t
new file mode 100644
index 0000000..865d250
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-9-o32.t
@@ -0,0 +1,7 @@
+#...
+00400180 <.iplt.func1>:
+  400180:	3c0f0041 	lui	t7,0x41
+  400184:	8df90198 	lw	t9,408\(t7\)
+  400188:	03200008 	jr	t9
+  40018c:	00000000 	nop
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-9.s
b/ld/testsuite/ld-mips-elf/ifunc-9.s
new file mode 100644
index 0000000..72c8a46
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-9.s
@@ -0,0 +1,94 @@
+	.gnu_attribute 4, 1
+	.abicalls
+	.text
+	.ent	f1_a
+	.type	f1_a, @function
+f1_a:
+	nop
+	.end	f1_a
+	.size	f1_a, .-f1_a
+
+	.ent	f1_b
+	.type	f1_b, @function
+f1_b:
+	nop
+	.end	f1_b
+	.size	f1_b, .-f1_b
+
+	.ent	f1_c
+	.type	f1_c, @function
+f1_c:
+	.frame	$fp,8,$31		# vars= 0, regs= 1/0, args= 0, gp= 0
+	nop
+	.end	f1_c
+	.size	f1_c, .-f1_c
+
+	.globl	func1_ifunc
+	.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)
+	.end	func1_ifunc
+	.size	func1_ifunc, .-func1_ifunc
+
+	.type	func1, @gnu_indirect_function
+	func1 = func1_ifunc
+
+	.option	pic0
+	.globl	ref1
+	.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
+	addiu	$sp,$sp,-32
+	sw	$31,28($sp)
+	sw	$fp,24($sp)
+	move	$fp,$sp
+	jal	func1
+
+	move	$sp,$fp
+	lw	$31,28($sp)
+	lw	$fp,24($sp)
+	addiu	$sp,$sp,32
+	j	$31
+
+	.end	ref1
+	.size	ref1, .-ref1
+
+	.globl	main
+	.globl	main
+	.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
+	.cpload	$25
+	addiu	$sp,$sp,-32
+	sw	$31,28($sp)
+	sw	$fp,24($sp)
+	move	$fp,$sp
+	.cprestore	16
+.reloc	1f,R_MIPS16_CALL16,func1
+1:	lw	$2,%call16(func1)($28)
+	move	$25,$2
+	jalr	$25
+
+	jal	ref1
+
+	lw	$28,16($fp)
+	move	$sp,$fp
+	lw	$31,28($sp)
+	lw	$fp,24($sp)
+	addiu	$sp,$sp,32
+	j	$31
+
+	.end	main
+	.size	main, .-main
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..8f904f1
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-dyn-def.s
@@ -0,0 +1,40 @@
+	.gnu_attribute 4, 1
+	.abicalls
+	.text
+	.ent	f1_a
+	.type	f1_a, @function
+f1_a:
+	nop
+	.end	f1_a
+	.size	f1_a, .-f1_a
+
+	.ent	f1_b
+	.type	f1_b, @function
+f1_b:
+	nop
+	.end	f1_b
+	.size	f1_b, .-f1_b
+
+	.ent	f1_c
+	.type	f1_c, @function
+f1_c:
+	nop
+	.end	f1_c
+	.size	f1_c, .-f1_c
+
+	.globl	func1_ifunc
+	.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)
+	.end	func1_ifunc
+	.size	func1_ifunc, .-func1_ifunc
+
+	.globl	func1
+	.type	func1, @gnu_indirect_function
+	func1 = func1_ifunc
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..3f9afac
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-dyn-main.s
@@ -0,0 +1,21 @@
+	.gnu_attribute 4, 1
+	.abicalls
+	.option	pic0
+	.text
+	.globl	main
+	.ent	main
+	.type	main, @function
+main:
+	addiu	$sp,$sp,-32
+	sw	$31,28($sp)
+	sw	$fp,24($sp)
+	move	$fp,$sp
+	jal	ref1
+
+	move	$sp,$fp
+	lw	$31,28($sp)
+	lw	$fp,24($sp)
+	addiu	$sp,$sp,32
+	j	$31
+	.end	main
+	.size	main, .-main
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..284b016
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-dyn-ref.s
@@ -0,0 +1,24 @@
+	.gnu_attribute 4, 1
+	.abicalls
+	.text
+	.globl	ref1
+	.ent	ref1
+	.type	ref1, @function
+ref1:
+	addiu	$sp,$sp,-32
+	sw	$31,28($sp)
+	sw	$fp,24($sp)
+	move	$fp,$sp
+	lw	$2,%call16(func1)($28)
+	move	$25,$2
+	jalr	$25
+
+	lw	$28,16($fp)
+	move	$sp,$fp
+	lw	$31,28($sp)
+	lw	$fp,24($sp)
+	addiu	$sp,$sp,32
+	j	$31
+
+	.end	ref1
+	.size	ref1, .-ref1
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..4ff90bc
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-dyn.ld
@@ -0,0 +1,27 @@
+ENTRY(_start)
+SECTIONS
+{
+  . = 0 + SIZEOF_HEADERS;
+
+  . = 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-0x400000.t
b/ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000.t
new file mode 100644
index 0000000..cdd8d6c
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000.t
@@ -0,0 +1,11 @@
+tmpdir/ifunc-iplt-0x400000:     file format elf64-tradbigmips
+
+
+Disassembly of section .iplt:
+
+0000000000400188 <.iplt.func1>:
+  400188:	3c0f0041 	lui	t3,0x41
+  40018c:	ddf902e0 	ld	t9,736\(t3\)
+  400190:	03200008 	jr	t9
+  400194:	00000000 	nop
+	...
diff --git a/ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000000.t
b/ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000000.t
new file mode 100644
index 0000000..27eb668
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-0x400000000.t
@@ -0,0 +1,13 @@
+tmpdir/ifunc-iplt-0x400000000:     file format elf64-tradbigmips
+
+
+Disassembly of section .iplt:
+
+0000000400000188 <.iplt.func1>:
+   400000188:	3c0f0004 	lui	t3,0x4
+   40000018c:	25ef0001 	addiu	t3,t3,1
+   400000190:	000f7c38 	dsll	t3,t3,0x10
+   400000194:	ddf902e0 	ld	t9,736\(t3\)
+   400000198:	03200008 	jr	t9
+   40000019c:	00000000 	nop
+	...
diff --git a/ld/testsuite/ld-mips-elf/ifunc-iplt-0x4000000000000.t
b/ld/testsuite/ld-mips-elf/ifunc-iplt-0x4000000000000.t
new file mode 100644
index 0000000..672a363
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-0x4000000000000.t
@@ -0,0 +1,14 @@
+tmpdir/ifunc-iplt-0x4000000000000:     file format elf64-tradbigmips
+
+
+Disassembly of section .iplt:
+
+0004000000000188 <.iplt.func1>:
+   4000000000188:	3c0f0004 	lui	t3,0x4
+   400000000018c:	3c0e0001 	lui	t2,0x1
+   4000000000190:	25ef0000 	addiu	t3,t3,0
+   4000000000194:	000f783c 	dsll32	t3,t3,0x0
+   4000000000198:	01ee782d 	daddu	t3,t3,t2
+   400000000019c:	ddf902e0 	ld	t9,736\(t3\)
+   40000000001a0:	03200008 	jr	t9
+   40000000001a4:	00000000 	nop
diff --git a/ld/testsuite/ld-mips-elf/ifunc-iplt-micromips.igot
b/ld/testsuite/ld-mips-elf/ifunc-iplt-micromips.igot
new file mode 100644
index 0000000..4095700
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-micromips.igot
@@ -0,0 +1,4 @@
+tmpdir/ifunc-iplt-micromips:     file format elf32-tradbigmips
+
+Contents of section .igot:
+ 4101c0 00400141 .*
diff --git a/ld/testsuite/ld-mips-elf/ifunc-iplt-micromips.t
b/ld/testsuite/ld-mips-elf/ifunc-iplt-micromips.t
new file mode 100644
index 0000000..3249116
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-micromips.t
@@ -0,0 +1,10 @@
+tmpdir/ifunc-iplt-micromips:     file format elf32-tradbigmips
+
+
+Disassembly of section .iplt:
+
+004001b0 <.iplt.comp.func1>:
+  4001b0:	41af 0041 	lui	t7,0x41
+  4001b4:	ff2f 01c0 	lw	t9,448\(t7\)
+  4001b8:	45b9      	jrc	t9
+  4001ba:	0c00      	nop
diff --git a/ld/testsuite/ld-mips-elf/ifunc-iplt-mips16.igot
b/ld/testsuite/ld-mips-elf/ifunc-iplt-mips16.igot
new file mode 100644
index 0000000..00b341e
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-mips16.igot
@@ -0,0 +1,4 @@
+tmpdir/ifunc-iplt-mips16:     file format elf32-tradbigmips
+
+Contents of section .igot:
+ 410190 00400125 .*
diff --git a/ld/testsuite/ld-mips-elf/ifunc-iplt-mips16.t
b/ld/testsuite/ld-mips-elf/ifunc-iplt-mips16.t
new file mode 100644
index 0000000..0ae609e
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-mips16.t
@@ -0,0 +1,10 @@
+#...
+Disassembly of section .iplt:
+
+00400170 <.iplt.comp.func1>:
+  400170:	b202      	lw	v0,400178 <.iplt.comp.func1\+0x8>
+  400172:	9a40      	lw	v0,0\(v0\)
+  400174:	ea00      	jr	v0
+  400176:	653a      	move	t9,v0
+  400178:	0041      	addiu	s0,sp,260
+  40017a:	0190      	addiu	s1,sp,576
diff --git a/ld/testsuite/ld-mips-elf/ifunc-iplt-mips32r6.t
b/ld/testsuite/ld-mips-elf/ifunc-iplt-mips32r6.t
new file mode 100644
index 0000000..10f77b7
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-mips32r6.t
@@ -0,0 +1,10 @@
+
+tmpdir/ifunc-iplt-mips32r6:     file format elf32-tradbigmips
+
+
+Disassembly of section .iplt:
+
+00400210 <.iplt.func1>:
+  400210:	3c0f0041 	lui	t7,0x41
+  400214:	8df90220 	lw	t9,544\(t7\)
+  400218:	d8190000 	jrc	t9
diff --git a/ld/testsuite/ld-mips-elf/ifunc-iplt-mix-1.t
b/ld/testsuite/ld-mips-elf/ifunc-iplt-mix-1.t
new file mode 100644
index 0000000..dff51f6
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-mix-1.t
@@ -0,0 +1,29 @@
+#...
+Disassembly of section .iplt:
+
+00400140 <.iplt.comp.func1>:
+  400140:	lw	v0,400148 <.iplt.comp.func1\+0x8>
+  400142:	lw	v0,0\(v0\)
+  400144:	jr	v0
+  400146:	move	t9,v0
+  400148:	addiu	s0,sp,260
+  40014a:	addiu	s1,sp,512
+
+0040014c <.iplt.comp.func2>:
+  40014c:	lw	v0,400154 <.iplt.comp.func2\+0x8>
+  40014e:	lw	v0,0\(v0\)
+  400150:	jr	v0
+  400152:	move	t9,v0
+  400154:	addiu	s0,sp,260
+  400156:	addiu	s1,sp,528
+
+00400158 <.iplt.func1>:
+  400158:	lui	t7,0x41
+  40015c:	lw	t9,384\(t7\)
+  400160:	jr	t9
+
+00400164 <.iplt.func2>:
+  400164:	lui	t7,0x41
+  400168:	lw	t9,388\(t7\)
+  40016c:	jr	t9
+  400170:	nop
diff --git a/ld/testsuite/ld-mips-elf/ifunc-iplt-mix-2.t
b/ld/testsuite/ld-mips-elf/ifunc-iplt-mix-2.t
new file mode 100644
index 0000000..24d7b47
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt-mix-2.t
@@ -0,0 +1,27 @@
+tmpdir/ifunc-mix-2:     file format elf32-tradbigmips
+
+
+Disassembly of section .iplt:
+
+00400140 <.iplt.comp.func1>:
+  400140:	lui	t7,0x41
+  400144:	lw	t9,384\(t7\)
+  400148:	jrc	t9
+  40014a:	nop
+
+0040014c <.iplt.comp.func2>:
+  40014c:	lui	t7,0x41
+  400150:	lw	t9,388\(t7\)
+  400154:	jrc	t9
+  400156:	nop
+
+00400158 <.iplt.func1>:
+  400158:	lui	t7,0x41
+  40015c:	lw	t9,384\(t7\)
+  400160:	jr	t9
+
+00400164 <.iplt.func2>:
+  400164:	lui	t7,0x41
+  400168:	lw	t9,388\(t7\)
+  40016c:	jr	t9
+  400170:	nop
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..b8e9598
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-iplt.ld
@@ -0,0 +1,27 @@
+ENTRY(_start)
+SECTIONS
+{
+  . = 0 + SIZEOF_HEADERS;
+  . = 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-mix-1.igot
b/ld/testsuite/ld-mips-elf/ifunc-mix-1.igot
new file mode 100644
index 0000000..08c8f77
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-mix-1.igot
@@ -0,0 +1,3 @@
+#...
+Contents of section .igot:
+ 410180 00400100 00400109 .*
diff --git a/ld/testsuite/ld-mips-elf/ifunc-mix-1.s
b/ld/testsuite/ld-mips-elf/ifunc-mix-1.s
new file mode 100644
index 0000000..a4f6afb
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-mix-1.s
@@ -0,0 +1,53 @@
+	.abicalls
+	.option	pic0
+	.text
+	.set nomips16
+	.globl	func1_ifunc
+	.ent	func1_ifunc
+	.type	func1_ifunc, @function
+func1_ifunc:
+	j	$31
+	.end	func1_ifunc
+	.size	func1_ifunc, .-func1_ifunc
+
+	.globl func1
+	.type	func1, @gnu_indirect_function
+	func1 = func1_ifunc
+
+	.set mips16
+	.globl	func2_ifunc
+	.ent	func2_ifunc
+	.type	func2_ifunc, @function
+func2_ifunc:
+	j	$31
+	.end	func2_ifunc
+	.size	func2_ifunc, .-func2_ifunc
+
+	.type	func2, @gnu_indirect_function
+	.globl func2
+	func2 = func2_ifunc
+
+	.set	nomips16
+	.globl	main
+	.ent	main
+	.option	pic0
+	.type	main, @function
+main:
+	move	$7, $31
+	jal	func2
+	jal	func1
+	jr	$7
+	.end	main
+	.size	main, .-main
+
+	.set	mips16
+	.globl	test_mips16
+	.ent	test_mips16
+	.type	test_mips16, @function
+test_mips16:
+	move	$7, $31
+	jal	func2
+	jal	func1
+	jr	$7
+	.end	test_mips16
+	.size	test_mips16, .-test_mips16
diff --git a/ld/testsuite/ld-mips-elf/ifunc-mix-1.sym
b/ld/testsuite/ld-mips-elf/ifunc-mix-1.sym
new file mode 100644
index 0000000..a58ad3f
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-mix-1.sym
@@ -0,0 +1,15 @@
+#...
+     9: 00400158    12 FUNC    LOCAL  DEFAULT    5 \.iplt\.func1
+.*
+    11: 0040014c    12 FUNC    LOCAL  DEFAULT \[MIPS16\]     5
\.iplt\.comp\.func2
+    12: 00400140    12 FUNC    LOCAL  DEFAULT \[MIPS16\]     5
\.iplt\.comp\.func1
+    13: 00400164    16 FUNC    LOCAL  DEFAULT    5 \.iplt\.func2
+.*
+.*
+    16: 00400100     8 IFUNC   GLOBAL DEFAULT    4 func1
+.*
+.*
+.*
+.*
+    21: 00400108     4 IFUNC   GLOBAL DEFAULT \[MIPS16\]     4 func2
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-mix-1.t
b/ld/testsuite/ld-mips-elf/ifunc-mix-1.t
new file mode 100644
index 0000000..e6010bb
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-mix-1.t
@@ -0,0 +1,18 @@
+#...
+0040010c <main>:
+  40010c:	move	a3,ra
+  400110:	jal	400164 <\.iplt\.func2>
+  400114:	nop
+  400118:	jal	400158 <\.iplt\.func1>
+  40011c:	nop
+  400120:	jr	a3
+  400124:	nop
+
+00400128 <test_mips16>:
+  400128:	move	a3,ra
+  40012a:	jal	40014c <\.iplt\.comp\.func2>
+  40012e:	nop
+  400130:	jal	400140 <\.iplt\.comp\.func1>
+  400134:	nop
+  400136:	jr	a3
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-mix-2.igot
b/ld/testsuite/ld-mips-elf/ifunc-mix-2.igot
new file mode 100644
index 0000000..08c8f77
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-mix-2.igot
@@ -0,0 +1,3 @@
+#...
+Contents of section .igot:
+ 410180 00400100 00400109 .*
diff --git a/ld/testsuite/ld-mips-elf/ifunc-mix-2.s
b/ld/testsuite/ld-mips-elf/ifunc-mix-2.s
new file mode 100644
index 0000000..b019337
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-mix-2.s
@@ -0,0 +1,55 @@
+	.abicalls
+	.option	pic0
+	.text
+	.set nomicromips
+	.globl	func1_ifunc
+	.ent	func1_ifunc
+	.type	func1_ifunc, @function
+func1_ifunc:
+	j	$31
+	.end	func1_ifunc
+	.size	func1_ifunc, .-func1_ifunc
+
+	.globl func1
+	.type	func1, @gnu_indirect_function
+	func1 = func1_ifunc
+
+	.set micromips
+	.globl	func2_ifunc
+	.ent	func2_ifunc
+	.type	func2_ifunc, @function
+func2_ifunc:
+	j	$31
+	.end	func2_ifunc
+	.size	func2_ifunc, .-func2_ifunc
+
+	.type	func2, @gnu_indirect_function
+	.globl func2
+	func2 = func2_ifunc
+
+	.align	2
+	.set nomicromips
+	.globl	main
+	.ent	main
+	.option	pic0
+	.type	main, @function
+main:
+	move	$7, $31
+	jal	func2
+	jal	func1
+	jr	$7
+	.end	main
+	.size	main, .-main
+
+	.set	micromips
+	.globl	test_micromips
+	.ent	test_micromips
+	.option	pic0
+	.type	test_micromips, @function
+test_micromips:
+	move	$7, $31
+	jal	func2
+	jal	func1
+	jr	$7
+	.end	test_micromips
+	.size	test_micromips, .-test_micromips
diff --git a/ld/testsuite/ld-mips-elf/ifunc-mix-2.sym
b/ld/testsuite/ld-mips-elf/ifunc-mix-2.sym
new file mode 100644
index 0000000..7e1a48f
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-mix-2.sym
@@ -0,0 +1,15 @@
+#...
+     9: 00400158    12 FUNC    LOCAL  DEFAULT    5 \.iplt\.func1
+.*
+    11: 0040014c    12 FUNC    LOCAL  DEFAULT \[MICROMIPS\]     5
\.iplt\.comp\.func2
+    12: 00400140    12 FUNC    LOCAL  DEFAULT \[MICROMIPS\]     5
\.iplt\.comp\.func1
+    13: 00400164    16 FUNC    LOCAL  DEFAULT    5 \.iplt\.func2
+.*
+.*
+    16: 00400100     8 IFUNC   GLOBAL DEFAULT    4 func1
+.*
+.*
+.*
+.*
+    21: 00400108     2 IFUNC   GLOBAL DEFAULT \[MICROMIPS\]     4 func2
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-mix-2.t
b/ld/testsuite/ld-mips-elf/ifunc-mix-2.t
new file mode 100644
index 0000000..03563d3
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-mix-2.t
@@ -0,0 +1,19 @@
+#...
+0040010c <main>:
+  40010c:	move	a3,ra
+  400110:	jal	400164 <\.iplt\.func2>
+  400114:	nop
+  400118:	jal	400158 <\.iplt\.func1>
+  40011c:	nop
+  400120:	jr	a3
+  400124:	nop
+
+00400128 <test_micromips>:
+  400128:	move	a3,ra
+  40012a:	jal	40014c <\.iplt\.comp\.func2>
+  40012e:	nop
+  400132:	jal	400140 <\.iplt\.comp\.func1>
+  400136:	nop
+  40013a:	jrc	a3
+  40013c:	nop
+#pass
diff --git a/ld/testsuite/ld-mips-elf/ifunc-static-def-mips16.s
b/ld/testsuite/ld-mips-elf/ifunc-static-def-mips16.s
new file mode 100644
index 0000000..6202e4e
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-static-def-mips16.s
@@ -0,0 +1,124 @@
+	.abicalls
+	.option	pic0
+	.text
+
+	.align	2
+
+	.set	mips16
+	.set	nomicromips
+	.ent	f1_a
+	.type	f1_a, @function
+f1_a:
+	.frame	$17,8,$31		# vars= 0, regs= 1/0, args= 0, gp= 0
+	.mask	0x00020000,-4
+	.fmask	0x00000000,0
+	save	8,$17
+	move	$17,$sp
+	li	$2,1
+	move	$sp,$17
+	restore	8,$17
+	j	$31
+	.end	f1_a
+
+	.size	f1_a, .-f1_a
+	.align	2
+	.set	mips16
+	.set	nomicromips
+	.ent	f1_b
+	.type	f1_b, @function
+f1_b:
+	.frame	$17,8,$31		# vars= 0, regs= 1/0, args= 0, gp= 0
+	.mask	0x00020000,-4
+	.fmask	0x00000000,0
+	save	8,$17
+	move	$17,$sp
+	li	$2,2
+	move	$sp,$17
+	restore	8,$17
+	j	$31
+	.end	f1_b
+
+	.size	f1_b, .-f1_b
+	.align	2
+	.set	mips16
+	.set	nomicromips
+	.ent	f1_c
+	.type	f1_c, @function
+f1_c:
+	.frame	$17,8,$31		# vars= 0, regs= 1/0, args= 0, gp= 0
+	.mask	0x00020000,-4
+	.fmask	0x00000000,0
+	save	8,$17
+	move	$17,$sp
+	li	$2,3
+	move	$sp,$17
+	restore	8,$17
+	j	$31
+	.end	f1_c
+	.size	f1_c, .-f1_c
+
+	.comm	global,4,4
+	.globl	result
+	.data
+	.align	2
+	.type	result, @object
+	.size	result, 4
+result:
+	.word	6
+	.text
+	.align	2
+	.globl	func1_ifunc
+	.set	mips16
+	.set	nomicromips
+	.ent	func1_ifunc
+	.type	func1_ifunc, @function
+func1_ifunc:
+	.frame	$17,24,$31		# vars= 8, regs= 1/0, args= 0, gp= 8
+	.mask	0x00020000,-4
+	.fmask	0x00000000,0
+	save	24,$17
+	move	$17,$sp
+	lw	$2,$L13
+	lw	$2,0($2)
+	beqz	$2,$L8
+	li	$2,48
+	sw	$2,8($17)
+	b	$L9
+$L8:
+	li	$2,3
+	sw	$2,8($17)
+$L9:
+	lw	$3,8($17)
+	li	$2,240
+	and	$2,$3
+	beqz	$2,$L10
+	lw	$2,$L14
+	b	$L11
+$L10:
+	lw	$3,8($17)
+	li	$2,15
+	and	$2,$3
+	beqz	$2,$L12
+	lw	$2,$L15
+	b	$L11
+$L12:
+	lw	$2,$L16
+$L11:
+	move	$sp,$17
+	restore	24,$17
+	j	$31
+	.align	2
+$L13:
+	.word	global
+$L14:
+	.word	f1_a
+$L15:
+	.word	f1_b
+$L16:
+	.word	f1_c
+	.end	func1_ifunc
+
+	.size	func1_ifunc, .-func1_ifunc
+	.globl	func1
+	.type	func1, @gnu_indirect_function
+	func1 = func1_ifunc
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..1550b04
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-static-def.s
@@ -0,0 +1,98 @@
+	.abicalls
+	.option	pic0
+	.text
+	.ent	f1_a
+	.type	f1_a, @function
+f1_a:
+	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
+	.end	f1_a
+	.size	f1_a, .-f1_a
+
+	.ent	f1_b
+	.type	f1_b, @function
+f1_b:
+	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
+	.end	f1_b
+	.size	f1_b, .-f1_b
+
+	.ent	f1_c
+	.type	f1_c, @function
+f1_c:
+	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
+	.end	f1_c
+	.size	f1_c, .-f1_c
+
+	.globl	func1_ifunc
+	.ent	func1_ifunc
+	.type	func1_ifunc, @function
+func1_ifunc:
+	addiu	$sp,$sp,-432
+	sw	$31,428($sp)
+	sw	$fp,424($sp)
+	move	$fp,$sp
+	addiu	$2,$fp,28
+	move	$4,$2
+
+	beq	$2,$0,$L8
+
+	li	$2,48			# 0x30
+	sw	$2,24($fp)
+	j	$L9
+
+$L8:
+	li	$2,3			# 0x3
+	sw	$2,24($fp)
+$L9:
+	lw	$2,24($fp)
+	andi	$2,$2,0xf0
+	beq	$2,$0,$L10
+
+	lui	$2,%hi(f1_a)
+	addiu	$2,$2,%lo(f1_a)
+	j	$L13
+
+$L10:
+	lw	$2,24($fp)
+	andi	$2,$2,0xf
+	beq	$2,$0,$L12
+
+	lui	$2,%hi(f1_b)
+	addiu	$2,$2,%lo(f1_b)
+	j	$L13
+
+$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
+
+	.end	func1_ifunc
+	.size	func1_ifunc, .-func1_ifunc
+	.globl	func1
+	.type	func1, @gnu_indirect_function
+	func1 = func1_ifunc
diff --git a/ld/testsuite/ld-mips-elf/ifunc-static-main-mips16.s
b/ld/testsuite/ld-mips-elf/ifunc-static-main-mips16.s
new file mode 100644
index 0000000..953e704
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-static-main-mips16.s
@@ -0,0 +1,18 @@
+	.abicalls
+	.option	pic0
+	.text
+	.align	2
+	.globl	main
+	.set	mips16
+	.set	nomicromips
+	.ent	main
+	.type	main, @function
+main:
+	save	32,$17,$31
+	addiu	$17,$sp,16
+	jal	func1
+	move	$sp,$17
+	restore	16,$17,$31
+	j	$31
+	.end	main
+	.size	main, .-main
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..36f6330
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-static-main.s
@@ -0,0 +1,21 @@
+	.abicalls
+	.option	pic0
+	.text
+	.globl	main
+	.ent	main
+	.type	main, @function
+main:
+	addiu	$sp,$sp,-32
+	sw	$31,28($sp)
+	sw	$fp,24($sp)
+	move	$fp,$sp
+	jal	func1
+
+	move	$sp,$fp
+	lw	$31,28($sp)
+	lw	$fp,24($sp)
+	addiu	$sp,$sp,32
+	j	$31
+
+	.end	main
+	.size	main, .-main
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..c275102
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/ifunc-static-ref.s
@@ -0,0 +1,12 @@
+	.abicalls
+	.option	pic0
+	.text
+	.globl	ref1
+	.set 	mips32r2
+	.ent	ref1
+	.type	ref1, @function
+ref1:
+	jal	func1
+	j	$31
+	.end	ref1
+	.size	ref1, .-ref1
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..31725ea
--- /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
+    3   0: 0000040c    24 FUNC    GLOBAL DEFAULT   1 func1_ifunc
+    2   0: 0000040c    24 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..b289382
--- /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
+    3   0: 000000000000040c    24 FUNC    GLOBAL DEFAULT   1 func1_ifunc
+    2   0: 000000000000040c    24 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..31725ea
--- /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
+    3   0: 0000040c    24 FUNC    GLOBAL DEFAULT   1 func1_ifunc
+    2   0: 0000040c    24 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..801ed0e
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/libifunc-2-n32.sym
@@ -0,0 +1,4 @@
+#...
+    2   0: 00000400    56 FUNC    GLOBAL DEFAULT   1 ref1
+    3   0: 00000440     0 FUNC    GLOBAL DEFAULT UND func1
+#pass
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..02cea64
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/libifunc-2-n64.sym
@@ -0,0 +1,4 @@
+#...
+    2   0: 0000000000000400    56 FUNC    GLOBAL DEFAULT   1 ref1
+    3   0: 0000000000000440     0 FUNC    GLOBAL DEFAULT UND func1
+#pass
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..801ed0e
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/libifunc-2-o32.sym
@@ -0,0 +1,4 @@
+#...
+    2   0: 00000400    56 FUNC    GLOBAL DEFAULT   1 ref1
+    3   0: 00000440     0 FUNC    GLOBAL DEFAULT UND func1
+#pass
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..27d5d2d
--- /dev/null
+++ b/ld/testsuite/ld-mips-elf/mips-ifunc.exp
@@ -0,0 +1,273 @@
+# Expect script for MIPS IFUNC linker tests
+#   Copyright 2016
+#   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) "-mips32"
+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 (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 (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 (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 (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 -s ifunc-4-${abi}.sym" \
+		"readelf -r ifunc-4-${abi}.r" \
+		"objdump -dj.iplt ifunc-4-${abi}.t"] \
+	    "ifunc-4-${abi}" \
+	] \
+	[list \
+	    "IFUNC 5 (DSO 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" \
+		"readelf -d ifunc-5.dyn" \
+		"readelf -A ifunc-5-${abi}.got"] \
+	    "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 -d ifunc-6.dyn" \
+		"readelf -r ifunc-6-${abi}.r"] \
+	    "ifunc-6-${abi}" \
+	] \
+    ]
+}
+
+# IPLT sequences change based on how big the address of the
+# .igot.plt section is based on Mips loading immediate values.
+#
+set addrs { "0x400000" "0x400000000" "0x4000000000000" }
+foreach { addr } $addrs {
+    run_ld_link_tests [list \
+	[list \
+	    "IFUNC IPLT (Static executable with def and ref) ${addr}" \
+	    "$abi_ldflags(n64) -Bstatic -Ttext-segment ${addr}" "" \
+	    "$abi_asflags(n64) -non_shared" \
+	    [list ifunc-static-main.s ifunc-static-def.s ifunc-static-ref.s] \
+	    [list "objdump -dj.iplt ifunc-iplt-${addr}.t"] \
+	    "ifunc-iplt-${addr}" \
+	] \
+    ]
+}
+
+
+set abi o32
+run_ld_link_tests [list \
+	[list \
+	    "IFUNC 7 (Local binding with non-PIC call-only ref) ${abi}" \
+	    "$abi_ldflags($abi) -static -Bstatic" "" \
+	    "$abi_asflags($abi)" \
+	     [list ifunc-7.s] \
+	     [list "readelf -r ifunc-7-${abi}.r" \
+		 "readelf -S ifunc-7-${abi}.sec"] \
+	    "ifunc-7-${abi}" \
+	] \
+	[list \
+	    "IFUNC 8 (Local binding with PIC call-only ref) ${abi}" \
+	    "$abi_ldflags($abi) -static" "" \
+	    "$abi_asflags($abi)" \
+	     [list ifunc-8.s] \
+	     [list "readelf -r ifunc-8-${abi}.r" \
+		 "readelf -S ifunc-8-${abi}.sec" \
+		 "objdump -sj.got ifunc-8-${abi}.got"] \
+	    "ifunc-8-${abi}" \
+	] \
+	[list \
+	    "IFUNC 9 (Local binding with PIC/non-PIC call-only refs) ${abi}" \
+	    "$abi_ldflags($abi) -static -Bstatic" "" \
+	    "$abi_asflags($abi) -non_shared" \
+	    [list ifunc-9.s] \
+	    [list "readelf -r ifunc-9-${abi}.r" \
+		"readelf -S ifunc-9-${abi}.sec" \
+		"objdump -sj.got ifunc-9-${abi}.got" \
+		"objdump -dj.iplt ifunc-9-${abi}.t"] \
+	    "ifunc-9-${abi}" \
+	] \
+	[list \
+	    "IFUNC 10 (Local binding with non-PIC call/non-call refs) ${abi}" \
+	    "$abi_ldflags($abi) -static -Bstatic" "" \
+	    "$abi_asflags($abi) -non_shared" \
+	    [list ifunc-10.s] \
+	    [list "readelf -r ifunc-10-${abi}.r" \
+		"readelf -S ifunc-10-${abi}.sec"] \
+	    "ifunc-10-${abi}" \
+	] \
+	[list \
+	    "IFUNC 11 (Local binding with PIC-only call/non-call refs) ${abi}" \
+	    "$abi_ldflags($abi) -static" "" \
+	    "$abi_asflags($abi)" \
+	    [list ifunc-11.s] \
+	    [list "readelf -r ifunc-11-${abi}.r" \
+		"readelf -S ifunc-11-${abi}.sec" \
+		"objdump -sj.got ifunc-11-${abi}.got"] \
+	    "ifunc-11-${abi}" \
+	] \
+	[list \
+	    "IFUNC 12 (Local binding with PIC/non-PIC, call/non-call refs)
${abi}" \
+	    "$abi_ldflags($abi) -static -Bstatic" "" \
+	    "$abi_asflags($abi) -non_shared" \
+	    [list ifunc-12.s] \
+	    [list "readelf -r ifunc-12-${abi}.r" \
+		"readelf -S ifunc-12-${abi}.sec" \
+		"objdump -sj.got ifunc-12-${abi}.got"] \
+	    "ifunc-12-${abi}" \
+	] \
+	[list \
+	    "IFUNC 13 non-PIC calls to PIC IFUNC ${abi}" \
+	    "$abi_ldflags($abi) -static -Bstatic" "" \
+	    "$abi_asflags($abi) -non_shared" \
+	    [list ifunc-dyn-def.s ifunc-dyn-ref.s ifunc-static-main.s] \
+	    [list "readelf -r ifunc-13-${abi}.r" \
+		"readelf -S ifunc-13-${abi}.sec" \
+		"objdump -Dj.text ifunc-13-${abi}.t"] \
+	    "ifunc-13-${abi}" \
+	] \
+	[list \
+	    "IFUNC IPLT (Static executable with def and ref) mips32r6" \
+	    "" "" \
+	    "$abi_asflags(o32) -mips32r6" \
+	    [list ifunc-static-main.s ifunc-static-def.s] \
+	    [list "objdump -dj.iplt ifunc-iplt-mips32r6.t"] \
+	    "ifunc-iplt-mips32r6" \
+	] \
+	[list \
+	    "IFUNC IPLT (Static executable with def and ref) micromips" \
+	    "" "" \
+	    "$abi_asflags(o32) -mmicromips" \
+	    [list ifunc-static-main.s ifunc-static-def.s] \
+	    [list "objdump -dj.iplt ifunc-iplt-micromips.t" \
+		"objdump -sj.igot ifunc-iplt-micromips.igot"] \
+	    "ifunc-iplt-micromips" \
+	] \
+	[list \
+	    "IFUNC IPLT (Static executable with def and ref) mips16" \
+	    "" "" \
+	    "$abi_asflags(o32) -mips32r2 -mips16" \
+	    [list ifunc-static-main-mips16.s ifunc-static-def-mips16.s] \
+	    [list "objdump -dj.iplt ifunc-iplt-mips16.t" \
+		"objdump -sj.igot ifunc-iplt-mips16.igot"] \
+	    "ifunc-iplt-mips16" \
+	] \
+	[list \
+	    "IFUNC IPLT Mix 1 (mips32 & mips16)" \
+	    "$abi_ldflags($abi) -static -Bstatic" "" \
+	    "$abi_asflags($abi) -mips2" \
+	    [list ifunc-mix-1.s] \
+	     { { readelf -s ifunc-mix-1.sym } \
+	      { objdump -sj.igot ifunc-mix-1.igot } \
+	      { objdump {-dj.iplt --no-show-raw-insn} ifunc-iplt-mix-1.t } \
+	      { objdump {-dj.text --no-show-raw-insn} ifunc-mix-1.t } }\
+	    "ifunc-mix-1" \
+	] \
+	[list \
+	    "IFUNC IPLT Mix 2 (mips32 & micromips)" \
+	    "$abi_ldflags($abi) -static -Bstatic" "" \
+	    "$abi_asflags($abi) -mips2" \
+	    [list ifunc-mix-2.s] \
+	     { { readelf -s ifunc-mix-2.sym } \
+	       { objdump -sj.igot ifunc-mix-2.igot } \
+	       { objdump { -dj.iplt --no-show-raw-insn } ifunc-iplt-mix-2.t } \
+	       { objdump { -dj.text --no-show-raw-insn } ifunc-mix-2.t } } \
+	    "ifunc-mix-2" \
+	] \
+]
-- 
1.7.9.5

^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v5)
  2016-12-05 14:44                                   ` Maciej W. Rozycki
  2017-01-09 21:10                                     ` Faraz Shahbazker
@ 2017-01-24 20:41                                     ` Faraz Shahbazker
  2017-01-24 20:41                                     ` [PATCH 2/2] " Faraz Shahbazker
  2 siblings, 0 replies; 35+ messages in thread
From: Faraz Shahbazker @ 2017-01-24 20:41 UTC (permalink / raw)
  To: Maciej W. Rozycki; +Cc: binutils, Richard Sandiford

[-- Attachment #1: Type: text/plain, Size: 83980 bytes --]

Hi,

Spec attached. Test cases in following patch.

Regards,
Faraz Shahbazker

bfd/ChangeLog:

	* reloc.c (ENUMDOC) <BFD_RELOC_MIPS_IRELATIVE>: Add entry for
	BFD_RELOC_MIPS_IRELATIVE.
	* elf32-mips.c (elf_mips_irelative_howto): New variable.
	(bfd_elf32_bfd_reloc_type_lookup) <R_MIPS_IRELATIVE>: New case.
	(bfd_elf32_bfd_reloc_name_lookup): Handle R_MIPS_IRELATIVE.
	(mips_elf32_rtype_to_howto) <R_MIPS_IRELATIVE>: New case.
	* elf64-mips.c (elf_mips_irelative_howto): New variable.
	(bfd_elf64_bfd_reloc_type_lookup) <R_MIPS_IRELATIVE>: New case.
	(bfd_elf64_bfd_reloc_name_lookup): Handle R_MIPS_IRELATIVE.
	(mips_elf64_rtype_to_howto) <R_MIPS_IRELATIVE>: New case.
	* elfn32-mips.c (elf_mips_irelative_howto): New variable.
	(bfd_elfn32_bfd_reloc_type_lookup) <R_MIPS_IRELATIVE>: New case.
	(bfd_elfn32_bfd_reloc_name_lookup): Handle R_MIPS_IRELATIVE.
	(mips_elfn32_rtype_to_howto) <R_MIPS_IRELATIVE>: New case.
	* elfxx-mips.c (mips_got_info): Add fields general_gotno
	and assigned_general_gotno.
	(mips_elf_link_hash_entry): Add fields needs_iplt_mips,
	needs_iplt_comp, needs_igot, needs_ireloc, iplt_mips_offset,
	iplt_comp_offset, igot_offset, has_got_relocs,
	has_jal_mips_relocs, has_jal_comp_relocs.
	(mips_elf_link_hash_table): Add fields iplt_mips_entry_size,
	iplt_comp_entry_size, loc_has_table, loc_hash_memory,
	iplt_mips_offset, iplt_comp_offset.
	(MIPS16_P): New macro.
	(MIPS_NOP_INSN): Likewise.
	(mips16_exec_iplt_entry): New variable.
	(mips32_exec_iplt_entry): Likewise.
	(mips32r6_exec_iplt_entry): Likewise.
	(micromips32_exec_iplt_entry): Likewise.
	(mips64_exec_iplt_entry): Likewise.
	(mips64_48b_exec_iplt_entry): Likewise.
	(mips_elf_link_hash_newfunc): Initialize new fields in
	mips_elf_link_hash_entry.
	(mips_elf_create_stub_symbol): Set ISA address bit and other
	field for compressed stubs.
	(mips_elf_add_la25_intro): Add argument other in the call to
	mips_elf_create_stub_symbol.
	(mips_elf_add_la25_trampoline): Likewise.
	(mips_elf_rel_dyn_section): Move up to avoid forward declaration.
	(mips_get_irel_section): New function.
	(mips_elf_allocate_ireloc): Likewise.
	(mips_elf_allocate_iplt): Likewise.
	(mips_elf_check_ifunc_symbols): Likewise.
	(mips_elf_lay_out_iplt): Likewise.
	(mips_elf_lay_out_iplt_wrap): Likewise.
	(mips_elf_check_symbols): Call mips_elf_check_ifunc_symbols to
	allocate an IPLT entry for an IFUNC symbol.  Skip creation of
	la25 stubs for IFUNCs having IPLT stubs.
	(mips_elf_count_got_entry): Count GOT entries for IFUNCs that do
	not have an IPLT stub under the general GOT region and allocate
	space for an IRELATIVE relocation for each.  Change return type
	to boolean.
	(mips_elf_output_dynamic_relocation): Compose IRELATIVE relocation
	with R_MIPS_64 for ABI64.
	(mips_elf_got16_entry): Add argument h and pass it to
	mips_elf_create_local_got_entry instead of NULL.
	(mips_elf_create_local_got_entry): Change hash-lookup for IFUNCs
	to use non-NULL input BFD and a pointer to the
	mips_elf_link_hash_entry.  Add check for sufficient general GOT
	entries.  Assign local IFUNCs from general GOT entries pool.
	Allow early return for existing hash entry only if gotidx has
	been assigned.  Allocate general GOT only if symbol does not
	have an IGOT entry.
	(mips_elf_record_local_got_symbol): Add argument pointing to
	mips_elf_link_hash_entry.
	(mips_elf_check_recreate_got): Handle return value from
	mips_elf_count_got_entry.
	(mips_elf_recreate_got): Likewise.
	(mips_elf_add_got_entry): Likewise.
	(mips_use_local_got_p): Return TRUE for IFUNC symbol definitions.
	(mips_elf_create_ifunc_sections): New function.
	(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 symbols that need IPLT.
	Force global IFUNC to be allocated from local GOT region.
	(mips_elf_create_dynamic_relocation): Emit IRELATIVE relocation
	instead of REL32 for local IFUNC reference.  Relax assertion for
	IFUNC symbols in explicit GOT region to have dynamic relocations.
	(_bfd_mips_elf_face_sections): Set entry-size for .igot section.
	(_bfd_mips_elf_add_symbol_hook): Set has_gnu_symbols flag for
	IFUNC.
	(_bfd_mips_elf_section_processing): Size .igot section.
	(_bfd_mips_elf_check_relocs): Check need to create IFUNC sections.
	If symbol is an IFUNC,  don't convert it to an STT_FUNC.  Relax
	error checking to allow local IFUNCs to be accessed via call16
	reloc.  Record calls to a local IFUNC symbols and pre-allocate GOT
	entries for call16 and got16 relocations.
	(_bfd_mips_elf_adjust_dynamic_symbol): Add STT_GNU_IFUNC to sanity
	check.
	(_bfd_mips_elf_always_size_sections): Allocate an IPLT for each
	local IFUNCs.
	(mips_elf_lay_out_got): Offset local GOT entries to follow general
	GOT entries.
	(_bfd_mips_elf_size_dynamic_sections): Exclude IPLT and IGOT.
	Create dynamic tag for DT_MIPS_GENERAL_GOTNO if needed.
	(_bfd_mips_elf_relocate_section): Relax error checking to allow
	local IFUNCs to be accessed via standalone got16 reloc.  Pass
	gnu_ifunc_p flag when calling mips_elf_perform_relocation.
	(mips_elf_create_iplt): New function.
	(mips_elf_create_comp_iplt): Likewise.
	(mips_elf_check_local_got_index): Likewise.
	(mips_elf_create_ireloc): Likewise.
	(_bfd_mips_elf_finish_dynamic_symbol): Create IPLT stub/IRELATIVE
	relocation for IFUNC symbols as necessary.  Set IFUNC symbol value
	to the IPLT entry address for executable 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 the value of the dynamic tag - DT_MIPS_GENERAL_GOTNO.
	(_bfd_mips_elf_final_write_processing) <SHT_REL>: Set sh_link for
	section containing IRELATIVE relocations.
	(local_htab_hash): New function.
	(loc_htab_eq): New function.
	(_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) <DT_MIPS_GENERAL_GOTNO>: New case.
	(_bfd_mips_post_process_headers): If ELF uses GNU IFUNCs, increment
	ABIVERSION to 4.

binutils/ChangeLog:

	* readelf.c: (get_mips_dynamic_type) <DT_MIPS_GENERAL_GOTNO>:
	New case.
	(dynamic_section_mips_val): Likewise.
	(process_mips_specific): Print general GOT entries.

elfcpp/ChangeLog:

	* elfcpp.h (DT): Add case for dynamic tag
	DT_MIPS_GENERAL_GOTNO.

include/elf/ChangeLog:

	* mips.h (R_MIPS_IRELATIVE): New relocation.
	(DT_MIPS_GENERAL_GOTNO): New macro.

bfd/ChangeLog:

	* bfd-in2.h: Regenerate.
	* libbfd.h: Regenerate.
---
 bfd/elf32-mips.c   |   22 +
 bfd/elf64-mips.c   |   22 +
 bfd/elfn32-mips.c  |   22 +
 bfd/elfxx-mips.c   | 1421
++++++++++++++++++++++++++++++++++++++++++++++++----
 bfd/reloc.c        |    5 +
 binutils/readelf.c |   25 +-
 elfcpp/elfcpp.h    |    2 +
 include/elf/mips.h |    7 +
 8 files changed, 1421 insertions(+), 105 deletions(-)

diff --git a/bfd/elf32-mips.c b/bfd/elf32-mips.c
index 8c1a68eb..b665a0f 100644
--- a/bfd/elf32-mips.c
+++ b/bfd/elf32-mips.c
@@ -1661,6 +1661,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_mips_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.  */

@@ -2142,6 +2158,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;
     }
 }

@@ -2189,6 +2207,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;
 }
@@ -2215,6 +2235,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/elf64-mips.c b/bfd/elf64-mips.c
index 5edbd4a..25e19f3 100644
--- a/bfd/elf64-mips.c
+++ b/bfd/elf64-mips.c
@@ -2904,6 +2904,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_mips_elf_generic_reloc, /* special_function */
+	 "R_MIPS_IRELATIVE",	/* name */
+	 TRUE,			/* partial_inplace */
+	 0xffffffff,		/* src_mask */
+	 0xffffffff,		/* dst_mask */
+	 FALSE);		/* pcrel_offset */
+
 \f
 /* Swap in a MIPS 64-bit Rel reloc.  */

@@ -3522,6 +3538,8 @@ bfd_elf64_bfd_reloc_type_lookup (bfd *abfd
ATTRIBUTE_UNUSED,
       return &elf_mips_copy_howto;
     case BFD_RELOC_MIPS_JUMP_SLOT:
       return &elf_mips_jump_slot_howto;
+    case BFD_RELOC_MIPS_IRELATIVE:
+      return &elf_mips_irelative_howto;
     default:
       bfd_set_error (bfd_error_bad_value);
       return NULL;
@@ -3573,6 +3591,8 @@ bfd_elf64_bfd_reloc_name_lookup (bfd *abfd
ATTRIBUTE_UNUSED,
     return &elf_mips_copy_howto;
   if (strcasecmp (elf_mips_jump_slot_howto.name, r_name) == 0)
     return &elf_mips_jump_slot_howto;
+  if (strcasecmp (elf_mips_irelative_howto.name, r_name) == 0)
+    return &elf_mips_irelative_howto;

   return NULL;
 }
@@ -3601,6 +3621,8 @@ mips_elf64_rtype_to_howto (unsigned int r_type,
bfd_boolean rela_p)
       return &elf_mips_copy_howto;
     case R_MIPS_JUMP_SLOT:
       return &elf_mips_jump_slot_howto;
+    case R_MIPS_IRELATIVE:
+      return &elf_mips_irelative_howto;
     default:
       if (r_type >= R_MICROMIPS_min && r_type < R_MICROMIPS_max)
 	{
diff --git a/bfd/elfn32-mips.c b/bfd/elfn32-mips.c
index c09713a..5f94c12 100644
--- a/bfd/elfn32-mips.c
+++ b/bfd/elfn32-mips.c
@@ -2870,6 +2870,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_mips_elf_generic_reloc, /* special_function */
+	 "R_MIPS_IRELATIVE",	/* name */
+	 TRUE,			/* partial_inplace */
+	 0xffffffff,		/* src_mask */
+	 0xffffffff,		/* dst_mask */
+	 FALSE);		/* pcrel_offset */
+
 \f
 /* Set the GP value for OUTPUT_BFD.  Returns FALSE if this is a
    dangerous relocation.  */
@@ -3339,6 +3355,8 @@ bfd_elf32_bfd_reloc_type_lookup (bfd *abfd
ATTRIBUTE_UNUSED,
       return &elf_mips_copy_howto;
     case BFD_RELOC_MIPS_JUMP_SLOT:
       return &elf_mips_jump_slot_howto;
+    case BFD_RELOC_MIPS_IRELATIVE:
+      return &elf_mips_irelative_howto;
     default:
       bfd_set_error (bfd_error_bad_value);
       return NULL;
@@ -3391,6 +3409,8 @@ bfd_elf32_bfd_reloc_name_lookup (bfd *abfd
ATTRIBUTE_UNUSED,
     return &elf_mips_copy_howto;
   if (strcasecmp (elf_mips_jump_slot_howto.name, r_name) == 0)
     return &elf_mips_jump_slot_howto;
+  if (strcasecmp (elf_mips_irelative_howto.name, r_name) == 0)
+    return &elf_mips_irelative_howto;

   return NULL;
 }
@@ -3419,6 +3439,8 @@ mips_elf_n32_rtype_to_howto (unsigned int r_type,
bfd_boolean rela_p)
       return &elf_mips_copy_howto;
     case R_MIPS_JUMP_SLOT:
       return &elf_mips_jump_slot_howto;
+    case R_MIPS_IRELATIVE:
+      return &elf_mips_irelative_howto;
     default:
       if (r_type >= R_MICROMIPS_min && r_type < R_MICROMIPS_max)
 	{
diff --git a/bfd/elfxx-mips.c b/bfd/elfxx-mips.c
index ce58c43..42c22ca 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 {
@@ -165,10 +166,14 @@ struct mips_got_info
   unsigned int tls_assigned_gotno;
   /* The number of local .got entries, eventually including page
entries.  */
   unsigned int local_gotno;
+  /* The number of explicitly relocated .got entries.  */
+  unsigned int general_gotno;
   /* The maximum number of page entries needed.  */
   unsigned int page_gotno;
   /* The number of relocations needed for the GOT entries.  */
   unsigned int relocs;
+  /* The first unused general .got entry.  */
+  unsigned int assigned_general_gotno;
   /* The first unused local .got entry.  */
   unsigned int assigned_low_gotno;
   /* The last unused local .got entry.  */
@@ -375,6 +380,15 @@ struct mips_elf_link_hash_entry
      being called returns a floating point value.  */
   asection *call_fp_stub;

+  /* Offset to regular MIPS entry in the IPLT table.  */
+  int iplt_mips_offset;
+
+  /* Offset to compressed entry in the IPLT table.  */
+  int iplt_comp_offset;
+
+  /* Offset into the IGOT table.  */
+  int igot_offset;
+
   /* The highest GGA_* value that satisfies all references to this
symbol.  */
   unsigned int global_got_area : 2;

@@ -392,6 +406,17 @@ struct mips_elf_link_hash_entry
      cannot possibly be made dynamic).  */
   unsigned int has_static_relocs : 1;

+  /* True if there is a GOT relocation against this symbol.  */
+  unsigned int has_got_relocs : 1;
+
+  /* True if there is a JAL relocations against this symbol
+     from MIPS code.  */
+  unsigned int has_jal_mips_relocs : 1;
+
+  /* True if there is a JAL relocations against this symbol
+     from compressed (mips16/micromips) code.  */
+  unsigned int has_jal_comp_relocs : 1;
+
   /* True if we must not create a .MIPS.stubs entry for this symbol.
      This is set, for example, if there are relocations related to
      taking the function's address, i.e. any but R_MIPS_CALL*16 ones.
@@ -413,6 +438,18 @@ struct mips_elf_link_hash_entry

   /* Does this symbol resolve to a PLT entry?  */
   unsigned int use_plt_entry : 1;
+
+  /* Does this symbol need a regular IPLT stub?  */
+  unsigned int needs_iplt_mips : 1;
+
+  /* Does this symbol need a compressed IPLT stub?  */
+  unsigned int needs_iplt_comp : 1;
+
+  /* Does this symbol need an IGOT entry?  */
+  unsigned int needs_igot : 1;
+
+  /* Does this ifunc symbol need an IRELATIVE relocation?  */
+  unsigned int needs_ireloc : 1;
 };

 /* MIPS ELF linker hash table.  */
@@ -479,6 +516,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 a regular IPLT entry in bytes.  */
+  bfd_vma iplt_mips_entry_size;
+
+  /* The size of a compressed IPLT entry in bytes.  */
+  bfd_vma iplt_comp_entry_size;
+
+  /* The offset of the next standard IPLT entry to create.  */
+  bfd_vma iplt_mips_offset;
+
+  /* The offset of the next compressed PLT entry to create.  */
+  bfd_vma iplt_comp_offset;
+
   /* The number of functions that need a lazy-binding stub.  */
   bfd_vma lazy_stub_count;

@@ -510,6 +559,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.  */
@@ -794,6 +847,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 \
@@ -870,6 +927,9 @@ static bfd *reldyn_sorting_bfd;
 #define MIPS_ELF_LOAD_WORD(abfd) \
   (ABI_64_P (abfd) ? 0xdc000000 : 0x8c000000)

+/* Opcode for NOP instruction.  */
+#define MIPS_NOP_INSN 0
+
 /* Add a dynamic symbol table-entry.  */
 #define MIPS_ELF_ADD_DYNAMIC_ENTRY(info, tag, val)	\
   _bfd_elf_add_dynamic_entry (info, tag, val)
@@ -1179,6 +1239,81 @@ 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)		*/
+  0x9a40,		/* lw	 $2, 0($2)		*/
+  0xea00,		/* jr	 $2			*/
+  0x653a,		/* move  $25, $2		*/
+  0x0000, 0x0000	/* .word (.igot address)	*/
+};
+
+/* The format of 32 bit IPLT entries.  The trailing NOP is not
+   represented here, but will be added to the last IPLT stub.  */
+static const bfd_vma mips32_exec_iplt_entry[] =
+{
+  0x3c0f0000,	/* lui $15, %hi(.igot address)		*/
+  0x8df90000,	/* lw  $25, %lo(.igot address)($15)	*/
+  0x03200008	/* jr  $25				*/
+};
+
+/* Format of 32 bit IPLT entries for R6. JR encoding differs.  */
+static const bfd_vma mips32r6_exec_iplt_entry[] =
+{
+  0x3c0f0000,	/* lui	$15, %hi(.igot address)		*/
+  0x8df90000,	/* lw	$25, %lo(.igot address)($15)	*/
+  0xd8190000	/* jrc	$25				*/
+};
+
+/* The format of micromips IPLT entries.  We add an extra NOP to round
+   off the entry,  in spite of the compact branch to avoid 32-bit
+   instructions spanning a cache-line boundary.  */
+static const bfd_vma micromips_exec_iplt_entry[] =
+{
+  0x41af, 0x0000,	/* lui $15, %hi(.igot address)		*/
+  0xff2f, 0x0000,	/* lw  $25, %lo(.igot address)($15)	*/
+  0x45b9,		/* jrc $25				*/
+  0x0c00		/* nop					*/
+};
+
+/* The format of 32-bit micromips IPLT entries.  The trailing NOP is not
+   represented here, but will be added to the last IPLT stub.  */
+static const bfd_vma micromips_insn32_exec_iplt_entry[] =
+{
+  0x41af, 0x0000,	/* lui $15, %hi(.igot address)		*/
+  0xff2f, 0x0000,	/* lw  $25, %lo(.igot address)($15)	*/
+  0x0019, 0x0f3c	/* jr $25				*/
+};
+
+/* The format of 64-bit IPLT entries.  The trailing NOP is not
+   represented here, but will be added to the last IPLT stub.  */
+static const bfd_vma mips64_exec_iplt_entry[] =
+{
+  0x3c0f0000,	/* lui $15, %highest(.igot address)		*/
+  0x3c0e0000,	/* lui $14, %hi(.igot address)			*/
+  0x25ef0000,	/* addiu $15, $15, %higher(.igot address)	*/
+  0x000f783c,	/* dsll32 $15, $15, 0x0			*/
+  0x01ee782d,	/* daddu $15, $15, $14				*/
+  0xddf90000,	/* ld $25, %lo(.igot address)($15)		*/
+  0x03200008	/* jr $25					*/
+};
+
+/* The format of 64-bit IPLT entries for 48bit address.  The stub
+   will be padded with NOPs to match the size of a regular
+   64-bit entry.  */
+static const bfd_vma mips64_48b_exec_iplt_entry[] =
+{
+  0x3c0f0000,	/* lui $15, %higher(.got.iplt entry)		*/
+  0x25ef0000,	/* addiu $15, $15, %high(.got.iplt entry)	*/
+  0x000f7c38,	/* dsll $15, $15, 16				*/
+  0xddf90000,	/* ld $25, %lo(.got.iplt entry)($15)		*/
+  0x03200008	/* jr $25					*/
+};
+
 \f
 /* microMIPS 32-bit opcode helper installer.  */

@@ -1272,11 +1407,21 @@ mips_elf_link_hash_newfunc (struct
bfd_hash_entry *entry,
       ret->got_only_for_calls = TRUE;
       ret->readonly_reloc = FALSE;
       ret->has_static_relocs = FALSE;
+      ret->has_got_relocs = FALSE;
+      ret->has_jal_mips_relocs = FALSE;
+      ret->has_jal_comp_relocs = FALSE;
       ret->no_fn_stub = FALSE;
       ret->need_fn_stub = FALSE;
       ret->has_nonpic_branches = FALSE;
       ret->needs_lazy_stub = FALSE;
       ret->use_plt_entry = FALSE;
+      ret->needs_iplt_mips = FALSE;
+      ret->needs_iplt_comp = FALSE;
+      ret->needs_igot = FALSE;
+      ret->needs_ireloc = FALSE;
+      ret->iplt_mips_offset = -1;
+      ret->iplt_comp_offset = -1;
+      ret->igot_offset = -1;
     }

   return (struct bfd_hash_entry *) ret;
@@ -1570,15 +1715,16 @@ static bfd_boolean
 mips_elf_create_stub_symbol (struct bfd_link_info *info,
 			     struct mips_elf_link_hash_entry *h,
 			     const char *prefix, asection *s, bfd_vma value,
-			     bfd_vma size)
+			     bfd_vma size, unsigned int other)
 {
-  bfd_boolean micromips_p = ELF_ST_IS_MICROMIPS (h->root.other);
+  bfd_boolean micromips_p = ELF_ST_IS_MICROMIPS (other);
+  bfd_boolean mips16_p = ELF_ST_IS_MIPS16 (other);
   struct bfd_link_hash_entry *bh;
   struct elf_link_hash_entry *elfh;
   char *name;
   bfd_boolean res;

-  if (micromips_p)
+  if (micromips_p || mips16_p)
     value |= 1;

   /* Create a new symbol.  */
@@ -1596,8 +1742,12 @@ 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 (micromips_p)
     elfh->other = ELF_ST_SET_MICROMIPS (elfh->other);
+  else if (mips16_p)
+    elfh->other = ELF_ST_SET_MIPS16 (elfh->other);
+
   return TRUE;
 }

@@ -1867,7 +2017,8 @@ mips_elf_add_la25_intro (struct mips_elf_la25_stub
*stub,
     s->size = (1 << align) - 8;

   /* Create a symbol for the stub.  */
-  mips_elf_create_stub_symbol (info, stub->h, ".pic.", s, s->size, 8);
+  mips_elf_create_stub_symbol (info, stub->h, ".pic.", s, s->size, 8,
+			       stub->h->root.other);
   stub->stub_section = s;
   stub->offset = s->size;

@@ -1904,7 +2055,8 @@ mips_elf_add_la25_trampoline (struct
mips_elf_la25_stub *stub,
     }

   /* Create a symbol for the stub.  */
-  mips_elf_create_stub_symbol (info, stub->h, ".pic.", s, s->size, 16);
+  mips_elf_create_stub_symbol (info, stub->h, ".pic.", s, s->size, 16,
+			       stub->h->root.other);
   stub->stub_section = s;
   stub->offset = s->size;

@@ -1969,6 +2121,230 @@ 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 one 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 IRELATIVE relocations.  If the link is dynamic, the
+   relocations should go in .rel.dyn, 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;
+}
+
+/* Reserve space in the rel.iplt section for an IRELATIVE relocation.  */
+
+static bfd_boolean
+mips_elf_allocate_ireloc (struct bfd_link_info *info,
+			  struct mips_elf_link_hash_table *mhtab,
+			  struct mips_elf_link_hash_entry *mh)
+{
+  asection *srel;
+  bfd *dynobj;
+
+  if (mh->needs_ireloc)
+    return TRUE;
+
+  srel = mips_get_irel_section (info, mhtab);
+  dynobj = elf_hash_table (info)->dynobj;
+  if (srel != mhtab->root.irelplt && srel->size == 0)
+    {
+      /* Make room for a null element.  */
+      srel->size += MIPS_ELF_REL_SIZE (dynobj);
+      ++srel->reloc_count;
+    }
+  srel->size += MIPS_ELF_REL_SIZE (dynobj);
+  mh->needs_ireloc = TRUE;
+
+  return TRUE;
+}
+
+/* Reserve space in the iplt and igot tables for an ifunc entry
+   and allocate space for an IRELATIVE relocation.  */
+
+static bfd_boolean
+mips_elf_allocate_iplt (struct bfd_link_info *info,
+			struct mips_elf_link_hash_table *mhtab,
+			struct mips_elf_link_hash_entry *mh)
+{
+  bfd *abfd = info->output_bfd;
+
+  BFD_ASSERT (mhtab->root.iplt != NULL);
+  BFD_ASSERT (mhtab->root.igotplt != NULL);
+
+  /* Enable stubs according to caller modes.  */
+  mh->needs_iplt_comp = mh->has_jal_comp_relocs;
+  mh->needs_iplt_mips = mh->has_jal_mips_relocs;
+
+  /* An IPLT stub is requested, but no call relocs were found.
+     Pick stub from mode of resolver.  */
+  if (!mh->needs_iplt_comp && !mh->needs_iplt_mips)
+    {
+      mh->needs_iplt_comp = ELF_ST_IS_COMPRESSED (mh->root.other);
+      mh->needs_iplt_mips = !ELF_ST_IS_COMPRESSED (mh->root.other);
+    }
+
+  /* Compressed entries can be allocated immediately.  */
+  if (mh->needs_iplt_comp)
+    {
+      int entry_size = (mhtab->iplt_comp_entry_size
+			+ (mhtab->insn32 ? 4 : 0));
+
+      mh->iplt_comp_offset = mhtab->iplt_comp_offset;
+      if (!mips_elf_create_stub_symbol (info, mh, ".iplt.comp.",
+					mhtab->root.iplt,
+					mhtab->iplt_comp_offset, entry_size,
+					(MICROMIPS_P (info->output_bfd)
+					 ? STO_MICROMIPS
+					 : STO_MIPS16)))
+	return FALSE;
+      mhtab->iplt_comp_offset += mhtab->iplt_comp_entry_size;
+    }
+
+  /* Regular entries are allocated partially, to be completed after all
+     compressed entries have been allocated.  */
+  if (mh->needs_iplt_mips)
+    {
+      mh->iplt_mips_offset = mhtab->iplt_mips_offset;
+      mhtab->iplt_mips_offset += mhtab->iplt_mips_entry_size;
+    }
+
+  /* Only create IGOT entry if there are no GOT relocations, or when
+     there are non-CALL references to the symbol.  In the latter case,
+     existing GOT entry must point to IPLT, so an IGOT entry is needed
+     to catch the resolution of the IRELATIVE relocation.  */
+  if (!mh->has_got_relocs || mh->root.pointer_equality_needed)
+    {
+      mh->igot_offset = mhtab->root.igotplt->size;
+      mhtab->root.igotplt->size += MIPS_ELF_GOT_SIZE (abfd);
+      mh->needs_igot = TRUE;
+    }
+
+  /* IRELATIVE fixup will be needed for each IPLT entry.  */
+  if (!mips_elf_allocate_ireloc (info, mips_elf_hash_table (info), mh))
+    return FALSE;
+
+  return TRUE;
+}
+
+/* htab_traverse callback that is called before sizing sections.
+   DATA points to a mips_htab_traverse_info structure.  */
+
+static bfd_boolean
+mips_elf_check_ifunc_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 (h
+      && !h->needs_iplt_mips
+      && !h->needs_iplt_comp
+      && h->root.type == STT_GNU_IFUNC
+      && h->root.def_regular)
+    {
+      struct bfd_link_info *info = hti->info;
+
+      /* For global symbols, an .iplt entry is needed in non-PIC
+	 binaries.  For local symbols, it is needed only if the symbol
+	 has static relocations.  */
+      if ((h->root.forced_local ? h->has_static_relocs : !bfd_link_pic
(info))
+	  && !mips_elf_allocate_iplt (info, mips_elf_hash_table (info), h))
+	{
+	  hti->error = TRUE;
+	  return FALSE;
+	}
+    }
+
+  return TRUE;
+}
+
+/* htab_traverse callback that is called before sizing sections to lay-out
+   all regular IPLT entries after the compressed ones.  DATA points to a
+   mips_htab_traverse_info structure.  */
+
+static bfd_boolean
+mips_elf_lay_out_iplt (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 (h->needs_iplt_mips)
+    {
+      struct bfd_link_info *info = hti->info;
+      struct mips_elf_link_hash_table *mhtab = mips_elf_hash_table (info);
+      bfd_vma entry_size = mhtab->iplt_mips_entry_size;
+
+      /* Size up the last IPLT stub for the lagging delay slot.  */
+      if (!MIPSR6_P (mhtab->root.dynobj)
+	  && (h->iplt_mips_offset + entry_size == mhtab->iplt_mips_offset))
+	entry_size += 4;
+      h->iplt_mips_offset += mhtab->iplt_comp_offset;
+
+      if (!mips_elf_create_stub_symbol (info, h, ".iplt.",
mhtab->root.iplt,
+					h->iplt_mips_offset, entry_size, 0))
+	{
+	  hti->error = TRUE;
+	  return FALSE;
+	}
+    }
+
+  return TRUE;
+}
+
+/* A mips_elf_link_hash_traverse callback that is called before sizing
+   sections to lay-out regular IPLT entries after the compressed ones.
+   DATA points to a mips_htab_traverse_info structure.  */
+
+static bfd_boolean
+mips_elf_lay_out_iplt_wrap (struct mips_elf_link_hash_entry *h, void *data)
+{
+  struct mips_htab_traverse_info *hti
+    = (struct mips_htab_traverse_info *) data;
+
+  return mips_elf_lay_out_iplt ((void **) &h, hti);
+}
+
+
 /* A mips_elf_link_hash_traverse callback that is called before sizing
    sections.  DATA points to a mips_htab_traverse_info structure.  */

@@ -1981,6 +2357,12 @@ mips_elf_check_symbols (struct
mips_elf_link_hash_entry *h, void *data)
   if (!bfd_link_relocatable (hti->info))
     mips_elf_check_mips16_stubs (hti->info, h);

+  /* Create stubs and relocations for IFUNC symbols.  */
+  if (h
+      && h->root.type == STT_GNU_IFUNC
+      && !mips_elf_check_ifunc_symbols ((void **) &h, hti))
+    return FALSE;
+
   if (mips_elf_local_pic_function_p (h))
     {
       /* PR 12845: If H is in a section that has been garbage
@@ -1992,13 +2374,16 @@ mips_elf_check_symbols (struct
mips_elf_link_hash_entry *h, void *data)
 	 If we're creating a non-PIC relocatable object, mark H as
 	 being PIC.  If we're creating a non-relocatable object with
 	 non-PIC branches and jumps to H, make sure that H has an la25
-	 stub.  */
+	 stub.  IFUNCs with IPLT stubs don't need an la25 stub.  */
       if (bfd_link_relocatable (hti->info))
 	{
 	  if (!PIC_OBJECT_P (hti->output_bfd))
 	    h->root.other = ELF_ST_SET_MIPS_PIC (h->root.other);
 	}
-      else if (h->has_nonpic_branches && !mips_elf_add_la25_stub
(hti->info, h))
+      else if (h->has_nonpic_branches
+	       && !(h->root.type == STT_GNU_IFUNC
+		    && (h->needs_iplt_mips || h->needs_iplt_comp))
+	       && !mips_elf_add_la25_stub (hti->info, h))
 	{
 	  hti->error = TRUE;
 	  return FALSE;
@@ -3171,37 +3556,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
@@ -3283,7 +3637,7 @@ mips_tls_got_relocs (struct bfd_link_info *info,
unsigned char tls_type,
 /* Add the number of GOT entries and TLS relocations required by ENTRY
    to G.  */

-static void
+static bfd_boolean
 mips_elf_count_got_entry (struct bfd_link_info *info,
 			  struct mips_got_info *g,
 			  struct mips_got_entry *entry)
@@ -3295,10 +3649,24 @@ mips_elf_count_got_entry (struct bfd_link_info
*info,
 					entry->symndx < 0
 					? &entry->d.h->root : NULL);
     }
-  else if (entry->symndx >= 0 || entry->d.h->global_got_area == GGA_NONE)
-    g->local_gotno += 1;
-  else
+  else if (entry->symndx < 0 && entry->d.h->global_got_area != GGA_NONE)
     g->global_gotno += 1;
+  else if (entry->symndx < 0
+	   && entry->d.h->root.type == STT_GNU_IFUNC
+	   && entry->d.h->root.def_regular
+	   && !entry->d.h->needs_igot)
+    {
+      /* Skip IFUNCs from local/global GOT, they are already counted
+	 as general GOT entries with explicit relocations.  */
+      g->general_gotno += 1;
+      if (!mips_elf_allocate_ireloc (info, mips_elf_hash_table (info),
+				     entry->d.h))
+	return FALSE;
+    }
+  else
+    g->local_gotno += 1;
+
+  return TRUE;
 }

 /* Output a simple dynamic relocation into SRELOC.  */
@@ -3320,6 +3688,9 @@ mips_elf_output_dynamic_relocation (bfd *output_bfd,

   if (ABI_64_P (output_bfd))
     {
+      if (r_type == R_MIPS_IRELATIVE)
+	rel[1].r_info = ELF_R_INFO (output_bfd, indx, R_MIPS_64);
+
       (*get_elf_backend_data (output_bfd)->s->swap_reloc_out)
 	(output_bfd, &rel[0],
 	 (sreloc->contents
@@ -3628,7 +3999,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;

@@ -3643,7 +4015,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
@@ -3688,6 +4060,7 @@ mips_elf_create_local_got_entry (bfd *abfd, struct
bfd_link_info *info,
   struct mips_got_info *g;
   struct mips_elf_link_hash_table *htab;
   bfd_vma gotidx;
+  bfd_boolean general_got_p;

   htab = mips_elf_hash_table (info);
   BFD_ASSERT (htab != NULL);
@@ -3732,22 +4105,35 @@ mips_elf_create_local_got_entry (bfd *abfd,
struct bfd_link_info *info,
       return entry;
     }

-  lookup.abfd = NULL;
   lookup.symndx = -1;
-  lookup.d.address = value;
+  if (h && h->root.type == STT_GNU_IFUNC)
+    {
+      lookup.abfd = ibfd;
+      lookup.d.h = h;
+    }
+  else
+    {
+      lookup.abfd = NULL;
+      lookup.d.address = value;
+    }
+
   loc = htab_find_slot (g->got_entries, &lookup, INSERT);
   if (!loc)
     return NULL;

   entry = (struct mips_got_entry *) *loc;
-  if (entry)
+  if (entry && entry->gotidx >= 0)
     return entry;

-  if (g->assigned_low_gotno > g->assigned_high_gotno)
+  general_got_p = (h && h->needs_ireloc && !h->needs_igot);
+
+  if ((!general_got_p && g->assigned_low_gotno > g->assigned_high_gotno)
+      || g->assigned_general_gotno > g->local_gotno)
     {
       /* We didn't allocate enough space in the GOT.  */
-      _bfd_error_handler
-	(_("not enough GOT space for local GOT entries"));
+      (*_bfd_error_handler)
+	(_("not enough GOT space for %s GOT entries"),
+	 general_got_p ? "general" : "local");
       bfd_set_error (bfd_error_bad_value);
       return NULL;
     }
@@ -3756,7 +4142,15 @@ mips_elf_create_local_got_entry (bfd *abfd,
struct bfd_link_info *info,
   if (!entry)
     return NULL;

-  if (got16_reloc_p (r_type)
+  if (general_got_p)
+    {
+      /* Allocate IFUNC slots in the general GOT region since they
+	 will need explicit IRELATIVE relocations.  */
+      lookup.gotidx = MIPS_ELF_GOT_SIZE (abfd) *
g->assigned_general_gotno++;
+      if (h->needs_igot)
+	h->igot_offset = lookup.gotidx;
+    }
+  else if (got16_reloc_p (r_type)
       || call16_reloc_p (r_type)
       || got_page_reloc_p (r_type)
       || got_disp_reloc_p (r_type))
@@ -3999,7 +4393,8 @@ mips_elf_record_global_got_symbol (struct
elf_link_hash_entry *h,

 static bfd_boolean
 mips_elf_record_local_got_symbol (bfd *abfd, long symndx, bfd_vma addend,
-				  struct bfd_link_info *info, int r_type)
+				  struct bfd_link_info *info, int r_type,
+				  struct mips_elf_link_hash_entry *h)
 {
   struct mips_elf_link_hash_table *htab;
   struct mips_got_info *g;
@@ -4013,7 +4408,10 @@ mips_elf_record_local_got_symbol (bfd *abfd, long
symndx, bfd_vma addend,

   entry.abfd = abfd;
   entry.symndx = symndx;
-  entry.d.addend = addend;
+  if (h)
+    entry.d.h = h;
+  else
+    entry.d.addend = addend;
   entry.tls_type = mips_elf_reloc_tls_type (r_type);
   return mips_elf_record_got_entry (info, abfd, &entry);
 }
@@ -4133,7 +4531,11 @@ mips_elf_check_recreate_got (void **entryp, void
*data)
 	  return 0;
 	}
     }
-  mips_elf_count_got_entry (arg->info, arg->g, entry);
+  if (!mips_elf_count_got_entry (arg->info, arg->g, entry))
+    {
+      arg->g = NULL;
+      return 0;
+    }
   return 1;
 }

@@ -4189,7 +4591,11 @@ mips_elf_recreate_got (void **entryp, void *data)
 	  *entry = new_entry;
 	}
       *slot = entry;
-      mips_elf_count_got_entry (arg->info, arg->g, entry);
+      if (!mips_elf_count_got_entry (arg->info, arg->g, entry))
+	{
+	  arg->g = NULL;
+	  return 0;
+	}
     }
   return 1;
 }
@@ -4430,7 +4836,11 @@ mips_use_local_got_p (struct bfd_link_info *info,
      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)
+
+  /* Both global & local IFUNC symbols actually use the explicitly
relocated
+     GOT region, but we don't distinguish it from the local GOT just
yet.  */
+  if (h->root.dynindx == -1
+      || (h->root.type == STT_GNU_IFUNC && h->root.def_regular))
     return TRUE;

   /* Symbols that bind locally can (and in the case of forced-local
@@ -4511,7 +4921,11 @@ mips_elf_add_got_entry (void **entryp, void *data)
   if (!*slot)
     {
       *slot = entry;
-      mips_elf_count_got_entry (arg->info, arg->g, entry);
+      if (!mips_elf_count_got_entry (arg->info, arg->g, entry))
+	{
+	  arg->g = NULL;
+	  return 0;
+	}
     }
   return 1;
 }
@@ -5116,6 +5530,85 @@ 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 (bfd *abfd, 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);
+  if (htab->root.dynobj == NULL)
+    htab->root.dynobj = abfd;
+  dynobj = htab->root.dynobj;
+  bed = get_elf_backend_data (dynobj);
+  flags = bed->dynamic_sec_flags;
+
+  /* This function may be called multiple times.  */
+  if ((elf_tdata (info->output_bfd)->has_gnu_symbols &
elf_gnu_symbol_ifunc)
+      == elf_gnu_symbol_ifunc)
+    return TRUE;
+
+  if (!bfd_link_pic (info))
+    {
+      int mips32_size = (MIPSR6_P (dynobj)
+			 ? 4 * ARRAY_SIZE (mips32r6_exec_iplt_entry)
+			 : 4 * ARRAY_SIZE (mips32_exec_iplt_entry));
+
+      if (ABI_64_P (dynobj))
+	htab->iplt_mips_entry_size = 4 * ARRAY_SIZE (mips64_exec_iplt_entry);
+      else if (!MICROMIPS_P (dynobj))	/* mips32/mips16  */
+	{
+	  htab->iplt_mips_entry_size = (mips32_size
+					+ (LOAD_INTERLOCKS_P (dynobj) ? 0 : 4));
+	  htab->iplt_comp_entry_size = 2 * ARRAY_SIZE (mips16_exec_iplt_entry);
+	}
+      else if (htab->insn32)		/* mips32/micromips + insn32  */
+	{
+	  htab->iplt_mips_entry_size = mips32_size;
+	  htab->iplt_comp_entry_size
+	    = 2 * ARRAY_SIZE (micromips_insn32_exec_iplt_entry);
+	}
+      else				/* mips32/micromips  */
+	{
+	  htab->iplt_mips_entry_size = mips32_size;
+	  htab->iplt_comp_entry_size
+	    = 2 * ARRAY_SIZE (micromips_exec_iplt_entry);
+	}
+
+      BFD_ASSERT (htab->root.iplt == NULL);
+      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;
+
+      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;
+    }
+
+  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;
+
+  /* Mark the output BFD.  */
+  elf_tdata (info->output_bfd)->has_gnu_symbols |= elf_gnu_symbol_ifunc;
+
+  return TRUE;
+}
+
 /* Create the .got section to hold the global offset table.  */

 static bfd_boolean
@@ -5233,6 +5726,80 @@ mips_elf_relocation_needs_la25_stub (bfd
*input_bfd, int r_type,
     }
 }
 \f
+/* 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 = NULL;
+  asection *sec;
+  hashval_t h;
+  void **slot;
+  Elf_Internal_Sym *isym;
+  Elf_Internal_Shdr *symtab_hdr;
+  char *namep;
+
+  sec = abfd->sections;
+  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));
+  if (isym == NULL)
+    return NULL;
+
+  symtab_hdr = &elf_tdata (abfd)->symtab_hdr;
+  namep = bfd_elf_string_from_elf_section (abfd, symtab_hdr->sh_link,
+					   isym->st_name);
+
+  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)));
+
+  /* Mark the input BFD that declared this IFUNC.  */
+  elf_tdata (abfd)->has_gnu_symbols |= elf_gnu_symbol_ifunc;
+
+  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;
+      ret->igot_offset = -1;
+      ret->root.other = isym->st_other;
+      ret->got_only_for_calls = TRUE;
+
+      *slot = ret;
+    }
+
+  return ret;
+}
+\f
 /* Calculate the value produced by the RELOCATION (which comes from
    the INPUT_BFD).  The ADDEND is the addend to use for this
    RELOCATION; RELOCATION->R_ADDEND is ignored.
@@ -5285,6 +5852,8 @@ mips_elf_calculate_relocation (bfd *abfd, bfd
*input_bfd,
   /* TRUE if the symbol referred to by this relocation is a section
      symbol.  */
   bfd_boolean section_p = FALSE;
+  /* 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
@@ -5383,6 +5952,15 @@ 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);
+	      if (h == NULL)
+		return bfd_reloc_notsupported;
+	      local_gnu_ifunc_p = TRUE;
+	    }
 	}
     }
   else
@@ -5610,6 +6188,33 @@ 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_mips || h->needs_iplt_comp)
+	   && r_type != R_MIPS_GPREL16
+	   && (h->needs_igot
+	       || (!call16_reloc_p (r_type) && !got16_reloc_p (r_type))))
+    {
+      bfd_boolean comp_p;
+      BFD_ASSERT (htab->root.iplt != NULL);
+
+      symbol = (htab->root.iplt->output_section->vma
+		+ htab->root.iplt->output_offset);
+
+      /* Conditions for deciding between regular and compressed IPLT stubs:
+	 if only one type is available, use it;  else choose by mode of
+	 referring code.  */
+      if (h->needs_iplt_mips ^ h->needs_iplt_comp)
+	comp_p = h->needs_iplt_comp;
+      else
+	comp_p = mips16_reloc_p (r_type) || micromips_reloc_p (r_type);
+
+      target_is_micromips_code_p = (comp_p && !mips16_reloc_p (r_type));
+      target_is_16_bit_code_p = (comp_p && !micromips_reloc_p (r_type));
+
+      /* Set ISA bit in address for compressed stub.  */
+      symbol += (comp_p ? h->iplt_comp_offset + 1 : h->iplt_mips_offset);
+    }

   /* Make sure MIPS16 and microMIPS are not used together.  */
   if ((mips16_branch_reloc_p (r_type) && target_is_micromips_code_p)
@@ -5694,7 +6299,11 @@ mips_elf_calculate_relocation (bfd *abfd, bfd
*input_bfd,
 	  if (g == MINUS_ONE)
 	    return bfd_reloc_outofrange;
 	}
-      else if (!local_p)
+      else if (local_p && !htab->is_vxworks
+	       && (call16_reloc_p (r_type) || got16_reloc_p (r_type)))
+	/* The calculation below does not involve "g".  */
+	break;
+      else if (!local_p && !(h && h->needs_ireloc && !h->needs_igot))
 	{
 	  /* On VxWorks, CALL relocations should refer to the .got.plt
 	     entry, which is initialized to point at the PLT stub.  */
@@ -5718,12 +6327,10 @@ mips_elf_calculate_relocation (bfd *abfd, bfd
*input_bfd,
 		MIPS_ELF_PUT_WORD (dynobj, symbol, htab->root.sgot->contents + g);
 	    }
 	}
-      else if (!htab->is_vxworks
-	       && (call16_reloc_p (r_type) || got16_reloc_p (r_type)))
-	/* The calculation below does not involve "g".  */
-	break;
       else
 	{
+	  /* IFUNCs use the explicitly-relocated GOT region, however we don't
+	     distinguish it from the local GOT at this stage.  */
 	  g = mips_elf_local_got_index (abfd, input_bfd, info,
 					symbol + addend, r_symndx, h, r_type);
 	  if (g == MINUS_ONE)
@@ -6001,8 +6608,10 @@ mips_elf_calculate_relocation (bfd *abfd, bfd
*input_bfd,
 	 R_MIPS*_GOT16; every relocation evaluates to "G".  */
       if (!htab->is_vxworks && local_p)
 	{
-	  value = mips_elf_got16_entry (abfd, input_bfd, info,
-					symbol + addend, !was_local_p);
+	  /* Local IFUNC symbols must be accessed through GOT, similar to
+	     global symbols, to allow for indirection.  */
+	  value = mips_elf_got16_entry (abfd, input_bfd, info, symbol + addend,
+					!was_local_p || local_gnu_ifunc_p, h);
 	  if (value == MINUS_ONE)
 	    return bfd_reloc_outofrange;
 	  value
@@ -6267,8 +6876,13 @@ mips_elf_calculate_relocation (bfd *abfd, bfd
*input_bfd,
     case R_MICROMIPS_JALR:
       /* This relocation is only a hint.  In some cases, we optimize
 	 it into a bal instruction.  But we don't try to optimize
-	 when the symbol does not resolve locally.  */
-      if (h != NULL && !SYMBOL_CALLS_LOCAL (info, &h->root))
+	 when the symbol does not resolve locally.  Likewise for
+         IFUNCs that don't have an IPLT stub,  since there is no
+         canonical address for the jump.  */
+      if (h != NULL && (!SYMBOL_CALLS_LOCAL (info, &h->root)
+			|| (h->root.type == STT_GNU_IFUNC
+			    && !h->needs_iplt_mips
+			    && !h->needs_iplt_comp)))
 	return bfd_reloc_continue;
       value = symbol + addend;
       break;
@@ -6549,7 +7163,9 @@ mips_elf_create_dynamic_relocation (bfd *output_bfd,
      in the relocation.  */
   if (h != NULL && ! SYMBOL_REFERENCES_LOCAL (info, &h->root))
     {
-      BFD_ASSERT (htab->is_vxworks || h->global_got_area != GGA_NONE);
+      BFD_ASSERT (htab->is_vxworks
+		  || h->global_got_area != GGA_NONE
+		  || h->root.type == STT_GNU_IFUNC);
       indx = h->root.dynindx;
       if (SGI_COMPAT (output_bfd))
 	defined_p = h->root.def_regular;
@@ -6608,31 +7224,44 @@ mips_elf_create_dynamic_relocation (bfd *output_bfd,
   if (defined_p && r_type != R_MIPS_REL32)
     *addendp += symbol;

-  if (htab->is_vxworks)
-    /* VxWorks uses non-relative relocations for this.  */
-    outrel[0].r_info = ELF32_R_INFO (indx, R_MIPS_32);
+  /* Morph REL32 in to IRELATIVE fix-up for local IFUNC reference.  */
+  if (h
+      && h->root.type == STT_GNU_IFUNC
+      && SYMBOL_REFERENCES_LOCAL (info, &h->root))
+    {
+      outrel[0].r_info = ELF_R_INFO (output_bfd, 0, R_MIPS_IRELATIVE);
+      outrel[1].r_info = ELF_R_INFO (output_bfd, 0, (ABI_64_P (output_bfd)
+						     ? R_MIPS_64
+						     : R_MIPS_NONE));
+    }
   else
-    /* The relocation is always an REL32 relocation because we don't
-       know where the shared library will wind up at load-time.  */
-    outrel[0].r_info = ELF_R_INFO (output_bfd, (unsigned long) indx,
-				   R_MIPS_REL32);
-
-  /* For strict adherence to the ABI specification, we should
-     generate a R_MIPS_64 relocation record by itself before the
-     _REL32/_64 record as well, such that the addend is read in as
-     a 64-bit value (REL32 is a 32-bit relocation, after all).
-     However, since none of the existing ELF64 MIPS dynamic
-     loaders seems to care, we don't waste space with these
-     artificial relocations.  If this turns out to not be true,
-     mips_elf_allocate_dynamic_relocation() should be tweaked so
-     as to make room for a pair of dynamic relocations per
-     invocation if ABI_64_P, and here we should generate an
-     additional relocation record with R_MIPS_64 by itself for a
-     NULL symbol before this relocation record.  */
-  outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
-				 ABI_64_P (output_bfd)
-				 ? R_MIPS_64
-				 : R_MIPS_NONE);
+    {
+      if (htab->is_vxworks)
+	/* VxWorks uses non-relative relocations for this.  */
+	outrel[0].r_info = ELF32_R_INFO (indx, R_MIPS_32);
+      else
+	/* The relocation is always an REL32 relocation because we don't
+	   know where the shared library will wind up at load-time.  */
+	outrel[0].r_info = ELF_R_INFO (output_bfd, (unsigned long) indx,
+				       R_MIPS_REL32);
+
+      /* For strict adherence to the ABI specification, we should
+	 generate a R_MIPS_64 relocation record by itself before the
+	 _REL32/_64 record as well, such that the addend is read in as
+	 a 64-bit value (REL32 is a 32-bit relocation, after all).
+	 However, since none of the existing ELF64 MIPS dynamic
+	 loaders seems to care, we don't waste space with these
+	 artificial relocations.  If this turns out to not be true,
+	 mips_elf_allocate_dynamic_relocation() should be tweaked so
+	 as to make room for a pair of dynamic relocations per
+	 invocation if ABI_64_P, and here we should generate an
+	 additional relocation record with R_MIPS_64 by itself for a
+	 NULL symbol before this relocation record.  */
+      outrel[1].r_info = ELF_R_INFO (output_bfd, 0,
+				     ABI_64_P (output_bfd)
+				     ? R_MIPS_64
+				     : R_MIPS_NONE);
+    }
   outrel[2].r_info = ELF_R_INFO (output_bfd, 0, R_MIPS_NONE);

   /* Adjust the output offset of the relocation to reference the
@@ -7501,6 +8130,8 @@ _bfd_mips_elf_fake_sections (bfd *abfd,
Elf_Internal_Shdr *hdr, asection *sec)
       hdr->sh_flags |= SHF_ALLOC;
       hdr->sh_entsize = 8;
     }
+  else if (strcmp (name, ".igot") == 0)
+    hdr->sh_entsize = MIPS_ELF_GOT_SIZE (abfd);

   /* The generic elf_fake_sections will set up REL_HDR using the default
    kind of relocations.  We used to set up a second header for the
@@ -7700,6 +8331,10 @@ _bfd_mips_elf_add_symbol_hook (bfd *abfd, struct
bfd_link_info *info,
   if (ELF_ST_IS_COMPRESSED (sym->st_other))
     ++*valp;

+  /* Mark the input BFD that declares an IFUNC.  */
+  if (ELF_ST_TYPE(sym->st_info) == STT_GNU_IFUNC)
+    elf_tdata (abfd)->has_gnu_symbols |= elf_gnu_symbol_ifunc;
+
   return TRUE;
 }

@@ -8346,15 +8981,34 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct
bfd_link_info *info,
       unsigned long r_symndx;
       unsigned int r_type;
       struct elf_link_hash_entry *h;
+      struct mips_elf_link_hash_entry *ih = NULL;
       bfd_boolean can_make_dynamic_p;
       bfd_boolean call_reloc_p;
+      bfd_boolean has_jal_reloc_p;
       bfd_boolean constrain_symbol_p;

       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;
+
+	  /* Relocation is for local STT_GNU_IFUNC symbol.  */
+	  if (isym->st_info == STT_GNU_IFUNC)
+	    {
+	      /* Ensure that we have a hash entry for this symbol.  */
+	      ih = get_local_sym_hash (htab, abfd, rel);
+	      if (ih == NULL)
+		return FALSE;
+	    }
+
+	  h = NULL;
+       }
       else if (r_symndx >= extsymoff + NUM_SHDR_ENTRIES (symtab_hdr))
 	{
 	  _bfd_error_handler
@@ -8387,6 +9041,10 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct
bfd_link_info *info,
 	 and if pointer equality therefore doesn't matter.  */
       call_reloc_p = FALSE;

+      /* Set JAL_RELOC_P to true if the relocation is for a direct
+	 jump-and-link call.  */
+      has_jal_reloc_p = FALSE;
+
       /* Set CONSTRAIN_SYMBOL_P if we need to take the relocation
 	 into account when deciding how to define the symbol.
 	 Relocations in nonallocatable sections such as .pdr and
@@ -8491,12 +9149,14 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct
bfd_link_info *info,
 	  break;

 	case R_MIPS_26:
+	case R_MIPS16_26:
+	case R_MICROMIPS_26_S1:
+	  has_jal_reloc_p = TRUE;
+	  /* Fall through.  */
 	case R_MIPS_PC16:
 	case R_MIPS_PC21_S2:
 	case R_MIPS_PC26_S2:
-	case R_MIPS16_26:
 	case R_MIPS16_PC16_S1:
-	case R_MICROMIPS_26_S1:
 	case R_MICROMIPS_PC7_S1:
 	case R_MICROMIPS_PC10_S1:
 	case R_MICROMIPS_PC16_S1:
@@ -8510,7 +9170,20 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct
bfd_link_info *info,
 	  if (constrain_symbol_p)
 	    {
 	      if (!can_make_dynamic_p)
-		((struct mips_elf_link_hash_entry *) h)->has_static_relocs = 1;
+		{
+		  struct mips_elf_link_hash_entry *mh
+		    = (struct mips_elf_link_hash_entry *) h;
+
+		  mh->has_static_relocs = TRUE;
+		  if (!bfd_link_pic (info)
+		      && has_jal_reloc_p)
+		    {
+		      if (r_type == R_MIPS_26)
+			mh->has_jal_mips_relocs = TRUE;
+		      else
+			mh->has_jal_comp_relocs = TRUE;
+		    }
+		}

 	      if (!call_reloc_p)
 		h->pointer_equality_needed = 1;
@@ -8541,6 +9214,24 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct
bfd_link_info *info,
 		info->flags |= DF_TEXTREL;
 	    }
 	}
+      else if (ih)
+	{
+	  if (constrain_symbol_p
+	      && !can_make_dynamic_p
+	      && !bfd_link_pic (info))
+	    {
+	      ih->has_static_relocs = TRUE;
+	      if (has_jal_reloc_p)
+		{
+		  if (r_type == R_MIPS_26)
+		    ih->has_jal_mips_relocs = TRUE;
+		  else
+		    ih->has_jal_comp_relocs = TRUE;
+		}
+	    }
+	  if (!call_reloc_p)
+	    ih->root.pointer_equality_needed = TRUE;
+	}
       else if (call_lo16_reloc_p (r_type)
 	       || got_lo16_reloc_p (r_type)
 	       || got_disp_reloc_p (r_type)
@@ -8554,8 +9245,8 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct
bfd_link_info *info,
 	     always evaluate to "G".  We don't count R_MIPS_GOT_HI16, or
 	     R_MIPS_CALL_HI16 because these are always followed by an
 	     R_MIPS_GOT_LO16 or R_MIPS_CALL_LO16.  */
-	  if (!mips_elf_record_local_got_symbol (abfd, r_symndx,
-						 rel->r_addend, info, r_type))
+	  if (!mips_elf_record_local_got_symbol (abfd, r_symndx, rel->r_addend,
+						 info, r_type, NULL))
 	    return FALSE;
 	}

@@ -8569,7 +9260,7 @@ _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)
+	  if (h == NULL && ih == NULL)
 	    {
 	      _bfd_error_handler
 		/* xgettext:c-format */
@@ -8578,6 +9269,10 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct
bfd_link_info *info,
 	      bfd_set_error (bfd_error_bad_value);
 	      return FALSE;
 	    }
+	  if (h != NULL)
+	    ((struct mips_elf_link_hash_entry *) h)->has_got_relocs = TRUE;
+	  else if (ih != NULL)
+	    ih->has_got_relocs = TRUE;
 	  /* Fall through.  */

 	case R_MIPS_CALL_HI16:
@@ -8595,10 +9290,17 @@ _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;
 	    }
+	  else
+	    if (ih &&
+		!mips_elf_record_local_got_symbol (abfd, -1, rel->r_addend,
+						   info, r_type, ih))
+	      return FALSE;
 	  break;

 	case R_MIPS_GOT_PAGE:
@@ -8631,8 +9333,13 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct
bfd_link_info *info,
 		}
 	      else
 		addend = rel->r_addend;
-	      if (!mips_elf_record_got_page_ref (info, abfd, r_symndx,
-						 h, addend))
+
+	      if (ih &&
+		  !mips_elf_record_local_got_symbol (abfd, -1, rel->r_addend,
+						     info, r_type, ih))
+		return FALSE;
+	      else if (!mips_elf_record_got_page_ref (info, abfd, r_symndx,
+						   h, addend))
 		return FALSE;

 	      if (h)
@@ -8656,6 +9363,15 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct
bfd_link_info *info,
 	  if (h && !mips_elf_record_global_got_symbol (h, abfd, info,
 						       FALSE, r_type))
 	    return FALSE;
+	  else if (ih && !mips_elf_record_local_got_symbol (abfd, -1,
+							    rel->r_addend,
+							    info, r_type, ih))
+	    return FALSE;
+
+	  if (h != NULL)
+	    ((struct mips_elf_link_hash_entry *) h)->has_got_relocs = TRUE;
+	  else if (ih != NULL)
+	    ih->has_got_relocs = TRUE;
 	  break;

 	case R_MIPS_TLS_GOTTPREL:
@@ -8690,7 +9406,7 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct
bfd_link_info *info,
 	    {
 	      if (!mips_elf_record_local_got_symbol (abfd, r_symndx,
 						     rel->r_addend,
-						     info, r_type))
+						     info, r_type, NULL))
 		return FALSE;
 	    }
 	  break;
@@ -8870,6 +9586,13 @@ _bfd_mips_elf_check_relocs (bfd *abfd, struct
bfd_link_info *info,
 	}
     }

+  /* This needs to happen early.  If the sections aren't needed
+     they will not get generated.  */
+  if (((elf_tdata (abfd)->has_gnu_symbols & elf_gnu_symbol_ifunc)
+       == elf_gnu_symbol_ifunc)
+      && !mips_elf_create_ifunc_sections (abfd, info))
+    return FALSE;
+
   return TRUE;
 }
 \f
@@ -9140,6 +9863,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
@@ -9431,6 +10155,36 @@ _bfd_mips_elf_always_size_sections (bfd *output_bfd,
   if (hti.error)
     return FALSE;

+  /* Allocate relocs for local IFUNC symbols.  */
+  htab_traverse (htab->loc_hash_table, mips_elf_check_ifunc_symbols, &hti);
+  if (hti.error)
+    return FALSE;
+
+  /* All compressed IPLT entries have been allocated.  Now we can lay-out
+     the regular IPLT entries and create corresponding stub symbols.  */
+
+  /* Add space for a NOP in the last delay slot of insn32 stub.  */
+  if (htab->iplt_comp_offset != 0 && htab->insn32)
+    htab->iplt_comp_offset += 4;
+
+  /* Global IFUNCs.  */
+  mips_elf_link_hash_traverse (htab, mips_elf_lay_out_iplt_wrap, &hti);
+  if (hti.error)
+    return FALSE;
+
+  /* Local IFUNCs.  */
+  htab_traverse (htab->loc_hash_table, mips_elf_lay_out_iplt, &hti);
+  if (hti.error)
+    return FALSE;
+
+  /* Add space for a NOP in the last delay slot.  */
+  if (!MIPSR6_P (output_bfd) && htab->iplt_mips_offset != 0)
+    htab->iplt_mips_offset += 4;
+
+  /* Calculate final size of IPLT section.  */
+  if (htab->root.iplt)
+    htab->root.iplt->size = htab->iplt_comp_offset +
htab->iplt_mips_offset;
+
   return TRUE;
 }

@@ -9475,6 +10229,10 @@ mips_elf_lay_out_got (bfd *output_bfd, struct
bfd_link_info *info)
   if (!mips_elf_resolve_final_got_entries (info, g))
     return FALSE;

+  g->assigned_general_gotno = htab->reserved_gotno;
+  g->local_gotno += g->general_gotno;
+  g->assigned_low_gotno += g->general_gotno;
+
   /* Calculate the total loadable size of the output.  That
      will give us the maximum number of GOT_PAGE entries
      required.  */
@@ -9916,6 +10674,8 @@ _bfd_mips_elf_size_dynamic_sections (bfd *output_bfd,
       else if (! CONST_STRNEQ (name, ".init")
 	       && s != htab->root.sgot
 	       && s != htab->root.sgotplt
+	       && s != htab->root.iplt
+	       && s != htab->root.igotplt
 	       && s != htab->sstubs
 	       && s != htab->root.sdynbss
 	       && s != htab->root.sdynrelro)
@@ -10062,6 +10822,14 @@ _bfd_mips_elf_size_dynamic_sections (bfd
*output_bfd,
 	  if (! MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_PLTGOT, 0))
 	    return FALSE;
 	}
+
+      if ((elf_tdata (output_bfd)->has_gnu_symbols & elf_gnu_symbol_ifunc)
+	  == elf_gnu_symbol_ifunc)
+	{
+	  if (!MIPS_ELF_ADD_DYNAMIC_ENTRY (info, DT_MIPS_GENERAL_GOTNO, 0))
+	    return FALSE;
+	}
+
       if (htab->is_vxworks
 	  && !elf_vxworks_add_dynamic_entries (output_bfd, info))
 	return FALSE;
@@ -10193,6 +10961,7 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd,
struct bfd_link_info *info,
       Elf_Internal_Shdr *symtab_hdr;
       struct elf_link_hash_entry *h;
       bfd_boolean rel_reloc;
+      bfd_boolean local_gnu_ifunc_p = FALSE;

       rel_reloc = (NEWABI_P (input_bfd)
 		   && mips_elf_rel_relocation_p (input_bfd, input_section,
@@ -10204,8 +10973,25 @@ _bfd_mips_elf_relocate_section (bfd
*output_bfd, struct bfd_link_info *info,
       symtab_hdr = &elf_tdata (input_bfd)->symtab_hdr;
       if (mips_elf_local_relocation_p (input_bfd, rel, local_sections))
 	{
+	  Elf_Internal_Sym *isym;
+	  struct mips_elf_link_hash_table *htab;
+
 	  sec = local_sections[r_symndx];
 	  h = NULL;
+	  htab = mips_elf_hash_table (info);
+	  isym = bfd_sym_from_r_symndx (&htab->sym_cache, input_bfd, r_symndx);
+
+	  if (isym == NULL)
+	    return FALSE;
+
+	  /* Relocation is for local STT_GNU_IFUNC symbol.  */
+	  if (isym->st_info == STT_GNU_IFUNC)
+	    {
+	      /* Ensure that we have a hash entry for this symbol.  */
+	      if (get_local_sym_hash (htab, input_bfd, rel) == NULL)
+		return FALSE;
+	      local_gnu_ifunc_p = TRUE;
+	    }
 	}
       else
 	{
@@ -10264,7 +11050,8 @@ _bfd_mips_elf_relocate_section (bfd *output_bfd,
struct bfd_link_info *info,
 	      if (hi16_reloc_p (r_type)
 		  || (got16_reloc_p (r_type)
 		      && mips_elf_local_relocation_p (input_bfd, rel,
-						      local_sections)))
+						      local_sections)
+		      && !local_gnu_ifunc_p))
 		{
 		  if (!mips_elf_add_lo16_rel_addend (input_bfd, rel, relend,
 						     contents, &addend))
@@ -10661,6 +11448,303 @@ 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,
+		      const bfd_vma igotplt_address)
+{
+  bfd_byte *loc, *dslot;
+  const bfd_vma *iplt_entry;
+  bfd_vma high = mips_elf_high (igotplt_address);
+  bfd_vma low = igotplt_address & 0xffff;
+
+  /* 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_mips_offset;
+
+  /* Fill in the IPLT entry itself.  */
+  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;
+
+      if (highest)
+	{
+	  /* Full 64-bit address space.  */
+	  bfd_put_32 (output_bfd, iplt_entry[0] | highest, loc);
+	  bfd_put_32 (output_bfd, iplt_entry[1] | high, loc + 4);
+	  bfd_put_32 (output_bfd, iplt_entry[2] | higher, loc + 8);
+	  bfd_put_32 (output_bfd, iplt_entry[3], 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);
+	}
+      else if (higher)
+	{
+	  /* 48-bit address space.  */
+	  iplt_entry = mips64_48b_exec_iplt_entry;
+	  bfd_put_32 (output_bfd, iplt_entry[0] | higher, loc);
+	  bfd_put_32 (output_bfd, iplt_entry[1] | high, loc + 4);
+	  bfd_put_32 (output_bfd, iplt_entry[2], loc + 8);
+	  bfd_put_32 (output_bfd, iplt_entry[3] | low, loc + 12);
+	  bfd_put_32 (output_bfd, iplt_entry[4], loc + 16);
+	  bfd_put_32 (output_bfd, MIPS_NOP_INSN, loc + 20);
+	  bfd_put_32 (output_bfd, MIPS_NOP_INSN, loc + 24);
+	}
+      else
+	{
+	  /* 32-bit address space.  */
+	  bfd_put_32 (output_bfd, iplt_entry[0] | high, loc);
+	  bfd_put_32 (output_bfd, iplt_entry[5] | low, loc + 4);
+	  bfd_put_32 (output_bfd, iplt_entry[6], loc + 8);
+	  bfd_put_32 (output_bfd, MIPS_NOP_INSN, loc + 12);
+	  bfd_put_32 (output_bfd, MIPS_NOP_INSN, loc + 16);
+	  bfd_put_32 (output_bfd, MIPS_NOP_INSN, loc + 20);
+	  bfd_put_32 (output_bfd, MIPS_NOP_INSN, loc + 24);
+	}
+      dslot = loc + 28;
+    }
+  else
+    {
+      if (MIPSR6_P (output_bfd))
+	iplt_entry = mips32r6_exec_iplt_entry;
+      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 (MIPSR6_P (output_bfd))
+	{
+	  bfd_put_32 (output_bfd, iplt_entry[2], loc + 8);
+	  dslot = NULL;
+	}
+      else if (LOAD_INTERLOCKS_P (output_bfd))
+	{
+	  bfd_put_32 (output_bfd, iplt_entry[2], loc + 8);
+	  dslot = loc + 12;
+	}
+      else
+	{
+	  bfd_put_32 (output_bfd, iplt_entry[3], loc + 8);
+	  bfd_put_32 (output_bfd, iplt_entry[2], loc + 12);
+	  dslot = loc + 16;
+	}
+    }
+
+  /* Emit a NOP in the delay slot for the last stub.  */
+  if (!MIPSR6_P (output_bfd)
+      && (hmips->iplt_mips_offset + htab->iplt_mips_entry_size
+	  == htab->iplt_mips_offset - 4))
+    bfd_put_32 (output_bfd, MIPS_NOP_INSN, dslot);
+
+  return TRUE;
+}
+
+/* Create the contents of the compressed iplt entry for an IFUNC
symbol.  */
+
+static bfd_boolean
+mips_elf_create_comp_iplt (bfd *output_bfd,
+			   struct mips_elf_link_hash_table *htab,
+			   struct mips_elf_link_hash_entry *hmips,
+			   bfd_vma igotplt_address)
+{
+  bfd_byte *loc;
+  const bfd_vma *iplt_entry;
+  bfd_vma high = mips_elf_high (igotplt_address);
+  bfd_vma low = igotplt_address & 0xffff;
+
+  /* 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_comp_offset;
+
+  if (MIPS16_P (output_bfd))
+    {
+      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 (MICROMIPS_P (output_bfd))
+    {
+      iplt_entry = (htab->insn32
+		    ? micromips_insn32_exec_iplt_entry
+		    : micromips_exec_iplt_entry);
+
+      bfd_put_16 (output_bfd, iplt_entry[0], loc);
+      bfd_put_16 (output_bfd, high, loc + 2);
+      bfd_put_16 (output_bfd, iplt_entry[2], loc + 4);
+      bfd_put_16 (output_bfd, low, loc + 6);
+      bfd_put_16 (output_bfd, iplt_entry[4], loc + 8);
+      bfd_put_16 (output_bfd, iplt_entry[5], loc + 10);
+
+      /* Emit a NOP in the delay slot for the last insn32 stub.  */
+      if (htab->insn32
+	  && (hmips->iplt_comp_offset + htab->iplt_comp_entry_size
+	      == htab->iplt_comp_offset - 4))
+	{
+	  bfd_put_16 (output_bfd, MIPS_NOP_INSN, loc + 12);
+	  bfd_put_16 (output_bfd, MIPS_NOP_INSN, loc + 14);
+	}
+    }
+
+  return TRUE;
+}
+
+/* Find local GOT index for VALUE.  Return -1 if no GOT slot is found.  */
+
+static bfd_vma
+mips_elf_check_local_got_index (bfd *abfd, struct bfd_link_info *info,
+				struct mips_elf_link_hash_entry *h)
+{
+  struct mips_got_entry lookup, *entry;
+  void **loc;
+  struct mips_got_info *g;
+  struct mips_elf_link_hash_table *htab;
+
+  htab = mips_elf_hash_table (info);
+  BFD_ASSERT (htab != NULL);
+
+  g = mips_elf_bfd_got (abfd, FALSE);
+
+  /* Check for existing local GOT entry.  */
+  if (g != NULL)
+    {
+      lookup.abfd = abfd;
+      lookup.symndx = -1;
+      lookup.d.h = h;
+      lookup.tls_type = GOT_TLS_NONE;
+      loc = htab_find_slot (g->got_entries, &lookup, NO_INSERT);
+    }
+  else
+    return -1;
+
+  if (loc && *loc)
+    {
+      entry = (struct mips_got_entry *) *loc;
+      return entry->gotidx;
+    }
+  else
+    return -1;
+}
+
+/* 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_vma igotplt_address = 0;
+  int igot_offset = -1;
+  asection *gotsect, *relsect;
+  bfd_vma value = sym->st_value;
+
+  if (!hmips->needs_igot)
+    {
+      gotsect = htab->root.sgot;
+      /* Check if IFUNC symbol already has an assigned GOT slot.  */
+      igot_offset = mips_elf_check_local_got_index (output_bfd, info,
hmips);
+    }
+  else
+    {
+      bfd_byte *loc;
+      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, value, loc);
+      else
+	bfd_put_32 (output_bfd, value, loc);
+    }
+
+  igotplt_address = (gotsect->output_section->vma + gotsect->output_offset
+		     + igot_offset);
+
+  relsect = mips_get_irel_section (info, htab);
+
+  if (igot_offset >= 0)
+    {
+      if (hmips->needs_iplt_mips
+	  || hmips->needs_iplt_comp
+	  || SYMBOL_REFERENCES_LOCAL (info, &hmips->root))
+	{
+	  if (relsect->contents == NULL)
+	    {
+	      /* Allocate memory for the relocation section contents.  */
+	      relsect->contents = bfd_zalloc (dynobj, relsect->size);
+	      if (relsect->contents == NULL)
+		return FALSE;
+	    }
+
+	  /* Emit an IRELATIVE relocation against the [I]GOT entry.  */
+	  mips_elf_output_dynamic_relocation (output_bfd, relsect,
+					      relsect->reloc_count++, 0,
+					      R_MIPS_IRELATIVE,
+					      igotplt_address);
+	}
+      else
+	{
+	  /* Emit an R_MIPS_REL32 relocation against global GOT entry for
+	     a preemptible symbol.  */
+	  asection *sec = hmips->root.root.u.def.section;
+	  Elf_Internal_Rela rel[3];
+
+	  memset (rel, 0, sizeof (rel));
+	  rel[0].r_info = ELF_R_INFO (output_bfd, 0, R_MIPS_REL32);
+	  rel[0].r_offset = rel[1].r_offset = rel[2].r_offset = igot_offset;
+
+	  mips_elf_create_dynamic_relocation (output_bfd, info, rel, hmips,
+					      sec, value, NULL, gotsect);
+	}
+    }
+  /* Generate a regular .iplt stub, if required.  */
+  if (hmips->needs_iplt_mips
+      && !mips_elf_create_iplt (output_bfd, htab, hmips, igotplt_address))
+    return FALSE;
+
+  /* Generate a compressed .iplt stub, if required.  */
+  if (hmips->needs_iplt_comp
+      && !mips_elf_create_comp_iplt (output_bfd, htab, hmips,
igotplt_address))
+    return FALSE;
+
+  return TRUE;
+}
+
 /* Finish up dynamic symbol handling.  We set the contents of various
    dynamic sections here.  */

@@ -10989,6 +12073,19 @@ _bfd_mips_elf_finish_dynamic_symbol (bfd
*output_bfd,
       sym->st_other = other;
     }

+  if (hmips->root.type == STT_GNU_IFUNC)
+    {
+      if (hmips->needs_ireloc
+	  && !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.  */
@@ -11152,9 +12249,43 @@ _bfd_mips_elf_finish_dynamic_symbol (bfd
*output_bfd,
       sym->st_other -= STO_MICROMIPS;
     }

+  if (hmips->needs_iplt_mips || hmips->needs_iplt_comp)
+    {
+      /* Point at the iplt stub for this ifunc symbol.  */
+      sym->st_value = (htab->root.iplt->output_section->vma
+		       + htab->root.iplt->output_offset);
+      if (ELF_ST_IS_COMPRESSED (hmips->root.other))
+	sym->st_value += hmips->iplt_comp_offset + 1;
+      else
+	sym->st_value += hmips->iplt_mips_offset;
+      sym->st_info = ELF_ST_INFO (STB_GLOBAL, STT_FUNC);
+    }
+
   return TRUE;
 }

+/* htab_traverse callback to finish local dynamic symbol handling.  We
force
+   local IFUNC symbols through bfd_mips_elf-finish_dynamic_symbol 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);
+  isym.st_other = h->root.other;
+  if (ELF_ST_IS_COMPRESSED (isym.st_other))
+    isym.st_value |= 1;
+
+  return _bfd_mips_elf_finish_dynamic_symbol (info->output_bfd, info,
+					      &h->root, &isym);
+}
+
 /* Likewise, for VxWorks.  */

 bfd_boolean
@@ -11546,6 +12677,13 @@ _bfd_mips_elf_finish_dynamic_sections (bfd
*output_bfd,
   sgot = htab->root.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;
@@ -11717,6 +12855,10 @@ _bfd_mips_elf_finish_dynamic_sections (bfd
*output_bfd,
 	      dyn.d_un.d_ptr = s->vma;
 	      break;

+	    case DT_MIPS_GENERAL_GOTNO:
+	      dyn.d_un.d_val = htab->reserved_gotno + gg->general_gotno;
+	      break;
+
 	    case DT_PLTREL:
 	      BFD_ASSERT (htab->use_plts_and_copy_relocs);
 	      if (htab->is_vxworks)
@@ -12224,6 +13366,17 @@ _bfd_mips_elf_final_write_processing (bfd *abfd,
 	  (*hdrpp)->sh_link = elf_section_data (sec)->this_idx;
 	  break;

+	case SHT_REL:
+	  if ((elf_tdata (abfd)->has_gnu_symbols & elf_gnu_symbol_ifunc)
+	      == elf_gnu_symbol_ifunc)
+	    {
+	      BFD_ASSERT ((*hdrpp)->bfd_section != NULL);
+	      name = bfd_get_section_name (abfd, (*hdrpp)->bfd_section);
+	      BFD_ASSERT (name != NULL);
+	      if (CONST_STRNEQ (name, ".rel.dyn") && (*hdrpp)->sh_link == 0)
+		(*hdrpp)->sh_link = elf_onesymtab (abfd);
+	    }
+	  break;
 	}
     }
 }
@@ -14040,6 +15193,49 @@ _bfd_mips_elf_relax_section (bfd *abfd,
asection *sec,
   return FALSE;
 }
 \f
+/* 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 *
@@ -14063,6 +15259,16 @@ _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;
 }

@@ -15718,6 +16924,8 @@ _bfd_mips_elf_get_target_dtag (bfd_vma dtag)
       return "DT_MIPS_PLTGOT";
     case DT_MIPS_RWPLT:
       return "DT_MIPS_RWPLT";
+    case DT_MIPS_GENERAL_GOTNO:
+      return "DT_MIPS_GENERAL_GOTNO";
     }
 }

@@ -16362,6 +17570,11 @@ _bfd_mips_post_process_headers (bfd *abfd,
struct bfd_link_info *link_info)
   if (mips_elf_tdata (abfd)->abiflags.fp_abi == Val_GNU_MIPS_ABI_FP_64
       || mips_elf_tdata (abfd)->abiflags.fp_abi == Val_GNU_MIPS_ABI_FP_64A)
     i_ehdrp->e_ident[EI_ABIVERSION] = 3;
+
+  if ((elf_tdata (abfd)->has_gnu_symbols & elf_gnu_symbol_ifunc)
+      == elf_gnu_symbol_ifunc)
+    i_ehdrp->e_ident[EI_ABIVERSION] = 4;
+
 }

 int
diff --git a/bfd/reloc.c b/bfd/reloc.c
index d50155e..36569c7 100644
--- a/bfd/reloc.c
+++ b/bfd/reloc.c
@@ -2450,6 +2450,11 @@ ENUMX
   BFD_RELOC_MIPS_EH
 ENUMDOC
   MIPS ELF relocations.
+
+ENUM
+  BFD_RELOC_MIPS_IRELATIVE
+ENUMDOC
+  MIPS support for STT_GNU_IFUNC.
 COMMENT

 ENUM
diff --git a/binutils/readelf.c b/binutils/readelf.c
index b488714..e9a9279 100644
--- a/binutils/readelf.c
+++ b/binutils/readelf.c
@@ -1746,6 +1746,7 @@ 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_GENERAL_GOTNO: return "MIPS_GENERAL_GOTNO";
     default:
       return NULL;
     }
@@ -8974,6 +8975,7 @@ dynamic_section_mips_val (Elf_Internal_Dyn * entry)
     case DT_MIPS_DELTA_SYM_NO:
     case DT_MIPS_DELTA_CLASSSYM_NO:
     case DT_MIPS_COMPACT_SIZE:
+    case DT_MIPS_GENERAL_GOTNO:
       print_vma (entry->d_un.d_val, DEC);
       break;

@@ -14663,6 +14665,7 @@ process_mips_specific (FILE * file)
   bfd_vma mips_pltgot = 0;
   bfd_vma jmprel = 0;
   bfd_vma local_gotno = 0;
+  bfd_vma general_gotno = 0;
   bfd_vma gotsym = 0;
   bfd_vma symtabno = 0;

@@ -14756,6 +14759,9 @@ process_mips_specific (FILE * file)
       case DT_MIPS_LOCAL_GOTNO:
 	local_gotno = entry->d_un.d_val;
 	break;
+      case DT_MIPS_GENERAL_GOTNO:
+	general_gotno = entry->d_un.d_val;
+	break;
       case DT_MIPS_GOTSYM:
 	gotsym = entry->d_un.d_val;
 	break;
@@ -15155,7 +15161,7 @@ process_mips_specific (FILE * file)

   if (pltgot != 0 && local_gotno != 0)
     {
-      bfd_vma ent, local_end, global_end;
+      bfd_vma ent, local_end, global_end, general_end;
       size_t i, offset;
       unsigned char * data;
       unsigned char * data_end;
@@ -15164,6 +15170,7 @@ process_mips_specific (FILE * file)
       ent = pltgot;
       addr_size = (is_32bit_elf ? 4 : 8);
       local_end = pltgot + local_gotno * addr_size;
+      general_end = pltgot + general_gotno * addr_size;

       /* PR binutils/17533 file: 012-111227-0.004  */
       if (symtabno < gotsym)
@@ -15213,6 +15220,22 @@ process_mips_specific (FILE * file)
 	}
       printf ("\n");

+      if (ent < general_end)
+	{
+	  printf (_(" General entries:\n"));
+	  printf ("  %*s %10s %*s\n",
+		  addr_size * 2, _("Address"), _("Access"),
+		  addr_size * 2, _("Initial"));
+	  while (ent < general_end)
+	    {
+	      ent = print_mips_got_entry (data, pltgot, ent, data_end);
+	      printf ("\n");
+	      if (ent == (bfd_vma) -1)
+		goto got_print_fail;
+	    }
+	  printf ("\n");
+	}
+
       if (ent < local_end)
 	{
 	  printf (_(" Local entries:\n"));
diff --git a/elfcpp/elfcpp.h b/elfcpp/elfcpp.h
index cce40d4..afde2c0 100644
--- a/elfcpp/elfcpp.h
+++ b/elfcpp/elfcpp.h
@@ -871,6 +871,8 @@ enum DT
   DT_MIPS_RWPLT = 0x70000034,
   // Relative offset of run time loader map, used for debugging.
   DT_MIPS_RLD_MAP_REL = 0x70000035,
+  // The GOT index of the first implicitly relocated GOT entry.
+  DT_MIPS_GENERAL_GOTNO = 0x70000036,

   DT_AUXILIARY = 0x7ffffffd,
   DT_USED = 0x7ffffffe,
diff --git a/include/elf/mips.h b/include/elf/mips.h
index 3e27b05..aa2cdde 100644
--- a/include/elf/mips.h
+++ b/include/elf/mips.h
@@ -120,6 +120,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)
@@ -756,6 +759,10 @@ extern void bfd_mips_elf32_swap_reginfo_out

 /* Relative offset of run time loader map, used for debugging.  */
 #define DT_MIPS_RLD_MAP_REL    0x70000035
+
+/* The GOT index of the first implicitly relocated GOT entry.  */
+#define DT_MIPS_GENERAL_GOTNO  0x70000036
+
 \f
 /* Flags which may appear in a DT_MIPS_FLAGS entry.  */

-- 
1.7.9.5

[-- Attachment #2: Spec.txt --]
[-- Type: text/plain, Size: 9115 bytes --]

====
MIPS IFUNC ABI specification
====

Introduction
----

This document describes the working of GNU indirect functions(IFUNC)
for MIPS.

Terminology
----
Shared file or shared object refers to any object file with e_type
ET_DYN. Unless otherwise distinguished, this includes dynamic shared libraries
(PIC) and position-independent executables (PIE).

Executable file or executable refers to any object file with e_type
ET_EXEC. Unless otherwise distinguished, this includes statically linked and
dynamically linked executables.

Relocations for IFUNC resolution
----

The relocation table may now contain a new relocation type generated by the
static linker:

R_MIPS_IRELATIVE(128)
This relocation marks the location of an IFUNC indirection that needs to be
resolved by the dynamic linker at load time. The relocation step calls the
IFUNC resolver function given at the relocation offset possibly with
additional processor-specific (hardware capability) arguments and writes the
address returned by the resolver back to the relocation offset. For
shared objects, the relocation step may add a base address to the
relocation offset to calculate the run-time address of the resolver
function.

The operation can be described as:
[B + O] <= ((ifunc_proto) B + O) (<hwcaps>)
where O = relocation offset
      B = relative relocation bias; =0 for executable files

IFUNCs in executable files
----
Code/data references to IFUNCs in executable files are resolved by
means of indirection stubs and an indirection table, except where
all references are GOT-based calls.

IGOT sections - the indirection table
----
If symbol has no GOT entry ,indirection is acheived by creating an entry in
the new IGOT section. Each entry in IGOT has a corresponding IRELATIVE
relocation. This IGOT entry is initialized to point to the resolver function
at link time and modified to point to the resolved function by the IRELATIVE
fix-up. Executable files are not relocatable, so the relocation bias is always
zero.

IPLT section - indirection stubs
----

The IFUNC Procedure Linkage Table (IPLT) consists of a set of stubs generated
by the static linker to stand in for IFUNC references in executable file that
can not be resolved via a GOT entry.

If an IPLT stub is required, the IFUNC call is performed by an absolute JAL
instruction to the IPLT stub. The stub redirects the call to the actual
function by reading a pointer from the IGOT section. Data references use the
absolute address of the IPLT stub in place of the IFUNC. IPLT Stubs are
required for all IFUNC symbols which are not bound locally.

When an IFUNC symbol binds locally there are 6 cases:

    PIC relocs?  non-PIC relocs?  all call-only?   variant
    n            y                y                (1)
    y            n                y                (2)
    y            y                y                (3)
    n            y                n                (4)
    y            n                n                (5)
    y            y                n                (5)

    (1) Need an IPLT.  Nothing other than the IPLT needs the associated
        IRELATIVE GOT entry, so the entry can go in .igot.

    (2) No need for an IPLT, we can just have a .got entry with an IRELATIVE
        relocation.

    (3) Need an IPLT.  The associated IRELATIVE GOT entry should go in .got
        so that it can be used by the PIC references.

    (4) Same as (1), using the IPLT as the canonical function address.

    (5) Need an IPLT, which acts as the canonical function address.
        Nothing other than the IPLT needs the associated IRELATIVE GOT entry,
        so that entry can go in .igot.  However, we need a separate
        .got entry to satisfy the PIC references.  This can be a normal
        local GOT entry (i.e. not use the new area).

    If the symbol is exported then for (1)-(3) the symbol definition
    is the IFUNC.  For (4) and (5) it's the IPLT.


IFUNCs in shared objects
----

In the case of shared objects, data references to IFUNCs use explicit
dynamic relocations, where as calls or code-references are routed
through the GOT which itself is relocated implicitly by the dynamic
linker. Morever, global symbols can be pre-empted by the dynamic
linker at load time. There are 4 scenarios to consider:

Data relocations for local IFUNC symbols
----

The static linker replaces each R_MIPS_REL32 dynamic relocation for a
local IFUNC reference with an IRELATIVE relocation. The IRELATIVE
resolution step above includes an implicit relative relocation. Shared
objects have a non-zero bias element provided by the dynamic linker.

Code relocations for local IFUNC symbols
----

Non-preemptible IFUNC symbols have entries in the local GOT region.
Call or reference in code is resolved by reading from the
corresponding GOT entry. The local GOT can be considered as a series
of implicit REL32 relocations. For IFUNC resolution, we must
replace each implicit REL32 relocation with an explicit IRELATIVE
relocation, just as we did above for explicit REL32 relocations in the
case of data relocations.

Thus, each IFUNC entry in the local IGOT gets a corresponding
IRELATIVE relocation. To prevent these entries from being relocated
twice (once implicitly as part of GOT relocation and again
by the IRELATIVE relocation), a part of the GOT is marked as
containing only explictly relocated entries. This part is skipped over
during normal GOT relocation and only modified by explicit
relocations. This part is marked off from the implicitly relocated
local GOT region by the dynamic tag MIPS_GENERAL_GOTNO.

Data relocations for global IFUNC symbols
----

For a global preemptible symbol, the dynamic linker must determine at
load-time, whether the IFUNC is preempted by an external
symbol. R_MIPS_REL32 relocations against such symbols cannot simply be
replaced with IRELATIVE ones.  The REL32 relocation step for such
references must be modified to include an implicit IRELATIVE
relocation, to be performed only if the resolved symbol is also an
IFUNC. The relocations produced remain exactly the same in this case,
but the relocation process employed by the dynamic linker changes to
account for symbol preemption.

Code relocations for global IFUNC symbols
----

Preemptible global IFUNC symbols have entries in the global GOT
region. A call or reference in code is resolved by reading from the
corresponding GOT entry. The global GOT can be considered as a
series of implicit REL32 relocations.

The static linker generates an explicit R_MIPS_REL32 relocation
against the GOT entry of each global IFUNC as a means of delaying the
IFUNC resolution step. These R_MIPS_REL32 relocations for code behave
similar to explicit data relocations described above, encapsulating
both symbol preemption and an optional IRELATIVE relocation. Since
these GOT entries are explicitly relocated, they too are allocated with 
the general GOT region as described above.

Dynamic Section
----

Dynamic section entries give information to the dynamic linker.  The following
new dynamic table entries are required for IFUNC resolution:

MIPS_GENERAL_GOTNO(36)
        Number of explicitly relocated GOT entries. Alternatively, start of
        the normally relocated local GOT region. This entry is optional. If not
	present, its value is assumed by the linker to be 1(non-GNU) or 2(GNU).

Revised GOT layout
----

GOT0: Reserved for dynamic linker
GOT1: Reserved, GNU-specific, optional

GOT2 ...
MIPS_GENERAL_GOTNO - 1: Explicitly relocated GOT entries (local & global)

MIPS_GENERAL_GOTNO ...
MIPS_LOCAL_GOTNO - 1: Implicitly relocated local GOT entries

MIPS_LOCAL_GOTNO ...
MIPS_LOCAL_GOTNO + MIPS_SYMTABNO - MIPS_GOTSYM: Implicitly relocated Global GOT entries

IFUNC address in assembly code
----

In shared objects, IFUNC references must always be made through GOT, for both
local and global symbols. Each IFUNC symbol will always have a corresponding
GOT entry within a reachable offset of $gp.

For 32-bit, address of IFUNC symbol foo can be obtained as:
   lw      <reg>,%got16(foo)($28)

For 64-bit, address of IFUNC symbol foo can be obtained as:
   ld      <reg>,%got_disp(foo)($28)

It is not necessary to do a 2-step calculation by adding the %lo16
bits of the symbol for local IFUNC symbols. Doing so will result in
a spurious address.

Non-PIC calls to PIC IFUNC
----
A call from non-PIC code to PIC function needs to be routed through a stub
that initializes $25 for the PIC function. Since all IPLT stubs initialize
$25, an extra PIC stub is not needed and will not be generated for such calls
where the IPLT stub has already been generated.

Compressed or non-compressed IPLT stubs
----

Depending on whether an IFUNC resolver resolves to a compressed ISA or regular
MIPS ISA, a mode switch may be needed to invoke the function. The static
linker will tries to minimize mode switches by binding each IFUNC reference to
a stub matching the calling/referring code. An IFUNC that is referred to from
both compressed and regular MIPS code will have both types of stubs. The
compressed stubs are sorted to appear before all the regular MIPS stubs.

^ permalink raw reply	[flat|nested] 35+ messages in thread

end of thread, other threads:[~2017-01-24 20:41 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-06-18 23:28 [RFC] Add IFUNC support for MIPS Faraz Shahbazker
2015-06-19 15:46 ` Matthew Fortune
2015-06-19 16:26   ` Faraz Shahbazker
2015-06-22 20:40 ` Richard Sandiford
2015-06-23  6:10   ` Alan Modra
2015-06-23 17:54   ` Faraz Shahbazker
2015-06-23 18:20     ` Richard Sandiford
2015-06-23 19:13       ` Faraz Shahbazker
2015-07-21 12:08         ` Faraz Shahbazker
2015-07-23 21:44           ` Richard Sandiford
2015-07-31 12:57             ` Faraz Shahbazker
2015-09-21 18:50             ` [RFC] Add IFUNC support for MIPS (v3) Faraz Shahbazker
2015-09-27 11:23               ` Richard Sandiford
2015-10-13 15:50                 ` Faraz Shahbazker
2015-11-02 20:40                   ` Faraz Shahbazker
2015-11-08 12:47                   ` Richard Sandiford
2015-12-21 22:52                     ` Faraz Shahbazker
2015-12-22 22:29                       ` Richard Sandiford
2016-01-08  0:10                         ` [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v4) Faraz Shahbazker
2016-01-21 20:02                           ` Faraz Shahbazker
2016-03-06  7:14                           ` Maciej W. Rozycki
2016-03-16 21:20                             ` Faraz Shahbazker
2016-04-29 14:19                               ` Maciej W. Rozycki
2016-05-21  3:18                                 ` Faraz Shahbazker
2016-12-05 14:44                                   ` Maciej W. Rozycki
2017-01-09 21:10                                     ` Faraz Shahbazker
2017-01-24 20:41                                     ` [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v5) Faraz Shahbazker
2017-01-24 20:41                                     ` [PATCH 2/2] " Faraz Shahbazker
2016-04-29 13:58                             ` [PATCH 1/2] [RFC] Add IFUNC support for MIPS (v4) Maciej W. Rozycki
2016-05-21  2:01                               ` Faraz Shahbazker
2016-05-27 22:17                                 ` Faraz Shahbazker
2016-01-08  0:11                         ` [PATCH 2/2] " Faraz Shahbazker
2016-03-06  7:18                           ` Maciej W. Rozycki
2015-08-02 13:08         ` [RFC] Add IFUNC support for MIPS Richard Sandiford
2015-08-03 15:29           ` Faraz Shahbazker

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).