Commit 06b6c630 by Ian Lance Taylor

compiler: enable escape analysis for runtime

    
    The runtime package was hard-coded non-escape, and the escape
    analysis was not run for the runtime package. This CL removes
    the hard-code, and lets the escape analysis decide. It is not
    allowed for local variables and closures in the runtime to be
    heap allocated. This CL adds the check that make sure that they
    indeed do not escape.
    
    The escape analysis is always run when compiling the runtime
    now.
    
    Fixes golang/go#17431
    
    Reviewed-on: https://go-review.googlesource.com/86246

From-SVN: r256820
parent 78a5fce0
3ea7fc3b918210e7248dbc51d90af20639dc4167 1072286ca9249bd6f75628aead325a66286bcf5b
The first line of this file holds the git revision number of the last The first line of this file holds the git revision number of the last
merge done from the gofrontend repository. merge done from the gofrontend repository.
...@@ -873,13 +873,12 @@ escape_hash_match(std::string suffix, std::string name) ...@@ -873,13 +873,12 @@ escape_hash_match(std::string suffix, std::string name)
void void
Gogo::analyze_escape() Gogo::analyze_escape()
{ {
if (!optimize_allocation_flag.is_enabled() || saw_errors()) if (saw_errors())
return; return;
// Currently runtime is hard-coded to non-escape in various places. if (!optimize_allocation_flag.is_enabled()
// Don't run escape analysis for runtime. && !this->compiling_runtime())
// TODO: remove this once it works for runtime. // We always run escape analysis when compiling runtime.
if (this->compiling_runtime() && this->package_name() == "runtime")
return; return;
// Discover strongly connected groups of functions to analyze for escape // Discover strongly connected groups of functions to analyze for escape
...@@ -1473,6 +1472,35 @@ Escape_analysis_assign::statement(Block*, size_t*, Statement* s) ...@@ -1473,6 +1472,35 @@ Escape_analysis_assign::statement(Block*, size_t*, Statement* s)
return TRAVERSE_SKIP_COMPONENTS; return TRAVERSE_SKIP_COMPONENTS;
} }
// Helper function to emit moved-to-heap diagnostics.
static void
move_to_heap(Gogo* gogo, Expression *expr)
{
Named_object* no;
if (expr->var_expression() != NULL)
no = expr->var_expression()->named_object();
else if (expr->enclosed_var_expression() != NULL)
no = expr->enclosed_var_expression()->variable();
else
return;
if ((no->is_variable()
&& !no->var_value()->is_global())
|| no->is_result_variable())
{
Node* n = Node::make_node(expr);
if (gogo->debug_escape_level() != 0)
go_inform(n->definition_location(),
"moved to heap: %s",
n->ast_format(gogo).c_str());
if (gogo->compiling_runtime() && gogo->package_name() == "runtime")
go_error_at(expr->location(),
"%s escapes to heap, not allowed in runtime",
n->ast_format(gogo).c_str());
}
}
// Model expressions within a function as assignments and flows between nodes. // Model expressions within a function as assignments and flows between nodes.
int int
...@@ -1489,13 +1517,7 @@ Escape_analysis_assign::expression(Expression** pexpr) ...@@ -1489,13 +1517,7 @@ Escape_analysis_assign::expression(Expression** pexpr)
if (debug_level > 1) if (debug_level > 1)
go_inform((*pexpr)->location(), "%s too large for stack", go_inform((*pexpr)->location(), "%s too large for stack",
n->ast_format(gogo).c_str()); n->ast_format(gogo).c_str());
if (debug_level != 0 move_to_heap(gogo, *pexpr);
&& ((*pexpr)->var_expression() != NULL
|| (*pexpr)->enclosed_var_expression() != NULL))
go_inform(n->definition_location(),
"moved to heap: %s",
n->ast_format(gogo).c_str());
n->set_encoding(Node::ESCAPE_HEAP); n->set_encoding(Node::ESCAPE_HEAP);
(*pexpr)->address_taken(true); (*pexpr)->address_taken(true);
this->assign(this->context_->sink(), n); this->assign(this->context_->sink(), n);
...@@ -2968,25 +2990,20 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, ...@@ -2968,25 +2990,20 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src,
if (src_leaks) if (src_leaks)
{ {
src->set_encoding(Node::ESCAPE_HEAP); src->set_encoding(Node::ESCAPE_HEAP);
if (debug_level != 0 && osrcesc != src->encoding()) if (osrcesc != src->encoding())
{ {
if (underlying->var_expression() != NULL move_to_heap(gogo, underlying);
|| underlying->enclosed_var_expression() != NULL) if (debug_level > 1)
go_inform(underlying_node->definition_location(), go_inform(src->location(),
"moved to heap: %s", "%s escapes to heap, level={%d %d}, "
underlying_node->ast_format(gogo).c_str()); "dst.eld=%d, src.eld=%d",
src->ast_format(gogo).c_str(), level.value(),
if (debug_level > 1) level.suffix_value(), dst_state->loop_depth,
go_inform(src->location(), mod_loop_depth);
"%s escapes to heap, level={%d %d}, " else if (debug_level > 0)
"dst.eld=%d, src.eld=%d", go_inform(src->location(), "%s escapes to heap",
src->ast_format(gogo).c_str(), level.value(), src->ast_format(gogo).c_str());
level.suffix_value(), dst_state->loop_depth, }
mod_loop_depth);
else
go_inform(src->location(), "%s escapes to heap",
src->ast_format(gogo).c_str());
}
this->flood(level.decrease(), dst, this->flood(level.decrease(), dst,
underlying_node, mod_loop_depth); underlying_node, mod_loop_depth);
......
...@@ -3700,7 +3700,7 @@ Expression::make_unsafe_cast(Type* type, Expression* expr, ...@@ -3700,7 +3700,7 @@ Expression::make_unsafe_cast(Type* type, Expression* expr,
// called after escape analysis but before inserting write barriers. // called after escape analysis but before inserting write barriers.
void void
Unary_expression::check_operand_address_taken(Gogo* gogo) Unary_expression::check_operand_address_taken(Gogo*)
{ {
if (this->op_ != OPERATOR_AND) if (this->op_ != OPERATOR_AND)
return; return;
...@@ -3714,13 +3714,6 @@ Unary_expression::check_operand_address_taken(Gogo* gogo) ...@@ -3714,13 +3714,6 @@ Unary_expression::check_operand_address_taken(Gogo* gogo)
if ((n->encoding() & ESCAPE_MASK) == int(Node::ESCAPE_NONE)) if ((n->encoding() & ESCAPE_MASK) == int(Node::ESCAPE_NONE))
this->escapes_ = false; this->escapes_ = false;
// When compiling the runtime, the address operator does not cause
// local variables to escape. When escape analysis becomes the
// default, this should be changed to make it an error if we have an
// address operator that escapes.
if (gogo->compiling_runtime() && gogo->package_name() == "runtime")
this->escapes_ = false;
Named_object* var = NULL; Named_object* var = NULL;
if (this->expr_->var_expression() != NULL) if (this->expr_->var_expression() != NULL)
var = this->expr_->var_expression()->named_object(); var = this->expr_->var_expression()->named_object();
...@@ -7028,26 +7021,14 @@ Bound_method_expression::do_flatten(Gogo* gogo, Named_object*, ...@@ -7028,26 +7021,14 @@ Bound_method_expression::do_flatten(Gogo* gogo, Named_object*,
vals->push_back(val); vals->push_back(val);
Expression* ret = Expression::make_struct_composite_literal(st, vals, loc); Expression* ret = Expression::make_struct_composite_literal(st, vals, loc);
ret = Expression::make_heap_expression(ret, loc);
if (!gogo->compiling_runtime() || gogo->package_name() != "runtime") Node* n = Node::make_node(this);
{ if ((n->encoding() & ESCAPE_MASK) == Node::ESCAPE_NONE)
ret = Expression::make_heap_expression(ret, loc); ret->heap_expression()->set_allocate_on_stack();
Node* n = Node::make_node(this); else if (gogo->compiling_runtime() && gogo->package_name() == "runtime")
if ((n->encoding() & ESCAPE_MASK) == Node::ESCAPE_NONE) go_error_at(loc, "%s escapes to heap, not allowed in runtime",
ret->heap_expression()->set_allocate_on_stack(); n->ast_format(gogo).c_str());
}
else
{
// When compiling the runtime, method closures do not escape.
// When escape analysis becomes the default, and applies to
// method closures, this should be changed to make it an error
// if a method closure escapes.
Temporary_statement* ctemp = Statement::make_temporary(st, ret, loc);
inserter->insert(ctemp);
ret = Expression::make_temporary_reference(ctemp, loc);
ret = Expression::make_unary(OPERATOR_AND, ret, loc);
ret->unary_expression()->set_does_not_escape();
}
// If necessary, check whether the expression or any embedded // If necessary, check whether the expression or any embedded
// pointers are nil. // pointers are nil.
...@@ -9577,12 +9558,6 @@ Call_expression::lower_varargs(Gogo* gogo, Named_object* function, ...@@ -9577,12 +9558,6 @@ Call_expression::lower_varargs(Gogo* gogo, Named_object* function,
Type* varargs_type, size_t param_count, Type* varargs_type, size_t param_count,
Slice_storage_escape_disp escape_disp) Slice_storage_escape_disp escape_disp)
{ {
// When compiling the runtime, varargs slices do not escape. When
// escape analysis becomes the default, this should be changed to
// make it an error if we have a varargs slice that escapes.
if (gogo->compiling_runtime() && gogo->package_name() == "runtime")
escape_disp = SLICE_STORAGE_DOES_NOT_ESCAPE;
if (this->varargs_are_lowered_) if (this->varargs_are_lowered_)
return; return;
......
...@@ -3059,21 +3059,6 @@ Parse::create_closure(Named_object* function, Enclosing_vars* enclosing_vars, ...@@ -3059,21 +3059,6 @@ Parse::create_closure(Named_object* function, Enclosing_vars* enclosing_vars,
Struct_type* st = closure_var->var_value()->type()->deref()->struct_type(); Struct_type* st = closure_var->var_value()->type()->deref()->struct_type();
Expression* cv = Expression::make_struct_composite_literal(st, initializer, Expression* cv = Expression::make_struct_composite_literal(st, initializer,
location); location);
// When compiling the runtime, closures do not escape. When escape
// analysis becomes the default, and applies to closures, this
// should be changed to make it an error if a closure escapes.
if (this->gogo_->compiling_runtime()
&& this->gogo_->package_name() == "runtime")
{
Temporary_statement* ctemp = Statement::make_temporary(st, cv, location);
this->gogo_->add_statement(ctemp);
Expression* ref = Expression::make_temporary_reference(ctemp, location);
Expression* addr = Expression::make_unary(OPERATOR_AND, ref, location);
addr->unary_expression()->set_does_not_escape();
return addr;
}
return Expression::make_heap_expression(cv, location); return Expression::make_heap_expression(cv, location);
} }
......
...@@ -54,14 +54,10 @@ Mark_address_taken::expression(Expression** pexpr) ...@@ -54,14 +54,10 @@ Mark_address_taken::expression(Expression** pexpr)
// Slice of an array. The escape analysis models this with // Slice of an array. The escape analysis models this with
// a child Node representing the address of the array. // a child Node representing the address of the array.
bool escapes = false; bool escapes = false;
if (!this->gogo_->compiling_runtime() Node* n = Node::make_node(expr);
|| this->gogo_->package_name() != "runtime") if (n->child() == NULL
{ || (n->child()->encoding() & ESCAPE_MASK) != Node::ESCAPE_NONE)
Node* n = Node::make_node(expr); escapes = true;
if (n->child() == NULL
|| (n->child()->encoding() & ESCAPE_MASK) != Node::ESCAPE_NONE)
escapes = true;
}
aie->array()->address_taken(escapes); aie->array()->address_taken(escapes);
} }
...@@ -127,6 +123,53 @@ Mark_address_taken::expression(Expression** pexpr) ...@@ -127,6 +123,53 @@ Mark_address_taken::expression(Expression** pexpr)
return TRAVERSE_CONTINUE; return TRAVERSE_CONTINUE;
} }
// Check variables and closures do not escape when compiling runtime.
class Check_escape : public Traverse
{
public:
Check_escape(Gogo* gogo)
: Traverse(traverse_expressions | traverse_variables),
gogo_(gogo)
{ }
int
expression(Expression**);
int
variable(Named_object*);
private:
Gogo* gogo_;
};
int
Check_escape::variable(Named_object* no)
{
if ((no->is_variable() && no->var_value()->is_in_heap())
|| (no->is_result_variable()
&& no->result_var_value()->is_in_heap()))
go_error_at(no->location(),
"%s escapes to heap, not allowed in runtime",
no->name().c_str());
return TRAVERSE_CONTINUE;
}
int
Check_escape::expression(Expression** pexpr)
{
Expression* expr = *pexpr;
Func_expression* fe = expr->func_expression();
if (fe != NULL && fe->closure() != NULL)
{
Node* n = Node::make_node(expr);
if (n->encoding() == Node::ESCAPE_HEAP)
go_error_at(expr->location(),
"heap-allocated closure, not allowed in runtime");
}
return TRAVERSE_CONTINUE;
}
// Add write barriers to the IR. This are required by the concurrent // Add write barriers to the IR. This are required by the concurrent
// garbage collector. A write barrier is needed for any write of a // garbage collector. A write barrier is needed for any write of a
// pointer into memory controlled by the garbage collector. Write // pointer into memory controlled by the garbage collector. Write
...@@ -370,6 +413,12 @@ Gogo::add_write_barriers() ...@@ -370,6 +413,12 @@ Gogo::add_write_barriers()
Mark_address_taken mat(this); Mark_address_taken mat(this);
this->traverse(&mat); this->traverse(&mat);
if (this->compiling_runtime() && this->package_name() == "runtime")
{
Check_escape chk(this);
this->traverse(&chk);
}
Write_barriers wb(this); Write_barriers wb(this);
this->traverse(&wb); this->traverse(&wb);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment