Commit bb14e19c by Ian Lance Taylor

compiler: bring escape analysis mostly in line with gc compiler

    
    This CL ports the latest (~Go 1.10) escape analysis code from
    the gc compiler. Changes include:
    
    - In the gc compiler, the variable expression is represented
      with the variable node itself (ONAME). It is the same node
      used in the AST for multiple var expressions for the same
      variable. In our case, the var expressions nodes are distinct
      nodes. We need to propagate the escape state from/to the
      underlying variable in getter and setter. We already do it in
      the setter. Do it in the getter as well.
    
    - At the point of escape analysis, some AST constructs have not
      been lowered to runtime calls, for example, map literal
      construction and some builtin calls. Change the analysis to
      work on the non-lowered AST constructs instead of call
      expressions for them. For this to work, the analysis needs to
      look into Builtin_call_expression. Move its class definition
      from expressions.cc to expressions.h, and add necessary
      accessors. Also fix bugs in other runtime call handlings
      (selectsend, ifaceX2Y2, etc.).
    
    - Handle closures properly. The analysis tracks the function
      reference expression, and the escape state is propagated to
      the underlying heap expression for get_backend to do stack
      allocation for non-escaping closures.
    
    - Fix add_dereference. Before, this was doing expr->deref(),
      which undoes an indirection instead of add one. In the gc
      compiler, it adds a level of indirection, which is modeled as
      an OIND node regardless of the type of the expression. We
      can't do this for non-pointer typed expression, otherwise it
      will result in a type error. Instead, we model it with a
      special flavor of Node, "indirect". The flood phase handles
      this by incrementing its level.
    
    - Slicing of an array was not handled correctly. The gc compiler
      has an implicit (compiler inserted) OADDR node for the array,
      so the analysis is actually performed on the address of the
      array. We don't have this implicit address-of expression in
      the AST. Instead, we model this by adding an implicit child to
      the Node of the Array_index_expression representing slicing of
      an array.
    
    - Array_index_expression may represent indexing or slicing. The
      code distinguishes them by looking at whether the type of the
      expression is a slice. This does not work if the slice element
      is a slice. Instead, check whether its end() is NULL.
    
    - Temporary references was handled only in a limited case, as
      part of address-of expression. This CL handles it in general.
      The analysis uses the Temporary_statement as the point of
      tracking, and forwards Temporary_reference_expression to the
      underlying statement when needed.
    
    - Handle call return value flows, escpecially multiple return
      values. This includes porting part of CL 8202, CL 20102, and
      other fixes.
    
    - Support go:noescape pragma.
    
    - Add special handling for self assignment like
      b.buf = b.buf[m:n]. (CL 3162)
    
    - Remove ESCAPE_SCOPE, which was treated essentially the same as
      ESCAPE_HEAP, and was removed from the gc compiler. (CL 32130)
    
    - Run flood phase until fix point. (CL 30693)
    
    - Unnamed parameters do not escape. (CL 38600)
    
    - Various small bug fixes and improvements.
    
    "make check-go" passes except the one test in math/big, when the
    escape analysis is on. The escape analysis is still not run by
    default.
    
    Reviewed-on: https://go-review.googlesource.com/83876

From-SVN: r255976
parent e2a29465
66de779004bdaafefc27e4132324a47d86a0f122 83fc0e440b8c151edc5b1c67006257aad522ca04
The first line of this file holds the git revision number of the last The first line of this file holds the git revision number of the last
merge done from the gofrontend repository. merge done from the gofrontend repository.
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "expressions.h" #include "expressions.h"
#include "statements.h" #include "statements.h"
#include "escape.h" #include "escape.h"
#include "lex.h"
#include "ast-dump.h" #include "ast-dump.h"
#include "go-optimize.h" #include "go-optimize.h"
#include "go-diagnostics.h" #include "go-diagnostics.h"
...@@ -34,6 +35,19 @@ Node::type() const ...@@ -34,6 +35,19 @@ Node::type() const
return this->object()->func_value()->type(); return this->object()->func_value()->type();
else if (this->expr() != NULL) else if (this->expr() != NULL)
return this->expr()->type(); 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
return this->child()->type()->deref();
}
else if (this->statement() != NULL
&& this->statement()->temporary_statement() != NULL)
return this->statement()->temporary_statement()->type();
else else
return NULL; return NULL;
} }
...@@ -49,6 +63,8 @@ Node::location() const ...@@ -49,6 +63,8 @@ Node::location() const
return this->expr()->location(); return this->expr()->location();
else if (this->statement() != NULL) else if (this->statement() != NULL)
return this->statement()->location(); return this->statement()->location();
else if (this->is_indirect())
return this->child()->location();
else else
return Linemap::unknown_location(); return Linemap::unknown_location();
} }
...@@ -133,7 +149,7 @@ Node::ast_format(Gogo* gogo) const ...@@ -133,7 +149,7 @@ Node::ast_format(Gogo* gogo) const
} }
Ast_dump_context::dump_to_stream(this->expr(), &ss); Ast_dump_context::dump_to_stream(this->expr(), &ss);
} }
else else if (this->statement() != NULL)
{ {
Statement* s = this->statement(); Statement* s = this->statement();
Goto_unnamed_statement* unnamed = s->goto_unnamed_statement(); Goto_unnamed_statement* unnamed = s->goto_unnamed_statement();
...@@ -162,8 +178,25 @@ Node::ast_format(Gogo* gogo) const ...@@ -162,8 +178,25 @@ Node::ast_format(Gogo* gogo) const
} }
} }
} }
Ast_dump_context::dump_to_stream(s, &ss); 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()); std::string s = strip_packed_prefix(gogo, ss.str());
...@@ -175,7 +208,7 @@ Node::ast_format(Gogo* gogo) const ...@@ -175,7 +208,7 @@ Node::ast_format(Gogo* gogo) const
// This is an implementation of gc's Jconv with obj.FmtShort. // This is an implementation of gc's Jconv with obj.FmtShort.
std::string std::string
Node::details() const Node::details()
{ {
std::stringstream details; std::stringstream details;
...@@ -258,10 +291,6 @@ Node::details() const ...@@ -258,10 +291,6 @@ Node::details() const
details << " esc(h)"; details << " esc(h)";
break; break;
case Node::ESCAPE_SCOPE:
details << " esc(s)";
break;
case Node::ESCAPE_NONE: case Node::ESCAPE_NONE:
details << " esc(no)"; details << " esc(no)";
break; break;
...@@ -383,6 +412,8 @@ Node::op_format() const ...@@ -383,6 +412,8 @@ Node::op_format() const
break; break;
} }
} }
if (this->is_indirect())
op << "*";
return op.str(); return op.str();
} }
...@@ -413,16 +444,40 @@ Node::state(Escape_context* context, Named_object* fn) ...@@ -413,16 +444,40 @@ Node::state(Escape_context* context, Named_object* fn)
return this->state_; return this->state_;
} }
void int
Node::set_encoding(int enc) Node::encoding()
{ {
this->encoding_ = enc;
if (this->expr() != NULL if (this->expr() != NULL
&& this->expr()->var_expression() != NULL) && this->expr()->var_expression() != NULL)
{ {
// Set underlying object as well. // Get the underlying object's encoding.
Named_object* no = this->expr()->var_expression()->named_object(); Named_object* no = this->expr()->var_expression()->named_object();
Node::make_node(no)->set_encoding(enc); 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);
}
} }
} }
...@@ -461,9 +516,17 @@ Node::is_big(Escape_context* context) const ...@@ -461,9 +516,17 @@ Node::is_big(Escape_context* context) const
Expression_list::iterator p = call->args()->begin(); Expression_list::iterator p = call->args()->begin();
++p; ++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; Numeric_constant nc;
unsigned long v; unsigned long v;
if ((*p)->numeric_constant_value(&nc) if (e->numeric_constant_value(&nc)
&& nc.to_unsigned_long(&v) == Numeric_constant::NC_UL_VALID) && nc.to_unsigned_long(&v) == Numeric_constant::NC_UL_VALID)
big = big || v >= (1 << 16); big = big || v >= (1 << 16);
} }
...@@ -531,13 +594,22 @@ Node::make_node(Statement* s) ...@@ -531,13 +594,22 @@ Node::make_node(Statement* s)
return n; return n;
} }
// Make an indirect node with given child.
Node*
Node::make_indirect_node(Node* child)
{
Node* n = new Node(child);
return n;
}
// Returns the maximum of an exisiting escape value // Returns the maximum of an exisiting escape value
// (and its additional parameter flow flags) and a new escape type. // (and its additional parameter flow flags) and a new escape type.
int int
Node::max_encoding(int e, int etype) Node::max_encoding(int e, int etype)
{ {
if ((e & ESCAPE_MASK) >= etype) if ((e & ESCAPE_MASK) > etype)
return e; return e;
if (etype == Node::ESCAPE_NONE || etype == Node::ESCAPE_RETURN) if (etype == Node::ESCAPE_NONE || etype == Node::ESCAPE_RETURN)
return (e & ~ESCAPE_MASK) | etype; return (e & ~ESCAPE_MASK) | etype;
...@@ -587,7 +659,6 @@ Escape_context::Escape_context(Gogo* gogo, bool recursive) ...@@ -587,7 +659,6 @@ Escape_context::Escape_context(Gogo* gogo, bool recursive)
{ {
// The sink always escapes to heap and strictly lives outside of the // The sink always escapes to heap and strictly lives outside of the
// current function i.e. loop_depth == -1. // current function i.e. loop_depth == -1.
this->sink_->set_encoding(Node::ESCAPE_HEAP);
Node::Escape_state* state = this->sink_->state(this, NULL); Node::Escape_state* state = this->sink_->state(this, NULL);
state->loop_depth = -1; state->loop_depth = -1;
} }
...@@ -656,6 +727,7 @@ Escape_context::init_retvals(Node* n, Function_type* fntype) ...@@ -656,6 +727,7 @@ Escape_context::init_retvals(Node* n, Function_type* fntype)
return; return;
Node::Escape_state* state = n->state(this, NULL); Node::Escape_state* state = n->state(this, NULL);
state->retvals.clear();
Location loc = n->location(); Location loc = n->location();
int i = 0; int i = 0;
...@@ -672,7 +744,8 @@ Escape_context::init_retvals(Node* n, Function_type* fntype) ...@@ -672,7 +744,8 @@ Escape_context::init_retvals(Node* n, Function_type* fntype)
Named_object::make_variable(buf, NULL, dummy_var); Named_object::make_variable(buf, NULL, dummy_var);
Node* dummy_node = Node::make_node(dummy_no); Node* dummy_node = Node::make_node(dummy_no);
// Initialize the state of the dummy output node. // Initialize the state of the dummy output node.
dummy_node->state(this, NULL); 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. // Add dummy node to the retvals of n.
state->retvals.push_back(dummy_node); state->retvals.push_back(dummy_node);
...@@ -681,20 +754,30 @@ Escape_context::init_retvals(Node* n, Function_type* fntype) ...@@ -681,20 +754,30 @@ Escape_context::init_retvals(Node* n, Function_type* fntype)
// Apply an indirection to N and return the result. // Apply an indirection to N and return the result.
// This really only works if N is an expression node; it essentially becomes
// Node::make_node(n->expr()->deref()). We need the escape context to set the
// correct loop depth, however.
Node* Node*
Escape_context::add_dereference(Node* n) Escape_context::add_dereference(Node* n)
{ {
// Just return the original node if we can't add an indirection. Expression* e = n->expr();
if (n->object() != NULL || n->statement() != NULL) Location loc = n->location();
return n; Node* ind;
if (e != NULL
Node* ind = Node::make_node(n->expr()->deref()); && e->type()->points_to() != NULL
// Initialize the state if this node doesn't already exist. && !e->type()->points_to()->is_void_type())
ind->state(this, NULL); {
// 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; return ind;
} }
...@@ -775,10 +858,43 @@ Gogo::analyze_escape() ...@@ -775,10 +858,43 @@ Gogo::analyze_escape()
// Propagate levels across each dst. This is the flood phase. // Propagate levels across each dst. This is the flood phase.
std::set<Node*> dsts = context->dsts(); std::set<Node*> dsts = context->dsts();
std::unordered_map<Node*, int> escapes;
for (std::set<Node*>::iterator n = dsts.begin(); for (std::set<Node*>::iterator n = dsts.begin();
n != dsts.end(); n != dsts.end();
++n) ++n)
this->propagate_escape(context, *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_inform((*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. // Tag each exported function's parameters with escape information.
for (std::vector<Named_object*>::iterator fn = stack.begin(); for (std::vector<Named_object*>::iterator fn = stack.begin();
...@@ -799,7 +915,6 @@ Gogo::analyze_escape() ...@@ -799,7 +915,6 @@ Gogo::analyze_escape()
strip_packed_prefix(this, debug_function_name(state->fn)).c_str(), strip_packed_prefix(this, debug_function_name(state->fn)).c_str(),
(*n)->ast_format(this).c_str()); (*n)->ast_format(this).c_str());
} }
// TODO(cmang): Which objects in context->noesc actually don't escape.
} }
delete context; delete context;
} }
...@@ -893,8 +1008,8 @@ Escape_analysis_discover::visit(Named_object* fn) ...@@ -893,8 +1008,8 @@ Escape_analysis_discover::visit(Named_object* fn)
this->stack_.push(fn); this->stack_.push(fn);
min = this->visit_code(fn, min); min = this->visit_code(fn, min);
if ((min == id || min == id + 1) if ((min == id || min == id + 1)
&& fn->is_function() && ((fn->is_function() && fn->func_value()->enclosing() == NULL)
&& fn->func_value()->enclosing() == NULL) || fn->is_function_declaration()))
{ {
bool recursive = min == id; bool recursive = min == id;
std::vector<Named_object*> group; std::vector<Named_object*> group;
...@@ -1072,6 +1187,38 @@ private: ...@@ -1072,6 +1187,38 @@ private:
Named_object* fn_; 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. // Model statements within a function as assignments and flows between nodes.
int int
...@@ -1121,6 +1268,14 @@ Escape_analysis_assign::statement(Block*, size_t*, Statement* s) ...@@ -1121,6 +1268,14 @@ Escape_analysis_assign::statement(Block*, size_t*, Statement* s)
} }
break; break;
case Statement::STATEMENT_TEMPORARY:
{
Expression* init = s->temporary_statement()->init();
if (init != NULL)
this->assign(Node::make_node(s), Node::make_node(init));
}
break;
case Statement::STATEMENT_LABEL: case Statement::STATEMENT_LABEL:
{ {
Label_statement* label_stmt = s->label_statement(); Label_statement* label_stmt = s->label_statement();
...@@ -1144,22 +1299,36 @@ Escape_analysis_assign::statement(Block*, size_t*, Statement* s) ...@@ -1144,22 +1299,36 @@ Escape_analysis_assign::statement(Block*, size_t*, Statement* s)
// Want to model the assignment of each case variable to the switched upon // Want to model the assignment of each case variable to the switched upon
// variable. This should be lowered into assignment statements; nothing // variable. This should be lowered into assignment statements; nothing
// to here if that's the case. // to here if that's the case.
// TODO(cmang): Verify.
break; break;
case Statement::STATEMENT_ASSIGNMENT: case Statement::STATEMENT_ASSIGNMENT:
{ {
Assignment_statement* assn = s->assignment_statement(); Assignment_statement* assn = s->assignment_statement();
Node* lhs = Node::make_node(assn->lhs()); Expression* lhs = assn->lhs();
Node* rhs = Node::make_node(assn->rhs()); Expression* rhs = assn->rhs();
Node* lhs_node = Node::make_node(lhs);
// TODO(cmang): Add special case for escape analysis no-op: Node* rhs_node = Node::make_node(rhs);
// func (b *Buffer) Foo() {
// n, m := ... // Filter out the following special case.
// b.buf = b.buf[n:m] //
// } // func (b *Buffer) Foo() {
// This is okay for now, it just means b escapes; it is conservative. // n, m := ...
this->assign(lhs, rhs); // 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; break;
...@@ -1199,8 +1368,6 @@ Escape_analysis_assign::statement(Block*, size_t*, Statement* s) ...@@ -1199,8 +1368,6 @@ Escape_analysis_assign::statement(Block*, size_t*, Statement* s)
} }
break; break;
// TODO(cmang): Associate returned values with dummy return nodes.
default: default:
break; break;
} }
...@@ -1221,7 +1388,15 @@ Escape_analysis_assign::expression(Expression** pexpr) ...@@ -1221,7 +1388,15 @@ Escape_analysis_assign::expression(Expression** pexpr)
&& n->is_big(this->context_)) && n->is_big(this->context_))
{ {
if (debug_level > 1) if (debug_level > 1)
go_inform((*pexpr)->location(), "too large for stack"); go_inform((*pexpr)->location(), "%s too large for stack",
n->ast_format(gogo).c_str());
if (debug_level != 0
&& ((*pexpr)->var_expression() != NULL
|| (*pexpr)->enclosed_var_expression() != NULL))
go_inform(n->definition_location(),
"moved to heap: %s",
n->ast_format(gogo).c_str());
n->set_encoding(Node::ESCAPE_HEAP); n->set_encoding(Node::ESCAPE_HEAP);
(*pexpr)->address_taken(true); (*pexpr)->address_taken(true);
this->assign(this->context_->sink(), n); this->assign(this->context_->sink(), n);
...@@ -1244,100 +1419,158 @@ Escape_analysis_assign::expression(Expression** pexpr) ...@@ -1244,100 +1419,158 @@ Escape_analysis_assign::expression(Expression** pexpr)
case Expression::EXPRESSION_CALL: case Expression::EXPRESSION_CALL:
{ {
Call_expression* call = (*pexpr)->call_expression(); Call_expression* call = (*pexpr)->call_expression();
this->call(call); 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_inform((*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(); Func_expression* fe = call->fn()->func_expression();
if (fe != NULL && fe->is_runtime_function()) if (fe != NULL && fe->is_runtime_function())
{ {
switch (fe->runtime_code()) switch (fe->runtime_code())
{ {
case Runtime::GOPANIC:
{
// Argument could leak through recover.
Node* panic_arg = Node::make_node(call->args()->front());
this->assign(this->context_->sink(), panic_arg);
}
break;
case Runtime::GROWSLICE:
{
// The contents being appended leak.
if (call->is_varargs())
{
Node* appended = Node::make_node(call->args()->back());
this->assign_deref(this->context_->sink(), appended);
}
else
{
for (Expression_list::const_iterator pa =
call->args()->begin();
pa != call->args()->end();
++pa)
{
Node* arg = Node::make_node(*pa);
this->assign(this->context_->sink(), arg);
}
}
if (debug_level > 2)
go_error_at((*pexpr)->location(),
"special treatment of append(slice1, slice2...)");
// The content of the original slice leaks as well.
Node* appendee = Node::make_node(call->args()->front());
this->assign_deref(this->context_->sink(), appendee);
}
break;
case Runtime::SLICECOPY:
case Runtime::SLICESTRINGCOPY:
case Runtime::TYPEDSLICECOPY:
{
// Lose track of the copied content.
Node* copied = Node::make_node(call->args()->back());
this->assign_deref(this->context_->sink(), copied);
}
break;
case Runtime::MAKECHAN: case Runtime::MAKECHAN:
case Runtime::MAKEMAP: case Runtime::MAKEMAP:
case Runtime::MAKESLICE: case Runtime::MAKESLICE:
case Runtime::MAKESLICE64: case Runtime::MAKESLICE64:
case Runtime::SLICEBYTETOSTRING: this->context_->track(n);
case Runtime::SLICERUNETOSTRING:
case Runtime::STRINGTOSLICEBYTE:
case Runtime::STRINGTOSLICERUNE:
case Runtime::CONCATSTRINGS:
case Runtime::CONCATSTRING2:
case Runtime::CONCATSTRING3:
case Runtime::CONCATSTRING4:
case Runtime::CONCATSTRING5:
case Runtime::CONSTRUCT_MAP:
case Runtime::INTSTRING:
{
Node* runtime_node = Node::make_node(fe);
this->context_->track(runtime_node);
}
break; 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::SELECTSEND:
{
// Send to a channel, lose track. The last argument is
// the address of the value to send.
Node* arg_node = Node::make_node(call->args()->back());
this->assign_deref(this->context_->sink(), arg_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: default:
break; break;
} }
} }
else
this->call(call);
} }
break; break;
case Expression::EXPRESSION_ALLOCATION: case Expression::EXPRESSION_ALLOCATION:
{ // This is Runtime::NEW.
// Same as above; this is Runtime::NEW. this->context_->track(n);
Node* alloc_node = Node::make_node(*pexpr); break;
this->context_->track(alloc_node);
} case Expression::EXPRESSION_STRING_CONCAT:
this->context_->track(n);
break; break;
case Expression::EXPRESSION_CONVERSION: case Expression::EXPRESSION_CONVERSION:
{ {
Type_conversion_expression* tce = (*pexpr)->conversion_expression(); 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* tce_node = Node::make_node(tce);
Node* converted = Node::make_node(tce->expr()); Node* converted = Node::make_node(tce->expr());
this->context_->track(tce_node); this->context_->track(tce_node);
...@@ -1478,26 +1711,6 @@ Escape_analysis_assign::expression(Expression** pexpr) ...@@ -1478,26 +1711,6 @@ Escape_analysis_assign::expression(Expression** pexpr)
var = operand->var_expression()->named_object(); var = operand->var_expression()->named_object();
else if (operand->enclosed_var_expression() != NULL) else if (operand->enclosed_var_expression() != NULL)
var = operand->enclosed_var_expression()->variable(); var = operand->enclosed_var_expression()->variable();
else if (operand->temporary_reference_expression() != NULL)
{
// Found in runtime/chanbarrier_test.go. The address of a struct
// reference is usually a heap expression, except when it is a part
// of a case statement. In that case, it is lowered into a
// temporary reference and never linked to the heap expression that
// initializes it. In general, when taking the address of some
// temporary, the analysis should really be looking at the initial
// value of that temporary.
Temporary_reference_expression* tre =
operand->temporary_reference_expression();
if (tre->statement() != NULL
&& tre->statement()->temporary_statement()->init() != NULL)
{
Expression* init =
tre->statement()->temporary_statement()->init();
Node* init_node = Node::make_node(init);
this->assign(addr_node, init_node);
}
}
if (var == NULL) if (var == NULL)
break; break;
...@@ -1525,6 +1738,21 @@ Escape_analysis_assign::expression(Expression** pexpr) ...@@ -1525,6 +1738,21 @@ Escape_analysis_assign::expression(Expression** pexpr)
} }
break; break;
case Expression::EXPRESSION_ARRAY_INDEX:
{
Array_index_expression* aie = (*pexpr)->array_index_expression();
if (aie->end() != NULL && !aie->array()->type()->is_slice_type())
{
// Slicing an 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);
}
}
break;
default: default:
break; break;
} }
...@@ -1587,6 +1815,17 @@ Escape_analysis_assign::call(Call_expression* call) ...@@ -1587,6 +1815,17 @@ Escape_analysis_assign::call(Call_expression* call)
} }
this->context_->init_retvals(call_node, fntype); 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; return;
} }
...@@ -1601,33 +1840,26 @@ Escape_analysis_assign::call(Call_expression* call) ...@@ -1601,33 +1840,26 @@ Escape_analysis_assign::call(Call_expression* call)
Function* f = fn->named_object()->func_value(); Function* f = fn->named_object()->func_value();
const Bindings* callee_bindings = f->block()->bindings(); const Bindings* callee_bindings = f->block()->bindings();
Function::Results* results = f->result_variables();
const Typed_identifier_list* results = fntype->results();
if (results != NULL) if (results != NULL)
{ {
// Setup output list on this call node. // Setup output list on this call node.
Node::Escape_state* state = call_node->state(this->context_, NULL); Node::Escape_state* state = call_node->state(this->context_, NULL);
for (Typed_identifier_list::const_iterator p1 = results->begin(); for (Function::Results::const_iterator p1 = results->begin();
p1 != results->end(); p1 != results->end();
++p1) ++p1)
{ {
if (p1->name().empty() || Gogo::is_sink_name(p1->name())) Node* result_node = Node::make_node(*p1);
continue;
Named_object* result_no =
callee_bindings->lookup_local(p1->name());
go_assert(result_no != NULL);
Node* result_node = Node::make_node(result_no);
state->retvals.push_back(result_node); state->retvals.push_back(result_node);
} }
} }
std::vector<Node*>::iterator p = arg_nodes.begin(); std::vector<Node*>::iterator p = arg_nodes.begin();
if (fntype->is_method() if (fntype->is_method())
&& fntype->receiver()->type()->has_pointer())
{ {
std::string rcvr_name = fntype->receiver()->name(); std::string rcvr_name = fntype->receiver()->name();
if (rcvr_name.empty() || Gogo::is_sink_name(rcvr_name)) if (rcvr_name.empty() || Gogo::is_sink_name(rcvr_name)
|| !fntype->receiver()->type()->has_pointer())
; ;
else else
{ {
...@@ -1635,7 +1867,17 @@ Escape_analysis_assign::call(Call_expression* call) ...@@ -1635,7 +1867,17 @@ Escape_analysis_assign::call(Call_expression* call)
callee_bindings->lookup_local(fntype->receiver()->name()); callee_bindings->lookup_local(fntype->receiver()->name());
go_assert(rcvr_no != NULL); go_assert(rcvr_no != NULL);
Node* rcvr_node = Node::make_node(rcvr_no); Node* rcvr_node = Node::make_node(rcvr_no);
this->assign(rcvr_node, *p); 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; ++p;
} }
...@@ -1688,21 +1930,20 @@ Escape_analysis_assign::call(Call_expression* call) ...@@ -1688,21 +1930,20 @@ Escape_analysis_assign::call(Call_expression* call)
// Receiver. // Receiver.
std::vector<Node*>::iterator p = arg_nodes.begin(); std::vector<Node*>::iterator p = arg_nodes.begin();
if (fntype->is_method() if (fntype->is_method()
&& fntype->receiver()->type()->has_pointer()
&& p != arg_nodes.end()) && p != arg_nodes.end())
{ {
// First argument to call will be the receiver. // First argument to call will be the receiver.
std::string* note = fntype->receiver()->note(); std::string* note = fntype->receiver()->note();
if (fntype->receiver()->type()->points_to() == NULL if (fntype->receiver()->type()->points_to() == NULL
&& (*p)->expr()->unary_expression() != NULL && (*p)->expr()->type()->points_to() != NULL)
&& (*p)->expr()->unary_expression()->op() == OPERATOR_AND) // 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
// This is a call to a value method that has been lowered into a call // method calls and takes the address of the value passed as the
// to a pointer method. Gccgo generates a pointer method for all // receiver then immediately dereferences it within the function.
// method calls and takes the address of the value passed as the // In this case, the receiver address does not escape; its content
// receiver then immediately dereferences it within the function. // flows to the call.
// In this case, the receiver does not escape. this->assign_from_note(note, call_state->retvals,
} this->context_->add_dereference(*p));
else else
{ {
if (!Type::are_identical(fntype->receiver()->type(), if (!Type::are_identical(fntype->receiver()->type(),
...@@ -1732,7 +1973,6 @@ Escape_analysis_assign::call(Call_expression* call) ...@@ -1732,7 +1973,6 @@ Escape_analysis_assign::call(Call_expression* call)
this->context_->track(*p); this->context_->track(*p);
} }
// TODO(cmang): Special care for varargs parameter?
Type* t = pn->type(); Type* t = pn->type();
if (t != NULL if (t != NULL
&& t->has_pointer()) && t->has_pointer())
...@@ -1740,10 +1980,12 @@ Escape_analysis_assign::call(Call_expression* call) ...@@ -1740,10 +1980,12 @@ Escape_analysis_assign::call(Call_expression* call)
std::string* note = pn->note(); std::string* note = pn->note();
int enc = this->assign_from_note(note, call_state->retvals, *p); int enc = this->assign_from_note(note, call_state->retvals, *p);
if (enc == Node::ESCAPE_NONE if (enc == Node::ESCAPE_NONE
&& (call->is_deferred() && !call->is_deferred()
|| call->is_concurrent())) && !call->is_concurrent())
{ {
// TODO(cmang): Mark the argument as strictly non-escaping. // 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?
} }
} }
} }
...@@ -1778,7 +2020,10 @@ Escape_analysis_assign::assign(Node* dst, Node* src) ...@@ -1778,7 +2020,10 @@ Escape_analysis_assign::assign(Node* dst, Node* src)
src->ast_format(gogo).c_str(), src->details().c_str(), src->ast_format(gogo).c_str(), src->details().c_str(),
src->op_format().c_str()); src->op_format().c_str());
if (dst->expr() != NULL) if (dst->is_indirect())
// Lose track of the dereference.
dst = this->context_->sink();
else if (dst->expr() != NULL)
{ {
// Analyze the lhs of the assignment. // Analyze the lhs of the assignment.
// Replace DST with this->context_->sink() if we can't track it. // Replace DST with this->context_->sink() if we can't track it.
...@@ -1845,6 +2090,14 @@ Escape_analysis_assign::assign(Node* dst, Node* src) ...@@ -1845,6 +2090,14 @@ Escape_analysis_assign::assign(Node* dst, Node* src)
} }
break; break;
case Expression::EXPRESSION_TEMPORARY_REFERENCE:
{
// Temporary is tracked through the underlying Temporary_statement.
Statement* t = dst->expr()->temporary_reference_expression()->statement();
dst = Node::make_node(t);
}
break;
default: default:
// TODO(cmang): Add debugging info here: only a few expressions // TODO(cmang): Add debugging info here: only a few expressions
// should leave DST unmodified. // should leave DST unmodified.
...@@ -1852,12 +2105,17 @@ Escape_analysis_assign::assign(Node* dst, Node* src) ...@@ -1852,12 +2105,17 @@ Escape_analysis_assign::assign(Node* dst, Node* src)
} }
} }
if (src->expr() != NULL) 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(); Expression* e = src->expr();
switch (e->classification()) switch (e->classification())
{ {
case Expression::EXPRESSION_VAR_REFERENCE: case Expression::EXPRESSION_VAR_REFERENCE:
case Expression::EXPRESSION_ENCLOSED_VAR_REFERENCE:
// DST = var // DST = var
case Expression::EXPRESSION_HEAP: case Expression::EXPRESSION_HEAP:
// DST = &T{...}. // DST = &T{...}.
...@@ -1872,6 +2130,8 @@ Escape_analysis_assign::assign(Node* dst, Node* src) ...@@ -1872,6 +2130,8 @@ Escape_analysis_assign::assign(Node* dst, Node* src)
// DST = new(T). // DST = new(T).
case Expression::EXPRESSION_BOUND_METHOD: case Expression::EXPRESSION_BOUND_METHOD:
// DST = x.M. // DST = x.M.
case Expression::EXPRESSION_STRING_CONCAT:
// DST = str1 + str2
this->flows(dst, src); this->flows(dst, src);
break; break;
...@@ -1883,73 +2143,34 @@ Escape_analysis_assign::assign(Node* dst, Node* src) ...@@ -1883,73 +2143,34 @@ Escape_analysis_assign::assign(Node* dst, Node* src)
} }
break; break;
case Expression::EXPRESSION_ENCLOSED_VAR_REFERENCE:
{
Named_object* var = e->enclosed_var_expression()->variable();
Node* var_node = Node::make_node(var);
this->flows(dst, var_node);
}
break;
case Expression::EXPRESSION_CALL: case Expression::EXPRESSION_CALL:
{ {
Call_expression* call = e->call_expression(); 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(); Func_expression* fe = call->fn()->func_expression();
if (fe != NULL && fe->is_runtime_function()) if (fe != NULL && fe->is_runtime_function())
{ {
switch (fe->runtime_code()) switch (fe->runtime_code())
{ {
case Runtime::GROWSLICE:
{
// 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;
}
case Runtime::MAKECHAN: case Runtime::MAKECHAN:
case Runtime::MAKEMAP: case Runtime::MAKEMAP:
case Runtime::MAKESLICE: case Runtime::MAKESLICE:
case Runtime::MAKESLICE64: case Runtime::MAKESLICE64:
// DST = make(...). // DST = make(...).
case Runtime::SLICEBYTETOSTRING: this->flows(dst, src);
// DST = string([]byte{...}). break;
case Runtime::SLICERUNETOSTRING:
// DST = string([]int{...}).
case Runtime::STRINGTOSLICEBYTE:
// DST = []byte(str).
case Runtime::STRINGTOSLICERUNE:
// DST = []rune(str).
case Runtime::CONCATSTRINGS:
case Runtime::CONCATSTRING2:
case Runtime::CONCATSTRING3:
case Runtime::CONCATSTRING4:
case Runtime::CONCATSTRING5:
// DST = str1 + str2
case Runtime::CONSTRUCT_MAP:
// When building a map literal's backend representation.
// Likely never seen here and covered in
// Expression::EXPRESSION_MAP_CONSTRUCTION.
case Runtime::INTSTRING:
// DST = string(i).
case Runtime::IFACEE2E2:
case Runtime::IFACEI2E2:
case Runtime::IFACEE2I2:
case Runtime::IFACEI2I2:
case Runtime::IFACEE2T2P:
case Runtime::IFACEI2T2P:
case Runtime::IFACEE2T2:
case Runtime::IFACEI2T2:
case Runtime::REQUIREITAB:
// All versions of interface conversion that might result
// from a type assertion. Some of these are the result of
// a tuple type assertion statement and may not be covered
// by the case in Expression::EXPRESSION_CONVERSION or
// Expression::EXPRESSION_TYPE_GUARD.
this->flows(dst, src);
break;
default: default:
break; break;
...@@ -1969,28 +2190,85 @@ Escape_analysis_assign::assign(Node* dst, Node* src) ...@@ -1969,28 +2190,85 @@ Escape_analysis_assign::assign(Node* dst, Node* src)
break; break;
} }
// TODO(cmang): Handle case from issue 4529. // Result flows to dst.
// Node* call_node = Node::make_node(e); Node* call_node = Node::make_node(e);
// Node::Escape_state* call_state = call_node->state(this->context_, NULL); Node::Escape_state* call_state = call_node->state(this->context_, NULL);
// std::vector<Node*> retvals = call_state->retvals; std::vector<Node*> retvals = call_state->retvals;
// for (std::vector<Node*>::const_iterator p = retvals.begin(); for (std::vector<Node*>::const_iterator p = retvals.begin();
// p != retvals.end(); p != retvals.end();
// ++p) ++p)
// this->flows(dst, *p); this->flows(dst, *p);
} }
break; 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: case Expression::EXPRESSION_FUNC_REFERENCE:
if (e->func_expression()->closure() != NULL) if (e->func_expression()->closure() != NULL)
{ this->flows(dst, src);
// If SRC is a reference to a function closure, DST flows into
// the underyling closure variable.
Expression* closure = e->func_expression()->closure();
Node* closure_node = Node::make_node(closure);
this->flows(dst, closure_node);
}
break; 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()))
{
// string([]byte), string([]rune), []byte(string), []rune(string), string(rune)
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: case Expression::EXPRESSION_FIELD_REFERENCE:
{ {
// A non-pointer can't escape from a struct. // A non-pointer can't escape from a struct.
...@@ -1999,7 +2277,6 @@ Escape_analysis_assign::assign(Node* dst, Node* src) ...@@ -1999,7 +2277,6 @@ Escape_analysis_assign::assign(Node* dst, Node* src)
} }
// Fall through. // Fall through.
case Expression::EXPRESSION_CONVERSION:
case Expression::EXPRESSION_TYPE_GUARD: case Expression::EXPRESSION_TYPE_GUARD:
case Expression::EXPRESSION_ARRAY_INDEX: case Expression::EXPRESSION_ARRAY_INDEX:
case Expression::EXPRESSION_STRING_INDEX: case Expression::EXPRESSION_STRING_INDEX:
...@@ -2016,15 +2293,23 @@ Escape_analysis_assign::assign(Node* dst, Node* src) ...@@ -2016,15 +2293,23 @@ Escape_analysis_assign::assign(Node* dst, Node* src)
break; break;
} }
} }
else if (e->conversion_expression() != NULL)
left = e->conversion_expression()->expr();
else if (e->type_guard_expression() != NULL) else if (e->type_guard_expression() != NULL)
left = e->type_guard_expression()->expr(); left = e->type_guard_expression()->expr();
else if (e->array_index_expression() != NULL) else if (e->array_index_expression() != NULL)
{ {
Array_index_expression* aie = e->array_index_expression(); Array_index_expression* aie = e->array_index_expression();
if (e->type()->is_slice_type()) if (aie->end() != NULL)
left = aie->array(); // 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()) else if (!aie->array()->type()->is_slice_type())
{ {
// Indexing an array preserves the input value. // Indexing an array preserves the input value.
...@@ -2041,15 +2326,9 @@ Escape_analysis_assign::assign(Node* dst, Node* src) ...@@ -2041,15 +2326,9 @@ Escape_analysis_assign::assign(Node* dst, Node* src)
else if (e->string_index_expression() != NULL) else if (e->string_index_expression() != NULL)
{ {
String_index_expression* sie = e->string_index_expression(); String_index_expression* sie = e->string_index_expression();
if (e->type()->is_slice_type()) if (e->type()->is_string_type())
// slicing
left = sie->string(); left = sie->string();
else if (!sie->string()->type()->is_slice_type())
{
// Indexing a string preserves the input value.
Node* string_node = Node::make_node(sie->string());
this->assign(dst, string_node);
break;
}
else else
{ {
this->flows(dst, src); this->flows(dst, src);
...@@ -2072,6 +2351,7 @@ Escape_analysis_assign::assign(Node* dst, Node* src) ...@@ -2072,6 +2351,7 @@ Escape_analysis_assign::assign(Node* dst, Node* src)
case OPERATOR_PLUS: case OPERATOR_PLUS:
case OPERATOR_MINUS: case OPERATOR_MINUS:
case OPERATOR_XOR: case OPERATOR_XOR:
case OPERATOR_OR:
case OPERATOR_MULT: case OPERATOR_MULT:
case OPERATOR_DIV: case OPERATOR_DIV:
case OPERATOR_MOD: case OPERATOR_MOD:
...@@ -2123,13 +2403,7 @@ Escape_analysis_assign::assign(Node* dst, Node* src) ...@@ -2123,13 +2403,7 @@ Escape_analysis_assign::assign(Node* dst, Node* src)
case Expression::EXPRESSION_TEMPORARY_REFERENCE: case Expression::EXPRESSION_TEMPORARY_REFERENCE:
{ {
Statement* temp = e->temporary_reference_expression()->statement(); Statement* temp = e->temporary_reference_expression()->statement();
if (temp != NULL this->assign(dst, Node::make_node(temp));
&& temp->temporary_statement()->init() != NULL)
{
Expression* init = temp->temporary_statement()->init();
Node* init_node = Node::make_node(init);
this->assign(dst, init_node);
}
} }
break; break;
...@@ -2139,6 +2413,8 @@ Escape_analysis_assign::assign(Node* dst, Node* src) ...@@ -2139,6 +2413,8 @@ Escape_analysis_assign::assign(Node* dst, Node* src)
break; 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. // Model the assignment of DST to an indirection of SRC.
...@@ -2161,36 +2437,6 @@ Escape_analysis_assign::assign_deref(Node* dst, Node* src) ...@@ -2161,36 +2437,6 @@ Escape_analysis_assign::assign_deref(Node* dst, Node* src)
// or numeric constants. // or numeric constants.
return; return;
case Expression::EXPRESSION_FIXED_ARRAY_CONSTRUCTION:
case Expression::EXPRESSION_SLICE_CONSTRUCTION:
case Expression::EXPRESSION_STRUCT_CONSTRUCTION:
{
// Dereferencing an array, slice, or struct is like accessing each
// of its values. In this situation, we model the flow from src to
// dst where src is one of the above as a flow from each of src's
// values to dst.
Expression* e = src->expr();
Expression_list* vals = NULL;
if (e->slice_literal() != NULL)
vals = e->slice_literal()->vals();
else if (e->array_literal() != NULL)
vals = e->array_literal()->vals();
else
vals = e->struct_literal()->vals();
if (vals != NULL)
{
for (Expression_list::const_iterator p = vals->begin();
p != vals->end();
++p)
{
if ((*p) != NULL)
this->assign(dst, Node::make_node(*p));
}
}
}
return;
default: default:
break; break;
} }
...@@ -2229,7 +2475,8 @@ Escape_analysis_assign::assign_from_note(std::string* note, ...@@ -2229,7 +2475,8 @@ Escape_analysis_assign::assign_from_note(std::string* note,
} }
if (this->context_->gogo()->debug_escape_level() > 2) if (this->context_->gogo()->debug_escape_level() > 2)
go_inform(src->location(), "assignfromtag:: src= em=%s", go_inform(src->location(), "assignfromtag:: src=%s em=%s",
src->ast_format(context_->gogo()).c_str(),
Escape_note::make_tag(enc).c_str()); Escape_note::make_tag(enc).c_str());
if (enc == Node::ESCAPE_UNKNOWN) if (enc == Node::ESCAPE_UNKNOWN)
...@@ -2282,8 +2529,7 @@ void ...@@ -2282,8 +2529,7 @@ void
Escape_analysis_assign::flows(Node* dst, Node* src) Escape_analysis_assign::flows(Node* dst, Node* src)
{ {
// Don't bother capturing the flow from scalars. // Don't bother capturing the flow from scalars.
if (src->expr() != NULL if (src->type() != NULL && !src->type()->has_pointer())
&& !src->expr()->type()->has_pointer())
return; return;
// Don't confuse a blank identifier with the sink. // Don't confuse a blank identifier with the sink.
...@@ -2294,8 +2540,7 @@ Escape_analysis_assign::flows(Node* dst, Node* src) ...@@ -2294,8 +2540,7 @@ Escape_analysis_assign::flows(Node* dst, Node* src)
Node::Escape_state* src_state = src->state(this->context_, NULL); Node::Escape_state* src_state = src->state(this->context_, NULL);
if (dst == src if (dst == src
|| dst_state == src_state || dst_state == src_state
|| dst_state->flows.find(src) != dst_state->flows.end() || dst_state->flows.find(src) != dst_state->flows.end())
|| src_state->flows.find(dst) != src_state->flows.end())
return; return;
Gogo* gogo = this->context_->gogo(); Gogo* gogo = this->context_->gogo();
...@@ -2331,6 +2576,7 @@ Gogo::assign_connectivity(Escape_context* context, Named_object* fn) ...@@ -2331,6 +2576,7 @@ Gogo::assign_connectivity(Escape_context* context, Named_object* fn)
{ {
Node* res_node = Node::make_node(*p); Node* res_node = Node::make_node(*p);
Node::Escape_state* res_state = res_node->state(context, fn); Node::Escape_state* res_state = res_node->state(context, fn);
res_state->fn = fn;
res_state->loop_depth = 0; res_state->loop_depth = 0;
// If this set of functions is recursive, we lose track of the return values. // If this set of functions is recursive, we lose track of the return values.
...@@ -2359,6 +2605,7 @@ Gogo::assign_connectivity(Escape_context* context, Named_object* fn) ...@@ -2359,6 +2605,7 @@ Gogo::assign_connectivity(Escape_context* context, Named_object* fn)
go_assert(param_no != NULL); go_assert(param_no != NULL);
Node* param_node = Node::make_node(param_no); Node* param_node = Node::make_node(param_no);
Node::Escape_state* param_state = param_node->state(context, fn); Node::Escape_state* param_state = param_node->state(context, fn);
param_state->fn = fn;
param_state->loop_depth = 1; param_state->loop_depth = 1;
if (!p->type()->has_pointer()) if (!p->type()->has_pointer())
...@@ -2493,10 +2740,12 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, ...@@ -2493,10 +2740,12 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src,
else else
dst_no = dst->object(); dst_no = dst->object();
bool dst_is_result = dst_no != NULL && dst_no->is_result_variable(); 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 if (src_is_param
&& dst_is_result && dst_is_result
&& (src->encoding() & ESCAPE_MASK) < int(Node::ESCAPE_SCOPE) && src_state->fn == dst_state->fn
&& (src->encoding() & ESCAPE_MASK) < int(Node::ESCAPE_HEAP)
&& dst->encoding() != Node::ESCAPE_HEAP) && dst->encoding() != Node::ESCAPE_HEAP)
{ {
// This case handles: // This case handles:
...@@ -2547,7 +2796,7 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, ...@@ -2547,7 +2796,7 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src,
// escape from struct. // escape from struct.
if (src_is_param if (src_is_param
&& dst->encoding() == Node::ESCAPE_HEAP && dst->encoding() == Node::ESCAPE_HEAP
&& (src->encoding() & ESCAPE_MASK) < int(Node::ESCAPE_SCOPE) && (src->encoding() & ESCAPE_MASK) < int(Node::ESCAPE_HEAP)
&& level.value() > 0) && level.value() > 0)
{ {
int enc = int enc =
...@@ -2561,16 +2810,17 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, ...@@ -2561,16 +2810,17 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src,
// A src object leaks if its value or address is assigned to a dst object // A src object leaks if its value or address is assigned to a dst object
// in a different scope (at a different loop depth). // in a different scope (at a different loop depth).
Node::Escape_state* dst_state = dst->state(this->context_, NULL);
bool src_leaks = (level.value() <= 0 bool src_leaks = (level.value() <= 0
&& level.suffix_value() <= 0 && level.suffix_value() <= 0
&& dst_state->loop_depth < mod_loop_depth); && 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 // old src encoding, used to prevent duplicate error messages
int osrcesc = src->encoding(); int osrcesc = src->encoding();
if (src_is_param if (src_is_param
&& (src_leaks || dst_state->loop_depth < 0) && (src_leaks || dst_state->loop_depth < 0)
&& (src->encoding() & ESCAPE_MASK) < int(Node::ESCAPE_SCOPE)) && (src->encoding() & ESCAPE_MASK) < int(Node::ESCAPE_HEAP))
{ {
if (level.suffix_value() > 0) if (level.suffix_value() > 0)
{ {
...@@ -2587,7 +2837,7 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, ...@@ -2587,7 +2837,7 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src,
if (debug_level != 0) if (debug_level != 0)
go_inform(src->definition_location(), "leaking param: %s", go_inform(src->definition_location(), "leaking param: %s",
src->ast_format(gogo).c_str()); src->ast_format(gogo).c_str());
src->set_encoding(Node::ESCAPE_SCOPE); src->set_encoding(Node::ESCAPE_HEAP);
} }
} }
else if (src->expr() != NULL) else if (src->expr() != NULL)
...@@ -2623,7 +2873,8 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, ...@@ -2623,7 +2873,8 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src,
src->set_encoding(Node::ESCAPE_HEAP); src->set_encoding(Node::ESCAPE_HEAP);
if (debug_level != 0 && osrcesc != src->encoding()) if (debug_level != 0 && osrcesc != src->encoding())
{ {
if (underlying->var_expression() != NULL) if (underlying->var_expression() != NULL
|| underlying->enclosed_var_expression() != NULL)
go_inform(underlying_node->definition_location(), go_inform(underlying_node->definition_location(),
"moved to heap: %s", "moved to heap: %s",
underlying_node->ast_format(gogo).c_str()); underlying_node->ast_format(gogo).c_str());
...@@ -2675,66 +2926,56 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, ...@@ -2675,66 +2926,56 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src,
else if (e->call_expression() != NULL) else if (e->call_expression() != NULL)
{ {
Call_expression* call = e->call_expression(); Call_expression* call = e->call_expression();
if (call->fn()->func_expression() != NULL) if (call->is_builtin())
{ {
Func_expression* func = call->fn()->func_expression(); Builtin_call_expression* bce = call->builtin_call_expression();
if (func->is_runtime_function()) if (bce->code() == Builtin_call_expression::BUILTIN_APPEND)
{ {
switch (func->runtime_code()) // Propagate escape information to appendee.
{ Expression* appendee = call->args()->front();
case Runtime::GROWSLICE: this->flood(level, dst, Node::make_node(appendee), -1);
{ }
// Propagate escape information to appendee. }
Expression* appendee = call->args()->front(); else if (call->fn()->func_expression() != NULL
this->flood(level, dst, Node::make_node(appendee), -1); && call->fn()->func_expression()->is_runtime_function())
} {
break; switch (call->fn()->func_expression()->runtime_code())
{
case Runtime::MAKECHAN: case Runtime::MAKECHAN:
case Runtime::MAKEMAP: case Runtime::MAKEMAP:
case Runtime::MAKESLICE: case Runtime::MAKESLICE:
case Runtime::MAKESLICE64: case Runtime::MAKESLICE64:
case Runtime::SLICEBYTETOSTRING: if (src_leaks)
case Runtime::SLICERUNETOSTRING: {
case Runtime::STRINGTOSLICEBYTE: src->set_encoding(Node::ESCAPE_HEAP);
case Runtime::STRINGTOSLICERUNE: if (debug_level != 0 && osrcesc != src->encoding())
case Runtime::CONCATSTRINGS: go_inform(src->location(), "%s escapes to heap",
case Runtime::CONCATSTRING2: src->ast_format(gogo).c_str());
case Runtime::CONCATSTRING3: extra_loop_depth = mod_loop_depth;
case Runtime::CONCATSTRING4: }
case Runtime::CONCATSTRING5: break;
case Runtime::CONSTRUCT_MAP:
case Runtime::INTSTRING: default:
case Runtime::REQUIREITAB: break;
// All runtime calls that involve allocation of memory }
// except new. Runtime::NEW gets lowered into an }
// allocation expression. else if (src_state->retvals.size() > 0)
if (src_leaks) {
{ // In this case a link went directly to a call, but should really go
src->set_encoding(Node::ESCAPE_HEAP); // to the dummy .outN outputs that were created for the call that
if (debug_level != 0 && osrcesc != src->encoding()) // themselves link to the inputs with levels adjusted.
go_inform(src->location(), "%s escapes to heap", // See e.g. #10466.
src->ast_format(gogo).c_str()); // This can only happen with functions returning a single result.
extra_loop_depth = mod_loop_depth; go_assert(src_state->retvals.size() == 1);
} if (debug_level > 2)
break; go_inform(src->location(), "[%d] dst %s escwalk replace src: %s with %s",
this->context_->loop_depth(),
default: dst->ast_format(gogo).c_str(),
break; src->ast_format(gogo).c_str(),
} src_state->retvals[0]->ast_format(gogo).c_str());
} src = src_state->retvals[0];
else if (src_leaks src_state = src->state(this->context_, NULL);
&& (func->closure() != NULL }
|| func->bound_method_expression() != NULL))
{
// A closure or bound method; we lost track of actual function
// so if this leaks, this call must be done on the heap.
src->set_encoding(Node::ESCAPE_HEAP);
if (debug_level != 0)
go_inform(src->location(), "%s escapes to heap",
src->ast_format(gogo).c_str());
}
}
} }
else if (e->allocation_expression() != NULL && src_leaks) else if (e->allocation_expression() != NULL && src_leaks)
{ {
...@@ -2743,14 +2984,64 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, ...@@ -2743,14 +2984,64 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src,
if (debug_level != 0 && osrcesc != src->encoding()) if (debug_level != 0 && osrcesc != src->encoding())
go_inform(src->location(), "%s escapes to heap", go_inform(src->location(), "%s escapes to heap",
src->ast_format(gogo).c_str()); 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_inform(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()))
{
// string([]byte), string([]rune), []byte(string), []rune(string), string(rune)
src->set_encoding(Node::ESCAPE_HEAP);
if (debug_level != 0 && osrcesc != src->encoding())
go_inform(src->location(), "%s escapes to heap",
src->ast_format(gogo).c_str());
extra_loop_depth = mod_loop_depth;
}
}
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 else if ((e->field_reference_expression() != NULL
&& e->field_reference_expression()->expr()->unary_expression() == NULL) && e->field_reference_expression()->expr()->unary_expression() == NULL)
|| e->type_guard_expression() != NULL || e->type_guard_expression() != NULL
|| (e->array_index_expression() != NULL || (e->array_index_expression() != NULL
&& e->type()->is_slice_type()) && e->array_index_expression()->end() != NULL)
|| (e->string_index_expression() != NULL || (e->string_index_expression() != NULL
&& e->type()->is_slice_type())) && e->type()->is_string_type()))
{ {
Expression* underlying; Expression* underlying;
if (e->field_reference_expression() != NULL) if (e->field_reference_expression() != NULL)
...@@ -2779,14 +3070,7 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, ...@@ -2779,14 +3070,7 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src,
underlying = underlying->unary_expression()->operand(); underlying = underlying->unary_expression()->operand();
} }
else if (e->array_index_expression() != NULL) else if (e->array_index_expression() != NULL)
{ underlying = e->array_index_expression()->array();
underlying = e->array_index_expression()->array();
if (!underlying->type()->is_slice_type())
{
Node* underlying_node = Node::make_node(underlying);
this->flood(level, dst, underlying_node, 1);
}
}
else if (e->map_index_expression() != NULL) else if (e->map_index_expression() != NULL)
underlying = e->map_index_expression()->map(); underlying = e->map_index_expression()->map();
else else
...@@ -2796,9 +3080,15 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, ...@@ -2796,9 +3080,15 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src,
Node* underlying_node = Node::make_node(underlying); Node* underlying_node = Node::make_node(underlying);
this->flood(level.increase(), dst, underlying_node, -1); this->flood(level.increase(), dst, underlying_node, -1);
} }
else if (e->temporary_reference_expression() != NULL)
// TODO(cmang): Add case for Issue #10466. {
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(); level = level.copy();
for (std::set<Node*>::const_iterator p = src_state->flows.begin(); for (std::set<Node*>::const_iterator p = src_state->flows.begin();
...@@ -2815,6 +3105,13 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, ...@@ -2815,6 +3105,13 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src,
void void
Gogo::propagate_escape(Escape_context* context, Node* dst) 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); Node::Escape_state* state = dst->state(context, NULL);
Gogo* gogo = context->gogo(); Gogo* gogo = context->gogo();
if (gogo->debug_escape_level() > 1) if (gogo->debug_escape_level() > 1)
...@@ -2854,13 +3151,31 @@ Escape_analysis_tag::tag(Named_object* fn) ...@@ -2854,13 +3151,31 @@ Escape_analysis_tag::tag(Named_object* fn)
{ {
// External functions are assumed unsafe // External functions are assumed unsafe
// unless //go:noescape is given before the declaration. // unless //go:noescape is given before the declaration.
if (fn->package() != NULL || !fn->is_function()) if (fn->package() != NULL)
return;
if (fn->is_function_declaration())
{ {
// TODO(cmang): Implement //go:noescape directive for external functions; Function_declaration* fdcl = fn->func_declaration_value();
// mark input parameters as not escaping. if ((fdcl->pragmas() & GOPRAGMA_NOESCAPE) != 0)
return; {
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(); Function_type* fntype = fn->func_value()->type();
Bindings* bindings = fn->func_value()->block()->bindings(); Bindings* bindings = fn->func_value()->block()->bindings();
...@@ -2881,7 +3196,6 @@ Escape_analysis_tag::tag(Named_object* fn) ...@@ -2881,7 +3196,6 @@ Escape_analysis_tag::tag(Named_object* fn)
break; break;
case Node::ESCAPE_HEAP: // flooded, moved to heap. case Node::ESCAPE_HEAP: // flooded, moved to heap.
case Node::ESCAPE_SCOPE: // flooded, value leaves scope.
break; break;
default: default:
...@@ -2898,7 +3212,12 @@ Escape_analysis_tag::tag(Named_object* fn) ...@@ -2898,7 +3212,12 @@ Escape_analysis_tag::tag(Named_object* fn)
++p, ++i) ++p, ++i)
{ {
if (p->name().empty() || Gogo::is_sink_name(p->name())) if (p->name().empty() || Gogo::is_sink_name(p->name()))
continue; {
// 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()); Named_object* param_no = bindings->lookup(p->name());
go_assert(param_no != NULL); go_assert(param_no != NULL);
...@@ -2913,7 +3232,6 @@ Escape_analysis_tag::tag(Named_object* fn) ...@@ -2913,7 +3232,6 @@ Escape_analysis_tag::tag(Named_object* fn)
break; break;
case Node::ESCAPE_HEAP: // flooded, moved to heap. case Node::ESCAPE_HEAP: // flooded, moved to heap.
case Node::ESCAPE_SCOPE: // flooded, value leaves scope.
break; break;
default: default:
......
...@@ -51,7 +51,7 @@ public: ...@@ -51,7 +51,7 @@ public:
suffix_value() const suffix_value() const
{ return this->suffix_value_; } { return this->suffix_value_; }
// Increase the level because a node is referenced. // Increase the level because a node is dereferenced.
Level Level
increase() const increase() const
{ {
...@@ -61,7 +61,7 @@ public: ...@@ -61,7 +61,7 @@ public:
return Level(this->value_ + 1, this->suffix_value_ + 1); return Level(this->value_ + 1, this->suffix_value_ + 1);
} }
// Decrease the level because a node is dereferenced. // Decrease the level because a node is referenced.
Level Level
decrease() const decrease() const
{ {
...@@ -104,9 +104,9 @@ public: ...@@ -104,9 +104,9 @@ public:
} }
private: private:
// The sum of all indirects (-1) and references (+1) applied to a Node. // The sum of all references (-1) and indirects (+1) applied to a Node.
int value_; int value_;
// The sum of all indirects (-1) abd references (+1) applied to a copied Node. // The sum of all references (-1) abd indirects (+1) applied to a copied Node.
int suffix_value_; int suffix_value_;
}; };
...@@ -123,7 +123,10 @@ class Node ...@@ -123,7 +123,10 @@ class Node
{ {
NODE_OBJECT, NODE_OBJECT,
NODE_EXPRESSION, NODE_EXPRESSION,
NODE_STATEMENT NODE_STATEMENT,
// A "fake" node that models the indirection of its child node.
// This node does not correspond to an AST node.
NODE_INDIRECT
}; };
// The state necessary to keep track of how a node escapes. // The state necessary to keep track of how a node escapes.
...@@ -161,11 +164,6 @@ class Node ...@@ -161,11 +164,6 @@ class Node
ESCAPE_NONE, ESCAPE_NONE,
// Is returned or reachable from a return statement. // Is returned or reachable from a return statement.
ESCAPE_RETURN, ESCAPE_RETURN,
// Allocated in an inner loop, assigned to an outer loop,
// which allows construction of non-escaping but arbitrarily large linked
// data structures (i.e., not eligible for allocation in a fixed-size stack
// stack frame).
ESCAPE_SCOPE,
// Reachable from the heap. // Reachable from the heap.
ESCAPE_HEAP, ESCAPE_HEAP,
// By construction will not escape. // By construction will not escape.
...@@ -174,17 +172,25 @@ class Node ...@@ -174,17 +172,25 @@ class Node
// Multiple constructors for each classification. // Multiple constructors for each classification.
Node(Named_object* no) Node(Named_object* no)
: classification_(NODE_OBJECT), state_(NULL), encoding_(ESCAPE_UNKNOWN) : classification_(NODE_OBJECT), state_(NULL), encoding_(ESCAPE_UNKNOWN),
child_(NULL)
{ this->u_.object_val = no; } { this->u_.object_val = no; }
Node(Expression* e) Node(Expression* e)
: classification_(NODE_EXPRESSION), state_(NULL), encoding_(ESCAPE_UNKNOWN) : classification_(NODE_EXPRESSION), state_(NULL), encoding_(ESCAPE_UNKNOWN),
child_(NULL)
{ this->u_.expression_val = e; } { this->u_.expression_val = e; }
Node(Statement* s) Node(Statement* s)
: classification_(NODE_STATEMENT), state_(NULL), encoding_(ESCAPE_UNKNOWN) : classification_(NODE_STATEMENT), state_(NULL), encoding_(ESCAPE_UNKNOWN),
child_(NULL)
{ this->u_.statement_val = s; } { this->u_.statement_val = s; }
Node(Node *n)
: classification_(NODE_INDIRECT), state_(NULL), encoding_(ESCAPE_UNKNOWN),
child_(n)
{}
// Return this node's type. // Return this node's type.
Type* Type*
type() const; type() const;
...@@ -203,7 +209,7 @@ class Node ...@@ -203,7 +209,7 @@ class Node
// Return this node's detailed format string. // Return this node's detailed format string.
std::string std::string
details() const; details();
std::string std::string
op_format() const; op_format() const;
...@@ -214,8 +220,7 @@ class Node ...@@ -214,8 +220,7 @@ class Node
// Return this node's escape encoding. // Return this node's escape encoding.
int int
encoding() const encoding();
{ return this->encoding_; }
// Set the node's escape encoding. // Set the node's escape encoding.
void void
...@@ -252,6 +257,22 @@ class Node ...@@ -252,6 +257,22 @@ class Node
: NULL); : NULL);
} }
bool
is_indirect() const
{ return this->classification_ == NODE_INDIRECT; }
// Return its child node.
// Child node is used only in indirect node, and in expression node
// representing slicing an array.
Node*
child() const
{ return this->child_; }
// Set the child node.
void
set_child(Node* n)
{ this->child_ = n; }
// Static creation methods for each value supported in the union. // Static creation methods for each value supported in the union.
static Node* static Node*
make_node(Named_object*); make_node(Named_object*);
...@@ -262,6 +283,9 @@ class Node ...@@ -262,6 +283,9 @@ class Node
static Node* static Node*
make_node(Statement*); make_node(Statement*);
static Node*
make_indirect_node(Node*);
// Return the maximum of an existing escape encoding E and a new // Return the maximum of an existing escape encoding E and a new
// escape type. // escape type.
static int static int
...@@ -294,6 +318,10 @@ class Node ...@@ -294,6 +318,10 @@ class Node
// | Escapement_encoding: ESCAPE_BITS | // | Escapement_encoding: ESCAPE_BITS |
int encoding_; int encoding_;
// Child node, used only in indirect node, and expression node representing
// slicing an array.
Node* child_;
// Cache all the Nodes created via Node::make_node to make the API simpler. // Cache all the Nodes created via Node::make_node to make the API simpler.
static std::map<Named_object*, Node*> objects; static std::map<Named_object*, Node*> objects;
static std::map<Expression*, Node*> expressions; static std::map<Expression*, Node*> expressions;
......
...@@ -7113,114 +7113,6 @@ Expression::make_bound_method(Expression* expr, const Method* method, ...@@ -7113,114 +7113,6 @@ Expression::make_bound_method(Expression* expr, const Method* method,
// Class Builtin_call_expression. This is used for a call to a // Class Builtin_call_expression. This is used for a call to a
// builtin function. // builtin function.
class Builtin_call_expression : public Call_expression
{
public:
Builtin_call_expression(Gogo* gogo, Expression* fn, Expression_list* args,
bool is_varargs, Location location);
protected:
// This overrides Call_expression::do_lower.
Expression*
do_lower(Gogo*, Named_object*, Statement_inserter*, int);
Expression*
do_flatten(Gogo*, Named_object*, Statement_inserter*);
bool
do_is_constant() const;
bool
do_numeric_constant_value(Numeric_constant*) const;
bool
do_discarding_value();
Type*
do_type();
void
do_determine_type(const Type_context*);
void
do_check_types(Gogo*);
Expression*
do_copy();
Bexpression*
do_get_backend(Translate_context*);
void
do_export(Export*) const;
virtual bool
do_is_recover_call() const;
virtual void
do_set_recover_arg(Expression*);
private:
// The builtin functions.
enum Builtin_function_code
{
BUILTIN_INVALID,
// Predeclared builtin functions.
BUILTIN_APPEND,
BUILTIN_CAP,
BUILTIN_CLOSE,
BUILTIN_COMPLEX,
BUILTIN_COPY,
BUILTIN_DELETE,
BUILTIN_IMAG,
BUILTIN_LEN,
BUILTIN_MAKE,
BUILTIN_NEW,
BUILTIN_PANIC,
BUILTIN_PRINT,
BUILTIN_PRINTLN,
BUILTIN_REAL,
BUILTIN_RECOVER,
// Builtin functions from the unsafe package.
BUILTIN_ALIGNOF,
BUILTIN_OFFSETOF,
BUILTIN_SIZEOF
};
Expression*
one_arg() const;
bool
check_one_arg();
static Type*
real_imag_type(Type*);
static Type*
complex_type(Type*);
Expression*
lower_make(Statement_inserter*);
Expression* flatten_append(Gogo*, Named_object*, Statement_inserter*);
bool
check_int_value(Expression*, bool is_length, bool* small);
// A pointer back to the general IR structure. This avoids a global
// variable, or passing it around everywhere.
Gogo* gogo_;
// The builtin function being called.
Builtin_function_code code_;
// Used to stop endless loops when the length of an array uses len
// or cap of the array itself.
mutable bool seen_;
// Whether the argument is set for calls to BUILTIN_RECOVER.
bool recover_arg_is_set_;
};
Builtin_call_expression::Builtin_call_expression(Gogo* gogo, Builtin_call_expression::Builtin_call_expression(Gogo* gogo,
Expression* fn, Expression* fn,
Expression_list* args, Expression_list* args,
......
...@@ -39,6 +39,7 @@ class Unary_expression; ...@@ -39,6 +39,7 @@ class Unary_expression;
class Binary_expression; class Binary_expression;
class String_concat_expression; class String_concat_expression;
class Call_expression; class Call_expression;
class Builtin_call_expression;
class Call_result_expression; class Call_result_expression;
class Func_expression; class Func_expression;
class Func_descriptor_expression; class Func_descriptor_expression;
...@@ -2246,6 +2247,15 @@ class Call_expression : public Expression ...@@ -2246,6 +2247,15 @@ class Call_expression : public Expression
set_is_multi_value_arg() set_is_multi_value_arg()
{ this->is_multi_value_arg_ = true; } { this->is_multi_value_arg_ = true; }
// Whether this is a call to builtin function.
virtual bool
is_builtin()
{ return false; }
// Convert to a Builtin_call_expression, or return NULL.
inline Builtin_call_expression*
builtin_call_expression();
protected: protected:
int int
do_traverse(Traverse*); do_traverse(Traverse*);
...@@ -2351,6 +2361,133 @@ class Call_expression : public Expression ...@@ -2351,6 +2361,133 @@ class Call_expression : public Expression
bool is_flattened_; bool is_flattened_;
}; };
// A call expression to a builtin function.
class Builtin_call_expression : public Call_expression
{
public:
Builtin_call_expression(Gogo* gogo, Expression* fn, Expression_list* args,
bool is_varargs, Location location);
// The builtin functions.
enum Builtin_function_code
{
BUILTIN_INVALID,
// Predeclared builtin functions.
BUILTIN_APPEND,
BUILTIN_CAP,
BUILTIN_CLOSE,
BUILTIN_COMPLEX,
BUILTIN_COPY,
BUILTIN_DELETE,
BUILTIN_IMAG,
BUILTIN_LEN,
BUILTIN_MAKE,
BUILTIN_NEW,
BUILTIN_PANIC,
BUILTIN_PRINT,
BUILTIN_PRINTLN,
BUILTIN_REAL,
BUILTIN_RECOVER,
// Builtin functions from the unsafe package.
BUILTIN_ALIGNOF,
BUILTIN_OFFSETOF,
BUILTIN_SIZEOF
};
Builtin_function_code
code()
{ return this->code_; }
// This overrides Call_expression::is_builtin.
bool
is_builtin()
{ return true; }
protected:
// This overrides Call_expression::do_lower.
Expression*
do_lower(Gogo*, Named_object*, Statement_inserter*, int);
Expression*
do_flatten(Gogo*, Named_object*, Statement_inserter*);
bool
do_is_constant() const;
bool
do_numeric_constant_value(Numeric_constant*) const;
bool
do_discarding_value();
Type*
do_type();
void
do_determine_type(const Type_context*);
void
do_check_types(Gogo*);
Expression*
do_copy();
Bexpression*
do_get_backend(Translate_context*);
void
do_export(Export*) const;
virtual bool
do_is_recover_call() const;
virtual void
do_set_recover_arg(Expression*);
private:
Expression*
one_arg() const;
bool
check_one_arg();
static Type*
real_imag_type(Type*);
static Type*
complex_type(Type*);
Expression*
lower_make(Statement_inserter*);
Expression* flatten_append(Gogo*, Named_object*, Statement_inserter*);
bool
check_int_value(Expression*, bool is_length, bool* small);
// A pointer back to the general IR structure. This avoids a global
// variable, or passing it around everywhere.
Gogo* gogo_;
// The builtin function being called.
Builtin_function_code code_;
// Used to stop endless loops when the length of an array uses len
// or cap of the array itself.
mutable bool seen_;
// Whether the argument is set for calls to BUILTIN_RECOVER.
bool recover_arg_is_set_;
};
inline Builtin_call_expression*
Call_expression::builtin_call_expression()
{
return (this->is_builtin()
? static_cast<Builtin_call_expression*>(this)
: NULL);
}
// A single result from a call which returns multiple results. // A single result from a call which returns multiple results.
class Call_result_expression : public Expression class Call_result_expression : public Expression
......
...@@ -1577,6 +1577,11 @@ class Function_declaration ...@@ -1577,6 +1577,11 @@ class Function_declaration
set_asm_name(const std::string& asm_name) set_asm_name(const std::string& asm_name)
{ this->asm_name_ = asm_name; } { this->asm_name_ = asm_name; }
// Return the pragmas for this function.
unsigned int
pragmas() const
{ return this->pragmas_; }
// Set the pragmas for this function. // Set the pragmas for this function.
void void
set_pragmas(unsigned int pragmas) set_pragmas(unsigned int pragmas)
......
...@@ -438,26 +438,18 @@ Runtime::name_to_code(const std::string& name) ...@@ -438,26 +438,18 @@ Runtime::name_to_code(const std::string& name)
{ {
Function code = Runtime::NUMBER_OF_FUNCTIONS; Function code = Runtime::NUMBER_OF_FUNCTIONS;
// Aliases seen in function declaration code. // Look through the known names for a match.
// TODO(cmang): Add other aliases. for (size_t i = 0; i < Runtime::NUMBER_OF_FUNCTIONS; i++)
if (name == "new")
code = Runtime::NEW;
else if (name == "close")
code = Runtime::CLOSE;
else if (name == "copy")
code = Runtime::SLICECOPY;
else if (name == "append")
code = Runtime::GROWSLICE;
else if (name == "delete")
code = Runtime::MAPDELETE;
else
{ {
// Look through the known names for a match. const char* runtime_function_name = runtime_functions[i].name;
for (size_t i = 0; i < Runtime::NUMBER_OF_FUNCTIONS; i++) if (strcmp(runtime_function_name, name.c_str()) == 0)
{ code = static_cast<Runtime::Function>(i);
if (strcmp(runtime_functions[i].name, name.c_str()) == 0) // The names in the table have "runtime." prefix. We may be
code = static_cast<Runtime::Function>(i); // called with a name without the prefix. Try matching
} // without the prefix as well.
if (strncmp(runtime_function_name, "runtime.", 8) == 0
&& strcmp(runtime_function_name + 8, name.c_str()) == 0)
code = static_cast<Runtime::Function>(i);
} }
return code; return code;
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment