#include "expressions.h"
#include "statements.h"
#include "escape.h"
+#include "lex.h"
#include "ast-dump.h"
#include "go-optimize.h"
#include "go-diagnostics.h"
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
+ return this->child()->type()->deref();
+ }
+ else if (this->statement() != NULL
+ && this->statement()->temporary_statement() != NULL)
+ return this->statement()->temporary_statement()->type();
else
return 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();
}
}
Ast_dump_context::dump_to_stream(this->expr(), &ss);
}
- else
+ else if (this->statement() != NULL)
{
Statement* s = this->statement();
Goto_unnamed_statement* unnamed = s->goto_unnamed_statement();
}
}
}
- 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());
// This is an implementation of gc's Jconv with obj.FmtShort.
std::string
-Node::details() const
+Node::details()
{
std::stringstream details;
details << " esc(h)";
break;
- case Node::ESCAPE_SCOPE:
- details << " esc(s)";
- break;
-
case Node::ESCAPE_NONE:
details << " esc(no)";
break;
break;
}
}
+ if (this->is_indirect())
+ op << "*";
return op.str();
}
return this->state_;
}
-void
-Node::set_encoding(int enc)
+int
+Node::encoding()
{
- this->encoding_ = enc;
if (this->expr() != 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();
- 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);
+ }
}
}
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 ((*p)->numeric_constant_value(&nc)
+ if (e->numeric_constant_value(&nc)
&& nc.to_unsigned_long(&v) == Numeric_constant::NC_UL_VALID)
big = big || v >= (1 << 16);
}
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
// (and its additional parameter flow flags) and a new escape type.
int
Node::max_encoding(int e, int etype)
{
- if ((e & ESCAPE_MASK) >= etype)
+ if ((e & ESCAPE_MASK) > etype)
return e;
if (etype == Node::ESCAPE_NONE || etype == Node::ESCAPE_RETURN)
return (e & ~ESCAPE_MASK) | etype;
{
// The sink always escapes to heap and strictly lives outside of the
// current function i.e. loop_depth == -1.
- this->sink_->set_encoding(Node::ESCAPE_HEAP);
Node::Escape_state* state = this->sink_->state(this, NULL);
state->loop_depth = -1;
}
return;
Node::Escape_state* state = n->state(this, NULL);
+ state->retvals.clear();
Location loc = n->location();
int i = 0;
Named_object::make_variable(buf, NULL, dummy_var);
Node* dummy_node = Node::make_node(dummy_no);
// 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.
state->retvals.push_back(dummy_node);
// 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*
Escape_context::add_dereference(Node* n)
{
- // Just return the original node if we can't add an indirection.
- if (n->object() != NULL || n->statement() != NULL)
- return n;
-
- Node* ind = Node::make_node(n->expr()->deref());
- // Initialize the state if this node doesn't already exist.
- ind->state(this, NULL);
+ 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;
}
// Propagate levels across each dst. This is the flood phase.
std::set<Node*> dsts = context->dsts();
+ std::unordered_map<Node*, int> escapes;
for (std::set<Node*>::iterator n = dsts.begin();
n != dsts.end();
++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.
for (std::vector<Named_object*>::iterator fn = stack.begin();
strip_packed_prefix(this, debug_function_name(state->fn)).c_str(),
(*n)->ast_format(this).c_str());
}
- // TODO(cmang): Which objects in context->noesc actually don't escape.
}
delete context;
}
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() && fn->func_value()->enclosing() == NULL)
+ || fn->is_function_declaration()))
{
bool recursive = min == id;
std::vector<Named_object*> group;
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
}
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:
{
Label_statement* label_stmt = s->label_statement();
// 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.
- // TODO(cmang): Verify.
break;
case Statement::STATEMENT_ASSIGNMENT:
{
Assignment_statement* assn = s->assignment_statement();
- Node* lhs = Node::make_node(assn->lhs());
- Node* rhs = Node::make_node(assn->rhs());
-
- // TODO(cmang): Add special case for escape analysis no-op:
- // func (b *Buffer) Foo() {
- // n, m := ...
- // b.buf = b.buf[n:m]
- // }
- // This is okay for now, it just means b escapes; it is conservative.
- this->assign(lhs, rhs);
+ 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;
}
break;
- // TODO(cmang): Associate returned values with dummy return nodes.
-
default:
break;
}
&& n->is_big(this->context_))
{
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);
(*pexpr)->address_taken(true);
this->assign(this->context_->sink(), n);
case Expression::EXPRESSION_CALL:
{
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();
if (fe != NULL && fe->is_runtime_function())
{
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::MAKEMAP:
case Runtime::MAKESLICE:
case Runtime::MAKESLICE64:
- case Runtime::SLICEBYTETOSTRING:
- 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);
- }
+ 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::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:
break;
}
}
+ else
+ this->call(call);
}
break;
case Expression::EXPRESSION_ALLOCATION:
- {
- // Same as above; this is Runtime::NEW.
- Node* alloc_node = Node::make_node(*pexpr);
- this->context_->track(alloc_node);
- }
+ // 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);
var = operand->var_expression()->named_object();
else if (operand->enclosed_var_expression() != NULL)
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)
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:
break;
}
}
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;
}
Function* f = fn->named_object()->func_value();
const Bindings* callee_bindings = f->block()->bindings();
-
- const Typed_identifier_list* results = fntype->results();
+ 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 (Typed_identifier_list::const_iterator p1 = results->begin();
+ for (Function::Results::const_iterator p1 = results->begin();
p1 != results->end();
++p1)
{
- if (p1->name().empty() || Gogo::is_sink_name(p1->name()))
- continue;
-
- Named_object* result_no =
- callee_bindings->lookup_local(p1->name());
- go_assert(result_no != NULL);
- Node* result_node = Node::make_node(result_no);
+ 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()
- && fntype->receiver()->type()->has_pointer())
+ if (fntype->is_method())
{
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
{
callee_bindings->lookup_local(fntype->receiver()->name());
go_assert(rcvr_no != NULL);
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;
}
// Receiver.
std::vector<Node*>::iterator p = arg_nodes.begin();
if (fntype->is_method()
- && fntype->receiver()->type()->has_pointer()
&& 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()->unary_expression() != 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
- // 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 does not escape.
- }
+ && (*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(),
this->context_->track(*p);
}
- // TODO(cmang): Special care for varargs parameter?
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()))
+ && !call->is_deferred()
+ && !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?
}
}
}
src->ast_format(gogo).c_str(), src->details().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.
// Replace DST with this->context_->sink() if we can't track it.
}
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:
// TODO(cmang): Add debugging info here: only a few expressions
// should leave DST unmodified.
}
}
- 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();
switch (e->classification())
{
case Expression::EXPRESSION_VAR_REFERENCE:
+ case Expression::EXPRESSION_ENCLOSED_VAR_REFERENCE:
// DST = var
case Expression::EXPRESSION_HEAP:
// DST = &T{...}.
// DST = new(T).
case Expression::EXPRESSION_BOUND_METHOD:
// DST = x.M.
+ case Expression::EXPRESSION_STRING_CONCAT:
+ // DST = str1 + str2
this->flows(dst, 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:
{
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::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::MAKEMAP:
case Runtime::MAKESLICE:
case Runtime::MAKESLICE64:
// DST = make(...).
- case Runtime::SLICEBYTETOSTRING:
- // DST = string([]byte{...}).
- 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;
+ this->flows(dst, src);
+ break;
default:
break;
break;
}
- // TODO(cmang): Handle case from issue 4529.
- // 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);
+ // 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)
- {
- // 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);
- }
+ 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()))
+ {
+ // 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:
{
// A non-pointer can't escape from a struct.
}
// Fall through.
- case Expression::EXPRESSION_CONVERSION:
case Expression::EXPRESSION_TYPE_GUARD:
case Expression::EXPRESSION_ARRAY_INDEX:
case Expression::EXPRESSION_STRING_INDEX:
break;
}
}
- else if (e->conversion_expression() != NULL)
- left = e->conversion_expression()->expr();
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 (e->type()->is_slice_type())
- left = aie->array();
+ 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.
else if (e->string_index_expression() != NULL)
{
String_index_expression* sie = e->string_index_expression();
- if (e->type()->is_slice_type())
+ if (e->type()->is_string_type())
+ // slicing
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
{
this->flows(dst, src);
case OPERATOR_PLUS:
case OPERATOR_MINUS:
case OPERATOR_XOR:
+ case OPERATOR_OR:
case OPERATOR_MULT:
case OPERATOR_DIV:
case OPERATOR_MOD:
case Expression::EXPRESSION_TEMPORARY_REFERENCE:
{
Statement* temp = e->temporary_reference_expression()->statement();
- if (temp != NULL
- && temp->temporary_statement()->init() != NULL)
- {
- Expression* init = temp->temporary_statement()->init();
- Node* init_node = Node::make_node(init);
- this->assign(dst, init_node);
- }
+ this->assign(dst, Node::make_node(temp));
}
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.
// or numeric constants.
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:
break;
}
}
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());
if (enc == Node::ESCAPE_UNKNOWN)
Escape_analysis_assign::flows(Node* dst, Node* src)
{
// Don't bother capturing the flow from scalars.
- if (src->expr() != NULL
- && !src->expr()->type()->has_pointer())
+ if (src->type() != NULL && !src->type()->has_pointer())
return;
// Don't confuse a blank identifier with the sink.
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()
- || src_state->flows.find(dst) != src_state->flows.end())
+ || dst_state->flows.find(src) != dst_state->flows.end())
return;
Gogo* gogo = this->context_->gogo();
{
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.
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())
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->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)
{
// This case handles:
// escape from struct.
if (src_is_param
&& dst->encoding() == Node::ESCAPE_HEAP
- && (src->encoding() & ESCAPE_MASK) < int(Node::ESCAPE_SCOPE)
+ && (src->encoding() & ESCAPE_MASK) < int(Node::ESCAPE_HEAP)
&& level.value() > 0)
{
int enc =
// A src object leaks if its value or address is assigned to a dst object
// 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
&& 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_SCOPE))
+ && (src->encoding() & ESCAPE_MASK) < int(Node::ESCAPE_HEAP))
{
if (level.suffix_value() > 0)
{
if (debug_level != 0)
go_inform(src->definition_location(), "leaking param: %s",
src->ast_format(gogo).c_str());
- src->set_encoding(Node::ESCAPE_SCOPE);
+ src->set_encoding(Node::ESCAPE_HEAP);
}
}
else if (src->expr() != NULL)
src->set_encoding(Node::ESCAPE_HEAP);
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(),
"moved to heap: %s",
underlying_node->ast_format(gogo).c_str());
else if (e->call_expression() != NULL)
{
Call_expression* call = e->call_expression();
- if (call->fn()->func_expression() != NULL)
- {
- Func_expression* func = call->fn()->func_expression();
- if (func->is_runtime_function())
- {
- switch (func->runtime_code())
- {
- case Runtime::GROWSLICE:
- {
- // Propagate escape information to appendee.
- Expression* appendee = call->args()->front();
- this->flood(level, dst, Node::make_node(appendee), -1);
- }
- break;
-
- case Runtime::MAKECHAN:
- case Runtime::MAKEMAP:
- case Runtime::MAKESLICE:
- case Runtime::MAKESLICE64:
- case Runtime::SLICEBYTETOSTRING:
- 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:
- case Runtime::REQUIREITAB:
- // All runtime calls that involve allocation of memory
- // except new. Runtime::NEW gets lowered into an
- // allocation expression.
- if (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;
- }
- break;
-
- default:
- break;
- }
- }
- else if (src_leaks
- && (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());
- }
- }
+ 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::MAKEMAP:
+ case Runtime::MAKESLICE:
+ case Runtime::MAKESLICE64:
+ if (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;
+ }
+ 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_inform(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)
{
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->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
&& e->field_reference_expression()->expr()->unary_expression() == NULL)
|| e->type_guard_expression() != NULL
|| (e->array_index_expression() != NULL
- && e->type()->is_slice_type())
+ && e->array_index_expression()->end() != NULL)
|| (e->string_index_expression() != NULL
- && e->type()->is_slice_type()))
+ && e->type()->is_string_type()))
{
Expression* underlying;
if (e->field_reference_expression() != NULL)
underlying = underlying->unary_expression()->operand();
}
else if (e->array_index_expression() != NULL)
- {
- 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);
- }
- }
+ underlying = e->array_index_expression()->array();
else if (e->map_index_expression() != NULL)
underlying = e->map_index_expression()->map();
else
Node* underlying_node = Node::make_node(underlying);
this->flood(level.increase(), dst, underlying_node, -1);
}
-
- // TODO(cmang): Add case for Issue #10466.
+ 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();
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)
{
// External functions are assumed unsafe
// 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;
- // mark input parameters as not escaping.
- return;
+ 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();
break;
case Node::ESCAPE_HEAP: // flooded, moved to heap.
- case Node::ESCAPE_SCOPE: // flooded, value leaves scope.
break;
default:
++p, ++i)
{
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());
go_assert(param_no != NULL);
break;
case Node::ESCAPE_HEAP: // flooded, moved to heap.
- case Node::ESCAPE_SCOPE: // flooded, value leaves scope.
break;
default: