gdb/doc/
[deliverable/binutils-gdb.git] / gold / powerpc.cc
index 3f3041f9cec6bed614b7f1efae356f5948cd4cea..2a2277c6c870150594087e334947b6aa1462a38a 100644 (file)
@@ -71,7 +71,8 @@ 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), opd_valid_(false), opd_ent_(), access_from_map_()
+      special_(0), has_small_toc_reloc_(false), opd_valid_(false),
+      opd_ent_(), access_from_map_(), has14_(), stub_table_()
   { }
 
   ~Powerpc_relobj()
@@ -201,12 +202,6 @@ public:
   void
   do_read_relocs(Read_relocs_data*);
 
-  // Set up some symbols, then perform Sized_relobj_file method.
-  // Occurs after garbage collection, which is why opd info can't be
-  // set up here.
-  void
-  do_scan_relocs(Symbol_table*, Layout*, Read_relocs_data*);
-
   bool
   do_find_special_sections(Read_symbols_data* sd);
 
@@ -232,6 +227,14 @@ public:
   toc_base_offset() const
   { return 0x8000; }
 
+  void
+  set_has_small_toc_reloc()
+  { has_small_toc_reloc_ = true; }
+
+  bool
+  has_small_toc_reloc() const
+  { return has_small_toc_reloc_; }
+
   void
   set_has_14bit_branch(unsigned int shndx)
   {
@@ -286,6 +289,10 @@ private:
   // For 32-bit the .got2 section shdnx, for 64-bit the .opd section shndx.
   unsigned int special_;
 
+  // For 64-bit, whether this object uses small model relocs to access
+  // the toc.
+  bool has_small_toc_reloc_;
+
   // Set at the start of gc_process_relocs, when we know opd_ent_
   // vector is valid.  The flag could be made atomic and set in
   // do_read_relocs with memory_order_release and then tested with
@@ -329,7 +336,8 @@ class Target_powerpc : public Sized_target<size, big_endian>
       got_(NULL), plt_(NULL), iplt_(NULL), brlt_section_(NULL),
       glink_(NULL), rela_dyn_(NULL), copy_relocs_(elfcpp::R_POWERPC_COPY),
       dynbss_(NULL), tlsld_got_offset_(-1U),
-      stub_tables_(), branch_lookup_table_(), branch_info_()
+      stub_tables_(), branch_lookup_table_(), branch_info_(),
+      plt_thread_safe_(false)
   {
   }
 
@@ -403,6 +411,9 @@ class Target_powerpc : public Sized_target<size, big_endian>
   Stub_table<size, big_endian>*
   new_stub_table();
 
+  void
+  do_define_standard_symbols(Symbol_table*, Layout*);
+
   // Finalize the sections.
   void
   do_finalize_sections(Layout*, const Input_objects*, Symbol_table*);
@@ -606,16 +617,108 @@ class Target_powerpc : public Sized_target<size, big_endian>
       }
   }
 
+  bool
+  plt_thread_safe() const
+  { return this->plt_thread_safe_; }
+
  private:
 
+  class Track_tls
+  {
+  public:
+    enum Tls_get_addr
+    {
+      NOT_EXPECTED = 0,
+      EXPECTED = 1,
+      SKIP = 2,
+      NORMAL = 3
+    };
+
+    Track_tls()
+      : tls_get_addr_(NOT_EXPECTED),
+       relinfo_(NULL), relnum_(0), r_offset_(0)
+    { }
+
+    ~Track_tls()
+    {
+      if (this->tls_get_addr_ != NOT_EXPECTED)
+       this->missing();
+    }
+
+    void
+    missing(void)
+    {
+      if (this->relinfo_ != NULL)
+       gold_error_at_location(this->relinfo_, this->relnum_, this->r_offset_,
+                              _("missing expected __tls_get_addr call"));
+    }
+
+    void
+    expect_tls_get_addr_call(
+       const Relocate_info<size, big_endian>* relinfo,
+       size_t relnum,
+       Address r_offset)
+    {
+      this->tls_get_addr_ = EXPECTED;
+      this->relinfo_ = relinfo;
+      this->relnum_ = relnum;
+      this->r_offset_ = r_offset;
+    }
+
+    void
+    expect_tls_get_addr_call()
+    { this->tls_get_addr_ = EXPECTED; }
+
+    void
+    skip_next_tls_get_addr_call()
+    {this->tls_get_addr_ = SKIP; }
+
+    Tls_get_addr
+    maybe_skip_tls_get_addr_call(unsigned int r_type, const Symbol* gsym)
+    {
+      bool is_tls_call = ((r_type == elfcpp::R_POWERPC_REL24
+                          || r_type == elfcpp::R_PPC_PLTREL24)
+                         && gsym != NULL
+                         && strcmp(gsym->name(), "__tls_get_addr") == 0);
+      Tls_get_addr last_tls = this->tls_get_addr_;
+      this->tls_get_addr_ = NOT_EXPECTED;
+      if (is_tls_call && last_tls != EXPECTED)
+       return last_tls;
+      else if (!is_tls_call && last_tls != NOT_EXPECTED)
+       {
+         this->missing();
+         return EXPECTED;
+       }
+      return NORMAL;
+    }
+
+  private:
+    // What we're up to regarding calls to __tls_get_addr.
+    // On powerpc, the branch and link insn making a call to
+    // __tls_get_addr is marked with a relocation, R_PPC64_TLSGD,
+    // R_PPC64_TLSLD, R_PPC_TLSGD or R_PPC_TLSLD, in addition to the
+    // usual R_POWERPC_REL24 or R_PPC_PLTREL25 relocation on a call.
+    // The marker relocation always comes first, and has the same
+    // symbol as the reloc on the insn setting up the __tls_get_addr
+    // argument.  This ties the arg setup insn with the call insn,
+    // allowing ld to safely optimize away the call.  We check that
+    // every call to __tls_get_addr has a marker relocation, and that
+    // every marker relocation is on a call to __tls_get_addr.
+    Tls_get_addr tls_get_addr_;
+    // Info about the last reloc for error message.
+    const Relocate_info<size, big_endian>* relinfo_;
+    size_t relnum_;
+    Address r_offset_;
+  };
+
   // The class which scans relocations.
-  class Scan
+  class Scan : protected Track_tls
   {
   public:
     typedef typename elfcpp::Elf_types<size>::Elf_Addr Address;
 
     Scan()
-      : issued_non_pic_error_(false)
+      : Track_tls(), issued_non_pic_error_(false)
     { }
 
     static inline int
@@ -690,33 +793,17 @@ class Target_powerpc : public Sized_target<size, big_endian>
                    unsigned int *dest_shndx);
 
   // The class which implements relocation.
-  class Relocate
+  class Relocate : protected Track_tls
   {
    public:
     // Use 'at' branch hints when true, 'y' when false.
     // FIXME maybe: set this with an option.
     static const bool is_isa_v2 = true;
 
-    enum skip_tls
-    {
-      CALL_NOT_EXPECTED = 0,
-      CALL_EXPECTED = 1,
-      CALL_SKIP = 2
-    };
-
     Relocate()
-      : call_tls_get_addr_(CALL_NOT_EXPECTED)
+      : Track_tls()
     { }
 
-    ~Relocate()
-    {
-      if (this->call_tls_get_addr_ != CALL_NOT_EXPECTED)
-       {
-         // FIXME: This needs to specify the location somehow.
-         gold_error(_("missing expected __tls_get_addr call"));
-       }
-    }
-
     // Do a relocation.  Return false if the caller should not issue
     // any warnings about this relocation.
     inline bool
@@ -728,10 +815,6 @@ class Target_powerpc : public Sized_target<size, big_endian>
             unsigned char*,
             typename elfcpp::Elf_types<size>::Elf_Addr,
             section_size_type);
-
-    // This is set if we should skip the next reloc, which should be a
-    // call to __tls_get_addr.
-    enum skip_tls call_tls_get_addr_;
   };
 
   class Relocate_comdat_behavior
@@ -947,6 +1030,8 @@ class Target_powerpc : public Sized_target<size, big_endian>
 
   typedef std::vector<Branch_info> Branches;
   Branches branch_info_;
+
+  bool plt_thread_safe_;
 };
 
 template<>
@@ -1187,13 +1272,13 @@ use_plt_offset(const Symbol* gsym, int flags)
   if (gsym->is_from_dynobj())
     return true;
 
-  // If we are generating a shared object, and gsym symbol is
+  // If we are generating a shared object, and this symbol is
   // undefined or preemptible, we need to use the PLT entry.
   if (parameters->options().shared()
       && (gsym->is_undefined() || gsym->is_preemptible()))
     return true;
 
-  // If gsym is a call to a weak undefined symbol, we need to use
+  // If this is a call to a weak undefined symbol, we need to use
   // the PLT entry; the symbol may be defined by a library loaded
   // at runtime.
   if ((flags & Symbol::FUNCTION_CALL) && gsym->is_weak_undefined())
@@ -1540,13 +1625,13 @@ Powerpc_relobj<size, big_endian>::do_read_relocs(Read_relocs_data* rd)
     }
 }
 
-// Set up some symbols, then perform Sized_relobj_file method.
+// Set up some symbols.
 
 template<int size, bool big_endian>
 void
-Powerpc_relobj<size, big_endian>::do_scan_relocs(Symbol_table* symtab,
-                                                Layout* layout,
-                                                Read_relocs_data* rd)
+Target_powerpc<size, big_endian>::do_define_standard_symbols(
+    Symbol_table* symtab,
+    Layout* layout)
 {
   if (size == 32)
     {
@@ -1588,7 +1673,6 @@ Powerpc_relobj<size, big_endian>::do_scan_relocs(Symbol_table* symtab,
                                        0, false, false);
        }
     }
-  Sized_relobj_file<size, big_endian>::do_scan_relocs(symtab, layout, rd);
 }
 
 // Set up PowerPC target specific relobj.
@@ -1637,7 +1721,7 @@ public:
       symtab_(symtab), layout_(layout),
       header_ent_cnt_(size == 32 ? 3 : 1),
       header_index_(size == 32 ? 0x2000 : 0)
-  {}
+  { }
 
   class Got_entry;
 
@@ -2064,6 +2148,7 @@ Target_powerpc<size, big_endian>::Branch_info::make_stub(
            return;
          to = symval.value(this->object_, 0);
        }
+      to += this->addend_;
       if (stub_table == NULL)
        stub_table = this->object_->stub_table(this->shndx_);
       gold_assert(stub_table != NULL);
@@ -2093,17 +2178,41 @@ Target_powerpc<size, big_endian>::do_relax(int pass,
 {
   unsigned int prev_brlt_size = 0;
   if (pass == 1)
-    this->group_sections(layout, task);
-  else
     {
-      prev_brlt_size = this->branch_lookup_table_.size();
-      this->branch_lookup_table_.clear();
-      for (typename Stub_tables::iterator p = this->stub_tables_.begin();
-          p != this->stub_tables_.end();
-          ++p)
+      bool thread_safe = parameters->options().plt_thread_safe();
+      if (size == 64 && !parameters->options().user_set_plt_thread_safe())
        {
-         (*p)->clear_long_branch_stubs();
+         const char* const thread_starter[] =
+           {
+             "pthread_create",
+             /* libstdc++ */
+             "_ZNSt6thread15_M_start_threadESt10shared_ptrINS_10_Impl_baseEE",
+             /* librt */
+             "aio_init", "aio_read", "aio_write", "aio_fsync", "lio_listio",
+             "mq_notify", "create_timer",
+             /* libanl */
+             "getaddrinfo_a",
+             /* libgomp */
+             "GOMP_parallel_start",
+             "GOMP_parallel_loop_static_start",
+             "GOMP_parallel_loop_dynamic_start",
+             "GOMP_parallel_loop_guided_start",
+             "GOMP_parallel_loop_runtime_start",
+             "GOMP_parallel_sections_start", 
+           };
+
+         for (unsigned int i = 0;
+              i < sizeof(thread_starter) / sizeof(thread_starter[0]);
+              i++)
+           {
+             Symbol* sym = symtab->lookup(thread_starter[i], NULL);
+             thread_safe = sym != NULL && sym->in_reg() && sym->in_real_elf();
+             if (thread_safe)
+               break;
+           }
        }
+      this->plt_thread_safe_ = thread_safe;
+      this->group_sections(layout, task);
     }
 
   // We need address of stub tables valid for make_stub.
@@ -2119,6 +2228,20 @@ Target_powerpc<size, big_endian>::do_relax(int pass,
       (*p)->set_address_and_size(os, off);
     }
 
+  if (pass != 1)
+    {
+      // Clear plt call stubs, long branch stubs and branch lookup table.
+      prev_brlt_size = this->branch_lookup_table_.size();
+      this->branch_lookup_table_.clear();
+      for (typename Stub_tables::iterator p = this->stub_tables_.begin();
+          p != this->stub_tables_.end();
+          ++p)
+       {
+         (*p)->clear_stubs();
+       }
+    }
+
+  // Build all the stubs.
   Stub_table<size, big_endian>* ifunc_stub_table
     = this->stub_tables_.size() == 0 ? NULL : this->stub_tables_[0];
   Stub_table<size, big_endian>* one_stub_table
@@ -2130,6 +2253,7 @@ Target_powerpc<size, big_endian>::do_relax(int pass,
       b->make_stub(one_stub_table, ifunc_stub_table, symtab);
     }
 
+  // Did anything change size?
   unsigned int num_huge_branches = this->branch_lookup_table_.size();
   bool again = num_huge_branches != prev_brlt_size;
   if (size == 64 && num_huge_branches != 0)
@@ -2150,6 +2274,9 @@ Target_powerpc<size, big_endian>::do_relax(int pass,
        }
     }
 
+  // Set output section offsets for all input sections in an output
+  // section that just changed size.  Anything past the stubs will
+  // need updating.
   for (typename Output_sections::iterator p = os_need_update.begin();
        p != os_need_update.end();
        p++)
@@ -2345,10 +2472,12 @@ Output_data_plt_powerpc<size, big_endian>::add_local_ifunc_entry(
 }
 
 static const uint32_t add_0_11_11      = 0x7c0b5a14;
+static const uint32_t add_2_2_11       = 0x7c425a14;
 static const uint32_t add_3_3_2                = 0x7c631214;
 static const uint32_t add_3_3_13       = 0x7c636a14;
 static const uint32_t add_11_0_11      = 0x7d605a14;
 static const uint32_t add_12_2_11      = 0x7d825a14;
+static const uint32_t add_12_12_11     = 0x7d8c5a14;
 static const uint32_t addi_11_11       = 0x396b0000;
 static const uint32_t addi_12_12       = 0x398c0000;
 static const uint32_t addi_2_2         = 0x38420000;
@@ -2367,6 +2496,8 @@ static const uint32_t bcl_20_31           = 0x429f0005;
 static const uint32_t bctr             = 0x4e800420;
 static const uint32_t blr              = 0x4e800020;
 static const uint32_t blrl             = 0x4e800021;
+static const uint32_t bnectr_p4                = 0x4ce20420;
+static const uint32_t cmpldi_2_0       = 0x28220000;
 static const uint32_t cror_15_15_15    = 0x4def7b82;
 static const uint32_t cror_31_31_31    = 0x4ffffb82;
 static const uint32_t ld_0_1           = 0xe8010000;
@@ -2405,6 +2536,7 @@ static const uint32_t std_2_1             = 0xf8410000;
 static const uint32_t stfd_0_1         = 0xd8010000;
 static const uint32_t stvx_0_12_0      = 0x7c0c01ce;
 static const uint32_t sub_11_11_12     = 0x7d6c5850;
+static const uint32_t xor_11_11_11     = 0x7d6b5a78;
 
 // Write out the PLT.
 
@@ -2617,6 +2749,31 @@ Output_data_brlt_powerpc<size, big_endian>::do_write(Output_file* of)
     }
 }
 
+static inline uint32_t
+l(uint32_t a)
+{
+  return a & 0xffff;
+}
+
+static inline uint32_t
+hi(uint32_t a)
+{
+  return l(a >> 16);
+}
+
+static inline uint32_t
+ha(uint32_t a)
+{
+  return hi(a + 0x8000);
+}
+
+template<bool big_endian>
+static inline void
+write_insn(unsigned char* p, uint32_t v)
+{
+  elfcpp::Swap<32, big_endian>::writeval(p, v);
+}
+
 // Stub_table holds information about plt and long branch stubs.
 // Stubs are built in an area following some input section determined
 // by group_sections().  This input section is converted to a relaxed
@@ -2632,7 +2789,8 @@ class Stub_table : public Output_relaxed_input_section
   Stub_table(Target_powerpc<size, big_endian>* targ)
     : Output_relaxed_input_section(NULL, 0, 0),
       targ_(targ), plt_call_stubs_(), long_branch_stubs_(),
