escape: Add basic debugging.

Emit basic debug information when compiling with the flag
    -fgo-debug-escape#.
    
    Reviewed-on: https://go-review.googlesource.com/22376

	2016-08-02  Chris Manghane  <cmang@google.com>

	* lang.opt: Add -fgo-debug-escape option.
	* go-c.h (go_create_gogo): Add debug_escape_level parameter.
	* go-lang.c (go_langhook_init): Pass go_debug_escape_level to
	go_create_gogo.

From-SVN: r239002
This commit is contained in:
Chris Manghane 2016-08-02 21:43:48 +00:00 committed by Ian Lance Taylor
parent 00803109af
commit 7295570dd4
10 changed files with 577 additions and 7 deletions

View file

@ -1,3 +1,10 @@
2016-08-02 Chris Manghane <cmang@google.com>
* lang.opt: Add -fgo-debug-escape option.
* go-c.h (go_create_gogo): Add debug_escape_level parameter.
* go-lang.c (go_langhook_init): Pass go_debug_escape_level to
go_create_gogo.
2016-05-06 Chris Manghane <cmang@google.com> 2016-05-06 Chris Manghane <cmang@google.com>
* Make-lang.in (GO_OBJS): Add go/escape.o (based on an entirely * Make-lang.in (GO_OBJS): Add go/escape.o (based on an entirely

View file

@ -34,7 +34,8 @@ extern void go_add_search_path (const char*);
extern void go_create_gogo (int int_type_size, int pointer_size, extern void go_create_gogo (int int_type_size, int pointer_size,
const char* pkgpath, const char *prefix, const char* pkgpath, const char *prefix,
const char *relative_import_path, const char *relative_import_path,
bool check_divide_zero, bool check_divide_overflow); bool check_divide_zero, bool check_divide_overflow,
int debug_escape_level);
extern void go_parse_input_files (const char**, unsigned int, extern void go_parse_input_files (const char**, unsigned int,
bool only_check_syntax, bool only_check_syntax,

View file

@ -101,7 +101,7 @@ go_langhook_init (void)
go_type_for_size). */ go_type_for_size). */
go_create_gogo (INT_TYPE_SIZE, POINTER_SIZE, go_pkgpath, go_prefix, go_create_gogo (INT_TYPE_SIZE, POINTER_SIZE, go_pkgpath, go_prefix,
go_relative_import_path, go_check_divide_zero, go_relative_import_path, go_check_divide_zero,
go_check_divide_overflow); go_check_divide_overflow, go_debug_escape_level);
build_common_builtin_nodes (); build_common_builtin_nodes ();

View file

@ -1,4 +1,4 @@
d4b47fef149fc905ae6b418934f6be8cf6be433e 89a0b3a04f80df388242166b8835f12e82ceb194
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.

View file

@ -6,12 +6,14 @@
#include <limits> #include <limits>
#include <stack> #include <stack>
#include <sstream>
#include "gogo.h" #include "gogo.h"
#include "types.h" #include "types.h"
#include "expressions.h" #include "expressions.h"
#include "statements.h" #include "statements.h"
#include "escape.h" #include "escape.h"
#include "ast-dump.h"
// class Node. // class Node.
@ -47,6 +49,305 @@ Node::location() const
return Linemap::unknown_location(); return Linemap::unknown_location();
} }
// To match the cmd/gc debug output, strip away the packed prefixes on functions
// and variable/expressions.
std::string
strip_packed_prefix(Gogo* gogo, const std::string& s)
{
std::string packed_prefix = "." + gogo->pkgpath() + ".";
std::string fmt = s;
for (size_t pos = fmt.find(packed_prefix);
pos != std::string::npos;
pos = fmt.find(packed_prefix))
fmt.erase(pos, packed_prefix.length());
return fmt;
}
// A helper for debugging; return this node's AST formatted string.
// This is an implementation of gc's Nconv with obj.FmtShort.
std::string
Node::ast_format(Gogo* gogo) const
{
std::ostringstream ss;
if (this->is_sink())
ss << ".sink";
else if (this->object() != NULL)
{
Named_object* no = this->object();
if (no->is_function() && no->func_value()->enclosing() != NULL)
return "func literal";
ss << no->name();
}
else if (this->expr() != NULL)
{
Expression* e = this->expr();
bool is_call = e->call_expression() != NULL;
if (is_call)
e->call_expression()->fn();
Func_expression* fe = e->func_expression();;
bool is_closure = fe != NULL && fe->closure() != NULL;
if (is_closure)
{
if (is_call)
return "(func literal)()";
return "func literal";
}
Ast_dump_context::dump_to_stream(this->expr(), &ss);
}
else
{
Statement* s = this->statement();
Goto_unnamed_statement* unnamed = s->goto_unnamed_statement();
if (unnamed != NULL)
{
Statement* derived = unnamed->unnamed_label()->derived_from();
if (derived != NULL)
{
switch (derived->classification())
{
case Statement::STATEMENT_FOR:
case Statement::STATEMENT_FOR_RANGE:
return "for loop";
break;
case Statement::STATEMENT_SWITCH:
return "switch";
break;
case Statement::STATEMENT_TYPE_SWITCH:
return "type switch";
break;
default:
break;
}
}
}
Ast_dump_context::dump_to_stream(s, &ss);
}
return strip_packed_prefix(gogo, ss.str());
}
// A helper for debugging; return this node's detailed format string.
// This is an implementation of gc's Jconv with obj.FmtShort.
std::string
Node::details() const
{
std::stringstream details;
if (!this->is_sink())
details << " l(" << LOCATION_LINE(this->location().gcc_location()) << ")";
bool is_varargs = false;
bool is_address_taken = false;
bool is_in_heap = false;
bool is_assigned = false;
std::string class_name;
Expression* e = this->expr();
Named_object* node_object = NULL;
if (this->object() != NULL)
node_object = this->object();
else if (e != NULL && e->var_expression() != NULL)
node_object = e->var_expression()->named_object();
if (node_object)
{
// TODO(cmang): For named variables and functions, we want to output
// the function depth.
if (node_object->is_variable())
{
Variable* var = node_object->var_value();
is_varargs = var->is_varargs_parameter();
is_address_taken = (var->is_address_taken()
|| var->is_non_escaping_address_taken());
is_in_heap = var->is_in_heap();
is_assigned = var->init() != NULL;
if (var->is_global())
class_name = "PEXTERN";
else if (var->is_parameter())
class_name = "PPARAM";
else if (var->is_closure())
class_name = "PPARAMREF";
else
class_name = "PAUTO";
}
else if (node_object->is_result_variable())
class_name = "PPARAMOUT";
else if (node_object->is_function()
|| node_object->is_function_declaration())
class_name = "PFUNC";
}
else if (e != NULL && e->enclosed_var_expression() != NULL)
{
Named_object* enclosed = e->enclosed_var_expression()->variable();
if (enclosed->is_variable())
{
Variable* var = enclosed->var_value();
is_address_taken = (var->is_address_taken()
|| var->is_non_escaping_address_taken());
}
else
{
Result_variable* var = enclosed->result_var_value();
is_address_taken = (var->is_address_taken()
|| var->is_non_escaping_address_taken());
}
class_name = "PPARAMREF";
}
if (!class_name.empty())
{
details << " class(" << class_name;
if (is_in_heap)
details << ",heap";
details << ")";
}
switch ((this->encoding() & ESCAPE_MASK))
{
case Node::ESCAPE_UNKNOWN:
break;
case Node::ESCAPE_HEAP:
details << " esc(h)";
break;
case Node::ESCAPE_SCOPE:
details << " esc(s)";
break;
case Node::ESCAPE_NONE:
details << " esc(no)";
break;
case Node::ESCAPE_NEVER:
details << " esc(N)";
break;
default:
details << " esc(" << this->encoding() << ")";
break;
}
if (this->state_ != NULL && this->state_->loop_depth != 0)
details << " ld(" << this->state_->loop_depth << ")";
if (is_varargs)
details << " isddd(1)";
if (is_address_taken)
details << " addrtaken";
if (is_assigned)
details << " assigned";
return details.str();
}
std::string
Node::op_format() const
{
std::stringstream op;
Ast_dump_context adc(&op, false);
if (this->expr() != NULL)
{
Expression* e = this->expr();
switch (e->classification())
{
case Expression::EXPRESSION_UNARY:
adc.dump_operator(e->unary_expression()->op());
break;
case Expression::EXPRESSION_BINARY:
adc.dump_operator(e->binary_expression()->op());
break;
case Expression::EXPRESSION_CALL:
op << "function call";
break;
case Expression::EXPRESSION_FUNC_REFERENCE:
if (e->func_expression()->is_runtime_function())
{
switch (e->func_expression()->runtime_code())
{
case Runtime::PANIC:
op << "panic";
case Runtime::APPEND:
op << "append";
break;
case Runtime::COPY:
op << "copy";
break;
case Runtime::MAKECHAN:
case Runtime::MAKECHANBIG:
case Runtime::MAKEMAP:
case Runtime::MAKEMAPBIG:
case Runtime::MAKESLICE1:
case Runtime::MAKESLICE2:
case Runtime::MAKESLICE1BIG:
case Runtime::MAKESLICE2BIG:
op << "make";
break;
case Runtime::DEFER:
op << "defer";
break;
case Runtime::RECOVER:
op << "recover";
break;
case Runtime::CLOSE:
op << "close";
break;
default:
break;
}
}
break;
case Expression::EXPRESSION_ALLOCATION:
op << "new";
break;
case Expression::EXPRESSION_RECEIVE:
op << "<-";
break;
default:
break;
}
}
if (this->statement() != NULL)
{
switch (this->statement()->classification())
{
case Statement::STATEMENT_DEFER:
op << "defer";
break;
case Statement::STATEMENT_RETURN:
op << "return";
break;
default:
break;
}
}
return op.str();
}
// Return this node's state, creating it if has not been initialized. // Return this node's state, creating it if has not been initialized.
Node::Escape_state* Node::Escape_state*
@ -255,6 +556,41 @@ Escape_context::Escape_context(Gogo* gogo, bool recursive)
state->loop_depth = -1; state->loop_depth = -1;
} }
std::string
debug_function_name(Named_object* fn)
{
if (fn == NULL)
return "<S>";
if (!fn->is_function()
|| fn->func_value()->enclosing() == NULL)
return Gogo::unpack_hidden_name(fn->name());
// Closures are named ".$nested#" where # starts from 0 to distinguish
// between closures. The cmd/gc closures are named in the format
// "enclosing.func#" where # starts from 1. If this is a closure, format
// its name to match cmd/gc.
Named_object* enclosing = fn->func_value()->enclosing();
// Extract #.
std::string name = Gogo::unpack_hidden_name(fn->name());
int closure_num = (int)strtol(name.substr(6).c_str(), NULL, 0);
closure_num++;
name = Gogo::unpack_hidden_name(enclosing->name());
char buf[200];
snprintf(buf, sizeof buf, "%s.func%d", name.c_str(), closure_num);
return buf;
}
// Return the name of the current function.
std::string
Escape_context::current_function_name() const
{
return debug_function_name(this->current_function_);
}
// Initialize the dummy return values for this Node N using the results // Initialize the dummy return values for this Node N using the results
// in FNTYPE. // in FNTYPE.
@ -381,6 +717,21 @@ Gogo::analyze_escape()
++fn) ++fn)
this->tag_function(context, *fn); this->tag_function(context, *fn);
if (this->debug_escape_level() != 0)
{
std::vector<Node*> noesc = context->non_escaping_nodes();
for (std::vector<Node*>::const_iterator n = noesc.begin();
n != noesc.end();
++n)
{
Node::Escape_state* state = (*n)->state(context, NULL);
if (((*n)->encoding() & ESCAPE_MASK) == int(Node::ESCAPE_NONE))
inform((*n)->location(), "%s %s does not escape",
strip_packed_prefix(this, debug_function_name(state->fn)).c_str(),
(*n)->ast_format(this).c_str());
}
// TODO(cmang): Which objects in context->noesc actually don't escape.
}
delete context; delete context;
} }
} }
@ -668,6 +1019,20 @@ Escape_analysis_assign::statement(Block*, size_t*, Statement* s)
if (is_for_statement) if (is_for_statement)
this->context_->decrease_loop_depth(); this->context_->decrease_loop_depth();
Gogo* gogo = this->context_->gogo();
int debug_level = gogo->debug_escape_level();
if (debug_level > 1
&& s->unnamed_label_statement() == NULL
&& s->expression_statement() == NULL
&& !s->is_block_statement())
{
Node* n = Node::make_node(s);
std::string fn_name = this->context_->current_function_name();
inform(s->location(), "[%d] %s esc: %s",
this->context_->loop_depth(), fn_name.c_str(),
n->ast_format(gogo).c_str());
}
switch (s->classification()) switch (s->classification())
{ {
case Statement::STATEMENT_VARIABLE_DECLARATION: case Statement::STATEMENT_VARIABLE_DECLARATION:
@ -689,8 +1054,19 @@ Escape_analysis_assign::statement(Block*, size_t*, Statement* s)
case Statement::STATEMENT_LABEL: case Statement::STATEMENT_LABEL:
{ {
if (s->label_statement()->label()->looping()) Label_statement* label_stmt = s->label_statement();
if (label_stmt->label()->looping())
this->context_->increase_loop_depth(); this->context_->increase_loop_depth();
if (debug_level > 1)
{
std::string label_type = (label_stmt->label()->looping()
? "looping"
: "nonlooping");
inform(s->location(), "%s %s label",
label_stmt->label()->name().c_str(),
label_type.c_str());
}
} }
break; break;
@ -767,11 +1143,16 @@ Escape_analysis_assign::statement(Block*, size_t*, Statement* s)
int int
Escape_analysis_assign::expression(Expression** pexpr) Escape_analysis_assign::expression(Expression** pexpr)
{ {
Gogo* gogo = this->context_->gogo();
int debug_level = gogo->debug_escape_level();
// Big stuff escapes unconditionally. // Big stuff escapes unconditionally.
Node* n = Node::make_node(*pexpr); Node* n = Node::make_node(*pexpr);
if ((n->encoding() & ESCAPE_MASK) != int(Node::ESCAPE_HEAP) if ((n->encoding() & ESCAPE_MASK) != int(Node::ESCAPE_HEAP)
&& n->is_big(this->context_)) && n->is_big(this->context_))
{ {
if (debug_level > 1)
inform((*pexpr)->location(), "too large for stack");
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);
@ -780,6 +1161,15 @@ Escape_analysis_assign::expression(Expression** pexpr)
if ((*pexpr)->func_expression() == NULL) if ((*pexpr)->func_expression() == NULL)
(*pexpr)->traverse_subexpressions(this); (*pexpr)->traverse_subexpressions(this);
if (debug_level > 1)
{
Node* n = Node::make_node(*pexpr);
std::string fn_name = this->context_->current_function_name();
inform((*pexpr)->location(), "[%d] %s esc: %s",
this->context_->loop_depth(), fn_name.c_str(),
n->ast_format(gogo).c_str());
}
switch ((*pexpr)->classification()) switch ((*pexpr)->classification())
{ {
case Expression::EXPRESSION_CALL: case Expression::EXPRESSION_CALL:
@ -808,6 +1198,10 @@ Escape_analysis_assign::expression(Expression** pexpr)
Node* appended = Node::make_node(call->args()->back()); Node* appended = Node::make_node(call->args()->back());
this->assign_deref(this->context_->sink(), appended); this->assign_deref(this->context_->sink(), appended);
if (debug_level > 2)
error_at((*pexpr)->location(),
"special treatment of append(slice1, slice2...)");
// The content of the original slice leaks as well. // The content of the original slice leaks as well.
Node* appendee = Node::make_node(call->args()->back()); Node* appendee = Node::make_node(call->args()->back());
this->assign_deref(this->context_->sink(), appendee); this->assign_deref(this->context_->sink(), appendee);
@ -1060,6 +1454,9 @@ Escape_analysis_assign::expression(Expression** pexpr)
void void
Escape_analysis_assign::call(Call_expression* call) Escape_analysis_assign::call(Call_expression* call)
{ {
Gogo* gogo = this->context_->gogo();
int debug_level = gogo->debug_escape_level();
Func_expression* fn = call->fn()->func_expression(); Func_expression* fn = call->fn()->func_expression();
Function_type* fntype = call->get_function_type(); Function_type* fntype = call->get_function_type();
bool indirect = false; bool indirect = false;
@ -1099,6 +1496,10 @@ Escape_analysis_assign::call(Call_expression* call)
p != arg_nodes.end(); p != arg_nodes.end();
++p) ++p)
{ {
if (debug_level > 2)
inform(call->location(),
"esccall:: indirect call <- %s, untracked",
(*p)->ast_format(gogo).c_str());
this->assign(this->context_->sink(), *p); this->assign(this->context_->sink(), *p);
} }
@ -1111,6 +1512,10 @@ Escape_analysis_assign::call(Call_expression* call)
&& fn->named_object()->is_function() && fn->named_object()->is_function()
&& !fntype->is_tagged()) && !fntype->is_tagged())
{ {
if (debug_level > 2)
inform(call->location(), "esccall:: %s in recursive group",
call_node->ast_format(gogo).c_str());
Function* f = fn->named_object()->func_value(); Function* f = fn->named_object()->func_value();
const Bindings* callee_bindings = f->block()->bindings(); const Bindings* callee_bindings = f->block()->bindings();
@ -1176,6 +1581,9 @@ Escape_analysis_assign::call(Call_expression* call)
for (; p != arg_nodes.end(); ++p) for (; p != arg_nodes.end(); ++p)
{ {
if (debug_level > 2)
inform(call->location(), "esccall:: ... <- %s, untracked",
(*p)->ast_format(gogo).c_str());
this->assign(this->context_->sink(), *p); this->assign(this->context_->sink(), *p);
} }
} }
@ -1183,7 +1591,13 @@ Escape_analysis_assign::call(Call_expression* call)
return; return;
} }
if (debug_level > 2)
inform(call->location(), "esccall:: %s not recursive",
call_node->ast_format(gogo).c_str());
Node::Escape_state* call_state = call_node->state(this->context_, NULL); Node::Escape_state* call_state = call_node->state(this->context_, NULL);
if (!call_state->retvals.empty())
error("esc already decorated call %s", call_node->ast_format(gogo).c_str());
this->context_->init_retvals(call_node, fntype); this->context_->init_retvals(call_node, fntype);
// Receiver. // Receiver.
@ -1251,6 +1665,9 @@ Escape_analysis_assign::call(Call_expression* call)
for (; p != arg_nodes.end(); ++p) for (; p != arg_nodes.end(); ++p)
{ {
if (debug_level > 2)
inform(call->location(), "esccall:: ... <- %s, untracked",
(*p)->ast_format(gogo).c_str());
this->assign(this->context_->sink(), *p); this->assign(this->context_->sink(), *p);
} }
} }
@ -1265,6 +1682,17 @@ Escape_analysis_assign::call(Call_expression* call)
void void
Escape_analysis_assign::assign(Node* dst, Node* src) Escape_analysis_assign::assign(Node* dst, Node* src)
{ {
Gogo* gogo = this->context_->gogo();
int debug_level = gogo->debug_escape_level();
if (debug_level > 1)
inform(dst->location(), "[%d] %s escassign: %s(%s)[%s] = %s(%s)[%s]",
this->context_->loop_depth(),
strip_packed_prefix(gogo, this->context_->current_function_name()).c_str(),
dst->ast_format(gogo).c_str(), dst->details().c_str(),
dst->op_format().c_str(),
src->ast_format(gogo).c_str(), src->details().c_str(),
src->op_format().c_str());
if (dst->expr() != NULL) if (dst->expr() != NULL)
{ {
// Analyze the lhs of the assignment. // Analyze the lhs of the assignment.
@ -1684,6 +2112,10 @@ Escape_analysis_assign::assign_from_note(std::string* note,
} }
} }
if (this->context_->gogo()->debug_escape_level() > 2)
inform(src->location(), "assignfromtag:: src= em=%s",
Escape_note::make_tag(enc).c_str());
if (enc == Node::ESCAPE_UNKNOWN) if (enc == Node::ESCAPE_UNKNOWN)
{ {
// Lost track of the value. // Lost track of the value.
@ -1750,6 +2182,11 @@ Escape_analysis_assign::flows(Node* dst, Node* src)
|| src_state->flows.find(dst) != src_state->flows.end()) || src_state->flows.find(dst) != src_state->flows.end())
return; return;
Gogo* gogo = this->context_->gogo();
if (gogo->debug_escape_level() > 2)
inform(Linemap::unknown_location(), "flows:: %s <- %s",
dst->ast_format(gogo).c_str(), src->ast_format(gogo).c_str());
if (dst_state->flows.empty()) if (dst_state->flows.empty())
this->context_->add_dst(dst); this->context_->add_dst(dst);
@ -1905,6 +2342,22 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src,
src_state->level = level; src_state->level = level;
int mod_loop_depth = std::max(extra_loop_depth, src_state->loop_depth); int mod_loop_depth = std::max(extra_loop_depth, src_state->loop_depth);
Gogo* gogo = this->context_->gogo();
int debug_level = gogo->debug_escape_level();
if (debug_level > 1)
inform(Linemap::unknown_location(),
"escwalk: level:{%d %d} depth:%d "
"op=%s %s(%s) "
"scope:%s[%d] "
"extraloopdepth=%d",
level.value(), level.suffix_value(), this->context_->pdepth(),
src->op_format().c_str(),
src->ast_format(gogo).c_str(),
src->details().c_str(),
debug_function_name(src_state->fn).c_str(),
src_state->loop_depth,
extra_loop_depth);
this->context_->increase_pdepth(); this->context_->increase_pdepth();
// Input parameter flowing into output parameter? // Input parameter flowing into output parameter?
@ -1934,6 +2387,20 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src,
// 2. return &in // 2. return &in
// 3. tmp := in; return &tmp // 3. tmp := in; return &tmp
// 4. return *in // 4. return *in
if (debug_level != 0)
{
if (debug_level == 1)
inform(src->location(),
"leaking param: %s to result %s level=%d",
src->ast_format(gogo).c_str(), dst->ast_format(gogo).c_str(),
level.value());
else
inform(src->location(),
"leaking param: %s to result %s level={%d %d}",
src->ast_format(gogo).c_str(), dst->ast_format(gogo).c_str(),
level.value(), level.suffix_value());
}
if ((src->encoding() & ESCAPE_MASK) != Node::ESCAPE_RETURN) if ((src->encoding() & ESCAPE_MASK) != Node::ESCAPE_RETURN)
{ {
int enc = int enc =
@ -1968,6 +2435,9 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src,
Node::max_encoding((src->encoding() | ESCAPE_CONTENT_ESCAPES), Node::max_encoding((src->encoding() | ESCAPE_CONTENT_ESCAPES),
Node::ESCAPE_NONE); Node::ESCAPE_NONE);
src->set_encoding(enc); src->set_encoding(enc);
if (debug_level != 0)
inform(src->location(), "mark escaped content: %s",
src->ast_format(gogo).c_str());
} }
// A src object leaks if its value or address is assigned to a dst object // A src object leaks if its value or address is assigned to a dst object
@ -1987,9 +2457,14 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src,
Node::max_encoding((src->encoding() | ESCAPE_CONTENT_ESCAPES), Node::max_encoding((src->encoding() | ESCAPE_CONTENT_ESCAPES),
Node::ESCAPE_NONE); Node::ESCAPE_NONE);
src->set_encoding(enc); src->set_encoding(enc);
if (debug_level != 0)
inform(src->location(), "leaking param content: %s",
src->ast_format(gogo).c_str());
} }
else else
{ {
if (debug_level != 0)
inform(src->location(), "leaking param");
src->set_encoding(Node::ESCAPE_SCOPE); src->set_encoding(Node::ESCAPE_SCOPE);
} }
} }
@ -1998,6 +2473,10 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src,
Expression* e = src->expr(); Expression* e = src->expr();
if (e->enclosed_var_expression() != NULL) if (e->enclosed_var_expression() != NULL)
{ {
if (src_leaks && debug_level != 0)
inform(src->location(), "leaking closure reference %s",
src->ast_format(gogo).c_str());
Node* enclosed_node = Node* enclosed_node =
Node::make_node(e->enclosed_var_expression()->variable()); Node::make_node(e->enclosed_var_expression()->variable());
this->flood(level, dst, enclosed_node, -1); this->flood(level, dst, enclosed_node, -1);
@ -2020,6 +2499,23 @@ 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)
{
inform(underlying->location(), "moved to heap: %s",
underlying_node->ast_format(gogo).c_str());
if (debug_level > 1)
inform(src->location(),
"%s escapes to heap, level={%d %d}, "
"dst.eld=%d, src.eld=%d",
src->ast_format(gogo).c_str(), level.value(),
level.suffix_value(), dst_state->loop_depth,
mod_loop_depth);
else
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);
extra_loop_depth = mod_loop_depth; extra_loop_depth = mod_loop_depth;
@ -2046,6 +2542,9 @@ 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)
inform(src->location(), "%s escapes to heap",
src->ast_format(gogo).c_str());
extra_loop_depth = mod_loop_depth; extra_loop_depth = mod_loop_depth;
} }
} }
@ -2089,6 +2588,9 @@ 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)
inform(src->location(), "%s escapes to heap",
src->ast_format(gogo).c_str());
extra_loop_depth = mod_loop_depth; extra_loop_depth = mod_loop_depth;
} }
break; break;
@ -2104,6 +2606,9 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src,
// A closure or bound method; we lost track of actual function // A closure or bound method; we lost track of actual function
// so if this leaks, this call must be done on the heap. // so if this leaks, this call must be done on the heap.
src->set_encoding(Node::ESCAPE_HEAP); src->set_encoding(Node::ESCAPE_HEAP);
if (debug_level != 0)
inform(src->location(), "%s escapes to heap",
src->ast_format(gogo).c_str());
} }
} }
} }
@ -2111,6 +2616,9 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src,
{ {
// Calls to Runtime::NEW get lowered into an allocation expression. // Calls to Runtime::NEW get lowered into an allocation expression.
src->set_encoding(Node::ESCAPE_HEAP); src->set_encoding(Node::ESCAPE_HEAP);
if (debug_level != 0)
inform(src->location(), "%s escapes to heap",
src->ast_format(gogo).c_str());
} }
else if ((e->field_reference_expression() != NULL else if ((e->field_reference_expression() != NULL
&& e->field_reference_expression()->expr()->unary_expression() == NULL) && e->field_reference_expression()->expr()->unary_expression() == NULL)
@ -2184,6 +2692,13 @@ void
Gogo::propagate_escape(Escape_context* context, Node* dst) Gogo::propagate_escape(Escape_context* context, Node* dst)
{ {
Node::Escape_state* state = dst->state(context, NULL); Node::Escape_state* state = dst->state(context, NULL);
Gogo* gogo = context->gogo();
if (gogo->debug_escape_level() > 1)
inform(Linemap::unknown_location(), "escflood:%d: dst %s scope:%s[%d]",
context->flood_id(), dst->ast_format(gogo).c_str(),
debug_function_name(state->fn).c_str(),
state->loop_depth);
Escape_analysis_flood eaf(context); Escape_analysis_flood eaf(context);
for (std::set<Node*>::const_iterator p = state->flows.begin(); for (std::set<Node*>::const_iterator p = state->flows.begin();
p != state->flows.end(); p != state->flows.end();

View file

@ -193,6 +193,17 @@ class Node
Location Location
location() const; location() const;
// Return this node's AST formatted string.
std::string
ast_format(Gogo*) const;
// Return this node's detailed format string.
std::string
details() const;
std::string
op_format() const;
// Return this node's escape state. // Return this node's escape state.
Escape_state* Escape_state*
state(Escape_context* context, Named_object* fn); state(Escape_context* context, Named_object* fn);
@ -343,6 +354,10 @@ class Escape_context
set_current_function(Named_object* fn) set_current_function(Named_object* fn)
{ this->current_function_ = fn; } { this->current_function_ = fn; }
// Return the name of the current function.
std::string
current_function_name() const;
// Return true if this is the context for a mutually recursive set of functions. // Return true if this is the context for a mutually recursive set of functions.
bool bool
recursive() const recursive() const

View file

@ -22,7 +22,8 @@ GO_EXTERN_C
void void
go_create_gogo(int int_type_size, int pointer_size, const char *pkgpath, go_create_gogo(int int_type_size, int pointer_size, const char *pkgpath,
const char *prefix, const char *relative_import_path, const char *prefix, const char *relative_import_path,
bool check_divide_by_zero, bool check_divide_overflow) bool check_divide_by_zero, bool check_divide_overflow,
int debug_escape_level)
{ {
go_assert(::gogo == NULL); go_assert(::gogo == NULL);
Linemap* linemap = go_get_linemap(); Linemap* linemap = go_get_linemap();
@ -39,6 +40,7 @@ go_create_gogo(int int_type_size, int pointer_size, const char *pkgpath,
::gogo->set_check_divide_by_zero(check_divide_by_zero); ::gogo->set_check_divide_by_zero(check_divide_by_zero);
if (check_divide_overflow) if (check_divide_overflow)
::gogo->set_check_divide_overflow(check_divide_overflow); ::gogo->set_check_divide_overflow(check_divide_overflow);
::gogo->set_debug_escape_level(debug_escape_level);
} }
// Parse the input files. // Parse the input files.

View file

@ -239,6 +239,16 @@ class Gogo
set_check_divide_overflow(bool b) set_check_divide_overflow(bool b)
{ this->check_divide_overflow_ = b; } { this->check_divide_overflow_ = b; }
// Return the level of escape analysis debug information to emit.
int
debug_escape_level() const
{ return this->debug_escape_level_; }
// Set the level of escape analysis debugging from a command line option.
void
set_debug_escape_level(int level)
{ this->debug_escape_level_ = level; }
// Return the priority to use for the package we are compiling. // Return the priority to use for the package we are compiling.
// This is two more than the largest priority of any package we // This is two more than the largest priority of any package we
// import. // import.
@ -786,6 +796,9 @@ class Gogo
// Whether or not to check for division overflow, from the // Whether or not to check for division overflow, from the
// -fgo-check-divide-overflow option. // -fgo-check-divide-overflow option.
bool check_divide_overflow_; bool check_divide_overflow_;
// The level of escape analysis debug information to emit, from the
// -fgo-debug-escape option.
int debug_escape_level_;
// A list of types to verify. // A list of types to verify.
std::vector<Type*> verify_types_; std::vector<Type*> verify_types_;
// A list of interface types defined while parsing. // A list of interface types defined while parsing.
@ -2715,7 +2728,7 @@ class Unnamed_label
{ {
public: public:
Unnamed_label(Location location) Unnamed_label(Location location)
: location_(location), blabel_(NULL) : location_(location), derived_from_(NULL), blabel_(NULL)
{ } { }
// Get the location where the label is defined. // Get the location where the label is defined.
@ -2728,6 +2741,16 @@ class Unnamed_label
set_location(Location location) set_location(Location location)
{ this->location_ = location; } { this->location_ = location; }
// Get the top level statement this unnamed label is derived from.
Statement*
derived_from() const
{ return this->derived_from_; }
// Set the top level statement this unnamed label is derived from.
void
set_derived_from(Statement* s)
{ this->derived_from_ = s; }
// Return a statement which defines this label. // Return a statement which defines this label.
Bstatement* Bstatement*
get_definition(Translate_context*); get_definition(Translate_context*);
@ -2743,6 +2766,9 @@ class Unnamed_label
// The location where the label is defined. // The location where the label is defined.
Location location_; Location location_;
// The top-level statement this unnamed label was derived/lowered from.
// This is NULL is this label is not the top-level of a lowered statement.
Statement* derived_from_;
// The backend representation of this label. // The backend representation of this label.
Blabel* blabel_; Blabel* blabel_;
}; };

View file

@ -3058,7 +3058,6 @@ Unnamed_label_statement::do_get_backend(Translate_context* context)
return this->label_->get_definition(context); return this->label_->get_definition(context);
} }
// Dump the AST representation for an unnamed label definition statement. // Dump the AST representation for an unnamed label definition statement.
void void
@ -5091,6 +5090,7 @@ For_statement::do_lower(Gogo*, Named_object*, Block* enclosing,
} }
Unnamed_label* top = new Unnamed_label(this->location()); Unnamed_label* top = new Unnamed_label(this->location());
top->set_derived_from(this);
b->add_statement(Statement::make_unnamed_label_statement(top)); b->add_statement(Statement::make_unnamed_label_statement(top));
s = Statement::make_block_statement(this->statements_, s = Statement::make_block_statement(this->statements_,

View file

@ -69,6 +69,10 @@ frequire-return-statement
Go Var(go_require_return_statement) Init(1) Warning Go Var(go_require_return_statement) Init(1) Warning
Functions which return values must end with return statements. Functions which return values must end with return statements.
fgo-debug-escape
Go Joined UInteger Var(go_debug_escape_level) Init(0)
Emit debugging information related to the escape analysis pass when run with -fgo-optimize-allocs.
o o
Go Joined Separate Go Joined Separate
; Documented in common.opt ; Documented in common.opt