diff mbox

Go patch committed: assign phase of escape analysis

Message ID CAOyqgcX_Qr4k_0_Azc4ZCmZ7re4yugzEBVp42KW6SMeRaqCWqQ@mail.gmail.com
State New
Headers show

Commit Message

Ian Lance Taylor June 14, 2016, 4:29 a.m. UTC
This patch by Chris Manghane implements the assign phase of escape
analysis.  This builds a graph of assignments within a function.  This
is just another step toward escape analysis; it is not yet enabled.

Ian
diff mbox

Patch

Index: gcc/go/gofrontend/MERGE
===================================================================
--- gcc/go/gofrontend/MERGE	(revision 237286)
+++ gcc/go/gofrontend/MERGE	(working copy)
@@ -1,4 +1,4 @@ 
-054ff1ece3dd5888a445efeaf3ae197b16d4186f
+f768153eb2a7a72587c9c0997955cdbbc70322d0
 
 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 236804)
+++ gcc/go/gofrontend/escape.cc	(working copy)
@@ -88,6 +88,55 @@  Node::set_encoding(int 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;
+
+  int64_t size;
+  bool ok = t->backend_type_size(context->gogo(), &size);
+  bool big = ok && (size < 0 || size > 10 * 1024 * 1024);
+
+  if (this->expr() != NULL)
+    {
+      if (this->expr()->allocation_expression() != NULL)
+	{
+	  ok = t->deref()->backend_type_size(context->gogo(), &size);
+	  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::MAKESLICE1
+		  || fn->runtime_code() == Runtime::MAKESLICE2
+		  || fn->runtime_code() == Runtime::MAKESLICE1BIG
+		  || fn->runtime_code() == Runtime::MAKESLICE2BIG))
+	    {
+	      // Second argument is length.
+	      Expression_list::iterator p = call->args()->begin();
+	      ++p;
+
+	      Numeric_constant nc;
+	      unsigned long v;
+	      if ((*p)->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
@@ -161,6 +210,37 @@  Node::max_encoding(int e, int 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)
@@ -493,14 +573,1258 @@  Gogo::discover_analysis_sets()
   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<Node*>& 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_;
+};
+
+// 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();
+
+  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_LABEL:
+      {
+	if (s->label_statement()->label()->looping())
+	  this->context_->increase_loop_depth();
+      }
+      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.
+      // TODO(cmang): Verify.
+      break;
+
+    case Statement::STATEMENT_ASSIGNMENT:
+      {
+	Assignment_statement* assn = s->assignment_statement();
+	Node* lhs = Node::make_node(assn->lhs());
+	Node* rhs = Node::make_node(assn->rhs());
+
+	// TODO(cmang): Add special case for escape analysis no-op:
+	// func (b *Buffer) Foo() {
+	// 	n, m := ...
+	//	b.buf = b.buf[n:m]
+	// }
+	// This is okay for now, it just means b escapes; it is conservative.
+	this->assign(lhs, rhs);
+      }
+      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)
+	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;
+
+      // TODO(cmang): Associate returned values with dummy return nodes.
+
+    default:
+      break;
+    }
+  return TRAVERSE_SKIP_COMPONENTS;
+}
+
+// Model expressions within a function as assignments and flows between nodes.
+
+int
+Escape_analysis_assign::expression(Expression** pexpr)
+{
+  // Big stuff escapes unconditionally.
+  Node* n = Node::make_node(*pexpr);
+  if ((n->encoding() & ESCAPE_MASK) != int(Node::ESCAPE_HEAP)
+      && n->is_big(this->context_))
+    {
+      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);
+
+  switch ((*pexpr)->classification())
+    {
+    case Expression::EXPRESSION_CALL:
+      {
+	Call_expression* call = (*pexpr)->call_expression();
+	this->call(call);
+
+	Func_expression* fe = call->fn()->func_expression();
+	if (fe != NULL && fe->is_runtime_function())
+	  {
+	    switch (fe->runtime_code())
+	      {
+	      case Runtime::PANIC:
+		{
+		  // Argument could leak through recover.
+		  Node* panic_arg = Node::make_node(call->args()->front());
+		  this->assign(this->context_->sink(), panic_arg);
+		}
+		break;
+
+	      case Runtime::APPEND:
+		{
+		  // Unlike gc/esc.go, a call to append has already had its
+		  // varargs lowered into a slice of arguments.
+		  // The content of the appended slice leaks.
+		  Node* appended = Node::make_node(call->args()->back());
+		  this->assign_deref(this->context_->sink(), appended);
+
+		  // The content of the original slice leaks as well.
+		  Node* appendee = Node::make_node(call->args()->back());
+		  this->assign_deref(this->context_->sink(), appendee);
+		}
+		break;
+
+	      case Runtime::COPY:
+		{
+		  // 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::MAKECHANBIG:
+	      case Runtime::MAKEMAP:
+	      case Runtime::MAKEMAPBIG:
+	      case Runtime::MAKESLICE1:
+	      case Runtime::MAKESLICE2:
+	      case Runtime::MAKESLICE1BIG:
+	      case Runtime::MAKESLICE2BIG:
+	      case Runtime::BYTE_ARRAY_TO_STRING:
+	      case Runtime::INT_ARRAY_TO_STRING:
+	      case Runtime::STRING_TO_BYTE_ARRAY:
+	      case Runtime::STRING_TO_INT_ARRAY:
+	      case Runtime::STRING_PLUS:
+	      case Runtime::CONSTRUCT_MAP:
+	      case Runtime::INT_TO_STRING:
+		{
+		  Node* runtime_node = Node::make_node(fe);
+		  this->context_->track(runtime_node);
+		}
+		break;
+
+	      default:
+		break;
+	      }
+	  }
+      }
+      break;
+
+    case Expression::EXPRESSION_ALLOCATION:
+      {
+	// Same as above; this is Runtime::NEW.
+	Node* alloc_node = Node::make_node(*pexpr);
+	this->context_->track(alloc_node);
+      }
+      break;
+
+    case Expression::EXPRESSION_CONVERSION:
+      {
+	Type_conversion_expression* tce = (*pexpr)->conversion_expression();
+	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_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_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);
+		Node::Escape_state* state =
+		  enclosed_node->state(this->context_, NULL);
+		state->loop_depth = this->context_->loop_depth();
+		this->assign(closure_node, enclosed_node);
+	      }
+	  }
+      }
+      break;
+
+    case Expression::EXPRESSION_UNARY:
+      {
+	if ((*pexpr)->unary_expression()->op() != OPERATOR_AND)
+	  break;
+
+	Node* addr_node = Node::make_node(*pexpr);
+	this->context_->track(addr_node);
+
+	Expression* operand = (*pexpr)->unary_expression()->operand();
+	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();
+	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;
+
+	if (var->is_variable()
+	    && !var->var_value()->is_parameter())
+	  {
+	    // For &x, use the loop depth of x if known.
+	    Node::Escape_state* addr_state =
+	      addr_node->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)
+	      addr_state->loop_depth = operand_state->loop_depth;
+	  }
+	else if ((var->is_variable()
+		  && var->var_value()->is_parameter())
+		 || var->is_result_variable())
+	  {
+	    Node::Escape_state* addr_state =
+	      addr_node->state(this->context_, NULL);
+	    addr_state->loop_depth = 1;
+	  }
+      }
+      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)
+{
+  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<Node*> 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<Node*>::iterator p = arg_nodes.begin();
+           p != arg_nodes.end();
+           ++p)
+	{
+	  this->assign(this->context_->sink(), *p);
+	}
+
+      this->context_->init_retvals(call_node, fntype);
+      return;
+    }
+
+  // If FN is an untagged function.
+  if (fn != NULL
+      && fn->named_object()->is_function()
+      && !fntype->is_tagged())
+    {
+      Function* f = fn->named_object()->func_value();
+      const Bindings* callee_bindings = f->block()->bindings();
+
+      const Typed_identifier_list* results = fntype->results();
+      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();
+	       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);
+	      state->retvals.push_back(result_node);
+	    }
+	}
+
+      std::vector<Node*>::iterator p = arg_nodes.begin();
+      if (fntype->is_method()
+	  && fntype->receiver()->type()->has_pointer())
+	{
+	  std::string rcvr_name = fntype->receiver()->name();
+	  if (rcvr_name.empty() || Gogo::is_sink_name(rcvr_name))
+	    ;
+	  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);
+	      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)
+	    {
+	      this->assign(this->context_->sink(), *p);
+	    }
+	}
+
+      return;
+    }
+
+  Node::Escape_state* call_state = call_node->state(this->context_, NULL);
+  this->context_->init_retvals(call_node, fntype);
+
+  // 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.
+	}
+      else
+	{
+	  if (!Type::are_identical(fntype->receiver()->type(),
+			       (*p)->expr()->type(), true, 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(),
+				   true, 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);
+	    }
+
+	  // TODO(cmang): Special care for varargs parameter?
+	  Type* t = pn->type();
+	  if (t != NULL
+	      && t->has_pointer())
+	    {
+	      std::string* note = pn->note();
+	      int enc = this->assign_from_note(note, call_state->retvals, *p);
+	      if (enc == Node::ESCAPE_NONE
+		  && (call->is_deferred()
+		      || call->is_concurrent()))
+		{
+		  // TODO(cmang): Mark the argument as strictly non-escaping.
+		}
+	    }
+	}
+
+      for (; p != arg_nodes.end(); ++p)
+	{
+	  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)
+{
+  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;
+
+	default:
+	  // TODO(cmang): Add debugging info here: only a few expressions
+	  // should leave DST unmodified.
+	  break;
+        }
+    }
+
+  if (src->expr() != NULL)
+    {
+      Expression* e = src->expr();
+      switch (e->classification())
+        {
+	case Expression::EXPRESSION_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_ALLOCATION:
+	  // DST = new(T).
+	case Expression::EXPRESSION_BOUND_METHOD:
+	  // DST = x.M.
+	  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_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();
+	    Func_expression* fe = call->fn()->func_expression();
+	    if (fe != NULL && fe->is_runtime_function())
+	      {
+		switch (fe->runtime_code())
+		  {
+		  case Runtime::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;
+		    }
+
+		  case Runtime::MAKECHAN:
+		  case Runtime::MAKECHANBIG:
+		  case Runtime::MAKEMAP:
+		  case Runtime::MAKEMAPBIG:
+		  case Runtime::MAKESLICE1:
+		  case Runtime::MAKESLICE2:
+		  case Runtime::MAKESLICE1BIG:
+		  case Runtime::MAKESLICE2BIG:
+		    // DST = make(...).
+		  case Runtime::BYTE_ARRAY_TO_STRING:
+		    // DST = string([]byte{...}).
+		  case Runtime::INT_ARRAY_TO_STRING:
+		    // DST = string([]int{...}).
+		  case Runtime::STRING_TO_BYTE_ARRAY:
+		    // DST = []byte(str).
+		  case Runtime::STRING_TO_INT_ARRAY:
+		    // DST = []int(str).
+		  case Runtime::STRING_PLUS:
+		    // 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::INT_TO_STRING:
+		    // 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::CONVERT_INTERFACE:
+		    // 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;
+
+		  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;
+	      }
+
+	    // 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);
+	  }
+	  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);
+	    }
+	  break;
+
+	case Expression::EXPRESSION_FIELD_REFERENCE:
+	  {
+	    // A non-pointer can't escape from a struct.
+	    if (!e->type()->has_pointer())
+	      break;
+	  }
+
+	case Expression::EXPRESSION_CONVERSION:
+	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->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();
+		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_slice_type())
+		  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);
+		    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_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();
+	    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);
+	      }
+	  }
+	  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;
+	}
+    }
+}
+
+// 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<Node*>& 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 (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<Node*>::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->expr() != NULL
+      && !src->expr()->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()
+      || src_state->flows.find(dst) != src_state->flows.end())
+    return;
+
+  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*, Named_object*)
+Gogo::assign_connectivity(Escape_context* context, Named_object* fn)
 {
-  // TODO(cmang): Model the flow analysis of input parameters and results for a
-  // function.
-  // TODO(cmang): Analyze the current function's body.
+  // 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->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->loop_depth = 1;
+
+      if (!p->type()->has_pointer())
+        continue;
+
+      // External function?  Parameters must escape unless //go:noescape is set.
+      // TODO(cmang): Implement //go:noescape directive.
+      if (fn->package() != NULL)
+	param_node->set_encoding(Node::ESCAPE_HEAP);
+      else
+	param_node->set_encoding(Node::ESCAPE_NONE);
+
+      // TODO(cmang): Track this node in no_escape list.
+    }
+
+  Escape_analysis_loop el;
+  fn->func_value()->traverse(&el);
+
+  fn->func_value()->traverse(&ea);
+  context->set_loop_depth(save_depth);
 }
 
 // Propagate escape information across the nodes modeled in this Analysis_set.
Index: gcc/go/gofrontend/escape.h
===================================================================
--- gcc/go/gofrontend/escape.h	(revision 236804)
+++ gcc/go/gofrontend/escape.h	(working copy)
@@ -206,7 +206,9 @@  class Node
   void
   set_encoding(int enc);
 
-  // Is this node a sink?
+  bool
+  is_big(Escape_context*) const;
+
   bool
   is_sink() const;
 
@@ -250,6 +252,11 @@  class Node
   static int
   max_encoding(int e, int etype);
 
+  // Return a modified encoding for an input parameter that flows into an
+  // output parameter.
+  static int
+  note_inout_flows(int e, int index, Level level);
+
  private:
   // The classification of this Node.
   Node_classification classification_;
Index: gcc/go/gofrontend/gogo.h
===================================================================
--- gcc/go/gofrontend/gogo.h	(revision 236804)
+++ gcc/go/gofrontend/gogo.h	(working copy)
@@ -2578,7 +2578,7 @@  class Label
  public:
   Label(const std::string& name)
     : name_(name), location_(Linemap::unknown_location()), snapshot_(NULL),
-      refs_(), is_used_(false), blabel_(NULL)
+      refs_(), is_used_(false), blabel_(NULL), depth_(DEPTH_UNKNOWN)
   { }
 
   // Return the label's name.
@@ -2601,6 +2601,26 @@  class Label
   set_is_used()
   { this->is_used_ = true; }
 
+  // Return whether this label is looping.
+  bool
+  looping() const
+  { return this->depth_ == DEPTH_LOOPING; }
+
+  // Set this label as looping.
+  void
+  set_looping()
+  { this->depth_ = DEPTH_LOOPING; }
+
+  // Return whether this label is nonlooping.
+  bool
+  nonlooping() const
+  { return this->depth_ == DEPTH_NONLOOPING; }
+
+  // Set this label as nonlooping.
+  void
+  set_nonlooping()
+  { this->depth_ = DEPTH_NONLOOPING; }
+
   // Return the location of the definition.
   Location
   location() const
@@ -2660,6 +2680,16 @@  class Label
   is_dummy_label() const
   { return this->name_ == "_"; }
 
+  // A classification of a label's looping depth.
+  enum Loop_depth
+  {
+    DEPTH_UNKNOWN,
+    // A label never jumped to.
+    DEPTH_NONLOOPING,
+    // A label jumped to.
+    DEPTH_LOOPING
+  };
+
  private:
   // The name of the label.
   std::string name_;
@@ -2675,6 +2705,8 @@  class Label
   bool is_used_;
   // The backend representation.
   Blabel* blabel_;
+  // The looping depth of this label, for escape analysis.
+  Loop_depth depth_;
 };
 
 // An unnamed label.  These are used when lowering loops.
Index: gcc/go/gofrontend/statements.cc
===================================================================
--- gcc/go/gofrontend/statements.cc	(revision 236804)
+++ gcc/go/gofrontend/statements.cc	(working copy)
@@ -1793,40 +1793,6 @@  Statement::make_statement(Expression* ex
   return new Expression_statement(expr, is_ignored);
 }
 
-// A block statement--a list of statements which may include variable
-// definitions.
-
-class Block_statement : public Statement
-{
- public:
-  Block_statement(Block* block, Location location)
-    : Statement(STATEMENT_BLOCK, location),
-      block_(block)
-  { }
-
- protected:
-  int
-  do_traverse(Traverse* traverse)
-  { return this->block_->traverse(traverse); }
-
-  void
-  do_determine_types()
-  { this->block_->determine_types(); }
-
-  bool
-  do_may_fall_through() const
-  { return this->block_->may_fall_through(); }
-
-  Bstatement*
-  do_get_backend(Translate_context* context);
-
-  void
-  do_dump_statement(Ast_dump_context*) const;
-
- private:
-  Block* block_;
-};
-
 // Convert a block to the backend representation of a statement.
 
 Bstatement*
