[PATCH 07/11] alpha: support IFUNC symbols in non-PIC links

Matt Turner <[email protected]>
Newsgroups gmane.comp.gnu.binutils
Message-ID <[email protected]>
A non-PIC link reserves no dynamic relocation for a reference it
resolves itself, so an IFUNC reference has nothing to turn into an
R_ALPHA_IRELATIVE the way a PIC link does. Put those in a new .rela.iplt
instead, which the linker script places with the other dynamic
relocations: ld.so applies it along with the rest of .rela.dyn, and in a
static executable libc applies the range between __rela_iplt_start and
__rela_iplt_end.

Unlike .rela.dyn, .rela.iplt has to be sized exactly: libc applies every
entry of it, so a slot reserved and then not filled in would be an
R_ALPHA_NONE that its startup code chokes on. Only the input sections
that make it into the output are relocated, and check_relocs runs before
a linker script has had the chance to discard any of them, so the sizing
runs in late_size_sections instead, over the sections that survived. It
marks the got entry each surviving reference reaches, since however many
references reach one it holds one address and gets one relocation, and
counts the data references, which get one each. A reference whose symbol
is itself defined in a discarded section reserves nothing either: the
generic code reports it rather than letting relocate_section reach it.

The got entries are counted again on each relaxation trip, since
re-merging the GOTs can still combine two entries of a global IFUNC into
one. The marks are made once: which sections survive does not change
after this point, and merging two entries carries the mark over. Only
the entry that survives a merge is asked for its mark, so it has to take
the one the entry it absorbs made, which is not always its own: the
reference that created the surviving entry may be the one a linker
script discarded.

A non-dynamic IFUNC gets no PLT entry, like any other function: a call
goes through the GOT entry, and the IRELATIVE fills that in. A defined
IFUNC then reaches finish_dynamic_symbol even when there are no dynamic
sections (elf_link_output_extsym), where there is nothing left to do.

A local IFUNC has to work: glibc's configure probe for linker IFUNC
support is a data reference to one.

Removing alpha from the skip list in ld-ifunc/ifunc.exp is left for
later in the series, once the cases that cannot work are diagnosed.
---
 bfd/elf64-alpha.c                             | 346 +++++++++++++++++-
 ld/testsuite/ld-alpha/alpha.exp               |   6 +
 ld/testsuite/ld-alpha/ifunc-comdat-a.s        |  11 +
 ld/testsuite/ld-alpha/ifunc-comdat-b.s        |  14 +
 ld/testsuite/ld-alpha/ifunc-comdat.d          |   4 +
 ld/testsuite/ld-alpha/ifunc-discard.d         |  11 +
 ld/testsuite/ld-alpha/ifunc-discard.ld        |   4 +
 ld/testsuite/ld-alpha/ifunc-discard.s         |  33 ++
 ld/testsuite/ld-alpha/ifunc-extern.d          |  14 +
 ld/testsuite/ld-alpha/ifunc-extern.s          |  16 +
 ld/testsuite/ld-alpha/ifunc-gc-local.d        |   8 +
 ld/testsuite/ld-alpha/ifunc-gc-local.s        |  32 ++
 ld/testsuite/ld-alpha/ifunc-gc.d              |   8 +
 ld/testsuite/ld-alpha/ifunc-gc.s              |  32 ++
 ld/testsuite/ld-alpha/ifunc-global-dynamic.d  |   9 +
 ld/testsuite/ld-alpha/ifunc-global-export.d   |  12 +
 .../ld-alpha/ifunc-global-nocombreloc.d       |   9 +
 ld/testsuite/ld-alpha/ifunc-global-static.d   |  17 +
 .../ld-alpha/ifunc-local-call-static.d        |   8 +
 ld/testsuite/ld-alpha/ifunc-local-dynamic.d   |   9 +
 ld/testsuite/ld-alpha/ifunc-local-static.d    |  17 +
 ld/testsuite/ld-alpha/ifunc-multi-a.s         |  24 ++
 ld/testsuite/ld-alpha/ifunc-multi-b.s         |  16 +
 ld/testsuite/ld-alpha/ifunc-multi.d           |  13 +
 ld/testsuite/ld-alpha/ifunc-none.d            |   7 +
 ld/testsuite/ld-alpha/ifunc-none.s            |  12 +
 ld/testsuite/ld-alpha/ifunc-relax-a.s         |  32 ++
 ld/testsuite/ld-alpha/ifunc-relax-b.s         |  47 +++
 ld/testsuite/ld-alpha/ifunc-relax-discard.d   |  14 +
 ld/testsuite/ld-alpha/ifunc-relax-discard.s   |  41 +++
 ld/testsuite/ld-alpha/ifunc-relax.d           |  14 +
 ld/testsuite/ld-alpha/ifunc-shlib.s           |   9 +
 ld/testsuite/ld-alpha/ifunc-weak-dynamic.d    |   9 +
 ld/testsuite/ld-alpha/ifunc-weak-static.d     |   9 +
 .../ld-alpha/ifunc-weak-undef-dynamic.d       |   5 +
 .../ld-alpha/ifunc-weak-undef-static.d        |   5 +
 ld/testsuite/ld-alpha/ifunc-weak-undef.s      |  22 ++
 ld/testsuite/ld-alpha/ifunc-weak.s            |  24 ++
 38 files changed, 911 insertions(+), 12 deletions(-)
 create mode 100644 ld/testsuite/ld-alpha/ifunc-comdat-a.s
 create mode 100644 ld/testsuite/ld-alpha/ifunc-comdat-b.s
 create mode 100644 ld/testsuite/ld-alpha/ifunc-comdat.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-discard.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-discard.ld
 create mode 100644 ld/testsuite/ld-alpha/ifunc-discard.s
 create mode 100644 ld/testsuite/ld-alpha/ifunc-extern.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-extern.s
 create mode 100644 ld/testsuite/ld-alpha/ifunc-gc-local.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-gc-local.s
 create mode 100644 ld/testsuite/ld-alpha/ifunc-gc.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-gc.s
 create mode 100644 ld/testsuite/ld-alpha/ifunc-global-dynamic.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-global-export.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-global-nocombreloc.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-global-static.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-local-call-static.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-local-dynamic.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-local-static.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-multi-a.s
 create mode 100644 ld/testsuite/ld-alpha/ifunc-multi-b.s
 create mode 100644 ld/testsuite/ld-alpha/ifunc-multi.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-none.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-none.s
 create mode 100644 ld/testsuite/ld-alpha/ifunc-relax-a.s
 create mode 100644 ld/testsuite/ld-alpha/ifunc-relax-b.s
 create mode 100644 ld/testsuite/ld-alpha/ifunc-relax-discard.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-relax-discard.s
 create mode 100644 ld/testsuite/ld-alpha/ifunc-relax.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-shlib.s
 create mode 100644 ld/testsuite/ld-alpha/ifunc-weak-dynamic.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-weak-static.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-weak-undef-dynamic.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-weak-undef-static.d
 create mode 100644 ld/testsuite/ld-alpha/ifunc-weak-undef.s
 create mode 100644 ld/testsuite/ld-alpha/ifunc-weak.s

