diff options
Diffstat (limited to 'gcc/go')
-rw-r--r-- | gcc/go/gofrontend/MERGE | 2 | ||||
-rw-r--r-- | gcc/go/gofrontend/escape.cc | 1062 | ||||
-rw-r--r-- | gcc/go/gofrontend/escape.h | 60 | ||||
-rw-r--r-- | gcc/go/gofrontend/expressions.cc | 108 | ||||
-rw-r--r-- | gcc/go/gofrontend/expressions.h | 137 | ||||
-rw-r--r-- | gcc/go/gofrontend/gogo.h | 5 | ||||
-rw-r--r-- | gcc/go/gofrontend/runtime.cc | 30 |
7 files changed, 888 insertions, 516 deletions
diff --git a/gcc/go/gofrontend/MERGE b/gcc/go/gofrontend/MERGE index e3908c1..b81922e 100644 --- a/gcc/go/gofrontend/MERGE +++ b/gcc/go/gofrontend/MERGE @@ -1,4 +1,4 @@ -66de779004bdaafefc27e4132324a47d86a0f122 +83fc0e440b8c151edc5b1c67006257aad522ca04 The first line of this file holds the git revision number of the last merge done from the gofrontend repository. diff --git a/gcc/go/gofrontend/escape.cc b/gcc/go/gofrontend/escape.cc index abdca82..8706808 100644 --- a/gcc/go/gofrontend/escape.cc +++ b/gcc/go/gofrontend/escape.cc @@ -15,6 +15,7 @@ #include "expressions.h" #include "statements.h" #include "escape.h" +#include "lex.h" #include "ast-dump.h" #include "go-optimize.h" #include "go-diagnostics.h" @@ -34,6 +35,19 @@ Node::type() const 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; } @@ -49,6 +63,8 @@ Node::location() const 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(); } @@ -133,7 +149,7 @@ Node::ast_format(Gogo* gogo) const } 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(); @@ -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()); @@ -175,7 +208,7 @@ Node::ast_format(Gogo* gogo) const // This is an implementation of gc's Jconv with obj.FmtShort. std::string -Node::details() const +Node::details() { std::stringstream details; @@ -258,10 +291,6 @@ Node::details() const details << " esc(h)"; break; - case Node::ESCAPE_SCOPE: - details << " esc(s)"; - break; - case Node::ESCAPE_NONE: details << " esc(no)"; break; @@ -383,6 +412,8 @@ Node::op_format() const break; } } + if (this->is_indirect()) + op << "*"; return op.str(); } @@ -413,16 +444,40 @@ Node::state(Escape_context* context, Named_object* fn) 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); + } } } @@ -461,9 +516,17 @@ Node::is_big(Escape_context* context) const 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); } @@ -531,13 +594,22 @@ Node::make_node(Statement* s) 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; @@ -587,7 +659,6 @@ Escape_context::Escape_context(Gogo* gogo, bool recursive) { // 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; } @@ -656,6 +727,7 @@ Escape_context::init_retvals(Node* n, Function_type* fntype) return; Node::Escape_state* state = n->state(this, NULL); + state->retvals.clear(); Location loc = n->location(); int i = 0; @@ -672,7 +744,8 @@ Escape_context::init_retvals(Node* n, Function_type* fntype) 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); @@ -681,20 +754,30 @@ Escape_context::init_retvals(Node* n, Function_type* fntype) // 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; } @@ -775,10 +858,43 @@ Gogo::analyze_escape() // 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(); @@ -799,7 +915,6 @@ Gogo::analyze_escape() strip_packed_prefix(this, debug_function_name(state->fn)).c_str(), (*n)->ast_format(this).c_str()); } - // TODO(cmang): Which objects in context->noesc actually don't escape. } delete context; } @@ -893,8 +1008,8 @@ Escape_analysis_discover::visit(Named_object* fn) 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; @@ -1072,6 +1187,38 @@ private: 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 @@ -1121,6 +1268,14 @@ Escape_analysis_assign::statement(Block*, size_t*, Statement* s) } 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(); @@ -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 // 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; @@ -1199,8 +1368,6 @@ Escape_analysis_assign::statement(Block*, size_t*, Statement* s) } break; - // TODO(cmang): Associate returned values with dummy return nodes. - default: break; } @@ -1221,7 +1388,15 @@ Escape_analysis_assign::expression(Expression** pexpr) && 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); @@ -1244,100 +1419,158 @@ Escape_analysis_assign::expression(Expression** pexpr) 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); @@ -1478,26 +1711,6 @@ Escape_analysis_assign::expression(Expression** pexpr) 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; @@ -1525,6 +1738,21 @@ Escape_analysis_assign::expression(Expression** pexpr) } 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; } @@ -1587,6 +1815,17 @@ Escape_analysis_assign::call(Call_expression* call) } 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; } @@ -1601,33 +1840,26 @@ Escape_analysis_assign::call(Call_expression* call) 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 { @@ -1635,7 +1867,17 @@ Escape_analysis_assign::call(Call_expression* call) 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; } @@ -1688,21 +1930,20 @@ Escape_analysis_assign::call(Call_expression* call) // 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(), @@ -1732,7 +1973,6 @@ Escape_analysis_assign::call(Call_expression* call) this->context_->track(*p); } - // TODO(cmang): Special care for varargs parameter? Type* t = pn->type(); if (t != NULL && t->has_pointer()) @@ -1740,10 +1980,12 @@ Escape_analysis_assign::call(Call_expression* call) 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? } } } @@ -1778,7 +2020,10 @@ Escape_analysis_assign::assign(Node* dst, Node* src) 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. @@ -1845,6 +2090,14 @@ Escape_analysis_assign::assign(Node* dst, Node* src) } 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. @@ -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(); switch (e->classification()) { case Expression::EXPRESSION_VAR_REFERENCE: + case Expression::EXPRESSION_ENCLOSED_VAR_REFERENCE: // DST = var case Expression::EXPRESSION_HEAP: // DST = &T{...}. @@ -1872,6 +2130,8 @@ Escape_analysis_assign::assign(Node* dst, Node* src) // DST = new(T). case Expression::EXPRESSION_BOUND_METHOD: // DST = x.M. + case Expression::EXPRESSION_STRING_CONCAT: + // DST = str1 + str2 this->flows(dst, src); break; @@ -1883,73 +2143,34 @@ Escape_analysis_assign::assign(Node* dst, Node* src) } 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; @@ -1969,28 +2190,85 @@ Escape_analysis_assign::assign(Node* dst, Node* src) 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. @@ -1999,7 +2277,6 @@ Escape_analysis_assign::assign(Node* dst, Node* src) } // Fall through. - case Expression::EXPRESSION_CONVERSION: case Expression::EXPRESSION_TYPE_GUARD: case Expression::EXPRESSION_ARRAY_INDEX: case Expression::EXPRESSION_STRING_INDEX: @@ -2016,15 +2293,23 @@ Escape_analysis_assign::assign(Node* dst, Node* src) 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. @@ -2041,15 +2326,9 @@ Escape_analysis_assign::assign(Node* dst, Node* src) 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); @@ -2072,6 +2351,7 @@ Escape_analysis_assign::assign(Node* dst, Node* src) case OPERATOR_PLUS: case OPERATOR_MINUS: case OPERATOR_XOR: + case OPERATOR_OR: case OPERATOR_MULT: case OPERATOR_DIV: case OPERATOR_MOD: @@ -2123,13 +2403,7 @@ Escape_analysis_assign::assign(Node* dst, Node* src) 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; @@ -2139,6 +2413,8 @@ Escape_analysis_assign::assign(Node* dst, Node* src) break; } } + else if (src->statement() != NULL && src->statement()->temporary_statement() != NULL) + this->flows(dst, src); } // Model the assignment of DST to an indirection of SRC. @@ -2161,36 +2437,6 @@ Escape_analysis_assign::assign_deref(Node* dst, Node* 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; } @@ -2229,7 +2475,8 @@ Escape_analysis_assign::assign_from_note(std::string* note, } 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) @@ -2282,8 +2529,7 @@ void 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. @@ -2294,8 +2540,7 @@ Escape_analysis_assign::flows(Node* dst, Node* src) 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(); @@ -2331,6 +2576,7 @@ Gogo::assign_connectivity(Escape_context* context, Named_object* fn) { 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. @@ -2359,6 +2605,7 @@ Gogo::assign_connectivity(Escape_context* context, Named_object* fn) 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()) @@ -2493,10 +2740,12 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, 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: @@ -2547,7 +2796,7 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, // 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 = @@ -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 // 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) { @@ -2587,7 +2837,7 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, 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) @@ -2623,7 +2873,8 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, 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()); @@ -2675,66 +2926,56 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, 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) { @@ -2743,14 +2984,64 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, 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) @@ -2779,14 +3070,7 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, 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 @@ -2796,9 +3080,15 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, 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(); @@ -2815,6 +3105,13 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, 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) @@ -2854,13 +3151,31 @@ Escape_analysis_tag::tag(Named_object* fn) { // 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(); @@ -2881,7 +3196,6 @@ Escape_analysis_tag::tag(Named_object* fn) break; case Node::ESCAPE_HEAP: // flooded, moved to heap. - case Node::ESCAPE_SCOPE: // flooded, value leaves scope. break; default: @@ -2898,7 +3212,12 @@ Escape_analysis_tag::tag(Named_object* fn) ++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); @@ -2913,7 +3232,6 @@ Escape_analysis_tag::tag(Named_object* fn) break; case Node::ESCAPE_HEAP: // flooded, moved to heap. - case Node::ESCAPE_SCOPE: // flooded, value leaves scope. break; default: diff --git a/gcc/go/gofrontend/escape.h b/gcc/go/gofrontend/escape.h index 04247fe..ac72b19 100644 --- a/gcc/go/gofrontend/escape.h +++ b/gcc/go/gofrontend/escape.h @@ -51,7 +51,7 @@ public: suffix_value() const { return this->suffix_value_; } - // Increase the level because a node is referenced. + // Increase the level because a node is dereferenced. Level increase() const { @@ -61,7 +61,7 @@ public: 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 decrease() const { @@ -104,9 +104,9 @@ public: } 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_; - // 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_; }; @@ -123,7 +123,10 @@ class Node { NODE_OBJECT, 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. @@ -161,11 +164,6 @@ class Node ESCAPE_NONE, // Is returned or reachable from a return statement. 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. ESCAPE_HEAP, // By construction will not escape. @@ -174,17 +172,25 @@ class Node // Multiple constructors for each classification. 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; } 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; } 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; } + Node(Node *n) + : classification_(NODE_INDIRECT), state_(NULL), encoding_(ESCAPE_UNKNOWN), + child_(n) + {} + // Return this node's type. Type* type() const; @@ -203,7 +209,7 @@ class Node // Return this node's detailed format string. std::string - details() const; + details(); std::string op_format() const; @@ -214,8 +220,7 @@ class Node // Return this node's escape encoding. int - encoding() const - { return this->encoding_; } + encoding(); // Set the node's escape encoding. void @@ -252,6 +257,22 @@ class Node : 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 Node* make_node(Named_object*); @@ -262,6 +283,9 @@ class Node static Node* make_node(Statement*); + static Node* + make_indirect_node(Node*); + // Return the maximum of an existing escape encoding E and a new // escape type. static int @@ -294,6 +318,10 @@ class Node // | Escapement_encoding: ESCAPE_BITS | 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. static std::map<Named_object*, Node*> objects; static std::map<Expression*, Node*> expressions; diff --git a/gcc/go/gofrontend/expressions.cc b/gcc/go/gofrontend/expressions.cc index 219b163..8ae1013 100644 --- a/gcc/go/gofrontend/expressions.cc +++ b/gcc/go/gofrontend/expressions.cc @@ -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 // 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, Expression* fn, Expression_list* args, diff --git a/gcc/go/gofrontend/expressions.h b/gcc/go/gofrontend/expressions.h index 9de734e..a4f6558 100644 --- a/gcc/go/gofrontend/expressions.h +++ b/gcc/go/gofrontend/expressions.h @@ -39,6 +39,7 @@ class Unary_expression; class Binary_expression; class String_concat_expression; class Call_expression; +class Builtin_call_expression; class Call_result_expression; class Func_expression; class Func_descriptor_expression; @@ -2246,6 +2247,15 @@ class Call_expression : public Expression set_is_multi_value_arg() { 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: int do_traverse(Traverse*); @@ -2351,6 +2361,133 @@ class Call_expression : public Expression 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. class Call_result_expression : public Expression diff --git a/gcc/go/gofrontend/gogo.h b/gcc/go/gofrontend/gogo.h index 8fb263b..58d8732 100644 --- a/gcc/go/gofrontend/gogo.h +++ b/gcc/go/gofrontend/gogo.h @@ -1577,6 +1577,11 @@ class Function_declaration set_asm_name(const std::string& 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. void set_pragmas(unsigned int pragmas) diff --git a/gcc/go/gofrontend/runtime.cc b/gcc/go/gofrontend/runtime.cc index a921449..bb30cc1 100644 --- a/gcc/go/gofrontend/runtime.cc +++ b/gcc/go/gofrontend/runtime.cc @@ -438,26 +438,18 @@ Runtime::name_to_code(const std::string& name) { Function code = Runtime::NUMBER_OF_FUNCTIONS; - // Aliases seen in function declaration code. - // TODO(cmang): Add other aliases. - 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. + for (size_t i = 0; i < Runtime::NUMBER_OF_FUNCTIONS; i++) { - // Look through the known names for a match. - for (size_t i = 0; i < Runtime::NUMBER_OF_FUNCTIONS; i++) - { - if (strcmp(runtime_functions[i].name, name.c_str()) == 0) - code = static_cast<Runtime::Function>(i); - } + const char* runtime_function_name = runtime_functions[i].name; + if (strcmp(runtime_function_name, name.c_str()) == 0) + code = static_cast<Runtime::Function>(i); + // The names in the table have "runtime." prefix. We may be + // 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; } |