Commit 84cdf51d by Ian Lance Taylor

compiler, runtime: open code select

    
    This is the gofrontend version of https://golang.org/cl/37933,
    https://golang.org/cl/37934, and https://golang.org/cl/37935.
    Open code the initialization of select cases.
    
    This is a step toward updating libgo to the 1.11 release.
    
    Reviewed-on: https://go-review.googlesource.com/135000

From-SVN: r264290
parent 283b9caf
f68c03e509b26e7f483f2800eb70a5fbf3f74d0b f2cd046a4e0d681c3d21ee547b437d3eab8af268
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.
...@@ -1361,7 +1361,13 @@ Escape_analysis_assign::statement(Block*, size_t*, Statement* s) ...@@ -1361,7 +1361,13 @@ Escape_analysis_assign::statement(Block*, size_t*, Statement* s)
{ {
Expression* init = s->temporary_statement()->init(); Expression* init = s->temporary_statement()->init();
if (init != NULL) if (init != NULL)
this->assign(Node::make_node(s), Node::make_node(init)); {
Node* n = Node::make_node(init);
if (s->temporary_statement()->value_escapes())
this->assign(this->context_->sink(), n);
else
this->assign(Node::make_node(s), n);
}
} }
break; break;
...@@ -1616,15 +1622,6 @@ Escape_analysis_assign::expression(Expression** pexpr) ...@@ -1616,15 +1622,6 @@ Escape_analysis_assign::expression(Expression** pexpr)
} }
break; break;
case Runtime::SELECTSEND:
{
// Send to a channel, lose track. The last argument is
// the address of the value to send.
Node* arg_node = Node::make_node(call->args()->back());
this->assign_deref(this->context_->sink(), arg_node);
}
break;
case Runtime::IFACEE2T2: case Runtime::IFACEE2T2:
case Runtime::IFACEI2T2: case Runtime::IFACEI2T2:
{ {
...@@ -2228,8 +2225,12 @@ Escape_analysis_assign::assign(Node* dst, Node* src) ...@@ -2228,8 +2225,12 @@ Escape_analysis_assign::assign(Node* dst, Node* src)
case Expression::EXPRESSION_TEMPORARY_REFERENCE: case Expression::EXPRESSION_TEMPORARY_REFERENCE:
{ {
// Temporary is tracked through the underlying Temporary_statement. // Temporary is tracked through the underlying Temporary_statement.
Statement* t = dst->expr()->temporary_reference_expression()->statement(); Temporary_statement* t =
dst = Node::make_node(t); dst->expr()->temporary_reference_expression()->statement();
if (t->value_escapes())
dst = this->context_->sink();
else
dst = Node::make_node(t);
} }
break; break;
......
...@@ -152,22 +152,10 @@ DEF_GO_RUNTIME(CHANRECV1, "runtime.chanrecv1", P2(CHAN, POINTER), R0()) ...@@ -152,22 +152,10 @@ DEF_GO_RUNTIME(CHANRECV1, "runtime.chanrecv1", P2(CHAN, POINTER), R0())
DEF_GO_RUNTIME(CHANRECV2, "runtime.chanrecv2", P2(CHAN, POINTER), R1(BOOL)) DEF_GO_RUNTIME(CHANRECV2, "runtime.chanrecv2", P2(CHAN, POINTER), R1(BOOL))
// Start building a select statement. // Run a select, returning the index of the selected clause and
DEF_GO_RUNTIME(NEWSELECT, "runtime.newselect", P3(POINTER, INT64, INT32), R0()) // whether that channel received a value.
DEF_GO_RUNTIME(SELECTGO, "runtime.selectgo", P3(POINTER, POINTER, INT),
// Add a default clause to a select statement. R2(INT, BOOL))
DEF_GO_RUNTIME(SELECTDEFAULT, "runtime.selectdefault", P1(POINTER), R0())
// Add a send clause to a select statement.
DEF_GO_RUNTIME(SELECTSEND, "runtime.selectsend", P3(POINTER, CHAN, POINTER),
R0())
// Add a receive clause to a select statement.
DEF_GO_RUNTIME(SELECTRECV, "runtime.selectrecv",
P4(POINTER, CHAN, POINTER, BOOLPTR), R0())
// Run a select, returning the index of the selected clause.
DEF_GO_RUNTIME(SELECTGO, "runtime.selectgo", P1(POINTER), R1(INT))
// Panic. // Panic.
......
...@@ -4548,17 +4548,19 @@ Select_clauses::Select_clause::traverse(Traverse* traverse) ...@@ -4548,17 +4548,19 @@ Select_clauses::Select_clause::traverse(Traverse* traverse)
void void
Select_clauses::Select_clause::lower(Gogo* gogo, Named_object* function, Select_clauses::Select_clause::lower(Gogo* gogo, Named_object* function,
Block* b, Temporary_statement* sel) Block* b, Temporary_statement* scases,
size_t index, Temporary_statement* recvok)
{ {
Location loc = this->location_; Location loc = this->location_;
Expression* selref = Expression::make_temporary_reference(sel, loc); Expression* scase = Expression::make_temporary_reference(scases, loc);
selref = Expression::make_unary(OPERATOR_AND, selref, loc); Expression* index_expr = Expression::make_integer_ul(index, NULL, loc);
scase = Expression::make_array_index(scase, index_expr, NULL, NULL, loc);
if (this->is_default_) if (this->is_default_)
{ {
go_assert(this->channel_ == NULL && this->val_ == NULL); go_assert(this->channel_ == NULL && this->val_ == NULL);
this->lower_default(b, selref); this->lower_default(b, scase);
this->is_lowered_ = true; this->is_lowered_ = true;
return; return;
} }
...@@ -4572,9 +4574,9 @@ Select_clauses::Select_clause::lower(Gogo* gogo, Named_object* function, ...@@ -4572,9 +4574,9 @@ Select_clauses::Select_clause::lower(Gogo* gogo, Named_object* function,
loc); loc);
if (this->is_send_) if (this->is_send_)
this->lower_send(b, selref, chanref); this->lower_send(b, scase, chanref);
else else
this->lower_recv(gogo, function, b, selref, chanref); this->lower_recv(gogo, function, b, scase, chanref, recvok);
// Now all references should be handled through the statements, not // Now all references should be handled through the statements, not
// through here. // through here.
...@@ -4585,18 +4587,16 @@ Select_clauses::Select_clause::lower(Gogo* gogo, Named_object* function, ...@@ -4585,18 +4587,16 @@ Select_clauses::Select_clause::lower(Gogo* gogo, Named_object* function,
// Lower a default clause in a select statement. // Lower a default clause in a select statement.
void void
Select_clauses::Select_clause::lower_default(Block* b, Expression* selref) Select_clauses::Select_clause::lower_default(Block* b, Expression* scase)
{ {
Location loc = this->location_; Location loc = this->location_;
Expression* call = Runtime::make_call(Runtime::SELECTDEFAULT, loc, 1, this->set_case(b, scase, Expression::make_nil(loc), NULL, caseDefault);
selref);
b->add_statement(Statement::make_statement(call, true));
} }
// Lower a send clause in a select statement. // Lower a send clause in a select statement.
void void
Select_clauses::Select_clause::lower_send(Block* b, Expression* selref, Select_clauses::Select_clause::lower_send(Block* b, Expression* scase,
Expression* chanref) Expression* chanref)
{ {
Location loc = this->location_; Location loc = this->location_;
...@@ -4611,22 +4611,29 @@ Select_clauses::Select_clause::lower_send(Block* b, Expression* selref, ...@@ -4611,22 +4611,29 @@ Select_clauses::Select_clause::lower_send(Block* b, Expression* selref,
// evaluate the send values in the required order. // evaluate the send values in the required order.
Temporary_statement* val = Statement::make_temporary(valtype, this->val_, Temporary_statement* val = Statement::make_temporary(valtype, this->val_,
loc); loc);
// The value here escapes, because it might be sent on a channel.
// We record that via the Temporary_statement, so that the escape
// analysis pass can pick it up. The gc compiler lowers select
// statements after escape analysis, so it doesn't have to worry
// about this.
val->set_value_escapes();
b->add_statement(val); b->add_statement(val);
Expression* valref = Expression::make_temporary_reference(val, loc); Expression* valref = Expression::make_temporary_reference(val, loc);
Expression* valaddr = Expression::make_unary(OPERATOR_AND, valref, loc); Expression* valaddr = Expression::make_unary(OPERATOR_AND, valref, loc);
Type* unsafe_pointer_type = Type::make_pointer_type(Type::make_void_type());
valaddr = Expression::make_cast(unsafe_pointer_type, valaddr, loc);
Expression* call = Runtime::make_call(Runtime::SELECTSEND, loc, 3, selref, this->set_case(b, scase, chanref, valaddr, caseSend);
chanref, valaddr);
b->add_statement(Statement::make_statement(call, true));
} }
// Lower a receive clause in a select statement. // Lower a receive clause in a select statement.
void void
Select_clauses::Select_clause::lower_recv(Gogo* gogo, Named_object* function, Select_clauses::Select_clause::lower_recv(Gogo* gogo, Named_object* function,
Block* b, Expression* selref, Block* b, Expression* scase,
Expression* chanref) Expression* chanref,
Temporary_statement* recvok)
{ {
Location loc = this->location_; Location loc = this->location_;
...@@ -4640,26 +4647,10 @@ Select_clauses::Select_clause::lower_recv(Gogo* gogo, Named_object* function, ...@@ -4640,26 +4647,10 @@ Select_clauses::Select_clause::lower_recv(Gogo* gogo, Named_object* function,
Expression* valref = Expression::make_temporary_reference(val, loc); Expression* valref = Expression::make_temporary_reference(val, loc);
Expression* valaddr = Expression::make_unary(OPERATOR_AND, valref, loc); Expression* valaddr = Expression::make_unary(OPERATOR_AND, valref, loc);
Type* unsafe_pointer_type = Type::make_pointer_type(Type::make_void_type());
valaddr = Expression::make_cast(unsafe_pointer_type, valaddr, loc);
Temporary_statement* closed_temp = NULL; this->set_case(b, scase, chanref, valaddr, caseRecv);
Expression* caddr;
if (this->closed_ == NULL && this->closedvar_ == NULL)
caddr = Expression::make_nil(loc);
else
{
closed_temp = Statement::make_temporary(Type::lookup_bool_type(), NULL,
loc);
b->add_statement(closed_temp);
Expression* cref = Expression::make_temporary_reference(closed_temp,
loc);
caddr = Expression::make_unary(OPERATOR_AND, cref, loc);
}
Expression* call = Runtime::make_call(Runtime::SELECTRECV, loc, 4, selref,
chanref, valaddr, caddr);
b->add_statement(Statement::make_statement(call, true));
// If the block of statements is executed, arrange for the received // If the block of statements is executed, arrange for the received
// value to move from VAL to the place where the statements expect // value to move from VAL to the place where the statements expect
...@@ -4684,16 +4675,14 @@ Select_clauses::Select_clause::lower_recv(Gogo* gogo, Named_object* function, ...@@ -4684,16 +4675,14 @@ Select_clauses::Select_clause::lower_recv(Gogo* gogo, Named_object* function,
if (this->closedvar_ != NULL) if (this->closedvar_ != NULL)
{ {
go_assert(this->closed_ == NULL); go_assert(this->closed_ == NULL);
Expression* cref = Expression::make_temporary_reference(closed_temp, Expression* cref = Expression::make_temporary_reference(recvok, loc);
loc);
this->closedvar_->var_value()->set_init(cref); this->closedvar_->var_value()->set_init(cref);
} }
else if (this->closed_ != NULL && !this->closed_->is_sink_expression()) else if (this->closed_ != NULL && !this->closed_->is_sink_expression())
{ {
if (init == NULL) if (init == NULL)
init = new Block(b, loc); init = new Block(b, loc);
Expression* cref = Expression::make_temporary_reference(closed_temp, Expression* cref = Expression::make_temporary_reference(recvok, loc);
loc);
init->add_statement(Statement::make_assignment(this->closed_, cref, init->add_statement(Statement::make_assignment(this->closed_, cref,
loc)); loc));
} }
...@@ -4709,6 +4698,45 @@ Select_clauses::Select_clause::lower_recv(Gogo* gogo, Named_object* function, ...@@ -4709,6 +4698,45 @@ Select_clauses::Select_clause::lower_recv(Gogo* gogo, Named_object* function,
} }
} }
// Set the fields of an scase struct, an element in the array that we
// pass to the runtime function selectgo.
void
Select_clauses::Select_clause::set_case(Block* b,
Expression* scase,
Expression* chanref,
Expression* elem,
int kind)
{
Location loc = this->location_;
Struct_type* scase_type = scase->type()->struct_type();
int field_index = 0;
go_assert(scase_type->field(field_index)->is_field_name("c"));
Expression* ref = Expression::make_field_reference(scase, field_index, loc);
Type* unsafe_pointer_type = Type::make_pointer_type(Type::make_void_type());
chanref = Expression::make_unsafe_cast(unsafe_pointer_type, chanref, loc);
Statement* s = Statement::make_assignment(ref, chanref, loc);
b->add_statement(s);
if (elem != NULL)
{
field_index = 1;
go_assert(scase_type->field(field_index)->is_field_name("elem"));
ref = Expression::make_field_reference(scase->copy(), field_index, loc);
s = Statement::make_assignment(ref, elem, loc);
b->add_statement(s);
}
field_index = 2;
go_assert(scase_type->field(field_index)->is_field_name("kind"));
Type* uint16_type = Type::lookup_integer_type("uint16");
Expression* k = Expression::make_integer_ul(kind, uint16_type, loc);
ref = Expression::make_field_reference(scase->copy(), field_index, loc);
s = Statement::make_assignment(ref, k, loc);
b->add_statement(s);
}
// Determine types. // Determine types.
void void
...@@ -4828,12 +4856,13 @@ Select_clauses::traverse(Traverse* traverse) ...@@ -4828,12 +4856,13 @@ Select_clauses::traverse(Traverse* traverse)
void void
Select_clauses::lower(Gogo* gogo, Named_object* function, Block* b, Select_clauses::lower(Gogo* gogo, Named_object* function, Block* b,
Temporary_statement* sel) Temporary_statement* scases, Temporary_statement* recvok)
{ {
size_t i = 0;
for (Clauses::iterator p = this->clauses_.begin(); for (Clauses::iterator p = this->clauses_.begin();
p != this->clauses_.end(); p != this->clauses_.end();
++p) ++p, ++i)
p->lower(gogo, function, b, sel); p->lower(gogo, function, b, scases, i, recvok);
} }
// Determine types. // Determine types.
...@@ -4872,13 +4901,13 @@ Select_clauses::may_fall_through() const ...@@ -4872,13 +4901,13 @@ Select_clauses::may_fall_through() const
return false; return false;
} }
// Convert to the backend representation. We have already accumulated // Convert to the backend representation. Assemble the clauses and
// all the select information. Now we call selectgo, which will // build a switch statement on the index value returned by the call to
// return the index of the clause to execute. // selectgo.
Bstatement* Bstatement*
Select_clauses::get_backend(Translate_context* context, Select_clauses::get_backend(Translate_context* context,
Temporary_statement* sel, Temporary_statement* index,
Unnamed_label *break_label, Unnamed_label *break_label,
Location location) Location location)
{ {
...@@ -4909,21 +4938,14 @@ Select_clauses::get_backend(Translate_context* context, ...@@ -4909,21 +4938,14 @@ Select_clauses::get_backend(Translate_context* context,
clauses[i] = context->backend()->compound_statement(s, g); clauses[i] = context->backend()->compound_statement(s, g);
} }
Expression* selref = Expression::make_temporary_reference(sel, location); Expression* ref = Expression::make_temporary_reference(index, location);
selref = Expression::make_unary(OPERATOR_AND, selref, location); Bexpression* bindex = ref->get_backend(context);
Expression* call = Runtime::make_call(Runtime::SELECTGO, location, 1,
selref);
context->gogo()->lower_expression(context->function(), NULL, &call);
Bexpression* bcall = call->get_backend(context);
if (count == 0)
{
Bfunction* bfunction = context->function()->func_value()->get_decl();
return context->backend()->expression_statement(bfunction, bcall);
}
Bfunction* bfunction = context->function()->func_value()->get_decl(); Bfunction* bfunction = context->function()->func_value()->get_decl();
if (count == 0)
return context->backend()->expression_statement(bfunction, bindex);
Expression* crash = Runtime::make_call(Runtime::UNREACHABLE, location, 0); Expression* crash = Runtime::make_call(Runtime::UNREACHABLE, location, 0);
Bexpression* bcrash = crash->get_backend(context); Bexpression* bcrash = crash->get_backend(context);
clauses[count] = context->backend()->expression_statement(bfunction, bcrash); clauses[count] = context->backend()->expression_statement(bfunction, bcrash);
...@@ -4932,7 +4954,7 @@ Select_clauses::get_backend(Translate_context* context, ...@@ -4932,7 +4954,7 @@ Select_clauses::get_backend(Translate_context* context,
statements.reserve(2); statements.reserve(2);
Bstatement* switch_stmt = context->backend()->switch_statement(bfunction, Bstatement* switch_stmt = context->backend()->switch_statement(bfunction,
bcall, bindex,
cases, cases,
clauses, clauses,
location); location);
...@@ -4943,6 +4965,7 @@ Select_clauses::get_backend(Translate_context* context, ...@@ -4943,6 +4965,7 @@ Select_clauses::get_backend(Translate_context* context,
return context->backend()->statement_list(statements); return context->backend()->statement_list(statements);
} }
// Dump the AST representation for select clauses. // Dump the AST representation for select clauses.
void void
...@@ -4967,10 +4990,10 @@ Select_statement::break_label() ...@@ -4967,10 +4990,10 @@ Select_statement::break_label()
return this->break_label_; return this->break_label_;
} }
// Lower a select statement. This will still return a select // Lower a select statement. This will return a block containing this
// statement, but it will be modified to implement the order of // select statement. The block will implement the order of evaluation
// evaluation rules, and to include the send and receive statements as // rules, include the send and receive statements as explicit
// explicit statements in the clauses. // statements in the clauses, and call the runtime selectgo function.
Statement* Statement*
Select_statement::do_lower(Gogo* gogo, Named_object* function, Select_statement::do_lower(Gogo* gogo, Named_object* function,
...@@ -4983,30 +5006,69 @@ Select_statement::do_lower(Gogo* gogo, Named_object* function, ...@@ -4983,30 +5006,69 @@ Select_statement::do_lower(Gogo* gogo, Named_object* function,
Block* b = new Block(enclosing, loc); Block* b = new Block(enclosing, loc);
go_assert(this->sel_ == NULL);
int ncases = this->clauses_->size(); int ncases = this->clauses_->size();
Type* selstruct_type = Channel_type::select_type(ncases); Type* scase_type = Channel_type::select_case_type();
this->sel_ = Statement::make_temporary(selstruct_type, NULL, loc); Expression* ncases_expr =
b->add_statement(this->sel_); Expression::make_integer_ul(ncases, NULL,
Linemap::predeclared_location());
Array_type* scases_type = Type::make_array_type(scase_type, ncases_expr);
scases_type->set_is_array_incomparable();
Temporary_statement* scases = Statement::make_temporary(scases_type, NULL,
loc);
b->add_statement(scases);
int64_t selstruct_size; Expression* ncases2_expr =
if (!selstruct_type->backend_type_size(gogo, &selstruct_size)) Expression::make_integer_ul(ncases * 2, NULL,
{ Linemap::predeclared_location());
go_assert(saw_errors()); Type* uint16_type = Type::lookup_integer_type("uint16");
return Statement::make_error_statement(loc); Array_type* order_type = Type::make_array_type(uint16_type, ncases2_expr);
} order_type->set_is_array_incomparable();
Temporary_statement* order = Statement::make_temporary(order_type, NULL,
loc);
b->add_statement(order);
Expression* ref = Expression::make_temporary_reference(this->sel_, loc); Type* int_type = Type::lookup_integer_type("int");
ref = Expression::make_unary(OPERATOR_AND, ref, loc); this->index_ = Statement::make_temporary(int_type, NULL, loc);
Expression* selstruct_size_expr = b->add_statement(this->index_);
Expression::make_integer_int64(selstruct_size, NULL, loc);
Expression* size_expr = Expression::make_integer_ul(ncases, NULL, loc); Type* bool_type = Type::lookup_bool_type();
Expression* call = Runtime::make_call(Runtime::NEWSELECT, loc, 3, Temporary_statement* recvok = Statement::make_temporary(bool_type, NULL,
ref, selstruct_size_expr, size_expr); loc);
b->add_statement(Statement::make_statement(call, true)); b->add_statement(recvok);
// Initialize the scases array.
this->clauses_->lower(gogo, function, b, scases, recvok);
// Build the call to selectgo. Later, in do_get_backend, we will
// build a switch on the result that branches to the various cases.
Expression* scases_ref = Expression::make_temporary_reference(scases, loc);
scases_ref = Expression::make_unary(OPERATOR_AND, scases_ref, loc);
Type* unsafe_pointer_type = Type::make_pointer_type(Type::make_void_type());
scases_ref = Expression::make_cast(unsafe_pointer_type, scases_ref, loc);
Expression* order_ref = Expression::make_temporary_reference(order, loc);
order_ref = Expression::make_unary(OPERATOR_AND, order_ref, loc);
order_ref = Expression::make_cast(unsafe_pointer_type, order_ref, loc);
Expression* count_expr = Expression::make_integer_ul(ncases, int_type, loc);
Call_expression* call = Runtime::make_call(Runtime::SELECTGO, loc, 3,
scases_ref, order_ref,
count_expr);
Expression* result = Expression::make_call_result(call, 0);
Expression* ref = Expression::make_temporary_reference(this->index_, loc);
Statement* s = Statement::make_assignment(ref, result, loc);
b->add_statement(s);
result = Expression::make_call_result(call, 1);
ref = Expression::make_temporary_reference(recvok, loc);
s = Statement::make_assignment(ref, result, loc);
b->add_statement(s);
this->clauses_->lower(gogo, function, b, this->sel_);
this->is_lowered_ = true; this->is_lowered_ = true;
b->add_statement(this); b->add_statement(this);
...@@ -5031,8 +5093,8 @@ Select_statement::do_may_fall_through() const ...@@ -5031,8 +5093,8 @@ Select_statement::do_may_fall_through() const
Bstatement* Bstatement*
Select_statement::do_get_backend(Translate_context* context) Select_statement::do_get_backend(Translate_context* context)
{ {
return this->clauses_->get_backend(context, this->sel_, this->break_label(), return this->clauses_->get_backend(context, this->index_,
this->location()); this->break_label(), this->location());
} }
// Dump the AST representation for a select statement. // Dump the AST representation for a select statement.
......
...@@ -622,7 +622,8 @@ class Temporary_statement : public Statement ...@@ -622,7 +622,8 @@ class Temporary_statement : public Statement
public: public:
Temporary_statement(Type* type, Expression* init, Location location) Temporary_statement(Type* type, Expression* init, Location location)
: Statement(STATEMENT_TEMPORARY, location), : Statement(STATEMENT_TEMPORARY, location),
type_(type), init_(init), bvariable_(NULL), is_address_taken_(false) type_(type), init_(init), bvariable_(NULL), is_address_taken_(false),
value_escapes_(false)
{ } { }
// Return the type of the temporary variable. // Return the type of the temporary variable.
...@@ -640,6 +641,16 @@ class Temporary_statement : public Statement ...@@ -640,6 +641,16 @@ class Temporary_statement : public Statement
set_is_address_taken() set_is_address_taken()
{ this->is_address_taken_ = true; } { this->is_address_taken_ = true; }
// Whether the value escapes.
bool
value_escapes() const
{ return this->value_escapes_; }
// Record that the value escapes.
void
set_value_escapes()
{ this->value_escapes_ = true; }
// Return the temporary variable. This should not be called until // Return the temporary variable. This should not be called until
// after the statement itself has been converted. // after the statement itself has been converted.
Bvariable* Bvariable*
...@@ -676,6 +687,9 @@ class Temporary_statement : public Statement ...@@ -676,6 +687,9 @@ class Temporary_statement : public Statement
Bvariable* bvariable_; Bvariable* bvariable_;
// True if something takes the address of this temporary variable. // True if something takes the address of this temporary variable.
bool is_address_taken_; bool is_address_taken_;
// True if the value assigned to this temporary variable escapes.
// This is used for select statements.
bool value_escapes_;
}; };
// A variable declaration. This marks the point in the code where a // A variable declaration. This marks the point in the code where a
...@@ -851,7 +865,7 @@ class Send_statement : public Statement ...@@ -851,7 +865,7 @@ class Send_statement : public Statement
Expression* Expression*
channel() channel()
{ return this->channel_; } { return this->channel_; }
Expression* Expression*
val() val()
...@@ -924,7 +938,8 @@ class Select_clauses ...@@ -924,7 +938,8 @@ class Select_clauses
// Lower statements. // Lower statements.
void void
lower(Gogo*, Named_object*, Block*, Temporary_statement*); lower(Gogo*, Named_object*, Block*, Temporary_statement*,
Temporary_statement*);
// Determine types. // Determine types.
void void
...@@ -941,7 +956,7 @@ class Select_clauses ...@@ -941,7 +956,7 @@ class Select_clauses
// Convert to the backend representation. // Convert to the backend representation.
Bstatement* Bstatement*
get_backend(Translate_context*, Temporary_statement* sel, get_backend(Translate_context*, Temporary_statement* index,
Unnamed_label* break_label, Location); Unnamed_label* break_label, Location);
// Dump AST representation. // Dump AST representation.
...@@ -974,7 +989,8 @@ class Select_clauses ...@@ -974,7 +989,8 @@ class Select_clauses
// Lower statements. // Lower statements.
void void
lower(Gogo*, Named_object*, Block*, Temporary_statement*); lower(Gogo*, Named_object*, Block*, Temporary_statement*, size_t,
Temporary_statement*);
// Determine types. // Determine types.
void void
...@@ -1027,6 +1043,14 @@ class Select_clauses ...@@ -1027,6 +1043,14 @@ class Select_clauses
dump_clause(Ast_dump_context*) const; dump_clause(Ast_dump_context*) const;
private: private:
// These values must match the values in libgo/go/runtime/select.go.
enum
{
caseRecv = 1,
caseSend = 2,
caseDefault = 3,
};
void void
lower_default(Block*, Expression*); lower_default(Block*, Expression*);
...@@ -1034,7 +1058,11 @@ class Select_clauses ...@@ -1034,7 +1058,11 @@ class Select_clauses
lower_send(Block*, Expression*, Expression*); lower_send(Block*, Expression*, Expression*);
void void
lower_recv(Gogo*, Named_object*, Block*, Expression*, Expression*); lower_recv(Gogo*, Named_object*, Block*, Expression*, Expression*,
Temporary_statement*);
void
set_case(Block*, Expression*, Expression*, Expression*, int);
// The channel. // The channel.
Expression* channel_; Expression* channel_;
...@@ -1072,7 +1100,7 @@ class Select_statement : public Statement ...@@ -1072,7 +1100,7 @@ class Select_statement : public Statement
public: public:
Select_statement(Location location) Select_statement(Location location)
: Statement(STATEMENT_SELECT, location), : Statement(STATEMENT_SELECT, location),
clauses_(NULL), sel_(NULL), break_label_(NULL), is_lowered_(false) clauses_(NULL), index_(NULL), break_label_(NULL), is_lowered_(false)
{ } { }
// Add the clauses. // Add the clauses.
...@@ -1115,8 +1143,8 @@ class Select_statement : public Statement ...@@ -1115,8 +1143,8 @@ class Select_statement : public Statement
private: private:
// The select clauses. // The select clauses.
Select_clauses* clauses_; Select_clauses* clauses_;
// A temporary which holds the select structure we build up at runtime. // A temporary that holds the index value returned by selectgo.
Temporary_statement* sel_; Temporary_statement* index_;
// The break label. // The break label.
Unnamed_label* break_label_; Unnamed_label* break_label_;
// Whether this statement has been lowered. // Whether this statement has been lowered.
...@@ -1609,7 +1637,7 @@ class Case_clauses ...@@ -1609,7 +1637,7 @@ class Case_clauses
// Dump the AST representation to a dump context. // Dump the AST representation to a dump context.
void void
dump_clauses(Ast_dump_context*) const; dump_clauses(Ast_dump_context*) const;
private: private:
// For a constant switch we need to keep a record of constants we // For a constant switch we need to keep a record of constants we
// have already seen. // have already seen.
...@@ -1683,7 +1711,7 @@ class Case_clauses ...@@ -1683,7 +1711,7 @@ class Case_clauses
// Dump the AST representation to a dump context. // Dump the AST representation to a dump context.
void void
dump_clause(Ast_dump_context*) const; dump_clause(Ast_dump_context*) const;
private: private:
// The list of case expressions. // The list of case expressions.
Expression_list* cases_; Expression_list* cases_;
......
...@@ -8557,51 +8557,29 @@ Channel_type::do_import(Import* imp) ...@@ -8557,51 +8557,29 @@ Channel_type::do_import(Import* imp)
return Type::make_channel_type(may_send, may_receive, element_type); return Type::make_channel_type(may_send, may_receive, element_type);
} }
// Return the type to manage a select statement with ncases case // Return the type that the runtime package uses for one case of a
// statements. A value of this type is allocated on the stack. This // select statement. An array of values of this type is allocated on
// must match the type hselect in libgo/go/runtime/select.go. // the stack. This must match scase in libgo/go/runtime/select.go.
Type* Type*
Channel_type::select_type(int ncases) Channel_type::select_case_type()
{ {
Type* unsafe_pointer_type = Type::make_pointer_type(Type::make_void_type());
Type* uint16_type = Type::lookup_integer_type("uint16");
static Struct_type* scase_type; static Struct_type* scase_type;
if (scase_type == NULL) if (scase_type == NULL)
{ {
Type* uintptr_type = Type::lookup_integer_type("uintptr"); Type* unsafe_pointer_type =
Type* uint64_type = Type::lookup_integer_type("uint64"); Type::make_pointer_type(Type::make_void_type());
Type* uint16_type = Type::lookup_integer_type("uint16");
Type* int64_type = Type::lookup_integer_type("int64");
scase_type = scase_type =
Type::make_builtin_struct_type(7, Type::make_builtin_struct_type(4,
"c", unsafe_pointer_type,
"elem", unsafe_pointer_type, "elem", unsafe_pointer_type,
"chan", unsafe_pointer_type,
"pc", uintptr_type,
"kind", uint16_type, "kind", uint16_type,
"index", uint16_type, "releasetime", int64_type);
"receivedp", unsafe_pointer_type,
"releasetime", uint64_type);
scase_type->set_is_struct_incomparable(); scase_type->set_is_struct_incomparable();
} }
return scase_type;
Expression* ncases_expr =
Expression::make_integer_ul(ncases, NULL, Linemap::predeclared_location());
Array_type* scases = Type::make_array_type(scase_type, ncases_expr);
scases->set_is_array_incomparable();
Array_type* order = Type::make_array_type(uint16_type, ncases_expr);
order->set_is_array_incomparable();
Struct_type* ret =
Type::make_builtin_struct_type(7,
"tcase", uint16_type,
"ncase", uint16_type,
"pollorder", unsafe_pointer_type,
"lockorder", unsafe_pointer_type,
"scase", scases,
"lockorderarr", order,
"pollorderarr", order);
ret->set_is_struct_incomparable();
return ret;
} }
// Make a new channel type. // Make a new channel type.
......
...@@ -2981,7 +2981,7 @@ class Channel_type : public Type ...@@ -2981,7 +2981,7 @@ class Channel_type : public Type
make_chan_type_descriptor_type(); make_chan_type_descriptor_type();
static Type* static Type*
select_type(int ncases); select_case_type();
protected: protected:
int int
......
...@@ -7,146 +7,36 @@ package runtime ...@@ -7,146 +7,36 @@ package runtime
// This file contains the implementation of Go select statements. // This file contains the implementation of Go select statements.
import ( import (
"runtime/internal/sys"
"unsafe" "unsafe"
) )
// For gccgo, use go:linkname to rename compiler-called functions to // For gccgo, use go:linkname to rename compiler-called functions to
// themselves, so that the compiler will export them. // themselves, so that the compiler will export them.
// //
//go:linkname newselect runtime.newselect
//go:linkname selectdefault runtime.selectdefault
//go:linkname selectsend runtime.selectsend
//go:linkname selectrecv runtime.selectrecv
//go:linkname selectgo runtime.selectgo //go:linkname selectgo runtime.selectgo
const debugSelect = false const debugSelect = false
// scase.kind values.
// Known to compiler.
// Changes here must also be made in src/cmd/compile/internal/gc/select.go's walkselect.
const ( const (
// scase.kind
caseNil = iota caseNil = iota
caseRecv caseRecv
caseSend caseSend
caseDefault caseDefault
) )
// Select statement header.
// Known to compiler.
// Changes here must also be made in src/cmd/internal/gc/select.go's selecttype.
type hselect struct {
tcase uint16 // total count of scase[]
ncase uint16 // currently filled scase[]
pollorder *uint16 // case poll order
lockorder *uint16 // channel lock order
scase [1]scase // one per case (in order of appearance)
}
// Select case descriptor. // Select case descriptor.
// Known to compiler. // Known to compiler.
// Changes here must also be made in src/cmd/internal/gc/select.go's selecttype. // Changes here must also be made in src/cmd/internal/gc/select.go's scasetype.
type scase struct { type scase struct {
elem unsafe.Pointer // data element
c *hchan // chan c *hchan // chan
pc uintptr // return pc (for race detector / msan) elem unsafe.Pointer // data element
kind uint16 kind uint16
receivedp *bool // pointer to received bool, if any
releasetime int64 releasetime int64
} }
var (
chansendpc = funcPC(chansend)
chanrecvpc = funcPC(chanrecv)
)
func selectsize(size uintptr) uintptr {
selsize := unsafe.Sizeof(hselect{}) +
(size-1)*unsafe.Sizeof(hselect{}.scase[0]) +
size*unsafe.Sizeof(*hselect{}.lockorder) +
size*unsafe.Sizeof(*hselect{}.pollorder)
return round(selsize, sys.Int64Align)
}
func newselect(sel *hselect, selsize int64, size int32) {
if selsize != int64(selectsize(uintptr(size))) {
print("runtime: bad select size ", selsize, ", want ", selectsize(uintptr(size)), "\n")
throw("bad select size")
}
if size != int32(uint16(size)) {
throw("select size too large")
}
sel.tcase = uint16(size)
sel.ncase = 0
sel.lockorder = (*uint16)(add(unsafe.Pointer(&sel.scase), uintptr(size)*unsafe.Sizeof(hselect{}.scase[0])))
sel.pollorder = (*uint16)(add(unsafe.Pointer(sel.lockorder), uintptr(size)*unsafe.Sizeof(*hselect{}.lockorder)))
// For gccgo the temporary variable will not have been zeroed.
memclrNoHeapPointers(unsafe.Pointer(&sel.scase), uintptr(size)*unsafe.Sizeof(hselect{}.scase[0])+uintptr(size)*unsafe.Sizeof(*hselect{}.lockorder)+uintptr(size)*unsafe.Sizeof(*hselect{}.pollorder))
if debugSelect {
print("newselect s=", sel, " size=", size, "\n")
}
}
func selectsend(sel *hselect, c *hchan, elem unsafe.Pointer) {
pc := getcallerpc()
i := sel.ncase
if i >= sel.tcase {
throw("selectsend: too many cases")
}
sel.ncase = i + 1
if c == nil {
return
}
cas := (*scase)(add(unsafe.Pointer(&sel.scase), uintptr(i)*unsafe.Sizeof(sel.scase[0])))
cas.pc = pc
cas.c = c
cas.kind = caseSend
cas.elem = elem
if debugSelect {
print("selectsend s=", sel, " pc=", hex(cas.pc), " chan=", cas.c, "\n")
}
}
func selectrecv(sel *hselect, c *hchan, elem unsafe.Pointer, received *bool) {
pc := getcallerpc()
i := sel.ncase
if i >= sel.tcase {
throw("selectrecv: too many cases")
}
sel.ncase = i + 1
if c == nil {
return
}
cas := (*scase)(add(unsafe.Pointer(&sel.scase), uintptr(i)*unsafe.Sizeof(sel.scase[0])))
cas.pc = pc
cas.c = c
cas.kind = caseRecv
cas.elem = elem
cas.receivedp = received
if debugSelect {
print("selectrecv s=", sel, " pc=", hex(cas.pc), " chan=", cas.c, "\n")
}
}
func selectdefault(sel *hselect) {
pc := getcallerpc()
i := sel.ncase
if i >= sel.tcase {
throw("selectdefault: too many cases")
}
sel.ncase = i + 1
cas := (*scase)(add(unsafe.Pointer(&sel.scase), uintptr(i)*unsafe.Sizeof(sel.scase[0])))
cas.pc = pc
cas.c = nil
cas.kind = caseDefault
if debugSelect {
print("selectdefault s=", sel, " pc=", hex(cas.pc), "\n")
}
}
func sellock(scases []scase, lockorder []uint16) { func sellock(scases []scase, lockorder []uint16) {
var c *hchan var c *hchan
for _, o := range lockorder { for _, o := range lockorder {
...@@ -209,26 +99,39 @@ func block() { ...@@ -209,26 +99,39 @@ func block() {
// selectgo implements the select statement. // selectgo implements the select statement.
// //
// *sel is on the current goroutine's stack (regardless of any // cas0 points to an array of type [ncases]scase, and order0 points to
// escaping in selectgo). // an array of type [2*ncases]uint16. Both reside on the goroutine's
// stack (regardless of any escaping in selectgo).
// //
// selectgo returns the index of the chosen scase, which matches the // selectgo returns the index of the chosen scase, which matches the
// ordinal position of its respective select{recv,send,default} call. // ordinal position of its respective select{recv,send,default} call.
func selectgo(sel *hselect) int { // Also, if the chosen scase was a receive operation, it returns whether
// a value was received.
func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) {
if debugSelect { if debugSelect {
print("select: sel=", sel, "\n") print("select: cas0=", cas0, "\n")
}
if sel.ncase != sel.tcase {
throw("selectgo: case count mismatch")
} }
scaseslice := slice{unsafe.Pointer(&sel.scase), int(sel.ncase), int(sel.ncase)} cas1 := (*[1 << 16]scase)(unsafe.Pointer(cas0))
scases := *(*[]scase)(unsafe.Pointer(&scaseslice)) order1 := (*[1 << 17]uint16)(unsafe.Pointer(order0))
scases := cas1[:ncases:ncases]
pollorder := order1[:ncases:ncases]
lockorder := order1[ncases:][:ncases:ncases]
// Replace send/receive cases involving nil channels with
// caseNil so logic below can assume non-nil channel.
for i := range scases {
cas := &scases[i]
if cas.c == nil && cas.kind != caseDefault {
*cas = scase{}
}
}
var t0 int64 var t0 int64
if blockprofilerate > 0 { if blockprofilerate > 0 {
t0 = cputicks() t0 = cputicks()
for i := 0; i < int(sel.ncase); i++ { for i := 0; i < ncases; i++ {
scases[i].releasetime = -1 scases[i].releasetime = -1
} }
} }
...@@ -241,10 +144,13 @@ func selectgo(sel *hselect) int { ...@@ -241,10 +144,13 @@ func selectgo(sel *hselect) int {
// cases correctly, and they are rare enough not to bother // cases correctly, and they are rare enough not to bother
// optimizing (and needing to test). // optimizing (and needing to test).
// needed for gccgo, which doesn't zero pollorder
if ncases > 0 {
pollorder[0] = 0
}
// generate permuted order // generate permuted order
pollslice := slice{unsafe.Pointer(sel.pollorder), int(sel.ncase), int(sel.ncase)} for i := 1; i < ncases; i++ {
pollorder := *(*[]uint16)(unsafe.Pointer(&pollslice))
for i := 1; i < int(sel.ncase); i++ {
j := fastrandn(uint32(i + 1)) j := fastrandn(uint32(i + 1))
pollorder[i] = pollorder[j] pollorder[i] = pollorder[j]
pollorder[j] = uint16(i) pollorder[j] = uint16(i)
...@@ -252,9 +158,7 @@ func selectgo(sel *hselect) int { ...@@ -252,9 +158,7 @@ func selectgo(sel *hselect) int {
// sort the cases by Hchan address to get the locking order. // sort the cases by Hchan address to get the locking order.
// simple heap sort, to guarantee n log n time and constant stack footprint. // simple heap sort, to guarantee n log n time and constant stack footprint.
lockslice := slice{unsafe.Pointer(sel.lockorder), int(sel.ncase), int(sel.ncase)} for i := 0; i < ncases; i++ {
lockorder := *(*[]uint16)(unsafe.Pointer(&lockslice))
for i := 0; i < int(sel.ncase); i++ {
j := i j := i
// Start with the pollorder to permute cases on the same channel. // Start with the pollorder to permute cases on the same channel.
c := scases[pollorder[i]].c c := scases[pollorder[i]].c
...@@ -265,7 +169,7 @@ func selectgo(sel *hselect) int { ...@@ -265,7 +169,7 @@ func selectgo(sel *hselect) int {
} }
lockorder[j] = pollorder[i] lockorder[j] = pollorder[i]
} }
for i := int(sel.ncase) - 1; i >= 0; i-- { for i := ncases - 1; i >= 0; i-- {
o := lockorder[i] o := lockorder[i]
c := scases[o].c c := scases[o].c
lockorder[i] = lockorder[0] lockorder[i] = lockorder[0]
...@@ -287,14 +191,15 @@ func selectgo(sel *hselect) int { ...@@ -287,14 +191,15 @@ func selectgo(sel *hselect) int {
} }
lockorder[j] = o lockorder[j] = o
} }
/*
for i := 0; i+1 < int(sel.ncase); i++ { if debugSelect {
for i := 0; i+1 < ncases; i++ {
if scases[lockorder[i]].c.sortkey() > scases[lockorder[i+1]].c.sortkey() { if scases[lockorder[i]].c.sortkey() > scases[lockorder[i+1]].c.sortkey() {
print("i=", i, " x=", lockorder[i], " y=", lockorder[i+1], "\n") print("i=", i, " x=", lockorder[i], " y=", lockorder[i+1], "\n")
throw("select: broken sort") throw("select: broken sort")
} }
} }
*/ }
// lock all the channels involved in the select // lock all the channels involved in the select
sellock(scases, lockorder) sellock(scases, lockorder)
...@@ -316,7 +221,8 @@ loop: ...@@ -316,7 +221,8 @@ loop:
var dfl *scase var dfl *scase
var casi int var casi int
var cas *scase var cas *scase
for i := 0; i < int(sel.ncase); i++ { var recvOK bool
for i := 0; i < ncases; i++ {
casi = int(pollorder[i]) casi = int(pollorder[i])
cas = &scases[casi] cas = &scases[casi]
c = cas.c c = cas.c
...@@ -338,9 +244,6 @@ loop: ...@@ -338,9 +244,6 @@ loop:
} }
case caseSend: case caseSend:
if raceenabled {
racereadpc(unsafe.Pointer(c), cas.pc, chansendpc)
}
if c.closed != 0 { if c.closed != 0 {
goto sclose goto sclose
} }
...@@ -469,26 +372,11 @@ loop: ...@@ -469,26 +372,11 @@ loop:
c = cas.c c = cas.c
if debugSelect { if debugSelect {
print("wait-return: sel=", sel, " c=", c, " cas=", cas, " kind=", cas.kind, "\n") print("wait-return: cas0=", cas0, " c=", c, " cas=", cas, " kind=", cas.kind, "\n")
} }
if cas.kind == caseRecv && cas.receivedp != nil { if cas.kind == caseRecv {
*cas.receivedp = true recvOK = true
}
if raceenabled {
if cas.kind == caseRecv && cas.elem != nil {
raceWriteObjectPC(c.elemtype, cas.elem, cas.pc, chanrecvpc)
} else if cas.kind == caseSend {
raceReadObjectPC(c.elemtype, cas.elem, cas.pc, chansendpc)
}
}
if msanenabled {
if cas.kind == caseRecv && cas.elem != nil {
msanwrite(cas.elem, c.elemtype.size)
} else if cas.kind == caseSend {
msanread(cas.elem, c.elemtype.size)
}
} }
selunlock(scases, lockorder) selunlock(scases, lockorder)
...@@ -496,19 +384,7 @@ loop: ...@@ -496,19 +384,7 @@ loop:
bufrecv: bufrecv:
// can receive from buffer // can receive from buffer
if raceenabled { recvOK = true
if cas.elem != nil {
raceWriteObjectPC(c.elemtype, cas.elem, cas.pc, chanrecvpc)
}
raceacquire(chanbuf(c, c.recvx))
racerelease(chanbuf(c, c.recvx))
}
if msanenabled && cas.elem != nil {
msanwrite(cas.elem, c.elemtype.size)
}
if cas.receivedp != nil {
*cas.receivedp = true
}
qp = chanbuf(c, c.recvx) qp = chanbuf(c, c.recvx)
if cas.elem != nil { if cas.elem != nil {
typedmemmove(c.elemtype, cas.elem, qp) typedmemmove(c.elemtype, cas.elem, qp)
...@@ -524,14 +400,6 @@ bufrecv: ...@@ -524,14 +400,6 @@ bufrecv:
bufsend: bufsend:
// can send to buffer // can send to buffer
if raceenabled {
raceacquire(chanbuf(c, c.sendx))
racerelease(chanbuf(c, c.sendx))
raceReadObjectPC(c.elemtype, cas.elem, cas.pc, chansendpc)
}
if msanenabled {
msanread(cas.elem, c.elemtype.size)
}
typedmemmove(c.elemtype, chanbuf(c, c.sendx), cas.elem) typedmemmove(c.elemtype, chanbuf(c, c.sendx), cas.elem)
c.sendx++ c.sendx++
if c.sendx == c.dataqsiz { if c.sendx == c.dataqsiz {
...@@ -545,19 +413,15 @@ recv: ...@@ -545,19 +413,15 @@ recv:
// can receive from sleeping sender (sg) // can receive from sleeping sender (sg)
recv(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2) recv(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2)
if debugSelect { if debugSelect {
print("syncrecv: sel=", sel, " c=", c, "\n") print("syncrecv: cas0=", cas0, " c=", c, "\n")
}
if cas.receivedp != nil {
*cas.receivedp = true
} }
recvOK = true
goto retc goto retc
rclose: rclose:
// read at end of closed channel // read at end of closed channel
selunlock(scases, lockorder) selunlock(scases, lockorder)
if cas.receivedp != nil { recvOK = false
*cas.receivedp = false
}
if cas.elem != nil { if cas.elem != nil {
typedmemclr(c.elemtype, cas.elem) typedmemclr(c.elemtype, cas.elem)
} }
...@@ -568,15 +432,9 @@ rclose: ...@@ -568,15 +432,9 @@ rclose:
send: send:
// can send to a sleeping receiver (sg) // can send to a sleeping receiver (sg)
if raceenabled {
raceReadObjectPC(c.elemtype, cas.elem, cas.pc, chansendpc)
}
if msanenabled {
msanread(cas.elem, c.elemtype.size)
}
send(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2) send(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2)
if debugSelect { if debugSelect {
print("syncsend: sel=", sel, " c=", c, "\n") print("syncsend: cas0=", cas0, " c=", c, "\n")
} }
goto retc goto retc
...@@ -591,7 +449,7 @@ retc: ...@@ -591,7 +449,7 @@ retc:
checkPreempt() checkPreempt()
} }
return casi return casi, recvOK
sclose: sclose:
// send on closed channel // send on closed channel
...@@ -625,27 +483,25 @@ const ( ...@@ -625,27 +483,25 @@ const (
) )
//go:linkname reflect_rselect reflect.rselect //go:linkname reflect_rselect reflect.rselect
func reflect_rselect(cases []runtimeSelect) (chosen int, recvOK bool) { func reflect_rselect(cases []runtimeSelect) (int, bool) {
// flagNoScan is safe here, because all objects are also referenced from cases. if len(cases) == 0 {
size := selectsize(uintptr(len(cases))) block()
sel := (*hselect)(mallocgc(size, nil, true)) }
newselect(sel, int64(size), int32(len(cases))) sel := make([]scase, len(cases))
r := new(bool) order := make([]uint16, 2*len(cases))
for i := range cases { for i := range cases {
rc := &cases[i] rc := &cases[i]
switch rc.dir { switch rc.dir {
case selectDefault: case selectDefault:
selectdefault(sel) sel[i] = scase{kind: caseDefault}
case selectSend: case selectSend:
selectsend(sel, rc.ch, rc.val) sel[i] = scase{kind: caseSend, c: rc.ch, elem: rc.val}
case selectRecv: case selectRecv:
selectrecv(sel, rc.ch, rc.val, r) sel[i] = scase{kind: caseRecv, c: rc.ch, elem: rc.val}
} }
} }
chosen = selectgo(sel) return selectgo(&sel[0], &order[0], len(cases))
recvOK = *r
return
} }
func (q *waitq) dequeueSudoG(sgp *sudog) { func (q *waitq) dequeueSudoG(sgp *sudog) {
......
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