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
This commit is contained in:
parent
9c5784fa75
commit
0e68d70b7f
7 changed files with 289 additions and 5 deletions
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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 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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue