Commit 0e68d70b by Ian Lance Taylor

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
parent 9c5784fa
197b6fdfb861f07bab7365e350b5b855cfccc290 7a8e10be0ddb8909ce25a264d03b24cee4df60cc
The first line of this file holds the git revision number of the last The first line of this file holds the git revision number of the last
merge done from the gofrontend repository. merge done from the gofrontend repository.
...@@ -6262,7 +6262,8 @@ Function_declaration::get_or_make_decl(Gogo* gogo, Named_object* no) ...@@ -6262,7 +6262,8 @@ Function_declaration::get_or_make_decl(Gogo* gogo, Named_object* no)
if (this->asm_name_ == "runtime.gopanic" if (this->asm_name_ == "runtime.gopanic"
|| this->asm_name_ == "__go_runtime_error" || 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; flags |= Backend::function_does_not_return;
} }
......
...@@ -204,6 +204,22 @@ DEF_GO_RUNTIME(CHANRECV2, "runtime.chanrecv2", P2(CHAN, POINTER), R1(BOOL)) ...@@ -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), DEF_GO_RUNTIME(SELECTGO, "runtime.selectgo", P3(POINTER, POINTER, INT),
R2(INT, BOOL)) 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. // Panic.
DEF_GO_RUNTIME(GOPANIC, "runtime.gopanic", P1(EFACE), R0()) DEF_GO_RUNTIME(GOPANIC, "runtime.gopanic", P1(EFACE), R0())
......
...@@ -5665,6 +5665,28 @@ Select_statement::do_lower(Gogo* gogo, Named_object* function, ...@@ -5665,6 +5665,28 @@ Select_statement::do_lower(Gogo* gogo, Named_object* function,
Block* b = new Block(enclosing, loc); Block* b = new Block(enclosing, loc);
int ncases = this->clauses_->size(); 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(); Type* scase_type = Channel_type::select_case_type();
Expression* ncases_expr = Expression* ncases_expr =
Expression::make_integer_ul(ncases, NULL, Expression::make_integer_ul(ncases, NULL,
...@@ -5733,6 +5755,213 @@ Select_statement::do_lower(Gogo* gogo, Named_object* function, ...@@ -5733,6 +5755,213 @@ Select_statement::do_lower(Gogo* gogo, Named_object* function,
return Statement::make_block_statement(b, loc); 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 // Whether the select statement itself may fall through to the following
// statement. // statement.
......
...@@ -1061,7 +1061,7 @@ class Select_clauses ...@@ -1061,7 +1061,7 @@ class Select_clauses
// for the variable to set, and CLOSED is either NULL or a // for the variable to set, and CLOSED is either NULL or a
// Var_expression to set to whether the channel is closed. If VAL // Var_expression to set to whether the channel is closed. If VAL
// is NULL, VAR may be a variable to be initialized with the // 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 // with whether the channel is closed. IS_DEFAULT is true if this
// is the default clause. STATEMENTS is the list of statements to // is the default clause. STATEMENTS is the list of statements to
// execute. // execute.
...@@ -1110,7 +1110,6 @@ class Select_clauses ...@@ -1110,7 +1110,6 @@ class Select_clauses
void void
dump_clauses(Ast_dump_context*) const; dump_clauses(Ast_dump_context*) const;
private:
// A single clause. // A single clause.
class Select_clause class Select_clause
{ {
...@@ -1166,8 +1165,30 @@ class Select_clauses ...@@ -1166,8 +1165,30 @@ class Select_clauses
return this->is_send_; 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. // Return the statements.
const Block* Block*
statements() const statements() const
{ return this->statements_; } { return this->statements_; }
...@@ -1235,6 +1256,11 @@ class Select_clauses ...@@ -1235,6 +1256,11 @@ class Select_clauses
bool is_lowered_; bool is_lowered_;
}; };
Select_clause&
at(size_t i)
{ return this->clauses_.at(i); }
private:
typedef std::vector<Select_clause> Clauses; typedef std::vector<Select_clause> Clauses;
Clauses clauses_; Clauses clauses_;
...@@ -1288,6 +1314,14 @@ class Select_statement : public Statement ...@@ -1288,6 +1314,14 @@ class Select_statement : public Statement
do_dump_statement(Ast_dump_context*) const; do_dump_statement(Ast_dump_context*) const;
private: 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. // The select clauses.
Select_clauses* clauses_; Select_clauses* clauses_;
// A temporary that holds the index value returned by selectgo. // A temporary that holds the index value returned by selectgo.
......
...@@ -32,6 +32,9 @@ import ( ...@@ -32,6 +32,9 @@ import (
//go:linkname chanrecv1 runtime.chanrecv1 //go:linkname chanrecv1 runtime.chanrecv1
//go:linkname chanrecv2 runtime.chanrecv2 //go:linkname chanrecv2 runtime.chanrecv2
//go:linkname closechan runtime.closechan //go:linkname closechan runtime.closechan
//go:linkname selectnbsend runtime.selectnbsend
//go:linkname selectnbrecv runtime.selectnbrecv
//go:linkname selectnbrecv2 runtime.selectnbrecv2
const ( const (
maxAlign = 8 maxAlign = 8
......
...@@ -14,6 +14,7 @@ import ( ...@@ -14,6 +14,7 @@ import (
// themselves, so that the compiler will export them. // themselves, so that the compiler will export them.
// //
//go:linkname selectgo runtime.selectgo //go:linkname selectgo runtime.selectgo
//go:linkname block runtime.block
const debugSelect = false const debugSelect = false
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment