#include <limits>
#include <stack>
+#include <sstream>
#include "gogo.h"
#include "types.h"
#include "expressions.h"
#include "statements.h"
#include "escape.h"
+#include "ast-dump.h"
// class Node.
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.
Node::Escape_state*
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
// in FNTYPE.
++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;
}
}
if (is_for_statement)
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())
{
case Statement::STATEMENT_VARIABLE_DECLARATION:
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();
+
+ 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;
int
Escape_analysis_assign::expression(Expression** pexpr)
{
+ Gogo* gogo = this->context_->gogo();
+ int debug_level = gogo->debug_escape_level();
+
// Big stuff escapes unconditionally.
Node* n = Node::make_node(*pexpr);
if ((n->encoding() & ESCAPE_MASK) != int(Node::ESCAPE_HEAP)
&& n->is_big(this->context_))
{
+ if (debug_level > 1)
+ inform((*pexpr)->location(), "too large for stack");
n->set_encoding(Node::ESCAPE_HEAP);
(*pexpr)->address_taken(true);
this->assign(this->context_->sink(), n);
if ((*pexpr)->func_expression() == NULL)
(*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())
{
case Expression::EXPRESSION_CALL:
Node* appended = Node::make_node(call->args()->back());
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.
Node* appendee = Node::make_node(call->args()->back());
this->assign_deref(this->context_->sink(), appendee);
void
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();
Function_type* fntype = call->get_function_type();
bool indirect = false;
p != arg_nodes.end();
++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);
}
&& fn->named_object()->is_function()
&& !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();
const Bindings* callee_bindings = f->block()->bindings();
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);
}
}
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);
+ 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);
// Receiver.
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);
}
}
void
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)
{
// Analyze the lhs of the assignment.
}
}
+ 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)
{
// Lost track of the value.
|| src_state->flows.find(dst) != src_state->flows.end())
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())
this->context_->add_dst(dst);
src_state->level = level;
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();
// Input parameter flowing into output parameter?
// 2. return &in
// 3. tmp := in; return &tmp
// 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)
{
int enc =
Node::max_encoding((src->encoding() | ESCAPE_CONTENT_ESCAPES),
Node::ESCAPE_NONE);
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
Node::max_encoding((src->encoding() | ESCAPE_CONTENT_ESCAPES),
Node::ESCAPE_NONE);
src->set_encoding(enc);
+ if (debug_level != 0)
+ inform(src->location(), "leaking param content: %s",
+ src->ast_format(gogo).c_str());
}
else
{
+ if (debug_level != 0)
+ inform(src->location(), "leaking param");
src->set_encoding(Node::ESCAPE_SCOPE);
}
}
Expression* e = src->expr();
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::make_node(e->enclosed_var_expression()->variable());
this->flood(level, dst, enclosed_node, -1);
if (src_leaks)
{
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,
underlying_node, mod_loop_depth);
extra_loop_depth = mod_loop_depth;
if (src_leaks)
{
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;
}
}
if (src_leaks)
{
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;
}
break;
// A closure or bound method; we lost track of actual function
// so if this leaks, this call must be done on the heap.
src->set_encoding(Node::ESCAPE_HEAP);
+ if (debug_level != 0)
+ inform(src->location(), "%s escapes to heap",
+ src->ast_format(gogo).c_str());
}
}
}
{
// Calls to Runtime::NEW get lowered into an allocation expression.
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
&& e->field_reference_expression()->expr()->unary_expression() == NULL)
Gogo::propagate_escape(Escape_context* context, Node* dst)
{
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);
for (std::set<Node*>::const_iterator p = state->flows.begin();
p != state->flows.end();