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:
Ian Lance Taylor 2019-07-04 02:20:37 +00:00
parent 9c5784fa75
commit 0e68d70b7f
7 changed files with 289 additions and 5 deletions

View File

@ -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.

View File

@ -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;
}

View File

@ -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())

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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