From 0e68d70b7fbf4533d7b5ccd84c439026062b1a0e Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Thu, 4 Jul 2019 02:20:37 +0000 Subject: [PATCH] compiler: optimize 0,1,2-case select statement 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. Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/184998 From-SVN: r273034 --- gcc/go/gofrontend/MERGE | 2 +- gcc/go/gofrontend/gogo.cc | 3 +- gcc/go/gofrontend/runtime.def | 16 +++ gcc/go/gofrontend/statements.cc | 229 ++++++++++++++++++++++++++++++++ gcc/go/gofrontend/statements.h | 40 +++++- libgo/go/runtime/chan.go | 3 + libgo/go/runtime/select.go | 1 + 7 files changed, 289 insertions(+), 5 deletions(-) diff --git a/gcc/go/gofrontend/MERGE b/gcc/go/gofrontend/MERGE index 9c5c13bd133..410afb0bab1 100644 --- a/gcc/go/gofrontend/MERGE +++ b/gcc/go/gofrontend/MERGE @@ -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. diff --git a/gcc/go/gofrontend/gogo.cc b/gcc/go/gofrontend/gogo.cc index fcf9a93c785..3abf7ea9762 100644 --- a/gcc/go/gofrontend/gogo.cc +++ b/gcc/go/gofrontend/gogo.cc @@ -6262,7 +6262,8 @@ Function_declaration::get_or_make_decl(Gogo* gogo, Named_object* no) 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; } diff --git a/gcc/go/gofrontend/runtime.def b/gcc/go/gofrontend/runtime.def index f510a65e7b7..c754739227e 100644 --- a/gcc/go/gofrontend/runtime.def +++ b/gcc/go/gofrontend/runtime.def @@ -204,6 +204,22 @@ DEF_GO_RUNTIME(CHANRECV2, "runtime.chanrecv2", P2(CHAN, POINTER), R1(BOOL)) 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()) diff --git a/gcc/go/gofrontend/statements.cc b/gcc/go/gofrontend/statements.cc index 332d63f377e..1e88fabecf9 100644 --- a/gcc/go/gofrontend/statements.cc +++ b/gcc/go/gofrontend/statements.cc @@ -5665,6 +5665,28 @@ Select_statement::do_lower(Gogo* gogo, Named_object* function, 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, Named_object* function, 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. diff --git a/gcc/go/gofrontend/statements.h b/gcc/go/gofrontend/statements.h index 432da30f7e6..7c254d0e28e 100644 --- a/gcc/go/gofrontend/statements.h +++ b/gcc/go/gofrontend/statements.h @@ -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 Clauses; Clauses clauses_; @@ -1288,6 +1314,14 @@ class Select_statement : public Statement 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. diff --git a/libgo/go/runtime/chan.go b/libgo/go/runtime/chan.go index 6dfe2f3fc3e..6c8d6f70ebd 100644 --- a/libgo/go/runtime/chan.go +++ b/libgo/go/runtime/chan.go @@ -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 diff --git a/libgo/go/runtime/select.go b/libgo/go/runtime/select.go index d658a349ed2..16de9b88f6c 100644 --- a/libgo/go/runtime/select.go +++ b/libgo/go/runtime/select.go @@ -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 -- 2.30.2