-      orig_data_size_(0), plt_size_(0), branch_size_(0), prev_size_(0)
+      orig_data_size_(0), plt_size_(0), last_plt_size_(0),
+      branch_size_(0), last_branch_size_(0)
   { }
 
   // Delayed Output_relaxed_input_section init.
@@ -2680,8 +2838,10 @@ class Stub_table : public Output_relaxed_input_section
   find_long_branch_entry(const Powerpc_relobj<size, big_endian>*, Address);
 
   void
-  clear_long_branch_stubs()
+  clear_stubs()
   {
+    this->plt_call_stubs_.clear();
+    this->plt_size_ = 0;
     this->long_branch_stubs_.clear();
     this->branch_size_ = 0;
   }
@@ -2742,42 +2902,76 @@ class Stub_table : public Output_relaxed_input_section
        // a suitably aligned address.
        os->checkpoint_set_addralign(this->stub_align());
       }
-    if (this->prev_size_ != this->plt_size_ + this->branch_size_)
+    if (this->last_plt_size_ != this->plt_size_
+       || this->last_branch_size_ != this->branch_size_)
       {
-       this->prev_size_ = this->plt_size_ + this->branch_size_;
+       this->last_plt_size_ = this->plt_size_;
+       this->last_branch_size_ = this->branch_size_;
        return true;
       }
     return false;
   }
 
-  section_size_type
-  prev_size() const
-  { return this->prev_size_; }
-
-  void
-  set_prev_size(section_size_type val)
-  { this->prev_size_ = val; }
-
   Target_powerpc<size, big_endian>*
   targ() const
   { return targ_; }
 
  private:
+  class Plt_stub_ent;
+  class Plt_stub_ent_hash;
+  typedef Unordered_map<Plt_stub_ent, unsigned int,
+                       Plt_stub_ent_hash> Plt_stub_entries;
+
+  // Alignment of stub section.
   unsigned int
-  stub_align()
-  { return size == 32 ? 16 : 32; }
+  stub_align() const
+  {
+    if (size == 32)
+      return 16;
+    unsigned int min_align = 32;
+    unsigned int user_align = 1 << parameters->options().plt_align();
+    return std::max(user_align, min_align);
+  }
 
-  // We keep plt stubs aligned, so no fancy sizing.
+  // Size of a given plt call stub.
   unsigned int
-  plt_call_size() const
-  { return size == 32 ? 16 : 32; }
+  plt_call_size(typename Plt_stub_entries::const_iterator p) const
+  {
+    if (size == 32)
+      return 16;
+
+    Address pltaddr = p->second;
+    if (p->first.sym_ == NULL 
+       || (p->first.sym_->type() == elfcpp::STT_GNU_IFUNC
+           && p->first.sym_->can_use_relative_reloc(false)))
+      pltaddr += this->targ_->iplt_section()->address();
+    else
+      pltaddr += this->targ_->plt_section()->address();
+    Address tocbase = this->targ_->got_section()->output_section()->address();
+    const Powerpc_relobj<size, big_endian>* ppcobj = static_cast
+      <const Powerpc_relobj<size, big_endian>*>(p->first.object_);
+    tocbase += ppcobj->toc_base_offset();
+    Address off = pltaddr - tocbase;
+    bool static_chain = parameters->options().plt_static_chain();
+    bool thread_safe = this->targ_->plt_thread_safe();
+    unsigned int bytes = (4 * 5
+                         + 4 * static_chain
+                         + 8 * thread_safe
+                         + 4 * (ha(off) != 0)
+                         + 4 * (ha(off + 8 + 8 * static_chain) != ha(off)));
+    unsigned int align = 1 << parameters->options().plt_align();
+    if (align > 1)
+      bytes = (bytes + align - 1) & -align;
+    return bytes;
+  }
 
   // Return long branch stub size.
   unsigned int
   branch_stub_size(Address to)
   {
-    Address loc = this->stub_address() + this->plt_size_ + this->branch_size_;
-    if (loc - to + (1 << 25) < 2 << 25)
+    Address loc
+      = this->stub_address() + this->last_plt_size_ + this->branch_size_;
+    if (to - loc + (1 << 25) < 2 << 25)
       return 4;
     if (size == 64 || !parameters->options().output_is_position_independent())
       return 16;
@@ -2889,8 +3083,6 @@ class Stub_table : public Output_relaxed_input_section
   // In a sane world this would be a global.
   Target_powerpc<size, big_endian>* targ_;
   // Map sym/object/addend to stub offset.
-  typedef Unordered_map<Plt_stub_ent, unsigned int,
-                       Plt_stub_ent_hash> Plt_stub_entries;
   Plt_stub_entries plt_call_stubs_;
   // Map destination address to stub offset.
   typedef Unordered_map<Branch_stub_ent, unsigned int,
@@ -2899,7 +3091,7 @@ class Stub_table : public Output_relaxed_input_section
   // size of input section
   section_size_type orig_data_size_;
   // size of stubs
-  section_size_type plt_size_, branch_size_, prev_size_;
+  section_size_type plt_size_, last_plt_size_, branch_size_, last_branch_size_;
 };
 
 // Make a new stub table, and record.
@@ -2947,8 +3139,10 @@ Stub_table<size, big_endian>::add_plt_call_entry(
 {
   Plt_stub_ent ent(object, gsym, r_type, addend);
   Address off = this->plt_size_;
-  if (this->plt_call_stubs_.insert(std::make_pair(ent, off)).second)
-    this->plt_size_ = off + this->plt_call_size();
+  std::pair<typename Plt_stub_entries::iterator, bool> p
+    = this->plt_call_stubs_.insert(std::make_pair(ent, off));
+  if (p.second)
+    this->plt_size_ = off + this->plt_call_size(p.first);
 }
 
 template<int size, bool big_endian>
@@ -2961,8 +3155,10 @@ Stub_table<size, big_endian>::add_plt_call_entry(
 {
   Plt_stub_ent ent(object, locsym_index, r_type, addend);
   Address off = this->plt_size_;
-  if (this->plt_call_stubs_.insert(std::make_pair(ent, off)).second)
-    this->plt_size_ = off + this->plt_call_size();
+  std::pair<typename Plt_stub_entries::iterator, bool> p
+    = this->plt_call_stubs_.insert(std::make_pair(ent, off));
+  if (p.second)
+    this->plt_size_ = off + this->plt_call_size(p.first);
 }
 
 // Find a plt call stub.
@@ -3108,31 +3304,6 @@ Output_data_glink<size, big_endian>::set_final_data_size()
   this->set_data_size(total);
 }
 
-static inline uint32_t
-l(uint32_t a)
-{
-  return a & 0xffff;
-}
-
-static inline uint32_t
-hi(uint32_t a)
-{
-  return l(a >> 16);
-}
-
-static inline uint32_t
-ha(uint32_t a)
-{
-  return hi(a + 0x8000);
-}
-
-template<bool big_endian>
-static inline void
-write_insn(unsigned char* p, uint32_t v)
-{
-  elfcpp::Swap<32, big_endian>::writeval(p, v);
-}
-
 // Write out plt and long branch stub code.
 
 template<int size, bool big_endian>
@@ -3150,9 +3321,6 @@ Stub_table<size, big_endian>::do_write(Output_file* of)
   unsigned char* const oview = of->get_output_view(off, oview_size);
   unsigned char* p;
 
-  typedef typename elfcpp::Elf_types<size>::Elf_Addr Address;
-  static const Address invalid_address = static_cast<Address>(0) - 1;
-
   if (size == 64)
     {
       const Output_data_got_powerpc<size, big_endian>* got
@@ -3171,14 +3339,14 @@ Stub_table<size, big_endian>::do_write(Output_file* of)
               cs != this->plt_call_stubs_.end();
               ++cs)
            {
-             Address plt_addr;
+             Address pltoff;
              bool is_ifunc;
              const Symbol* gsym = cs->first.sym_;
              if (gsym != NULL)
                {
                  is_ifunc = (gsym->type() == elfcpp::STT_GNU_IFUNC
                              && gsym->can_use_relative_reloc(false));
-                 plt_addr = gsym->plt_offset();
+                 pltoff = gsym->plt_offset();
                }
              else
                {
@@ -3186,8 +3354,9 @@ Stub_table<size, big_endian>::do_write(Output_file* of)
                  const Sized_relobj_file<size, big_endian>* relobj
                    = cs->first.object_;
                  unsigned int local_sym_index = cs->first.locsym_;
-                 plt_addr = relobj->local_plt_offset(local_sym_index);
+                 pltoff = relobj->local_plt_offset(local_sym_index);
                }
+             Address plt_addr = pltoff;
              if (is_ifunc)
                {
                  if (iplt_base == invalid_address)
@@ -3199,43 +3368,86 @@ Stub_table<size, big_endian>::do_write(Output_file* of)
              const Powerpc_relobj<size, big_endian>* ppcobj = static_cast
                <const Powerpc_relobj<size, big_endian>*>(cs->first.object_);
              Address got_addr = got_os_addr + ppcobj->toc_base_offset();
-             Address pltoff = plt_addr - got_addr;
+             Address off = plt_addr - got_addr;
 
-             if (pltoff + 0x80008000 > 0xffffffff || (pltoff & 7) != 0)
+             if (off + 0x80008000 > 0xffffffff || (off & 7) != 0)
                gold_error(_("%s: linkage table error against `%s'"),
                           cs->first.object_->name().c_str(),
                           cs->first.sym_->demangled_name().c_str());
 
+             bool static_chain = parameters->options().plt_static_chain();
+             bool thread_safe = this->targ_->plt_thread_safe();
+             bool use_fake_dep = false;
+             Address cmp_branch_off = 0;
+             if (thread_safe)
+               {
+                 unsigned int pltindex
+                   = ((pltoff - this->targ_->first_plt_entry_offset())
+                      / this->targ_->plt_entry_size());
+                 Address glinkoff
+                   = (this->targ_->glink_section()->pltresolve_size
+                      + pltindex * 8);
+                 if (pltindex > 32768)
+                   glinkoff += (pltindex - 32768) * 4;
+                 Address to
+                   = this->targ_->glink_section()->address() + glinkoff;
+                 Address from
+                   = (this->stub_address() + cs->second + 24
+                      + 4 * (ha(off) != 0)
+                      + 4 * (ha(off + 8 + 8 * static_chain) != ha(off))
+                      + 4 * static_chain);
+                 cmp_branch_off = to - from;
+                 use_fake_dep = cmp_branch_off + (1 << 25) >= (1 << 26);
+               }
+
              p = oview + cs->second;
-             if (ha(pltoff) != 0)
+             if (ha(off) != 0)
                {
-                 write_insn<big_endian>(p, addis_12_2 + ha(pltoff)),   p += 4;
                  write_insn<big_endian>(p, std_2_1 + 40),              p += 4;
-                 write_insn<big_endian>(p, ld_11_12 + l(pltoff)),      p += 4;
-                 if (ha(pltoff + 16) != ha(pltoff))
+                 write_insn<big_endian>(p, addis_12_2 + ha(off)),      p += 4;
+                 write_insn<big_endian>(p, ld_11_12 + l(off)),         p += 4;
+                 if (ha(off + 8 + 8 * static_chain) != ha(off))
                    {
-                     write_insn<big_endian>(p, addi_12_12 + l(pltoff)),p += 4;
-                     pltoff = 0;
+                     write_insn<big_endian>(p, addi_12_12 + l(off)),   p += 4;
+                     off = 0;
                    }
                  write_insn<big_endian>(p, mtctr_11),                  p += 4;
-                 write_insn<big_endian>(p, ld_2_12 + l(pltoff + 8)),   p += 4;
-                 write_insn<big_endian>(p, ld_11_12 + l(pltoff + 16)), p += 4;
-                 write_insn<big_endian>(p, bctr);
+                 if (use_fake_dep)
+                   {
+                     write_insn<big_endian>(p, xor_11_11_11),          p += 4;
+                     write_insn<big_endian>(p, add_12_12_11),          p += 4;
+                   }
+                 write_insn<big_endian>(p, ld_2_12 + l(off + 8)),      p += 4;
+                 if (static_chain)
+                   write_insn<big_endian>(p, ld_11_12 + l(off + 16)),  p += 4;
                }
              else
                {
                  write_insn<big_endian>(p, std_2_1 + 40),              p += 4;
-                 write_insn<big_endian>(p, ld_11_2 + l(pltoff)),       p += 4;
-                 if (ha(pltoff + 16) != ha(pltoff))
+                 write_insn<big_endian>(p, ld_11_2 + l(off)),  p += 4;
+                 if (ha(off + 8 + 8 * static_chain) != ha(off))
                    {
-                     write_insn<big_endian>(p, addi_2_2 + l(pltoff)),  p += 4;
-                     pltoff = 0;
+                     write_insn<big_endian>(p, addi_2_2 + l(off)),     p += 4;
+                     off = 0;
                    }
                  write_insn<big_endian>(p, mtctr_11),                  p += 4;
-                 write_insn<big_endian>(p, ld_11_2 + l(pltoff + 16)),  p += 4;
-                 write_insn<big_endian>(p, ld_2_2 + l(pltoff + 8)),    p += 4;
-                 write_insn<big_endian>(p, bctr);
+                 if (use_fake_dep)
+                   {
+                     write_insn<big_endian>(p, xor_11_11_11),          p += 4;
+                     write_insn<big_endian>(p, add_2_2_11),            p += 4;
+                   }
+                 if (static_chain)
+                   write_insn<big_endian>(p, ld_11_2 + l(off + 16)),   p += 4;
+                 write_insn<big_endian>(p, ld_2_2 + l(off + 8)),       p += 4;
+               }
+             if (thread_safe && !use_fake_dep)
+               {
+                 write_insn<big_endian>(p, cmpldi_2_0),                p += 4;
+                 write_insn<big_endian>(p, bnectr_p4),                 p += 4;
+                 write_insn<big_endian>(p, b | (cmp_branch_off & 0x3fffffc));
                }
+             else
+               write_insn<big_endian>(p, bctr);
            }
        }
 
@@ -3340,17 +3552,17 @@ Stub_table<size, big_endian>::do_write(Output_file* of)
                      got_addr = g_o_t;
                    }
 
-                 Address pltoff = plt_addr - got_addr;
-                 if (ha(pltoff) == 0)
+                 Address off = plt_addr - got_addr;
+                 if (ha(off) == 0)
                    {
-                     write_insn<big_endian>(p +  0, lwz_11_30 + l(pltoff));
+                     write_insn<big_endian>(p +  0, lwz_11_30 + l(off));
                      write_insn<big_endian>(p +  4, mtctr_11);
                      write_insn<big_endian>(p +  8, bctr);
                    }
                  else
                    {
-                     write_insn<big_endian>(p +  0, addis_11_30 + ha(pltoff));
-                     write_insn<big_endian>(p +  4, lwz_11_11 + l(pltoff));
+                     write_insn<big_endian>(p +  0, addis_11_30 + ha(off));
+                     write_insn<big_endian>(p +  4, lwz_11_11 + l(off));
                      write_insn<big_endian>(p +  8, mtctr_11);
                      write_insn<big_endian>(p + 12, bctr);
                    }
@@ -4254,6 +4466,25 @@ Target_powerpc<size, big_endian>::Scan::local(
     const elfcpp::Sym<size, big_endian>& lsym,
     bool is_discarded)
 {
+  this->maybe_skip_tls_get_addr_call(r_type, NULL);
+
+  if ((size == 64 && r_type == elfcpp::R_PPC64_TLSGD)
+      || (size == 32 && r_type == elfcpp::R_PPC_TLSGD))
+    {
+      this->expect_tls_get_addr_call();
+      const tls::Tls_optimization tls_type = target->optimize_tls_gd(true);
+      if (tls_type != tls::TLSOPT_NONE)
+       this->skip_next_tls_get_addr_call();
+    }
+  else if ((size == 64 && r_type == elfcpp::R_PPC64_TLSLD)
+          || (size == 32 && r_type == elfcpp::R_PPC_TLSLD))
+    {
+      this->expect_tls_get_addr_call();
+      const tls::Tls_optimization tls_type = target->optimize_tls_ld();
+      if (tls_type != tls::TLSOPT_NONE)
+       this->skip_next_tls_get_addr_call();
+    }
+
   Powerpc_relobj<size, big_endian>* ppc_object
     = static_cast<Powerpc_relobj<size, big_endian>*>(object);
 
@@ -4558,6 +4789,21 @@ Target_powerpc<size, big_endian>::Scan::local(
       unsupported_reloc_local(object, r_type);
       break;
     }
+
+  switch (r_type)
+    {
+    case elfcpp::R_POWERPC_GOT_TLSLD16:
+    case elfcpp::R_POWERPC_GOT_TLSGD16:
+    case elfcpp::R_POWERPC_GOT_TPREL16:
+    case elfcpp::R_POWERPC_GOT_DTPREL16:
+    case elfcpp::R_POWERPC_GOT16:
+    case elfcpp::R_PPC64_GOT16_DS:
+    case elfcpp::R_PPC64_TOC16:
+    case elfcpp::R_PPC64_TOC16_DS:
+      ppc_object->set_has_small_toc_reloc();
+    default:
+      break;
+    }
 }
 
 // Report an unsupported relocation against a global symbol.
@@ -4588,6 +4834,27 @@ Target_powerpc<size, big_endian>::Scan::global(
     unsigned int r_type,
     Symbol* gsym)
 {
+  if (this->maybe_skip_tls_get_addr_call(r_type, gsym) == Track_tls::SKIP)
+    return;
+
+  if ((size == 64 && r_type == elfcpp::R_PPC64_TLSGD)
+      || (size == 32 && r_type == elfcpp::R_PPC_TLSGD))
+    {
+      this->expect_tls_get_addr_call();
+      const bool final = gsym->final_value_is_known();
+      const tls::Tls_optimization tls_type = target->optimize_tls_gd(final);
+      if (tls_type != tls::TLSOPT_NONE)
+       this->skip_next_tls_get_addr_call();
+    }
+  else if ((size == 64 && r_type == elfcpp::R_PPC64_TLSLD)
+          || (size == 32 && r_type == elfcpp::R_PPC_TLSLD))
+    {
+      this->expect_tls_get_addr_call();
+      const tls::Tls_optimization tls_type = target->optimize_tls_ld();
+      if (tls_type != tls::TLSOPT_NONE)
+       this->skip_next_tls_get_addr_call();
+    }
+
   Powerpc_relobj<size, big_endian>* ppc_object
     = static_cast<Powerpc_relobj<size, big_endian>*>(object);
 
@@ -4994,6 +5261,21 @@ Target_powerpc<size, big_endian>::Scan::global(
       unsupported_reloc_global(object, r_type, gsym);
       break;
     }
+
+  switch (r_type)
+    {
+    case elfcpp::R_POWERPC_GOT_TLSLD16:
+    case elfcpp::R_POWERPC_GOT_TLSGD16:
+    case elfcpp::R_POWERPC_GOT_TPREL16:
+    case elfcpp::R_POWERPC_GOT_DTPREL16:
+    case elfcpp::R_POWERPC_GOT16:
+    case elfcpp::R_PPC64_GOT16_DS:
+    case elfcpp::R_PPC64_TOC16:
+    case elfcpp::R_PPC64_TOC16_DS:
+      ppc_object->set_has_small_toc_reloc();
+    default:
+      break;
+    }
 }
 
 // Process relocations for gc.
@@ -5214,6 +5496,31 @@ Target_powerpc<size, big_endian>::define_save_restore_funcs(
     }
 }
 
+// Sort linker created .got section first (for the header), then input
+// sections belonging to files using small model code.
+
+template<bool big_endian>
+class Sort_toc_sections
+{
+ public:
+  bool
+  operator()(const Output_section::Input_section& is1,
+            const Output_section::Input_section& is2) const
+  {
+    if (!is1.is_input_section() && is2.is_input_section())
+      return true;
+    bool small1
+      = (is1.is_input_section()
+        && (static_cast<const Powerpc_relobj<64, big_endian>*>(is1.relobj())
+            ->has_small_toc_reloc()));
+    bool small2
+      = (is2.is_input_section()
+        && (static_cast<const Powerpc_relobj<64, big_endian>*>(is2.relobj())
+            ->has_small_toc_reloc()));
+    return small1 && !small2;
+  }
+};
+
 // Finalize the sections.
 
 template<int size, bool big_endian>
@@ -5267,6 +5574,15 @@ Target_powerpc<size, big_endian>::do_finalize_sections(
          // need to mess with the relaxation machinery checkpointing.
          this->got_section(symtab, layout);
          this->make_brlt_section(layout);
+
+         if (parameters->options().toc_sort())
+           {
+             Output_section* os = this->got_->output_section();
+             if (os != NULL && os->input_sections().size() > 1)
+               std::stable_sort(os->input_sections().begin(),
+                                os->input_sections().end(),
+                                Sort_toc_sections<big_endian>());
+           }
        }
     }
 
@@ -5308,6 +5624,33 @@ 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.
+
+static bool
+ok_lo_toc_insn(uint32_t insn)
+{
+  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 */);
+}
+
 // Return the value to use for a branch relocation.
 
 template<int size, bool big_endian>
@@ -5365,23 +5708,20 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
     Address address,
     section_size_type view_size)
 {
-  bool is_tls_call = ((r_type == elfcpp::R_POWERPC_REL24
-                      || r_type == elfcpp::R_PPC_PLTREL24)
-                     && gsym != NULL
-                     && strcmp(gsym->name(), "__tls_get_addr") == 0);
-  enum skip_tls last_tls = this->call_tls_get_addr_;
-  this->call_tls_get_addr_ = CALL_NOT_EXPECTED;
-  if (is_tls_call)
+  switch (this->maybe_skip_tls_get_addr_call(r_type, gsym))
     {
-      if (last_tls == CALL_NOT_EXPECTED)
-       gold_error_at_location(relinfo, relnum, rela.get_r_offset(),
-                              _("__tls_get_addr call lacks marker reloc"));
-      else if (last_tls == CALL_SKIP)
-       return false;
+    case Track_tls::NOT_EXPECTED:
+      gold_error_at_location(relinfo, relnum, rela.get_r_offset(),
+                            _("__tls_get_addr call lacks marker reloc"));
+      break;
+    case Track_tls::EXPECTED:
+      // We have already complained.
+      break;
+    case Track_tls::SKIP:
+      return true;
+    case Track_tls::NORMAL:
+      break;
     }
-  else if (last_tls != CALL_NOT_EXPECTED)
-    gold_error_at_location(relinfo, relnum, rela.get_r_offset(),
-                          _("missing expected __tls_get_addr call"));
 
   typedef Powerpc_relocate_functions<size, big_endian> Reloc;
   typedef typename elfcpp::Swap<32, big_endian>::Valtype Insn;
@@ -5678,7 +6018,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
     {
       // Second instruction of a global dynamic sequence,
       // the __tls_get_addr call
-      this->call_tls_get_addr_ = CALL_EXPECTED;
+      this->expect_tls_get_addr_call(relinfo, relnum, rela.get_r_offset());
       const bool final = gsym == NULL || gsym->final_value_is_known();
       const tls::Tls_optimization tls_type = target->optimize_tls_gd(final);
       if (tls_type != tls::TLSOPT_NONE)
@@ -5701,7 +6041,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
              view += 2 * big_endian;
              value = psymval->value(object, rela.get_r_addend());
            }
-         this->call_tls_get_addr_ = CALL_SKIP;
+         this->skip_next_tls_get_addr_call();
        }
     }
   else if ((size == 64 && r_type == elfcpp::R_PPC64_TLSLD)
@@ -5709,14 +6049,14 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
     {
       // Second instruction of a local dynamic sequence,
       // the __tls_get_addr call
-      this->call_tls_get_addr_ = CALL_EXPECTED;
+      this->expect_tls_get_addr_call(relinfo, relnum, rela.get_r_offset());
       const tls::Tls_optimization tls_type = target->optimize_tls_ld();
       if (tls_type == tls::TLSOPT_TO_LE)
        {
          Insn* iview = reinterpret_cast<Insn*>(view);
          Insn insn = addi_3_3;
          elfcpp::Swap<32, big_endian>::writeval(iview, insn);
-         this->call_tls_get_addr_ = CALL_SKIP;
+         this->skip_next_tls_get_addr_call();
          r_type = elfcpp::R_POWERPC_TPREL16_LO;
          view += 2 * big_endian;
          value = dtp_offset;
@@ -5888,6 +6228,75 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
       break;
     }
 
+  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;
+
+       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:
+         if (parameters->options().toc_optimize())
+           {
+             Insn* iview = reinterpret_cast<Insn*>(view - 2 * big_endian);
+             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)
+               {
+                 elfcpp::Swap<32, big_endian>::writeval(iview, nop);
+                 return true;
+               }
+           }
+         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:
+         if (parameters->options().toc_optimize())
+           {
+             Insn* iview = reinterpret_cast<Insn*>(view - 2 * big_endian);
+             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)
+               {
+                 if ((insn & (0x3f << 26)) == 12u << 26 /* addic */)
+                   {
+                     // Transform addic to addi when we change reg.
+                     insn &= ~((0x3f << 26) | (0x1f << 16));
+                     insn |= (14u << 26) | (2 << 16);
+                   }
+                 else
+                   {
+                     insn &= ~(0x1f << 16);
+                     insn |= 2 << 16;
+                   }
+                 elfcpp::Swap<32, big_endian>::writeval(iview, insn);
+               }
+           }
+         break;
+       }
+    }
+
   typename Reloc::Overflow_check overflow = Reloc::CHECK_NONE;
   switch (r_type)
     {
@@ -6714,7 +7123,8 @@ class Target_selector_powerpc : public Target_selector
 {
 public:
   Target_selector_powerpc()
-    : Target_selector(elfcpp::EM_NONE, size, big_endian,
+    : Target_selector(size == 64 ? elfcpp::EM_PPC64 : elfcpp::EM_PPC,
+                     size, big_endian,
                      (size == 64
                       ? (big_endian ? "elf64-powerpc" : "elf64-powerpcle")
                       : (big_endian ? "elf32-powerpc" : "elf32-powerpcle")),
@@ -6723,28 +7133,6 @@ public:
                       : (big_endian ? "elf32ppc" : "elf32lppc")))
   { }
 
-  virtual Target*
-  do_recognize(Input_file*, off_t, int machine, int, int)
-  {
-    switch (size)
-      {
-      case 64:
-       if (machine != elfcpp::EM_PPC64)
-         return NULL;
-       break;
-
-      case 32:
-       if (machine != elfcpp::EM_PPC)
-         return NULL;
-       break;
-
-      default:
-       return NULL;
-      }
-
-    return this->instantiate_target();
-  }
-
   virtual Target*
   do_instantiate_target()
   { return new Target_powerpc<size, big_endian>(); }
@@ -6755,4 +7143,14 @@ Target_selector_powerpc<32, false> target_selector_ppc32le;
 Target_selector_powerpc<64, true> target_selector_ppc64;
 Target_selector_powerpc<64, false> target_selector_ppc64le;
 
+// Instantiate these constants for -O0
+template<int size, bool big_endian>
+const int Output_data_glink<size, big_endian>::pltresolve_size;
+template<int size, bool big_endian>
+const typename Stub_table<size, big_endian>::Address
+  Stub_table<size, big_endian>::invalid_address;
+template<int size, bool big_endian>
+const typename Target_powerpc<size, big_endian>::Address
+  Target_powerpc<size, big_endian>::invalid_address;
+
 } // End anonymous namespace.
This page took 0.03751 seconds and 4 git commands to generate.