3479 lines
104 KiB
C++
3479 lines
104 KiB
C++
// escape.cc -- Go escape analysis (based on Go compiler algorithm).
|
|
|
|
// Copyright 2016 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
#include "go-system.h"
|
|
|
|
#include <limits>
|
|
#include <stack>
|
|
#include <sstream>
|
|
|
|
#include "gogo.h"
|
|
#include "types.h"
|
|
#include "expressions.h"
|
|
#include "statements.h"
|
|
#include "escape.h"
|
|
#include "lex.h"
|
|
#include "ast-dump.h"
|
|
#include "go-optimize.h"
|
|
#include "go-diagnostics.h"
|
|
#include "go-sha1.h"
|
|
|
|
// class Node.
|
|
|
|
// Return the node's type, if it makes sense for it to have one.
|
|
|
|
Type*
|
|
Node::type() const
|
|
{
|
|
if (this->object() != NULL
|
|
&& this->object()->is_variable())
|
|
return this->object()->var_value()->type();
|
|
else if (this->object() != NULL
|
|
&& this->object()->is_function())
|
|
return this->object()->func_value()->type();
|
|
else if (this->expr() != NULL)
|
|
return this->expr()->type();
|
|
else if (this->is_indirect())
|
|
{
|
|
if (this->child()->type()->deref()->is_void_type())
|
|
// This is a void*. The referred type can be actually any type,
|
|
// which may also be pointer. We model it as another void*, so
|
|
// we don't lose pointer-ness.
|
|
return this->child()->type();
|
|
else if (this->child()->type()->is_slice_type())
|
|
// We model "indirect" of a slice as dereferencing its pointer
|
|
// field (to get element). Use element type here.
|
|
return this->child()->type()->array_type()->element_type();
|
|
else if (this->child()->type()->is_string_type())
|
|
return Type::lookup_integer_type("uint8");
|
|
else
|
|
return this->child()->type()->deref();
|
|
}
|
|
else if (this->statement() != NULL
|
|
&& this->statement()->temporary_statement() != NULL)
|
|
return this->statement()->temporary_statement()->type();
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
// A helper for reporting; return this node's location.
|
|
|
|
Location
|
|
Node::location() const
|
|
{
|
|
if (this->object() != NULL && !this->object()->is_sink())
|
|
return this->object()->location();
|
|
else if (this->expr() != NULL)
|
|
return this->expr()->location();
|
|
else if (this->statement() != NULL)
|
|
return this->statement()->location();
|
|
else if (this->is_indirect())
|
|
return this->child()->location();
|
|
else
|
|
return Linemap::unknown_location();
|
|
}
|
|
|
|
// A helper for reporting; return the location where the underlying
|
|
// object is defined.
|
|
|
|
Location
|
|
Node::definition_location() const
|
|
{
|
|
if (this->object() != NULL && !this->object()->is_sink())
|
|
{
|
|
Named_object* no = this->object();
|
|
if (no->is_variable() || no->is_result_variable())
|
|
return no->location();
|
|
}
|
|
else if (this->expr() != NULL)
|
|
{
|
|
Var_expression* ve = this->expr()->var_expression();
|
|
if (ve != NULL)
|
|
{
|
|
Named_object* no = ve->named_object();
|
|
if (no->is_variable() || no->is_result_variable())
|
|
return no->location();
|
|
}
|
|
Enclosed_var_expression* eve = this->expr()->enclosed_var_expression();
|
|
if (eve != NULL)
|
|
{
|
|
Named_object* no = eve->variable();
|
|
if (no->is_variable() || no->is_result_variable())
|
|
return no->location();
|
|
}
|
|
}
|
|
return this->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->message_name();
|
|
}
|
|
else if (this->expr() != NULL)
|
|
{
|
|
Expression* e = this->expr();
|
|
|
|
bool is_call = e->call_expression() != NULL;
|
|
if (is_call)
|
|
e = e->call_expression()->fn();
|
|
Func_expression* fe = e->func_expression();;
|
|
if (fe != NULL)
|
|
{
|
|
Named_object* no = fe->named_object();
|
|
if (no->is_function() && no->func_value()->enclosing() != NULL)
|
|
{
|
|
if (is_call)
|
|
return "(func literal)()";
|
|
return "func literal";
|
|
}
|
|
}
|
|
|
|
Ast_dump_context::dump_to_stream(this->expr(), &ss);
|
|
}
|
|
else if (this->statement() != NULL)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
Temporary_statement* tmp = s->temporary_statement();
|
|
if (tmp != NULL)
|
|
{
|
|
// Temporary's format can never match gc's output, and
|
|
// temporaries are inserted differently anyway. We just
|
|
// print something convenient.
|
|
ss << "tmp." << (uintptr_t) tmp;
|
|
if (tmp->init() != NULL)
|
|
{
|
|
ss << " [ = ";
|
|
Ast_dump_context::dump_to_stream(tmp->init(), &ss);
|
|
ss << " ]";
|
|
}
|
|
}
|
|
else
|
|
Ast_dump_context::dump_to_stream(s, &ss);
|
|
}
|
|
else if (this->is_indirect())
|
|
return "*(" + this->child()->ast_format(gogo) + ")";
|
|
|
|
std::string s = strip_packed_prefix(gogo, ss.str());
|
|
|
|
// trim trailing space
|
|
return s.substr(0, s.find_last_not_of(' ') + 1);
|
|
}
|
|
|
|
// 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()
|
|
{
|
|
std::stringstream details;
|
|
|
|
if (!this->is_sink())
|
|
details << " l(" << Linemap::location_to_line(this->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_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::GOPANIC:
|
|
op << "panic";
|
|
break;
|
|
|
|
case Runtime::GROWSLICE:
|
|
op << "append";
|
|
break;
|
|
|
|
case Runtime::TYPEDSLICECOPY:
|
|
op << "copy";
|
|
break;
|
|
|
|
case Runtime::MAKECHAN:
|
|
case Runtime::MAKECHAN64:
|
|
case Runtime::MAKEMAP:
|
|
case Runtime::MAKESLICE:
|
|
case Runtime::MAKESLICE64:
|
|
op << "make";
|
|
break;
|
|
|
|
case Runtime::DEFERPROC:
|
|
op << "defer";
|
|
break;
|
|
|
|
case Runtime::GORECOVER:
|
|
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;
|
|
}
|
|
}
|
|
if (this->is_indirect())
|
|
op << "*";
|
|
return op.str();
|
|
}
|
|
|
|
// Return this node's state, creating it if has not been initialized.
|
|
|
|
Node::Escape_state*
|
|
Node::state(Escape_context* context, Named_object* fn)
|
|
{
|
|
if (this->state_ == NULL)
|
|
{
|
|
if (this->expr() != NULL && this->expr()->var_expression() != NULL)
|
|
{
|
|
// Tie state of variable references to underlying variables.
|
|
Named_object* var_no = this->expr()->var_expression()->named_object();
|
|
Node* var_node = Node::make_node(var_no);
|
|
this->state_ = var_node->state(context, fn);
|
|
}
|
|
else
|
|
{
|
|
this->state_ = new Node::Escape_state;
|
|
if (fn == NULL)
|
|
fn = context->current_function();
|
|
|
|
this->state_->fn = fn;
|
|
}
|
|
}
|
|
go_assert(this->state_ != NULL);
|
|
return this->state_;
|
|
}
|
|
|
|
Node::~Node()
|
|
{
|
|
if (this->state_ != NULL)
|
|
{
|
|
if (this->expr() == NULL || this->expr()->var_expression() == NULL)
|
|
// Var expression Node is excluded since it shares state with the
|
|
// underlying var Node.
|
|
delete this->state_;
|
|
}
|
|
}
|
|
|
|
int
|
|
Node::encoding()
|
|
{
|
|
if (this->expr() != NULL
|
|
&& this->expr()->var_expression() != NULL)
|
|
{
|
|
// Get the underlying object's encoding.
|
|
Named_object* no = this->expr()->var_expression()->named_object();
|
|
int enc = Node::make_node(no)->encoding();
|
|
this->encoding_ = enc;
|
|
}
|
|
return this->encoding_;
|
|
}
|
|
|
|
void
|
|
Node::set_encoding(int enc)
|
|
{
|
|
this->encoding_ = enc;
|
|
if (this->expr() != NULL)
|
|
{
|
|
if (this->expr()->var_expression() != NULL)
|
|
{
|
|
// Set underlying object as well.
|
|
Named_object* no = this->expr()->var_expression()->named_object();
|
|
Node::make_node(no)->set_encoding(enc);
|
|
}
|
|
else if (this->expr()->func_expression() != NULL)
|
|
{
|
|
// Propagate the escape state to the underlying
|
|
// closure (heap expression).
|
|
Expression* closure = this->expr()->func_expression()->closure();
|
|
if (closure != NULL)
|
|
Node::make_node(closure)->set_encoding(enc);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
Node::is_big(Escape_context* context) const
|
|
{
|
|
Type* t = this->type();
|
|
if (t == NULL
|
|
|| t->is_call_multiple_result_type()
|
|
|| t->is_sink_type()
|
|
|| t->is_void_type()
|
|
|| t->is_abstract())
|
|
return false;
|
|
|
|
bool big = false;
|
|
if (t->struct_type() != NULL
|
|
|| (t->array_type() != NULL && !t->is_slice_type()))
|
|
{
|
|
int64_t size;
|
|
bool ok = t->backend_type_size(context->gogo(), &size);
|
|
big = ok && (size < 0 || size > 10 * 1024 * 1024);
|
|
}
|
|
|
|
if (this->expr() != NULL)
|
|
{
|
|
if (this->expr()->allocation_expression() != NULL)
|
|
{
|
|
Type* pt = t->deref();
|
|
if (pt->struct_type() != NULL
|
|
|| (pt->array_type() != NULL && !pt->is_slice_type()))
|
|
{
|
|
int64_t size;
|
|
bool ok = pt->backend_type_size(context->gogo(), &size);
|
|
if (ok)
|
|
big = big || size <= 0 || size >= (1 << 16);
|
|
}
|
|
}
|
|
else if (this->expr()->call_expression() != NULL)
|
|
{
|
|
Call_expression* call = this->expr()->call_expression();
|
|
Func_expression* fn = call->fn()->func_expression();
|
|
if (fn != NULL
|
|
&& fn->is_runtime_function()
|
|
&& (fn->runtime_code() == Runtime::MAKESLICE
|
|
|| fn->runtime_code() == Runtime::MAKESLICE64))
|
|
{
|
|
// Second argument is length.
|
|
Expression_list::iterator p = call->args()->begin();
|
|
++p;
|
|
|
|
Expression* e = *p;
|
|
if (e->temporary_reference_expression() != NULL)
|
|
{
|
|
Temporary_reference_expression* tre = e->temporary_reference_expression();
|
|
if (tre->statement() != NULL && tre->statement()->init() != NULL)
|
|
e = tre->statement()->init();
|
|
}
|
|
|
|
Numeric_constant nc;
|
|
unsigned long v;
|
|
if (e->numeric_constant_value(&nc)
|
|
&& nc.to_unsigned_long(&v) == Numeric_constant::NC_UL_VALID)
|
|
big = big || v >= (1 << 16);
|
|
}
|
|
}
|
|
}
|
|
|
|
return big;
|
|
}
|
|
|
|
bool
|
|
Node::is_sink() const
|
|
{
|
|
if (this->object() != NULL
|
|
&& this->object()->is_sink())
|
|
return true;
|
|
else if (this->expr() != NULL
|
|
&& this->expr()->is_sink_expression())
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
Unordered_map(Named_object*, Node*) Node::objects;
|
|
Unordered_map(Expression*, Node*) Node::expressions;
|
|
Unordered_map(Statement*, Node*) Node::statements;
|
|
std::vector<Node*> Node::indirects;
|
|
|
|
// Make a object node or return a cached node for this object.
|
|
|
|
Node*
|
|
Node::make_node(Named_object* no)
|
|
{
|
|
std::pair<Named_object*, Node*> val(no, NULL);
|
|
std::pair<Unordered_map(Named_object*, Node*)::iterator, bool> ins =
|
|
Node::objects.insert(val);
|
|
if (ins.second)
|
|
ins.first->second = new Node(no);
|
|
return ins.first->second;
|
|
}
|
|
|
|
// Make an expression node or return a cached node for this expression.
|
|
|
|
Node*
|
|
Node::make_node(Expression* e)
|
|
{
|
|
std::pair<Expression*, Node*> val(e, NULL);
|
|
std::pair<Unordered_map(Expression*, Node*)::iterator, bool> ins =
|
|
Node::expressions.insert(val);
|
|
if (ins.second)
|
|
ins.first->second = new Node(e);
|
|
return ins.first->second;
|
|
}
|
|
|
|
// Make a statement node or return a cached node for this statement.
|
|
|
|
Node*
|
|
Node::make_node(Statement* s)
|
|
{
|
|
std::pair<Statement*, Node*> val(s, NULL);
|
|
std::pair<Unordered_map(Statement*, Node*)::iterator, bool> ins =
|
|
Node::statements.insert(val);
|
|
if (ins.second)
|
|
ins.first->second = new Node(s);
|
|
return ins.first->second;
|
|
}
|
|
|
|
// Make an indirect node with given child.
|
|
|
|
Node*
|
|
Node::make_indirect_node(Node* child)
|
|
{
|
|
Node* n = new Node(child);
|
|
Node::indirects.push_back(n);
|
|
return n;
|
|
}
|
|
|
|
// Returns the maximum of an exisiting escape value
|
|
// (and its additional parameter flow flags) and a new escape type.
|
|
|
|
int
|
|
Node::max_encoding(int e, int etype)
|
|
{
|
|
if ((e & ESCAPE_MASK) > etype)
|
|
return e;
|
|
if (etype == Node::ESCAPE_NONE || etype == Node::ESCAPE_RETURN)
|
|
return (e & ~ESCAPE_MASK) | etype;
|
|
return etype;
|
|
}
|
|
|
|
// Return a modified encoding for an input parameter that flows into an
|
|
// output parameter.
|
|
|
|
int
|
|
Node::note_inout_flows(int e, int index, Level level)
|
|
{
|
|
// Flow+level is encoded in two bits.
|
|
// 00 = not flow, xx = level+1 for 0 <= level <= maxEncodedLevel.
|
|
// 16 bits for Esc allows 6x2bits or 4x3bits or 3x4bits if additional
|
|
// information would be useful.
|
|
if (level.value() <= 0 && level.suffix_value() > 0)
|
|
return Node::max_encoding(e|ESCAPE_CONTENT_ESCAPES, Node::ESCAPE_NONE);
|
|
if (level.value() < 0)
|
|
return Node::ESCAPE_HEAP;
|
|
if (level.value() > ESCAPE_MAX_ENCODED_LEVEL)
|
|
level = Level::From(ESCAPE_MAX_ENCODED_LEVEL);
|
|
|
|
int encoded = level.value() + 1;
|
|
int shift = ESCAPE_BITS_PER_OUTPUT_IN_TAG * index + ESCAPE_RETURN_BITS;
|
|
int old = (e >> shift) & ESCAPE_BITS_MASK_FOR_TAG;
|
|
if (old == 0
|
|
|| (encoded != 0 && encoded < old))
|
|
old = encoded;
|
|
|
|
int encoded_flow = old << shift;
|
|
if (((encoded_flow >> shift) & ESCAPE_BITS_MASK_FOR_TAG) != old)
|
|
{
|
|
// Failed to encode. Put this on the heap.
|
|
return Node::ESCAPE_HEAP;
|
|
}
|
|
|
|
return (e & ~(ESCAPE_BITS_MASK_FOR_TAG << shift)) | encoded_flow;
|
|
}
|
|
|
|
// Class Escape_context.
|
|
|
|
Escape_context::Escape_context(Gogo* gogo, bool recursive)
|
|
: gogo_(gogo), current_function_(NULL), recursive_(recursive),
|
|
sink_(Node::make_node(Named_object::make_sink())), loop_depth_(0),
|
|
flood_id_(0), pdepth_(0)
|
|
{
|
|
// The sink always escapes to heap and strictly lives outside of the
|
|
// current function i.e. loop_depth == -1.
|
|
Node::Escape_state* state = this->sink_->state(this, NULL);
|
|
state->loop_depth = -1;
|
|
}
|
|
|
|
std::string
|
|
debug_function_name(Named_object* fn)
|
|
{
|
|
if (fn == NULL)
|
|
return "<S>";
|
|
|
|
if (!fn->is_function())
|
|
return Gogo::unpack_hidden_name(fn->name());
|
|
|
|
std::string fnname = Gogo::unpack_hidden_name(fn->name());
|
|
if (fn->func_value()->is_method())
|
|
{
|
|
// Methods in gc compiler are named "T.m" or "(*T).m" where
|
|
// T is the receiver type. Add the receiver here.
|
|
Type* rt = fn->func_value()->type()->receiver()->type();
|
|
switch (rt->classification())
|
|
{
|
|
case Type::TYPE_NAMED:
|
|
fnname = rt->named_type()->name() + "." + fnname;
|
|
break;
|
|
|
|
case Type::TYPE_POINTER:
|
|
{
|
|
Named_type* nt = rt->points_to()->named_type();
|
|
if (nt != NULL)
|
|
fnname = "(*" + nt->name() + ")." + fnname;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return fnname;
|
|
}
|
|
|
|
// 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.
|
|
|
|
void
|
|
Escape_context::init_retvals(Node* n, Function_type* fntype)
|
|
{
|
|
if (fntype == NULL || fntype->results() == NULL)
|
|
return;
|
|
|
|
Node::Escape_state* state = n->state(this, NULL);
|
|
state->retvals.clear();
|
|
Location loc = n->location();
|
|
|
|
int i = 0;
|
|
char buf[50];
|
|
for (Typed_identifier_list::const_iterator p = fntype->results()->begin();
|
|
p != fntype->results()->end();
|
|
++p, ++i)
|
|
{
|
|
snprintf(buf, sizeof buf, ".out%d", i);
|
|
Variable* dummy_var = new Variable(p->type(), NULL, false, false,
|
|
false, loc);
|
|
dummy_var->set_is_used();
|
|
Named_object* dummy_no =
|
|
Named_object::make_variable(buf, NULL, dummy_var);
|
|
Node* dummy_node = Node::make_node(dummy_no);
|
|
// Initialize the state of the dummy output node.
|
|
Node::Escape_state* dummy_node_state = dummy_node->state(this, NULL);
|
|
dummy_node_state->loop_depth = this->loop_depth_;
|
|
|
|
// Add dummy node to the retvals of n.
|
|
state->retvals.push_back(dummy_node);
|
|
}
|
|
}
|
|
|
|
|
|
// Apply an indirection to N and return the result.
|
|
|
|
Node*
|
|
Escape_context::add_dereference(Node* n)
|
|
{
|
|
Expression* e = n->expr();
|
|
Location loc = n->location();
|
|
Node* ind;
|
|
if (e != NULL
|
|
&& e->type()->points_to() != NULL
|
|
&& !e->type()->points_to()->is_void_type())
|
|
{
|
|
// We don't dereference void*, which can be actually any pointer type.
|
|
Expression* deref_expr = Expression::make_unary(OPERATOR_MULT, e, loc);
|
|
ind = Node::make_node(deref_expr);
|
|
}
|
|
else
|
|
// The gc compiler simply makes an OIND node. We can't do it
|
|
// for non-pointer type because that will result in a type error.
|
|
// Instead, we model this by making a node with a special flavor.
|
|
ind = Node::make_indirect_node(n);
|
|
|
|
// Initialize the state.
|
|
Node::Escape_state* state = ind->state(this, NULL);
|
|
state->loop_depth = n->state(this, NULL)->loop_depth;
|
|
return ind;
|
|
}
|
|
|
|
void
|
|
Escape_context::track(Node* n)
|
|
{
|
|
n->set_encoding(Node::ESCAPE_NONE);
|
|
// Initialize this node's state if it hasn't been encountered
|
|
// before.
|
|
Node::Escape_state* state = n->state(this, NULL);
|
|
state->loop_depth = this->loop_depth_;
|
|
|
|
this->noesc_.push_back(n);
|
|
}
|
|
|
|
// Return the string representation of an escapement encoding.
|
|
|
|
std::string
|
|
Escape_note::make_tag(int encoding)
|
|
{
|
|
char buf[50];
|
|
snprintf(buf, sizeof buf, "esc:0x%x", encoding);
|
|
return buf;
|
|
}
|
|
|
|
// Return the escapement encoding for a string tag.
|
|
|
|
int
|
|
Escape_note::parse_tag(std::string* tag)
|
|
{
|
|
if (tag == NULL || tag->substr(0, 4) != "esc:")
|
|
return Node::ESCAPE_UNKNOWN;
|
|
int encoding = (int)strtol(tag->substr(4).c_str(), NULL, 0);
|
|
if (encoding == 0)
|
|
return Node::ESCAPE_UNKNOWN;
|
|
return encoding;
|
|
}
|
|
|
|
|
|
// The -fgo-optimize-alloc flag activates this escape analysis.
|
|
|
|
Go_optimize optimize_allocation_flag("allocs", true);
|
|
|
|
// A helper function to compute whether a function name has a
|
|
// matching hash value.
|
|
|
|
static bool
|
|
escape_hash_match(std::string suffix, std::string name)
|
|
{
|
|
if (suffix.empty())
|
|
return true;
|
|
if (suffix.at(0) == '-')
|
|
return !escape_hash_match(suffix.substr(1), name);
|
|
|
|
const char* p = name.c_str();
|
|
Go_sha1_helper* sha1_helper = go_create_sha1_helper();
|
|
sha1_helper->process_bytes(p, strlen(p));
|
|
std::string s = sha1_helper->finish();
|
|
delete sha1_helper;
|
|
|
|
int j = suffix.size() - 1;
|
|
for (int i = s.size() - 1; i >= 0; i--)
|
|
{
|
|
char c = s.at(i);
|
|
for (int k = 0; k < 8; k++, j--, c>>=1)
|
|
{
|
|
if (j < 0)
|
|
return true;
|
|
char bit = suffix.at(j) - '0';
|
|
if ((c&1) != bit)
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Analyze the program flow for escape information.
|
|
|
|
void
|
|
Gogo::analyze_escape()
|
|
{
|
|
if (saw_errors())
|
|
return;
|
|
|
|
if (!optimize_allocation_flag.is_enabled()
|
|
&& !this->compiling_runtime())
|
|
// We always run escape analysis when compiling runtime.
|
|
return;
|
|
|
|
// Discover strongly connected groups of functions to analyze for escape
|
|
// information in this package.
|
|
this->discover_analysis_sets();
|
|
|
|
if (!this->debug_escape_hash().empty())
|
|
std::cerr << "debug-escape-hash " << this->debug_escape_hash() << "\n";
|
|
|
|
for (std::vector<Analysis_set>::iterator p = this->analysis_sets_.begin();
|
|
p != this->analysis_sets_.end();
|
|
++p)
|
|
{
|
|
std::vector<Named_object*> stack = p->first;
|
|
|
|
if (!this->debug_escape_hash().empty())
|
|
{
|
|
bool match = false;
|
|
for (std::vector<Named_object*>::const_iterator fn = stack.begin();
|
|
fn != stack.end();
|
|
++fn)
|
|
match = match || escape_hash_match(this->debug_escape_hash(), (*fn)->message_name());
|
|
if (!match)
|
|
{
|
|
// Escape analysis won't run on these functions, but still
|
|
// need to tag them, so the caller knows.
|
|
for (std::vector<Named_object*>::iterator fn = stack.begin();
|
|
fn != stack.end();
|
|
++fn)
|
|
if ((*fn)->is_function())
|
|
{
|
|
Function_type* fntype = (*fn)->func_value()->type();
|
|
fntype->set_is_tagged();
|
|
|
|
std::cerr << "debug-escape-hash disables " << debug_function_name(*fn) << "\n";
|
|
}
|
|
|
|
continue;
|
|
}
|
|
for (std::vector<Named_object*>::const_iterator fn = stack.begin();
|
|
fn != stack.end();
|
|
++fn)
|
|
if ((*fn)->is_function())
|
|
std::cerr << "debug-escape-hash triggers " << debug_function_name(*fn) << "\n";
|
|
}
|
|
|
|
Escape_context* context = new Escape_context(this, p->second);
|
|
|
|
// Analyze the flow of each function; build the connection graph.
|
|
// This is the assign phase.
|
|
for (std::vector<Named_object*>::reverse_iterator fn = stack.rbegin();
|
|
fn != stack.rend();
|
|
++fn)
|
|
{
|
|
context->set_current_function(*fn);
|
|
this->assign_connectivity(context, *fn);
|
|
}
|
|
|
|
// Propagate levels across each dst. This is the flood phase.
|
|
std::set<Node*> dsts = context->dsts();
|
|
Unordered_map(Node*, int) escapes;
|
|
for (std::set<Node*>::iterator n = dsts.begin();
|
|
n != dsts.end();
|
|
++n)
|
|
{
|
|
escapes[*n] = (*n)->encoding();
|
|
this->propagate_escape(context, *n);
|
|
}
|
|
for (;;)
|
|
{
|
|
// Reflood if the roots' escape states increase. Run until fix point.
|
|
// This is rare.
|
|
bool done = true;
|
|
for (std::set<Node*>::iterator n = dsts.begin();
|
|
n != dsts.end();
|
|
++n)
|
|
{
|
|
if ((*n)->object() == NULL
|
|
&& ((*n)->expr() == NULL
|
|
|| ((*n)->expr()->var_expression() == NULL
|
|
&& (*n)->expr()->enclosed_var_expression() == NULL
|
|
&& (*n)->expr()->func_expression() == NULL)))
|
|
continue;
|
|
if (escapes[*n] != (*n)->encoding())
|
|
{
|
|
done = false;
|
|
if (this->debug_escape_level() > 2)
|
|
go_debug((*n)->location(), "Reflooding %s %s",
|
|
debug_function_name((*n)->state(context, NULL)->fn).c_str(),
|
|
(*n)->ast_format(this).c_str());
|
|
escapes[*n] = (*n)->encoding();
|
|
this->propagate_escape(context, *n);
|
|
}
|
|
}
|
|
if (done)
|
|
break;
|
|
}
|
|
|
|
// Tag each exported function's parameters with escape information.
|
|
for (std::vector<Named_object*>::iterator fn = stack.begin();
|
|
fn != stack.end();
|
|
++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() == Node::ESCAPE_NONE)
|
|
go_debug((*n)->location(), "%s %s does not escape",
|
|
strip_packed_prefix(this, debug_function_name(state->fn)).c_str(),
|
|
(*n)->ast_format(this).c_str());
|
|
}
|
|
}
|
|
delete context;
|
|
}
|
|
}
|
|
|
|
// Traverse the program, discovering the functions that are roots of strongly
|
|
// connected components. The goal of this phase to produce a set of functions
|
|
// that must be analyzed in order.
|
|
|
|
class Escape_analysis_discover : public Traverse
|
|
{
|
|
public:
|
|
Escape_analysis_discover(Gogo* gogo)
|
|
: Traverse(traverse_functions | traverse_func_declarations),
|
|
gogo_(gogo), component_ids_()
|
|
{ }
|
|
|
|
int
|
|
function(Named_object*);
|
|
|
|
int
|
|
function_declaration(Named_object*);
|
|
|
|
int
|
|
visit(Named_object*);
|
|
|
|
int
|
|
visit_code(Named_object*, int);
|
|
|
|
private:
|
|
// A counter used to generate the ID for the function node in the graph.
|
|
static int id;
|
|
|
|
// Type used to map functions to an ID in a graph of connected components.
|
|
typedef Unordered_map(Named_object*, int) Component_ids;
|
|
|
|
// The Go IR.
|
|
Gogo* gogo_;
|
|
// The list of functions encountered during connected component discovery.
|
|
Component_ids component_ids_;
|
|
// The stack of functions that this component consists of.
|
|
std::stack<Named_object*> stack_;
|
|
};
|
|
|
|
int Escape_analysis_discover::id = 0;
|
|
|
|
// Visit each function.
|
|
|
|
int
|
|
Escape_analysis_discover::function(Named_object* fn)
|
|
{
|
|
this->visit(fn);
|
|
return TRAVERSE_CONTINUE;
|
|
}
|
|
|
|
int
|
|
Escape_analysis_discover::function_declaration(Named_object* fn)
|
|
{
|
|
this->visit(fn);
|
|
return TRAVERSE_CONTINUE;
|
|
}
|
|
|
|
// Visit a function FN, adding it to the current stack of functions
|
|
// in this connected component. If this is the root of the component,
|
|
// create a set of functions to be analyzed later.
|
|
//
|
|
// Finding these sets is finding strongly connected components
|
|
// in the static call graph. The algorithm for doing that is taken
|
|
// from Sedgewick, Algorithms, Second Edition, p. 482, with two
|
|
// adaptations.
|
|
//
|
|
// First, a closure (fn->func_value()->enclosing() == NULL) cannot be the
|
|
// root of a connected component. Refusing to use it as a root
|
|
// forces it into the component of the function in which it appears.
|
|
// This is more convenient for escape analysis.
|
|
//
|
|
// Second, each function becomes two virtual nodes in the graph,
|
|
// with numbers n and n+1. We record the function's node number as n
|
|
// but search from node n+1. If the search tells us that the component
|
|
// number (min) is n+1, we know that this is a trivial component: one function
|
|
// plus its closures. If the search tells us that the component number is
|
|
// n, then there was a path from node n+1 back to node n, meaning that
|
|
// the function set is mutually recursive. The escape analysis can be
|
|
// more precise when analyzing a single non-recursive function than
|
|
// when analyzing a set of mutually recursive functions.
|
|
|
|
int
|
|
Escape_analysis_discover::visit(Named_object* fn)
|
|
{
|
|
Component_ids::const_iterator p = this->component_ids_.find(fn);
|
|
if (p != this->component_ids_.end())
|
|
// Already visited.
|
|
return p->second;
|
|
|
|
this->id++;
|
|
int id = this->id;
|
|
this->component_ids_[fn] = id;
|
|
this->id++;
|
|
int min = this->id;
|
|
|
|
this->stack_.push(fn);
|
|
min = this->visit_code(fn, min);
|
|
if ((min == id || min == id + 1)
|
|
&& ((fn->is_function() && fn->func_value()->enclosing() == NULL)
|
|
|| fn->is_function_declaration()))
|
|
{
|
|
bool recursive = min == id;
|
|
std::vector<Named_object*> group;
|
|
|
|
for (; !this->stack_.empty(); this->stack_.pop())
|
|
{
|
|
Named_object* n = this->stack_.top();
|
|
if (n == fn)
|
|
{
|
|
this->stack_.pop();
|
|
break;
|
|
}
|
|
|
|
group.push_back(n);
|
|
this->component_ids_[n] = std::numeric_limits<int>::max();
|
|
}
|
|
group.push_back(fn);
|
|
this->component_ids_[fn] = std::numeric_limits<int>::max();
|
|
|
|
std::reverse(group.begin(), group.end());
|
|
this->gogo_->add_analysis_set(group, recursive);
|
|
}
|
|
|
|
return min;
|
|
}
|
|
|
|
// Helper class for discovery step. Traverse expressions looking for
|
|
// function calls and closures to visit during the discovery step.
|
|
|
|
class Escape_discover_expr : public Traverse
|
|
{
|
|
public:
|
|
Escape_discover_expr(Escape_analysis_discover* ead, int min)
|
|
: Traverse(traverse_expressions),
|
|
ead_(ead), min_(min)
|
|
{ }
|
|
|
|
int
|
|
min()
|
|
{ return this->min_; }
|
|
|
|
int
|
|
expression(Expression** pexpr);
|
|
|
|
private:
|
|
// The original discovery analysis.
|
|
Escape_analysis_discover* ead_;
|
|
// The minimum component ID in this group.
|
|
int min_;
|
|
};
|
|
|
|
// Visit any calls or closures found when discovering expressions.
|
|
|
|
int
|
|
Escape_discover_expr::expression(Expression** pexpr)
|
|
{
|
|
Expression* e = *pexpr;
|
|
Named_object* fn = NULL;
|
|
if (e->call_expression() != NULL
|
|
&& e->call_expression()->fn()->func_expression() != NULL)
|
|
{
|
|
// Method call or function call.
|
|
fn = e->call_expression()->fn()->func_expression()->named_object();
|
|
}
|
|
else if (e->func_expression() != NULL)
|
|
{
|
|
Named_object* no = e->func_expression()->named_object();
|
|
if (no->is_function() && no->func_value()->enclosing() != NULL)
|
|
{
|
|
// Nested function.
|
|
fn = no;
|
|
}
|
|
}
|
|
|
|
if (fn != NULL)
|
|
this->min_ = std::min(this->min_, this->ead_->visit(fn));
|
|
return TRAVERSE_CONTINUE;
|
|
}
|
|
|
|
// Visit the body of each function, returns ID of the minimum connected
|
|
// component found in the body.
|
|
|
|
int
|
|
Escape_analysis_discover::visit_code(Named_object* fn, int min)
|
|
{
|
|
if (!fn->is_function())
|
|
return min;
|
|
|
|
Escape_discover_expr ede(this, min);
|
|
fn->func_value()->traverse(&ede);
|
|
return ede.min();
|
|
}
|
|
|
|
// Discover strongly connected groups of functions to analyze.
|
|
|
|
void
|
|
Gogo::discover_analysis_sets()
|
|
{
|
|
Escape_analysis_discover ead(this);
|
|
this->traverse(&ead);
|
|
}
|
|
|
|
// Traverse all label and goto statements and mark the underlying label
|
|
// as looping or not looping.
|
|
|
|
class Escape_analysis_loop : public Traverse
|
|
{
|
|
public:
|
|
Escape_analysis_loop()
|
|
: Traverse(traverse_statements)
|
|
{ }
|
|
|
|
int
|
|
statement(Block*, size_t*, Statement*);
|
|
};
|
|
|
|
int
|
|
Escape_analysis_loop::statement(Block*, size_t*, Statement* s)
|
|
{
|
|
if (s->label_statement() != NULL)
|
|
s->label_statement()->label()->set_nonlooping();
|
|
else if (s->goto_statement() != NULL)
|
|
{
|
|
if (s->goto_statement()->label()->nonlooping())
|
|
s->goto_statement()->label()->set_looping();
|
|
}
|
|
return TRAVERSE_CONTINUE;
|
|
}
|
|
|
|
// Traversal class used to look at all interesting statements within a function
|
|
// in order to build a connectivity graph between all nodes within a context's
|
|
// scope.
|
|
|
|
class Escape_analysis_assign : public Traverse
|
|
{
|
|
public:
|
|
Escape_analysis_assign(Escape_context* context, Named_object* fn)
|
|
: Traverse(traverse_statements
|
|
| traverse_expressions),
|
|
context_(context), fn_(fn)
|
|
{ }
|
|
|
|
// Model statements within a function as assignments and flows between nodes.
|
|
int
|
|
statement(Block*, size_t*, Statement*);
|
|
|
|
// Model expressions within a function as assignments and flows between nodes.
|
|
int
|
|
expression(Expression**);
|
|
|
|
// Model calls within a function as assignments and flows between arguments
|
|
// and results.
|
|
void
|
|
call(Call_expression* call);
|
|
|
|
// Model the assignment of DST to SRC.
|
|
void
|
|
assign(Node* dst, Node* src);
|
|
|
|
// Model the assignment of DST to dereference of SRC.
|
|
void
|
|
assign_deref(Node* dst, Node* src);
|
|
|
|
// Model the input-to-output assignment flow of one of a function call's
|
|
// arguments, where the flow is encoding in NOTE.
|
|
int
|
|
assign_from_note(std::string* note, const std::vector<Node*>& dsts,
|
|
Node* src);
|
|
|
|
// Record the flow of SRC to DST in DST.
|
|
void
|
|
flows(Node* dst, Node* src);
|
|
|
|
private:
|
|
// The escape context for this set of functions.
|
|
Escape_context* context_;
|
|
// The current function being analyzed.
|
|
Named_object* fn_;
|
|
};
|
|
|
|
// Helper function to detect self assignment like the following.
|
|
//
|
|
// func (b *Buffer) Foo() {
|
|
// n, m := ...
|
|
// b.buf = b.buf[n:m]
|
|
// }
|
|
|
|
static bool
|
|
is_self_assignment(Expression* lhs, Expression* rhs)
|
|
{
|
|
Unary_expression* lue =
|
|
(lhs->field_reference_expression() != NULL
|
|
? lhs->field_reference_expression()->expr()->unary_expression()
|
|
: lhs->unary_expression());
|
|
Var_expression* lve =
|
|
(lue != NULL && lue->op() == OPERATOR_MULT ? lue->operand()->var_expression() : NULL);
|
|
Array_index_expression* raie = rhs->array_index_expression();
|
|
String_index_expression* rsie = rhs->string_index_expression();
|
|
Expression* rarray =
|
|
(raie != NULL && raie->end() != NULL && raie->array()->type()->is_slice_type()
|
|
? raie->array()
|
|
: (rsie != NULL && rsie->type()->is_string_type() ? rsie->string() : NULL));
|
|
Unary_expression* rue =
|
|
(rarray != NULL && rarray->field_reference_expression() != NULL
|
|
? rarray->field_reference_expression()->expr()->unary_expression()
|
|
: (rarray != NULL ? rarray->unary_expression() : NULL));
|
|
Var_expression* rve =
|
|
(rue != NULL && rue->op() == OPERATOR_MULT ? rue->operand()->var_expression() : NULL);
|
|
return lve != NULL && rve != NULL
|
|
&& lve->named_object() == rve->named_object();
|
|
}
|
|
|
|
// Model statements within a function as assignments and flows between nodes.
|
|
|
|
int
|
|
Escape_analysis_assign::statement(Block*, size_t*, Statement* s)
|
|
{
|
|
// Adjust the loop depth as we enter/exit blocks related to for statements.
|
|
bool is_for_statement = (s->is_block_statement()
|
|
&& s->block_statement()->is_lowered_for_statement());
|
|
if (is_for_statement)
|
|
this->context_->increase_loop_depth();
|
|
|
|
s->traverse_contents(this);
|
|
|
|
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();
|
|
go_debug(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:
|
|
{
|
|
Named_object* var = s->variable_declaration_statement()->var();
|
|
Node* var_node = Node::make_node(var);
|
|
Node::Escape_state* state = var_node->state(this->context_, NULL);
|
|
state->loop_depth = this->context_->loop_depth();
|
|
|
|
// Set the loop depth for this declaration.
|
|
if (var->is_variable()
|
|
&& var->var_value()->init() != NULL)
|
|
{
|
|
Node* init_node = Node::make_node(var->var_value()->init());
|
|
this->assign(var_node, init_node);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Statement::STATEMENT_TEMPORARY:
|
|
{
|
|
Expression* init = s->temporary_statement()->init();
|
|
if (init != NULL)
|
|
{
|
|
Node* n = Node::make_node(init);
|
|
if (s->temporary_statement()->value_escapes())
|
|
this->assign(this->context_->sink(), n);
|
|
else
|
|
this->assign(Node::make_node(s), n);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Statement::STATEMENT_LABEL:
|
|
{
|
|
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");
|
|
go_inform(s->location(), "%s %s label",
|
|
label_stmt->label()->name().c_str(),
|
|
label_type.c_str());
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Statement::STATEMENT_SWITCH:
|
|
case Statement::STATEMENT_TYPE_SWITCH:
|
|
// Want to model the assignment of each case variable to the switched upon
|
|
// variable. This should be lowered into assignment statements; nothing
|
|
// to here if that's the case.
|
|
break;
|
|
|
|
case Statement::STATEMENT_ASSIGNMENT:
|
|
{
|
|
Assignment_statement* assn = s->assignment_statement();
|
|
Expression* lhs = assn->lhs();
|
|
Expression* rhs = assn->rhs();
|
|
Node* lhs_node = Node::make_node(lhs);
|
|
Node* rhs_node = Node::make_node(rhs);
|
|
|
|
// Filter out the following special case.
|
|
//
|
|
// func (b *Buffer) Foo() {
|
|
// n, m := ...
|
|
// b.buf = b.buf[n:m]
|
|
// }
|
|
//
|
|
// This assignment is a no-op for escape analysis,
|
|
// it does not store any new pointers into b that were not already there.
|
|
// However, without this special case b will escape.
|
|
if (is_self_assignment(lhs, rhs))
|
|
{
|
|
if (debug_level != 0)
|
|
go_inform(s->location(), "%s ignoring self-assignment to %s",
|
|
strip_packed_prefix(gogo, this->context_->current_function_name()).c_str(),
|
|
lhs_node->ast_format(gogo).c_str());
|
|
break;
|
|
}
|
|
|
|
this->assign(lhs_node, rhs_node);
|
|
}
|
|
break;
|
|
|
|
case Statement::STATEMENT_SEND:
|
|
{
|
|
Node* sent_node = Node::make_node(s->send_statement()->val());
|
|
this->assign(this->context_->sink(), sent_node);
|
|
}
|
|
break;
|
|
|
|
case Statement::STATEMENT_DEFER:
|
|
if (this->context_->loop_depth() == 1)
|
|
{
|
|
// Defer statement may need to allocate a thunk. When it is
|
|
// not inside a loop, this can be stack allocated, as it
|
|
// runs before the function finishes.
|
|
Node* n = Node::make_node(s);
|
|
n->set_encoding(Node::ESCAPE_NONE);
|
|
break;
|
|
}
|
|
// fallthrough
|
|
|
|
case Statement::STATEMENT_GO:
|
|
{
|
|
// Defer f(x) or go f(x).
|
|
// Both f and x escape to the heap.
|
|
Thunk_statement* thunk = s->thunk_statement();
|
|
if (thunk->call()->call_expression() == NULL)
|
|
break;
|
|
|
|
Call_expression* call = thunk->call()->call_expression();
|
|
Node* func_node = Node::make_node(call->fn());
|
|
this->assign(this->context_->sink(), func_node);
|
|
if (call->args() != NULL)
|
|
{
|
|
for (Expression_list::const_iterator p = call->args()->begin();
|
|
p != call->args()->end();
|
|
++p)
|
|
{
|
|
Node* arg_node = Node::make_node(*p);
|
|
this->assign(this->context_->sink(), arg_node);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
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_debug(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.
|
|
|
|
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)
|
|
go_debug((*pexpr)->location(), "%s too large for stack",
|
|
n->ast_format(gogo).c_str());
|
|
move_to_heap(gogo, *pexpr);
|
|
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)
|
|
{
|
|
std::string fn_name = this->context_->current_function_name();
|
|
go_debug((*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:
|
|
{
|
|
Call_expression* call = (*pexpr)->call_expression();
|
|
if (call->is_builtin())
|
|
{
|
|
Builtin_call_expression* bce = call->builtin_call_expression();
|
|
switch (bce->code())
|
|
{
|
|
case Builtin_call_expression::BUILTIN_PANIC:
|
|
{
|
|
// Argument could leak through recover.
|
|
Node* panic_arg = Node::make_node(call->args()->front());
|
|
this->assign(this->context_->sink(), panic_arg);
|
|
}
|
|
break;
|
|
|
|
case Builtin_call_expression::BUILTIN_APPEND:
|
|
{
|
|
// The contents being appended leak.
|
|
if (call->is_varargs())
|
|
{
|
|
// append(slice1, slice2...) -- slice2 itself does not escape, but contents do
|
|
Node* appended = Node::make_node(call->args()->back());
|
|
this->assign_deref(this->context_->sink(), appended);
|
|
if (debug_level > 2)
|
|
go_debug((*pexpr)->location(),
|
|
"special treatment of append(slice1, slice2...)");
|
|
}
|
|
else
|
|
{
|
|
for (Expression_list::const_iterator pa =
|
|
call->args()->begin() + 1;
|
|
pa != call->args()->end();
|
|
++pa)
|
|
{
|
|
Node* arg = Node::make_node(*pa);
|
|
this->assign(this->context_->sink(), arg);
|
|
}
|
|
}
|
|
|
|
// The content of the original slice leaks as well.
|
|
Node* appendee = Node::make_node(call->args()->front());
|
|
this->assign_deref(this->context_->sink(), appendee);
|
|
}
|
|
break;
|
|
|
|
case Builtin_call_expression::BUILTIN_COPY:
|
|
{
|
|
// Lose track of the copied content.
|
|
Node* copied = Node::make_node(call->args()->back());
|
|
this->assign_deref(this->context_->sink(), copied);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
Func_expression* fe = call->fn()->func_expression();
|
|
if (fe != NULL && fe->is_runtime_function())
|
|
{
|
|
switch (fe->runtime_code())
|
|
{
|
|
case Runtime::MAKECHAN:
|
|
case Runtime::MAKECHAN64:
|
|
case Runtime::MAKEMAP:
|
|
case Runtime::MAKESLICE:
|
|
case Runtime::MAKESLICE64:
|
|
this->context_->track(n);
|
|
break;
|
|
|
|
case Runtime::MAPASSIGN:
|
|
{
|
|
// Map key escapes. The last argument is the address
|
|
// of the key.
|
|
Node* key_node = Node::make_node(call->args()->back());
|
|
this->assign_deref(this->context_->sink(), key_node);
|
|
}
|
|
break;
|
|
|
|
case Runtime::MAPASSIGN_FAST32PTR:
|
|
case Runtime::MAPASSIGN_FAST64PTR:
|
|
case Runtime::MAPASSIGN_FASTSTR:
|
|
{
|
|
// Map key escapes. The last argument is the key.
|
|
Node* key_node = Node::make_node(call->args()->back());
|
|
this->assign(this->context_->sink(), key_node);
|
|
}
|
|
break;
|
|
|
|
case Runtime::IFACEE2T2:
|
|
case Runtime::IFACEI2T2:
|
|
{
|
|
// x, ok = v.(T), where T is non-pointer non-interface,
|
|
// is lowered to
|
|
// ok = IFACEI2T2(type, v, (void*)&tmp_x)
|
|
// Here v flows to tmp_x.
|
|
// Note: other IFACEX2Y2 returns the conversion result.
|
|
// Those are handled in ::assign.
|
|
Node* src_node = Node::make_node(call->args()->at(1));
|
|
Node* dst_node;
|
|
Expression* arg2 = call->args()->at(2);
|
|
// Try to pull tmp_x out of the arg2 expression, and let v
|
|
// flows into it, instead of simply dereference arg2,
|
|
// which looks like dereference of an arbitrary pointer
|
|
// and causes v immediately escape.
|
|
// The expression form matches statement.cc,
|
|
// Tuple_type_guard_assignment_statement::lower_to_object_type.
|
|
Unary_expression* ue =
|
|
(arg2->conversion_expression() != NULL
|
|
? arg2->conversion_expression()->expr()->unary_expression()
|
|
: arg2->unary_expression());
|
|
if (ue != NULL && ue->op() == OPERATOR_AND)
|
|
{
|
|
if (!ue->operand()->type()->has_pointer())
|
|
// Don't bother flowing non-pointer.
|
|
break;
|
|
dst_node = Node::make_node(ue->operand());
|
|
}
|
|
else
|
|
dst_node = this->context_->add_dereference(Node::make_node(arg2));
|
|
this->assign(dst_node, src_node);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
this->call(call);
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_ALLOCATION:
|
|
// This is Runtime::NEW.
|
|
this->context_->track(n);
|
|
break;
|
|
|
|
case Expression::EXPRESSION_STRING_CONCAT:
|
|
this->context_->track(n);
|
|
break;
|
|
|
|
case Expression::EXPRESSION_CONVERSION:
|
|
{
|
|
Type_conversion_expression* tce = (*pexpr)->conversion_expression();
|
|
Type* ft = tce->expr()->type();
|
|
Type* tt = tce->type();
|
|
if ((ft->is_string_type() && tt->is_slice_type())
|
|
|| (ft->is_slice_type() && tt->is_string_type())
|
|
|| (ft->integer_type() != NULL && tt->is_string_type()))
|
|
{
|
|
// string([]byte), string([]rune), []byte(string), []rune(string), string(rune)
|
|
this->context_->track(n);
|
|
break;
|
|
}
|
|
Node* tce_node = Node::make_node(tce);
|
|
Node* converted = Node::make_node(tce->expr());
|
|
this->context_->track(tce_node);
|
|
|
|
this->assign(tce_node, converted);
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_UNSAFE_CONVERSION:
|
|
{
|
|
Unsafe_type_conversion_expression* uce =
|
|
(*pexpr)->unsafe_conversion_expression();
|
|
Node* expr_node = Node::make_node(uce->expr());
|
|
this->assign(n, expr_node);
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_FIXED_ARRAY_CONSTRUCTION:
|
|
case Expression::EXPRESSION_SLICE_CONSTRUCTION:
|
|
{
|
|
Node* array_node = Node::make_node(*pexpr);
|
|
if ((*pexpr)->slice_literal() != NULL)
|
|
this->context_->track(array_node);
|
|
|
|
Expression_list* vals = ((*pexpr)->slice_literal() != NULL
|
|
? (*pexpr)->slice_literal()->vals()
|
|
: (*pexpr)->array_literal()->vals());
|
|
|
|
if (vals != NULL)
|
|
{
|
|
// Connect the array to its values.
|
|
for (Expression_list::const_iterator p = vals->begin();
|
|
p != vals->end();
|
|
++p)
|
|
if ((*p) != NULL)
|
|
this->assign(array_node, Node::make_node(*p));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_STRUCT_CONSTRUCTION:
|
|
{
|
|
Node* struct_node = Node::make_node(*pexpr);
|
|
Expression_list* vals = (*pexpr)->struct_literal()->vals();
|
|
if (vals != NULL)
|
|
{
|
|
// Connect the struct to its values.
|
|
for (Expression_list::const_iterator p = vals->begin();
|
|
p != vals->end();
|
|
++p)
|
|
{
|
|
if ((*p) != NULL)
|
|
this->assign(struct_node, Node::make_node(*p));
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_SLICE_VALUE:
|
|
{
|
|
// Connect the pointer field to the slice value.
|
|
Node* slice_node = Node::make_node(*pexpr);
|
|
Node* ptr_node =
|
|
Node::make_node((*pexpr)->slice_value_expression()->valmem());
|
|
this->assign(slice_node, ptr_node);
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_HEAP:
|
|
{
|
|
Node* pointer_node = Node::make_node(*pexpr);
|
|
Node* lit_node = Node::make_node((*pexpr)->heap_expression()->expr());
|
|
this->context_->track(pointer_node);
|
|
|
|
// Connect pointer node to literal node; if the pointer node escapes, so
|
|
// does the literal node.
|
|
this->assign(pointer_node, lit_node);
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_BOUND_METHOD:
|
|
{
|
|
Node* bound_node = Node::make_node(*pexpr);
|
|
this->context_->track(bound_node);
|
|
|
|
Expression* obj = (*pexpr)->bound_method_expression()->first_argument();
|
|
Node* obj_node = Node::make_node(obj);
|
|
|
|
// A bound method implies the receiver will be used outside of the
|
|
// lifetime of the method in some way. We lose track of the receiver.
|
|
this->assign(this->context_->sink(), obj_node);
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_MAP_CONSTRUCTION:
|
|
{
|
|
Map_construction_expression* mce = (*pexpr)->map_literal();
|
|
Node* map_node = Node::make_node(mce);
|
|
this->context_->track(map_node);
|
|
|
|
// All keys and values escape to memory.
|
|
if (mce->vals() != NULL)
|
|
{
|
|
for (Expression_list::const_iterator p = mce->vals()->begin();
|
|
p != mce->vals()->end();
|
|
++p)
|
|
{
|
|
if ((*p) != NULL)
|
|
this->assign(this->context_->sink(), Node::make_node(*p));
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_FUNC_REFERENCE:
|
|
{
|
|
Func_expression* fe = (*pexpr)->func_expression();
|
|
if (fe->closure() != NULL)
|
|
{
|
|
// Connect captured variables to the closure.
|
|
Node* closure_node = Node::make_node(fe);
|
|
this->context_->track(closure_node);
|
|
|
|
// A closure expression already exists as the heap expression:
|
|
// &struct{f func_code, v []*Variable}{...}.
|
|
// Link closure to the addresses of the variables enclosed.
|
|
Heap_expression* he = fe->closure()->heap_expression();
|
|
Struct_construction_expression* sce = he->expr()->struct_literal();
|
|
|
|
// First field is function code, other fields are variable
|
|
// references.
|
|
Expression_list::const_iterator p = sce->vals()->begin();
|
|
++p;
|
|
for (; p != sce->vals()->end(); ++p)
|
|
{
|
|
Node* enclosed_node = Node::make_node(*p);
|
|
this->context_->track(enclosed_node);
|
|
this->assign(closure_node, enclosed_node);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_UNARY:
|
|
{
|
|
Expression* operand = (*pexpr)->unary_expression()->operand();
|
|
|
|
if ((*pexpr)->unary_expression()->op() == OPERATOR_AND)
|
|
{
|
|
this->context_->track(n);
|
|
|
|
Named_object* var = NULL;
|
|
if (operand->var_expression() != NULL)
|
|
var = operand->var_expression()->named_object();
|
|
else if (operand->enclosed_var_expression() != NULL)
|
|
var = operand->enclosed_var_expression()->variable();
|
|
|
|
if (var != NULL
|
|
&& ((var->is_variable() && var->var_value()->is_parameter())
|
|
|| var->is_result_variable()))
|
|
{
|
|
Node::Escape_state* addr_state = n->state(this->context_, NULL);
|
|
addr_state->loop_depth = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((*pexpr)->unary_expression()->op() != OPERATOR_AND
|
|
&& (*pexpr)->unary_expression()->op() != OPERATOR_MULT)
|
|
break;
|
|
|
|
// For &x and *x, use the loop depth of x if known.
|
|
Node::Escape_state* expr_state = n->state(this->context_, NULL);
|
|
Node* operand_node = Node::make_node(operand);
|
|
Node::Escape_state* operand_state = operand_node->state(this->context_, NULL);
|
|
if (operand_state->loop_depth != 0)
|
|
expr_state->loop_depth = operand_state->loop_depth;
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_ARRAY_INDEX:
|
|
{
|
|
Array_index_expression* aie = (*pexpr)->array_index_expression();
|
|
|
|
// Propagate the loopdepth to element.
|
|
Node* array_node = Node::make_node(aie->array());
|
|
Node::Escape_state* elem_state = n->state(this->context_, NULL);
|
|
Node::Escape_state* array_state = array_node->state(this->context_, NULL);
|
|
elem_state->loop_depth = array_state->loop_depth;
|
|
|
|
if (aie->end() != NULL && !aie->array()->type()->is_slice_type())
|
|
{
|
|
// Slicing an array. This effectively takes the address of the array.
|
|
Expression* addr = Expression::make_unary(OPERATOR_AND, aie->array(),
|
|
aie->location());
|
|
Node* addr_node = Node::make_node(addr);
|
|
n->set_child(addr_node);
|
|
this->context_->track(addr_node);
|
|
|
|
Node::Escape_state* addr_state = addr_node->state(this->context_, NULL);
|
|
if (array_state->loop_depth != 0)
|
|
addr_state->loop_depth = array_state->loop_depth;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_FIELD_REFERENCE:
|
|
{
|
|
// Propagate the loopdepth to field.
|
|
Node* struct_node =
|
|
Node::make_node((*pexpr)->field_reference_expression()->expr());
|
|
Node::Escape_state* field_state = n->state(this->context_, NULL);
|
|
Node::Escape_state* struct_state = struct_node->state(this->context_, NULL);
|
|
field_state->loop_depth = struct_state->loop_depth;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return TRAVERSE_SKIP_COMPONENTS;
|
|
}
|
|
|
|
// Model calls within a function as assignments and flows between arguments
|
|
// and results.
|
|
|
|
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;
|
|
|
|
// Interface method calls or closure calls are indirect calls.
|
|
if (fntype == NULL
|
|
|| (fntype->is_method()
|
|
&& fntype->receiver()->type()->interface_type() != NULL)
|
|
|| fn == NULL
|
|
|| (fn->named_object()->is_function()
|
|
&& fn->named_object()->func_value()->enclosing() != NULL))
|
|
indirect = true;
|
|
|
|
Node* call_node = Node::make_node(call);
|
|
std::vector<Node*> arg_nodes;
|
|
if (call->fn()->interface_field_reference_expression() != NULL)
|
|
{
|
|
Interface_field_reference_expression* ifre =
|
|
call->fn()->interface_field_reference_expression();
|
|
Node* field_node = Node::make_node(ifre->expr());
|
|
arg_nodes.push_back(field_node);
|
|
}
|
|
|
|
if (call->args() != NULL)
|
|
{
|
|
for (Expression_list::const_iterator p = call->args()->begin();
|
|
p != call->args()->end();
|
|
++p)
|
|
arg_nodes.push_back(Node::make_node(*p));
|
|
}
|
|
|
|
if (indirect)
|
|
{
|
|
// We don't know what happens to the parameters through indirect calls.
|
|
// Be conservative and assume they all flow to theSink.
|
|
for (std::vector<Node*>::iterator p = arg_nodes.begin();
|
|
p != arg_nodes.end();
|
|
++p)
|
|
{
|
|
if (debug_level > 2)
|
|
go_debug(call->location(),
|
|
"esccall:: indirect call <- %s, untracked",
|
|
(*p)->ast_format(gogo).c_str());
|
|
this->assign(this->context_->sink(), *p);
|
|
}
|
|
|
|
this->context_->init_retvals(call_node, fntype);
|
|
|
|
// It could be a closure call that returns captured variable.
|
|
// Model this by flowing the func expression to result.
|
|
// See issue #14409.
|
|
Node* fn_node = Node::make_node(call->fn());
|
|
std::vector<Node*> retvals = call_node->state(this->context_, NULL)->retvals;
|
|
for (std::vector<Node*>::const_iterator p = retvals.begin();
|
|
p != retvals.end();
|
|
++p)
|
|
this->assign_deref(*p, fn_node);
|
|
|
|
return;
|
|
}
|
|
|
|
// If FN is an untagged function.
|
|
if (fn != NULL
|
|
&& fn->named_object()->is_function()
|
|
&& !fntype->is_tagged())
|
|
{
|
|
if (debug_level > 2)
|
|
go_debug(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();
|
|
Function::Results* results = f->result_variables();
|
|
if (results != NULL)
|
|
{
|
|
// Setup output list on this call node.
|
|
Node::Escape_state* state = call_node->state(this->context_, NULL);
|
|
for (Function::Results::const_iterator p1 = results->begin();
|
|
p1 != results->end();
|
|
++p1)
|
|
{
|
|
Node* result_node = Node::make_node(*p1);
|
|
state->retvals.push_back(result_node);
|
|
}
|
|
}
|
|
|
|
std::vector<Node*>::iterator p = arg_nodes.begin();
|
|
if (fntype->is_method())
|
|
{
|
|
std::string rcvr_name = fntype->receiver()->name();
|
|
if (rcvr_name.empty() || Gogo::is_sink_name(rcvr_name)
|
|
|| !fntype->receiver()->type()->has_pointer())
|
|
;
|
|
else
|
|
{
|
|
Named_object* rcvr_no =
|
|
callee_bindings->lookup_local(fntype->receiver()->name());
|
|
go_assert(rcvr_no != NULL);
|
|
Node* rcvr_node = Node::make_node(rcvr_no);
|
|
if (fntype->receiver()->type()->points_to() == NULL
|
|
&& (*p)->expr()->type()->points_to() != NULL)
|
|
// This is a call to a value method that has been lowered into a call
|
|
// to a pointer method. Gccgo generates a pointer method for all
|
|
// method calls and takes the address of the value passed as the
|
|
// receiver then immediately dereferences it within the function.
|
|
// In this case, the receiver address does not escape; its content
|
|
// flows to the call.
|
|
this->assign_deref(rcvr_node, *p);
|
|
else
|
|
this->assign(rcvr_node, *p);
|
|
}
|
|
++p;
|
|
}
|
|
|
|
const Typed_identifier_list* til = fntype->parameters();
|
|
if (til != NULL)
|
|
{
|
|
for (Typed_identifier_list::const_iterator p1 = til->begin();
|
|
p1 != til->end();
|
|
++p1, ++p)
|
|
{
|
|
if (p1->name().empty() || Gogo::is_sink_name(p1->name()))
|
|
continue;
|
|
|
|
Named_object* param_no =
|
|
callee_bindings->lookup_local(p1->name());
|
|
go_assert(param_no != NULL);
|
|
Expression* arg = (*p)->expr();
|
|
if (arg->var_expression() != NULL
|
|
&& arg->var_expression()->named_object() == param_no)
|
|
continue;
|
|
|
|
Node* param_node = Node::make_node(param_no);
|
|
this->assign(param_node, *p);
|
|
}
|
|
|
|
for (; p != arg_nodes.end(); ++p)
|
|
{
|
|
if (debug_level > 2)
|
|
go_debug(call->location(), "esccall:: ... <- %s, untracked",
|
|
(*p)->ast_format(gogo).c_str());
|
|
this->assign(this->context_->sink(), *p);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (debug_level > 2)
|
|
go_debug(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())
|
|
go_error_at(Linemap::unknown_location(),
|
|
"esc already decorated call %s",
|
|
call_node->ast_format(gogo).c_str());
|
|
this->context_->init_retvals(call_node, fntype);
|
|
|
|
// Receiver.
|
|
std::vector<Node*>::iterator p = arg_nodes.begin();
|
|
if (fntype->is_method()
|
|
&& p != arg_nodes.end())
|
|
{
|
|
// First argument to call will be the receiver.
|
|
std::string* note = fntype->receiver()->note();
|
|
if (fntype->receiver()->type()->points_to() == NULL
|
|
&& (*p)->expr()->type()->points_to() != NULL)
|
|
// This is a call to a value method that has been lowered into a call
|
|
// to a pointer method. Gccgo generates a pointer method for all
|
|
// method calls and takes the address of the value passed as the
|
|
// receiver then immediately dereferences it within the function.
|
|
// In this case, the receiver address does not escape; its content
|
|
// flows to the call.
|
|
this->assign_from_note(note, call_state->retvals,
|
|
this->context_->add_dereference(*p));
|
|
else
|
|
{
|
|
if (!Type::are_identical(fntype->receiver()->type(),
|
|
(*p)->expr()->type(), Type::COMPARE_TAGS,
|
|
NULL))
|
|
{
|
|
// This will be converted later, preemptively track it instead
|
|
// of its conversion expression which will show up in a later pass.
|
|
this->context_->track(*p);
|
|
}
|
|
this->assign_from_note(note, call_state->retvals, *p);
|
|
}
|
|
p++;
|
|
}
|
|
|
|
const Typed_identifier_list* til = fntype->parameters();
|
|
if (til != NULL)
|
|
{
|
|
for (Typed_identifier_list::const_iterator pn = til->begin();
|
|
pn != til->end() && p != arg_nodes.end();
|
|
++pn, ++p)
|
|
{
|
|
if (!Type::are_identical(pn->type(), (*p)->expr()->type(),
|
|
Type::COMPARE_TAGS, NULL))
|
|
{
|
|
// This will be converted later, preemptively track it instead
|
|
// of its conversion expression which will show up in a later pass.
|
|
this->context_->track(*p);
|
|
}
|
|
|
|
Type* t = pn->type();
|
|
if (t != NULL
|
|
&& t->has_pointer())
|
|
{
|
|
std::string* note = pn->note();
|
|
int enc = this->assign_from_note(note, call_state->retvals, *p);
|
|
if (enc == Node::ESCAPE_NONE
|
|
&& !call->is_deferred()
|
|
&& !call->is_concurrent())
|
|
{
|
|
// TODO(cmang): Mark the argument as strictly non-escaping?
|
|
// In the gc compiler this is for limiting the lifetime of
|
|
// temporaries. We probably don't need this?
|
|
}
|
|
}
|
|
}
|
|
|
|
for (; p != arg_nodes.end(); ++p)
|
|
{
|
|
if (debug_level > 2)
|
|
go_debug(call->location(), "esccall:: ... <- %s, untracked",
|
|
(*p)->ast_format(gogo).c_str());
|
|
this->assign(this->context_->sink(), *p);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Model the assignment of DST to SRC.
|
|
// Assert that SRC somehow gets assigned to DST.
|
|
// DST might need to be examined for evaluations that happen inside of it.
|
|
// For example, in [DST]*f(x) = [SRC]y, we lose track of the indirection and
|
|
// DST becomes the sink in our model.
|
|
|
|
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)
|
|
go_debug(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->is_indirect())
|
|
// Lose track of the dereference.
|
|
dst = this->context_->sink();
|
|
else if (dst->expr() != NULL)
|
|
{
|
|
// Analyze the lhs of the assignment.
|
|
// Replace DST with this->context_->sink() if we can't track it.
|
|
Expression* e = dst->expr();
|
|
switch (e->classification())
|
|
{
|
|
case Expression::EXPRESSION_VAR_REFERENCE:
|
|
{
|
|
// If DST is a global variable, we have no way to track it.
|
|
Named_object* var = e->var_expression()->named_object();
|
|
if (var->is_variable() && var->var_value()->is_global())
|
|
dst = this->context_->sink();
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_FIELD_REFERENCE:
|
|
{
|
|
Expression* strct = e->field_reference_expression()->expr();
|
|
if (strct->heap_expression() != NULL)
|
|
{
|
|
// When accessing the field of a struct reference, we lose
|
|
// track of the indirection.
|
|
dst = this->context_->sink();
|
|
break;
|
|
}
|
|
|
|
// Treat DST.x = SRC as if it were DST = SRC.
|
|
Node* struct_node = Node::make_node(strct);
|
|
this->assign(struct_node, src);
|
|
return;
|
|
}
|
|
|
|
case Expression::EXPRESSION_ARRAY_INDEX:
|
|
{
|
|
Array_index_expression* are = e->array_index_expression();
|
|
if (!are->array()->type()->is_slice_type())
|
|
{
|
|
// Treat DST[i] = SRC as if it were DST = SRC if DST if a fixed
|
|
// array.
|
|
Node* array_node = Node::make_node(are->array());
|
|
this->assign(array_node, src);
|
|
return;
|
|
}
|
|
|
|
// Lose track of the slice dereference.
|
|
dst = this->context_->sink();
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_UNARY:
|
|
// Lose track of the dereference.
|
|
if (e->unary_expression()->op() == OPERATOR_MULT)
|
|
dst = this->context_->sink();
|
|
break;
|
|
|
|
case Expression::EXPRESSION_MAP_INDEX:
|
|
{
|
|
// Lose track of the map's key and value.
|
|
Expression* index = e->map_index_expression()->index();
|
|
Node* index_node = Node::make_node(index);
|
|
this->assign(this->context_->sink(), index_node);
|
|
|
|
dst = this->context_->sink();
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_TEMPORARY_REFERENCE:
|
|
{
|
|
// Temporary is tracked through the underlying Temporary_statement.
|
|
Temporary_statement* t =
|
|
dst->expr()->temporary_reference_expression()->statement();
|
|
if (t->value_escapes())
|
|
dst = this->context_->sink();
|
|
else
|
|
dst = Node::make_node(t);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// TODO(cmang): Add debugging info here: only a few expressions
|
|
// should leave DST unmodified.
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (src->object() != NULL)
|
|
this->flows(dst, src);
|
|
else if (src->is_indirect())
|
|
this->flows(dst, src);
|
|
else if (src->expr() != NULL)
|
|
{
|
|
Expression* e = src->expr();
|
|
switch (e->classification())
|
|
{
|
|
case Expression::EXPRESSION_VAR_REFERENCE:
|
|
case Expression::EXPRESSION_ENCLOSED_VAR_REFERENCE:
|
|
// DST = var
|
|
case Expression::EXPRESSION_HEAP:
|
|
// DST = &T{...}.
|
|
case Expression::EXPRESSION_FIXED_ARRAY_CONSTRUCTION:
|
|
case Expression::EXPRESSION_SLICE_CONSTRUCTION:
|
|
// DST = [...]T{...}.
|
|
case Expression::EXPRESSION_MAP_CONSTRUCTION:
|
|
// DST = map[T]V{...}.
|
|
case Expression::EXPRESSION_STRUCT_CONSTRUCTION:
|
|
// DST = T{...}.
|
|
case Expression::EXPRESSION_SLICE_VALUE:
|
|
// DST = slice{ptr, len, cap}
|
|
case Expression::EXPRESSION_ALLOCATION:
|
|
// DST = new(T).
|
|
case Expression::EXPRESSION_BOUND_METHOD:
|
|
// DST = x.M.
|
|
case Expression::EXPRESSION_STRING_CONCAT:
|
|
// DST = str1 + str2
|
|
this->flows(dst, src);
|
|
break;
|
|
|
|
case Expression::EXPRESSION_UNSAFE_CONVERSION:
|
|
{
|
|
Expression* underlying = e->unsafe_conversion_expression()->expr();
|
|
Node* underlying_node = Node::make_node(underlying);
|
|
this->assign(dst, underlying_node);
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_CALL:
|
|
{
|
|
Call_expression* call = e->call_expression();
|
|
if (call->is_builtin())
|
|
{
|
|
Builtin_call_expression* bce = call->builtin_call_expression();
|
|
if (bce->code() == Builtin_call_expression::BUILTIN_APPEND)
|
|
{
|
|
// Append returns the first argument.
|
|
// The subsequent arguments are already leaked because
|
|
// they are operands to append.
|
|
Node* appendee = Node::make_node(call->args()->front());
|
|
this->assign(dst, appendee);
|
|
}
|
|
break;
|
|
}
|
|
Func_expression* fe = call->fn()->func_expression();
|
|
if (fe != NULL && fe->is_runtime_function())
|
|
{
|
|
switch (fe->runtime_code())
|
|
{
|
|
case Runtime::MAKECHAN:
|
|
case Runtime::MAKECHAN64:
|
|
case Runtime::MAKEMAP:
|
|
case Runtime::MAKESLICE:
|
|
case Runtime::MAKESLICE64:
|
|
// DST = make(...).
|
|
this->flows(dst, src);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
else if (fe != NULL
|
|
&& fe->named_object()->is_function()
|
|
&& fe->named_object()->func_value()->is_method()
|
|
&& (call->is_deferred()
|
|
|| call->is_concurrent()))
|
|
{
|
|
// For a method call thunk, lose track of the call and treat it
|
|
// as if DST = RECEIVER.
|
|
Node* rcvr_node = Node::make_node(call->args()->front());
|
|
this->assign(dst, rcvr_node);
|
|
break;
|
|
}
|
|
|
|
// Result flows to dst.
|
|
Node* call_node = Node::make_node(e);
|
|
Node::Escape_state* call_state = call_node->state(this->context_, NULL);
|
|
std::vector<Node*> retvals = call_state->retvals;
|
|
for (std::vector<Node*>::const_iterator p = retvals.begin();
|
|
p != retvals.end();
|
|
++p)
|
|
this->flows(dst, *p);
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_CALL_RESULT:
|
|
{
|
|
Call_result_expression* cre = e->call_result_expression();
|
|
Call_expression* call = cre->call()->call_expression();
|
|
if (call->is_builtin())
|
|
break;
|
|
if (call->fn()->func_expression() != NULL
|
|
&& call->fn()->func_expression()->is_runtime_function())
|
|
{
|
|
switch (call->fn()->func_expression()->runtime_code())
|
|
{
|
|
case Runtime::IFACEE2E2:
|
|
case Runtime::IFACEI2E2:
|
|
case Runtime::IFACEE2I2:
|
|
case Runtime::IFACEI2I2:
|
|
case Runtime::IFACEE2T2P:
|
|
case Runtime::IFACEI2T2P:
|
|
{
|
|
// x, ok = v.(T), where T is a pointer or interface,
|
|
// is lowered to
|
|
// x, ok = IFACEI2E2(v), or
|
|
// x, ok = IFACEI2I2(type, v)
|
|
// The last arg flows to the first result.
|
|
// Note: IFACEX2T2 has different signature, handled by
|
|
// ::expression.
|
|
if (cre->index() != 0)
|
|
break;
|
|
Node* arg_node = Node::make_node(call->args()->back());
|
|
this->assign(dst, arg_node);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
Node* call_node = Node::make_node(call);
|
|
Node* ret_node = call_node->state(context_, NULL)->retvals[cre->index()];
|
|
this->assign(dst, ret_node);
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_FUNC_REFERENCE:
|
|
if (e->func_expression()->closure() != NULL)
|
|
this->flows(dst, src);
|
|
break;
|
|
|
|
case Expression::EXPRESSION_CONVERSION:
|
|
{
|
|
Type_conversion_expression* tce = e->conversion_expression();
|
|
Type* ft = tce->expr()->type();
|
|
Type* tt = tce->type();
|
|
if ((ft->is_string_type() && tt->is_slice_type())
|
|
|| (ft->is_slice_type() && tt->is_string_type())
|
|
|| (ft->integer_type() != NULL && tt->is_string_type())
|
|
|| tt->interface_type() != NULL)
|
|
{
|
|
// string([]byte), string([]rune), []byte(string), []rune(string), string(rune),
|
|
// interface(T)
|
|
this->flows(dst, src);
|
|
break;
|
|
}
|
|
// Conversion preserves input value.
|
|
Expression* underlying = tce->expr();
|
|
this->assign(dst, Node::make_node(underlying));
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_FIELD_REFERENCE:
|
|
{
|
|
// A non-pointer can't escape from a struct.
|
|
if (!e->type()->has_pointer())
|
|
break;
|
|
}
|
|
// Fall through.
|
|
|
|
case Expression::EXPRESSION_TYPE_GUARD:
|
|
case Expression::EXPRESSION_ARRAY_INDEX:
|
|
case Expression::EXPRESSION_STRING_INDEX:
|
|
{
|
|
Expression* left = NULL;
|
|
if (e->field_reference_expression() != NULL)
|
|
{
|
|
left = e->field_reference_expression()->expr();
|
|
if (left->unary_expression() != NULL
|
|
&& left->unary_expression()->op() == OPERATOR_MULT)
|
|
{
|
|
// DST = (*x).f
|
|
this->flows(dst, src);
|
|
break;
|
|
}
|
|
}
|
|
else if (e->type_guard_expression() != NULL)
|
|
left = e->type_guard_expression()->expr();
|
|
else if (e->array_index_expression() != NULL)
|
|
{
|
|
Array_index_expression* aie = e->array_index_expression();
|
|
if (aie->end() != NULL)
|
|
// slicing
|
|
if (aie->array()->type()->is_slice_type())
|
|
left = aie->array();
|
|
else
|
|
{
|
|
// slicing an array
|
|
// The gc compiler has an implicit address operator.
|
|
go_assert(src->child() != NULL);
|
|
this->assign(dst, src->child());
|
|
break;
|
|
}
|
|
else if (!aie->array()->type()->is_slice_type())
|
|
{
|
|
// Indexing an array preserves the input value.
|
|
Node* array_node = Node::make_node(aie->array());
|
|
this->assign(dst, array_node);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
this->flows(dst, src);
|
|
break;
|
|
}
|
|
}
|
|
else if (e->string_index_expression() != NULL)
|
|
{
|
|
String_index_expression* sie = e->string_index_expression();
|
|
if (e->type()->is_string_type())
|
|
// slicing
|
|
left = sie->string();
|
|
else
|
|
{
|
|
this->flows(dst, src);
|
|
break;
|
|
}
|
|
}
|
|
go_assert(left != NULL);
|
|
|
|
// Conversions, field access, and slicing all preserve the input
|
|
// value.
|
|
Node* left_node = Node::make_node(left);
|
|
this->assign(dst, left_node);
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_BINARY:
|
|
{
|
|
switch (e->binary_expression()->op())
|
|
{
|
|
case OPERATOR_PLUS:
|
|
case OPERATOR_MINUS:
|
|
case OPERATOR_XOR:
|
|
case OPERATOR_OR:
|
|
case OPERATOR_MULT:
|
|
case OPERATOR_DIV:
|
|
case OPERATOR_MOD:
|
|
case OPERATOR_LSHIFT:
|
|
case OPERATOR_RSHIFT:
|
|
case OPERATOR_AND:
|
|
case OPERATOR_BITCLEAR:
|
|
{
|
|
Node* left = Node::make_node(e->binary_expression()->left());
|
|
this->assign(dst, left);
|
|
Node* right = Node::make_node(e->binary_expression()->right());
|
|
this->assign(dst, right);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_UNARY:
|
|
{
|
|
switch (e->unary_expression()->op())
|
|
{
|
|
case OPERATOR_PLUS:
|
|
case OPERATOR_MINUS:
|
|
case OPERATOR_XOR:
|
|
{
|
|
Node* op_node =
|
|
Node::make_node(e->unary_expression()->operand());
|
|
this->assign(dst, op_node);
|
|
}
|
|
break;
|
|
|
|
case OPERATOR_MULT:
|
|
// DST = *x
|
|
case OPERATOR_AND:
|
|
// DST = &x
|
|
this->flows(dst, src);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Expression::EXPRESSION_TEMPORARY_REFERENCE:
|
|
{
|
|
Statement* temp = e->temporary_reference_expression()->statement();
|
|
this->assign(dst, Node::make_node(temp));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// TODO(cmang): Add debug info here; this should not be reachable.
|
|
// For now, just to be conservative, we'll just say dst flows to src.
|
|
break;
|
|
}
|
|
}
|
|
else if (src->statement() != NULL && src->statement()->temporary_statement() != NULL)
|
|
this->flows(dst, src);
|
|
}
|
|
|
|
// Model the assignment of DST to an indirection of SRC.
|
|
|
|
void
|
|
Escape_analysis_assign::assign_deref(Node* dst, Node* src)
|
|
{
|
|
if (src->expr() != NULL)
|
|
{
|
|
switch (src->expr()->classification())
|
|
{
|
|
case Expression::EXPRESSION_BOOLEAN:
|
|
case Expression::EXPRESSION_STRING:
|
|
case Expression::EXPRESSION_INTEGER:
|
|
case Expression::EXPRESSION_FLOAT:
|
|
case Expression::EXPRESSION_COMPLEX:
|
|
case Expression::EXPRESSION_NIL:
|
|
case Expression::EXPRESSION_IOTA:
|
|
// No need to try indirections on literal values
|
|
// or numeric constants.
|
|
return;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
this->assign(dst, this->context_->add_dereference(src));
|
|
}
|
|
|
|
// Model the input-to-output assignment flow of one of a function call's
|
|
// arguments, where the flow is encoded in NOTE.
|
|
|
|
int
|
|
Escape_analysis_assign::assign_from_note(std::string* note,
|
|
const std::vector<Node*>& dsts,
|
|
Node* src)
|
|
{
|
|
int enc = Escape_note::parse_tag(note);
|
|
if (src->expr() != NULL)
|
|
{
|
|
switch (src->expr()->classification())
|
|
{
|
|
case Expression::EXPRESSION_BOOLEAN:
|
|
case Expression::EXPRESSION_STRING:
|
|
case Expression::EXPRESSION_INTEGER:
|
|
case Expression::EXPRESSION_FLOAT:
|
|
case Expression::EXPRESSION_COMPLEX:
|
|
case Expression::EXPRESSION_NIL:
|
|
case Expression::EXPRESSION_IOTA:
|
|
// There probably isn't a note for a literal value. Literal values
|
|
// usually don't escape unless we lost track of the value some how.
|
|
return enc;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (this->context_->gogo()->debug_escape_level() > 2)
|
|
go_debug(src->location(), "assignfromtag:: src=%s em=%s",
|
|
src->ast_format(context_->gogo()).c_str(),
|
|
Escape_note::make_tag(enc).c_str());
|
|
|
|
if (enc == Node::ESCAPE_UNKNOWN)
|
|
{
|
|
// Lost track of the value.
|
|
this->assign(this->context_->sink(), src);
|
|
return enc;
|
|
}
|
|
else if (enc == Node::ESCAPE_NONE)
|
|
return enc;
|
|
|
|
// If the content inside a parameter (reached via indirection) escapes to
|
|
// the heap, mark it.
|
|
if ((enc & ESCAPE_CONTENT_ESCAPES) != 0)
|
|
this->assign(this->context_->sink(), this->context_->add_dereference(src));
|
|
|
|
int save_enc = enc;
|
|
enc >>= ESCAPE_RETURN_BITS;
|
|
for (std::vector<Node*>::const_iterator p = dsts.begin();
|
|
enc != 0 && p != dsts.end();
|
|
++p)
|
|
{
|
|
// Prefer the lowest-level path to the reference (for escape purposes).
|
|
// Two-bit encoding (for example. 1, 3, and 4 bits are other options)
|
|
// 01 = 0-level
|
|
// 10 = 1-level, (content escapes),
|
|
// 11 = 2-level, (content of content escapes).
|
|
int bits = enc & ESCAPE_BITS_MASK_FOR_TAG;
|
|
if (bits > 0)
|
|
{
|
|
Node* n = src;
|
|
for (int i = 0; i < bits - 1; ++i)
|
|
{
|
|
// Encode level > 0 as indirections.
|
|
n = this->context_->add_dereference(n);
|
|
}
|
|
this->assign(*p, n);
|
|
}
|
|
enc >>= ESCAPE_BITS_PER_OUTPUT_IN_TAG;
|
|
}
|
|
|
|
// If there are too many outputs to fit in the tag, that is handled on the
|
|
// encoding end as ESCAPE_HEAP, so there is no need to check here.
|
|
return save_enc;
|
|
}
|
|
|
|
// Record the flow of SRC to DST in DST.
|
|
|
|
void
|
|
Escape_analysis_assign::flows(Node* dst, Node* src)
|
|
{
|
|
// Don't bother capturing the flow from scalars.
|
|
if (src->type() != NULL && !src->type()->has_pointer())
|
|
return;
|
|
|
|
// Don't confuse a blank identifier with the sink.
|
|
if (dst->is_sink() && dst != this->context_->sink())
|
|
return;
|
|
|
|
Node::Escape_state* dst_state = dst->state(this->context_, NULL);
|
|
Node::Escape_state* src_state = src->state(this->context_, NULL);
|
|
if (dst == src
|
|
|| dst_state == src_state
|
|
|| dst_state->flows.find(src) != dst_state->flows.end())
|
|
return;
|
|
|
|
Gogo* gogo = this->context_->gogo();
|
|
if (gogo->debug_escape_level() > 2)
|
|
go_debug(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);
|
|
|
|
dst_state->flows.insert(src);
|
|
}
|
|
|
|
// Build a connectivity graph between nodes in the function being analyzed.
|
|
|
|
void
|
|
Gogo::assign_connectivity(Escape_context* context, Named_object* fn)
|
|
{
|
|
// Must be defined outside of this package.
|
|
if (!fn->is_function())
|
|
return;
|
|
|
|
int save_depth = context->loop_depth();
|
|
context->set_loop_depth(1);
|
|
|
|
Escape_analysis_assign ea(context, fn);
|
|
Function::Results* res = fn->func_value()->result_variables();
|
|
if (res != NULL)
|
|
{
|
|
for (Function::Results::const_iterator p = res->begin();
|
|
p != res->end();
|
|
++p)
|
|
{
|
|
Node* res_node = Node::make_node(*p);
|
|
Node::Escape_state* res_state = res_node->state(context, fn);
|
|
res_state->fn = fn;
|
|
res_state->loop_depth = 0;
|
|
|
|
// If this set of functions is recursive, we lose track of the return values.
|
|
// Just say that the result flows to the sink.
|
|
if (context->recursive())
|
|
ea.flows(context->sink(), res_node);
|
|
}
|
|
}
|
|
|
|
const Bindings* callee_bindings = fn->func_value()->block()->bindings();
|
|
Function_type* fntype = fn->func_value()->type();
|
|
Typed_identifier_list* params = (fntype->parameters() != NULL
|
|
? fntype->parameters()->copy()
|
|
: new Typed_identifier_list);
|
|
if (fntype->receiver() != NULL)
|
|
params->push_back(*fntype->receiver());
|
|
|
|
for (Typed_identifier_list::const_iterator p = params->begin();
|
|
p != params->end();
|
|
++p)
|
|
{
|
|
if (p->name().empty() || Gogo::is_sink_name(p->name()))
|
|
continue;
|
|
|
|
Named_object* param_no = callee_bindings->lookup_local(p->name());
|
|
go_assert(param_no != NULL);
|
|
Node* param_node = Node::make_node(param_no);
|
|
Node::Escape_state* param_state = param_node->state(context, fn);
|
|
param_state->fn = fn;
|
|
param_state->loop_depth = 1;
|
|
|
|
if (!p->type()->has_pointer())
|
|
continue;
|
|
|
|
param_node->set_encoding(Node::ESCAPE_NONE);
|
|
context->track(param_node);
|
|
}
|
|
|
|
Escape_analysis_loop el;
|
|
fn->func_value()->traverse(&el);
|
|
|
|
fn->func_value()->traverse(&ea);
|
|
context->set_loop_depth(save_depth);
|
|
}
|
|
|
|
class Escape_analysis_flood
|
|
{
|
|
public:
|
|
Escape_analysis_flood(Escape_context* context)
|
|
: context_(context)
|
|
{ }
|
|
|
|
// Use the escape information in dst to update the escape information in src
|
|
// and src's upstream.
|
|
void
|
|
flood(Level, Node* dst, Node* src, int);
|
|
|
|
private:
|
|
// The escape context for the group of functions being flooded.
|
|
Escape_context* context_;
|
|
};
|
|
|
|
// Whenever we hit a dereference node, the level goes up by one, and whenever
|
|
// we hit an address-of, the level goes down by one. as long as we're on a
|
|
// level > 0 finding an address-of just means we're following the upstream
|
|
// of a dereference, so this address doesn't leak (yet).
|
|
// If level == 0, it means the /value/ of this node can reach the root of this
|
|
// flood so if this node is an address-of, its argument should be marked as
|
|
// escaping iff its current function and loop depth are different from the
|
|
// flood's root.
|
|
// Once an object has been moved to the heap, all of its upstream should be
|
|
// considered escaping to the global scope.
|
|
// This is an implementation of gc/esc.go:escwalkBody.
|
|
|
|
void
|
|
Escape_analysis_flood::flood(Level level, Node* dst, Node* src,
|
|
int extra_loop_depth)
|
|
{
|
|
// No need to flood src if it is a literal.
|
|
if (src->expr() != NULL)
|
|
{
|
|
switch (src->expr()->classification())
|
|
{
|
|
case Expression::EXPRESSION_BOOLEAN:
|
|
case Expression::EXPRESSION_STRING:
|
|
case Expression::EXPRESSION_INTEGER:
|
|
case Expression::EXPRESSION_FLOAT:
|
|
case Expression::EXPRESSION_COMPLEX:
|
|
case Expression::EXPRESSION_NIL:
|
|
case Expression::EXPRESSION_IOTA:
|
|
return;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
Node::Escape_state* src_state = src->state(this->context_, NULL);
|
|
if (src_state->flood_id == this->context_->flood_id())
|
|
{
|
|
// Esclevels are vectors, do not compare as integers,
|
|
// and must use "min" of old and new to guarantee
|
|
// convergence.
|
|
level = level.min(src_state->level);
|
|
if (level == src_state->level)
|
|
{
|
|
// Have we been here already with an extraloopdepth,
|
|
// or is the extraloopdepth provided no improvement on
|
|
// what's already been seen?
|
|
if (src_state->max_extra_loop_depth >= extra_loop_depth
|
|
|| src_state->loop_depth >= extra_loop_depth)
|
|
return;
|
|
src_state->max_extra_loop_depth = extra_loop_depth;
|
|
}
|
|
}
|
|
else
|
|
src_state->max_extra_loop_depth = -1;
|
|
|
|
src_state->flood_id = this->context_->flood_id();
|
|
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)
|
|
go_debug(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?
|
|
Named_object* src_no = NULL;
|
|
if (src->expr() != NULL && src->expr()->var_expression() != NULL)
|
|
src_no = src->expr()->var_expression()->named_object();
|
|
else
|
|
src_no = src->object();
|
|
bool src_is_param = (src_no != NULL
|
|
&& src_no->is_variable()
|
|
&& src_no->var_value()->is_parameter());
|
|
|
|
Named_object* dst_no = NULL;
|
|
if (dst->expr() != NULL && dst->expr()->var_expression() != NULL)
|
|
dst_no = dst->expr()->var_expression()->named_object();
|
|
else
|
|
dst_no = dst->object();
|
|
bool dst_is_result = dst_no != NULL && dst_no->is_result_variable();
|
|
Node::Escape_state* dst_state = dst->state(this->context_, NULL);
|
|
|
|
if (src_is_param
|
|
&& dst_is_result
|
|
&& src_state->fn == dst_state->fn
|
|
&& (src->encoding() & ESCAPE_MASK) < int(Node::ESCAPE_HEAP)
|
|
&& dst->encoding() != Node::ESCAPE_HEAP)
|
|
{
|
|
// This case handles:
|
|
// 1. return in
|
|
// 2. return &in
|
|
// 3. tmp := in; return &tmp
|
|
// 4. return *in
|
|
if (debug_level != 0)
|
|
{
|
|
if (debug_level == 1)
|
|
go_debug(src->definition_location(),
|
|
"leaking param: %s to result %s level=%d",
|
|
src->ast_format(gogo).c_str(),
|
|
dst->ast_format(gogo).c_str(),
|
|
level.value());
|
|
else
|
|
go_debug(src->definition_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::ESCAPE_RETURN | (src->encoding() & ESCAPE_CONTENT_ESCAPES);
|
|
src->set_encoding(enc);
|
|
}
|
|
|
|
int enc = Node::note_inout_flows(src->encoding(),
|
|
dst_no->result_var_value()->index(),
|
|
level);
|
|
src->set_encoding(enc);
|
|
|
|
// In gc/esc.go:escwalkBody, this is a goto to the label for recursively
|
|
// flooding the connection graph. Inlined here for convenience.
|
|
level = level.copy();
|
|
for (std::set<Node*>::const_iterator p = src_state->flows.begin();
|
|
p != src_state->flows.end();
|
|
++p)
|
|
this->flood(level, dst, *p, extra_loop_depth);
|
|
return;
|
|
}
|
|
|
|
// If parameter content escape to heap, set ESCAPE_CONTENT_ESCAPES.
|
|
// Note minor confusion around escape from pointer-to-struct vs
|
|
// escape from struct.
|
|
if (src_is_param
|
|
&& dst->encoding() == Node::ESCAPE_HEAP
|
|
&& (src->encoding() & ESCAPE_MASK) < int(Node::ESCAPE_HEAP)
|
|
&& level.value() > 0)
|
|
{
|
|
int enc =
|
|
Node::max_encoding((src->encoding() | ESCAPE_CONTENT_ESCAPES),
|
|
Node::ESCAPE_NONE);
|
|
src->set_encoding(enc);
|
|
if (debug_level != 0)
|
|
go_debug(src->definition_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
|
|
// in a different scope (at a different loop depth).
|
|
bool src_leaks = (level.value() <= 0
|
|
&& level.suffix_value() <= 0
|
|
&& dst_state->loop_depth < mod_loop_depth);
|
|
src_leaks = src_leaks || (level.value() <= 0
|
|
&& (dst->encoding() & ESCAPE_MASK) == Node::ESCAPE_HEAP);
|
|
// old src encoding, used to prevent duplicate error messages
|
|
int osrcesc = src->encoding();
|
|
|
|
if (src_is_param
|
|
&& (src_leaks || dst_state->loop_depth < 0)
|
|
&& (src->encoding() & ESCAPE_MASK) < int(Node::ESCAPE_HEAP))
|
|
{
|
|
if (level.suffix_value() > 0)
|
|
{
|
|
int enc =
|
|
Node::max_encoding((src->encoding() | ESCAPE_CONTENT_ESCAPES),
|
|
Node::ESCAPE_NONE);
|
|
src->set_encoding(enc);
|
|
if (debug_level != 0 && osrcesc != src->encoding())
|
|
go_debug(src->definition_location(), "leaking param content: %s",
|
|
src->ast_format(gogo).c_str());
|
|
}
|
|
else
|
|
{
|
|
if (debug_level != 0)
|
|
go_debug(src->definition_location(), "leaking param: %s",
|
|
src->ast_format(gogo).c_str());
|
|
src->set_encoding(Node::ESCAPE_HEAP);
|
|
}
|
|
}
|
|
else if (src->expr() != NULL)
|
|
{
|
|
Expression* e = src->expr();
|
|
if (e->enclosed_var_expression() != NULL)
|
|
{
|
|
if (src_leaks && debug_level != 0)
|
|
go_debug(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);
|
|
}
|
|
else if (e->heap_expression() != NULL
|
|
|| (e->unary_expression() != NULL
|
|
&& e->unary_expression()->op() == OPERATOR_AND))
|
|
{
|
|
// Pointer literals and address-of expressions.
|
|
Expression* underlying;
|
|
if (e->heap_expression())
|
|
underlying = e->heap_expression()->expr();
|
|
else
|
|
underlying = e->unary_expression()->operand();
|
|
Node* underlying_node = Node::make_node(underlying);
|
|
|
|
// If the address leaks, the underyling object must be moved
|
|
// to the heap.
|
|
underlying->address_taken(src_leaks);
|
|
if (src_leaks)
|
|
{
|
|
src->set_encoding(Node::ESCAPE_HEAP);
|
|
if (osrcesc != src->encoding())
|
|
{
|
|
move_to_heap(gogo, underlying);
|
|
if (debug_level > 1)
|
|
go_debug(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 if (debug_level > 0)
|
|
go_debug(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;
|
|
}
|
|
else
|
|
{
|
|
// Decrease the level each time we take the address of the object.
|
|
this->flood(level.decrease(), dst, underlying_node, -1);
|
|
}
|
|
}
|
|
else if (e->slice_literal() != NULL)
|
|
{
|
|
Slice_construction_expression* slice = e->slice_literal();
|
|
if (slice->vals() != NULL)
|
|
{
|
|
for (Expression_list::const_iterator p = slice->vals()->begin();
|
|
p != slice->vals()->end();
|
|
++p)
|
|
{
|
|
if ((*p) != NULL)
|
|
this->flood(level.decrease(), dst, Node::make_node(*p), -1);
|
|
}
|
|
}
|
|
if (src_leaks)
|
|
{
|
|
src->set_encoding(Node::ESCAPE_HEAP);
|
|
if (debug_level != 0 && osrcesc != src->encoding())
|
|
go_debug(src->location(), "%s escapes to heap",
|
|
src->ast_format(gogo).c_str());
|
|
extra_loop_depth = mod_loop_depth;
|
|
}
|
|
}
|
|
else if (e->call_expression() != NULL)
|
|
{
|
|
Call_expression* call = e->call_expression();
|
|
if (call->is_builtin())
|
|
{
|
|
Builtin_call_expression* bce = call->builtin_call_expression();
|
|
if (bce->code() == Builtin_call_expression::BUILTIN_APPEND)
|
|
{
|
|
// Propagate escape information to appendee.
|
|
Expression* appendee = call->args()->front();
|
|
this->flood(level, dst, Node::make_node(appendee), -1);
|
|
}
|
|
}
|
|
else if (call->fn()->func_expression() != NULL
|
|
&& call->fn()->func_expression()->is_runtime_function())
|
|
{
|
|
switch (call->fn()->func_expression()->runtime_code())
|
|
{
|
|
case Runtime::MAKECHAN:
|
|
case Runtime::MAKECHAN64:
|
|
case Runtime::MAKEMAP:
|
|
case Runtime::MAKESLICE:
|
|
case Runtime::MAKESLICE64:
|
|
if (src_leaks)
|
|
{
|
|
src->set_encoding(Node::ESCAPE_HEAP);
|
|
if (debug_level != 0 && osrcesc != src->encoding())
|
|
go_debug(src->location(), "%s escapes to heap",
|
|
src->ast_format(gogo).c_str());
|
|
extra_loop_depth = mod_loop_depth;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else if (src_state->retvals.size() > 0)
|
|
{
|
|
// In this case a link went directly to a call, but should really go
|
|
// to the dummy .outN outputs that were created for the call that
|
|
// themselves link to the inputs with levels adjusted.
|
|
// See e.g. #10466.
|
|
// This can only happen with functions returning a single result.
|
|
go_assert(src_state->retvals.size() == 1);
|
|
if (debug_level > 2)
|
|
go_debug(src->location(), "[%d] dst %s escwalk replace src: %s with %s",
|
|
this->context_->loop_depth(),
|
|
dst->ast_format(gogo).c_str(),
|
|
src->ast_format(gogo).c_str(),
|
|
src_state->retvals[0]->ast_format(gogo).c_str());
|
|
src = src_state->retvals[0];
|
|
src_state = src->state(this->context_, NULL);
|
|
}
|
|
}
|
|
else if (e->allocation_expression() != NULL && src_leaks)
|
|
{
|
|
// Calls to Runtime::NEW get lowered into an allocation expression.
|
|
src->set_encoding(Node::ESCAPE_HEAP);
|
|
if (debug_level != 0 && osrcesc != src->encoding())
|
|
go_debug(src->location(), "%s escapes to heap",
|
|
src->ast_format(gogo).c_str());
|
|
extra_loop_depth = mod_loop_depth;
|
|
}
|
|
else if ((e->map_literal() != NULL
|
|
|| e->string_concat_expression() != NULL
|
|
|| (e->func_expression() != NULL && e->func_expression()->closure() != NULL)
|
|
|| e->bound_method_expression() != NULL)
|
|
&& src_leaks)
|
|
{
|
|
src->set_encoding(Node::ESCAPE_HEAP);
|
|
if (debug_level != 0 && osrcesc != src->encoding())
|
|
go_debug(src->location(), "%s escapes to heap",
|
|
src->ast_format(gogo).c_str());
|
|
extra_loop_depth = mod_loop_depth;
|
|
}
|
|
else if (e->conversion_expression() != NULL && src_leaks)
|
|
{
|
|
Type_conversion_expression* tce = e->conversion_expression();
|
|
Type* ft = tce->expr()->type();
|
|
Type* tt = tce->type();
|
|
if ((ft->is_string_type() && tt->is_slice_type())
|
|
|| (ft->is_slice_type() && tt->is_string_type())
|
|
|| (ft->integer_type() != NULL && tt->is_string_type())
|
|
|| tt->interface_type() != NULL)
|
|
{
|
|
// string([]byte), string([]rune), []byte(string), []rune(string), string(rune),
|
|
// interface(T)
|
|
src->set_encoding(Node::ESCAPE_HEAP);
|
|
if (debug_level != 0 && osrcesc != src->encoding())
|
|
go_debug(src->location(), "%s escapes to heap",
|
|
src->ast_format(gogo).c_str());
|
|
extra_loop_depth = mod_loop_depth;
|
|
if (tt->interface_type() != NULL
|
|
&& ft->has_pointer()
|
|
&& !ft->is_direct_iface_type())
|
|
// We're converting from a non-direct interface type.
|
|
// The interface will hold a heap copy of the data
|
|
// Flow the data to heap. See issue 29353.
|
|
this->flood(level, this->context_->sink(),
|
|
Node::make_node(tce->expr()), -1);
|
|
}
|
|
}
|
|
else if (e->array_index_expression() != NULL
|
|
&& !e->array_index_expression()->array()->type()->is_slice_type())
|
|
{
|
|
Array_index_expression* aie = e->array_index_expression();
|
|
if (aie->end() != NULL)
|
|
{
|
|
// Slicing an array.
|
|
// Flow its implicit address-of node to DST.
|
|
this->flood(level, dst, src->child(), -1);
|
|
}
|
|
else
|
|
{
|
|
// Indexing an array.
|
|
// An array element flowing to DST behaves like the array
|
|
// flowing to DST.
|
|
Expression* underlying = e->array_index_expression()->array();
|
|
Node* underlying_node = Node::make_node(underlying);
|
|
this->flood(level, dst, underlying_node, -1);
|
|
}
|
|
}
|
|
else if ((e->field_reference_expression() != NULL
|
|
&& e->field_reference_expression()->expr()->unary_expression() == NULL)
|
|
|| e->type_guard_expression() != NULL
|
|
|| (e->array_index_expression() != NULL
|
|
&& e->array_index_expression()->end() != NULL)
|
|
|| (e->string_index_expression() != NULL
|
|
&& e->type()->is_string_type()))
|
|
{
|
|
Expression* underlying;
|
|
if (e->field_reference_expression() != NULL)
|
|
underlying = e->field_reference_expression()->expr();
|
|
else if (e->type_guard_expression() != NULL)
|
|
underlying = e->type_guard_expression()->expr();
|
|
else if (e->array_index_expression() != NULL)
|
|
underlying = e->array_index_expression()->array();
|
|
else
|
|
underlying = e->string_index_expression()->string();
|
|
|
|
Node* underlying_node = Node::make_node(underlying);
|
|
this->flood(level, dst, underlying_node, -1);
|
|
}
|
|
else if ((e->field_reference_expression() != NULL
|
|
&& e->field_reference_expression()->expr()->unary_expression() != NULL)
|
|
|| e->array_index_expression() != NULL
|
|
|| e->map_index_expression() != NULL
|
|
|| (e->unary_expression() != NULL
|
|
&& e->unary_expression()->op() == OPERATOR_MULT))
|
|
{
|
|
Expression* underlying;
|
|
if (e->field_reference_expression() != NULL)
|
|
{
|
|
underlying = e->field_reference_expression()->expr();
|
|
underlying = underlying->unary_expression()->operand();
|
|
}
|
|
else if (e->array_index_expression() != NULL)
|
|
underlying = e->array_index_expression()->array();
|
|
else if (e->map_index_expression() != NULL)
|
|
underlying = e->map_index_expression()->map();
|
|
else
|
|
underlying = e->unary_expression()->operand();
|
|
|
|
// Increase the level for a dereference.
|
|
Node* underlying_node = Node::make_node(underlying);
|
|
this->flood(level.increase(), dst, underlying_node, -1);
|
|
}
|
|
else if (e->temporary_reference_expression() != NULL)
|
|
{
|
|
Statement* t = e->temporary_reference_expression()->statement();
|
|
this->flood(level, dst, Node::make_node(t), -1);
|
|
}
|
|
}
|
|
else if (src->is_indirect())
|
|
// Increase the level for a dereference.
|
|
this->flood(level.increase(), dst, src->child(), -1);
|
|
|
|
level = level.copy();
|
|
for (std::set<Node*>::const_iterator p = src_state->flows.begin();
|
|
p != src_state->flows.end();
|
|
++p)
|
|
this->flood(level, dst, *p, extra_loop_depth);
|
|
|
|
this->context_->decrease_pdepth();
|
|
}
|
|
|
|
// Propagate escape information across the nodes modeled in this Analysis_set.
|
|
// This is an implementation of gc/esc.go:escflood.
|
|
|
|
void
|
|
Gogo::propagate_escape(Escape_context* context, Node* dst)
|
|
{
|
|
if (dst->object() == NULL
|
|
&& (dst->expr() == NULL
|
|
|| (dst->expr()->var_expression() == NULL
|
|
&& dst->expr()->enclosed_var_expression() == NULL
|
|
&& dst->expr()->func_expression() == NULL)))
|
|
return;
|
|
|
|
Node::Escape_state* state = dst->state(context, NULL);
|
|
Gogo* gogo = context->gogo();
|
|
if (gogo->debug_escape_level() > 1)
|
|
go_debug(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();
|
|
++p)
|
|
{
|
|
context->increase_flood_id();
|
|
eaf.flood(Level::From(0), dst, *p, -1);
|
|
}
|
|
}
|
|
|
|
class Escape_analysis_tag
|
|
{
|
|
public:
|
|
Escape_analysis_tag(Escape_context* context)
|
|
: context_(context)
|
|
{ }
|
|
|
|
// Add notes to the function's type about the escape information of its
|
|
// input parameters.
|
|
void
|
|
tag(Named_object* fn);
|
|
|
|
private:
|
|
Escape_context* context_;
|
|
};
|
|
|
|
void
|
|
Escape_analysis_tag::tag(Named_object* fn)
|
|
{
|
|
// External functions are assumed unsafe
|
|
// unless //go:noescape is given before the declaration.
|
|
if (fn->is_function_declaration())
|
|
{
|
|
Function_declaration* fdcl = fn->func_declaration_value();
|
|
if ((fdcl->pragmas() & GOPRAGMA_NOESCAPE) != 0)
|
|
{
|
|
Function_type* fntype = fdcl->type();
|
|
if (fntype->parameters() != NULL)
|
|
{
|
|
const Typed_identifier_list* til = fntype->parameters();
|
|
int i = 0;
|
|
for (Typed_identifier_list::const_iterator p = til->begin();
|
|
p != til->end();
|
|
++p, ++i)
|
|
if (p->type()->has_pointer())
|
|
fntype->add_parameter_note(i, Node::ESCAPE_NONE);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!fn->is_function())
|
|
return;
|
|
|
|
Function_type* fntype = fn->func_value()->type();
|
|
Bindings* bindings = fn->func_value()->block()->bindings();
|
|
|
|
if (fntype->is_method())
|
|
{
|
|
if (fntype->receiver()->name().empty()
|
|
|| Gogo::is_sink_name(fntype->receiver()->name()))
|
|
// Unnamed receiver is not used in the function body, does not escape.
|
|
fntype->add_receiver_note(Node::ESCAPE_NONE);
|
|
else
|
|
{
|
|
Named_object* rcvr_no = bindings->lookup(fntype->receiver()->name());
|
|
go_assert(rcvr_no != NULL);
|
|
Node* rcvr_node = Node::make_node(rcvr_no);
|
|
switch ((rcvr_node->encoding() & ESCAPE_MASK))
|
|
{
|
|
case Node::ESCAPE_NONE: // not touched by flood
|
|
case Node::ESCAPE_RETURN:
|
|
if (fntype->receiver()->type()->has_pointer())
|
|
// Don't bother tagging for scalars.
|
|
fntype->add_receiver_note(rcvr_node->encoding());
|
|
break;
|
|
|
|
case Node::ESCAPE_HEAP: // flooded, moved to heap.
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int i = 0;
|
|
if (fntype->parameters() != NULL)
|
|
{
|
|
const Typed_identifier_list* til = fntype->parameters();
|
|
for (Typed_identifier_list::const_iterator p = til->begin();
|
|
p != til->end();
|
|
++p, ++i)
|
|
{
|
|
if (p->name().empty() || Gogo::is_sink_name(p->name()))
|
|
{
|
|
// Parameter not used in the function body, does not escape.
|
|
if (p->type()->has_pointer())
|
|
fntype->add_parameter_note(i, Node::ESCAPE_NONE);
|
|
continue;
|
|
}
|
|
|
|
Named_object* param_no = bindings->lookup(p->name());
|
|
go_assert(param_no != NULL);
|
|
Node* param_node = Node::make_node(param_no);
|
|
switch ((param_node->encoding() & ESCAPE_MASK))
|
|
{
|
|
case Node::ESCAPE_NONE: // not touched by flood
|
|
case Node::ESCAPE_RETURN:
|
|
if (p->type()->has_pointer())
|
|
// Don't bother tagging for scalars.
|
|
fntype->add_parameter_note(i, param_node->encoding());
|
|
break;
|
|
|
|
case Node::ESCAPE_HEAP: // flooded, moved to heap.
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
fntype->set_is_tagged();
|
|
}
|
|
|
|
// Tag each top-level function with escape information that will be used to
|
|
// retain analysis results across imports.
|
|
|
|
void
|
|
Gogo::tag_function(Escape_context* context, Named_object* fn)
|
|
{
|
|
Escape_analysis_tag eat(context);
|
|
eat.tag(fn);
|
|
}
|
|
|
|
// Reclaim memory of escape analysis Nodes.
|
|
|
|
void
|
|
Gogo::reclaim_escape_nodes()
|
|
{
|
|
Node::reclaim_nodes();
|
|
}
|
|
|
|
void
|
|
Node::reclaim_nodes()
|
|
{
|
|
for (Unordered_map(Named_object*, Node*)::iterator p =
|
|
Node::objects.begin();
|
|
p != Node::objects.end();
|
|
++p)
|
|
delete p->second;
|
|
Node::objects.clear();
|
|
|
|
for (Unordered_map(Expression*, Node*)::iterator p =
|
|
Node::expressions.begin();
|
|
p != Node::expressions.end();
|
|
++p)
|
|
delete p->second;
|
|
Node::expressions.clear();
|
|
|
|
for (Unordered_map(Statement*, Node*)::iterator p =
|
|
Node::statements.begin();
|
|
p != Node::statements.end();
|
|
++p)
|
|
delete p->second;
|
|
Node::statements.clear();
|
|
|
|
for (std::vector<Node*>::iterator p = Node::indirects.begin();
|
|
p != Node::indirects.end();
|
|
++p)
|
|
delete *p;
|
|
Node::indirects.clear();
|
|
}
|