From: Ian Lance Taylor Date: Fri, 22 Dec 2017 15:55:10 +0000 (+0000) Subject: compiler: bring escape analysis mostly in line with gc compiler X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=bb14e19c2be54dd10f40d705364e08524ec8310c;p=gcc.git compiler: bring escape analysis mostly in line with gc compiler This CL ports the latest (~Go 1.10) escape analysis code from the gc compiler. Changes include: - In the gc compiler, the variable expression is represented with the variable node itself (ONAME). It is the same node used in the AST for multiple var expressions for the same variable. In our case, the var expressions nodes are distinct nodes. We need to propagate the escape state from/to the underlying variable in getter and setter. We already do it in the setter. Do it in the getter as well. - At the point of escape analysis, some AST constructs have not been lowered to runtime calls, for example, map literal construction and some builtin calls. Change the analysis to work on the non-lowered AST constructs instead of call expressions for them. For this to work, the analysis needs to look into Builtin_call_expression. Move its class definition from expressions.cc to expressions.h, and add necessary accessors. Also fix bugs in other runtime call handlings (selectsend, ifaceX2Y2, etc.). - Handle closures properly. The analysis tracks the function reference expression, and the escape state is propagated to the underlying heap expression for get_backend to do stack allocation for non-escaping closures. - Fix add_dereference. Before, this was doing expr->deref(), which undoes an indirection instead of add one. In the gc compiler, it adds a level of indirection, which is modeled as an OIND node regardless of the type of the expression. We can't do this for non-pointer typed expression, otherwise it will result in a type error. Instead, we model it with a special flavor of Node, "indirect". The flood phase handles this by incrementing its level. - Slicing of an array was not handled correctly. The gc compiler has an implicit (compiler inserted) OADDR node for the array, so the analysis is actually performed on the address of the array. We don't have this implicit address-of expression in the AST. Instead, we model this by adding an implicit child to the Node of the Array_index_expression representing slicing of an array. - Array_index_expression may represent indexing or slicing. The code distinguishes them by looking at whether the type of the expression is a slice. This does not work if the slice element is a slice. Instead, check whether its end() is NULL. - Temporary references was handled only in a limited case, as part of address-of expression. This CL handles it in general. The analysis uses the Temporary_statement as the point of tracking, and forwards Temporary_reference_expression to the underlying statement when needed. - Handle call return value flows, escpecially multiple return values. This includes porting part of CL 8202, CL 20102, and other fixes. - Support go:noescape pragma. - Add special handling for self assignment like b.buf = b.buf[m:n]. (CL 3162) - Remove ESCAPE_SCOPE, which was treated essentially the same as ESCAPE_HEAP, and was removed from the gc compiler. (CL 32130) - Run flood phase until fix point. (CL 30693) - Unnamed parameters do not escape. (CL 38600) - Various small bug fixes and improvements. "make check-go" passes except the one test in math/big, when the escape analysis is on. The escape analysis is still not run by default. Reviewed-on: https://go-review.googlesource.com/83876 From-SVN: r255976 --- diff --git a/gcc/go/gofrontend/MERGE b/gcc/go/gofrontend/MERGE index e3908c17c1e..b81922efe59 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 abdca820515..870680860d5 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 dsts = context->dsts(); + std::unordered_map escapes; for (std::set::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::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::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 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 retvals = call_node->state(this->context_, NULL)->retvals; + for (std::vector::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::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::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 retvals = call_state->retvals; - // for (std::vector::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 retvals = call_state->retvals; + for (std::vector::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::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 04247fe35cc..ac72b19a2a3 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 objects; static std::map expressions; diff --git a/gcc/go/gofrontend/expressions.cc b/gcc/go/gofrontend/expressions.cc index 219b16361ed..8ae1013a6a9 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 9de734e636f..a4f655828a5 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(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 8fb263bd165..58d873230ea 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 a9214498022..bb30cc15aad 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(i); - } + const char* runtime_function_name = runtime_functions[i].name; + if (strcmp(runtime_function_name, name.c_str()) == 0) + code = static_cast(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(i); } return code; }