diff --git ./bfd/elf64-alpha.c ./bfd/elf64-alpha.c
index ebb2f0ded11..470a6818023 100644
--- ./bfd/elf64-alpha.c
+++ ./bfd/elf64-alpha.c
@@ -135,6 +135,11 @@ struct alpha_elf_got_entry
 
   /* Have we adjusted this entry for SEC_MERGE?  */
   unsigned char reloc_xlated;
+
+  /* Does a section that survives into the output refer to this entry?
+     Only such an entry is ever filled in, so only such an entry gets an
+     R_ALPHA_IRELATIVE reserved for it.  */
+  unsigned char irel_live;
 };
 
 struct alpha_elf_reloc_entry
@@ -196,6 +201,12 @@ struct alpha_elf_link_hash_table
   /* The most recent relax pass that we've seen.  The GOTs
      should be regenerated if this doesn't match.  */
   int relax_trip;
+
+  /* The part of the size of .rela.iplt that is due to data references to
+     an IFUNC, counted once the sections that survive into the output are
+     known.  The rest comes from got entries and is recomputed whenever the
+     GOTs are.  */
+  bfd_size_type irelplt_data_size;
 };
 
 /* Look up an entry in a Alpha ELF linker hash table.  */
@@ -1808,6 +1819,260 @@ elf64_alpha_ifunc_reloc_p (unsigned long r_type)
   return r_type == R_ALPHA_LITERAL || r_type == R_ALPHA_REFQUAD;
 }
 
+/* True if a reference to the IFUNC described by H (global) or SYM (local)
+   needs its R_ALPHA_IRELATIVE in .rela.iplt: the link is not PIC, so
+   nothing has reserved a slot for it alongside the R_ALPHA_RELATIVE it
+   would otherwise have had, and the symbol is not preemptible.  */
+
+static bool
+elf64_alpha_ifunc_irelplt_p (struct alpha_elf_link_hash_entry *h,
+			     Elf_Internal_Sym *sym,
+			     struct bfd_link_info *info)
+{
+  if (!elf64_alpha_ifunc_p (h, sym) || bfd_link_pic (info))
+    return false;
+
+  return h == NULL || !alpha_elf_dynamic_symbol_p (&h->root, info);
+}
+
+/* Create .rela.iplt.  Called from check_relocs, since the section has to
+   exist before input sections are mapped to output sections.  One that
+   stays empty is stripped by elf64_alpha_late_size_sections.  */
+
+static bool
+elf64_alpha_create_irelplt (struct bfd_link_info *info)
+{
+  struct elf_link_hash_table *htab = elf_hash_table (info);
+  const struct elf_backend_data *bed;
+  asection *s;
+
+  if (htab->irelplt != NULL)
+    return true;
+
+  bed = get_elf_backend_data (info->output_bfd);
+  s = bfd_make_section_anyway_with_flags (htab->dynobj, ".rela.iplt",
+					  (bed->dynamic_sec_flags
+					   | SEC_READONLY));
+  if (s == NULL || !bfd_set_section_alignment (s, 3))
+    return false;
+
+  htab->irelplt = s;
+  return true;
+}
+
+/* Note that an R_ALPHA_IRELATIVE will be applied to SEC.  A read-only one
+   needs the page made writable at startup, which only the dynamic linker
+   can do.  Only a data relocation can land in such a section; the GOT is
+   always writable.  */
+
+static void
+elf64_alpha_note_irelative (struct bfd_link_info *info, asection *sec)
+{
+  if ((sec->flags & SEC_READONLY) != 0)
+    {
+      info->flags |= DF_TEXTREL;
+      info->callbacks->minfo
+	(_("%pB: R_ALPHA_IRELATIVE in read-only section `%pA'\n"),
+	 sec->owner, sec);
+    }
+}
+
+/* Note the references to an IFUNC that need an R_ALPHA_IRELATIVE in
+   .rela.iplt: mark the got entries that get one and count the data
+   relocations that do.  Only the input sections that make it into the
+   output are relocated, and only their references are emitted, so this runs
+   once the output sections are known rather than in check_relocs, which
+   still sees the sections a linker script goes on to discard.  */
+
+static bool
+elf64_alpha_scan_irelative (struct bfd_link_info *info)
+{
+  struct alpha_elf_link_hash_table *htab = alpha_elf_hash_table (info);
+  bfd *abfd;
+
+  htab->irelplt_data_size = 0;
+
+  for (abfd = info->input_bfds; abfd != NULL; abfd = abfd->link.next)
+    {
+      Elf_Internal_Shdr *symtab_hdr;
+      asection *sec;
+
+      if (!is_alpha_elf (abfd) || (abfd->flags & DYNAMIC) != 0)
+	continue;
+
+      symtab_hdr = &elf_symtab_hdr (abfd);
+
+      for (sec = abfd->sections; sec != NULL; sec = sec->next)
+	{
+	  Elf_Internal_Rela *relocs, *rel, *relend;
+	  bool noted = false;
+
+	  /* The sections elf_link_input_bfd will relocate.  One a linker
+	     script discarded and one discarded as a duplicate both have the
+	     absolute section for their output.  */
+	  if ((sec->flags & (SEC_ALLOC | SEC_RELOC | SEC_HAS_CONTENTS))
+	      != (SEC_ALLOC | SEC_RELOC | SEC_HAS_CONTENTS)
+	      || sec->reloc_count == 0
+	      || sec->output_section == NULL
+	      || bfd_is_abs_section (sec->output_section))
+	    continue;
+
+	  relocs = _bfd_elf_link_read_relocs (abfd, sec, NULL, NULL,
+					      info->keep_memory);
+	  if (relocs == NULL)
+	    return false;
+
+	  relend = relocs + sec->reloc_count;
+	  for (rel = relocs; rel < relend; rel++)
+	    {
+	      unsigned long r_type = ELF64_R_TYPE (rel->r_info);
+	      unsigned long r_symndx = ELF64_R_SYM (rel->r_info);
+	      struct alpha_elf_link_hash_entry *h = NULL;
+	      Elf_Internal_Sym *isym = NULL;
+	      struct alpha_elf_got_entry *gotent;
+
+	      if (!elf64_alpha_ifunc_reloc_p (r_type))
+		continue;
+
+	      if (r_symndx < symtab_hdr->sh_info)
+		{
+		  isym = bfd_sym_from_r_symndx
+		    (&elf_hash_table (info)->sym_cache, abfd, r_symndx);
+		  if (isym == NULL)
+		    return false;
+		}
+	      else
+		{
+		  h = (alpha_elf_sym_hashes (abfd)
+		       [r_symndx - symtab_hdr->sh_info]);
+		  while (h->root.root.type == bfd_link_hash_indirect
+			 || h->root.root.type == bfd_link_hash_warning)
+		    h = ((struct alpha_elf_link_hash_entry *)
+			 h->root.root.u.i.link);
+		}
+
+	      /* A symbol defined in a section the linker discarded reaches
+		 relocate_section as an error rather than as a relocation to
+		 emit, so nothing may be reserved for it.  */
+	      if (h != NULL)
+		{
+		  if ((h->root.root.type == bfd_link_hash_defined
+		       || h->root.root.type == bfd_link_hash_defweak)
+		      && discarded_section (h->root.root.u.def.section))
+		    continue;
+		}
+	      else if (isym->st_shndx < SHN_LORESERVE
+		       || isym->st_shndx > SHN_HIRESERVE)
+		{
+		  /* An index above the reserved range is one that
+		     bfd_sym_from_r_symndx expanded from an SHN_XINDEX
+		     entry; elf_link_input_bfd looks that one up too.  */
+		  asection *isec;
+
+		  isec = bfd_section_from_elf_index (abfd, isym->st_shndx);
+		  if (isec != NULL && discarded_section (isec))
+		    continue;
+		}
+
+	      if (!elf64_alpha_ifunc_irelplt_p (h, isym, info))
+		continue;
+
+	      if (r_type == R_ALPHA_REFQUAD)
+		{
+		  htab->irelplt_data_size += sizeof (Elf64_External_Rela);
+		  if (!noted)
+		    {
+		      elf64_alpha_note_irelative (info, sec);
+		      noted = true;
+		    }
+		  continue;
+		}
+
+	      /* However many references reach a got entry, it holds one
+		 address and gets one R_ALPHA_IRELATIVE.  Find it the way
+		 relocate_section will.  */
+	      if (h != NULL)
+		gotent = h->got_entries;
+	      else if (alpha_elf_tdata (abfd)->local_got_entries != NULL)
+		gotent = alpha_elf_tdata (abfd)->local_got_entries[r_symndx];
+	      else
+		gotent = NULL;
+
+	      for (; gotent != NULL; gotent = gotent->next)
+		if (gotent->gotobj == alpha_elf_tdata (abfd)->gotobj
+		    && gotent->reloc_type == r_type
+		    && gotent->addend == rel->r_addend)
+		  {
+		    gotent->irel_live = 1;
+		    break;
+		  }
+	    }
+
+	  if (elf_section_data (sec)->relocs != relocs)
+	    free (relocs);
+	}
+    }
+
+  return true;
+}
+
+/* Reserve space in .rela.iplt for the got entries of a global IFUNC that
+   elf64_alpha_scan_irelative marked.  */
+
+static bool
+elf64_alpha_size_irelative (struct alpha_elf_link_hash_entry *h,
+			    struct bfd_link_info *info)
+{
+  asection *irelplt = elf_hash_table (info)->irelplt;
+  struct alpha_elf_got_entry *gotent;
+
+  for (gotent = h->got_entries; gotent != NULL; gotent = gotent->next)
+    if (gotent->irel_live && gotent->use_count > 0)
+      irelplt->size += sizeof (Elf64_External_Rela);
+
+  return true;
+}
+
+/* Size .rela.iplt.  The data references are counted once, since nothing
+   later changes them; the got entries of a global IFUNC can still merge
+   when the GOTs are re-merged during relaxation, so the part of the size
+   that comes from them is recomputed alongside them.  */
+
+static void
+elf64_alpha_size_irelplt_section (struct bfd_link_info *info)
+{
+  struct alpha_elf_link_hash_table *htab = alpha_elf_hash_table (info);
+  asection *irelplt = elf_hash_table (info)->irelplt;
+  bfd *i;
+
+  if (irelplt == NULL)
+    return;
+
+  irelplt->size = htab->irelplt_data_size;
+  alpha_elf_link_hash_traverse (htab, elf64_alpha_size_irelative, info);
+
+  /* A local symbol has no hash entry for the traversal to reach.  */
+  for (i = htab->got_list; i != NULL; i = alpha_elf_tdata (i)->got_link_next)
+    {
+      bfd *j;
+
+      for (j = i; j != NULL; j = alpha_elf_tdata (j)->in_got_link_next)
+	{
+	  struct alpha_elf_got_entry **local_got_entries, *gotent;
+	  int k, n;
+
+	  local_got_entries = alpha_elf_tdata (j)->local_got_entries;
+	  if (local_got_entries == NULL)
+	    continue;
+
+	  for (k = 0, n = elf_symtab_hdr (j).sh_info; k < n; ++k)
+	    for (gotent = local_got_entries[k]; gotent; gotent = gotent->next)
+	      if (gotent->irel_live && gotent->use_count > 0)
+		irelplt->size += sizeof (Elf64_External_Rela);
+	}
+    }
+}
+
 /* Handle dynamic relocations when doing an Alpha ELF link.  */
 
 static bool
@@ -1844,14 +2109,29 @@ elf64_alpha_check_relocs (bfd *abfd, struct bfd_link_info *info,
 
       unsigned long r_symndx, r_type;
       struct alpha_elf_link_hash_entry *h;
+      Elf_Internal_Sym *isym;
       unsigned int gotent_flags;
       bool maybe_dynamic;
       unsigned int need;
       bfd_vma addend;
 
       r_symndx = ELF64_R_SYM (rel->r_info);
+      r_type = ELF64_R_TYPE (rel->r_info);
+      isym = NULL;
       if (r_symndx < symtab_hdr->sh_info)
-	h = NULL;
+	{
+	  h = NULL;
+
+	  /* A local IFUNC needs the same treatment as a global one, but
+	     only the symbol table says that it is one.  */
+	  if (elf64_alpha_ifunc_reloc_p (r_type))
+	    {
+	      isym = bfd_sym_from_r_symndx (&elf_hash_table (info)->sym_cache,
+					    abfd, r_symndx);
+	      if (isym == NULL)
+		return false;
+	    }
+	}
       else
 	{
 	  h = sym_hashes[r_symndx - symtab_hdr->sh_info];
@@ -1877,9 +2157,16 @@ elf64_alpha_check_relocs (bfd *abfd, struct bfd_link_info *info,
 		|| h->root.root.type == bfd_link_hash_defweak))
 	maybe_dynamic = true;
 
+      /* Whether a global IFUNC needs .rela.iplt is not settled until
+	 sizing, so create the section for every reference that might.  */
+      if (elf64_alpha_ifunc_reloc_p (r_type)
+	  && elf64_alpha_ifunc_p (h, isym)
+	  && !bfd_link_pic (info)
+	  && !elf64_alpha_create_irelplt (info))
+	return false;
+
       need = 0;
       gotent_flags = 0;
-      r_type = ELF64_R_TYPE (rel->r_info);
       addend = rel->r_addend;
 
       switch (r_type)
@@ -1911,7 +2198,16 @@ elf64_alpha_check_relocs (bfd *abfd, struct bfd_link_info *info,
 
 	case R_ALPHA_REFLONG:
 	case R_ALPHA_REFQUAD:
-	  if (bfd_link_pic (info) || maybe_dynamic)
+	  if (r_type == R_ALPHA_REFQUAD && elf64_alpha_ifunc_p (h, isym))
+	    {
+	      /* A reference that becomes an R_ALPHA_IRELATIVE in .rela.iplt
+		 is sized later, from the sections that survive.  Whether a
+		 global one does is not settled until then, so record it
+		 either way; a local symbol never becomes dynamic.  */
+	      if (h != NULL || !elf64_alpha_ifunc_irelplt_p (NULL, isym, info))
+		need = NEED_DYNREL;
+	    }
+	  else if (bfd_link_pic (info) || maybe_dynamic)
 	    need = NEED_DYNREL;
 	  break;
 
@@ -2349,6 +2645,7 @@ elf64_alpha_merge_gots (bfd *a, bfd *b)
 		    && ae->addend == be->addend)
 		  {
 		    ae->flags |= be->flags;
+		    ae->irel_live |= be->irel_live;
 		    ae->use_count += be->use_count;
 		    *pbe = be->next;
 		    memset (be, 0xa5, sizeof (*be));
@@ -2861,7 +3158,15 @@ elf64_alpha_late_size_sections (struct bfd_link_info *info)
       elf64_alpha_size_rela_got_section (info);
       elf64_alpha_size_plt_section (info);
     }
-  /* else we're not dynamic and by definition we don't need such things.  */
+
+  /* The sizing above reserves nothing for a non-dynamic IFUNC, since the
+     dynamic linker never sees it; its relocations go in .rela.iplt.  */
+  if (elf_hash_table (info)->irelplt != NULL)
+    {
+      if (!elf64_alpha_scan_irelative (info))
+	return false;
+      elf64_alpha_size_irelplt_section (info);
+    }
 
   /* The check_relocs and adjust_dynamic_symbol entry points have
      determined the sizes of the various dynamic sections.  Allocate
@@ -3763,6 +4068,7 @@ elf64_alpha_relax_section (bfd *abfd, asection *sec,
 	  elf64_alpha_size_plt_section (link_info);
 	  elf64_alpha_size_rela_got_section (link_info);
 	}
+      elf64_alpha_size_irelplt_section (link_info);
     }
 
   symtab_hdr = &elf_symtab_hdr (abfd);
@@ -4416,9 +4722,14 @@ elf64_alpha_relocate_section (struct bfd_link_info *info,
 	      /* If the symbol has been forced local, output a RELATIVE
 		 reloc, otherwise it will be handled in finish_dynamic_symbol.
 		 An IFUNC gets an IRELATIVE instead.  */
-	      if (bfd_link_pic (info)
-		  && !dynamic_symbol_p
-		  && !undef_weak_ref)
+	      if (elf64_alpha_ifunc_irelplt_p (h, sym, info))
+		elf64_alpha_emit_dynrel (info->output_bfd, info, sgot,
+					 elf_hash_table (info)->irelplt,
+					 gotent->got_offset, 0,
+					 R_ALPHA_IRELATIVE, value);
+	      else if (bfd_link_pic (info)
+		       && !dynamic_symbol_p
+		       && !undef_weak_ref)
 		elf64_alpha_emit_dynrel (info->output_bfd, info, sgot, srelgot,
 					 gotent->got_offset, 0,
 					 (elf64_alpha_ifunc_p (h, sym)
@@ -4547,6 +4858,7 @@ elf64_alpha_relocate_section (struct bfd_link_info *info,
 	  {
 	    long dynindx, dyntype = r_type;
 	    bfd_vma dynaddend;
+	    asection *srel_out = srel;
 
 	    /* Careful here to remember RELATIVE relocations for global
 	       variables for symbolic shared objects.  */
@@ -4576,16 +4888,20 @@ elf64_alpha_relocate_section (struct bfd_link_info *info,
 		dynaddend = value - dtp_base;
 	      }
 	    else if (elf64_alpha_ifunc_reloc_p (r_type)
-		     && bfd_link_pic (info)
 		     && elf64_alpha_ifunc_p (h, sym)
 		     && (input_section->flags & SEC_ALLOC))
 	      {
-		/* The RELATIVE this would otherwise get would store the
-		   address of the resolver.  Space for it is already
-		   reserved, so the IRELATIVE takes its place.  */
+		/* In a PIC link the RELATIVE this would otherwise get would
+		   store the address of the resolver, and space for it is
+		   already reserved, so the IRELATIVE takes its place.  A
+		   non-PIC link reserved nothing there and puts it in
+		   .rela.iplt instead.  */
 		dynindx = 0;
 		dyntype = R_ALPHA_IRELATIVE;
 		dynaddend = value;
+
+		if (elf64_alpha_ifunc_irelplt_p (h, sym, info))
+		  srel_out = elf_hash_table (info)->irelplt;
 	      }
 	    else if (bfd_link_pic (info)
 		     && r_symndx != STN_UNDEF
@@ -4615,7 +4931,7 @@ elf64_alpha_relocate_section (struct bfd_link_info *info,
 
 	    if (input_section->flags & SEC_ALLOC)
 	      elf64_alpha_emit_dynrel (info->output_bfd, info, input_section,
-				       srel, rel->r_offset, dynindx,
+				       srel_out, rel->r_offset, dynindx,
 				       dyntype, dynaddend);
 	  }
 	  goto default_reloc;
@@ -4850,6 +5166,12 @@ elf64_alpha_finish_dynamic_symbol (struct bfd_link_info *info,
 {
   struct alpha_elf_link_hash_entry *ah = (struct alpha_elf_link_hash_entry *)h;
 
+  /* Only a defined IFUNC gets here without dynamic sections.  There is no
+     PLT to fill in, whatever needs_plt says, and relocate_section has
+     already written its GOT entries.  */
+  if (!elf_hash_table (info)->dynamic_sections_created)
+    return true;
+
   if (h->needs_plt)
     {
       /* Fill in the .plt entry for this symbol.  */
diff --git ./ld/testsuite/ld-alpha/alpha.exp ./ld/testsuite/ld-alpha/alpha.exp
index af5d94e24a8..fa5862cc5eb 100644
--- ./ld/testsuite/ld-alpha/alpha.exp
+++ ./ld/testsuite/ld-alpha/alpha.exp
@@ -66,6 +66,8 @@ set alphatests {
      "" {got-realloc-a.s got-realloc-b.s}
      {{readelf -SW got-realloc.rd}}
      "got-realloc.so"}
+    {"IFUNC helper shared library" "-shared -melf64alpha" ""
+     "" {ifunc-shlib.s} {} "libalphaifunc.so"}
 }
 
 # Not implemented yet
@@ -75,6 +77,10 @@ set alphatests {
 
 run_ld_link_tests $alphatests
 
+# The dump tests run after the link tests above, so a .d file may name one
+# of the shared libraries they leave in tmpdir.  Several do: linking against
+# a shared library is the only way to make a dump test produce an
+# executable with dynamic sections.
 set test_list [lsort [glob -nocomplain $srcdir/$subdir/*.d]]
 foreach t $test_list {
     # We need to strip the ".d", but can leave the dirname.
diff --git ./ld/testsuite/ld-alpha/ifunc-comdat-a.s ./ld/testsuite/ld-alpha/ifunc-comdat-a.s
new file mode 100644
index 00000000000..5a71c7da3a8
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-comdat-a.s
@@ -0,0 +1,11 @@
+	.section .text.grp,"axG",@progbits,grp,comdat
+	.type	kept_ifunc, @gnu_indirect_function
+kept_ifunc:
+	ret
+
+	.text
+	.globl	_start
+	.ent	_start
+_start:
+	ret
+	.end	_start
diff --git ./ld/testsuite/ld-alpha/ifunc-comdat-b.s ./ld/testsuite/ld-alpha/ifunc-comdat-b.s
new file mode 100644
index 00000000000..f612ddd4cd8
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-comdat-b.s
@@ -0,0 +1,14 @@
+	# This copy of the group is discarded in favour of the one in
+	# ifunc-comdat-a.s, taking the definition of local_ifunc with it.
+	.section .text.grp,"axG",@progbits,grp,comdat
+	.type	local_ifunc, @gnu_indirect_function
+local_ifunc:
+	ret
+
+	# The reference survives, but nothing relocates it: the generic code
+	# reports it rather than letting relocate_section reach it.  Nothing
+	# may be reserved in .rela.iplt for it either.
+	.data
+	.globl	ptr
+ptr:
+	.quad	local_ifunc
diff --git ./ld/testsuite/ld-alpha/ifunc-comdat.d ./ld/testsuite/ld-alpha/ifunc-comdat.d
new file mode 100644
index 00000000000..752f92bba61
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-comdat.d
@@ -0,0 +1,4 @@
+#source: ifunc-comdat-a.s
+#source: ifunc-comdat-b.s
+#ld: -melf64alpha
+#error: \A`local_ifunc' referenced in section `\.data' of [^\n]*: defined in discarded section `\.text\.grp\[grp\]' of [^\n]*\n?\Z
diff --git ./ld/testsuite/ld-alpha/ifunc-discard.d ./ld/testsuite/ld-alpha/ifunc-discard.d
new file mode 100644
index 00000000000..86fa8d48dc4
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-discard.d
@@ -0,0 +1,11 @@
+#source: ifunc-discard.s
+#ld: -melf64alpha --no-warn-rwx-segments -T $srcdir/$subdir/ifunc-discard.ld
+#readelf: -Wr
+
+# Only the reference that survives the script reserves anything.  A slot
+# reserved for one the script discarded would be left as an R_ALPHA_NONE,
+# which libc's startup code would try to apply.
+Relocation section '\.rela\.dyn' .* contains 1 entry:
+#...
+[0-9a-f]+ +0+2a R_ALPHA_IRELATIVE +[0-9a-f]+
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-discard.ld ./ld/testsuite/ld-alpha/ifunc-discard.ld
new file mode 100644
index 00000000000..f0cc43278ba
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-discard.ld
@@ -0,0 +1,4 @@
+SECTIONS
+{
+  /DISCARD/ : { *(.data.discard) *(.text.discard) }
+}
diff --git ./ld/testsuite/ld-alpha/ifunc-discard.s ./ld/testsuite/ld-alpha/ifunc-discard.s
new file mode 100644
index 00000000000..005c660e8fe
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-discard.s
@@ -0,0 +1,33 @@
+	.text
+
+	.globl	global_ifunc
+	.type	global_ifunc, @gnu_indirect_function
+global_ifunc:
+	ret
+
+	.type	local_ifunc, @gnu_indirect_function
+local_ifunc:
+	ret
+
+	.globl	_start
+	.ent	_start
+_start:
+	ret
+	.end	_start
+
+	# The one reference that survives, so that .rela.iplt is not simply
+	# empty.
+	.data
+	.globl	ptr
+ptr:
+	.quad	global_ifunc
+
+	# A linker script discards these after check_relocs has seen them.
+	# Nothing relocates them, so nothing may be reserved for them.
+	.section .data.discard,"aw",@progbits
+	.quad	global_ifunc
+	.quad	local_ifunc
+
+	.section .text.discard,"ax",@progbits
+	ldq	$27, global_ifunc($29)	!literal!1
+	ldq	$27, local_ifunc($29)	!literal!2
diff --git ./ld/testsuite/ld-alpha/ifunc-extern.d ./ld/testsuite/ld-alpha/ifunc-extern.d
new file mode 100644
index 00000000000..f37118f98bf
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-extern.d
@@ -0,0 +1,14 @@
+#source: ifunc-extern.s
+#ld: -melf64alpha tmpdir/libalphaifunc.so
+#readelf: -Wr
+
+# The IFUNC is defined in another module, so the dynamic linker resolves
+# it: an ordinary PLT entry and a symbolic data relocation, no IRELATIVE.
+Relocation section '\.rela\.dyn' .* contains 1 entry:
+#...
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_REFQUAD .*shlib_ifunc \+ 0
+#...
+Relocation section '\.rela\.plt' .* contains 1 entry:
+#...
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_JMP_SLOT .*shlib_ifunc \+ 0
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-extern.s ./ld/testsuite/ld-alpha/ifunc-extern.s
new file mode 100644
index 00000000000..d8e2cddaba4
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-extern.s
@@ -0,0 +1,16 @@
+	.text
+
+	.globl	_start
+	.ent	_start
+_start:
+	ldgp	$29, 0($27)
+	ldq	$27, shlib_ifunc($29)	!literal!1
+	jsr	$26, ($27), 0		!lituse_jsr!1
+	ldgp	$29, 0($26)
+	ret
+	.end	_start
+
+	.data
+	.globl	ptr
+ptr:
+	.quad	shlib_ifunc
diff --git ./ld/testsuite/ld-alpha/ifunc-gc-local.d ./ld/testsuite/ld-alpha/ifunc-gc-local.d
new file mode 100644
index 00000000000..be3336c2e51
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-gc-local.d
@@ -0,0 +1,8 @@
+#source: ifunc-gc-local.s
+#ld: -melf64alpha --gc-sections -e _start
+#readelf: -Wr
+
+Relocation section '\.rela\.dyn' .* contains 1 entry:
+#...
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_IRELATIVE +[0-9a-f]+
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-gc-local.s ./ld/testsuite/ld-alpha/ifunc-gc-local.s
new file mode 100644
index 00000000000..bcbdbc02202
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-gc-local.s
@@ -0,0 +1,32 @@
+	.section .text.ifunc,"ax",@progbits
+	.type	local_ifunc, @gnu_indirect_function
+local_ifunc:
+	ret
+
+	# Both of these need an IRELATIVE, but --gc-sections drops the
+	# section holding the call before check_relocs runs, so only one is
+	# counted.
+	.section .text.dead,"ax",@progbits
+	.globl	dead
+	.ent	dead
+dead:
+	ldgp	$29, 0($27)
+	ldq	$27, local_ifunc($29)	!literal!1
+	jsr	$26, ($27), 0		!lituse_jsr!1
+	ldgp	$29, 0($26)
+	ret
+	.end	dead
+
+	.section .data.live,"aw",@progbits
+	.globl	ptr
+ptr:
+	.quad	local_ifunc
+
+	.section .text.start,"ax",@progbits
+	.globl	_start
+	.ent	_start
+_start:
+	ldgp	$29, 0($27)
+	ldah	$1, ptr($29)		!gprelhigh
+	ret
+	.end	_start
diff --git ./ld/testsuite/ld-alpha/ifunc-gc.d ./ld/testsuite/ld-alpha/ifunc-gc.d
new file mode 100644
index 00000000000..c9e9d389d3b
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-gc.d
@@ -0,0 +1,8 @@
+#source: ifunc-gc.s
+#ld: -melf64alpha --gc-sections -e _start
+#readelf: -Wr
+
+Relocation section '\.rela\.dyn' .* contains 1 entry:
+#...
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_IRELATIVE +[0-9a-f]+
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-gc.s ./ld/testsuite/ld-alpha/ifunc-gc.s
new file mode 100644
index 00000000000..cde51d4c3e4
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-gc.s
@@ -0,0 +1,32 @@
+	.section .text.ifunc,"ax",@progbits
+	.globl	global_ifunc
+	.type	global_ifunc, @gnu_indirect_function
+global_ifunc:
+	ret
+
+	# Both of these need an IRELATIVE, but --gc-sections drops the
+	# section holding the call, so only one may be reserved.
+	.section .text.dead,"ax",@progbits
+	.globl	dead
+	.ent	dead
+dead:
+	ldgp	$29, 0($27)
+	ldq	$27, global_ifunc($29)	!literal!1
+	jsr	$26, ($27), 0		!lituse_jsr!1
+	ldgp	$29, 0($26)
+	ret
+	.end	dead
+
+	.section .data.live,"aw",@progbits
+	.globl	ptr
+ptr:
+	.quad	global_ifunc
+
+	.section .text.start,"ax",@progbits
+	.globl	_start
+	.ent	_start
+_start:
+	ldgp	$29, 0($27)
+	ldah	$1, ptr($29)		!gprelhigh
+	ret
+	.end	_start
diff --git ./ld/testsuite/ld-alpha/ifunc-global-dynamic.d ./ld/testsuite/ld-alpha/ifunc-global-dynamic.d
new file mode 100644
index 00000000000..2915f33fa12
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-global-dynamic.d
@@ -0,0 +1,9 @@
+#source: ifunc-global.s
+#ld: -melf64alpha tmpdir/libalphaifunc.so
+#readelf: -Wr
+
+Relocation section '\.rela\.dyn' .* contains 2 entries:
+#...
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_IRELATIVE +[0-9a-f]+
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_IRELATIVE +[0-9a-f]+
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-global-export.d ./ld/testsuite/ld-alpha/ifunc-global-export.d
new file mode 100644
index 00000000000..9d40e14486a
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-global-export.d
@@ -0,0 +1,12 @@
+#source: ifunc-global.s
+#ld: -melf64alpha --export-dynamic tmpdir/libalphaifunc.so
+#readelf: -Wr
+
+# One IRELATIVE for the GOT entry and one for ptr, and nothing else.  The
+# IFUNC is in .dynsym but is not preemptible in an executable, so the link
+# resolves it itself and no PLT entry is left over from the guess that
+# check_relocs makes about one.
+Relocation section '\.rela\.dyn' .* contains 2 entries:
+ +Offset +Info +Type +Symbol's Value +Symbol's Name \+ Addend
+[0-9a-f]+ +0+2a R_ALPHA_IRELATIVE +[0-9a-f]+
+[0-9a-f]+ +0+2a R_ALPHA_IRELATIVE +[0-9a-f]+
diff --git ./ld/testsuite/ld-alpha/ifunc-global-nocombreloc.d ./ld/testsuite/ld-alpha/ifunc-global-nocombreloc.d
new file mode 100644
index 00000000000..2c5a6ebb174
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-global-nocombreloc.d
@@ -0,0 +1,9 @@
+#source: ifunc-global.s
+#ld: -melf64alpha -z nocombreloc
+#readelf: -Wr
+
+Relocation section '\.rela\.iplt' .* contains 2 entries:
+#...
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_IRELATIVE +[0-9a-f]+
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_IRELATIVE +[0-9a-f]+
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-global-static.d ./ld/testsuite/ld-alpha/ifunc-global-static.d
new file mode 100644
index 00000000000..415d5245b68
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-global-static.d
@@ -0,0 +1,17 @@
+#source: ifunc-global.s
+#ld: -melf64alpha
+#readelf: -Wrs
+
+# One IRELATIVE for the GOT entry and one for ptr.  The symbol table
+# entries confirm that the resolver kept its IFUNC type.
+Relocation section '\.rela\.dyn' .* contains 2 entries:
+#...
+[0-9a-f]+ +0+2a R_ALPHA_IRELATIVE +[0-9a-f]+
+[0-9a-f]+ +0+2a R_ALPHA_IRELATIVE +[0-9a-f]+
+#...
+Symbol table '\.symtab' contains .* entries:
+#...
+ +[0-9]+: [0-9a-f]+ +0 IFUNC +GLOBAL +DEFAULT +[0-9]+ global_ifunc
+#...
+ +[0-9]+: [0-9a-f]+ +0 NOTYPE +GLOBAL +DEFAULT +[0-9]+ ptr
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-local-call-static.d ./ld/testsuite/ld-alpha/ifunc-local-call-static.d
new file mode 100644
index 00000000000..f24f9dbd2b2
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-local-call-static.d
@@ -0,0 +1,8 @@
+#source: ifunc-local-call.s
+#ld: -melf64alpha
+#readelf: -Wr
+
+Relocation section '\.rela\.dyn' .* contains 1 entry:
+#...
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_IRELATIVE +[0-9a-f]+
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-local-dynamic.d ./ld/testsuite/ld-alpha/ifunc-local-dynamic.d
new file mode 100644
index 00000000000..f38004249bf
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-local-dynamic.d
@@ -0,0 +1,9 @@
+#source: ifunc-local.s
+#ld: -melf64alpha tmpdir/libalphaifunc.so
+#readelf: -Wr
+
+Relocation section '\.rela\.dyn' .* contains 2 entries:
+#...
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_IRELATIVE +[0-9a-f]+
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_IRELATIVE +[0-9a-f]+
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-local-static.d ./ld/testsuite/ld-alpha/ifunc-local-static.d
new file mode 100644
index 00000000000..7096633a729
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-local-static.d
@@ -0,0 +1,17 @@
+#source: ifunc-local.s
+#ld: -melf64alpha
+#readelf: -Wrs
+
+# One IRELATIVE for the GOT entry and one for ptr.  The symbol table
+# entries confirm that the resolver kept its IFUNC type.
+Relocation section '\.rela\.dyn' .* contains 2 entries:
+#...
+[0-9a-f]+ +0+2a R_ALPHA_IRELATIVE +[0-9a-f]+
+[0-9a-f]+ +0+2a R_ALPHA_IRELATIVE +[0-9a-f]+
+#...
+Symbol table '\.symtab' contains .* entries:
+#...
+ +[0-9]+: [0-9a-f]+ +0 IFUNC +LOCAL +DEFAULT +[0-9]+ local_ifunc
+#...
+ +[0-9]+: [0-9a-f]+ +0 NOTYPE +GLOBAL +DEFAULT +[0-9]+ ptr
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-multi-a.s ./ld/testsuite/ld-alpha/ifunc-multi-a.s
new file mode 100644
index 00000000000..bfdd314b023
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-multi-a.s
@@ -0,0 +1,24 @@
+	.text
+
+	# One object defines the IFUNC and both call and reference it, so
+	# that the GOT entries merge into one and the two data references
+	# accumulate on the same hash table entry.
+	.globl	global_ifunc
+	.type	global_ifunc, @gnu_indirect_function
+global_ifunc:
+	ret
+
+	.globl	_start
+	.ent	_start
+_start:
+	ldgp	$29, 0($27)
+	ldq	$27, global_ifunc($29)	!literal!1
+	jsr	$26, ($27), 0		!lituse_jsr!1
+	ldgp	$29, 0($26)
+	ret
+	.end	_start
+
+	.data
+	.globl	ptr_a
+ptr_a:
+	.quad	global_ifunc
diff --git ./ld/testsuite/ld-alpha/ifunc-multi-b.s ./ld/testsuite/ld-alpha/ifunc-multi-b.s
new file mode 100644
index 00000000000..119a8b3fbea
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-multi-b.s
@@ -0,0 +1,16 @@
+	.text
+
+	.globl	other
+	.ent	other
+other:
+	ldgp	$29, 0($27)
+	ldq	$27, global_ifunc($29)	!literal!1
+	jsr	$26, ($27), 0		!lituse_jsr!1
+	ldgp	$29, 0($26)
+	ret
+	.end	other
+
+	.data
+	.globl	ptr_b
+ptr_b:
+	.quad	global_ifunc
diff --git ./ld/testsuite/ld-alpha/ifunc-multi.d ./ld/testsuite/ld-alpha/ifunc-multi.d
new file mode 100644
index 00000000000..d39a428d69a
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-multi.d
@@ -0,0 +1,13 @@
+#source: ifunc-multi-a.s
+#source: ifunc-multi-b.s
+#ld: -melf64alpha
+#readelf: -Wr
+
+# One IRELATIVE for the merged GOT entry and one for each of the two data
+# references.
+Relocation section '\.rela\.dyn' .* contains 3 entries:
+#...
+[0-9a-f]+ +0+2a R_ALPHA_IRELATIVE +[0-9a-f]+
+[0-9a-f]+ +0+2a R_ALPHA_IRELATIVE +[0-9a-f]+
+[0-9a-f]+ +0+2a R_ALPHA_IRELATIVE +[0-9a-f]+
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-none.d ./ld/testsuite/ld-alpha/ifunc-none.d
new file mode 100644
index 00000000000..1ab9ce7e457
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-none.d
@@ -0,0 +1,7 @@
+#source: ifunc-none.s
+#ld: -melf64alpha
+#readelf: -Wr
+
+# A link with nothing to resolve at startup gets no relocations at all, and
+# in particular no .rela.iplt.
+There are no relocations in this file\.
diff --git ./ld/testsuite/ld-alpha/ifunc-none.s ./ld/testsuite/ld-alpha/ifunc-none.s
new file mode 100644
index 00000000000..0846a6f7f5e
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-none.s
@@ -0,0 +1,12 @@
+	.text
+
+	.globl	_start
+	.ent	_start
+_start:
+	ret
+	.end	_start
+
+	.data
+	.globl	ptr
+ptr:
+	.quad	_start
diff --git ./ld/testsuite/ld-alpha/ifunc-relax-a.s ./ld/testsuite/ld-alpha/ifunc-relax-a.s
new file mode 100644
index 00000000000..a7253da3ab1
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-relax-a.s
@@ -0,0 +1,32 @@
+/* 8000 got entries against undefined symbols, so relaxation cannot remove
+   any of them.  That is 64000 bytes; with the entry for the IFUNC it is
+   just under MAX_GOT_SIZE, so this object keeps its own got subsection.  */
+
+	.text
+	.globl	global_ifunc
+	.type	global_ifunc, @gnu_indirect_function
+global_ifunc:
+	ret
+
+	.globl	_start
+	.ent	_start
+_start:
+	ldgp	$29,0($27)
+	.prologue 1
+	ldq	$1,global_ifunc($29)	!literal
+	.irpc	w,01234567
+	.irpc	x,0123456789
+	.irpc	y,0123456789
+	.irpc	z,0123456789
+	ldq	$1,g\w\x\y\z($29)	!literal
+	.endr
+	.endr
+	.endr
+	.endr
+	ret	$31,($26),1
+	.end	_start
+
+	.data
+	.globl	ptr
+ptr:
+	.quad	global_ifunc
diff --git ./ld/testsuite/ld-alpha/ifunc-relax-b.s ./ld/testsuite/ld-alpha/ifunc-relax-b.s
new file mode 100644
index 00000000000..21e097d19af
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-relax-b.s
@@ -0,0 +1,47 @@
+/* 251 got entries, of which relaxation removes the 150 that are only used
+   by a call to a local function.  Adding this object's entries to the 8001
+   in ifunc-relax-a.s exceeds MAX_GOT_SIZE, so the two subsections cannot be
+   merged when they are first sized.  Once relaxation has dropped the 150
+   they fit, and the two entries for the IFUNC merge into one, which is one
+   fewer R_ALPHA_IRELATIVE than the first sizing reserved.  */
+
+	.macro	mkcall name, seq
+	ldq	$27,\name($29)		!literal!\seq
+	jsr	$26,($27),\name		!lituse_jsr!\seq
+	.endm
+
+	.text
+
+	.irpc	x,012
+	.irpc	y,0123456789
+	.irpc	z,01234
+	.ent	lf\x\y\z
+lf\x\y\z:
+	ldgp	$29,0($27)
+	.prologue 1
+	ret	$31,($26),1
+	.end	lf\x\y\z
+	.endr
+	.endr
+	.endr
+
+	.globl	bfunc
+	.ent	bfunc
+bfunc:
+	ldgp	$29,0($27)
+	.prologue 1
+	ldq	$1,global_ifunc($29)	!literal
+	.irpc	x,012
+	.irpc	y,0123456789
+	.irpc	z,01234
+	mkcall	lf\x\y\z, 1\x\y\z
+	.endr
+	.endr
+	.endr
+	.irpc	x,0123456789
+	.irpc	y,0123456789
+	ldq	$1,h\x\y($29)		!literal
+	.endr
+	.endr
+	ret	$31,($26),1
+	.end	bfunc
diff --git ./ld/testsuite/ld-alpha/ifunc-relax-discard.d ./ld/testsuite/ld-alpha/ifunc-relax-discard.d
new file mode 100644
index 00000000000..433cca04be8
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-relax-discard.d
@@ -0,0 +1,14 @@
+#source: ifunc-relax-discard.s
+#source: ifunc-relax-b.s
+#ld: -relax -melf64alpha --unresolved-symbols=ignore-all --no-warn-rwx-segments -T $srcdir/$subdir/ifunc-discard.ld
+#readelf: -Wr
+
+# The got entry that survives the merge during relaxation is the one whose
+# only reference the linker script discarded, so it carries no mark of its
+# own.  It has to take the one from the entry it absorbs, or nothing is
+# reserved for the reference that does survive.
+Relocation section '\.rela\.dyn' .* contains 2 entries:
+#...
+[0-9a-f]+ +0+2a R_ALPHA_IRELATIVE +[0-9a-f]+
+[0-9a-f]+ +0+2a R_ALPHA_IRELATIVE +[0-9a-f]+
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-relax-discard.s ./ld/testsuite/ld-alpha/ifunc-relax-discard.s
new file mode 100644
index 00000000000..450c1029fbf
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-relax-discard.s
@@ -0,0 +1,41 @@
+/* As ifunc-relax-a.s, but with this object's reference to the IFUNC in a
+   section that the linker script discards.  */
+
+	.text
+	.globl	global_ifunc
+	.type	global_ifunc, @gnu_indirect_function
+global_ifunc:
+	ret
+
+	# The only reference in this object to the IFUNC's got entry, in a
+	# section the linker script discards.  The entry is still created and
+	# still occupies the got, so it is the one that survives when the two
+	# got subsections merge, and it has to take the mark from the entry
+	# that the surviving reference in ifunc-relax-b.s made.
+	.section .text.discard,"ax",@progbits
+	ldgp	$29,0($27)
+	ldq	$1,global_ifunc($29)	!literal
+	ret
+
+	.text
+	.globl	_start
+	.ent	_start
+_start:
+	ldgp	$29,0($27)
+	.prologue 1
+	.irpc	w,01234567
+	.irpc	x,0123456789
+	.irpc	y,0123456789
+	.irpc	z,0123456789
+	ldq	$1,g\w\x\y\z($29)	!literal
+	.endr
+	.endr
+	.endr
+	.endr
+	ret	$31,($26),1
+	.end	_start
+
+	.data
+	.globl	ptr
+ptr:
+	.quad	global_ifunc
diff --git ./ld/testsuite/ld-alpha/ifunc-relax.d ./ld/testsuite/ld-alpha/ifunc-relax.d
new file mode 100644
index 00000000000..37c3ce2b871
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-relax.d
@@ -0,0 +1,14 @@
+#source: ifunc-relax-a.s
+#source: ifunc-relax-b.s
+#ld: -relax -melf64alpha --unresolved-symbols=ignore-all
+#readelf: -Wr
+
+# The two objects' got subsections cannot be merged until relaxation has
+# shrunk them, and the merge leaves one entry for the IFUNC where the first
+# sizing counted two.  One IRELATIVE for that entry and one for ptr; a third
+# would mean .rela.iplt was not sized again after the got was.
+Relocation section '\.rela\.dyn' .* contains 2 entries:
+#...
+[0-9a-f]+ +0+2a R_ALPHA_IRELATIVE +[0-9a-f]+
+[0-9a-f]+ +0+2a R_ALPHA_IRELATIVE +[0-9a-f]+
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-shlib.s ./ld/testsuite/ld-alpha/ifunc-shlib.s
new file mode 100644
index 00000000000..ad955ba7466
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-shlib.s
@@ -0,0 +1,9 @@
+	.text
+
+	# An IFUNC exported from a shared library.  A reference from another
+	# module is resolved by the dynamic linker, not by the static linker,
+	# so it is treated as an ordinary function here.
+	.globl	shlib_ifunc
+	.type	shlib_ifunc, @gnu_indirect_function
+shlib_ifunc:
+	ret
diff --git ./ld/testsuite/ld-alpha/ifunc-weak-dynamic.d ./ld/testsuite/ld-alpha/ifunc-weak-dynamic.d
new file mode 100644
index 00000000000..d5c7b4af8ce
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-weak-dynamic.d
@@ -0,0 +1,9 @@
+#source: ifunc-weak.s
+#ld: -melf64alpha tmpdir/libalphaifunc.so
+#readelf: -Wr
+
+Relocation section '\.rela\.dyn' .* contains 2 entries:
+#...
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_IRELATIVE +[0-9a-f]+
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_IRELATIVE +[0-9a-f]+
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-weak-static.d ./ld/testsuite/ld-alpha/ifunc-weak-static.d
new file mode 100644
index 00000000000..2b24975180c
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-weak-static.d
@@ -0,0 +1,9 @@
+#source: ifunc-weak.s
+#ld: -melf64alpha
+#readelf: -Wr
+
+Relocation section '\.rela\.dyn' .* contains 2 entries:
+#...
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_IRELATIVE +[0-9a-f]+
+[0-9a-f]+ +[0-9a-f]+ +R_ALPHA_IRELATIVE +[0-9a-f]+
+#pass
diff --git ./ld/testsuite/ld-alpha/ifunc-weak-undef-dynamic.d ./ld/testsuite/ld-alpha/ifunc-weak-undef-dynamic.d
new file mode 100644
index 00000000000..d9a69c98eb9
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-weak-undef-dynamic.d
@@ -0,0 +1,5 @@
+#source: ifunc-weak-undef.s
+#ld: -melf64alpha tmpdir/libalphaifunc.so
+#readelf: -Wr
+
+There are no relocations in this file\.
diff --git ./ld/testsuite/ld-alpha/ifunc-weak-undef-static.d ./ld/testsuite/ld-alpha/ifunc-weak-undef-static.d
new file mode 100644
index 00000000000..3f84c7bea2d
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-weak-undef-static.d
@@ -0,0 +1,5 @@
+#source: ifunc-weak-undef.s
+#ld: -melf64alpha
+#readelf: -Wr
+
+There are no relocations in this file\.
diff --git ./ld/testsuite/ld-alpha/ifunc-weak-undef.s ./ld/testsuite/ld-alpha/ifunc-weak-undef.s
new file mode 100644
index 00000000000..7d7a812309b
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-weak-undef.s
@@ -0,0 +1,22 @@
+	.text
+
+	# A weak reference that carries the IFUNC type without a definition.
+	# It resolves to zero like any other undefined weak function; the
+	# type must not make the linker treat it as a defined IFUNC.
+	.weak	weak_ifunc
+	.type	weak_ifunc, @gnu_indirect_function
+
+	.globl	_start
+	.ent	_start
+_start:
+	ldgp	$29, 0($27)
+	ldq	$27, weak_ifunc($29)	!literal!1
+	jsr	$26, ($27), 0		!lituse_jsr!1
+	ldgp	$29, 0($26)
+	ret
+	.end	_start
+
+	.data
+	.globl	ptr
+ptr:
+	.quad	weak_ifunc
diff --git ./ld/testsuite/ld-alpha/ifunc-weak.s ./ld/testsuite/ld-alpha/ifunc-weak.s
new file mode 100644
index 00000000000..0461a5f4032
--- /dev/null
+++ ./ld/testsuite/ld-alpha/ifunc-weak.s
@@ -0,0 +1,24 @@
+	.text
+
+	# A weak IFUNC, both called and referenced.  It is not preemptible
+	# in an executable, so this link resolves it itself, but its weak
+	# binding makes check_relocs guess that it might become dynamic.
+	.weak	weak_ifunc
+	.type	weak_ifunc, @gnu_indirect_function
+weak_ifunc:
+	ret
+
+	.globl	_start
+	.ent	_start
+_start:
+	ldgp	$29, 0($27)
+	ldq	$27, weak_ifunc($29)	!literal!1
+	jsr	$26, ($27), 0		!lituse_jsr!1
+	ldgp	$29, 0($26)
+	ret
+	.end	_start
+
+	.data
+	.globl	ptr
+ptr:
+	.quad	weak_ifunc
-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.