Go patch committed: Bring escape analysis mostly in line with gc compiler

Message ID CAOyqgcUse2Zm_hTRetNNZhHZTYG43dONzuP6w8LJdZ4LiDQXSQ@mail.gmail.com
State New
Headers show
Series
  • Go patch committed: Bring escape analysis mostly in line with gc compiler
Related show

Commit Message

Ian Lance Taylor Dec. 22, 2017, 3:55 p.m.
This patch to the Go frontend by Cherry Zhang brings the Go frontend's
escape analysis mostly in line with 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 patch 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 https://golang.org/cl/8202,
https://golang.org/cl/20102, and other fixes.

- Support go:noescape pragma.

- Add special handling for self assignment like b.buf = b.buf[m:n].
(https://golang.org/cl/3162)

- Remove ESCAPE_SCOPE, which was treated essentially the same as
ESCAPE_HEAP, and was removed from the gc compiler.
(https://golang.org/cl/32130)

- Run flood phase until fix point. (https://golang.org/cl/30693)

- Unnamed parameters do not escape. (https://golang.org/cl/38600)

- Various small bug fixes and improvements.

"make check-go" passes except one test in math/big, when the escape
analysis is on. The escape analysis is still not run by default.

Bootstrapped and ran Go testsuite on x86_64-pc-linux-gnu.  Committed
to mainline.

Ian

Patch

Index: gcc/go/gofrontend/MERGE
===================================================================
--- gcc/go/gofrontend/MERGE	(revision 255967)
+++ gcc/go/gofrontend/MERGE	(working copy)
@@ -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.
Index: gcc/go/gofrontend/escape.cc
===================================================================
--- gcc/go/gofrontend/escape.cc	(revision 255967)
+++ gcc/go/gofrontend/escape.cc	(working copy)
@@ -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, Nam
   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) co
 	      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* gog
 {
   // 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, Fu
     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, Fu
 	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, Fu
 
 
 // Apply an indirection to N and return the result.
-// This really only works if N is an expression node; it essentially becomes
-// Node::make_node(n->expr()->deref()).  We need the escape context to set the
-// correct loop depth, however.
 
 Node*
 Escape_context::add_dereference(Node* n)
 {
-  // Just return the original node if we can't add an indirection.
-  if (n->object() != NULL || n->statement() != NULL)
-    return n;
-
-  Node* ind = Node::make_node(n->expr()->deref());
-  // Initialize the state if this node doesn't already exist.
-  ind->state(this, NULL);
+  Expression* e = n->expr();
+  Location loc = n->location();
+  Node* ind;
+  if (e != NULL
+      && e->type()->points_to() != NULL
+      && !e->type()->points_to()->is_void_type())
+    {
+      // We don't dereference void*, which can be actually any pointer type.
+      Expression* deref_expr = Expression::make_unary(OPERATOR_MULT, e, loc);
+      ind = Node::make_node(deref_expr);
+    }
+  else
+    // The gc compiler simply makes an OIND node. We can't do it
+    // for non-pointer type because that will result in a type error.
+    // Instead, we model this by making a node with a special flavor.
+    ind = Node::make_indirect_node(n);
+
+  // Initialize the state.
+  Node::Escape_state* state = ind->state(this, NULL);
+  state->loop_depth = n->state(this, NULL)->loop_depth;
   return ind;
 }
 
@@ -775,10 +858,43 @@  Gogo::analyze_escape()
 
       // Propagate levels across each dst.  This is the flood phase.
       std::set<Node*> dsts = context->dsts();
+      std::unordered_map<Node*, int> escapes;
       for (std::set<Node*>::iterator n = dsts.begin();
            n != dsts.end();
            ++n)
-        this->propagate_escape(context, *n);
+        {
+          escapes[*n] = (*n)->encoding();
+          this->propagate_escape(context, *n);
+        }
+      for (;;)
+        {
+          // Reflood if the roots' escape states increase. Run until fix point.
+          // This is rare.
+          bool done = true;
+          for (std::set<Node*>::iterator n = dsts.begin();
+               n != dsts.end();
+               ++n)
+            {
+              if ((*n)->object() == NULL
+                  && ((*n)->expr() == NULL
+                      || ((*n)->expr()->var_expression() == NULL
+                          && (*n)->expr()->enclosed_var_expression() == NULL
+                          && (*n)->expr()->func_expression() == NULL)))
+                continue;
+              if (escapes[*n] != (*n)->encoding())
+                {
+                  done = false;
+                  if (this->debug_escape_level() > 2)
+                    go_inform((*n)->location(), "Reflooding %s %s",
+                              debug_function_name((*n)->state(context, NULL)->fn).c_str(),
+                              (*n)->ast_format(this).c_str());
+                  escapes[*n] = (*n)->encoding();
+                  this->propagate_escape(context, *n);
+                }
+            }
+          if (done)
+            break;
+        }
 
       // Tag each exported function's parameters with escape information.
       for (std::vector<Named_object*>::iterator fn = stack.begin();
@@ -799,7 +915,6 @@  Gogo::analyze_escape()
 			  strip_packed_prefix(this, debug_function_name(state->fn)).c_str(),
 			  (*n)->ast_format(this).c_str());
 	    }
-	  // TODO(cmang): Which objects in context->noesc actually don't escape.
 	}
       delete context;
     }
@@ -893,8 +1008,8 @@  Escape_analysis_discover::visit(Named_ob
   this->stack_.push(fn);
   min = this->visit_code(fn, min);
   if ((min == id || min == id + 1)
-      && fn->is_function()
-      && fn->func_value()->enclosing() == NULL)
+      && ((fn->is_function() && fn->func_value()->enclosing() == NULL)
+          || fn->is_function_declaration()))
     {
       bool recursive = min == id;
       std::vector<Named_object*> group;
@@ -1072,6 +1187,38 @@  private:
   Named_object* fn_;
 };
 
+// Helper function to detect self assignment like the following.
+//
+// func (b *Buffer) Foo() {
+//   n, m := ...
+//   b.buf = b.buf[n:m]
+// }
+
+static bool
+is_self_assignment(Expression* lhs, Expression* rhs)
+{
+  Unary_expression* lue =
+    (lhs->field_reference_expression() != NULL
+     ? lhs->field_reference_expression()->expr()->unary_expression()
+     : lhs->unary_expression());
+  Var_expression* lve =
+    (lue != NULL && lue->op() == OPERATOR_MULT ? lue->operand()->var_expression() : NULL);
+  Array_index_expression* raie = rhs->array_index_expression();
+  String_index_expression* rsie = rhs->string_index_expression();
+  Expression* rarray =
+    (raie != NULL && raie->end() != NULL && raie->array()->type()->is_slice_type()
+     ? raie->array()
+     : (rsie != NULL && rsie->type()->is_string_type() ? rsie->string() : NULL));
+  Unary_expression* rue =
+    (rarray != NULL && rarray->field_reference_expression() != NULL
+     ? rarray->field_reference_expression()->expr()->unary_expression()
+     : (rarray != NULL ? rarray->unary_expression() : NULL));
+  Var_expression* rve =
+    (rue != NULL && rue->op() == OPERATOR_MULT ? rue->operand()->var_expression() : NULL);
+  return lve != NULL && rve != NULL
+         && lve->named_object() == rve->named_object();
+}
+
 // Model statements within a function as assignments and flows between nodes.
 
 int
@@ -1121,6 +1268,14 @@  Escape_analysis_assign::statement(Block*
       }
       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*
       // 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());
+        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;
+          }
 
-	// 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);
+        this->assign(lhs_node, rhs_node);
       }
       break;
 
@@ -1199,8 +1368,6 @@  Escape_analysis_assign::statement(Block*
       }
       break;
 
-      // TODO(cmang): Associate returned values with dummy return nodes.
-
     default:
       break;
     }
@@ -1221,7 +1388,15 @@  Escape_analysis_assign::expression(Expre
       && 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(Expre
     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(Expre
 	  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(Expre
       }
       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_expres
 	}
 
       this->context_->init_retvals(call_node, fntype);
+
+      // It could be a closure call that returns captured variable.
+      // Model this by flowing the func expression to result.
+      // See issue #14409.
+      Node* fn_node = Node::make_node(call->fn());
+      std::vector<Node*> retvals = call_node->state(this->context_, NULL)->retvals;
+      for (std::vector<Node*>::const_iterator p = retvals.begin();
+           p != retvals.end();
+           ++p)
+        this->assign_deref(*p, fn_node);
+
       return;
     }
 
@@ -1601,33 +1840,26 @@  Escape_analysis_assign::call(Call_expres
 
       Function* f = fn->named_object()->func_value();
       const Bindings* callee_bindings = f->block()->bindings();
-
-      const Typed_identifier_list* results = fntype->results();
+      Function::Results* results = f->result_variables();
       if (results != NULL)
 	{
 	  // Setup output list on this call node.
 	  Node::Escape_state* state = call_node->state(this->context_, NULL);
-	  for (Typed_identifier_list::const_iterator p1 = results->begin();
+	  for (Function::Results::const_iterator p1 = results->begin();
 	       p1 != results->end();
 	       ++p1)
 	    {
-	      if (p1->name().empty() || Gogo::is_sink_name(p1->name()))
-		continue;
-
-	      Named_object* result_no =
-		callee_bindings->lookup_local(p1->name());
-	      go_assert(result_no != NULL);
-	      Node* result_node = Node::make_node(result_no);
+	      Node* result_node = Node::make_node(*p1);
 	      state->retvals.push_back(result_node);
 	    }
 	}
 
       std::vector<Node*>::iterator p = arg_nodes.begin();
-      if (fntype->is_method()
-	  && fntype->receiver()->type()->has_pointer())
+      if (fntype->is_method())
 	{
 	  std::string rcvr_name = fntype->receiver()->name();
-	  if (rcvr_name.empty() || Gogo::is_sink_name(rcvr_name))
+	  if (rcvr_name.empty() || Gogo::is_sink_name(rcvr_name)
+              || !fntype->receiver()->type()->has_pointer())
 	    ;
 	  else
 	    {
@@ -1635,7 +1867,17 @@  Escape_analysis_assign::call(Call_expres
 		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_expres
   // Receiver.
   std::vector<Node*>::iterator p = arg_nodes.begin();
   if (fntype->is_method()
-      && fntype->receiver()->type()->has_pointer()
       && p != arg_nodes.end())
     {
       // First argument to call will be the receiver.
       std::string* note = fntype->receiver()->note();
       if (fntype->receiver()->type()->points_to() == NULL
-	  && (*p)->expr()->unary_expression() != NULL
-	  && (*p)->expr()->unary_expression()->op() == OPERATOR_AND)
-	{
-	  // This is a call to a value method that has been lowered into a call
-	  // to a pointer method.  Gccgo generates a pointer method for all
-	  // method calls and takes the address of the value passed as the
-	  // receiver then immediately dereferences it within the function.
-	  // In this case, the receiver does not escape.
-	}
+          && (*p)->expr()->type()->points_to() != NULL)
+        // This is a call to a value method that has been lowered into a call
+        // to a pointer method.  Gccgo generates a pointer method for all
+        // method calls and takes the address of the value passed as the
+        // receiver then immediately dereferences it within the function.
+        // In this case, the receiver address does not escape; its content
+        // flows to the call.
+        this->assign_from_note(note, call_state->retvals,
+                               this->context_->add_dereference(*p));
       else
 	{
 	  if (!Type::are_identical(fntype->receiver()->type(),
@@ -1732,7 +1973,6 @@  Escape_analysis_assign::call(Call_expres
 	      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_expres
 	      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
 	      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
 	  }
 	  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
         }
     }
 
-  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
 	  // 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
 	  }
 	  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
 		break;
 	      }
 
-	    // TODO(cmang): Handle case from issue 4529.
-	    // Node* call_node = Node::make_node(e);
-	    // Node::Escape_state* call_state = call_node->state(this->context_, NULL);
-	    // std::vector<Node*> retvals = call_state->retvals;
-	    // for (std::vector<Node*>::const_iterator p = retvals.begin();
-	    //	    p != retvals.end();
-	    //	    ++p)
-	    //	 this->flows(dst, *p);
+            // Result flows to dst.
+            Node* call_node = Node::make_node(e);
+            Node::Escape_state* call_state = call_node->state(this->context_, NULL);
+            std::vector<Node*> retvals = call_state->retvals;
+            for (std::vector<Node*>::const_iterator p = retvals.begin();
+                 p != retvals.end();
+                 ++p)
+              this->flows(dst, *p);
 	  }
 	  break;
 
+        case Expression::EXPRESSION_CALL_RESULT:
+          {
+            Call_result_expression* cre = e->call_result_expression();
+            Call_expression* call = cre->call()->call_expression();
+            if (call->is_builtin())
+              break;
+            if (call->fn()->func_expression() != NULL
+                && call->fn()->func_expression()->is_runtime_function())
+              {
+                switch (call->fn()->func_expression()->runtime_code())
+                  {
+                  case Runtime::IFACEE2E2:
+                  case Runtime::IFACEI2E2:
+                  case Runtime::IFACEE2I2:
+                  case Runtime::IFACEI2I2:
+                  case Runtime::IFACEE2T2P:
+                  case Runtime::IFACEI2T2P:
+                    {
+                      // x, ok = v.(T), where T is a pointer or interface,
+                      // is lowered to
+                      // x, ok = IFACEI2E2(v), or
+                      // x, ok = IFACEI2I2(type, v)
+                      // The last arg flows to the first result.
+                      // Note: IFACEX2T2 has different signature, handled by
+                      // ::expression.
+                      if (cre->index() != 0)
+                        break;
+                      Node* arg_node = Node::make_node(call->args()->back());
+                      this->assign(dst, arg_node);
+                    }
+                    break;
+
+                  default:
+                    break;
+                  }
+                break;
+              }
+
+            Node* call_node = Node::make_node(call);
+            Node* ret_node = call_node->state(context_, NULL)->retvals[cre->index()];
+            this->assign(dst, ret_node);
+          }
+          break;
+
 	case Expression::EXPRESSION_FUNC_REFERENCE:
 	  if (e->func_expression()->closure() != NULL)
-	    {
-	      // If SRC is a reference to a function closure, DST flows into
-	      // the underyling closure variable.
-	      Expression* closure = e->func_expression()->closure();
-	      Node* closure_node = Node::make_node(closure);
-	      this->flows(dst, closure_node);
-	    }
+            this->flows(dst, src);
 	  break;
 
+        case Expression::EXPRESSION_CONVERSION:
+          {
+            Type_conversion_expression* tce = e->conversion_expression();
+            Type* ft = tce->expr()->type();
+            Type* tt = tce->type();
+            if ((ft->is_string_type() && tt->is_slice_type())
+                || (ft->is_slice_type() && tt->is_string_type())
+                || (ft->integer_type() != NULL && tt->is_string_type()))
+              {
+                // string([]byte), string([]rune), []byte(string), []rune(string), string(rune)
+                this->flows(dst, src);
+                break;
+              }
+            // Conversion preserves input value.
+            Expression* underlying = tce->expr();
+            this->assign(dst, Node::make_node(underlying));
+          }
+          break;
+
 	case Expression::EXPRESSION_FIELD_REFERENCE:
 	  {
 	    // A non-pointer can't escape from a struct.
@@ -1999,7 +2277,6 @@  Escape_analysis_assign::assign(Node* dst
 	  }
 	  // 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
 		    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
 	    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
 	      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
 	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
 	  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(Nod
 	  // 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
     }
 
   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::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
 	{
 	  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
       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
   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
   // 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
 
   // 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
 	  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
 	      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
       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
 	  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
 	      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* underlying_node = Node::make_node(underlying);
 	  this->flood(level.increase(), dst, underlying_node, -1);
 	}
-
-      // TODO(cmang): Add case for Issue #10466.
+      else if (e->temporary_reference_expression() != NULL)
+        {
+          Statement* t = e->temporary_reference_expression()->statement();
+          this->flood(level, dst, Node::make_node(t), -1);
+        }
     }
+  else if (src->is_indirect())
+    // Increase the level for a dereference.
+    this->flood(level.increase(), dst, src->child(), -1);
 
   level = level.copy();
   for (std::set<Node*>::const_iterator p = src_state->flows.begin();
@@ -2815,6 +3105,13 @@  Escape_analysis_flood::flood(Level level
 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* f
 {
   // 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* f
 	  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* f
 	   ++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* f
 	      break;
 
 	    case Node::ESCAPE_HEAP: // flooded, moved to heap.
-	    case Node::ESCAPE_SCOPE: // flooded, value leaves scope.
 	      break;
 
 	    default:
Index: gcc/go/gofrontend/escape.h
===================================================================
--- gcc/go/gofrontend/escape.h	(revision 255967)
+++ gcc/go/gofrontend/escape.h	(working copy)
@@ -51,7 +51,7 @@  public:
   suffix_value() const
   { return this->suffix_value_; }
 
-  // Increase the level because a node is referenced.
+  // Increase the level because a node is dereferenced.
   Level
   increase() const
   {
@@ -61,7 +61,7 @@  public:
     return Level(this->value_ + 1, this->suffix_value_ + 1);
   }
 
-  // Decrease the level because a node is dereferenced.
+  // Decrease the level because a node is referenced.
   Level
   decrease() const
   {
@@ -104,9 +104,9 @@  public:
   }
 
 private:
-  // The sum of all indirects (-1) and references (+1) applied to a Node.
+  // The sum of all references (-1) and indirects (+1) applied to a Node.
   int value_;
-  // The sum of all indirects (-1) abd references (+1) applied to a copied Node.
+  // The sum of all references (-1) abd indirects (+1) applied to a copied Node.
   int suffix_value_;
 };
 
@@ -123,7 +123,10 @@  class Node
     {
       NODE_OBJECT,
       NODE_EXPRESSION,
-      NODE_STATEMENT
+      NODE_STATEMENT,
+      // A "fake" node that models the indirection of its child node.
+      // This node does not correspond to an AST node.
+      NODE_INDIRECT
     };
 
   // The state necessary to keep track of how a node escapes.
@@ -161,11 +164,6 @@  class Node
     ESCAPE_NONE,
     // Is returned or reachable from a return statement.
     ESCAPE_RETURN,
-    // Allocated in an inner loop, assigned to an outer loop,
-    // which allows construction of non-escaping but arbitrarily large linked
-    // data structures (i.e., not eligible for allocation in a fixed-size stack
-    // stack frame).
-    ESCAPE_SCOPE,
     // Reachable from the heap.
     ESCAPE_HEAP,
     // By construction will not escape.
@@ -174,17 +172,25 @@  class Node
 
   // Multiple constructors for each classification.
   Node(Named_object* no)
-    : classification_(NODE_OBJECT), state_(NULL), encoding_(ESCAPE_UNKNOWN)
+    : classification_(NODE_OBJECT), state_(NULL), encoding_(ESCAPE_UNKNOWN),
+      child_(NULL)
   { this->u_.object_val = no; }
 
   Node(Expression* e)
-    : classification_(NODE_EXPRESSION), state_(NULL), encoding_(ESCAPE_UNKNOWN)
+    : classification_(NODE_EXPRESSION), state_(NULL), encoding_(ESCAPE_UNKNOWN),
+      child_(NULL)
   { this->u_.expression_val = e; }
 
   Node(Statement* s)
-    : classification_(NODE_STATEMENT), state_(NULL), encoding_(ESCAPE_UNKNOWN)
+    : classification_(NODE_STATEMENT), state_(NULL), encoding_(ESCAPE_UNKNOWN),
+      child_(NULL)
   { this->u_.statement_val = s; }
 
+  Node(Node *n)
+    : classification_(NODE_INDIRECT), state_(NULL), encoding_(ESCAPE_UNKNOWN),
+      child_(n)
+  {}
+
   // Return this node's type.
   Type*
   type() const;
@@ -203,7 +209,7 @@  class Node
 
   // Return this node's detailed format string.
   std::string
-  details() const;
+  details();
 
   std::string
   op_format() const;
@@ -214,8 +220,7 @@  class Node
 
   // Return this node's escape encoding.
   int
-  encoding() const
-  { return this->encoding_; }
+  encoding();
 
   // Set the node's escape encoding.
   void
@@ -252,6 +257,22 @@  class Node
             : NULL);
   }
 
+  bool
+  is_indirect() const
+  { return this->classification_ == NODE_INDIRECT; }
+
+  // Return its child node.
+  // Child node is used only in indirect node, and in expression node
+  // representing slicing an array.
+  Node*
+  child() const
+  { return this->child_; }
+
+  // Set the child node.
+  void
+  set_child(Node* n)
+  { this->child_ = n; }
+
   // Static creation methods for each value supported in the union.
   static Node*
   make_node(Named_object*);
@@ -262,6 +283,9 @@  class Node
   static Node*
   make_node(Statement*);
 
+  static Node*
+  make_indirect_node(Node*);
+
   // Return the maximum of an existing escape encoding E and a new
   // escape type.
   static int
@@ -294,6 +318,10 @@  class Node
   // | Escapement_encoding: ESCAPE_BITS |
   int encoding_;
 
+  // Child node, used only in indirect node, and expression node representing
+  // slicing an array.
+  Node* child_;
+
   // Cache all the Nodes created via Node::make_node to make the API simpler.
   static std::map<Named_object*, Node*> objects;
   static std::map<Expression*, Node*> expressions;
Index: gcc/go/gofrontend/expressions.cc
===================================================================
--- gcc/go/gofrontend/expressions.cc	(revision 255666)
+++ gcc/go/gofrontend/expressions.cc	(working copy)
@@ -7113,114 +7113,6 @@  Expression::make_bound_method(Expression
 // 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,
Index: gcc/go/gofrontend/expressions.h
===================================================================
--- gcc/go/gofrontend/expressions.h	(revision 255666)
+++ gcc/go/gofrontend/expressions.h	(working copy)
@@ -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 Expressio
   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 Expressio
   bool is_flattened_;
 };
 
+// A call expression to a builtin function.
+
+class Builtin_call_expression : public Call_expression
+{
+ public:
+  Builtin_call_expression(Gogo* gogo, Expression* fn, Expression_list* args,
+			  bool is_varargs, Location location);
+
+  // The builtin functions.
+  enum Builtin_function_code
+    {
+      BUILTIN_INVALID,
+
+      // Predeclared builtin functions.
+      BUILTIN_APPEND,
+      BUILTIN_CAP,
+      BUILTIN_CLOSE,
+      BUILTIN_COMPLEX,
+      BUILTIN_COPY,
+      BUILTIN_DELETE,
+      BUILTIN_IMAG,
+      BUILTIN_LEN,
+      BUILTIN_MAKE,
+      BUILTIN_NEW,
+      BUILTIN_PANIC,
+      BUILTIN_PRINT,
+      BUILTIN_PRINTLN,
+      BUILTIN_REAL,
+      BUILTIN_RECOVER,
+
+      // Builtin functions from the unsafe package.
+      BUILTIN_ALIGNOF,
+      BUILTIN_OFFSETOF,
+      BUILTIN_SIZEOF
+    };
+
+  Builtin_function_code
+  code()
+  { return this->code_; }
+
+  // This overrides Call_expression::is_builtin.
+  bool
+  is_builtin()
+  { return true; }
+
+ protected:
+  // This overrides Call_expression::do_lower.
+  Expression*
+  do_lower(Gogo*, Named_object*, Statement_inserter*, int);
+
+  Expression*
+  do_flatten(Gogo*, Named_object*, Statement_inserter*);
+
+  bool
+  do_is_constant() const;
+
+  bool
+  do_numeric_constant_value(Numeric_constant*) const;
+
+  bool
+  do_discarding_value();
+
+  Type*
+  do_type();
+
+  void
+  do_determine_type(const Type_context*);
+
+  void
+  do_check_types(Gogo*);
+
+  Expression*
+  do_copy();
+
+  Bexpression*
+  do_get_backend(Translate_context*);
+
+  void
+  do_export(Export*) const;
+
+  virtual bool
+  do_is_recover_call() const;
+
+  virtual void
+  do_set_recover_arg(Expression*);
+
+ private:
+  Expression*
+  one_arg() const;
+
+  bool
+  check_one_arg();
+
+  static Type*
+  real_imag_type(Type*);
+
+  static Type*
+  complex_type(Type*);
+
+  Expression*
+  lower_make(Statement_inserter*);
+
+  Expression* flatten_append(Gogo*, Named_object*, Statement_inserter*);
+
+  bool
+  check_int_value(Expression*, bool is_length, bool* small);
+
+  // A pointer back to the general IR structure.  This avoids a global
+  // variable, or passing it around everywhere.
+  Gogo* gogo_;
+  // The builtin function being called.
+  Builtin_function_code code_;
+  // Used to stop endless loops when the length of an array uses len
+  // or cap of the array itself.
+  mutable bool seen_;
+  // Whether the argument is set for calls to BUILTIN_RECOVER.
+  bool recover_arg_is_set_;
+};
+
+inline Builtin_call_expression*
+Call_expression::builtin_call_expression()
+{
+  return (this->is_builtin()
+          ? static_cast<Builtin_call_expression*>(this)
+          : NULL);
+}
+
 // A single result from a call which returns multiple results.
 
 class Call_result_expression : public Expression
Index: gcc/go/gofrontend/gogo.h
===================================================================
--- gcc/go/gofrontend/gogo.h	(revision 255967)
+++ gcc/go/gofrontend/gogo.h	(working copy)
@@ -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)
Index: gcc/go/gofrontend/runtime.cc
===================================================================
--- gcc/go/gofrontend/runtime.cc	(revision 255666)
+++ gcc/go/gofrontend/runtime.cc	(working copy)
@@ -438,26 +438,18 @@  Runtime::name_to_code(const std::string&
 {
   Function code = Runtime::NUMBER_OF_FUNCTIONS;
 
-  // Aliases seen in function declaration code.
-  // TODO(cmang): Add other aliases.
-  if (name == "new")
-    code = Runtime::NEW;
-  else if (name == "close")
-    code = Runtime::CLOSE;
-  else if (name == "copy")
-    code = Runtime::SLICECOPY;
-  else if (name == "append")
-    code = Runtime::GROWSLICE;
-  else if (name == "delete")
-    code = Runtime::MAPDELETE;
-  else
+  // Look through the known names for a match.
+  for (size_t i = 0; i < Runtime::NUMBER_OF_FUNCTIONS; i++)
     {
-      // Look through the known names for a match.
-      for (size_t i = 0; i < Runtime::NUMBER_OF_FUNCTIONS; i++)
-	{
-	  if (strcmp(runtime_functions[i].name, name.c_str()) == 0)
-	    code = static_cast<Runtime::Function>(i);
-	}
+      const char* runtime_function_name = runtime_functions[i].name;
+      if (strcmp(runtime_function_name, name.c_str()) == 0)
+        code = static_cast<Runtime::Function>(i);
+      // The names in the table have "runtime." prefix. We may be
+      // called with a name without the prefix. Try matching
+      // without the prefix as well.
+      if (strncmp(runtime_function_name, "runtime.", 8) == 0
+          && strcmp(runtime_function_name + 8, name.c_str()) == 0)
+        code = static_cast<Runtime::Function>(i);
     }
   return code;
 }