@@ -2944,37 +2910,13 @@  Statement::make_continue_statement(Unnam
   return new Bc_statement(false, label, location);
 }
 
-// A goto statement.
+// Class Goto_statement.
 
-class Goto_statement : public Statement
+int
+Goto_statement::do_traverse(Traverse*)
 {
- public:
-  Goto_statement(Label* label, Location location)
-    : Statement(STATEMENT_GOTO, location),
-      label_(label)
-  { }
-
- protected:
-  int
-  do_traverse(Traverse*)
-  { return TRAVERSE_CONTINUE; }
-
-  void
-  do_check_types(Gogo*);
-
-  bool
-  do_may_fall_through() const
-  { return false; }
-
-  Bstatement*
-  do_get_backend(Translate_context*);
-
-  void
-  do_dump_statement(Ast_dump_context*) const;
-
- private:
-  Label* label_;
-};
+  return TRAVERSE_CONTINUE;
+}
 
 // Check types for a label.  There aren't any types per se, but we use
 // this to give an error if the label was never defined.
@@ -3016,35 +2958,21 @@  Statement::make_goto_statement(Label* la
   return new Goto_statement(label, location);
 }
 
-// A goto statement to an unnamed label.
+// Class Goto_unnamed_statement.
 
-class Goto_unnamed_statement : public Statement
+int
+Goto_unnamed_statement::do_traverse(Traverse*)
 {
- public:
-  Goto_unnamed_statement(Unnamed_label* label, Location location)
-    : Statement(STATEMENT_GOTO_UNNAMED, location),
-      label_(label)
-  { }
-
- protected:
-  int
-  do_traverse(Traverse*)
-  { return TRAVERSE_CONTINUE; }
-
-  bool
-  do_may_fall_through() const
-  { return false; }
-
-  Bstatement*
-  do_get_backend(Translate_context* context)
-  { return this->label_->get_goto(context, this->location()); }
+  return TRAVERSE_CONTINUE;
+}
 
-  void
-  do_dump_statement(Ast_dump_context*) const;
+// Convert the goto unnamed statement to the backend representation.
 
- private:
-  Unnamed_label* label_;
-};
+Bstatement*
+Goto_unnamed_statement::do_get_backend(Translate_context* context)
+{
+  return this->label_->get_goto(context, this->location());
+}
 
 // Dump the AST representation for an unnamed goto statement
 
@@ -3109,32 +3037,27 @@  Statement::make_label_statement(Label* l
   return new Label_statement(label, location);
 }
 
-// An unnamed label statement.
+// Class Unnamed_label_statement.
 
-class Unnamed_label_statement : public Statement
-{
- public:
-  Unnamed_label_statement(Unnamed_label* label)
-    : Statement(STATEMENT_UNNAMED_LABEL, label->location()),
-      label_(label)
-  { }
+Unnamed_label_statement::Unnamed_label_statement(Unnamed_label* label)
+  : Statement(STATEMENT_UNNAMED_LABEL, label->location()),
+    label_(label)
+{ }
 
- protected:
-  int
-  do_traverse(Traverse*)
-  { return TRAVERSE_CONTINUE; }
+int
+Unnamed_label_statement::do_traverse(Traverse*)
+{
+  return TRAVERSE_CONTINUE;
+}
 
-  Bstatement*
-  do_get_backend(Translate_context* context)
-  { return this->label_->get_definition(context); }
+// Get the backend definition for this unnamed label statement.
 
-  void
-  do_dump_statement(Ast_dump_context*) const;
+Bstatement*
+Unnamed_label_statement::do_get_backend(Translate_context* context)
+{
+  return this->label_->get_definition(context);
+}
 
- private:
-  // The label.
-  Unnamed_label* label_;
-};
 
 // Dump the AST representation for an unnamed label definition statement.
 
@@ -5209,7 +5132,9 @@  For_statement::do_lower(Gogo*, Named_obj
 
   b->set_end_location(end_loc);
 
-  return Statement::make_block_statement(b, loc);
+  Statement* bs = Statement::make_block_statement(b, loc);
+  bs->block_statement()->set_is_lowered_for_statement();
+  return bs;
 }
 
 // Return the break label, creating it if necessary.
Index: gcc/go/gofrontend/statements.h
===================================================================
--- gcc/go/gofrontend/statements.h	(revision 236804)
+++ gcc/go/gofrontend/statements.h	(working copy)
@@ -19,9 +19,13 @@  class Assignment_statement;
 class Temporary_statement;
 class Variable_declaration_statement;
 class Expression_statement;
+class Block_statement;
 class Return_statement;
 class Thunk_statement;
+class Goto_statement;
+class Goto_unnamed_statement;
 class Label_statement;
+class Unnamed_label_statement;
 class If_statement;
 class For_statement;
 class For_range_statement;
@@ -366,6 +370,12 @@  class Statement
     return this->convert<Expression_statement, STATEMENT_EXPRESSION>();
   }
 
+  // If this is an block statement, return it.  Otherwise return
+  // NULL.
+  Block_statement*
+  block_statement()
+  { return this->convert<Block_statement, STATEMENT_BLOCK>(); }
+
   // If this is a return statement, return it.  Otherwise return NULL.
   Return_statement*
   return_statement()
@@ -376,11 +386,26 @@  class Statement
   Thunk_statement*
   thunk_statement();
 
+  // If this is a goto statement, return it.  Otherwise return NULL.
+  Goto_statement*
+  goto_statement()
+  { return this->convert<Goto_statement, STATEMENT_GOTO>(); }
+
+  // If this is a goto_unnamed statement, return it.  Otherwise return NULL.
+  Goto_unnamed_statement*
+  goto_unnamed_statement()
+  { return this->convert<Goto_unnamed_statement, STATEMENT_GOTO_UNNAMED>(); }
+
   // If this is a label statement, return it.  Otherwise return NULL.
   Label_statement*
   label_statement()
   { return this->convert<Label_statement, STATEMENT_LABEL>(); }
 
+  // If this is an unnamed_label statement, return it.  Otherwise return NULL.
+  Unnamed_label_statement*
+  unnamed_label_statement()
+  { return this->convert<Unnamed_label_statement, STATEMENT_UNNAMED_LABEL>(); }
+
   // If this is an if statement, return it.  Otherwise return NULL.
   If_statement*
   if_statement()
@@ -762,6 +787,50 @@  class Expression_statement : public Stat
   bool is_ignored_;
 };
 
+// A block statement--a list of statements which may include variable
+// definitions.
+
+class Block_statement : public Statement
+{
+ public:
+  Block_statement(Block* block, Location location)
+    : Statement(STATEMENT_BLOCK, location),
+      block_(block), is_lowered_for_statement_(false)
+  { }
+
+  void
+  set_is_lowered_for_statement()
+  { this->is_lowered_for_statement_ = true; }
+
+  bool
+  is_lowered_for_statement()
+  { return this->is_lowered_for_statement_; }
+
+ protected:
+  int
+  do_traverse(Traverse* traverse)
+  { return this->block_->traverse(traverse); }
+
+  void
+  do_determine_types()
+  { this->block_->determine_types(); }
+
+  bool
+  do_may_fall_through() const
+  { return this->block_->may_fall_through(); }
+
+  Bstatement*
+  do_get_backend(Translate_context* context);
+
+  void
+  do_dump_statement(Ast_dump_context*) const;
+
+ private:
+  Block* block_;
+  // True if this block statement represents a lowered for statement.
+  bool is_lowered_for_statement_;
+};
+
 // A send statement.
 
 class Send_statement : public Statement
@@ -1162,6 +1231,74 @@  class Defer_statement : public Thunk_sta
   do_dump_statement(Ast_dump_context*) const;
 };
 
+// A goto statement.
+
+class Goto_statement : public Statement
+{
+ public:
+  Goto_statement(Label* label, Location location)
+    : Statement(STATEMENT_GOTO, location),
+      label_(label)
+  { }
+
+  // Return the label being jumped to.
+  Label*
+  label() const
+  { return this->label_; }
+
+ protected:
+  int
+  do_traverse(Traverse*);
+
+  void
+  do_check_types(Gogo*);
+
+  bool
+  do_may_fall_through() const
+  { return false; }
+
+  Bstatement*
+  do_get_backend(Translate_context*);
+
+  void
+  do_dump_statement(Ast_dump_context*) const;
+
+ private:
+  Label* label_;
+};
+
+// A goto statement to an unnamed label.
+
+class Goto_unnamed_statement : public Statement
+{
+ public:
+  Goto_unnamed_statement(Unnamed_label* label, Location location)
+    : Statement(STATEMENT_GOTO_UNNAMED, location),
+      label_(label)
+  { }
+
+  Unnamed_label*
+  unnamed_label() const
+  { return this->label_; }
+
+ protected:
+  int
+  do_traverse(Traverse*);
+
+  bool
+  do_may_fall_through() const
+  { return false; }
+
+  Bstatement*
+  do_get_backend(Translate_context* context);
+
+  void
+  do_dump_statement(Ast_dump_context*) const;
+
+ private:
+  Unnamed_label* label_;
+};
+
 // A label statement.
 
 class Label_statement : public Statement
@@ -1173,7 +1310,7 @@  class Label_statement : public Statement
   { }
 
   // Return the label itself.
-  const Label*
+  Label*
   label() const
   { return this->label_; }
 
@@ -1192,6 +1329,28 @@  class Label_statement : public Statement
   Label* label_;
 };
 
+// An unnamed label statement.
+
+class Unnamed_label_statement : public Statement
+{
+ public:
+  Unnamed_label_statement(Unnamed_label* label);
+
+ protected:
+  int
+  do_traverse(Traverse*);
+
+  Bstatement*
+  do_get_backend(Translate_context* context);
+
+  void
+  do_dump_statement(Ast_dump_context*) const;
+
+ private:
+  // The label.
+  Unnamed_label* label_;
+};
+
 // An if statement.
 
 class If_statement : public Statement
Index: gcc/go/gofrontend/types.cc
===================================================================
--- gcc/go/gofrontend/types.cc	(revision 236804)
+++ gcc/go/gofrontend/types.cc	(working copy)
@@ -4502,10 +4502,7 @@  class Call_multiple_result_type : public
  protected:
   bool
   do_has_pointer() const
-  {
-    go_assert(saw_errors());
-    return false;
-  }
+  { return false; }
 
   bool
   do_compare_is_identity(Gogo*)