+// Emit the start of a __tls_get_addr_opt plt call stub.
+
+template<int size, bool big_endian>
+bool
+Stub_table<size, big_endian>::build_tls_opt_head(
+ unsigned char** pp,
+ typename Plt_stub_entries::const_iterator cs)
+{
+ if (this->targ_->is_tls_get_addr_opt(cs->first.sym_))
+ {
+ unsigned char* p = *pp;
+ if (size == 64)
+ {
+ write_insn<big_endian>(p, ld_11_3 + 0);
+ p += 4;
+ write_insn<big_endian>(p, ld_12_3 + 8);
+ p += 4;
+ write_insn<big_endian>(p, mr_0_3);
+ p += 4;
+ write_insn<big_endian>(p, cmpdi_11_0);
+ p += 4;
+ write_insn<big_endian>(p, add_3_12_13);
+ p += 4;
+ write_insn<big_endian>(p, beqlr);
+ p += 4;
+ write_insn<big_endian>(p, mr_3_0);
+ p += 4;
+ if (cs->second.r2save_ && !cs->second.localentry0_)
+ {
+ write_insn<big_endian>(p, mflr_11);
+ p += 4;
+ write_insn<big_endian>(p, (std_11_1 + this->targ_->stk_linker()));
+ p += 4;
+ }
+ }
+ else
+ {
+ write_insn<big_endian>(p, lwz_11_3 + 0);
+ p += 4;
+ write_insn<big_endian>(p, lwz_12_3 + 4);
+ p += 4;
+ write_insn<big_endian>(p, mr_0_3);
+ p += 4;
+ write_insn<big_endian>(p, cmpwi_11_0);
+ p += 4;
+ write_insn<big_endian>(p, add_3_12_2);
+ p += 4;
+ write_insn<big_endian>(p, beqlr);
+ p += 4;
+ write_insn<big_endian>(p, mr_3_0);
+ p += 4;
+ write_insn<big_endian>(p, nop);
+ p += 4;
+ }
+ *pp = p;
+ return true;
+ }
+ return false;
+}
+
+// Emit the tail of a __tls_get_addr_opt plt call stub.
+
+template<int size, bool big_endian>
+bool
+Stub_table<size, big_endian>::build_tls_opt_tail(
+ unsigned char* p,
+ typename Plt_stub_entries::const_iterator cs)
+{
+ if (size == 64
+ && cs->second.r2save_
+ && !cs->second.localentry0_
+ && this->targ_->is_tls_get_addr_opt(cs->first.sym_))
+ {
+ write_insn<big_endian>(p, bctrl);
+ p += 4;
+ write_insn<big_endian>(p, ld_2_1 + this->targ_->stk_toc());
+ p += 4;
+ write_insn<big_endian>(p, ld_11_1 + this->targ_->stk_linker());
+ p += 4;
+ write_insn<big_endian>(p, mtlr_11);
+ p += 4;
+ write_insn<big_endian>(p, blr);
+ return true;
+ }
+ return false;
+}
+
+// Emit pc-relative plt call stub code.
+
+template<bool big_endian>
+static unsigned char*
+build_powerxx_offset(unsigned char* p, uint64_t off, uint64_t odd, bool load)
+{
+ uint64_t insn;
+ if (off - odd + (1ULL << 33) < 1ULL << 34)
+ {
+ off -= odd;
+ if (odd)
+ {
+ write_insn<big_endian>(p, nop);
+ p += 4;
+ }
+ if (load)
+ insn = pld_12_pc;
+ else
+ insn = paddi_12_pc;
+ insn |= d34(off);
+ write_insn<big_endian>(p, insn >> 32);
+ p += 4;
+ write_insn<big_endian>(p, insn & 0xffffffff);
+ }
+ else if (off - (8 - odd) + (0x20002ULL << 32) < 0x40004ULL << 32)
+ {
+ off -= 8 - odd;
+ write_insn<big_endian>(p, li_11_0 | (ha34(off) & 0xffff));
+ p += 4;
+ if (!odd)
+ {
+ write_insn<big_endian>(p, sldi_11_11_34);
+ p += 4;
+ }
+ insn = paddi_12_pc | d34(off);
+ write_insn<big_endian>(p, insn >> 32);
+ p += 4;
+ write_insn<big_endian>(p, insn & 0xffffffff);
+ p += 4;
+ if (odd)
+ {
+ write_insn<big_endian>(p, sldi_11_11_34);
+ p += 4;
+ }
+ if (load)
+ write_insn<big_endian>(p, ldx_12_11_12);
+ else
+ write_insn<big_endian>(p, add_12_11_12);
+ }
+ else
+ {
+ off -= odd + 8;
+ write_insn<big_endian>(p, lis_11 | ((ha34(off) >> 16) & 0x3fff));
+ p += 4;
+ write_insn<big_endian>(p, ori_11_11_0 | (ha34(off) & 0xffff));
+ p += 4;
+ if (odd)
+ {
+ write_insn<big_endian>(p, sldi_11_11_34);
+ p += 4;
+ }
+ insn = paddi_12_pc | d34(off);
+ write_insn<big_endian>(p, insn >> 32);
+ p += 4;
+ write_insn<big_endian>(p, insn & 0xffffffff);
+ p += 4;
+ if (!odd)
+ {
+ write_insn<big_endian>(p, sldi_11_11_34);
+ p += 4;
+ }
+ if (load)
+ write_insn<big_endian>(p, ldx_12_11_12);
+ else
+ write_insn<big_endian>(p, add_12_11_12);
+ }
+ p += 4;
+ return p;
+}
+
+// Gets the address of a label (1:) in r11 and builds an offset in r12,
+// then adds it to r11 (LOAD false) or loads r12 from r11+r12 (LOAD true).
+// mflr %r12
+// bcl 20,31,1f
+// 1: mflr %r11
+// mtlr %r12
+// lis %r12,xxx-1b@highest
+// ori %r12,%r12,xxx-1b@higher
+// sldi %r12,%r12,32
+// oris %r12,%r12,xxx-1b@high
+// ori %r12,%r12,xxx-1b@l
+// add/ldx %r12,%r11,%r12
+
+template<bool big_endian>
+static unsigned char*
+build_notoc_offset(unsigned char* p, uint64_t off, bool load)
+{
+ write_insn<big_endian>(p, mflr_12);
+ p += 4;
+ write_insn<big_endian>(p, bcl_20_31);
+ p += 4;
+ write_insn<big_endian>(p, mflr_11);
+ p += 4;
+ write_insn<big_endian>(p, mtlr_12);
+ p += 4;
+ if (off + 0x8000 < 0x10000)
+ {
+ if (load)
+ write_insn<big_endian>(p, ld_12_11 + l(off));
+ else
+ write_insn<big_endian>(p, addi_12_11 + l(off));
+ }
+ else if (off + 0x80008000ULL < 0x100000000ULL)
+ {
+ write_insn<big_endian>(p, addis_12_11 + ha(off));
+ p += 4;
+ if (load)
+ write_insn<big_endian>(p, ld_12_12 + l(off));
+ else
+ write_insn<big_endian>(p, addi_12_12 + l(off));
+ }
+ else
+ {
+ if (off + 0x800000000000ULL < 0x1000000000000ULL)
+ {
+ write_insn<big_endian>(p, li_12_0 + ((off >> 32) & 0xffff));
+ p += 4;
+ }
+ else
+ {
+ write_insn<big_endian>(p, lis_12 + ((off >> 48) & 0xffff));
+ p += 4;
+ if (((off >> 32) & 0xffff) != 0)
+ {
+ write_insn<big_endian>(p, ori_12_12_0 + ((off >> 32) & 0xffff));
+ p += 4;
+ }
+ }
+ if (((off >> 32) & 0xffffffffULL) != 0)
+ {
+ write_insn<big_endian>(p, sldi_12_12_32);
+ p += 4;
+ }
+ if (hi(off) != 0)
+ {
+ write_insn<big_endian>(p, oris_12_12_0 + hi(off));
+ p += 4;
+ }
+ if (l(off) != 0)
+ {
+ write_insn<big_endian>(p, ori_12_12_0 + l(off));
+ p += 4;
+ }
+ if (load)
+ write_insn<big_endian>(p, ldx_12_11_12);
+ else
+ write_insn<big_endian>(p, add_12_11_12);
+ }
+ p += 4;
+ return p;
+}
+
+// Size of a given plt call stub.
+
+template<int size, bool big_endian>
+unsigned int
+Stub_table<size, big_endian>::plt_call_size(
+ typename Plt_stub_entries::const_iterator p) const
+{
+ if (size == 32)
+ {
+ const Symbol* gsym = p->first.sym_;
+ return (4 * 4
+ + (this->targ_->is_tls_get_addr_opt(gsym) ? 8 * 4 : 0));
+ }
+
+ const Output_data_plt_powerpc<size, big_endian>* plt;
+ uint64_t plt_addr = this->plt_off(p, &plt);
+ plt_addr += plt->address();
+ unsigned int bytes = 0;
+ const Symbol* gsym = p->first.sym_;
+ if (this->targ_->is_tls_get_addr_opt(gsym))
+ {
+ if (p->second.r2save_ && !p->second.localentry0_)
+ bytes = 13 * 4;
+ else
+ bytes = 7 * 4;
+ }
+
+ if (p->second.r2save_)
+ bytes += 4;
+
+ if (this->targ_->powerxx_stubs())
+ {
+ uint64_t from = this->stub_address() + p->second.off_ + bytes;
+ if (bytes > 8 * 4)
+ from -= 4 * 4;
+ uint64_t odd = from & 4;
+ uint64_t off = plt_addr - from;
+ if (off - odd + (1ULL << 33) < 1ULL << 34)
+ bytes += odd + 4 * 4;
+ else if (off - (8 - odd) + (0x20002ULL << 32) < 0x40004ULL << 32)
+ bytes += 7 * 4;
+ else
+ bytes += 8 * 4;
+ return bytes;
+ }
+
+ if (p->second.notoc_)
+ {
+ uint64_t from = this->stub_address() + p->second.off_ + bytes + 2 * 4;
+ if (bytes > 32)
+ from -= 4 * 4;
+ uint64_t off = plt_addr - from;
+ if (off + 0x8000 < 0x10000)
+ bytes += 7 * 4;
+ else if (off + 0x80008000ULL < 0x100000000ULL)
+ bytes += 8 * 4;
+ else
+ {
+ bytes += 8 * 4;
+ if (off + 0x800000000000ULL >= 0x1000000000000ULL
+ && ((off >> 32) & 0xffff) != 0)
+ bytes += 4;
+ if (((off >> 32) & 0xffffffffULL) != 0)
+ bytes += 4;
+ if (hi(off) != 0)
+ bytes += 4;
+ if (l(off) != 0)
+ bytes += 4;
+ }
+ return bytes;
+ }
+
+ uint64_t got_addr = 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_);
+ got_addr += ppcobj->toc_base_offset();
+ uint64_t off = plt_addr - got_addr;
+ bytes += 3 * 4 + 4 * (ha(off) != 0);
+ if (this->targ_->abiversion() < 2)
+ {
+ bool static_chain = parameters->options().plt_static_chain();
+ bool thread_safe = this->targ_->plt_thread_safe();
+ bytes += (4
+ + 4 * static_chain
+ + 8 * thread_safe
+ + 4 * (ha(off + 8 + 8 * static_chain) != ha(off)));
+ }
+ return bytes;
+}
+
+// Return long branch stub size.
+
+template<int size, bool big_endian>
+unsigned int
+Stub_table<size, big_endian>::branch_stub_size(
+ typename Branch_stub_entries::const_iterator p,
+ bool* need_lt)
+{
+ Address loc = this->stub_address() + this->last_plt_size_ + p->second.off_;
+ if (size == 32)
+ {
+ if (p->first.dest_ - loc + (1 << 25) < 2 << 25)
+ return 4;
+ if (parameters->options().output_is_position_independent())
+ return 32;
+ return 16;
+ }
+
+ uint64_t off = p->first.dest_ - loc;
+ if (p->second.notoc_)
+ {
+ if (this->targ_->powerxx_stubs())
+ {
+ Address odd = loc & 4;
+ if (off + (1 << 25) < 2 << 25)
+ return odd + 12;
+ if (off - odd + (1ULL << 33) < 1ULL << 34)
+ return odd + 16;
+ if (off - (8 - odd) + (0x20002ULL << 32) < 0x40004ULL << 32)
+ return 28;
+ return 32;
+ }
+ off -= 8;
+ if (off + 0x8000 < 0x10000)
+ return 24;
+ if (off + 0x80008000ULL < 0x100000000ULL)
+ {
+ if (off + 24 + (1 << 25) < 2 << 25)
+ return 28;
+ return 32;
+ }
+ unsigned int bytes = 32;
+ if (off + 0x800000000000ULL >= 0x1000000000000ULL
+ && ((off >> 32) & 0xffff) != 0)
+ bytes += 4;
+ if (((off >> 32) & 0xffffffffULL) != 0)
+ bytes += 4;
+ if (hi(off) != 0)
+ bytes += 4;
+ if (l(off) != 0)
+ bytes += 4;
+ return bytes;
+ }
+
+ if (off + (1 << 25) < 2 << 25)
+ return 4;
+ if (!this->targ_->powerxx_stubs())
+ *need_lt = true;
+ return 16;
+}
+
+template<int size, bool big_endian>
+void
+Stub_table<size, big_endian>::plt_error(const Plt_stub_key& p)
+{
+ if (p.sym_)
+ gold_error(_("linkage table error against `%s'"),
+ p.sym_->demangled_name().c_str());
+ else
+ gold_error(_("linkage table error against `%s:[local %u]'"),
+ p.object_->name().c_str(),
+ p.locsym_);
+}
+