compiler: avoid introducing redundant write barriers
The traversal used by the write barrier insertion phase can sometimes
wind up visiting new statements inserted during the traversal, which
then results in duplicate / redundant write barrier guards. Example
program to reproduce:
package small
type S struct {
N *S
K int
}
var G *S = &S{N: nil, K: 101}
This patch changes the traversal code to keep track of statements
already added and avoid processing them again later in the traversal.
Fixes golang/go#25867
Change-Id: Ie7a81b49e815aff7379d3d4ef7c7a864cebcff5b
Reviewed-on: https://go-review.googlesource.com/118637
Reviewed-by: Ian Lance Taylor <iant@golang.org>
diff --git a/go/gogo.cc b/go/gogo.cc
index 4d77ace..eb31aa2 100644
--- a/go/gogo.cc
+++ b/go/gogo.cc
@@ -8444,6 +8444,9 @@
void
Statement_inserter::insert(Statement* s)
{
+ if (this->statements_added_ != NULL)
+ this->statements_added_->insert(s);
+
if (this->block_ != NULL)
{
go_assert(this->pindex_ != NULL);
diff --git a/go/gogo.h b/go/gogo.h
index 139df17..6511599 100644
--- a/go/gogo.h
+++ b/go/gogo.h
@@ -3419,19 +3419,24 @@
class Statement_inserter
{
public:
+ typedef Unordered_set(Statement*) Statements;
+
// Empty constructor.
Statement_inserter()
- : block_(NULL), pindex_(NULL), gogo_(NULL), var_(NULL)
+ : block_(NULL), pindex_(NULL), gogo_(NULL), var_(NULL),
+ statements_added_(NULL)
{ }
// Constructor for a statement in a block.
- Statement_inserter(Block* block, size_t *pindex)
- : block_(block), pindex_(pindex), gogo_(NULL), var_(NULL)
+ Statement_inserter(Block* block, size_t *pindex, Statements *added = NULL)
+ : block_(block), pindex_(pindex), gogo_(NULL), var_(NULL),
+ statements_added_(added)
{ }
// Constructor for a global variable.
- Statement_inserter(Gogo* gogo, Variable* var)
- : block_(NULL), pindex_(NULL), gogo_(gogo), var_(var)
+ Statement_inserter(Gogo* gogo, Variable* var, Statements *added = NULL)
+ : block_(NULL), pindex_(NULL), gogo_(gogo), var_(var),
+ statements_added_(added)
{ go_assert(var->is_global()); }
// We use the default copy constructor and assignment operator.
@@ -3451,6 +3456,8 @@
Gogo* gogo_;
// The global variable, when looking at an initializer expression.
Variable* var_;
+ // If non-null, a set to record new statements inserted (non-owned).
+ Statements* statements_added_;
};
// When translating the gogo IR into the backend data structure, this
diff --git a/go/wb.cc b/go/wb.cc
index 094d8ec..99f467e 100644
--- a/go/wb.cc
+++ b/go/wb.cc
@@ -213,7 +213,7 @@
public:
Write_barriers(Gogo* gogo)
: Traverse(traverse_functions | traverse_variables | traverse_statements),
- gogo_(gogo), function_(NULL)
+ gogo_(gogo), function_(NULL), statements_added_()
{ }
int
@@ -230,6 +230,8 @@
Gogo* gogo_;
// Current function.
Function* function_;
+ // Statements introduced.
+ Statement_inserter::Statements statements_added_;
};
// Traverse a function. Just record it for later.
@@ -298,9 +300,10 @@
Location loc = init->location();
Expression* ref = Expression::make_var_reference(no, loc);
- Statement_inserter inserter(this->gogo_, var);
+ Statement_inserter inserter(this->gogo_, var, &this->statements_added_);
Statement* s = this->gogo_->assign_with_write_barrier(NULL, NULL, &inserter,
ref, init, loc);
+ this->statements_added_.insert(s);
var->add_preinit_statement(this->gogo_, s);
var->clear_init();
@@ -313,6 +316,9 @@
int
Write_barriers::statement(Block* block, size_t* pindex, Statement* s)
{
+ if (this->statements_added_.find(s) != this->statements_added_.end())
+ return TRAVERSE_SKIP_COMPONENTS;
+
switch (s->classification())
{
default:
@@ -355,7 +361,7 @@
Function* function = this->function_;
Location loc = init->location();
- Statement_inserter inserter(block, pindex);
+ Statement_inserter inserter(block, pindex, &this->statements_added_);
// Insert the variable declaration statement with no
// initializer, so that the variable exists.
@@ -370,6 +376,7 @@
&inserter,
ref, init,
loc);
+ this->statements_added_.insert(assign);
// Replace the old variable declaration statement with the new
// initialization.
@@ -391,12 +398,14 @@
// Change the assignment to use a write barrier.
Function* function = this->function_;
Location loc = as->location();
- Statement_inserter inserter = Statement_inserter(block, pindex);
+ Statement_inserter inserter =
+ Statement_inserter(block, pindex, &this->statements_added_);
Statement* assign = this->gogo_->assign_with_write_barrier(function,
block,
&inserter,
lhs, rhs,
loc);
+ this->statements_added_.insert(assign);
block->replace_statement(*pindex, assign);
}
break;