Retry powerpc gold stub grouping when groups prove too large

An unusually large number of stubs can result in the default section
group size being too large;  sections plus stubs exceed the range of a
branch.  Restarting the relaxation pass with a smaller group size can
sometimes help.

	* powerpc.cc (struct Stub_table_owner): New.
	(Powerpc_relobj): Rename stub_table_ to stub_table_index_, an
	unsigned int vector.  Update all references.
	(powerpc_relobj::set_stub_table): Take an unsigned int param
	rather than a Stub_table.  Update callers.
	(Powerpc_relobj::clear_stub_table): New function.
	(Target_powerpc): Add relax_failed_, relax_fail_count_ and
	stub_group_size_ vars.
	(Target_powerpc::new_stub_table): Delete.
	(max_branch_delta): New function, extracted from..
	(Target_powerpc::Relocate::relocate): ..here..
	(Target_powerpc::Branch_info::make_stub): ..and here.  Return
	status on whether stub created successfully.
	(Stub_control::Stub_control): Add "no_size_errors" param.  Move
	default sizing to..
	(Target_powerpc::do_relax): ..here.  Init stub_group_size_ and
	reduce on relax failure.
	(Target_powerpc::group_sections): Add "no_size_errors" param.
	Use stub_group_size_.  Set up group info in a temp vector,
	before building Stub_table vector.  Account for input sections
	possibly already converted to relaxed sections.
	(Stub_table::init): Delete.  Merge into..
	(Stub_table::Stub_table): ..here.
	(Stub_table::can_reach_stub): New function.
	(Stub_table::add_plt_call_entry): Add "from" parameter and
	return true iff stub could be reached.
	(Stub_table::add_long_branch_entry): Similarly.  Add "r_type"
	param too.
	(Stub_table::clear_stubs): Add "all" param.
This commit is contained in:
Alan Modra 2014-11-26 11:10:29 +10:30
parent aa4893954a
commit a3e60ddb0b
2 changed files with 243 additions and 132 deletions

View file

@ -1,3 +1,35 @@
2014-11-26 Alan Modra <amodra@gmail.com>
* powerpc.cc (struct Stub_table_owner): New.
(Powerpc_relobj): Rename stub_table_ to stub_table_index_, an
unsigned int vector. Update all references.
(powerpc_relobj::set_stub_table): Take an unsigned int param
rather than a Stub_table. Update callers.
(Powerpc_relobj::clear_stub_table): New function.
(Target_powerpc): Add relax_failed_, relax_fail_count_ and
stub_group_size_ vars.
(Target_powerpc::new_stub_table): Delete.
(max_branch_delta): New function, extracted from..
(Target_powerpc::Relocate::relocate): ..here..
(Target_powerpc::Branch_info::make_stub): ..and here. Return
status on whether stub created successfully.
(Stub_control::Stub_control): Add "no_size_errors" param. Move
default sizing to..
(Target_powerpc::do_relax): ..here. Init stub_group_size_ and
reduce on relax failure.
(Target_powerpc::group_sections): Add "no_size_errors" param.
Use stub_group_size_. Set up group info in a temp vector,
before building Stub_table vector. Account for input sections
possibly already converted to relaxed sections.
(Stub_table::init): Delete. Merge into..
(Stub_table::Stub_table): ..here.
(Stub_table::can_reach_stub): New function.
(Stub_table::add_plt_call_entry): Add "from" parameter and
return true iff stub could be reached.
(Stub_table::add_long_branch_entry): Similarly. Add "r_type"
param too.
(Stub_table::clear_stubs): Add "all" param.
2014-11-26 Alan Modra <amodra@gmail.com>
* powerpc.cc (Stub_control::set_output_and_owner): New function.

View file

@ -62,6 +62,15 @@ class Output_data_glink;
template<int size, bool big_endian>
class Stub_table;
template<int size, bool big_endian>
class Target_powerpc;
struct Stub_table_owner
{
Output_section* output_section;
const Output_section::Input_section* owner;
};
inline bool
is_branch_reloc(unsigned int r_type);
@ -77,7 +86,7 @@ public:
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_(),
opd_ent_(), access_from_map_(), has14_(), stub_table_index_(),
e_flags_(ehdr.get_e_flags()), st_other_()
{
this->set_abiversion(0);
@ -260,21 +269,34 @@ public:
{ return shndx < this->has14_.size() && this->has14_[shndx]; }
void
set_stub_table(unsigned int shndx, Stub_table<size, big_endian>* stub_table)
set_stub_table(unsigned int shndx, unsigned int stub_index)
{
if (shndx >= this->stub_table_.size())
this->stub_table_.resize(shndx + 1);
this->stub_table_[shndx] = stub_table;
if (shndx >= this->stub_table_index_.size())
this->stub_table_index_.resize(shndx + 1);
this->stub_table_index_[shndx] = stub_index;
}
Stub_table<size, big_endian>*
stub_table(unsigned int shndx)
{
if (shndx < this->stub_table_.size())
return this->stub_table_[shndx];
if (shndx < this->stub_table_index_.size())
{
Target_powerpc<size, big_endian>* target
= 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];
}
return NULL;
}
void
clear_stub_table()
{
this->stub_table_index_.clear();
}
int
abiversion() const
{ return this->e_flags_ & elfcpp::EF_PPC64_ABI; }
@ -343,7 +365,7 @@ private:
std::vector<bool> has14_;
// The stub table to use for a given input section.
std::vector<Stub_table<size, big_endian>*> stub_table_;
std::vector<unsigned int> stub_table_index_;
// Header e_flags
elfcpp::Elf_Word e_flags_;
@ -487,7 +509,8 @@ class Target_powerpc : public Sized_target<size, big_endian>
glink_(NULL), rela_dyn_(NULL), copy_relocs_(elfcpp::R_POWERPC_COPY),
tlsld_got_offset_(-1U),
stub_tables_(), branch_lookup_table_(), branch_info_(),
plt_thread_safe_(false)
plt_thread_safe_(false), relax_failed_(false), relax_fail_count_(0),
stub_group_size_(0)
{
}
@ -562,9 +585,6 @@ class Target_powerpc : public Sized_target<size, big_endian>
ppc_object->set_has_14bit_branch(data_shndx);
}
Stub_table<size, big_endian>*
new_stub_table();
void
do_define_standard_symbols(Symbol_table*, Layout*);
@ -1179,7 +1199,7 @@ class Target_powerpc : public Sized_target<size, big_endian>
// Look over all the input sections, deciding where to place stubs.
void
group_sections(Layout*, const Task*);
group_sections(Layout*, const Task*, bool);
// Sort output sections by address.
struct Sort_sections
@ -1206,7 +1226,7 @@ class Target_powerpc : public Sized_target<size, big_endian>
{ }
// If this branch needs a plt call stub, or a long branch stub, make one.
void
bool
make_stub(Stub_table<size, big_endian>*,
Stub_table<size, big_endian>*,
Symbol_table*) const;
@ -1284,6 +1304,10 @@ class Target_powerpc : public Sized_target<size, big_endian>
Branches branch_info_;
bool plt_thread_safe_;
bool relax_failed_;
int relax_fail_count_;
int32_t stub_group_size_;
};
template<>
@ -2361,27 +2385,13 @@ class Stub_control
// value of the parameter --stub-group-size. If --stub-group-size
// is passed a negative value, we restrict stubs to be always before
// the stubbed branches.
Stub_control(int32_t size)
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_(false),
stubs_always_before_branch_(size < 0),
suppress_size_errors_(no_size_errors),
group_end_addr_(0), owner_(NULL), output_section_(NULL)
{
if (stub_group_size_ == 1)
{
// Default values.
if (stubs_always_before_branch_)
{
stub_group_size_ = 0x1e00000;
stub14_group_size_ = 0x7800;
}
else
{
stub_group_size_ = 0x1c00000;
stub14_group_size_ = 0x7000;
}
suppress_size_errors_ = true;
}
}
// Return true iff input section can be handled by current stub
@ -2495,12 +2505,14 @@ Stub_control::can_add_to_stub_group(Output_section* o,
template<int size, bool big_endian>
void
Target_powerpc<size, big_endian>::group_sections(Layout* layout,
const Task*)
const Task*,
bool no_size_errors)
{
Stub_control stub_control(parameters->options().stub_group_size());
Stub_control stub_control(this->stub_group_size_, no_size_errors);
// Group input sections and insert stub table
Stub_table<size, big_endian>* stub_table = NULL;
Stub_table_owner* table_owner = NULL;
std::vector<Stub_table_owner*> tables;
Layout::Section_list section_list;
layout->get_executable_sections(&section_list);
std::stable_sort(section_list.begin(), section_list.end(), Sort_sections());
@ -2514,50 +2526,89 @@ Target_powerpc<size, big_endian>::group_sections(Layout* layout,
i != (*o)->input_sections().rend();
++i)
{
if (i->is_input_section())
if (i->is_input_section()
|| i->is_relaxed_input_section())
{
Powerpc_relobj<size, big_endian>* ppcobj = static_cast
<Powerpc_relobj<size, big_endian>*>(i->relobj());
bool has14 = ppcobj->has_14bit_branch(i->shndx());
if (!stub_control.can_add_to_stub_group(*o, &*i, has14))
{
stub_table->init(stub_control.owner(),
stub_control.output_section());
table_owner->output_section = stub_control.output_section();
table_owner->owner = stub_control.owner();
stub_control.set_output_and_owner(*o, &*i);
stub_table = NULL;
table_owner = NULL;
}
if (stub_table == NULL)
stub_table = this->new_stub_table();
ppcobj->set_stub_table(i->shndx(), stub_table);
if (table_owner == NULL)
{
table_owner = new Stub_table_owner;
tables.push_back(table_owner);
}
ppcobj->set_stub_table(i->shndx(), tables.size() - 1);
}
}
}
if (stub_table != NULL)
if (table_owner != NULL)
{
const Output_section::Input_section* i = stub_control.owner();
if (!i->is_input_section())
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.
gold_assert(this->stub_tables_.size() >= 2);
this->stub_tables_.pop_back();
delete stub_table;
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(), this->stub_tables_.back());
ppcobj->set_stub_table(i->shndx(), tables.size() - 1);
}
else
stub_table->init(i, stub_control.output_section());
{
table_owner->output_section = stub_control.output_section();
table_owner->owner = i;
}
}
for (typename std::vector<Stub_table_owner*>::iterator t = tables.begin();
t != tables.end();
++t)
{
Stub_table<size, big_endian>* stub_table;
if ((*t)->owner->is_input_section())
stub_table = new Stub_table<size, big_endian>(this,
(*t)->output_section,
(*t)->owner);
else if ((*t)->owner->is_relaxed_input_section())
stub_table = static_cast<Stub_table<size, big_endian>*>(
(*t)->owner->relaxed_input_section());
else
gold_unreachable();
this->stub_tables_.push_back(stub_table);
delete *t;
}
}
static unsigned long
max_branch_delta (unsigned int r_type)
{
if (r_type == elfcpp::R_POWERPC_REL14
|| r_type == elfcpp::R_POWERPC_REL14_BRTAKEN
|| r_type == elfcpp::R_POWERPC_REL14_BRNTAKEN)
return 1L << 15;
if (r_type == elfcpp::R_POWERPC_REL24
|| r_type == elfcpp::R_PPC_PLTREL24
|| r_type == elfcpp::R_PPC_LOCAL24PC)
return 1L << 25;
return 0;
}
// If this branch needs a plt call stub, or a long branch stub, make one.
template<int size, bool big_endian>
void
bool
Target_powerpc<size, big_endian>::Branch_info::make_stub(
Stub_table<size, big_endian>* stub_table,
Stub_table<size, big_endian>* ifunc_stub_table,
@ -2590,27 +2641,25 @@ Target_powerpc<size, big_endian>::Branch_info::make_stub(
stub_table = ifunc_stub_table;
}
gold_assert(stub_table != NULL);
Address from = this->object_->get_output_section_offset(this->shndx_);
if (from != invalid_address)
from += (this->object_->output_section(this->shndx_)->address()
+ this->offset_);
if (gsym != NULL)
stub_table->add_plt_call_entry(this->object_, gsym,
this->r_type_, this->addend_);
return stub_table->add_plt_call_entry(from,
this->object_, gsym,
this->r_type_, this->addend_);
else
stub_table->add_plt_call_entry(this->object_, this->r_sym_,
this->r_type_, this->addend_);
return stub_table->add_plt_call_entry(from,
this->object_, this->r_sym_,
this->r_type_, this->addend_);
}
}
else
{
unsigned long max_branch_offset;
if (this->r_type_ == elfcpp::R_POWERPC_REL14
|| this->r_type_ == elfcpp::R_POWERPC_REL14_BRTAKEN
|| this->r_type_ == elfcpp::R_POWERPC_REL14_BRNTAKEN)
max_branch_offset = 1 << 15;
else if (this->r_type_ == elfcpp::R_POWERPC_REL24
|| this->r_type_ == elfcpp::R_PPC_PLTREL24
|| this->r_type_ == elfcpp::R_PPC_LOCAL24PC)
max_branch_offset = 1 << 25;
else
return;
unsigned long max_branch_offset = max_branch_delta(this->r_type_);
if (max_branch_offset == 0)
return true;
Address from = this->object_->get_output_section_offset(this->shndx_);
gold_assert(from != invalid_address);
from += (this->object_->output_section(this->shndx_)->address()
@ -2625,16 +2674,16 @@ Target_powerpc<size, big_endian>::Branch_info::make_stub(
Object* symobj = gsym->object();
if (symobj->is_dynamic()
|| symobj->pluginobj() != NULL)
return;
return true;
bool is_ordinary;
unsigned int shndx = gsym->shndx(&is_ordinary);
if (shndx == elfcpp::SHN_UNDEF)
return;
return true;
}
break;
case Symbol::IS_UNDEFINED:
return;
return true;
default:
break;
@ -2642,7 +2691,7 @@ Target_powerpc<size, big_endian>::Branch_info::make_stub(
Symbol_table::Compute_final_value_status status;
to = symtab->compute_final_value<size>(gsym, &status);
if (status != Symbol_table::CFVS_OK)
return;
return true;
if (size == 64)
to += this->object_->ppc64_local_entry_offset(gsym);
}
@ -2657,7 +2706,7 @@ Target_powerpc<size, big_endian>::Branch_info::make_stub(
&symval, symtab);
if (status != ObjType::CFLV_OK
|| !symval.has_output_value())
return;
return true;
to = symval.value(this->object_, 0);
if (size == 64)
to += this->object_->ppc64_local_entry_offset(this->r_sym_);
@ -2680,11 +2729,13 @@ Target_powerpc<size, big_endian>::Branch_info::make_stub(
" no long branch stub for you"),
this->object_->name().c_str(),
this->object_->section_name(this->shndx_).c_str());
return;
return true;
}
stub_table->add_long_branch_entry(this->object_, to);
return stub_table->add_long_branch_entry(this->object_,
this->r_type_, from, to);
}
}
return true;
}
// Relaxation hook. This is where we do stub generation.
@ -2752,7 +2803,34 @@ Target_powerpc<size, big_endian>::do_relax(int pass,
}
}
this->plt_thread_safe_ = thread_safe;
this->group_sections(layout, task);
}
if (pass == 1)
{
this->stub_group_size_ = parameters->options().stub_group_size();
bool no_size_errors = true;
if (this->stub_group_size_ == 1)
this->stub_group_size_ = 0x1c00000;
else if (this->stub_group_size_ == -1)
this->stub_group_size_ = -0x1e00000;
else
no_size_errors = false;
this->group_sections(layout, task, no_size_errors);
}
else if (this->relax_failed_ && this->relax_fail_count_ < 3)
{
this->branch_lookup_table_.clear();
for (typename Stub_tables::iterator p = this->stub_tables_.begin();
p != this->stub_tables_.end();
++p)
{
(*p)->clear_stubs(true);
}
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"),
program_name, this->stub_group_size_);
this->group_sections(layout, task, true);
}
// We need address of stub tables valid for make_stub.
@ -2777,11 +2855,12 @@ Target_powerpc<size, big_endian>::do_relax(int pass,
p != this->stub_tables_.end();
++p)
{
(*p)->clear_stubs();
(*p)->clear_stubs(false);
}
}
// Build all the stubs.
this->relax_failed_ = false;
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
@ -2790,7 +2869,14 @@ Target_powerpc<size, big_endian>::do_relax(int pass,
b != this->branch_info_.end();
b++)
{
b->make_stub(one_stub_table, ifunc_stub_table, symtab);
if (!b->make_stub(one_stub_table, ifunc_stub_table, symtab)
&& !this->relax_failed_)
{
this->relax_failed_ = true;
this->relax_fail_count_++;
if (this->relax_fail_count_ < 3)
return true;
}
}
// Did anything change size?
@ -3475,26 +3561,35 @@ class Stub_table : public Output_relaxed_input_section
typedef typename elfcpp::Elf_types<size>::Elf_Addr Address;
static const Address invalid_address = static_cast<Address>(0) - 1;
Stub_table(Target_powerpc<size, big_endian>* targ)
: Output_relaxed_input_section(NULL, 0, 0),
Stub_table(Target_powerpc<size, big_endian>* targ,
Output_section* output_section,
const Output_section::Input_section* owner)
: 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_(0), plt_size_(0), last_plt_size_(0),
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)
{ }
{
this->set_output_section(output_section);
// Delayed Output_relaxed_input_section init.
void
init(const Output_section::Input_section*, Output_section*);
std::vector<Output_relaxed_input_section*> new_relaxed;
new_relaxed.push_back(this);
output_section->convert_input_sections_to_relaxed_sections(new_relaxed);
}
// Add a plt call stub.
void
add_plt_call_entry(const Sized_relobj_file<size, big_endian>*,
bool
add_plt_call_entry(Address,
const Sized_relobj_file<size, big_endian>*,
const Symbol*,
unsigned int,
Address);
void
add_plt_call_entry(const Sized_relobj_file<size, big_endian>*,
bool
add_plt_call_entry(Address,
const Sized_relobj_file<size, big_endian>*,
unsigned int,
unsigned int,
Address);
@ -3520,20 +3615,37 @@ class Stub_table : public Output_relaxed_input_section
Address) const;
// Add a long branch stub.
void
add_long_branch_entry(const Powerpc_relobj<size, big_endian>*, Address);
bool
add_long_branch_entry(const Powerpc_relobj<size, big_endian>*,
unsigned int, Address, Address);
Address
find_long_branch_entry(const Powerpc_relobj<size, big_endian>*,
Address) const;
bool
can_reach_stub(Address from, unsigned int off, unsigned int r_type)
{
unsigned long max_branch_offset = max_branch_delta(r_type);
if (max_branch_offset == 0)
return true;
gold_assert(from != invalid_address);
Address loc = off + this->stub_address();
return loc - from + max_branch_offset < 2 * max_branch_offset;
}
void
clear_stubs()
clear_stubs(bool all)
{
this->plt_call_stubs_.clear();
this->plt_size_ = 0;
this->long_branch_stubs_.clear();
this->branch_size_ = 0;
if (all)
{
this->last_plt_size_ = 0;
this->last_branch_size_ = 0;
}
}
Address
@ -3837,44 +3949,13 @@ class Stub_table : public Output_relaxed_input_section
bool eh_frame_added_;
};
// Make a new stub table, and record.
template<int size, bool big_endian>
Stub_table<size, big_endian>*
Target_powerpc<size, big_endian>::new_stub_table()
{
Stub_table<size, big_endian>* stub_table
= new Stub_table<size, big_endian>(this);
this->stub_tables_.push_back(stub_table);
return stub_table;
}
// Delayed stub table initialisation, because we create the stub table
// before we know to which section it will be attached.
template<int size, bool big_endian>
void
Stub_table<size, big_endian>::init(
const Output_section::Input_section* owner,
Output_section* output_section)
{
this->set_relobj(owner->relobj());
this->set_shndx(owner->shndx());
this->set_addralign(this->relobj()->section_addralign(this->shndx()));
this->set_output_section(output_section);
this->orig_data_size_ = owner->current_data_size();
std::vector<Output_relaxed_input_section*> new_relaxed;
new_relaxed.push_back(this);
output_section->convert_input_sections_to_relaxed_sections(new_relaxed);
}
// Add a plt call stub, if we do not already have one for this
// sym/object/addend combo.
template<int size, bool big_endian>
void
bool
Stub_table<size, big_endian>::add_plt_call_entry(
Address from,
const Sized_relobj_file<size, big_endian>* object,
const Symbol* gsym,
unsigned int r_type,
@ -3886,11 +3967,13 @@ Stub_table<size, big_endian>::add_plt_call_entry(
= this->plt_call_stubs_.insert(std::make_pair(ent, off));
if (p.second)
this->plt_size_ = off + this->plt_call_size(p.first);
return this->can_reach_stub(from, off, r_type);
}
template<int size, bool big_endian>
void
bool
Stub_table<size, big_endian>::add_plt_call_entry(
Address from,
const Sized_relobj_file<size, big_endian>* object,
unsigned int locsym_index,
unsigned int r_type,
@ -3902,6 +3985,7 @@ Stub_table<size, big_endian>::add_plt_call_entry(
= this->plt_call_stubs_.insert(std::make_pair(ent, off));
if (p.second)
this->plt_size_ = off + this->plt_call_size(p.first);
return this->can_reach_stub(from, off, r_type);
}
// Find a plt call stub.
@ -3956,9 +4040,11 @@ Stub_table<size, big_endian>::find_plt_call_entry(
// destination.
template<int size, bool big_endian>
void
bool
Stub_table<size, big_endian>::add_long_branch_entry(
const Powerpc_relobj<size, big_endian>* object,
unsigned int r_type,
Address from,
Address to)
{
Branch_stub_ent ent(object, to);
@ -3970,6 +4056,7 @@ Stub_table<size, big_endian>::add_long_branch_entry(
if (size == 64 && stub_size != 4)
this->targ_->add_branch_lookup_table(to);
}
return this->can_reach_stub(from, off, r_type);
}
// Find long branch stub.
@ -7107,15 +7194,7 @@ Target_powerpc<size, big_endian>::Relocate::relocate(
value = target->symval_for_branch(relinfo->symtab, value,
gsym, object, &dest_shndx);
}
unsigned int max_branch_offset = 0;
if (r_type == elfcpp::R_POWERPC_REL24
|| r_type == elfcpp::R_PPC_PLTREL24
|| r_type == elfcpp::R_PPC_LOCAL24PC)
max_branch_offset = 1 << 25;
else if (r_type == elfcpp::R_POWERPC_REL14
|| r_type == elfcpp::R_POWERPC_REL14_BRTAKEN
|| r_type == elfcpp::R_POWERPC_REL14_BRNTAKEN)
max_branch_offset = 1 << 15;
unsigned long max_branch_offset = max_branch_delta(r_type);
if (max_branch_offset != 0
&& value - address + max_branch_offset >= 2 * max_branch_offset)
{