Fix address violations when reading corrupt VMS records.
[deliverable/binutils-gdb.git] / gold / powerpc.cc
index f16faf7f34a2afff803520eaa45fa58e136ec80f..1f2bc9ede798de03a647c91cd8b10e6268c0a0b6 100644 (file)
@@ -1,6 +1,6 @@
 // powerpc.cc -- powerpc target support for gold.
 
-// Copyright (C) 2008-2015 Free Software Foundation, Inc.
+// Copyright (C) 2008-2017 Free Software Foundation, Inc.
 // Written by David S. Miller <davem@davemloft.net>
 //        and David Edelsohn <edelsohn@gnu.org>
 
@@ -70,6 +70,10 @@ class Target_powerpc;
 
 struct Stub_table_owner
 {
+  Stub_table_owner()
+    : output_section(NULL), owner(NULL)
+  { }
+
   Output_section* output_section;
   const Output_section::Input_section* owner;
 };
@@ -77,6 +81,9 @@ struct Stub_table_owner
 inline bool
 is_branch_reloc(unsigned int r_type);
 
+// Counter incremented on every Powerpc_relobj constructed.
+static uint32_t object_id = 0;
+
 template<int size, bool big_endian>
 class Powerpc_relobj : public Sized_relobj_file<size, big_endian>
 {
@@ -88,9 +95,10 @@ public:
   Powerpc_relobj(const std::string& name, Input_file* input_file, off_t offset,
                 const typename elfcpp::Ehdr<size, big_endian>& ehdr)
     : Sized_relobj_file<size, big_endian>(name, input_file, offset, ehdr),
-      special_(0), has_small_toc_reloc_(false), opd_valid_(false),
-      opd_ent_(), access_from_map_(), has14_(), stub_table_index_(),
-      e_flags_(ehdr.get_e_flags()), st_other_()
+      uniq_(object_id++), special_(0), relatoc_(0), toc_(0),
+      has_small_toc_reloc_(false), opd_valid_(false),
+      e_flags_(ehdr.get_e_flags()), no_toc_opt_(), opd_ent_(),
+      access_from_map_(), has14_(), stub_table_index_(), st_other_()
   {
     this->set_abiversion(0);
   }
@@ -102,6 +110,52 @@ public:
   void
   do_read_symbols(Read_symbols_data*);
 
+  // Arrange to always relocate .toc first.
+  virtual void
+  do_relocate_sections(
+      const Symbol_table* symtab, const Layout* layout,
+      const unsigned char* pshdrs, Output_file* of,
+      typename Sized_relobj_file<size, big_endian>::Views* pviews);
+
+  // The .toc section index.
+  unsigned int
+  toc_shndx() const
+  {
+    return this->toc_;
+  }
+
+  // Mark .toc entry at OFF as not optimizable.
+  void
+  set_no_toc_opt(Address off)
+  {
+    if (this->no_toc_opt_.empty())
+      this->no_toc_opt_.resize(this->section_size(this->toc_shndx())
+                              / (size / 8));
+    off /= size / 8;
+    if (off < this->no_toc_opt_.size())
+      this->no_toc_opt_[off] = true;
+  }
+
+  // Mark the entire .toc as not optimizable.
+  void
+  set_no_toc_opt()
+  {
+    this->no_toc_opt_.resize(1);
+    this->no_toc_opt_[0] = true;
+  }
+
+  // Return true if code using the .toc entry at OFF should not be edited.
+  bool
+  no_toc_opt(Address off) const
+  {
+    if (this->no_toc_opt_.empty())
+      return false;
+    off /= size / 8;
+    if (off >= this->no_toc_opt_.size())
+      return true;
+    return this->no_toc_opt_[off];
+  }
+
   // The .got2 section shndx.
   unsigned int
   got2_shndx() const
@@ -184,6 +238,12 @@ public:
                  const unsigned char* prelocs,
                  const unsigned char* plocal_syms);
 
+  // Returns true if a code sequence loading a TOC entry can be
+  // converted into code calculating a TOC pointer relative offset.
+  bool
+  make_toc_relative(Target_powerpc<size, big_endian>* target,
+                   Address* value);
+
   // Perform the Sized_relobj_file method, then set up opd info from
   // .opd relocs.
   void
@@ -275,7 +335,7 @@ public:
   set_stub_table(unsigned int shndx, unsigned int stub_index)
   {
     if (shndx >= this->stub_table_index_.size())
-      this->stub_table_index_.resize(shndx + 1);
+      this->stub_table_index_.resize(shndx + 1, -1);
     this->stub_table_index_[shndx] = stub_index;
   }
 
@@ -288,8 +348,8 @@ public:
          = static_cast<Target_powerpc<size, big_endian>*>(
              parameters->sized_target<size, big_endian>());
        unsigned int indx = this->stub_table_index_[shndx];
-       gold_assert(indx < target->stub_tables().size());
-       return target->stub_tables()[indx];
+       if (indx < target->stub_tables().size())
+         return target->stub_tables()[indx];
       }
     return NULL;
   }
@@ -300,6 +360,10 @@ public:
     this->stub_table_index_.clear();
   }
 
+  uint32_t
+  uniq() const
+  { return this->uniq_; }
+
   int
   abiversion() const
   { return this->e_flags_ & elfcpp::EF_PPC64_ABI; }
@@ -339,9 +403,16 @@ private:
   opd_ent_ndx(size_t off) const
   { return off >> 4;}
 
+  // Per object unique identifier
+  uint32_t uniq_;
+
   // For 32-bit the .got2 section shdnx, for 64-bit the .opd section shndx.
   unsigned int special_;
 
+  // For 64-bit the .rela.toc and .toc section shdnx.
+  unsigned int relatoc_;
+  unsigned int toc_;
+
   // For 64-bit, whether this object uses small model relocs to access
   // the toc.
   bool has_small_toc_reloc_;
@@ -353,6 +424,13 @@ private:
   // access_from_map_.
   bool opd_valid_;
 
+  // Header e_flags
+  elfcpp::Elf_Word e_flags_;
+
+  // For 64-bit, an array with one entry per 64-bit word in the .toc
+  // section, set if accesses using that word cannot be optimised.
+  std::vector<bool> no_toc_opt_;
+
   // The first 8-byte word of an OPD entry gives the address of the
   // entry point of the function.  Relocatable object files have a
   // relocation on this word.  The following vector records the
@@ -370,9 +448,6 @@ private:
   // The stub table to use for a given input section.
   std::vector<unsigned int> stub_table_index_;
 
-  // Header e_flags
-  elfcpp::Elf_Word e_flags_;
-
   // ELF st_other field for local symbols.
   std::vector<unsigned char> st_other_;
 };
@@ -386,7 +461,7 @@ public:
   Powerpc_dynobj(const std::string& name, Input_file* input_file, off_t offset,
                 const typename elfcpp::Ehdr<size, big_endian>& ehdr)
     : Sized_dynobj<size, big_endian>(name, input_file, offset, ehdr),
-      opd_shndx_(0), opd_ent_(), e_flags_(ehdr.get_e_flags())
+      opd_shndx_(0), e_flags_(ehdr.get_e_flags()), opd_ent_()
   {
     this->set_abiversion(0);
   }
@@ -483,14 +558,31 @@ private:
   unsigned int opd_shndx_;
   Address opd_address_;
 
+  // Header e_flags
+  elfcpp::Elf_Word e_flags_;
+
   // The first 8-byte word of an OPD entry gives the address of the
   // entry point of the function.  Records the section and offset
   // corresponding to the address.  Note that in dynamic objects,
   // offset is *not* relative to the section.
   std::vector<Opd_ent> opd_ent_;
+};
 
-  // Header e_flags
-  elfcpp::Elf_Word e_flags_;
+// Powerpc_copy_relocs class.  Needed to peek at dynamic relocs the
+// base class will emit.
+
+template<int sh_type, int size, bool big_endian>
+class Powerpc_copy_relocs : public Copy_relocs<sh_type, size, big_endian>
+{
+ public:
+  Powerpc_copy_relocs()
+    : Copy_relocs<sh_type, size, big_endian>(elfcpp::R_POWERPC_COPY)
+  { }
+
+  // Emit any saved relocations which turn out to be needed.  This is
+  // called after all the relocs have been scanned.
+  void
+  emit(Output_data_reloc<sh_type, true, size, big_endian>*);
 };
 
 template<int size, bool big_endian>
@@ -509,7 +601,7 @@ class Target_powerpc : public Sized_target<size, big_endian>
   Target_powerpc()
     : Sized_target<size, big_endian>(&powerpc_info),
       got_(NULL), plt_(NULL), iplt_(NULL), brlt_section_(NULL),
-      glink_(NULL), rela_dyn_(NULL), copy_relocs_(elfcpp::R_POWERPC_COPY),
+      glink_(NULL), rela_dyn_(NULL), copy_relocs_(),
       tlsld_got_offset_(-1U),
       stub_tables_(), branch_lookup_table_(), branch_info_(),
       plt_thread_safe_(false), relax_failed_(false), relax_fail_count_(0),
@@ -631,6 +723,7 @@ class Target_powerpc : public Sized_target<size, big_endian>
   void
   do_calls_non_split(Relobj* object, unsigned int shndx,
                     section_offset_type fnoffset, section_size_type fnsize,
+                    const unsigned char* prelocs, size_t reloc_count,
                     unsigned char* view, section_size_type view_size,
                     std::string* from, std::string* to) const;
 
@@ -662,6 +755,21 @@ class Target_powerpc : public Sized_target<size, big_endian>
                          const unsigned char* plocal_symbols,
                          Relocatable_relocs*);
 
+  // Scan the relocs for --emit-relocs.
+  void
+  emit_relocs_scan(Symbol_table* symtab,
+                  Layout* layout,
+                  Sized_relobj_file<size, big_endian>* object,
+                  unsigned int data_shndx,
+                  unsigned int sh_type,
+                  const unsigned char* prelocs,
+                  size_t reloc_count,
+                  Output_section* output_section,
+                  bool needs_special_offset_handling,
+                  size_t local_symbol_count,
+                  const unsigned char* plocal_syms,
+                  Relocatable_relocs* rr);
+
   // Emit relocations for a section.
   void
   relocate_relocs(const Relocate_info<size, big_endian>*,
@@ -837,6 +945,23 @@ class Target_powerpc : public Sized_target<size, big_endian>
       }
   }
 
+  // Wrapper used after relax to define a local symbol in output data,
+  // from the end if value < 0.
+  void
+  define_local(Symbol_table* symtab, const char* name,
+              Output_data* od, Address value, unsigned int symsize)
+  {
+    Symbol* sym
+      = symtab->define_in_output_data(name, NULL, Symbol_table::PREDEFINED,
+                                     od, value, symsize, elfcpp::STT_NOTYPE,
+                                     elfcpp::STB_LOCAL, elfcpp::STV_HIDDEN, 0,
+                                     static_cast<Signed_address>(value) < 0,
+                                     false);
+    // We are creating this symbol late, so need to fix up things
+    // done early in Layout::finalize.
+    sym->set_dynsym_index(-1U);
+  }
+
   bool
   plt_thread_safe() const
   { return this->plt_thread_safe_; }
@@ -1104,19 +1229,6 @@ class Target_powerpc : public Sized_target<size, big_endian>
     }
   };
 
-  // A class which returns the size required for a relocation type,
-  // used while scanning relocs during a relocatable link.
-  class Relocatable_size_for_reloc
-  {
-   public:
-    unsigned int
-    get_size_for_reloc(unsigned int, Relobj*)
-    {
-      gold_unreachable();
-      return 0;
-    }
-  };
-
   // Optimize the TLS relocation type based on what we know about the
   // symbol.  IS_FINAL is true if the final address of this symbol is
   // known at link time.
@@ -1307,7 +1419,7 @@ class Target_powerpc : public Sized_target<size, big_endian>
   // The dynamic reloc section.
   Reloc_section* rela_dyn_;
   // Relocs saved to avoid a COPY reloc.
-  Copy_relocs<elfcpp::SHT_RELA, size, big_endian> copy_relocs_;
+  Powerpc_copy_relocs<elfcpp::SHT_RELA, size, big_endian> copy_relocs_;
   // Offset of the GOT entry for local dynamic __tls_get_addr calls.
   unsigned int tlsld_got_offset_;
 
@@ -1765,8 +1877,8 @@ Powerpc_relobj<size, big_endian>::set_abiversion(int ver)
     }
 }
 
-// Stash away the index of .got2 or .opd in a relocatable object, if
-// such a section exists.
+// Stash away the index of .got2, .opd, .rela.toc, and .toc in a
+// relocatable object, if such sections exists.
 
 template<int size, bool big_endian>
 bool
@@ -1795,6 +1907,18 @@ Powerpc_relobj<size, big_endian>::do_find_special_sections(
                       this->name().c_str(), this->abiversion());
        }
     }
+  if (size == 64)
+    {
+      s = this->template find_shdr<size, big_endian>(pshdrs, ".rela.toc",
+                                                    names, names_size, NULL);
+      if (s != NULL)
+       {
+         unsigned int ndx = (s - pshdrs) / elfcpp::Elf_sizes<size>::shdr_size;
+         this->relatoc_ = ndx;
+         typename elfcpp::Shdr<size, big_endian> shdr(s);
+         this->toc_ = this->adjust_shndx(shdr.get_sh_info());
+       }
+    }
   return Sized_relobj_file<size, big_endian>::do_find_special_sections(sd);
 }
 
@@ -1809,10 +1933,8 @@ Powerpc_relobj<size, big_endian>::scan_opd_relocs(
 {
   if (size == 64)
     {
-      typedef typename Reloc_types<elfcpp::SHT_RELA, size, big_endian>::Reloc
-       Reltype;
-      const int reloc_size
-       = Reloc_types<elfcpp::SHT_RELA, size, big_endian>::reloc_size;
+      typedef typename elfcpp::Rela<size, big_endian> Reltype;
+      const int reloc_size = elfcpp::Elf_sizes<size>::rela_size;
       const int sym_size = elfcpp::Elf_sizes<size>::sym_size;
       Address expected_off = 0;
       bool regular = true;
@@ -1877,6 +1999,60 @@ Powerpc_relobj<size, big_endian>::scan_opd_relocs(
     }
 }
 
+// Returns true if a code sequence loading the TOC entry at VALUE
+// relative to the TOC pointer can be converted into code calculating
+// a TOC pointer relative offset.
+// If so, the TOC pointer relative offset is stored to VALUE.
+
+template<int size, bool big_endian>
+bool
+Powerpc_relobj<size, big_endian>::make_toc_relative(
+    Target_powerpc<size, big_endian>* target,
+    Address* value)
+{
+  if (size != 64)
+    return false;
+
+  // With -mcmodel=medium code it is quite possible to have
+  // toc-relative relocs referring to objects outside the TOC.
+  // Don't try to look at a non-existent TOC.
+  if (this->toc_shndx() == 0)
+    return false;
+
+  // Convert VALUE back to an address by adding got_base (see below),
+  // then to an offset in the TOC by subtracting the TOC output
+  // section address and the TOC output offset.  Since this TOC output
+  // section and the got output section are one and the same, we can
+  // omit adding and subtracting the output section address.
+  Address off = (*value + this->toc_base_offset()
+                - this->output_section_offset(this->toc_shndx()));
+  // Is this offset in the TOC?  -mcmodel=medium code may be using
+  // TOC relative access to variables outside the TOC.  Those of
+  // course can't be optimized.  We also don't try to optimize code
+  // that is using a different object's TOC.
+  if (off >= this->section_size(this->toc_shndx()))
+    return false;
+
+  if (this->no_toc_opt(off))
+    return false;
+
+  section_size_type vlen;
+  unsigned char* view = this->get_output_view(this->toc_shndx(), &vlen);
+  Address addr = elfcpp::Swap<size, big_endian>::readval(view + off);
+  // The TOC pointer
+  Address got_base = (target->got_section()->output_section()->address()
+                     + this->toc_base_offset());
+  addr -= got_base;
+  if (addr + (uint64_t) 0x80008000 >= (uint64_t) 1 << 32)
+    return false;
+
+  *value = addr;
+  return true;
+}
+
+// Perform the Sized_relobj_file method, then set up opd info from
+// .opd relocs.
+
 template<int size, bool big_endian>
 void
 Powerpc_relobj<size, big_endian>::do_read_relocs(Read_relocs_data* rd)
@@ -2072,6 +2248,31 @@ Powerpc_dynobj<size, big_endian>::do_read_symbols(Read_symbols_data* sd)
     }
 }
 
+// Relocate sections.
+
+template<int size, bool big_endian>
+void
+Powerpc_relobj<size, big_endian>::do_relocate_sections(
+    const Symbol_table* symtab, const Layout* layout,
+    const unsigned char* pshdrs, Output_file* of,
+    typename Sized_relobj_file<size, big_endian>::Views* pviews)
+{
+  unsigned int start = 1;
+  if (size == 64
+      && this->relatoc_ != 0
+      && !parameters->options().relocatable())
+    {
+      // Relocate .toc first.
+      this->relocate_section_range(symtab, layout, pshdrs, of, pviews,
+                                  this->relatoc_, this->relatoc_);
+      this->relocate_section_range(symtab, layout, pshdrs, of, pviews,
+                                  1, this->relatoc_ - 1);
+      start = this->relatoc_ + 1;
+    }
+  this->relocate_section_range(symtab, layout, pshdrs, of, pviews,
+                              start, this->shnum() - 1);
+}
+
 // Set up some symbols.
 
 template<int size, bool big_endian>
@@ -2432,14 +2633,13 @@ class Stub_control
  public:
   // Determine the stub group size.  The group size is the absolute
   // value of the parameter --stub-group-size.  If --stub-group-size
-  // is passed a negative value, we restrict stubs to be always before
+  // is passed a negative value, we restrict stubs to be always after
   // the stubbed branches.
-  Stub_control(int32_t size, bool no_size_errors)
-    : state_(NO_GROUP), stub_group_size_(abs(size)),
-      stub14_group_size_(abs(size) >> 10),
-      stubs_always_before_branch_(size < 0),
-      suppress_size_errors_(no_size_errors),
-      group_end_addr_(0), owner_(NULL), output_section_(NULL)
+  Stub_control(int32_t size, bool no_size_errors, bool multi_os)
+    : stub_group_size_(abs(size)), stubs_always_after_branch_(size < 0),
+      suppress_size_errors_(no_size_errors), multi_os_(multi_os),
+      state_(NO_GROUP), group_size_(0), group_start_addr_(0),
+      owner_(NULL), output_section_(NULL)
   {
   }
 
@@ -2469,31 +2669,40 @@ class Stub_control
  private:
   typedef enum
   {
+    // Initial state.
     NO_GROUP,
+    // Adding group sections before the stubs.
     FINDING_STUB_SECTION,
+    // Adding group sections after the stubs.
     HAS_STUB_SECTION
   } State;
 
-  State state_;
   uint32_t stub_group_size_;
-  uint32_t stub14_group_size_;
-  bool stubs_always_before_branch_;
+  bool stubs_always_after_branch_;
   bool suppress_size_errors_;
-  uint64_t group_end_addr_;
+  // True if a stub group can serve multiple output sections.
+  bool multi_os_;
+  State state_;
+  // Current max size of group.  Starts at stub_group_size_ but is
+  // reduced to stub_group_size_/1024 on seeing a section with
+  // external conditional branches.
+  uint32_t group_size_;
+  uint64_t group_start_addr_;
+  // owner_ and output_section_ specify the section to which stubs are
+  // attached.  The stubs are placed at the end of this section.
   const Output_section::Input_section* owner_;
   Output_section* output_section_;
 };
 
 // Return true iff input section can be handled by current stub
-// group.
+// group.  Sections are presented to this function in order,
+// so the first section is the head of the group.
 
 bool
 Stub_control::can_add_to_stub_group(Output_section* o,
                                    const Output_section::Input_section* i,
                                    bool has14)
 {
-  uint32_t group_size
-    = has14 ? this->stub14_group_size_ : this->stub_group_size_;
   bool whole_sec = o->order() == ORDER_INIT || o->order() == ORDER_FINI;
   uint64_t this_size;
   uint64_t start_addr = o->address();
@@ -2507,46 +2716,88 @@ Stub_control::can_add_to_stub_group(Output_section* o,
       start_addr += i->relobj()->output_section_offset(i->shndx());
       this_size = i->data_size();
     }
+
   uint64_t end_addr = start_addr + this_size;
-  bool toobig = this_size > group_size;
+  uint32_t group_size = this->stub_group_size_;
+  if (has14)
+    this->group_size_ = group_size = group_size >> 10;
 
-  if (toobig && !this->suppress_size_errors_)
+  if (this_size > group_size && !this->suppress_size_errors_)
     gold_warning(_("%s:%s exceeds group size"),
                 i->relobj()->name().c_str(),
                 i->relobj()->section_name(i->shndx()).c_str());
 
-  if (this->state_ != HAS_STUB_SECTION
-      && (!whole_sec || this->output_section_ != o)
-      && (this->state_ == NO_GROUP
-         || this->group_end_addr_ - end_addr < group_size))
-    {
-      this->owner_ = i;
-      this->output_section_ = o;
-    }
+  gold_debug(DEBUG_TARGET, "maybe add%s %s:%s size=%#llx total=%#llx",
+            has14 ? " 14bit" : "",
+            i->relobj()->name().c_str(),
+            i->relobj()->section_name(i->shndx()).c_str(),
+            (long long) this_size,
+            (this->state_ == NO_GROUP
+             ? this_size
+             : (long long) end_addr - this->group_start_addr_));
 
   if (this->state_ == NO_GROUP)
     {
+      // Only here on very first use of Stub_control
+      this->owner_ = i;
+      this->output_section_ = o;
       this->state_ = FINDING_STUB_SECTION;
-      this->group_end_addr_ = end_addr;
+      this->group_size_ = group_size;
+      this->group_start_addr_ = start_addr;
+      return true;
     }
-  else if (this->group_end_addr_ - start_addr < group_size)
+  else if (!this->multi_os_ && this->output_section_ != o)
     ;
-  // Adding this section would make the group larger than GROUP_SIZE.
-  else if (this->state_ == FINDING_STUB_SECTION
-          && !this->stubs_always_before_branch_
-          && !toobig)
+  else if (this->state_ == HAS_STUB_SECTION)
     {
-      // But wait, there's more!  Input sections up to GROUP_SIZE
-      // bytes before the stub table can be handled by it too.
-      this->state_ = HAS_STUB_SECTION;
-      this->group_end_addr_ = end_addr;
+      // Can we add this section, which is after the stubs, to the
+      // group?
+      if (end_addr - this->group_start_addr_ <= this->group_size_)
+       return true;
     }
-  else
+  else if (this->state_ == FINDING_STUB_SECTION)
     {
-      this->state_ = NO_GROUP;
-      return false;
+      if ((whole_sec && this->output_section_ == o)
+         || end_addr - this->group_start_addr_ <= this->group_size_)
+       {
+         // Stubs are added at the end of "owner_".
+         this->owner_ = i;
+         this->output_section_ = o;
+         return true;
+       }
+      // The group before the stubs has reached maximum size.
+      // Now see about adding sections after the stubs to the
+      // group.  If the current section has a 14-bit branch and
+      // the group before the stubs exceeds group_size_ (because
+      // they didn't have 14-bit branches), don't add sections
+      // after the stubs:  The size of stubs for such a large
+      // group may exceed the reach of a 14-bit branch.
+      if (!this->stubs_always_after_branch_
+         && this_size <= this->group_size_
+         && start_addr - this->group_start_addr_ <= this->group_size_)
+       {
+         gold_debug(DEBUG_TARGET, "adding after stubs");
+         this->state_ = HAS_STUB_SECTION;
+         this->group_start_addr_ = start_addr;
+         return true;
+       }
     }
-  return true;
+  else
+    gold_unreachable();
+
+  gold_debug(DEBUG_TARGET,
+            !this->multi_os_ && this->output_section_ != o
+            ? "nope, new output section\n"
+            : "nope, didn't fit\n");
+
+  // The section fails to fit in the current group.  Set up a few
+  // things for the next group.  owner_ and output_section_ will be
+  // set later after we've retrieved those values for the current
+  // group.
+  this->state_ = FINDING_STUB_SECTION;
+  this->group_size_ = group_size;
+  this->group_start_addr_ = start_addr;
+  return false;
 }
 
 // Look over all the input sections, deciding where to place stubs.
@@ -2557,7 +2808,8 @@ Target_powerpc<size, big_endian>::group_sections(Layout* layout,
                                                 const Task*,
                                                 bool no_size_errors)
 {
-  Stub_control stub_control(this->stub_group_size_, no_size_errors);
+  Stub_control stub_control(this->stub_group_size_, no_size_errors,
+                           parameters->options().stub_group_multi());
 
   // Group input sections and insert stub table
   Stub_table_owner* table_owner = NULL;
@@ -2565,14 +2817,14 @@ Target_powerpc<size, big_endian>::group_sections(Layout* layout,
   Layout::Section_list section_list;
   layout->get_executable_sections(&section_list);
   std::stable_sort(section_list.begin(), section_list.end(), Sort_sections());
-  for (Layout::Section_list::reverse_iterator o = section_list.rbegin();
-       o != section_list.rend();
+  for (Layout::Section_list::iterator o = section_list.begin();
+       o != section_list.end();
        ++o)
     {
       typedef Output_section::Input_section_list Input_section_list;
-      for (Input_section_list::const_reverse_iterator i
-            = (*o)->input_sections().rbegin();
-          i != (*o)->input_sections().rend();
+      for (Input_section_list::const_iterator i
+            = (*o)->input_sections().begin();
+          i != (*o)->input_sections().end();
           ++i)
        {
          if (i->is_input_section()
@@ -2599,26 +2851,8 @@ Target_powerpc<size, big_endian>::group_sections(Layout* layout,
     }
   if (table_owner != NULL)
     {
-      const Output_section::Input_section* i = stub_control.owner();
-
-      if (tables.size() >= 2 && tables[tables.size() - 2]->owner == i)
-       {
-         // Corner case.  A new stub group was made for the first
-         // section (last one looked at here) for some reason, but
-         // the first section is already being used as the owner for
-         // a stub table for following sections.  Force it into that
-         // stub group.
-         tables.pop_back();
-         delete table_owner;
-         Powerpc_relobj<size, big_endian>* ppcobj = static_cast
-           <Powerpc_relobj<size, big_endian>*>(i->relobj());
-         ppcobj->set_stub_table(i->shndx(), tables.size() - 1);
-       }
-      else
-       {
-         table_owner->output_section = stub_control.output_section();
-         table_owner->owner = i;
-       }
+      table_owner->output_section = stub_control.output_section();
+      table_owner->owner = stub_control.owner();;
     }
   for (typename std::vector<Stub_table_owner*>::iterator t = tables.begin();
        t != tables.end();
@@ -2629,7 +2863,8 @@ Target_powerpc<size, big_endian>::group_sections(Layout* layout,
       if ((*t)->owner->is_input_section())
        stub_table = new Stub_table<size, big_endian>(this,
                                                      (*t)->output_section,
-                                                     (*t)->owner);
+                                                     (*t)->owner,
+                                                     this->stub_tables_.size());
       else if ((*t)->owner->is_relaxed_input_section())
        stub_table = static_cast<Stub_table<size, big_endian>*>(
                        (*t)->owner->relaxed_input_section());
@@ -2670,6 +2905,8 @@ Target_powerpc<size, big_endian>::Branch_info::make_stub(
   Target_powerpc<size, big_endian>* target =
     static_cast<Target_powerpc<size, big_endian>*>(
       parameters->sized_target<size, big_endian>());
+  bool ok = true;
+
   if (gsym != NULL
       ? gsym->use_plt_offset(Scan::get_reference_flags(this->r_type_, target))
       : this->object_->local_has_plt_offset(this->r_sym_))
@@ -2695,13 +2932,13 @@ Target_powerpc<size, big_endian>::Branch_info::make_stub(
            from += (this->object_->output_section(this->shndx_)->address()
                     + this->offset_);
          if (gsym != NULL)
-           return stub_table->add_plt_call_entry(from,
-                                                 this->object_, gsym,
-                                                 this->r_type_, this->addend_);
+           ok = stub_table->add_plt_call_entry(from,
+                                               this->object_, gsym,
+                                               this->r_type_, this->addend_);
          else
-           return stub_table->add_plt_call_entry(from,
-                                                 this->object_, this->r_sym_,
-                                                 this->r_type_, this->addend_);
+           ok = stub_table->add_plt_call_entry(from,
+                                               this->object_, this->r_sym_,
+                                               this->r_type_, this->addend_);
        }
     }
   else
@@ -2749,6 +2986,8 @@ Target_powerpc<size, big_endian>::Branch_info::make_stub(
          const Symbol_value<size>* psymval
            = this->object_->local_symbol(this->r_sym_);
          Symbol_value<size> symval;
+         if (psymval->is_section_symbol())
+           symval.set_is_section_symbol();
          typedef Sized_relobj_file<size, big_endian> ObjType;
          typename ObjType::Compute_final_local_value_status status
            = this->object_->compute_final_local_value(this->r_sym_, psymval,
@@ -2786,12 +3025,22 @@ Target_powerpc<size, big_endian>::Branch_info::make_stub(
                           && gsym != NULL
                           && gsym->source() == Symbol::IN_OUTPUT_DATA
                           && gsym->output_data() == target->savres_section());
-         return stub_table->add_long_branch_entry(this->object_,
-                                                  this->r_type_,
-                                                  from, to, save_res);
+         ok = stub_table->add_long_branch_entry(this->object_,
+                                                this->r_type_,
+                                                from, to, save_res);
        }
     }
-  return true;
+  if (!ok)
+    gold_debug(DEBUG_TARGET,
+              "branch at %s:%s+%#lx\n"
+              "can't reach stub attached to %s:%s",
+              this->object_->name().c_str(),
+              this->object_->section_name(this->shndx_).c_str(),
+              (unsigned long) this->offset_,
+              stub_table->relobj()->name().c_str(),
+              stub_table->relobj()->section_name(stub_table->shndx()).c_str());
+
+  return ok;
 }
 
 // Relaxation hook.  This is where we do stub generation.
@@ -2884,7 +3133,7 @@ Target_powerpc<size, big_endian>::do_relax(int pass,
        }
       this->stub_tables_.clear();
       this->stub_group_size_ = this->stub_group_size_ / 4 * 3;
-      gold_info(_("%s: stub group size is too large; retrying with %d"),
+      gold_info(_("%s: stub group size is too large; retrying with %#x"),
                program_name, this->stub_group_size_);
       this->group_sections(layout, task, true);
     }
@@ -2979,7 +3228,13 @@ Target_powerpc<size, big_endian>::do_relax(int pass,
              Stub_table<size, big_endian>* stub_table
                = static_cast<Stub_table<size, big_endian>*>(
                    i->relaxed_input_section());
-             off += stub_table->set_address_and_size(os, off);
+             Address stub_table_size = stub_table->set_address_and_size(os, off);
+             off += stub_table_size;
+             // After a few iterations, set current stub table size
+             // as min size threshold, so later stub tables can only
+             // grow in size.
+             if (pass >= 4)
+               stub_table->set_min_size_threshold(stub_table_size);
            }
          else
            off += i->data_size();
@@ -3005,6 +3260,36 @@ Target_powerpc<size, big_endian>::do_relax(int pass,
        }
       this->brlt_section_->finalize_brlt_sizes();
     }
+
+  if (!again
+      && (parameters->options().user_set_emit_stub_syms()
+         ? parameters->options().emit_stub_syms()
+         : (size == 64
+            || parameters->options().output_is_position_independent()
+            || parameters->options().emit_relocs())))
+    {
+      for (typename Stub_tables::iterator p = this->stub_tables_.begin();
+          p != this->stub_tables_.end();
+          ++p)
+       (*p)->define_stub_syms(symtab);
+
+      if (this->glink_ != NULL)
+       {
+         int stub_size = this->glink_->pltresolve_size;
+         Address value = -stub_size;
+         if (size == 64)
+           {
+             value = 8;
+             stub_size -= 8;
+           }
+         this->define_local(symtab, "__glink_PLTresolve",
+                            this->glink_, value, stub_size);
+
+         if (size != 64)
+           this->define_local(symtab, "__glink", this->glink_, 0, 0);
+       }
+    }
+
   return again;
 }
 
@@ -3363,6 +3648,9 @@ Target_powerpc<size, big_endian>::make_plt_section(Symbol_table* symtab,
                                       ? ORDER_SMALL_DATA
                                       : ORDER_SMALL_BSS),
                                      false);
+
+      Output_section* rela_plt_os = plt_rel->output_section();
+      rela_plt_os->set_info_section(this->plt_->output_section());
     }
 }
 
@@ -3378,11 +3666,13 @@ Target_powerpc<size, big_endian>::make_iplt_section(Symbol_table* symtab,
       this->make_plt_section(symtab, layout);
 
       Reloc_section* iplt_rel = new Reloc_section(false);
-      this->rela_dyn_->output_section()->add_output_section_data(iplt_rel);
+      if (this->rela_dyn_->output_section())
+       this->rela_dyn_->output_section()->add_output_section_data(iplt_rel);
       this->iplt_
        = new Output_data_plt_powerpc<size, big_endian>(this, iplt_rel,
                                                        "** IPLT");
-      this->plt_->output_section()->add_output_section_data(this->iplt_);
+      if (this->plt_->output_section())
+       this->plt_->output_section()->add_output_section_data(this->iplt_);
     }
 }
 
@@ -3433,8 +3723,7 @@ class Output_data_brlt_powerpc : public Output_section_data_build
     os->set_section_offsets_need_adjustment();
     if (this->rel_ != NULL)
       {
-       unsigned int reloc_size
-         = Reloc_types<elfcpp::SHT_RELA, size, big_endian>::reloc_size;
+       const unsigned int reloc_size = elfcpp::Elf_sizes<size>::rela_size;
        this->rel_->reset_address_and_file_offset();
        this->rel_->set_current_data_size(num_branches * reloc_size);
        this->rel_->finalize_data_size();
@@ -3479,14 +3768,16 @@ Target_powerpc<size, big_endian>::make_brlt_section(Layout* layout)
        {
          // When PIC we can't fill in .branch_lt (like .plt it can be
          // a bss style section) but must initialise at runtime via
-         // dynamic relocats.
+         // dynamic relocations.
          this->rela_dyn_section(layout);
          brlt_rel = new Reloc_section(false);
-         this->rela_dyn_->output_section()->add_output_section_data(brlt_rel);
+         if (this->rela_dyn_->output_section())
+           this->rela_dyn_->output_section()
+             ->add_output_section_data(brlt_rel);
        }
       this->brlt_section_
        = new Output_data_brlt_powerpc<size, big_endian>(this, brlt_rel);
-      if (this->plt_ && is_pic)
+      if (this->plt_ && is_pic && this->plt_->output_section())
        this->plt_->output_section()
          ->add_output_section_data(this->brlt_section_);
       else
@@ -3624,15 +3915,16 @@ class Stub_table : public Output_relaxed_input_section
 
   Stub_table(Target_powerpc<size, big_endian>* targ,
             Output_section* output_section,
-            const Output_section::Input_section* owner)
+            const Output_section::Input_section* owner,
+            uint32_t id)
     : Output_relaxed_input_section(owner->relobj(), owner->shndx(),
                                   owner->relobj()
                                   ->section_addralign(owner->shndx())),
       targ_(targ), plt_call_stubs_(), long_branch_stubs_(),
       orig_data_size_(owner->current_data_size()),
       plt_size_(0), last_plt_size_(0),
-      branch_size_(0), last_branch_size_(0), eh_frame_added_(false),
-      need_save_res_(false)
+      branch_size_(0), last_branch_size_(0), min_size_threshold_(0),
+      eh_frame_added_(false), need_save_res_(false), uniq_(id)
   {
     this->set_output_section(output_section);
 
@@ -3723,6 +4015,11 @@ class Stub_table : public Output_relaxed_input_section
       off = align_address(off, this->stub_align());
     // Include original section size and alignment padding in size
     my_size += off - start_off;
+    // Ensure new size is always larger than min size
+    // threshold. Alignment requirement is included in "my_size", so
+    // increase "my_size" does not invalidate alignment.
+    if (my_size < this->min_size_threshold_)
+      my_size = this->min_size_threshold_;
     this->reset_address_and_file_offset();
     this->set_current_data_size(my_size);
     this->set_address_and_file_offset(os->address() + start_off,
@@ -3748,6 +4045,13 @@ class Stub_table : public Output_relaxed_input_section
   plt_size() const
   { return this->plt_size_; }
 
+  void
+  set_min_size_threshold(Address min_size)
+  { this->min_size_threshold_ = min_size; }
+
+  void
+  define_stub_syms(Symbol_table*);
+
   bool
   size_update()
   {
@@ -3817,6 +4121,10 @@ class Stub_table : public Output_relaxed_input_section
   class Plt_stub_ent_hash;
   typedef Unordered_map<Plt_stub_ent, unsigned int,
                        Plt_stub_ent_hash> Plt_stub_entries;
+  class Branch_stub_ent;
+  class Branch_stub_ent_hash;
+  typedef Unordered_map<Branch_stub_ent, unsigned int,
+                       Branch_stub_ent_hash> Branch_stub_entries;
 
   // Alignment of stub section.
   unsigned int
@@ -3885,11 +4193,10 @@ class Stub_table : public Output_relaxed_input_section
 
   // Return long branch stub size.
   unsigned int
-  branch_stub_size(Address to)
+  branch_stub_size(typename Branch_stub_entries::const_iterator p)
   {
-    Address loc
-      = this->stub_address() + this->last_plt_size_ + this->branch_size_;
-    if (to - loc + (1 << 25) < 2 << 25)
+    Address loc = this->stub_address() + this->last_plt_size_ + p->second;
+    if (p->first.dest_ - loc + (1 << 25) < 2 << 25)
       return 4;
     if (size == 64 || !parameters->options().output_is_position_independent())
       return 16;
@@ -3955,6 +4262,7 @@ class Stub_table : public Output_relaxed_input_section
     const Sized_relobj_file<size, big_endian>* object_;
     typename elfcpp::Elf_types<size>::Elf_Addr addend_;
     unsigned int locsym_;
+    unsigned int indx_;
   };
 
   class Plt_stub_ent_hash
@@ -4005,18 +4313,25 @@ class Stub_table : public Output_relaxed_input_section
   // Map sym/object/addend to stub offset.
   Plt_stub_entries plt_call_stubs_;
   // Map destination address to stub offset.
-  typedef Unordered_map<Branch_stub_ent, unsigned int,
-                       Branch_stub_ent_hash> Branch_stub_entries;
   Branch_stub_entries long_branch_stubs_;
   // size of input section
   section_size_type orig_data_size_;
   // size of stubs
   section_size_type plt_size_, last_plt_size_, branch_size_, last_branch_size_;
+  // Some rare cases cause (PR/20529) fluctuation in stub table
+  // size, which leads to an endless relax loop. This is to be fixed
+  // by, after the first few iterations, allowing only increase of
+  // stub table size. This variable sets the minimal possible size of
+  // a stub table, it is zero for the first few iterations, then
+  // increases monotonically.
+  Address min_size_threshold_;
   // Whether .eh_frame info has been created for this stub section.
   bool eh_frame_added_;
   // Set if this stub group needs a copy of out-of-line register
   // save/restore functions.
   bool need_save_res_;
+  // Per stub table unique identifier.
+  uint32_t uniq_;
 };
 
 // Add a plt call stub, if we do not already have one for this
@@ -4033,6 +4348,7 @@ Stub_table<size, big_endian>::add_plt_call_entry(
 {
   Plt_stub_ent ent(object, gsym, r_type, addend);
   unsigned int off = this->plt_size_;
+  ent.indx_ = this->plt_call_stubs_.size();
   std::pair<typename Plt_stub_entries::iterator, bool> p
     = this->plt_call_stubs_.insert(std::make_pair(ent, off));
   if (p.second)
@@ -4051,6 +4367,7 @@ Stub_table<size, big_endian>::add_plt_call_entry(
 {
   Plt_stub_ent ent(object, locsym_index, r_type, addend);
   unsigned int off = this->plt_size_;
+  ent.indx_ = this->plt_call_stubs_.size();
   std::pair<typename Plt_stub_entries::iterator, bool> p
     = this->plt_call_stubs_.insert(std::make_pair(ent, off));
   if (p.second)
@@ -4120,13 +4437,15 @@ Stub_table<size, big_endian>::add_long_branch_entry(
 {
   Branch_stub_ent ent(object, to, save_res);
   Address off = this->branch_size_;
-  if (this->long_branch_stubs_.insert(std::make_pair(ent, off)).second)
+  std::pair<typename Branch_stub_entries::iterator, bool> p
+    = this->long_branch_stubs_.insert(std::make_pair(ent, off));
+  if (p.second)
     {
       if (save_res)
        this->need_save_res_ = true;
       else
        {
-         unsigned int stub_size = this->branch_stub_size(to);
+         unsigned int stub_size = this->branch_stub_size(p.first);
          this->branch_size_ = off + stub_size;
          if (size == 64 && stub_size != 4)
            this->targ_->add_branch_lookup_table(to);
@@ -4307,6 +4626,73 @@ Output_data_glink<size, big_endian>::set_final_data_size()
   this->set_data_size(total);
 }
 
+// Define symbols on stubs, identifying the stub.
+
+template<int size, bool big_endian>
+void
+Stub_table<size, big_endian>::define_stub_syms(Symbol_table* symtab)
+{
+  if (!this->plt_call_stubs_.empty())
+    {
+      // The key for the plt call stub hash table includes addresses,
+      // therefore traversal order depends on those addresses, which
+      // can change between runs if gold is a PIE.  Unfortunately the
+      // output .symtab ordering depends on the order in which symbols
+      // are added to the linker symtab.  We want reproducible output
+      // so must sort the call stub symbols.
+      typedef typename Plt_stub_entries::const_iterator plt_iter;
+      std::vector<plt_iter> sorted;
+      sorted.resize(this->plt_call_stubs_.size());
+
+      for (plt_iter cs = this->plt_call_stubs_.begin();
+          cs != this->plt_call_stubs_.end();
+          ++cs)
+       sorted[cs->first.indx_] = cs;
+
+      for (unsigned int i = 0; i < this->plt_call_stubs_.size(); ++i)
+       {
+         plt_iter cs = sorted[i];
+         char add[10];
+         add[0] = 0;
+         if (cs->first.addend_ != 0)
+           sprintf(add, "+%x", static_cast<uint32_t>(cs->first.addend_));
+         char localname[18];
+         const char *symname;
+         if (cs->first.sym_ == NULL)
+           {
+             const Powerpc_relobj<size, big_endian>* ppcobj = static_cast
+               <const Powerpc_relobj<size, big_endian>*>(cs->first.object_);
+             sprintf(localname, "%x:%x", ppcobj->uniq(), cs->first.locsym_);
+             symname = localname;
+           }
+         else
+           symname = cs->first.sym_->name();
+         char* name = new char[8 + 10 + strlen(symname) + strlen(add) + 1];
+         sprintf(name, "%08x.plt_call.%s%s", this->uniq_, symname, add);
+         Address value = this->stub_address() - this->address() + cs->second;
+         unsigned int stub_size = this->plt_call_size(cs);
+         this->targ_->define_local(symtab, name, this, value, stub_size);
+       }
+    }
+
+  typedef typename Branch_stub_entries::const_iterator branch_iter;
+  for (branch_iter bs = this->long_branch_stubs_.begin();
+       bs != this->long_branch_stubs_.end();
+       ++bs)
+    {
+      if (bs->first.save_res_)
+       continue;
+
+      char* name = new char[8 + 13 + 16 + 1];
+      sprintf(name, "%08x.long_branch.%llx", this->uniq_,
+             static_cast<unsigned long long>(bs->first.dest_));
+      Address value = (this->stub_address() - this->address()
+                      + this->plt_size_ + bs->second);
+      unsigned int stub_size = this->branch_stub_size(bs);
+      this->targ_->define_local(symtab, name, this, value, stub_size);
+    }
+}
+
 // Write out plt and long branch stub code.
 
 template<int size, bool big_endian>
@@ -5542,6 +5928,45 @@ Target_powerpc<size, big_endian>::Scan::reloc_needs_plt_for_ifunc(
   return false;
 }
 
+// Return TRUE iff INSN is one we expect on a _LO variety toc/got
+// reloc.
+
+static bool
+ok_lo_toc_insn(uint32_t insn, unsigned int r_type)
+{
+  return ((insn & (0x3f << 26)) == 12u << 26 /* addic */
+         || (insn & (0x3f << 26)) == 14u << 26 /* addi */
+         || (insn & (0x3f << 26)) == 32u << 26 /* lwz */
+         || (insn & (0x3f << 26)) == 34u << 26 /* lbz */
+         || (insn & (0x3f << 26)) == 36u << 26 /* stw */
+         || (insn & (0x3f << 26)) == 38u << 26 /* stb */
+         || (insn & (0x3f << 26)) == 40u << 26 /* lhz */
+         || (insn & (0x3f << 26)) == 42u << 26 /* lha */
+         || (insn & (0x3f << 26)) == 44u << 26 /* sth */
+         || (insn & (0x3f << 26)) == 46u << 26 /* lmw */
+         || (insn & (0x3f << 26)) == 47u << 26 /* stmw */
+         || (insn & (0x3f << 26)) == 48u << 26 /* lfs */
+         || (insn & (0x3f << 26)) == 50u << 26 /* lfd */
+         || (insn & (0x3f << 26)) == 52u << 26 /* stfs */
+         || (insn & (0x3f << 26)) == 54u << 26 /* stfd */
+         || (insn & (0x3f << 26)) == 56u << 26 /* lq,lfq */
+         || ((insn & (0x3f << 26)) == 57u << 26 /* lxsd,lxssp,lfdp */
+             /* Exclude lfqu by testing reloc.  If relocs are ever
+                defined for the reduced D field in psq_lu then those
+                will need testing too.  */
+             && r_type != elfcpp::R_PPC64_TOC16_LO
+             && r_type != elfcpp::R_POWERPC_GOT16_LO)
+         || ((insn & (0x3f << 26)) == 58u << 26 /* ld,lwa */
+             && (insn & 1) == 0)
+         || (insn & (0x3f << 26)) == 60u << 26 /* stfq */
+         || ((insn & (0x3f << 26)) == 61u << 26 /* lxv,stx{v,sd,ssp},stfdp */
+             /* Exclude stfqu.  psq_stu as above for psq_lu.  */
+             && r_type != elfcpp::R_PPC64_TOC16_LO
+             && r_type != elfcpp::R_POWERPC_GOT16_LO)
+         || ((insn & (0x3f << 26)) == 62u << 26 /* std,stq */
+             && (insn & 1) == 0));
+}
+
 // Scan a relocation for a local symbol.
 
 template<int size, bool big_endian>
@@ -5705,9 +6130,11 @@ Target_powerpc<size, big_endian>::Scan::local(
     case elfcpp::R_POWERPC_REL14_BRTAKEN:
     case elfcpp::R_POWERPC_REL14_BRNTAKEN:
       if (!is_ifunc)
-       target->push_branch(ppc_object, data_shndx, reloc.get_r_offset(),
-                           r_type, elfcpp::elf_r_sym<size>(reloc.get_r_info()),
-                           reloc.get_r_addend());
+       {
+         unsigned int r_sym = elfcpp::elf_r_sym<size>(reloc.get_r_info());
+         target->push_branch(ppc_object, data_shndx, reloc.get_r_offset(),
+                             r_type, r_sym, reloc.get_r_addend());
+       }
       break;
 
     case elfcpp::R_PPC64_REL64:
@@ -5895,6 +6322,150 @@ Target_powerpc<size, big_endian>::Scan::local(
       break;
     }
 
+  if (size == 64
+      && parameters->options().toc_optimize())
+    {
+      if (data_shndx == ppc_object->toc_shndx())
+       {
+         bool ok = true;
+         if (r_type != elfcpp::R_PPC64_ADDR64
+             || (is_ifunc && target->abiversion() < 2))
+           ok = false;
+         else if (parameters->options().output_is_position_independent())
+           {
+             if (is_ifunc)
+               ok = false;
+             else
+               {
+                 unsigned int shndx = lsym.get_st_shndx();
+                 if (shndx >= elfcpp::SHN_LORESERVE
+                     && shndx != elfcpp::SHN_XINDEX)
+                   ok = false;
+               }
+           }
+         if (!ok)
+           ppc_object->set_no_toc_opt(reloc.get_r_offset());
+       }
+
+      enum {no_check, check_lo, check_ha} insn_check;
+      switch (r_type)
+       {
+       default:
+         insn_check = no_check;
+         break;
+
+       case elfcpp::R_POWERPC_GOT_TLSLD16_HA:
+       case elfcpp::R_POWERPC_GOT_TLSGD16_HA:
+       case elfcpp::R_POWERPC_GOT_TPREL16_HA:
+       case elfcpp::R_POWERPC_GOT_DTPREL16_HA:
+       case elfcpp::R_POWERPC_GOT16_HA:
+       case elfcpp::R_PPC64_TOC16_HA:
+         insn_check = check_ha;
+         break;
+
+       case elfcpp::R_POWERPC_GOT_TLSLD16_LO:
+       case elfcpp::R_POWERPC_GOT_TLSGD16_LO:
+       case elfcpp::R_POWERPC_GOT_TPREL16_LO:
+       case elfcpp::R_POWERPC_GOT_DTPREL16_LO:
+       case elfcpp::R_POWERPC_GOT16_LO:
+       case elfcpp::R_PPC64_GOT16_LO_DS:
+       case elfcpp::R_PPC64_TOC16_LO:
+       case elfcpp::R_PPC64_TOC16_LO_DS:
+         insn_check = check_lo;
+         break;
+       }
+
+      section_size_type slen;
+      const unsigned char* view = NULL;
+      if (insn_check != no_check)
+       {
+         view = ppc_object->section_contents(data_shndx, &slen, false);
+         section_size_type off =
+           convert_to_section_size_type(reloc.get_r_offset()) & -4;
+         if (off < slen)
+           {
+             uint32_t insn = elfcpp::Swap<32, big_endian>::readval(view + off);
+             if (insn_check == check_lo
+                 ? !ok_lo_toc_insn(insn, r_type)
+                 : ((insn & ((0x3f << 26) | 0x1f << 16))
+                    != ((15u << 26) | (2 << 16)) /* addis rt,2,imm */))
+               {
+                 ppc_object->set_no_toc_opt();
+                 gold_warning(_("%s: toc optimization is not supported "
+                                "for %#08x instruction"),
+                              ppc_object->name().c_str(), insn);
+               }
+           }
+       }
+
+      switch (r_type)
+       {
+       default:
+         break;
+       case elfcpp::R_PPC64_TOC16:
+       case elfcpp::R_PPC64_TOC16_LO:
+       case elfcpp::R_PPC64_TOC16_HI:
+       case elfcpp::R_PPC64_TOC16_HA:
+       case elfcpp::R_PPC64_TOC16_DS:
+       case elfcpp::R_PPC64_TOC16_LO_DS:
+         unsigned int shndx = lsym.get_st_shndx();
+         unsigned int r_sym = elfcpp::elf_r_sym<size>(reloc.get_r_info());
+         bool is_ordinary;
+         shndx = ppc_object->adjust_sym_shndx(r_sym, shndx, &is_ordinary);
+         if (is_ordinary && shndx == ppc_object->toc_shndx())
+           {
+             Address dst_off = lsym.get_st_value() + reloc.get_r_offset();
+             if (dst_off < ppc_object->section_size(shndx))
+               {
+                 bool ok = false;
+                 if (r_type == elfcpp::R_PPC64_TOC16_HA)
+                   ok = true;
+                 else if (r_type == elfcpp::R_PPC64_TOC16_LO_DS)
+                   {
+                     // Need to check that the insn is a ld
+                     if (!view)
+                       view = ppc_object->section_contents(data_shndx,
+                                                           &slen,
+                                                           false);
+                     section_size_type off =
+                       (convert_to_section_size_type(reloc.get_r_offset())
+                        + (big_endian ? -2 : 3));
+                     if (off < slen
+                         && (view[off] & (0x3f << 2)) == 58u << 2)
+                       ok = true;
+                   }
+                 if (!ok)
+                   ppc_object->set_no_toc_opt(dst_off);
+               }
+           }
+         break;
+       }
+    }
+
+  if (size == 32)
+    {
+      switch (r_type)
+       {
+       case elfcpp::R_POWERPC_REL32:
+         if (ppc_object->got2_shndx() != 0
+             && parameters->options().output_is_position_independent())
+           {
+             unsigned int shndx = lsym.get_st_shndx();
+             unsigned int r_sym = elfcpp::elf_r_sym<size>(reloc.get_r_info());
+             bool is_ordinary;
+             shndx = ppc_object->adjust_sym_shndx(r_sym, shndx, &is_ordinary);
+             if (is_ordinary && shndx == ppc_object->got2_shndx()
+                 && (ppc_object->section_flags(data_shndx)
+                     & elfcpp::SHF_EXECINSTR) != 0)
+               gold_error(_("%s: unsupported -mbss-plt code"),
+                          ppc_object->name().c_str());
+           }
+         break;
+       default:
+         break;
+       }
+    }
+
   switch (r_type)
     {
     case elfcpp::R_POWERPC_GOT_TLSLD16:
@@ -5968,9 +6539,9 @@ Target_powerpc<size, big_endian>::Scan::global(
   bool pushed_ifunc = false;
   if (is_ifunc && this->reloc_needs_plt_for_ifunc(target, object, r_type, true))
     {
+      unsigned int r_sym = elfcpp::elf_r_sym<size>(reloc.get_r_info());
       target->push_branch(ppc_object, data_shndx, reloc.get_r_offset(),
-                         r_type, elfcpp::elf_r_sym<size>(reloc.get_r_info()),
-                         reloc.get_r_addend());
+                         r_type, r_sym, reloc.get_r_addend());
       target->make_plt_entry(symtab, layout, gsym);
       pushed_ifunc = true;
     }
@@ -6021,7 +6592,7 @@ Target_powerpc<size, big_endian>::Scan::global(
          ppc_object->set_opd_discard(reloc.get_r_offset());
          break;
        }
-      // Fall thru
+      // Fall through.
     case elfcpp::R_PPC64_UADDR64:
     case elfcpp::R_POWERPC_ADDR32:
     case elfcpp::R_POWERPC_UADDR32:
@@ -6060,9 +6631,9 @@ Target_powerpc<size, big_endian>::Scan::global(
              }
            if (!is_ifunc || (!pushed_ifunc && need_ifunc_plt))
              {
+               unsigned int r_sym = elfcpp::elf_r_sym<size>(reloc.get_r_info());
                target->push_branch(ppc_object, data_shndx,
-                                   reloc.get_r_offset(), r_type,
-                                   elfcpp::elf_r_sym<size>(reloc.get_r_info()),
+                                   reloc.get_r_offset(), r_type, r_sym,
                                    reloc.get_r_addend());
                target->make_plt_entry(symtab, layout, gsym);
              }
@@ -6108,6 +6679,11 @@ Target_powerpc<size, big_endian>::Scan::global(
                                     object, data_shndx,
                                     reloc.get_r_offset(),
                                     reloc.get_r_addend());
+
+               if (size == 64
+                   && parameters->options().toc_optimize()
+                   && data_shndx == ppc_object->toc_shndx())
+                 ppc_object->set_no_toc_opt(reloc.get_r_offset());
              }
          }
       }
@@ -6117,10 +6693,9 @@ Target_powerpc<size, big_endian>::Scan::global(
     case elfcpp::R_POWERPC_REL24:
       if (!is_ifunc)
        {
+         unsigned int r_sym = elfcpp::elf_r_sym<size>(reloc.get_r_info());
          target->push_branch(ppc_object, data_shndx, reloc.get_r_offset(),
-                             r_type,
-                             elfcpp::elf_r_sym<size>(reloc.get_r_info()),
-                             reloc.get_r_addend());
+                             r_type, r_sym, reloc.get_r_addend());
          if (gsym->needs_plt_entry()
              || (!gsym->final_value_is_known()
                  && (gsym->is_undefined()
@@ -6128,7 +6703,7 @@ Target_powerpc<size, big_endian>::Scan::global(
                      || gsym->is_preemptible())))
            target->make_plt_entry(symtab, layout, gsym);
        }
-      // Fall thru
+      // Fall through.
 
     case elfcpp::R_PPC64_REL64:
     case elfcpp::R_POWERPC_REL32:
@@ -6158,9 +6733,11 @@ Target_powerpc<size, big_endian>::Scan::global(
     case elfcpp::R_POWERPC_REL14_BRTAKEN:
     case elfcpp::R_POWERPC_REL14_BRNTAKEN:
       if (!is_ifunc)
-       target->push_branch(ppc_object, data_shndx, reloc.get_r_offset(),
-                           r_type, elfcpp::elf_r_sym<size>(reloc.get_r_info()),
-                           reloc.get_r_addend());
+       {
+         unsigned int r_sym = elfcpp::elf_r_sym<size>(reloc.get_r_info());
+         target->push_branch(ppc_object, data_shndx, reloc.get_r_offset(),
+                             r_type, r_sym, reloc.get_r_addend());
+       }
       break;
 
     case elfcpp::R_POWERPC_REL16:
@@ -6395,6 +6972,136 @@ Target_powerpc<size, big_endian>::Scan::global(
       break;
     }
 
+  if (size == 64
+      && parameters->options().toc_optimize())
+    {
+      if (data_shndx == ppc_object->toc_shndx())
+       {
+         bool ok = true;
+         if (r_type != elfcpp::R_PPC64_ADDR64
+             || (is_ifunc && target->abiversion() < 2))
+           ok = false;
+         else if (parameters->options().output_is_position_independent()
+                  && (is_ifunc || gsym->is_absolute() || gsym->is_undefined()))
+           ok = false;
+         if (!ok)
+           ppc_object->set_no_toc_opt(reloc.get_r_offset());
+       }
+
+      enum {no_check, check_lo, check_ha} insn_check;
+      switch (r_type)
+       {
+       default:
+         insn_check = no_check;
+         break;
+
+       case elfcpp::R_POWERPC_GOT_TLSLD16_HA:
+       case elfcpp::R_POWERPC_GOT_TLSGD16_HA:
+       case elfcpp::R_POWERPC_GOT_TPREL16_HA:
+       case elfcpp::R_POWERPC_GOT_DTPREL16_HA:
+       case elfcpp::R_POWERPC_GOT16_HA:
+       case elfcpp::R_PPC64_TOC16_HA:
+         insn_check = check_ha;
+         break;
+
+       case elfcpp::R_POWERPC_GOT_TLSLD16_LO:
+       case elfcpp::R_POWERPC_GOT_TLSGD16_LO:
+       case elfcpp::R_POWERPC_GOT_TPREL16_LO:
+       case elfcpp::R_POWERPC_GOT_DTPREL16_LO:
+       case elfcpp::R_POWERPC_GOT16_LO:
+       case elfcpp::R_PPC64_GOT16_LO_DS:
+       case elfcpp::R_PPC64_TOC16_LO:
+       case elfcpp::R_PPC64_TOC16_LO_DS:
+         insn_check = check_lo;
+         break;
+       }
+
+      section_size_type slen;
+      const unsigned char* view = NULL;
+      if (insn_check != no_check)
+       {
+         view = ppc_object->section_contents(data_shndx, &slen, false);
+         section_size_type off =
+           convert_to_section_size_type(reloc.get_r_offset()) & -4;
+         if (off < slen)
+           {
+             uint32_t insn = elfcpp::Swap<32, big_endian>::readval(view + off);
+             if (insn_check == check_lo
+                 ? !ok_lo_toc_insn(insn, r_type)
+                 : ((insn & ((0x3f << 26) | 0x1f << 16))
+                    != ((15u << 26) | (2 << 16)) /* addis rt,2,imm */))
+               {
+                 ppc_object->set_no_toc_opt();
+                 gold_warning(_("%s: toc optimization is not supported "
+                                "for %#08x instruction"),
+                              ppc_object->name().c_str(), insn);
+               }
+           }
+       }
+
+      switch (r_type)
+       {
+       default:
+         break;
+       case elfcpp::R_PPC64_TOC16:
+       case elfcpp::R_PPC64_TOC16_LO:
+       case elfcpp::R_PPC64_TOC16_HI:
+       case elfcpp::R_PPC64_TOC16_HA:
+       case elfcpp::R_PPC64_TOC16_DS:
+       case elfcpp::R_PPC64_TOC16_LO_DS:
+         if (gsym->source() == Symbol::FROM_OBJECT
+             && !gsym->object()->is_dynamic())
+           {
+             Powerpc_relobj<size, big_endian>* sym_object
+               = static_cast<Powerpc_relobj<size, big_endian>*>(gsym->object());
+             bool is_ordinary;
+             unsigned int shndx = gsym->shndx(&is_ordinary);
+             if (shndx == sym_object->toc_shndx())
+               {
+                 Sized_symbol<size>* sym = symtab->get_sized_symbol<size>(gsym);
+                 Address dst_off = sym->value() + reloc.get_r_offset();
+                 if (dst_off < sym_object->section_size(shndx))
+                   {
+                     bool ok = false;
+                     if (r_type == elfcpp::R_PPC64_TOC16_HA)
+                       ok = true;
+                     else if (r_type == elfcpp::R_PPC64_TOC16_LO_DS)
+                       {
+                         // Need to check that the insn is a ld
+                         if (!view)
+                           view = ppc_object->section_contents(data_shndx,
+                                                               &slen,
+                                                               false);
+                         section_size_type off =
+                           (convert_to_section_size_type(reloc.get_r_offset())
+                            + (big_endian ? -2 : 3));
+                         if (off < slen
+                             && (view[off] & (0x3f << 2)) == (58u << 2))
+                           ok = true;
+                       }
+                     if (!ok)
+                       sym_object->set_no_toc_opt(dst_off);
+                   }
+               }
+           }
+         break;
+       }
+    }
+
+  if (size == 32)
+    {
+      switch (r_type)
+       {
+       case elfcpp::R_PPC_LOCAL24PC:
+         if (strcmp(gsym->name(), "_GLOBAL_OFFSET_TABLE_") == 0)
+           gold_error(_("%s: unsupported -mbss-plt code"),
+                      ppc_object->name().c_str());
+         break;
+       default:
+         break;
+       }
+    }
+
   switch (r_type)
     {
     case elfcpp::R_POWERPC_GOT_TLSLD16:
@@ -6429,7 +7136,9 @@ Target_powerpc<size, big_endian>::gc_process_relocs(
     const unsigned char* plocal_symbols)
 {
   typedef Target_powerpc<size, big_endian> Powerpc;
-  typedef typename Target_powerpc<size, big_endian>::Scan Scan;
+  typedef gold::Default_classify_reloc<elfcpp::SHT_RELA, size, big_endian>
+      Classify_reloc;
+
   Powerpc_relobj<size, big_endian>* ppc_object
     = static_cast<Powerpc_relobj<size, big_endian>*>(object);
   if (size == 64)
@@ -6459,8 +7168,7 @@ Target_powerpc<size, big_endian>::gc_process_relocs(
       return;
     }
 
-  gold::gc_process_relocs<size, big_endian, Powerpc, elfcpp::SHT_RELA, Scan,
-                         typename Target_powerpc::Relocatable_size_for_reloc>(
+  gold::gc_process_relocs<size, big_endian, Powerpc, Scan, Classify_reloc>(
     symtab,
     layout,
     this,
@@ -6590,6 +7298,8 @@ Target_powerpc<size, big_endian>::do_calls_non_split(
     unsigned int shndx,
     section_offset_type fnoffset,
     section_size_type fnsize,
+    const unsigned char* prelocs,
+    size_t reloc_count,
     unsigned char* view,
     section_size_type view_size,
     std::string* from,
@@ -6600,7 +7310,8 @@ Target_powerpc<size, big_endian>::do_calls_non_split(
     {
       // warn
       Target::do_calls_non_split(object, shndx, fnoffset, fnsize,
-                                view, view_size, from, to);
+                                prelocs, reloc_count, view, view_size,
+                                from, to);
       return;
     }
 
@@ -6703,7 +7414,8 @@ Target_powerpc<size, big_endian>::scan_relocs(
     const unsigned char* plocal_symbols)
 {
   typedef Target_powerpc<size, big_endian> Powerpc;
-  typedef typename Target_powerpc<size, big_endian>::Scan Scan;
+  typedef gold::Default_classify_reloc<elfcpp::SHT_RELA, size, big_endian>
+      Classify_reloc;
 
   if (sh_type == elfcpp::SHT_REL)
     {
@@ -6712,7 +7424,7 @@ Target_powerpc<size, big_endian>::scan_relocs(
       return;
     }
 
-  gold::scan_relocs<size, big_endian, Powerpc, elfcpp::SHT_RELA, Scan>(
+  gold::scan_relocs<size, big_endian, Powerpc, Scan, Classify_reloc>(
     symtab,
     layout,
     this,
@@ -6910,31 +7622,40 @@ Target_powerpc<size, big_endian>::do_finalize_sections(
     this->copy_relocs_.emit(this->rela_dyn_section(layout));
 }
 
-// Return TRUE iff INSN is one we expect on a _LO variety toc/got
-// reloc.
+// Emit any saved relocs, and mark toc entries using any of these
+// relocs as not optimizable.
 
-static bool
-ok_lo_toc_insn(uint32_t insn)
+template<int sh_type, int size, bool big_endian>
+void
+Powerpc_copy_relocs<sh_type, size, big_endian>::emit(
+    Output_data_reloc<sh_type, true, size, big_endian>* reloc_section)
 {
-  return ((insn & (0x3f << 26)) == 14u << 26 /* addi */
-         || (insn & (0x3f << 26)) == 32u << 26 /* lwz */
-         || (insn & (0x3f << 26)) == 34u << 26 /* lbz */
-         || (insn & (0x3f << 26)) == 36u << 26 /* stw */
-         || (insn & (0x3f << 26)) == 38u << 26 /* stb */
-         || (insn & (0x3f << 26)) == 40u << 26 /* lhz */
-         || (insn & (0x3f << 26)) == 42u << 26 /* lha */
-         || (insn & (0x3f << 26)) == 44u << 26 /* sth */
-         || (insn & (0x3f << 26)) == 46u << 26 /* lmw */
-         || (insn & (0x3f << 26)) == 47u << 26 /* stmw */
-         || (insn & (0x3f << 26)) == 48u << 26 /* lfs */
-         || (insn & (0x3f << 26)) == 50u << 26 /* lfd */
-         || (insn & (0x3f << 26)) == 52u << 26 /* stfs */
-         || (insn & (0x3f << 26)) == 54u << 26 /* stfd */
-         || ((insn & (0x3f << 26)) == 58u << 26 /* lwa,ld,lmd */
-             && (insn & 3) != 1)
-         || ((insn & (0x3f << 26)) == 62u << 26 /* std, stmd */
-             && ((insn & 3) == 0 || (insn & 3) == 3))
-         || (insn & (0x3f << 26)) == 12u << 26 /* addic */);
+  if (size == 64
+      && parameters->options().toc_optimize())
+    {
+      for (typename Copy_relocs<sh_type, size, big_endian>::
+            Copy_reloc_entries::iterator p = this->entries_.begin();
+          p != this->entries_.end();
+          ++p)
+       {
+         typename Copy_relocs<sh_type, size, big_endian>::Copy_reloc_entry&
+           entry = *p;
+
+         // If the symbol is no longer defined in a dynamic object,
+         // then we emitted a COPY relocation.  If it is still
+         // dynamic then we'll need dynamic relocations and thus
+         // can't optimize toc entries.
+         if (entry.sym_->is_from_dynobj())
+           {
+             Powerpc_relobj<size, big_endian>* ppc_object
+               = static_cast<Powerpc_relobj<size, big_endian>*>(entry.relobj_);
+             if (entry.shndx_ == ppc_object->toc_shndx())
+               ppc_object->set_no_toc_opt(entry.address_);
+           }
+       }
+    }
+
+  Copy_relocs<sh_type, size, big_endian>::emit(reloc_section);
 }
 
 // Return the value to use for a branch relocation.
@@ -6956,7 +7677,8 @@ Target_powerpc<size, big_endian>::symval_for_branch(
   // descriptor, use the function descriptor code entry address
   Powerpc_relobj<size, big_endian>* symobj = object;
   if (gsym != NULL
-      && gsym->source() != Symbol::FROM_OBJECT)
+      && (gsym->source() != Symbol::FROM_OBJECT
+         || gsym->object()->is_dynamic()))
     return true;
   if (gsym != NULL)
     symobj = static_cast<Powerpc_relobj<size, big_endian>*>(gsym->object());
@@ -7027,6 +7749,10 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
 
   typedef Powerpc_relocate_functions<size, big_endian> Reloc;
   typedef typename elfcpp::Swap<32, big_endian>::Valtype Insn;
+  typedef typename elfcpp::Rela<size, big_endian> Reltype;
+  // Offset from start of insn to d-field reloc.
+  const int d_offset = big_endian ? 2 : 0;
+
   Powerpc_relobj<size, big_endian>* const object
     = static_cast<Powerpc_relobj<size, big_endian>*>(relinfo->object);
   Address value = 0;
@@ -7097,7 +7823,6 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
        }
       else
        {
-         unsigned int r_sym = elfcpp::elf_r_sym<size>(rela.get_r_info());
          gold_assert(object->local_has_got_offset(r_sym, GOT_TYPE_STANDARD));
          value = object->local_got_offset(r_sym, GOT_TYPE_STANDARD);
        }
@@ -7199,7 +7924,6 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
            }
          else
            {
-             unsigned int r_sym = elfcpp::elf_r_sym<size>(rela.get_r_info());
              gold_assert(object->local_has_got_offset(r_sym, got_type));
              value = object->local_got_offset(r_sym, got_type);
            }
@@ -7210,7 +7934,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
          if (r_type == elfcpp::R_POWERPC_GOT_TLSGD16
              || r_type == elfcpp::R_POWERPC_GOT_TLSGD16_LO)
            {
-             Insn* iview = reinterpret_cast<Insn*>(view - 2 * big_endian);
+             Insn* iview = reinterpret_cast<Insn*>(view - d_offset);
              Insn insn = elfcpp::Swap<32, big_endian>::readval(iview);
              insn &= (1 << 26) - (1 << 16); // extract rt,ra from addi
              if (size == 32)
@@ -7227,7 +7951,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
          if (r_type == elfcpp::R_POWERPC_GOT_TLSGD16
              || r_type == elfcpp::R_POWERPC_GOT_TLSGD16_LO)
            {
-             Insn* iview = reinterpret_cast<Insn*>(view - 2 * big_endian);
+             Insn* iview = reinterpret_cast<Insn*>(view - d_offset);
              Insn insn = elfcpp::Swap<32, big_endian>::readval(iview);
              insn &= (1 << 26) - (1 << 21); // extract rt
              if (size == 32)
@@ -7240,7 +7964,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
            }
          else
            {
-             Insn* iview = reinterpret_cast<Insn*>(view - 2 * big_endian);
+             Insn* iview = reinterpret_cast<Insn*>(view - d_offset);
              Insn insn = nop;
              elfcpp::Swap<32, big_endian>::writeval(iview, insn);
              r_type = elfcpp::R_POWERPC_NONE;
@@ -7265,7 +7989,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
          if (r_type == elfcpp::R_POWERPC_GOT_TLSLD16
              || r_type == elfcpp::R_POWERPC_GOT_TLSLD16_LO)
            {
-             Insn* iview = reinterpret_cast<Insn*>(view - 2 * big_endian);
+             Insn* iview = reinterpret_cast<Insn*>(view - d_offset);
              Insn insn = elfcpp::Swap<32, big_endian>::readval(iview);
              insn &= (1 << 26) - (1 << 21); // extract rt
              if (size == 32)
@@ -7278,7 +8002,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
            }
          else
            {
-             Insn* iview = reinterpret_cast<Insn*>(view - 2 * big_endian);
+             Insn* iview = reinterpret_cast<Insn*>(view - d_offset);
              Insn insn = nop;
              elfcpp::Swap<32, big_endian>::writeval(iview, insn);
              r_type = elfcpp::R_POWERPC_NONE;
@@ -7299,7 +8023,6 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
        }
       else
        {
-         unsigned int r_sym = elfcpp::elf_r_sym<size>(rela.get_r_info());
          gold_assert(object->local_has_got_offset(r_sym, GOT_TYPE_DTPREL));
          value = object->local_got_offset(r_sym, GOT_TYPE_DTPREL);
        }
@@ -7322,7 +8045,6 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
            }
          else
            {
-             unsigned int r_sym = elfcpp::elf_r_sym<size>(rela.get_r_info());
              gold_assert(object->local_has_got_offset(r_sym, GOT_TYPE_TPREL));
              value = object->local_got_offset(r_sym, GOT_TYPE_TPREL);
            }
@@ -7334,7 +8056,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
          if (r_type == elfcpp::R_POWERPC_GOT_TPREL16
              || r_type == elfcpp::R_POWERPC_GOT_TPREL16_LO)
            {
-             Insn* iview = reinterpret_cast<Insn*>(view - 2 * big_endian);
+             Insn* iview = reinterpret_cast<Insn*>(view - d_offset);
              Insn insn = elfcpp::Swap<32, big_endian>::readval(iview);
              insn &= (1 << 26) - (1 << 21); // extract rt from ld
              if (size == 32)
@@ -7347,7 +8069,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
            }
          else
            {
-             Insn* iview = reinterpret_cast<Insn*>(view - 2 * big_endian);
+             Insn* iview = reinterpret_cast<Insn*>(view - d_offset);
              Insn insn = nop;
              elfcpp::Swap<32, big_endian>::writeval(iview, insn);
              r_type = elfcpp::R_POWERPC_NONE;
@@ -7379,7 +8101,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
              Insn insn = addi_3_3;
              elfcpp::Swap<32, big_endian>::writeval(iview, insn);
              r_type = elfcpp::R_POWERPC_TPREL16_LO;
-             view += 2 * big_endian;
+             view += d_offset;
              value = psymval->value(object, rela.get_r_addend());
            }
          this->skip_next_tls_get_addr_call();
@@ -7399,7 +8121,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
          elfcpp::Swap<32, big_endian>::writeval(iview, insn);
          this->skip_next_tls_get_addr_call();
          r_type = elfcpp::R_POWERPC_TPREL16_LO;
-         view += 2 * big_endian;
+         view += d_offset;
          value = dtp_offset;
        }
     }
@@ -7417,7 +8139,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
          gold_assert(insn != 0);
          elfcpp::Swap<32, big_endian>::writeval(iview, insn);
          r_type = elfcpp::R_POWERPC_TPREL16_LO;
-         view += 2 * big_endian;
+         view += d_offset;
          value = psymval->value(object, rela.get_r_addend());
        }
     }
@@ -7508,6 +8230,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
       if (size != 64)
        // R_PPC_TLSGD, R_PPC_TLSLD, R_PPC_EMB_RELST_LO, R_PPC_EMB_RELST_HI
        break;
+      // Fall through.
     case elfcpp::R_POWERPC_TPREL16:
     case elfcpp::R_POWERPC_TPREL16_LO:
     case elfcpp::R_POWERPC_TPREL16_HI:
@@ -7531,6 +8254,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
        // R_PPC_EMB_NADDR32, R_PPC_EMB_NADDR16, R_PPC_EMB_NADDR16_LO
        // R_PPC_EMB_NADDR16_HI, R_PPC_EMB_NADDR16_HA, R_PPC_EMB_SDAI16
        break;
+      // Fall through.
     case elfcpp::R_POWERPC_DTPREL16:
     case elfcpp::R_POWERPC_DTPREL16_LO:
     case elfcpp::R_POWERPC_DTPREL16_HI:
@@ -7559,6 +8283,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
     case elfcpp::R_POWERPC_ADDR14_BRTAKEN:
     case elfcpp::R_POWERPC_REL14_BRTAKEN:
       branch_bit = 1 << 21;
+      // Fall through.
     case elfcpp::R_POWERPC_ADDR14_BRNTAKEN:
     case elfcpp::R_POWERPC_REL14_BRNTAKEN:
       {
@@ -7594,14 +8319,24 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
 
   if (size == 64)
     {
-      // Multi-instruction sequences that access the TOC can be
-      // optimized, eg. addis ra,r2,0; addi rb,ra,x;
-      // to             nop;           addi rb,r2,x;
       switch (r_type)
        {
        default:
          break;
 
+         // Multi-instruction sequences that access the GOT/TOC can
+         // be optimized, eg.
+         //     addis ra,r2,x@got@ha; ld rb,x@got@l(ra);
+         // to  addis ra,r2,x@toc@ha; addi rb,ra,x@toc@l;
+         // and
+         //     addis ra,r2,0; addi rb,ra,x@toc@l;
+         // to  nop;           addi rb,r2,x@toc;
+         // FIXME: the @got sequence shown above is not yet
+         // optimized.  Note that gcc as of 2017-01-07 doesn't use
+         // the ELF @got relocs except for TLS, instead using the
+         // PowerOpen variant of a compiler managed GOT (called TOC).
+         // The PowerOpen TOC sequence equivalent to the first
+         // example is optimized.
        case elfcpp::R_POWERPC_GOT_TLSLD16_HA:
        case elfcpp::R_POWERPC_GOT_TLSGD16_HA:
        case elfcpp::R_POWERPC_GOT_TPREL16_HA:
@@ -7610,14 +8345,17 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
        case elfcpp::R_PPC64_TOC16_HA:
          if (parameters->options().toc_optimize())
            {
-             Insn* iview = reinterpret_cast<Insn*>(view - 2 * big_endian);
+             Insn* iview = reinterpret_cast<Insn*>(view - d_offset);
              Insn insn = elfcpp::Swap<32, big_endian>::readval(iview);
-             if ((insn & ((0x3f << 26) | 0x1f << 16))
-                 != ((15u << 26) | (2 << 16)) /* addis rt,2,imm */)
-               gold_error_at_location(relinfo, relnum, rela.get_r_offset(),
-                                      _("toc optimization is not supported "
-                                        "for %#08x instruction"), insn);
-             else if (value + 0x8000 < 0x10000)
+             if (r_type == elfcpp::R_PPC64_TOC16_HA
+                 && object->make_toc_relative(target, &value))
+               {
+                 gold_assert((insn & ((0x3f << 26) | 0x1f << 16))
+                             == ((15u << 26) | (2 << 16)));
+               }
+             if (((insn & ((0x3f << 26) | 0x1f << 16))
+                  == ((15u << 26) | (2 << 16)) /* addis rt,2,imm */)
+                 && value + 0x8000 < 0x10000)
                {
                  elfcpp::Swap<32, big_endian>::writeval(iview, nop);
                  return true;
@@ -7635,13 +8373,19 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
        case elfcpp::R_PPC64_TOC16_LO_DS:
          if (parameters->options().toc_optimize())
            {
-             Insn* iview = reinterpret_cast<Insn*>(view - 2 * big_endian);
+             Insn* iview = reinterpret_cast<Insn*>(view - d_offset);
              Insn insn = elfcpp::Swap<32, big_endian>::readval(iview);
-             if (!ok_lo_toc_insn(insn))
-               gold_error_at_location(relinfo, relnum, rela.get_r_offset(),
-                                      _("toc optimization is not supported "
-                                        "for %#08x instruction"), insn);
-             else if (value + 0x8000 < 0x10000)
+             bool changed = false;
+             if (r_type == elfcpp::R_PPC64_TOC16_LO_DS
+                 && object->make_toc_relative(target, &value))
+               {
+                 gold_assert ((insn & (0x3f << 26)) == 58u << 26 /* ld */);
+                 insn ^= (14u << 26) ^ (58u << 26);
+                 r_type = elfcpp::R_PPC64_TOC16_LO;
+                 changed = true;
+               }
+             if (ok_lo_toc_insn(insn, r_type)
+                 && value + 0x8000 < 0x10000)
                {
                  if ((insn & (0x3f << 26)) == 12u << 26 /* addic */)
                    {
@@ -7654,8 +8398,10 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
                      insn &= ~(0x1f << 16);
                      insn |= 2 << 16;
                    }
-                 elfcpp::Swap<32, big_endian>::writeval(iview, insn);
+                 changed = true;
                }
+             if (changed)
+               elfcpp::Swap<32, big_endian>::writeval(iview, insn);
            }
          break;
 
@@ -7700,6 +8446,55 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
                }
            }
          break;
+
+       case elfcpp::R_POWERPC_REL16_LO:
+         // If we are generating a non-PIC executable, edit
+         //    0:      addis 2,12,.TOC.-0b@ha
+         //            addi 2,2,.TOC.-0b@l
+         // used by ELFv2 global entry points to set up r2, to
+         //            lis 2,.TOC.@ha
+         //            addi 2,2,.TOC.@l
+         // if .TOC. is in range.  */
+         if (value + address - 4 + 0x80008000 <= 0xffffffff
+             && relnum != 0
+             && preloc != NULL
+             && target->abiversion() >= 2
+             && !parameters->options().output_is_position_independent()
+             && rela.get_r_addend() == d_offset + 4
+             && gsym != NULL
+             && strcmp(gsym->name(), ".TOC.") == 0)
+           {
+             const int reloc_size = elfcpp::Elf_sizes<size>::rela_size;
+             Reltype prev_rela(preloc - reloc_size);
+             if ((prev_rela.get_r_info()
+                  == elfcpp::elf_r_info<size>(r_sym,
+                                              elfcpp::R_POWERPC_REL16_HA))
+                 && prev_rela.get_r_offset() + 4 == rela.get_r_offset()
+                 && prev_rela.get_r_addend() + 4 == rela.get_r_addend())
+               {
+                 Insn* iview = reinterpret_cast<Insn*>(view - d_offset);
+                 Insn insn1 = elfcpp::Swap<32, big_endian>::readval(iview - 1);
+                 Insn insn2 = elfcpp::Swap<32, big_endian>::readval(iview);
+
+                 if ((insn1 & 0xffff0000) == addis_2_12
+                     && (insn2 & 0xffff0000) == addi_2_2)
+                   {
+                     insn1 = lis_2 + ha(value + address - 4);
+                     elfcpp::Swap<32, big_endian>::writeval(iview - 1, insn1);
+                     insn2 = addi_2_2 + l(value + address - 4);
+                     elfcpp::Swap<32, big_endian>::writeval(iview, insn2);
+                     if (relinfo->rr)
+                       {
+                         relinfo->rr->set_strategy(relnum - 1,
+                                                   Relocatable_relocs::RELOC_SPECIAL);
+                         relinfo->rr->set_strategy(relnum,
+                                                   Relocatable_relocs::RELOC_SPECIAL);
+                       }
+                     return true;
+                   }
+               }
+           }
+         break;
        }
     }
 
@@ -7795,7 +8590,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
       break;
     }
 
-  Insn* iview = reinterpret_cast<Insn*>(view - 2 * big_endian);
+  Insn* iview = reinterpret_cast<Insn*>(view - d_offset);
   Insn insn = 0;
 
   if (overflow == Reloc::CHECK_LOW_INSN
@@ -7873,6 +8668,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
          maybe_dq_reloc = true;
          break;
        }
+      // Fall through.
     case elfcpp::R_POWERPC_ADDR16:
     case elfcpp::R_POWERPC_REL16:
     case elfcpp::R_PPC64_TOC16:
@@ -7907,6 +8703,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
       if (size == 32)
        // R_PPC_EMB_MRKREF, R_PPC_EMB_RELST_LO, R_PPC_EMB_RELST_HA
        goto unsupp;
+      // Fall through.
     case elfcpp::R_POWERPC_ADDR16_HI:
     case elfcpp::R_POWERPC_REL16_HI:
     case elfcpp::R_PPC64_TOC16_HI:
@@ -7927,6 +8724,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
       if (size == 32)
        // R_PPC_EMB_RELSEC16, R_PPC_EMB_RELST_HI, R_PPC_EMB_BIT_FLD
        goto unsupp;
+      // Fall through.
     case elfcpp::R_POWERPC_ADDR16_HA:
     case elfcpp::R_POWERPC_REL16_HA:
     case elfcpp::R_PPC64_TOC16_HA:
@@ -7949,6 +8747,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
       if (size == 32)
        // R_PPC_EMB_NADDR16_LO
        goto unsupp;
+      // Fall through.
     case elfcpp::R_PPC64_ADDR16_HIGHER:
     case elfcpp::R_PPC64_TPREL16_HIGHER:
       Reloc::addr16_hi2(view, value);
@@ -7958,6 +8757,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
       if (size == 32)
        // R_PPC_EMB_NADDR16_HI
        goto unsupp;
+      // Fall through.
     case elfcpp::R_PPC64_ADDR16_HIGHERA:
     case elfcpp::R_PPC64_TPREL16_HIGHERA:
       Reloc::addr16_ha2(view, value);
@@ -7967,6 +8767,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
       if (size == 32)
        // R_PPC_EMB_NADDR16_HA
        goto unsupp;
+      // Fall through.
     case elfcpp::R_PPC64_ADDR16_HIGHEST:
     case elfcpp::R_PPC64_TPREL16_HIGHEST:
       Reloc::addr16_hi3(view, value);
@@ -7976,6 +8777,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
       if (size == 32)
        // R_PPC_EMB_SDAI16
        goto unsupp;
+      // Fall through.
     case elfcpp::R_PPC64_ADDR16_HIGHESTA:
     case elfcpp::R_PPC64_TPREL16_HIGHESTA:
       Reloc::addr16_ha3(view, value);
@@ -7986,11 +8788,13 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
       if (size == 32)
        // R_PPC_EMB_NADDR32, R_PPC_EMB_NADDR16
        goto unsupp;
+      // Fall through.
     case elfcpp::R_PPC64_TPREL16_DS:
     case elfcpp::R_PPC64_TPREL16_LO_DS:
       if (size == 32)
        // R_PPC_TLSGD, R_PPC_TLSLD
        break;
+      // Fall through.
     case elfcpp::R_PPC64_ADDR16_DS:
     case elfcpp::R_PPC64_ADDR16_LO_DS:
     case elfcpp::R_PPC64_TOC16_DS:
@@ -8119,11 +8923,13 @@ Target_powerpc<size, big_endian>::relocate_section(
   typedef typename Target_powerpc<size, big_endian>::Relocate Powerpc_relocate;
   typedef typename Target_powerpc<size, big_endian>::Relocate_comdat_behavior
     Powerpc_comdat_behavior;
+  typedef gold::Default_classify_reloc<elfcpp::SHT_RELA, size, big_endian>
+      Classify_reloc;
 
   gold_assert(sh_type == elfcpp::SHT_RELA);
 
-  gold::relocate_section<size, big_endian, Powerpc, elfcpp::SHT_RELA,
-                        Powerpc_relocate, Powerpc_comdat_behavior>(
+  gold::relocate_section<size, big_endian, Powerpc, Powerpc_relocate,
+                        Powerpc_comdat_behavior, Classify_reloc>(
     relinfo,
     this,
     prelocs,
@@ -8136,9 +8942,24 @@ Target_powerpc<size, big_endian>::relocate_section(
     reloc_symbol_changes);
 }
 
+template<int size, bool big_endian>
 class Powerpc_scan_relocatable_reloc
 {
 public:
+  typedef typename elfcpp::Rela<size, big_endian> Reltype;
+  static const int reloc_size = elfcpp::Elf_sizes<size>::rela_size;
+  static const int sh_type = elfcpp::SHT_RELA;
+
+  // Return the symbol referred to by the relocation.
+  static inline unsigned int
+  get_r_sym(const Reltype* reloc)
+  { return elfcpp::elf_r_sym<size>(reloc->get_r_info()); }
+
+  // Return the type of the relocation.
+  static inline unsigned int
+  get_r_type(const Reltype* reloc)
+  { return elfcpp::elf_r_type<size>(reloc->get_r_info()); }
+
   // Return the strategy to use for a local symbol which is not a
   // section symbol, given the relocation type.
   inline Relocatable_relocs::Reloc_strategy
@@ -8186,10 +9007,11 @@ Target_powerpc<size, big_endian>::scan_relocatable_relocs(
     const unsigned char* plocal_symbols,
     Relocatable_relocs* rr)
 {
+  typedef Powerpc_scan_relocatable_reloc<size, big_endian> Scan_strategy;
+
   gold_assert(sh_type == elfcpp::SHT_RELA);
 
-  gold::scan_relocatable_relocs<size, big_endian, elfcpp::SHT_RELA,
-                               Powerpc_scan_relocatable_reloc>(
+  gold::scan_relocatable_relocs<size, big_endian, Scan_strategy>(
     symtab,
     layout,
     object,
@@ -8203,6 +9025,45 @@ Target_powerpc<size, big_endian>::scan_relocatable_relocs(
     rr);
 }
 
+// Scan the relocs for --emit-relocs.
+
+template<int size, bool big_endian>
+void
+Target_powerpc<size, big_endian>::emit_relocs_scan(
+    Symbol_table* symtab,
+    Layout* layout,
+    Sized_relobj_file<size, big_endian>* object,
+    unsigned int data_shndx,
+    unsigned int sh_type,
+    const unsigned char* prelocs,
+    size_t reloc_count,
+    Output_section* output_section,
+    bool needs_special_offset_handling,
+    size_t local_symbol_count,
+    const unsigned char* plocal_syms,
+    Relocatable_relocs* rr)
+{
+  typedef gold::Default_classify_reloc<elfcpp::SHT_RELA, size, big_endian>
+      Classify_reloc;
+  typedef gold::Default_emit_relocs_strategy<Classify_reloc>
+      Emit_relocs_strategy;
+
+  gold_assert(sh_type == elfcpp::SHT_RELA);
+
+  gold::scan_relocatable_relocs<size, big_endian, Emit_relocs_strategy>(
+    symtab,
+    layout,
+    object,
+    data_shndx,
+    prelocs,
+    reloc_count,
+    output_section,
+    needs_special_offset_handling,
+    local_symbol_count,
+    plocal_syms,
+    rr);
+}
+
 // Emit relocations for a section.
 // This is a modified version of the function by the same name in
 // target-reloc.h.  Using relocate_special_relocatable for
@@ -8226,12 +9087,11 @@ Target_powerpc<size, big_endian>::relocate_relocs(
 {
   gold_assert(sh_type == elfcpp::SHT_RELA);
 
-  typedef typename Reloc_types<elfcpp::SHT_RELA, size, big_endian>::Reloc
-    Reltype;
-  typedef typename Reloc_types<elfcpp::SHT_RELA, size, big_endian>::Reloc_write
-    Reltype_write;
-  const int reloc_size
-    = Reloc_types<elfcpp::SHT_RELA, size, big_endian>::reloc_size;
+  typedef typename elfcpp::Rela<size, big_endian> Reltype;
+  typedef typename elfcpp::Rela_write<size, big_endian> Reltype_write;
+  const int reloc_size = elfcpp::Elf_sizes<size>::rela_size;
+  // Offset from start of insn to d-field reloc.
+  const int d_offset = big_endian ? 2 : 0;
 
   Powerpc_relobj<size, big_endian>* const object
     = static_cast<Powerpc_relobj<size, big_endian>*>(relinfo->object);
@@ -8357,8 +9217,21 @@ Target_powerpc<size, big_endian>::relocate_relocs(
        }
       else if (strategy == Relocatable_relocs::RELOC_SPECIAL)
        {
-         if (addend >= 32768)
-           addend += got2_addend;
+         if (size == 32)
+           {
+             if (addend >= 32768)
+               addend += got2_addend;
+           }
+         else if (r_type == elfcpp::R_POWERPC_REL16_HA)
+           {
+             r_type = elfcpp::R_POWERPC_ADDR16_HA;
+             addend -= d_offset;
+           }
+         else if (r_type == elfcpp::R_POWERPC_REL16_LO)
+           {
+             r_type = elfcpp::R_POWERPC_ADDR16_LO;
+             addend -= d_offset + 4;
+           }
        }
       else
        gold_unreachable();
@@ -8386,7 +9259,7 @@ Target_powerpc<size, big_endian>::relocate_relocs(
                  else
                    {
                      r_type = elfcpp::R_POWERPC_NONE;
-                     offset -= 2 * big_endian;
+                     offset -= d_offset;
                    }
                  break;
                default:
@@ -8416,7 +9289,7 @@ Target_powerpc<size, big_endian>::relocate_relocs(
                  else
                    {
                      r_type = elfcpp::R_POWERPC_NONE;
-                     offset -= 2 * big_endian;
+                     offset -= d_offset;
                    }
                }
            }
@@ -8435,7 +9308,7 @@ Target_powerpc<size, big_endian>::relocate_relocs(
                  else
                    {
                      r_type = elfcpp::R_POWERPC_NONE;
-                     offset -= 2 * big_endian;
+                     offset -= d_offset;
                    }
                }
            }
@@ -8453,7 +9326,7 @@ Target_powerpc<size, big_endian>::relocate_relocs(
                  break;
                case tls::TLSOPT_TO_LE:
                  r_type = elfcpp::R_POWERPC_TPREL16_LO;
-                 offset += 2 * big_endian;
+                 offset += d_offset;
                  zap_next = true;
                  break;
                default:
@@ -8474,7 +9347,7 @@ Target_powerpc<size, big_endian>::relocate_relocs(
                  r_sym = os->symtab_index();
                  addend = dtp_offset;
                  r_type = elfcpp::R_POWERPC_TPREL16_LO;
-                 offset += 2 * big_endian;
+                 offset += d_offset;
                  zap_next = true;
                }
            }
@@ -8485,7 +9358,7 @@ Target_powerpc<size, big_endian>::relocate_relocs(
              if (this->optimize_tls_ie(final) == tls::TLSOPT_TO_LE)
                {
                  r_type = elfcpp::R_POWERPC_TPREL16_LO;
-                 offset += 2 * big_endian;
+                 offset += d_offset;
                }
            }
        }
This page took 0.049914 seconds and 4 git commands to generate.