Skip to content

Commit

Permalink
compiler: fix bug in handling of unordered set during exporting
Browse files Browse the repository at this point in the history
In CL 183850 a change was made to combine tracking/discovery of
exported types and imported packages during export data generation. As
a result of this refactoring a bug was introduced: the new code can
potentially insert items into the exports set (an unordered_set) while
iterating through the same set, which is illegal according to the spec
for std::unordered_set.

This patch fixes the problem by changing the type discovery phase to
iterate through a separate list of sorted exports, as opposed to
iterating through the main unordered set.  Also included is a change
to fix the code that looks for variables that are referenced from
inlined routine bodies (this code wasn't scanning all of the function
that it needed to scan).

New test case for this problem in CL 186697.

Updates golang/go#33020.

Change-Id: Id9ae5af462bc0819e8c9a8dd038ce9746297ad5d
Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/185977
Reviewed-by: Ian Lance Taylor <iant@golang.org>
  • Loading branch information
thanm authored and ianlancetaylor committed Jul 18, 2019
1 parent 0e51b7e commit 19ed722
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 42 deletions.
77 changes: 42 additions & 35 deletions go/export.cc
Expand Up @@ -111,7 +111,7 @@ class Collect_export_references : public Traverse
: Traverse(traverse_expressions : Traverse(traverse_expressions
| traverse_types), | traverse_types),
exp_(exp), exports_(exports), imports_(imports), exp_(exp), exports_(exports), imports_(imports),
inline_fcn_worklist_(NULL) inline_fcn_worklist_(NULL), exports_finalized_(false)
{ } { }


// Initial entry point; performs a walk to expand the exports set. // Initial entry point; performs a walk to expand the exports set.
Expand All @@ -121,7 +121,7 @@ class Collect_export_references : public Traverse
// Second entry point (called after the method above), to find // Second entry point (called after the method above), to find
// all types referenced by exports. // all types referenced by exports.
void void
prepare_types(); prepare_types(const std::vector<Named_object*>& sorted_exports);


protected: protected:
// Override of parent class method. // Override of parent class method.
Expand All @@ -141,6 +141,13 @@ class Collect_export_references : public Traverse
traverse_named_type(Named_type*); traverse_named_type(Named_type*);


private: private:

// Add a named object to the exports set (during expand_exports()).
// Returns TRUE if a new object was added to the exports set,
// FALSE otherwise.
bool
add_to_exports(Named_object*);

// The exporter. // The exporter.
Export* exp_; Export* exp_;
// The set of named objects to export. // The set of named objects to export.
Expand All @@ -152,6 +159,8 @@ class Collect_export_references : public Traverse
// Worklist of functions we are exporting with inline bodies that need // Worklist of functions we are exporting with inline bodies that need
// to be checked. // to be checked.
std::vector<Named_object*>* inline_fcn_worklist_; std::vector<Named_object*>* inline_fcn_worklist_;
// Set to true if expand_exports() has been called and is complete.
bool exports_finalized_;
}; };


void void
Expand All @@ -172,6 +181,18 @@ Collect_export_references::expand_exports(std::vector<Named_object*>* fcns)
} }
} }
this->inline_fcn_worklist_ = NULL; this->inline_fcn_worklist_ = NULL;
this->exports_finalized_ = true;
}

bool
Collect_export_references::add_to_exports(Named_object* no)
{
std::pair<Unordered_set(Named_object*)::iterator, bool> ins =
this->exports_->insert(no);
// If the export list has been finalized, then we should not be
// adding anything new to the exports set.
go_assert(!this->exports_finalized_ || !ins.second);
return ins.second;
} }


int int
Expand All @@ -189,7 +210,7 @@ Collect_export_references::expression(Expression** pexpr)
if (var_package != NULL) if (var_package != NULL)
this->imports_->insert(var_package); this->imports_->insert(var_package);


this->exports_->insert(no); this->add_to_exports(no);
no->var_value()->set_is_referenced_by_inline(); no->var_value()->set_is_referenced_by_inline();
} }
return TRAVERSE_CONTINUE; return TRAVERSE_CONTINUE;
Expand All @@ -210,17 +231,16 @@ Collect_export_references::expression(Expression** pexpr)


if (this->inline_fcn_worklist_ != NULL) if (this->inline_fcn_worklist_ != NULL)
{ {
std::pair<Unordered_set(Named_object*)::iterator, bool> ins = bool added = this->add_to_exports(no);
this->exports_->insert(no);


if (no->is_function()) if (no->is_function())
no->func_value()->set_is_referenced_by_inline(); no->func_value()->set_is_referenced_by_inline();


// If ins.second is false then this object was already in // If 'added' is false then this object was already in
// exports_, in which case it was already added to // exports_, in which case it was already added to
// check_inline_refs_ the first time we added it to exports_, so // check_inline_refs_ the first time we added it to exports_, so
// we don't need to add it again. // we don't need to add it again.
if (ins.second if (added
&& no->is_function() && no->is_function()
&& no->func_value()->export_for_inlining()) && no->func_value()->export_for_inlining())
this->inline_fcn_worklist_->push_back(no); this->inline_fcn_worklist_->push_back(no);
Expand All @@ -238,11 +258,11 @@ Collect_export_references::expression(Expression** pexpr)
// exported inline function from another package). // exported inline function from another package).


void void
Collect_export_references::prepare_types() Collect_export_references::prepare_types(const std::vector<Named_object*>& sorted_exports)
{ {
// Iterate through the exported objects and traverse any types encountered. // Iterate through the exported objects and traverse any types encountered.
for (Unordered_set(Named_object*)::iterator p = this->exports_->begin(); for (std::vector<Named_object*>::const_iterator p = sorted_exports.begin();
p != this->exports_->end(); p != sorted_exports.end();
++p) ++p)
{ {
Named_object* no = *p; Named_object* no = *p;
Expand Down Expand Up @@ -506,7 +526,8 @@ Export::export_globals(const std::string& package_name,
const std::map<std::string, Package*>& imports, const std::map<std::string, Package*>& imports,
const std::string& import_init_fn, const std::string& import_init_fn,
const Import_init_set& imported_init_fns, const Import_init_set& imported_init_fns,
const Bindings* bindings) const Bindings* bindings,
Unordered_set(Named_object*)* functions_marked_inline)
{ {
// If there have been any errors so far, don't try to export // If there have been any errors so far, don't try to export
// anything. That way the export code doesn't have to worry about // anything. That way the export code doesn't have to worry about
Expand All @@ -520,35 +541,21 @@ Export::export_globals(const std::string& package_name,
// CHECK_INLINE_REFS is also on EXPORTS. // CHECK_INLINE_REFS is also on EXPORTS.
Unordered_set(Named_object*) exports; Unordered_set(Named_object*) exports;
std::vector<Named_object*> check_inline_refs; std::vector<Named_object*> check_inline_refs;
check_inline_refs.reserve(functions_marked_inline->size());

// Add all functions/methods from the "marked inlined" set to the
// CHECK_INLINE_REFS worklist.
for (Unordered_set(Named_object*)::const_iterator p = functions_marked_inline->begin();
p != functions_marked_inline->end();
++p)
check_inline_refs.push_back(*p);


for (Bindings::const_definitions_iterator p = bindings->begin_definitions(); for (Bindings::const_definitions_iterator p = bindings->begin_definitions();
p != bindings->end_definitions(); p != bindings->end_definitions();
++p) ++p)
{ {
if (should_export(*p)) if (should_export(*p))
{ exports.insert(*p);
exports.insert(*p);

if ((*p)->is_function()
&& (*p)->func_value()->export_for_inlining())
check_inline_refs.push_back(*p);
else if ((*p)->is_type())
{
const Bindings* methods = (*p)->type_value()->local_methods();
if (methods != NULL)
{
for (Bindings::const_definitions_iterator pm =
methods->begin_definitions();
pm != methods->end_definitions();
++pm)
{
Function* fn = (*pm)->func_value();
if (fn->export_for_inlining())
check_inline_refs.push_back(*pm);
}
}
}
}
} }


for (Bindings::const_declarations_iterator p = for (Bindings::const_declarations_iterator p =
Expand Down Expand Up @@ -593,7 +600,7 @@ Export::export_globals(const std::string& package_name,


// Collect up the set of types mentioned in things we're exporting, // Collect up the set of types mentioned in things we're exporting,
// and any packages that may be referred to indirectly. // and any packages that may be referred to indirectly.
collect.prepare_types(); collect.prepare_types(sorted_exports);


// Assign indexes to all exported types and types referenced by // Assign indexes to all exported types and types referenced by
// things we're exporting. Return value is index of first non-exported // things we're exporting. Return value is index of first non-exported
Expand Down
3 changes: 2 additions & 1 deletion go/export.h
Expand Up @@ -158,7 +158,8 @@ class Export : public String_dump
const std::map<std::string, Package*>& imports, const std::map<std::string, Package*>& imports,
const std::string& import_init_fn, const std::string& import_init_fn,
const Import_init_set& imported_init_fns, const Import_init_set& imported_init_fns,
const Bindings* bindings); const Bindings* bindings,
Unordered_set(Named_object*)* marked_inline_functions);


// Record a type that is mentioned in export data. Return value is // Record a type that is mentioned in export data. Return value is
// TRUE for newly visited types, FALSE for types that have been seen // TRUE for newly visited types, FALSE for types that have been seen
Expand Down
24 changes: 18 additions & 6 deletions go/gogo.cc
Expand Up @@ -5078,9 +5078,10 @@ Inline_within_budget::expression(Expression** pexpr)
class Mark_inline_candidates : public Traverse class Mark_inline_candidates : public Traverse
{ {
public: public:
Mark_inline_candidates() Mark_inline_candidates(Unordered_set(Named_object*)* marked)
: Traverse(traverse_functions : Traverse(traverse_functions
| traverse_types) | traverse_types),
marked_functions_(marked)
{ } { }


int int
Expand All @@ -5097,6 +5098,9 @@ class Mark_inline_candidates : public Traverse
// budget is a heuristic. In the usual GCC spirit, we could // budget is a heuristic. In the usual GCC spirit, we could
// consider setting this via a command line option. // consider setting this via a command line option.
const int budget_heuristic = 80; const int budget_heuristic = 80;

// Set of named objects that are marked as inline candidates.
Unordered_set(Named_object*)* marked_functions_;
}; };


// Mark a function if it is an inline candidate. // Mark a function if it is an inline candidate.
Expand All @@ -5109,7 +5113,10 @@ Mark_inline_candidates::function(Named_object* no)
Inline_within_budget iwb(&budget); Inline_within_budget iwb(&budget);
func->block()->traverse(&iwb); func->block()->traverse(&iwb);
if (budget >= 0) if (budget >= 0)
func->set_export_for_inlining(); {
func->set_export_for_inlining();
this->marked_functions_->insert(no);
}
return TRAVERSE_CONTINUE; return TRAVERSE_CONTINUE;
} }


Expand All @@ -5135,7 +5142,10 @@ Mark_inline_candidates::type(Type* t)
Inline_within_budget iwb(&budget); Inline_within_budget iwb(&budget);
func->block()->traverse(&iwb); func->block()->traverse(&iwb);
if (budget >= 0) if (budget >= 0)
func->set_export_for_inlining(); {
func->set_export_for_inlining();
this->marked_functions_->insert(no);
}
} }
return TRAVERSE_CONTINUE; return TRAVERSE_CONTINUE;
} }
Expand All @@ -5150,7 +5160,8 @@ Gogo::do_exports()


// Mark any functions whose body should be exported for inlining by // Mark any functions whose body should be exported for inlining by
// other packages. // other packages.
Mark_inline_candidates mic; Unordered_set(Named_object*) marked_functions;
Mark_inline_candidates mic(&marked_functions);
this->traverse(&mic); this->traverse(&mic);


// For now we always stream to a section. Later we may want to // For now we always stream to a section. Later we may want to
Expand Down Expand Up @@ -5187,7 +5198,8 @@ Gogo::do_exports()
this->imports_, this->imports_,
init_fn_name, init_fn_name,
this->imported_init_fns_, this->imported_init_fns_,
this->package_->bindings()); this->package_->bindings(),
&marked_functions);


if (!this->c_header_.empty() && !saw_errors()) if (!this->c_header_.empty() && !saw_errors())
this->write_c_header(); this->write_c_header();
Expand Down

0 comments on commit 19ed722

Please sign in to comment.