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