Go patch committed: Optimize 0, 1, 2-case select statements

Message ID CAOyqgcVmSsA7Ft9B+3bCThQu_7y+63xWB2F5GS2kD7HJAStN_A@mail.gmail.com
State New
Headers show
Series
  • Go patch committed: Optimize 0, 1, 2-case select statements
Related show

Commit Message

Ian Lance Taylor July 4, 2019, 2:20 a.m.
This Go patch by Cherry Zhang optimizes 0,1,2-case select statements.
For a select statement with zero-, one-, or two-case with a default
case, we can generate simpler code instead of calling the generic
selectgo.  A zero-case select is just blocking the execution.  A
one-case select is mostly just executing the case.  A two-case select
with a default case is a non-blocking send or receive.  We add these
special cases for lowering a select statement, using libgo code
already written for the gc compiler.  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 273032)
+++ gcc/go/gofrontend/MERGE	(working copy)
@@ -1,4 +1,4 @@ 
-197b6fdfb861f07bab7365e350b5b855cfccc290
+7a8e10be0ddb8909ce25a264d03b24cee4df60cc
 
 The first line of this file holds the git revision number of the last
 merge done from the gofrontend repository.
Index: gcc/go/gofrontend/gogo.cc
===================================================================
--- gcc/go/gofrontend/gogo.cc	(revision 273009)
+++ gcc/go/gofrontend/gogo.cc	(working copy)
@@ -6262,7 +6262,8 @@  Function_declaration::get_or_make_decl(G
 
 	  if (this->asm_name_ == "runtime.gopanic"
 	      || this->asm_name_ == "__go_runtime_error"
-              || this->asm_name_ == "runtime.panicdottype")
+              || this->asm_name_ == "runtime.panicdottype"
+              || this->asm_name_ == "runtime.block")
 	    flags |= Backend::function_does_not_return;
 	}
 
Index: gcc/go/gofrontend/runtime.def
===================================================================
--- gcc/go/gofrontend/runtime.def	(revision 272944)
+++ gcc/go/gofrontend/runtime.def	(working copy)
@@ -204,6 +204,22 @@  DEF_GO_RUNTIME(CHANRECV2, "runtime.chanr
 DEF_GO_RUNTIME(SELECTGO, "runtime.selectgo", P3(POINTER, POINTER, INT),
 	       R2(INT, BOOL))
 
+// Non-blocking send a value on a channel, used for two-case select
+// statement with a default case.
+DEF_GO_RUNTIME(SELECTNBSEND, "runtime.selectnbsend", P2(CHAN, POINTER), R1(BOOL))
+
+// Non-blocking receive a value from a channel, used for two-case select
+// statement with a default case.
+DEF_GO_RUNTIME(SELECTNBRECV, "runtime.selectnbrecv", P2(POINTER, CHAN), R1(BOOL))
+
+// Non-blocking tuple receive from a channel, used for two-case select
+// statement with a default case.
+DEF_GO_RUNTIME(SELECTNBRECV2, "runtime.selectnbrecv2", P3(POINTER, POINTER, CHAN),
+               R1(BOOL))
+
+// Block execution.  Used for zero-case select.
+DEF_GO_RUNTIME(BLOCK, "runtime.block", P0(), R0())
+
 
 // Panic.
 DEF_GO_RUNTIME(GOPANIC, "runtime.gopanic", P1(EFACE), R0())
Index: gcc/go/gofrontend/statements.cc
===================================================================
--- gcc/go/gofrontend/statements.cc	(revision 273032)
+++ gcc/go/gofrontend/statements.cc	(working copy)
@@ -5665,6 +5665,28 @@  Select_statement::do_lower(Gogo* gogo, N
   Block* b = new Block(enclosing, loc);
 
   int ncases = this->clauses_->size();
+
+  // Zero-case select.  Just block the execution.
+  if (ncases == 0)
+    {
+      Expression* call = Runtime::make_call(Runtime::BLOCK, loc, 0);
+      Statement *s = Statement::make_statement(call, false);
+      b->add_statement(s);
+      this->is_lowered_ = true;
+      return Statement::make_block_statement(b, loc);
+    }
+
+  // One-case select.  It is mostly just to run the case.
+  if (ncases == 1)
+    return this->lower_one_case(b);
+
+  // Two-case select with one default case.  It is a non-blocking
+  // send/receive.
+  if (ncases == 2
+      && (this->clauses_->at(0).is_default()
+          || this->clauses_->at(1).is_default()))
+    return this->lower_two_case(b);
+
   Type* scase_type = Channel_type::select_case_type();
   Expression* ncases_expr =
     Expression::make_integer_ul(ncases, NULL,
@@ -5733,6 +5755,213 @@  Select_statement::do_lower(Gogo* gogo, N
   return Statement::make_block_statement(b, loc);
 }
 
+// Lower a one-case select statement.
+
+Statement*
+Select_statement::lower_one_case(Block* b)
+{
+  Select_clauses::Select_clause& scase = this->clauses_->at(0);
+  Location loc = this->location();
+  Expression* chan = scase.channel();
+  if (chan != NULL)
+    {
+      // Lower this to
+      //   if chan == nil { block() }; send/recv; body
+      Temporary_statement* chantmp = Statement::make_temporary(NULL, chan, loc);
+      b->add_statement(chantmp);
+      Expression* chanref = Expression::make_temporary_reference(chantmp, loc);
+
+      Expression* nil = Expression::make_nil(loc);
+      Expression* cond = Expression::make_binary(OPERATOR_EQEQ, chanref, nil, loc);
+      Block* bnil = new Block(b, loc);
+      Expression* call = Runtime::make_call(Runtime::BLOCK, loc, 0);
+      Statement* s = Statement::make_statement(call, false);
+      bnil->add_statement(s);
+      Statement* ifs = Statement::make_if_statement(cond, bnil, NULL, loc);
+      b->add_statement(ifs);
+
+      chanref = chanref->copy();
+      Location cloc = scase.location();
+      if (scase.is_send())
+        {
+          s = Statement::make_send_statement(chanref, scase.val(), cloc);
+          b->add_statement(s);
+        }
+      else
+        {
+          if (scase.closed() == NULL && scase.closedvar() == NULL)
+            {
+              // Simple receive.
+              Expression* recv = Expression::make_receive(chanref, cloc);
+              if (scase.val() != NULL)
+                s = Statement::make_assignment(scase.val(), recv, cloc);
+              else if (scase.var() != NULL)
+                {
+                  Temporary_statement *ts =
+                    Statement::make_temporary(NULL, recv, cloc);
+                  Expression* ref =
+                    Expression::make_temporary_reference(ts, cloc);
+                  s = ts;
+                  scase.var()->var_value()->set_init(ref);
+                  scase.var()->var_value()->clear_type_from_chan_element();
+                }
+              else
+                s = Statement::make_statement(recv, false);
+              b->add_statement(s);
+            }
+          else
+            {
+              // Tuple receive.
+              Expression* lhs;
+              if (scase.val() != NULL)
+                lhs = scase.val();
+              else
+                {
+                  Type* valtype = chan->type()->channel_type()->element_type();
+                  Temporary_statement *ts =
+                    Statement::make_temporary(valtype, NULL, cloc);
+                  lhs = Expression::make_temporary_reference(ts, cloc);
+                  b->add_statement(ts);
+                }
+
+              Expression* lhs2;
+              if (scase.closed() != NULL)
+                lhs2 = scase.closed();
+              else
+                {
+                  Type* booltype = Type::make_boolean_type();
+                  Temporary_statement *ts =
+                    Statement::make_temporary(booltype, NULL, cloc);
+                  lhs2 = Expression::make_temporary_reference(ts, cloc);
+                  b->add_statement(ts);
+                }
+
+              s = Statement::make_tuple_receive_assignment(lhs, lhs2, chanref, cloc);
+              b->add_statement(s);
+
+              if (scase.var() != NULL)
+                {
+                  scase.var()->var_value()->set_init(lhs->copy());
+                  scase.var()->var_value()->clear_type_from_chan_element();
+                }
+
+              if (scase.closedvar() != NULL)
+                scase.closedvar()->var_value()->set_init(lhs2->copy());
+            }
+        }
+    }
+
+  Statement* bs =
+    Statement::make_block_statement(scase.statements(), scase.location());
+  b->add_statement(bs);
+
+  this->is_lowered_ = true;
+  return Statement::make_block_statement(b, loc);
+}
+
+// Lower a two-case select statement with one default case.
+
+Statement*
+Select_statement::lower_two_case(Block* b)
+{
+  Select_clauses::Select_clause& chancase =
+    (this->clauses_->at(0).is_default()
+     ? this->clauses_->at(1)
+     : this->clauses_->at(0));
+  Select_clauses::Select_clause& defcase =
+    (this->clauses_->at(0).is_default()
+     ? this->clauses_->at(0)
+     : this->clauses_->at(1));
+  Location loc = this->location();
+  Expression* chan = chancase.channel();
+
+  Temporary_statement* chantmp = Statement::make_temporary(NULL, chan, loc);
+  b->add_statement(chantmp);
+  Expression* chanref = Expression::make_temporary_reference(chantmp, loc);
+
+  Block* bchan;
+  Expression* call;
+  if (chancase.is_send())
+    {
+      // if selectnbsend(chan, &val) { body } else { default body }
+
+      Temporary_statement* ts = Statement::make_temporary(NULL, chancase.val(), loc);
+      // Tell the escape analysis that the value escapes, as it may be sent
+      // to a channel.
+      ts->set_value_escapes();
+      b->add_statement(ts);
+
+      Expression* ref = Expression::make_temporary_reference(ts, loc);
+      Expression* addr = Expression::make_unary(OPERATOR_AND, ref, loc);
+      call = Runtime::make_call(Runtime::SELECTNBSEND, loc, 2, chanref, addr);
+      bchan = chancase.statements();
+    }
+  else
+    {
+      Type* valtype = chan->type()->channel_type()->element_type();
+      Temporary_statement* ts = Statement::make_temporary(valtype, NULL, loc);
+      b->add_statement(ts);
+
+      Expression* ref = Expression::make_temporary_reference(ts, loc);
+      Expression* addr = Expression::make_unary(OPERATOR_AND, ref, loc);
+      Expression* okref = NULL;
+      if (chancase.closed() == NULL && chancase.closedvar() == NULL)
+        {
+          // Simple receive.
+          // if selectnbrecv(&lhs, chan) { body } else { default body }
+          call = Runtime::make_call(Runtime::SELECTNBRECV, loc, 2, addr, chanref);
+        }
+      else
+        {
+          // Tuple receive.
+          // if selectnbrecv2(&lhs, &ok, chan) { body } else { default body }
+
+          Type* booltype = Type::make_boolean_type();
+          Temporary_statement* ts = Statement::make_temporary(booltype, NULL, loc);
+          b->add_statement(ts);
+
+          okref = Expression::make_temporary_reference(ts, loc);
+          Expression* okaddr = Expression::make_unary(OPERATOR_AND, okref, loc);
+          call = Runtime::make_call(Runtime::SELECTNBRECV2, loc, 3, addr, okaddr,
+                                    chanref);
+        }
+
+      Location cloc = chancase.location();
+      bchan = new Block(b, loc);
+      if (chancase.val() != NULL && !chancase.val()->is_sink_expression())
+        {
+          Statement* as = Statement::make_assignment(chancase.val(), ref->copy(),
+                                                     cloc);
+          bchan->add_statement(as);
+        }
+      else if (chancase.var() != NULL)
+        {
+          chancase.var()->var_value()->set_init(ref->copy());
+          chancase.var()->var_value()->clear_type_from_chan_element();
+        }
+
+      if (chancase.closed() != NULL && !chancase.closed()->is_sink_expression())
+        {
+          Statement* as = Statement::make_assignment(chancase.closed(),
+                                                     okref->copy(), cloc);
+          bchan->add_statement(as);
+        }
+      else if (chancase.closedvar() != NULL)
+        chancase.closedvar()->var_value()->set_init(okref->copy());
+
+      Statement* bs = Statement::make_block_statement(chancase.statements(),
+                                                      cloc);
+      bchan->add_statement(bs);
+    }
+
+  Statement* ifs =
+    Statement::make_if_statement(call, bchan, defcase.statements(), loc);
+  b->add_statement(ifs);
+
+  this->is_lowered_ = true;
+  return Statement::make_block_statement(b, loc);
+}
+
 // Whether the select statement itself may fall through to the following
 // statement.
 
Index: gcc/go/gofrontend/statements.h
===================================================================
--- gcc/go/gofrontend/statements.h	(revision 272608)
+++ gcc/go/gofrontend/statements.h	(working copy)
@@ -1061,7 +1061,7 @@  class Select_clauses
   // 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
+  // received value, and CLOSEDVAR may 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.
@@ -1110,7 +1110,6 @@  class Select_clauses
   void
   dump_clauses(Ast_dump_context*) const;
 
- private:
   // A single clause.
   class Select_clause
   {
@@ -1166,8 +1165,30 @@  class Select_clauses
       return this->is_send_;
     }
 
+    // Return the value to send or the lvalue to receive into.
+    Expression*
+    val() const
+    { return this->val_; }
+
+    // Return the lvalue to set to whether the channel is closed
+    // on a receive.
+    Expression*
+    closed() const
+    { return this->closed_; }
+
+    // Return the variable to initialize, for "case a := <-ch".
+    Named_object*
+    var() const
+    { return this->var_; }
+
+    // Return the variable to initialize to whether the channel
+    // is closed, for "case a, c := <-ch".
+    Named_object*
+    closedvar() const
+    { return this->closedvar_; }
+
     // Return the statements.
-    const Block*
+    Block*
     statements() const
     { return this->statements_; }
 
@@ -1235,6 +1256,11 @@  class Select_clauses
     bool is_lowered_;
   };
 
+  Select_clause&
+  at(size_t i)
+  { return this->clauses_.at(i); }
+
+ private:
   typedef std::vector<Select_clause> Clauses;
 
   Clauses clauses_;
@@ -1288,6 +1314,14 @@  class Select_statement : public Statemen
   do_dump_statement(Ast_dump_context*) const;
 
  private:
+  // Lower a one-case select statement.
+  Statement*
+  lower_one_case(Block*);
+
+  // Lower a two-case select statement with one defualt case.
+  Statement*
+  lower_two_case(Block*);
+
   // The select clauses.
   Select_clauses* clauses_;
   // A temporary that holds the index value returned by selectgo.
Index: libgo/go/runtime/chan.go
===================================================================
--- libgo/go/runtime/chan.go	(revision 272608)
+++ libgo/go/runtime/chan.go	(working copy)
@@ -32,6 +32,9 @@  import (
 //go:linkname chanrecv1 runtime.chanrecv1
 //go:linkname chanrecv2 runtime.chanrecv2
 //go:linkname closechan runtime.closechan
+//go:linkname selectnbsend runtime.selectnbsend
+//go:linkname selectnbrecv runtime.selectnbrecv
+//go:linkname selectnbrecv2 runtime.selectnbrecv2
 
 const (
 	maxAlign  = 8
Index: libgo/go/runtime/select.go
===================================================================
--- libgo/go/runtime/select.go	(revision 272608)
+++ libgo/go/runtime/select.go	(working copy)
@@ -14,6 +14,7 @@  import (
 // themselves, so that the compiler will export them.
 //
 //go:linkname selectgo runtime.selectgo
+//go:linkname block runtime.block
 
 const debugSelect = false