+/* Generate the PLT slots together with the dynamic relocations needed
+ for IFUNC symbols. */
+
+static void
+elf_s390_finish_ifunc_symbol (bfd *output_bfd,
+ struct bfd_link_info *info,
+ struct elf_link_hash_entry *h,
+ struct elf_s390_link_hash_table *htab,
+ bfd_vma iplt_offset,
+ bfd_vma resolver_address)
+{
+ bfd_vma iplt_index;
+ bfd_vma got_offset;
+ bfd_vma igotiplt_offset;
+ Elf_Internal_Rela rela;
+ bfd_byte *loc;
+ asection *plt, *gotplt, *relplt;
+ bfd_vma relative_offset;
+
+ if (htab->elf.iplt == NULL
+ || htab->elf.igotplt == NULL
+ || htab->elf.irelplt == NULL)
+ abort ();
+
+ gotplt = htab->elf.igotplt;
+ relplt = htab->elf.irelplt;
+
+ /* Index of the PLT slot within iplt section. */
+ iplt_index = iplt_offset / PLT_ENTRY_SIZE;
+ plt = htab->elf.iplt;
+ /* Offset into the igot.plt section. */
+ igotiplt_offset = iplt_index * GOT_ENTRY_SIZE;
+ /* Offset into the got section. */
+ got_offset = igotiplt_offset + gotplt->output_offset;
+
+ /* S390 uses halfwords for relative branch calc! */
+ relative_offset = - (plt->output_offset +
+ (PLT_ENTRY_SIZE * iplt_index) + 18) / 2;
+ /* If offset is > 32768, branch to a previous branch
+ 390 can only handle +-64 K jumps. */
+ if ( -32768 > (int) relative_offset )
+ relative_offset
+ = -(unsigned) (((65536 / PLT_ENTRY_SIZE - 1) * PLT_ENTRY_SIZE) / 2);
+
+ /* Fill in the entry in the procedure linkage table. */
+ if (!bfd_link_pic (info))
+ {
+ memcpy (plt->contents + iplt_offset, elf_s390_plt_entry,
+ PLT_ENTRY_SIZE);
+
+ /* Adjust jump to the first plt entry. */
+ bfd_put_32 (output_bfd, (bfd_vma) 0+(relative_offset << 16),
+ plt->contents + iplt_offset + 20);
+
+ /* Push the GOT offset field. */
+ bfd_put_32 (output_bfd,
+ (gotplt->output_section->vma
+ + got_offset),
+ plt->contents + iplt_offset + 24);
+ }
+ else if (got_offset < 4096)
+ {
+ /* The GOT offset is small enough to be used directly as
+ displacement. */
+ memcpy (plt->contents + iplt_offset,
+ elf_s390_plt_pic12_entry,
+ PLT_ENTRY_SIZE);
+
+ /* Put in the GOT offset as displacement value. The 0xc000
+ value comes from the first word of the plt entry. Look
+ at the elf_s390_plt_pic16_entry content. */
+ bfd_put_16 (output_bfd, (bfd_vma)0xc000 | got_offset,
+ plt->contents + iplt_offset + 2);
+
+ /* Adjust the jump to the first plt entry. */
+ bfd_put_32 (output_bfd, (bfd_vma) 0+(relative_offset << 16),
+ plt->contents + iplt_offset + 20);
+ }
+ else if (got_offset < 32768)
+ {
+ /* The GOT offset is too big for a displacement but small
+ enough to be a signed 16 bit immediate value as it can be
+ used in an lhi instruction. */
+ memcpy (plt->contents + iplt_offset,
+ elf_s390_plt_pic16_entry,
+ PLT_ENTRY_SIZE);
+
+ /* Put in the GOT offset for the lhi instruction. */
+ bfd_put_16 (output_bfd, (bfd_vma)got_offset,
+ plt->contents + iplt_offset + 2);
+
+ /* Adjust the jump to the first plt entry. */
+ bfd_put_32 (output_bfd, (bfd_vma) 0+(relative_offset << 16),
+ plt->contents + iplt_offset + 20);
+ }
+ else
+ {
+ memcpy (plt->contents + iplt_offset,
+ elf_s390_plt_pic_entry,
+ PLT_ENTRY_SIZE);
+
+ /* Adjust the jump to the first plt entry. */
+ bfd_put_32 (output_bfd, (bfd_vma) 0+(relative_offset << 16),
+ plt->contents + iplt_offset + 20);
+
+ /* Push the GOT offset field. */
+ bfd_put_32 (output_bfd, got_offset,
+ plt->contents + iplt_offset + 24);
+ }
+ /* Insert offset into reloc. table here. */
+ bfd_put_32 (output_bfd, relplt->output_offset +
+ iplt_index * RELA_ENTRY_SIZE,
+ plt->contents + iplt_offset + 28);
+
+ /* Fill in the entry in the global offset table.
+ Points to instruction after GOT offset. */
+ bfd_put_32 (output_bfd,
+ (plt->output_section->vma
+ + plt->output_offset
+ + iplt_offset
+ + 12),
+ gotplt->contents + igotiplt_offset);
+
+ /* Fill in the entry in the .rela.plt section. */
+ rela.r_offset = gotplt->output_section->vma + got_offset;
+
+ if (!h
+ || h->dynindx == -1
+ || ((bfd_link_executable (info)
+ || ELF_ST_VISIBILITY (h->other) != STV_DEFAULT)
+ && h->def_regular))
+ {
+ /* The symbol can be locally resolved. */
+ rela.r_info = ELF32_R_INFO (0, R_390_IRELATIVE);
+ rela.r_addend = resolver_address;
+ }
+ else
+ {
+ rela.r_info = ELF32_R_INFO (h->dynindx, R_390_JMP_SLOT);
+ rela.r_addend = 0;
+ }
+
+ loc = relplt->contents + iplt_index * RELA_ENTRY_SIZE;
+ bfd_elf32_swap_reloca_out (output_bfd, &rela, loc);
+}
+