diff mbox

Go patch commited: tuple receives indicate whether channel closed

Message ID mcrpqphhxac.fsf@google.com
State New
Headers show

Commit Message

Ian Lance Taylor March 24, 2011, 6:01 a.m. UTC
The Go language has changed the meaning of
	v, ok := <-c
Now ok is set to whether the channel is closed, rather than having this
be a nonblocking read in which ok is set to whether the value was
received.  Nonblocking reads now require a select statement.  This patch
implements this in gccgo.  Bootstrapped and ran Go testsuite on
x86_64-unknown-linux-gnu.  Committed to mainline.

Ian
diff mbox

Patch

diff -r 2ddeb22c4cf0 go/gogo-tree.cc
--- a/go/gogo-tree.cc	Wed Mar 23 17:36:50 2011 -0700
+++ b/go/gogo-tree.cc	Wed Mar 23 22:54:00 2011 -0700
@@ -3057,7 +3057,7 @@ 
 				     location,
 				     "__go_receive_big",
 				     3,
-				     void_type_node,
+				     boolean_type_node,
 				     ptr_type_node,
 				     channel,
 				     ptr_type_node,
diff -r 2ddeb22c4cf0 go/gogo.cc
--- a/go/gogo.cc	Wed Mar 23 17:36:50 2011 -0700
+++ b/go/gogo.cc	Wed Mar 23 22:54:00 2011 -0700
@@ -1257,7 +1257,7 @@ 
   Statement* s = sorig;
   while (true)
     {
-      Statement* snew = s->lower(this->gogo_, block);
+      Statement* snew = s->lower(this->gogo_, this->function_, block);
       if (snew == s)
 	break;
       s = snew;
@@ -1305,6 +1305,15 @@ 
   this->traverse(&lower_parse_tree);
 }
 
+// Lower a block.
+
+void
+Gogo::lower_block(Named_object* function, Block* block)
+{
+  Lower_parse_tree lower_parse_tree(this, function);
+  block->traverse(&lower_parse_tree);
+}
+
 // Lower an expression.
 
 void
diff -r 2ddeb22c4cf0 go/gogo.h
--- a/go/gogo.h	Wed Mar 23 17:36:50 2011 -0700
+++ b/go/gogo.h	Wed Mar 23 22:54:00 2011 -0700
@@ -348,6 +348,10 @@ 
   void
   lower_parse_tree();
 
+  // Lower all the statements in a block.
+  void
+  lower_block(Named_object* function, Block*);
+
   // Lower an expression.
   void
   lower_expression(Named_object* function, Expression**);
diff -r 2ddeb22c4cf0 go/parse.cc
--- a/go/parse.cc	Wed Mar 23 17:36:50 2011 -0700
+++ b/go/parse.cc	Wed Mar 23 22:54:00 2011 -0700
@@ -4179,10 +4179,12 @@ 
   bool is_send = false;
   Expression* channel = NULL;
   Expression* val = NULL;
+  Expression* closed = NULL;
   std::string varname;
+  std::string closedname;
   bool is_default = false;
-  bool got_case = this->comm_case(&is_send, &channel, &val, &varname,
-				  &is_default);
+  bool got_case = this->comm_case(&is_send, &channel, &val, &closed,
+				  &varname, &closedname, &is_default);
 
   if (this->peek_token()->is_op(OPERATOR_COLON))
     this->advance_token();
@@ -4191,6 +4193,7 @@ 
 
   Block* statements = NULL;
   Named_object* var = NULL;
+  Named_object* closedvar = NULL;
   if (this->peek_token()->is_op(OPERATOR_SEMICOLON))
     this->advance_token();
   else if (this->statement_list_may_start_here())
@@ -4206,6 +4209,14 @@ 
 	  var = this->gogo_->add_variable(varname, v);
 	}
 
+      if (!closedname.empty())
+	{
+	  // FIXME: LOCATION is slightly wrong here.
+	  Variable* v = new Variable(Type::lookup_bool_type(), NULL,
+				     false, false, false, location);
+	  closedvar = this->gogo_->add_variable(closedname, v);
+	}
+
       this->statement_list();
       statements = this->gogo_->finish_block(this->location());
     }
@@ -4221,7 +4232,8 @@ 
     }
 
   if (got_case)
-    clauses->add(is_send, channel, val, var, is_default, statements, location);
+    clauses->add(is_send, channel, val, closed, var, closedvar, is_default,
+		 statements, location);
   else if (statements != NULL)
     {
       // Add the statements to make sure that any names they define
@@ -4234,7 +4246,8 @@ 
 
 bool
 Parse::comm_case(bool* is_send, Expression** channel, Expression** val,
-		 std::string* varname, bool* is_default)
+		 Expression** closed, std::string* varname,
+		 std::string* closedname, bool* is_default)
 {
   const Token* token = this->peek_token();
   if (token->is_keyword(KEYWORD_DEFAULT))
@@ -4245,7 +4258,8 @@ 
   else if (token->is_keyword(KEYWORD_CASE))
     {
       this->advance_token();
-      if (!this->send_or_recv_expr(is_send, channel, val, varname))
+      if (!this->send_or_recv_stmt(is_send, channel, val, closed, varname,
+				   closedname))
 	return false;
     }
   else
@@ -4259,74 +4273,160 @@ 
   return true;
 }
 
