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


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

[PATCH 1/2] [RFC] Add IFUNC support for MIPS (v5)


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 */
+
 
 /* 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 */
+
 
 /* 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					*/
+};
+
 
 /* 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,
     }
 }
 
+/* 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;
+}
+
 /* 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;
 }
 
@@ -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;
 }
 
+/* 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
+
 
 /* Flags which may appear in a DT_MIPS_FLAGS entry.  */

-- 
1.7.9.5

Attachment: Spec.txt
Description: Text document


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