-// RecvExpr =  [ Expression ( "=" | ":=" ) ] "<-" Expression .
+// RecvStmt   = [ Expression [ "," Expression ] ( "=" | ":=" ) ] RecvExpr .
+// RecvExpr   = Expression .
 
 bool
-Parse::send_or_recv_expr(bool* is_send, Expression** channel, Expression** val,
-			 std::string* varname)
+Parse::send_or_recv_stmt(bool* is_send, Expression** channel, Expression** val,
+			 Expression** closed, std::string* varname,
+			 std::string* closedname)
 {
   const Token* token = this->peek_token();
-  source_location location = token->location();
+  bool saw_comma = false;
+  bool closed_is_id = false;
   if (token->is_identifier())
     {
+      Gogo* gogo = this->gogo_;
       std::string recv_var = token->identifier();
-      bool is_var_exported = token->is_identifier_exported();
-      if (!this->advance_token()->is_op(OPERATOR_COLONEQ))
-	this->unget_token(Token::make_identifier_token(recv_var,
-						       is_var_exported,
-						       location));
-      else
+      bool is_rv_exported = token->is_identifier_exported();
+      source_location recv_var_loc = token->location();
+      token = this->advance_token();
+      if (token->is_op(OPERATOR_COLONEQ))
 	{
+	  // case rv := <-c:
 	  if (!this->advance_token()->is_op(OPERATOR_CHANOP))
 	    {
 	      error_at(this->location(), "expected %<<-%>");
 	      return false;
 	    }
+	  if (recv_var == "_")
+	    {
+	      error_at(recv_var_loc,
+		       "no new variables on left side of %<:=%>");
+	      recv_var = "blank";
+	    }
 	  *is_send = false;
-	  *varname = this->gogo_->pack_hidden_name(recv_var, is_var_exported);
+	  *varname = gogo->pack_hidden_name(recv_var, is_rv_exported);
 	  this->advance_token();
 	  *channel = this->expression(PRECEDENCE_NORMAL, false, true, NULL);
 	  return true;
 	}
+      else if (token->is_op(OPERATOR_COMMA))
+	{
+	  token = this->advance_token();
+	  if (token->is_identifier())
+	    {
+	      std::string recv_closed = token->identifier();
+	      bool is_rc_exported = token->is_identifier_exported();
+	      source_location recv_closed_loc = token->location();
+	      closed_is_id = true;
+
+	      token = this->advance_token();
+	      if (token->is_op(OPERATOR_COLONEQ))
+		{
+		  // case rv, rc := <-c:
+		  if (!this->advance_token()->is_op(OPERATOR_CHANOP))
+		    {
+		      error_at(this->location(), "expected %<<-%>");
+		      return false;
+		    }
+		  if (recv_var == "_" && recv_closed == "_")
+		    {
+		      error_at(recv_var_loc,
+			       "no new variables on left side of %<:=%>");
+		      recv_var = "blank";
+		    }
+		  *is_send = false;
+		  if (recv_var != "_")
+		    *varname = gogo->pack_hidden_name(recv_var,
+						      is_rv_exported);
+		  if (recv_closed != "_")
+		    *closedname = gogo->pack_hidden_name(recv_closed,
+							 is_rc_exported);
+		  this->advance_token();
+		  *channel = this->expression(PRECEDENCE_NORMAL, false, true,
+					      NULL);
+		  return true;
+		}
+
+	      this->unget_token(Token::make_identifier_token(recv_closed,
+							     is_rc_exported,
+							     recv_closed_loc));
+	    }
+
+	  *val = this->id_to_expression(gogo->pack_hidden_name(recv_var,
+							       is_rv_exported),
+					recv_var_loc);
+	  saw_comma = true;
+	}
+      else
+	this->unget_token(Token::make_identifier_token(recv_var,
+						       is_rv_exported,
+						       recv_var_loc));
     }
 
-  if (this->peek_token()->is_op(OPERATOR_CHANOP))
+  // If SAW_COMMA is false, then we are looking at the start of the
+  // send or receive expression.  If SAW_COMMA is true, then *VAL is
+  // set and we just read a comma.
+
+  if (!saw_comma && this->peek_token()->is_op(OPERATOR_CHANOP))
     {
+      // case <-c:
       *is_send = false;
       this->advance_token();
       *channel = this->expression(PRECEDENCE_NORMAL, false, true, NULL);
+      return true;
     }
-  else
+
+  Expression* e = this->expression(PRECEDENCE_NORMAL, true, true, NULL);
+
+  if (this->peek_token()->is_op(OPERATOR_EQ))
     {
-      Expression* left = this->expression(PRECEDENCE_NORMAL, true, true, NULL);
-
-      if (this->peek_token()->is_op(OPERATOR_EQ))
+      if (!this->advance_token()->is_op(OPERATOR_CHANOP))
 	{
-	  if (!this->advance_token()->is_op(OPERATOR_CHANOP))
-	    {
-	      error_at(this->location(), "missing %<<-%>");
-	      return false;
-	    }
-	  *is_send = false;
-	  *val = left;
-	  this->advance_token();
-	  *channel = this->expression(PRECEDENCE_NORMAL, false, true, NULL);
+	  error_at(this->location(), "missing %<<-%>");
+	  return false;
 	}
-      else if (this->peek_token()->is_op(OPERATOR_CHANOP))
+      *is_send = false;
+      this->advance_token();
+      *channel = this->expression(PRECEDENCE_NORMAL, false, true, NULL);
+      if (saw_comma)
 	{
-	  *is_send = true;
-	  *channel = this->verify_not_sink(left);
-	  this->advance_token();
-	  *val = this->expression(PRECEDENCE_NORMAL, false, true, NULL);
+	  // case v, e = <-c:
+	  // *VAL is already set.
+	  if (!e->is_sink_expression())
+	    *closed = e;
 	}
       else
 	{
-	  error_at(this->location(), "expected %<<-%> or %<=%>");
-	  return false;
+	  // case v = <-c:
+	  if (!e->is_sink_expression())
+	    *val = e;
 	}
+      return true;
     }
 
-  return true;
+  if (saw_comma)
+    {
+      if (closed_is_id)
+	error_at(this->location(), "expected %<=%> or %<:=%>");
+      else
+	error_at(this->location(), "expected %<=%>");
+      return false;
+    }
+
+  if (this->peek_token()->is_op(OPERATOR_CHANOP))
+    {
+      // case c <- v:
+      *is_send = true;
+      *channel = this->verify_not_sink(e);
+      this->advance_token();
+      *val = this->expression(PRECEDENCE_NORMAL, false, true, NULL);
+      return true;
+    }
+
+  error_at(this->location(), "expected %<<-%> or %<=%>");
+  return false;
 }
 
 // ForStat = "for" [ Condition | ForClause | RangeClause ] Block .
diff -r 2ddeb22c4cf0 go/parse.h
--- a/go/parse.h	Wed Mar 23 17:36:50 2011 -0700
+++ b/go/parse.h	Wed Mar 23 22:54:00 2011 -0700
@@ -246,8 +246,10 @@ 
   void type_switch_case(std::vector<Type*>*, bool*);
   void select_stat(const Label*);
   void comm_clause(Select_clauses*, bool* saw_default);
-  bool comm_case(bool*, Expression**, Expression**, std::string*, bool*);
-  bool send_or_recv_expr(bool*, Expression**, Expression**, std::string*);
+  bool comm_case(bool*, Expression**, Expression**, Expression**,
+		 std::string*, std::string*, bool*);
+  bool send_or_recv_stmt(bool*, Expression**, Expression**, Expression**,
+			 std::string*, std::string*);
   void for_stat(const Label*);
   void for_clause(Expression**, Block**);
   void range_clause_decl(const Typed_identifier_list*, Range_clause*);
diff -r 2ddeb22c4cf0 go/statements.cc
--- a/go/statements.cc	Wed Mar 23 17:36:50 2011 -0700
+++ b/go/statements.cc	Wed Mar 23 22:54:00 2011 -0700
@@ -638,7 +638,7 @@ 
   { gcc_unreachable(); }
 
   Statement*
-  do_lower(Gogo*, Block*);
+  do_lower(Gogo*, Named_object*, Block*);
 
   tree
   do_get_tree(Translate_context*)
@@ -667,7 +667,8 @@ 
 // statement.
 
 Statement*
-Assignment_operation_statement::do_lower(Gogo*, Block* enclosing)
+Assignment_operation_statement::do_lower(Gogo*, Named_object*,
+					 Block* enclosing)
 {
   source_location loc = this->location();
 
@@ -764,7 +765,7 @@ 
   { gcc_unreachable(); }
 
   Statement*
-  do_lower(Gogo*, Block*);
+  do_lower(Gogo*, Named_object*, Block*);
 
   tree
   do_get_tree(Translate_context*)
@@ -791,7 +792,7 @@ 
 // up into a set of single assignments.
 
 Statement*
-Tuple_assignment_statement::do_lower(Gogo*, Block* enclosing)
+Tuple_assignment_statement::do_lower(Gogo*, Named_object*, Block* enclosing)
 {
   source_location loc = this->location();
 
@@ -891,7 +892,7 @@ 
   { gcc_unreachable(); }
 
   Statement*
-  do_lower(Gogo*, Block*);
+  do_lower(Gogo*, Named_object*, Block*);
 
   tree
   do_get_tree(Translate_context*)
@@ -920,7 +921,8 @@ 
 // Lower a tuple map assignment.
 
 Statement*
-Tuple_map_assignment_statement::do_lower(Gogo*, Block* enclosing)
+Tuple_map_assignment_statement::do_lower(Gogo*, Named_object*,
+					 Block* enclosing)
 {
   source_location loc = this->location();
 
@@ -1037,7 +1039,7 @@ 
   { gcc_unreachable(); }
 
   Statement*
-  do_lower(Gogo*, Block*);
+  do_lower(Gogo*, Named_object*, Block*);
 
   tree
   do_get_tree(Translate_context*)
@@ -1066,7 +1068,7 @@ 
 // Lower a map assignment to a function call.
 
 Statement*
-Map_assignment_statement::do_lower(Gogo*, Block* enclosing)
+Map_assignment_statement::do_lower(Gogo*, Named_object*, Block* enclosing)
 {
   source_location loc = this->location();
 
@@ -1145,11 +1147,11 @@ 
 class Tuple_receive_assignment_statement : public Statement
 {
  public:
-  Tuple_receive_assignment_statement(Expression* val, Expression* success,
+  Tuple_receive_assignment_statement(Expression* val, Expression* closed,
 				     Expression* channel,
 				     source_location location)
     : Statement(STATEMENT_TUPLE_RECEIVE_ASSIGNMENT, location),
-      val_(val), success_(success), channel_(channel)
+      val_(val), closed_(closed), channel_(channel)
   { }
 
  protected:
@@ -1161,7 +1163,7 @@ 
   { gcc_unreachable(); }
 
   Statement*
-  do_lower(Gogo*, Block*);
+  do_lower(Gogo*, Named_object*, Block*);
 
   tree
   do_get_tree(Translate_context*)
@@ -1170,8 +1172,8 @@ 
  private:
   // Lvalue which receives the value from the channel.
   Expression* val_;
-  // Lvalue which receives whether the read succeeded or failed.
-  Expression* success_;
+  // Lvalue which receives whether the channel is closed.
+  Expression* closed_;
   // The channel on which we receive the value.
   Expression* channel_;
 };
@@ -1182,7 +1184,7 @@ 
 Tuple_receive_assignment_statement::do_traverse(Traverse* traverse)
 {
   if (this->traverse_expression(traverse, &this->val_) == TRAVERSE_EXIT
-      || this->traverse_expression(traverse, &this->success_) == TRAVERSE_EXIT)
+      || this->traverse_expression(traverse, &this->closed_) == TRAVERSE_EXIT)
     return TRAVERSE_EXIT;
   return this->traverse_expression(traverse, &this->channel_);
 }
@@ -1190,7 +1192,8 @@ 
 // Lower to a function call.
 
 Statement*
-Tuple_receive_assignment_statement::do_lower(Gogo*, Block* enclosing)
+Tuple_receive_assignment_statement::do_lower(Gogo*, Named_object*,
+					     Block* enclosing)
 {
   source_location loc = this->location();
 
@@ -1212,17 +1215,17 @@ 
   // evaluated in the right order.
   Move_ordered_evals moe(b);
   this->val_->traverse_subexpressions(&moe);
-  this->success_->traverse_subexpressions(&moe);
+  this->closed_->traverse_subexpressions(&moe);
 
   // var val_temp ELEMENT_TYPE
   Temporary_statement* val_temp =
     Statement::make_temporary(channel_type->element_type(), NULL, loc);
   b->add_statement(val_temp);
 
-  // var success_temp bool
-  Temporary_statement* success_temp =
+  // var closed_temp bool
+  Temporary_statement* closed_temp =
     Statement::make_temporary(Type::lookup_bool_type(), NULL, loc);
-  b->add_statement(success_temp);
+  b->add_statement(closed_temp);
 
   // func chanrecv2(c chan T, val *T) bool
   source_location bloc = BUILTINS_LOCATION;
@@ -1240,14 +1243,14 @@ 
     Named_object::make_function_declaration("chanrecv2", NULL, fntype, bloc);
   chanrecv2->func_declaration_value()->set_asm_name("runtime.chanrecv2");
 
-  // success_temp = chanrecv2(channel, &val_temp)
+  // closed_temp = chanrecv2(channel, &val_temp)
   Expression* func = Expression::make_func_reference(chanrecv2, NULL, loc);
   Expression_list* params = new Expression_list();
   params->push_back(this->channel_);
   Expression* ref = Expression::make_temporary_reference(val_temp, loc);
   params->push_back(Expression::make_unary(OPERATOR_AND, ref, loc));
   Expression* call = Expression::make_call(func, params, false, loc);
-  ref = Expression::make_temporary_reference(success_temp, loc);
+  ref = Expression::make_temporary_reference(closed_temp, loc);
   Statement* s = Statement::make_assignment(ref, call, loc);
   b->add_statement(s);
 
@@ -1256,9 +1259,9 @@ 
   s = Statement::make_assignment(this->val_, ref, loc);
   b->add_statement(s);
 
-  // success = success_temp
-  ref = Expression::make_temporary_reference(success_temp, loc);
-  s = Statement::make_assignment(this->success_, ref, loc);
+  // closed = closed_temp
+  ref = Expression::make_temporary_reference(closed_temp, loc);
+  s = Statement::make_assignment(this->closed_, ref, loc);
   b->add_statement(s);
 
   return Statement::make_block_statement(b, loc);
@@ -1267,11 +1270,11 @@ 
 // Make a nonblocking receive statement.
 
 Statement*
-Statement::make_tuple_receive_assignment(Expression* val, Expression* success,
+Statement::make_tuple_receive_assignment(Expression* val, Expression* closed,
 					 Expression* channel,
 					 source_location location)
 {
-  return new Tuple_receive_assignment_statement(val, success, channel,
+  return new Tuple_receive_assignment_statement(val, closed, channel,
 						location);
 }
 
@@ -1297,7 +1300,7 @@ 
   { gcc_unreachable(); }
 
   Statement*
-  do_lower(Gogo*, Block*);
+  do_lower(Gogo*, Named_object*, Block*);
 
   tree
   do_get_tree(Translate_context*)
@@ -1338,7 +1341,8 @@ 
 // Lower to a function call.
 
 Statement*
-Tuple_type_guard_assignment_statement::do_lower(Gogo*, Block* enclosing)
+Tuple_type_guard_assignment_statement::do_lower(Gogo*, Named_object*,
+						Block* enclosing)
 {
   source_location loc = this->location();
 
@@ -1643,7 +1647,7 @@ 
   { gcc_unreachable(); }
 
   Statement*
-  do_lower(Gogo*, Block*);
+  do_lower(Gogo*, Named_object*, Block*);
 
   tree
   do_get_tree(Translate_context*)
@@ -1659,7 +1663,7 @@ 
 // Lower to += or -=.
 
 Statement*
-Inc_dec_statement::do_lower(Gogo*, Block*)
+Inc_dec_statement::do_lower(Gogo*, Named_object*, Block*)
 {
   source_location loc = this->location();
 
@@ -2429,7 +2433,7 @@ 
 // panic/recover work correctly.
 
 Statement*
-Return_statement::do_lower(Gogo*, Block* enclosing)
+Return_statement::do_lower(Gogo*, Named_object*, Block* enclosing)
 {
   if (this->vals_ == NULL)
     return this;
@@ -3530,7 +3534,7 @@ 
 // of if statements.
 
 Statement*
-Switch_statement::do_lower(Gogo*, Block* enclosing)
+Switch_statement::do_lower(Gogo*, Named_object*, Block* enclosing)
 {
   source_location loc = this->location();
 
@@ -3871,7 +3875,7 @@ 
 // equality testing.
 
 Statement*
-Type_switch_statement::do_lower(Gogo*, Block* enclosing)
+Type_switch_statement::do_lower(Gogo*, Named_object*, Block* enclosing)
 {
   const source_location loc = this->location();
 
@@ -4079,6 +4083,11 @@ 
 	  if (Expression::traverse(&this->val_, traverse) == TRAVERSE_EXIT)
 	    return TRAVERSE_EXIT;
 	}
+      if (this->closed_ != NULL)
+	{
+	  if (Expression::traverse(&this->closed_, traverse) == TRAVERSE_EXIT)
+	    return TRAVERSE_EXIT;
+	}
     }
   if (this->statements_ != NULL)
     {
@@ -4093,7 +4102,8 @@ 
 // receive statements to the clauses.
 
 void
-Select_clauses::Select_clause::lower(Block* b)
+Select_clauses::Select_clause::lower(Gogo* gogo, Named_object* function,
+				     Block* b)
 {
   if (this->is_default_)
     {
@@ -4134,6 +4144,30 @@ 
       send->set_for_select();
       init->add_statement(send);
     }
+  else if (this->closed_ != NULL && !this->closed_->is_sink_expression())
+    {
+      gcc_assert(this->var_ == NULL && this->closedvar_ == NULL);
+      if (this->val_ == NULL)
+	this->val_ = Expression::make_sink(loc);
+      Statement* s = Statement::make_tuple_receive_assignment(this->val_,
+							      this->closed_,
+							      ref, loc);
+      init->add_statement(s);
+    }
+  else if (this->closedvar_ != NULL)
+    {
+      gcc_assert(this->val_ == NULL);
+      Expression* val;
+      if (this->var_ == NULL)
+	val = Expression::make_sink(loc);
+      else
+	val = Expression::make_var_reference(this->var_, loc);
+      Expression* closed = Expression::make_var_reference(this->closedvar_,
+							  loc);
+      Statement* s = Statement::make_tuple_receive_assignment(val, closed, ref,
+							      loc);
+      init->add_statement(s);
+    }
   else
     {
       Receive_expression* recv = Expression::make_receive(ref, loc);
@@ -4151,11 +4185,13 @@ 
 	}
       else
 	{
-	  recv->discarding_value();
 	  init->add_statement(Statement::make_statement(recv));
 	}
     }
 
+  // Lower any statements we just created.
+  gogo->lower_block(function, init);
+
   if (this->statements_ != NULL)
     init->add_statement(Statement::make_block_statement(this->statements_,
 							loc));
@@ -4222,12 +4258,12 @@ 
 // receive statements to the clauses.
 
 void
-Select_clauses::lower(Block* b)
+Select_clauses::lower(Gogo* gogo, Named_object* function, Block* b)
 {
   for (Clauses::iterator p = this->clauses_.begin();
        p != this->clauses_.end();
        ++p)
-    p->lower(b);
+    p->lower(gogo, function, b);
 }
 
 // Determine types.
@@ -4464,12 +4500,13 @@ 
 // explicit statements in the clauses.
 
 Statement*
-Select_statement::do_lower(Gogo*, Block* enclosing)
+Select_statement::do_lower(Gogo* gogo, Named_object* function,
+			   Block* enclosing)
 {
   if (this->is_lowered_)
     return this;
   Block* b = new Block(enclosing, this->location());
-  this->clauses_->lower(b);
+  this->clauses_->lower(gogo, function, b);
   this->is_lowered_ = true;
   b->add_statement(this);
   return Statement::make_block_statement(b, this->location());
@@ -4521,7 +4558,7 @@ 
 // complex statements make it easier to handle garbage collection.
 
 Statement*
-For_statement::do_lower(Gogo*, Block* enclosing)
+For_statement::do_lower(Gogo*, Named_object*, Block* enclosing)
 {
   Statement* s;
   source_location loc = this->location();
@@ -4652,7 +4689,7 @@ 
 // statements.
 
 Statement*
-For_range_statement::do_lower(Gogo* gogo, Block* enclosing)
+For_range_statement::do_lower(Gogo* gogo, Named_object*, Block* enclosing)
 {
   Type* range_type = this->range_->type();
   if (range_type->points_to() != NULL
diff -r 2ddeb22c4cf0 go/statements.h
--- a/go/statements.h	Wed Mar 23 17:36:50 2011 -0700
+++ b/go/statements.h	Wed Mar 23 22:54:00 2011 -0700
@@ -162,7 +162,7 @@ 
   // Make an assignment from a nonblocking receive to a pair of
   // variables.
   static Statement*
-  make_tuple_receive_assignment(Expression* val, Expression* success,
+  make_tuple_receive_assignment(Expression* val, Expression* closed,
 				Expression* channel, source_location);
 
   // Make an assignment from a type guard to a pair of variables.
@@ -284,11 +284,11 @@ 
 
   // Lower a statement.  This is called immediately after parsing to
   // simplify statements for further processing.  It returns the same
-  // Statement or a new one.  BLOCK is the block containing this
-  // statement.
+  // Statement or a new one.  FUNCTION is the function containing this
+  // statement.  BLOCK is the block containing this statement.
   Statement*
-  lower(Gogo* gogo, Block* block)
-  { return this->do_lower(gogo, block); }
+  lower(Gogo* gogo, Named_object* function, Block* block)
+  { return this->do_lower(gogo, function, block); }
 
   // Set type information for unnamed constants.
   void
@@ -381,7 +381,7 @@ 
   // Implemented by the child class: lower this statement to a simpler
   // one.
   virtual Statement*
-  do_lower(Gogo*, Block*)
+  do_lower(Gogo*, Named_object*, Block*)
   { return this; }
 
   // Implemented by child class: set type information for unnamed
@@ -574,7 +574,7 @@ 
   do_traverse_assignments(Traverse_assignments*);
 
   Statement*
-  do_lower(Gogo*, Block*);
+  do_lower(Gogo*, Named_object*, Block*);
 
   void
   do_determine_types();
@@ -649,17 +649,22 @@ 
   // Add a new clause.  IS_SEND is true if this is a send clause,
   // false for a receive clause.  For a send clause CHANNEL is the
   // channel and VAL is the value to send.  For a receive clause
-  // CHANNEL is the channel and VAL is either NULL or a Var_expression
-  // for the variable to set; if VAL is NULL, VAR may be a variable
-  // which is initialized with the received value.  IS_DEFAULT is true
-  // if this is the default clause.  STATEMENTS is the list of
-  // statements to execute.
+  // CHANNEL is the channel, VAL is either NULL or a Var_expression
+  // for the variable to set, and CLOSED is either NULL or a
+  // Var_expression to set to whether the channel is closed.  If VAL
+  // is NULL, VAR may be a variable to be initialized with the
+  // received value, and CLOSEDVAR ma be a variable to be initialized
+  // with whether the channel is closed.  IS_DEFAULT is true if this
+  // is the default clause.  STATEMENTS is the list of statements to
+  // execute.
   void
-  add(bool is_send, Expression* channel, Expression* val, Named_object* var,
-      bool is_default, Block* statements, source_location location)
+  add(bool is_send, Expression* channel, Expression* val, Expression* closed,
+      Named_object* var, Named_object* closedvar, bool is_default,
+      Block* statements, source_location location)
   {
-    this->clauses_.push_back(Select_clause(is_send, channel, val, var,
-					   is_default, statements, location));
+    this->clauses_.push_back(Select_clause(is_send, channel, val, closed, var,
+					   closedvar, is_default, statements,
+					   location));
   }
 
   // Traverse the select clauses.
@@ -668,7 +673,7 @@ 
 
   // Lower statements.
   void
-  lower(Block*);
+  lower(Gogo*, Named_object*, Block*);
 
   // Determine types.
   void
@@ -689,16 +694,18 @@ 
   {
    public:
     Select_clause()
-      : channel_(NULL), val_(NULL), var_(NULL), statements_(NULL),
-	is_send_(false), is_default_(false)
+      : channel_(NULL), val_(NULL), closed_(NULL), var_(NULL),
+	closedvar_(NULL), statements_(NULL), is_send_(false),
+	is_default_(false)
     { }
 
     Select_clause(bool is_send, Expression* channel, Expression* val,
-		  Named_object* var, bool is_default, Block* statements,
+		  Expression* closed, Named_object* var,
+		  Named_object* closedvar, bool is_default, Block* statements,
 		  source_location location)
-      : channel_(channel), val_(val), var_(var), statements_(statements),
-	location_(location), is_send_(is_send), is_default_(is_default),
-	is_lowered_(false)
+      : channel_(channel), val_(val), closed_(closed), var_(var),
+	closedvar_(closedvar), statements_(statements), location_(location),
+	is_send_(is_send), is_default_(is_default), is_lowered_(false)
     { gcc_assert(is_default ? channel == NULL : channel != NULL); }
 
     // Traverse the select clause.
@@ -707,7 +714,7 @@ 
 
     // Lower statements.
     void
-    lower(Block*);
+    lower(Gogo*, Named_object*, Block*);
 
     // Determine types.
     void
@@ -724,20 +731,6 @@ 
     channel() const
     { return this->channel_; }
 
-    // Return the value.  This will return NULL for the default
-    // clause, or for a receive clause for which no value was given.
-    Expression*
-    val() const
-    { return this->val_; }
-
-    // Return the variable to set when a receive clause is also a
-    // variable definition (v := <- ch).  This will return NULL for
-    // the default case, or for a send clause, or for a receive clause
-    // which does not define a variable.
-    Named_object*
-    var() const
-    { return this->var_; }
-
     // Return true for a send, false for a receive.
     bool
     is_send() const
@@ -768,10 +761,16 @@ 
    private:
     // The channel.
     Expression* channel_;
-    // The value to send or the variable to set.
+    // The value to send or the lvalue to receive into.
     Expression* val_;
-    // The variable to initialize, for "case a := <- ch".
+    // The lvalue to set to whether the channel is closed on a
+    // receive.
+    Expression* closed_;
+    // The variable to initialize, for "case a := <-ch".
     Named_object* var_;
+    // The variable to initialize to whether the channel is closed,
+    // for "case a, c := <-ch".
+    Named_object* closedvar_;
     // The statements to execute.
     Block* statements_;
     // The location of this clause.
@@ -821,7 +820,7 @@ 
   { return this->clauses_->traverse(traverse); }
 
   Statement*
-  do_lower(Gogo*, Block*);
+  do_lower(Gogo*, Named_object*, Block*);
 
   void
   do_determine_types()
@@ -1008,7 +1007,7 @@ 
   { gcc_unreachable(); }
 
   Statement*
-  do_lower(Gogo*, Block*);
+  do_lower(Gogo*, Named_object*, Block*);
 
   tree
   do_get_tree(Translate_context*)
@@ -1066,7 +1065,7 @@ 
   { gcc_unreachable(); }
 
   Statement*
-  do_lower(Gogo*, Block*);
+  do_lower(Gogo*, Named_object*, Block*);
 
   tree
   do_get_tree(Translate_context*)
@@ -1290,7 +1289,7 @@ 
   do_traverse(Traverse*);
 
   Statement*
-  do_lower(Gogo*, Block*);
+  do_lower(Gogo*, Named_object*, Block*);
 
   tree
   do_get_tree(Translate_context*)
@@ -1436,7 +1435,7 @@ 
   do_traverse(Traverse*);
 
   Statement*
-  do_lower(Gogo*, Block*);
+  do_lower(Gogo*, Named_object*, Block*);
 
   tree
   do_get_tree(Translate_context*)
diff -r 2ddeb22c4cf0 libgo/runtime/chan.goc
--- a/libgo/runtime/chan.goc	Wed Mar 23 17:36:50 2011 -0700
+++ b/libgo/runtime/chan.goc	Wed Mar 23 22:54:00 2011 -0700
@@ -12,28 +12,22 @@ 
 
 /* Do a nonblocking channel receive.  */
 
-func chanrecv2(c *chan, val *byte) (pres bool) {
+func chanrecv2(c *chan, val *byte) (received bool) {
 	if (c->element_size > 8) {
-		return __go_receive_nonblocking_big(c, val);
+		return __go_receive_big(c, val, 0);
 	} else {
-		struct __go_receive_nonblocking_small rs;
 		union {
 			char b[8];
 			uint64_t v;
 		} u;
 
-		rs = __go_receive_nonblocking_small (c);
-		if (!rs.__success) {
-			__builtin_memset(val, 0, c->element_size);
-			return 0;
-		}
-		u.v = rs.__val;
+		u.v = __go_receive_small_closed(c, 0, &received);
 #ifndef WORDS_BIGENDIAN
 		__builtin_memcpy(val, u.b, c->element_size);
 #else
 		__builtin_memcpy(val, u.b + 8 - c->element_size,
 				 c->element_size);
 #endif
-		return 1;
+		return received;
 	}
 }
diff -r 2ddeb22c4cf0 libgo/runtime/channel.h
--- a/libgo/runtime/channel.h	Wed Mar 23 17:36:50 2011 -0700
+++ b/libgo/runtime/channel.h	Wed Mar 23 22:54:00 2011 -0700
@@ -112,6 +112,9 @@ 
 
 extern uint64_t __go_receive_small (struct __go_channel *, _Bool);
 
+extern uint64_t __go_receive_small_closed (struct __go_channel *, _Bool,
+					   _Bool *);
+
 extern void __go_receive_release (struct __go_channel *);
 
 struct __go_receive_nonblocking_small
@@ -123,7 +126,7 @@ 
 extern struct __go_receive_nonblocking_small
 __go_receive_nonblocking_small (struct __go_channel *);
 
-extern void __go_receive_big (struct __go_channel *, void *, _Bool);
+extern _Bool __go_receive_big (struct __go_channel *, void *, _Bool);
 
 extern _Bool __go_receive_nonblocking_big (struct __go_channel *, void *);
 
diff -r 2ddeb22c4cf0 libgo/runtime/go-rec-big.c
--- a/libgo/runtime/go-rec-big.c	Wed Mar 23 17:36:50 2011 -0700
+++ b/libgo/runtime/go-rec-big.c	Wed Mar 23 22:54:00 2011 -0700
@@ -9,7 +9,7 @@ 
 #include "go-panic.h"
 #include "channel.h"
 
-void
+_Bool
 __go_receive_big (struct __go_channel *channel, void *val, _Bool for_select)
 {
   size_t alloc_size;
@@ -24,11 +24,13 @@ 
   if (!__go_receive_acquire (channel, for_select))
     {
       __builtin_memset (val, 0, channel->element_size);
-      return;
+      return 0;
     }
 
   offset = channel->next_fetch * alloc_size;
   __builtin_memcpy (val, &channel->data[offset], channel->element_size);
 
   __go_receive_release (channel);
+
+  return 1;
 }
diff -r 2ddeb22c4cf0 libgo/runtime/go-rec-small.c
--- a/libgo/runtime/go-rec-small.c	Wed Mar 23 17:36:50 2011 -0700
+++ b/libgo/runtime/go-rec-small.c	Wed Mar 23 22:54:00 2011 -0700
@@ -263,7 +263,8 @@ 
 /* Receive something 64 bits or smaller on a channel.  */
 
 uint64_t
-__go_receive_small (struct __go_channel *channel, _Bool for_select)
+__go_receive_small_closed (struct __go_channel *channel, _Bool for_select,
+			   _Bool *received)
 {
   uint64_t ret;
 
@@ -273,11 +274,26 @@ 
   __go_assert (channel->element_size <= sizeof (uint64_t));
 
   if (!__go_receive_acquire (channel, for_select))
-    return 0;
+    {
+      if (received != NULL)
+	*received = 0;
+      return 0;
+    }
 
   ret = channel->data[channel->next_fetch];
 
   __go_receive_release (channel);
 
+  if (received != NULL)
+    *received = 1;
+
   return ret;
 }
+
+/* Called by the compiler.  */
+
+uint64_t
+__go_receive_small (struct __go_channel *channel, _Bool for_select)
+{
+  return __go_receive_small_closed (channel, for_select, NULL);
+}