This patch by Chris Manghane implements the assign phase of escape analysis. This builds a graph of assignments within a function. This is just another step toward escape analysis; it is not yet enabled.
Ian
Index: gcc/go/gofrontend/MERGE =================================================================== --- gcc/go/gofrontend/MERGE (revision 237286) +++ gcc/go/gofrontend/MERGE (working copy) @@ -1,4 +1,4 @@ -054ff1ece3dd5888a445efeaf3ae197b16d4186f +f768153eb2a7a72587c9c0997955cdbbc70322d0 The first line of this file holds the git revision number of the last merge done from the gofrontend repository. Index: gcc/go/gofrontend/escape.cc =================================================================== --- gcc/go/gofrontend/escape.cc (revision 236804) +++ gcc/go/gofrontend/escape.cc (working copy) @@ -88,6 +88,55 @@ Node::set_encoding(int enc) } bool +Node::is_big(Escape_context* context) const +{ + Type* t = this->type(); + if (t == NULL + || t->is_call_multiple_result_type() + || t->is_sink_type() + || t->is_void_type() + || t->is_abstract()) + return false; + + int64_t size; + bool ok = t->backend_type_size(context->gogo(), &size); + bool big = ok && (size < 0 || size > 10 * 1024 * 1024); + + if (this->expr() != NULL) + { + if (this->expr()->allocation_expression() != NULL) + { + ok = t->deref()->backend_type_size(context->gogo(), &size); + big = big || size <= 0 || size >= (1 << 16); + } + else if (this->expr()->call_expression() != NULL) + { + Call_expression* call = this->expr()->call_expression(); + Func_expression* fn = call->fn()->func_expression(); + if (fn != NULL + && fn->is_runtime_function() + && (fn->runtime_code() == Runtime::MAKESLICE1 + || fn->runtime_code() == Runtime::MAKESLICE2 + || fn->runtime_code() == Runtime::MAKESLICE1BIG + || fn->runtime_code() == Runtime::MAKESLICE2BIG)) + { + // Second argument is length. + Expression_list::iterator p = call->args()->begin(); + ++p; + + Numeric_constant nc; + unsigned long v; + if ((*p)->numeric_constant_value(&nc) + && nc.to_unsigned_long(&v) == Numeric_constant::NC_UL_VALID) + big = big || v >= (1 << 16); + } + } + } + + return big; +} + +bool Node::is_sink() const { if (this->object() != NULL @@ -161,6 +210,37 @@ Node::max_encoding(int e, int etype) // Return a modified encoding for an input parameter that flows into an // output parameter. +int +Node::note_inout_flows(int e, int index, Level level) +{ + // Flow+level is encoded in two bits. + // 00 = not flow, xx = level+1 for 0 <= level <= maxEncodedLevel. + // 16 bits for Esc allows 6x2bits or 4x3bits or 3x4bits if additional + // information would be useful. + if (level.value() <= 0 && level.suffix_value() > 0) + return Node::max_encoding(e|ESCAPE_CONTENT_ESCAPES, Node::ESCAPE_NONE); + if (level.value() < 0) + return Node::ESCAPE_HEAP; + if (level.value() > ESCAPE_MAX_ENCODED_LEVEL) + level = Level::From(ESCAPE_MAX_ENCODED_LEVEL); + + int encoded = level.value() + 1; + int shift = ESCAPE_BITS_PER_OUTPUT_IN_TAG * index + ESCAPE_RETURN_BITS; + int old = (e >> shift) & ESCAPE_BITS_MASK_FOR_TAG; + if (old == 0 + || (encoded != 0 && encoded < old)) + old = encoded; + + int encoded_flow = old << shift; + if (((encoded_flow >> shift) & ESCAPE_BITS_MASK_FOR_TAG) != old) + { + // Failed to encode. Put this on the heap. + return Node::ESCAPE_HEAP; + } + + return (e & ~(ESCAPE_BITS_MASK_FOR_TAG << shift)) | encoded_flow; +} + // Class Escape_context. Escape_context::Escape_context(Gogo* gogo, bool recursive) @@ -493,14 +573,1258 @@ Gogo::discover_analysis_sets() this->traverse(&ead); } +// Traverse all label and goto statements and mark the underlying label +// as looping or not looping. + +class Escape_analysis_loop : public Traverse +{ + public: + Escape_analysis_loop() + : Traverse(traverse_statements) + { } + + int + statement(Block*, size_t*, Statement*); +}; + +int +Escape_analysis_loop::statement(Block*, size_t*, Statement* s) +{ + if (s->label_statement() != NULL) + s->label_statement()->label()->set_nonlooping(); + else if (s->goto_statement() != NULL) + { + if (s->goto_statement()->label()->nonlooping()) + s->goto_statement()->label()->set_looping(); + } + return TRAVERSE_CONTINUE; +} + +// Traversal class used to look at all interesting statements within a function +// in order to build a connectivity graph between all nodes within a context's +// scope. + +class Escape_analysis_assign : public Traverse +{ +public: + Escape_analysis_assign(Escape_context* context, Named_object* fn) + : Traverse(traverse_statements + | traverse_expressions), + context_(context), fn_(fn) + { } + + // Model statements within a function as assignments and flows between nodes. + int + statement(Block*, size_t*, Statement*); + + // Model expressions within a function as assignments and flows between nodes. + int + expression(Expression**); + + // Model calls within a function as assignments and flows between arguments + // and results. + void + call(Call_expression* call); + + // Model the assignment of DST to SRC. + void + assign(Node* dst, Node* src); + + // Model the assignment of DST to dereference of SRC. + void + assign_deref(Node* dst, Node* src); + + // Model the input-to-output assignment flow of one of a function call's + // arguments, where the flow is encoding in NOTE. + int + assign_from_note(std::string* note, const std::vector<Node*>& dsts, + Node* src); + + // Record the flow of SRC to DST in DST. + void + flows(Node* dst, Node* src); + +private: + // The escape context for this set of functions. + Escape_context* context_; + // The current function being analyzed. + Named_object* fn_; +}; + +// Model statements within a function as assignments and flows between nodes. + +int +Escape_analysis_assign::statement(Block*, size_t*, Statement* s) +{ + // Adjust the loop depth as we enter/exit blocks related to for statements. + bool is_for_statement = (s->is_block_statement() + && s->block_statement()->is_lowered_for_statement()); + if (is_for_statement) + this->context_->increase_loop_depth(); + + s->traverse_contents(this); + + if (is_for_statement) + this->context_->decrease_loop_depth(); + + switch (s->classification()) + { + case Statement::STATEMENT_VARIABLE_DECLARATION: + { + Named_object* var = s->variable_declaration_statement()->var(); + Node* var_node = Node::make_node(var); + Node::Escape_state* state = var_node->state(this->context_, NULL); + state->loop_depth = this->context_->loop_depth(); + + // Set the loop depth for this declaration. + if (var->is_variable() + && var->var_value()->init() != NULL) + { + Node* init_node = Node::make_node(var->var_value()->init()); + this->assign(var_node, init_node); + } + } + break; + + case Statement::STATEMENT_LABEL: + { + if (s->label_statement()->label()->looping()) + this->context_->increase_loop_depth(); + } + break; + + case Statement::STATEMENT_SWITCH: + case Statement::STATEMENT_TYPE_SWITCH: + // Want to model the assignment of each case variable to the switched upon + // variable. This should be lowered into assignment statements; nothing + // to here if that's the case. + // TODO(cmang): Verify. + break; + + case Statement::STATEMENT_ASSIGNMENT: + { + Assignment_statement* assn = s->assignment_statement(); + Node* lhs = Node::make_node(assn->lhs()); + Node* rhs = Node::make_node(assn->rhs()); + + // TODO(cmang): Add special case for escape analysis no-op: + // func (b *Buffer) Foo() { + // n, m := ... + // b.buf = b.buf[n:m] + // } + // This is okay for now, it just means b escapes; it is conservative. + this->assign(lhs, rhs); + } + break; + + case Statement::STATEMENT_SEND: + { + Node* sent_node = Node::make_node(s->send_statement()->val()); + this->assign(this->context_->sink(), sent_node); + } + break; + + case Statement::STATEMENT_DEFER: + if (this->context_->loop_depth() == 1) + break; + // fallthrough + + case Statement::STATEMENT_GO: + { + // Defer f(x) or go f(x). + // Both f and x escape to the heap. + Thunk_statement* thunk = s->thunk_statement(); + if (thunk->call()->call_expression() == NULL) + break; + + Call_expression* call = thunk->call()->call_expression(); + Node* func_node = Node::make_node(call->fn()); + this->assign(this->context_->sink(), func_node); + if (call->args() != NULL) + { + for (Expression_list::const_iterator p = call->args()->begin(); + p != call->args()->end(); + ++p) + { + Node* arg_node = Node::make_node(*p); + this->assign(this->context_->sink(), arg_node); + } + } + } + break; + + // TODO(cmang): Associate returned values with dummy return nodes. + + default: + break; + } + return TRAVERSE_SKIP_COMPONENTS; +} + +// Model expressions within a function as assignments and flows between nodes. + +int +Escape_analysis_assign::expression(Expression** pexpr) +{ + // Big stuff escapes unconditionally. + Node* n = Node::make_node(*pexpr); + if ((n->encoding() & ESCAPE_MASK) != int(Node::ESCAPE_HEAP) + && n->is_big(this->context_)) + { + n->set_encoding(Node::ESCAPE_HEAP); + (*pexpr)->address_taken(true); + this->assign(this->context_->sink(), n); + } + + if ((*pexpr)->func_expression() == NULL) + (*pexpr)->traverse_subexpressions(this); + + switch ((*pexpr)->classification()) + { + case Expression::EXPRESSION_CALL: + { + Call_expression* call = (*pexpr)->call_expression(); + this->call(call); + + Func_expression* fe = call->fn()->func_expression(); + if (fe != NULL && fe->is_runtime_function()) + { + switch (fe->runtime_code()) + { + case Runtime::PANIC: + { + // Argument could leak through recover. + Node* panic_arg = Node::make_node(call->args()->front()); + this->assign(this->context_->sink(), panic_arg); + } + break; + + case Runtime::APPEND: + { + // Unlike gc/esc.go, a call to append has already had its + // varargs lowered into a slice of arguments. + // The content of the appended slice leaks. + Node* appended = Node::make_node(call->args()->back()); + this->assign_deref(this->context_->sink(), appended); + + // The content of the original slice leaks as well. + Node* appendee = Node::make_node(call->args()->back()); + this->assign_deref(this->context_->sink(), appendee); + } + break; + + case Runtime::COPY: + { + // Lose track of the copied content. + Node* copied = Node::make_node(call->args()->back()); + this->assign_deref(this->context_->sink(), copied); + } + break; + + case Runtime::MAKECHAN: + case Runtime::MAKECHANBIG: + case Runtime::MAKEMAP: + case Runtime::MAKEMAPBIG: + case Runtime::MAKESLICE1: + case Runtime::MAKESLICE2: + case Runtime::MAKESLICE1BIG: + case Runtime::MAKESLICE2BIG: + case Runtime::BYTE_ARRAY_TO_STRING: + case Runtime::INT_ARRAY_TO_STRING: + case Runtime::STRING_TO_BYTE_ARRAY: + case Runtime::STRING_TO_INT_ARRAY: + case Runtime::STRING_PLUS: + case Runtime::CONSTRUCT_MAP: + case Runtime::INT_TO_STRING: + { + Node* runtime_node = Node::make_node(fe); + this->context_->track(runtime_node); + } + break; + + default: + break; + } + } + } + break; + + case Expression::EXPRESSION_ALLOCATION: + { + // Same as above; this is Runtime::NEW. + Node* alloc_node = Node::make_node(*pexpr); + this->context_->track(alloc_node); + } + break; + + case Expression::EXPRESSION_CONVERSION: + { + Type_conversion_expression* tce = (*pexpr)->conversion_expression(); + Node* tce_node = Node::make_node(tce); + Node* converted = Node::make_node(tce->expr()); + this->context_->track(tce_node); + + this->assign(tce_node, converted); + } + break; + + case Expression::EXPRESSION_FIXED_ARRAY_CONSTRUCTION: + case Expression::EXPRESSION_SLICE_CONSTRUCTION: + { + Node* array_node = Node::make_node(*pexpr); + if ((*pexpr)->slice_literal() != NULL) + this->context_->track(array_node); + + Expression_list* vals = ((*pexpr)->slice_literal() != NULL + ? (*pexpr)->slice_literal()->vals() + : (*pexpr)->array_literal()->vals()); + + if (vals != NULL) + { + // Connect the array to its values. + for (Expression_list::const_iterator p = vals->begin(); + p != vals->end(); + ++p) + if ((*p) != NULL) + this->assign(array_node, Node::make_node(*p)); + } + } + break; + + case Expression::EXPRESSION_STRUCT_CONSTRUCTION: + { + Node* struct_node = Node::make_node(*pexpr); + Expression_list* vals = (*pexpr)->struct_literal()->vals(); + if (vals != NULL) + { + // Connect the struct to its values. + for (Expression_list::const_iterator p = vals->begin(); + p != vals->end(); + ++p) + { + if ((*p) != NULL) + this->assign(struct_node, Node::make_node(*p)); + } + } + } + break; + + case Expression::EXPRESSION_HEAP: + { + Node* pointer_node = Node::make_node(*pexpr); + Node* lit_node = Node::make_node((*pexpr)->heap_expression()->expr()); + this->context_->track(pointer_node); + + // Connect pointer node to literal node; if the pointer node escapes, so + // does the literal node. + this->assign(pointer_node, lit_node); + } + break; + + case Expression::EXPRESSION_BOUND_METHOD: + { + Node* bound_node = Node::make_node(*pexpr); + this->context_->track(bound_node); + + Expression* obj = (*pexpr)->bound_method_expression()->first_argument(); + Node* obj_node = Node::make_node(obj); + + // A bound method implies the receiver will be used outside of the + // lifetime of the method in some way. We lose track of the receiver. + this->assign(this->context_->sink(), obj_node); + } + break; + + case Expression::EXPRESSION_MAP_CONSTRUCTION: + { + Map_construction_expression* mce = (*pexpr)->map_literal(); + Node* map_node = Node::make_node(mce); + this->context_->track(map_node); + + // All keys and values escape to memory. + if (mce->vals() != NULL) + { + for (Expression_list::const_iterator p = mce->vals()->begin(); + p != mce->vals()->end(); + ++p) + { + if ((*p) != NULL) + this->assign(this->context_->sink(), Node::make_node(*p)); + } + } + } + break; + + case Expression::EXPRESSION_FUNC_REFERENCE: + { + Func_expression* fe = (*pexpr)->func_expression(); + if (fe->closure() != NULL) + { + // Connect captured variables to the closure. + Node* closure_node = Node::make_node(fe); + this->context_->track(closure_node); + + // A closure expression already exists as the heap expression: + // &struct{f func_code, v []*Variable}{...}. + // Link closure to the addresses of the variables enclosed. + Heap_expression* he = fe->closure()->heap_expression(); + Struct_construction_expression* sce = he->expr()->struct_literal(); + + // First field is function code, other fields are variable + // references. + Expression_list::const_iterator p = sce->vals()->begin(); + ++p; + for (; p != sce->vals()->end(); ++p) + { + Node* enclosed_node = Node::make_node(*p); + Node::Escape_state* state = + enclosed_node->state(this->context_, NULL); + state->loop_depth = this->context_->loop_depth(); + this->assign(closure_node, enclosed_node); + } + } + } + break; + + case Expression::EXPRESSION_UNARY: + { + if ((*pexpr)->unary_expression()->op() != OPERATOR_AND) + break; + + Node* addr_node = Node::make_node(*pexpr); + this->context_->track(addr_node); + + Expression* operand = (*pexpr)->unary_expression()->operand(); + Named_object* var = NULL; + if (operand->var_expression() != NULL) + var = operand->var_expression()->named_object(); + else if (operand->enclosed_var_expression() != NULL) + var = operand->enclosed_var_expression()->variable(); + else if (operand->temporary_reference_expression() != NULL) + { + // Found in runtime/chanbarrier_test.go. The address of a struct + // reference is usually a heap expression, except when it is a part + // of a case statement. In that case, it is lowered into a + // temporary reference and never linked to the heap expression that + // initializes it. In general, when taking the address of some + // temporary, the analysis should really be looking at the initial + // value of that temporary. + Temporary_reference_expression* tre = + operand->temporary_reference_expression(); + if (tre->statement() != NULL + && tre->statement()->temporary_statement()->init() != NULL) + { + Expression* init = + tre->statement()->temporary_statement()->init(); + Node* init_node = Node::make_node(init); + this->assign(addr_node, init_node); + } + } + + if (var == NULL) + break; + + if (var->is_variable() + && !var->var_value()->is_parameter()) + { + // For &x, use the loop depth of x if known. + Node::Escape_state* addr_state = + addr_node->state(this->context_, NULL); + Node* operand_node = Node::make_node(operand); + Node::Escape_state* operand_state = + operand_node->state(this->context_, NULL); + if (operand_state->loop_depth != 0) + addr_state->loop_depth = operand_state->loop_depth; + } + else if ((var->is_variable() + && var->var_value()->is_parameter()) + || var->is_result_variable()) + { + Node::Escape_state* addr_state = + addr_node->state(this->context_, NULL); + addr_state->loop_depth = 1; + } + } + break; + + default: + break; + } + return TRAVERSE_SKIP_COMPONENTS; +} + +// Model calls within a function as assignments and flows between arguments +// and results. + +void +Escape_analysis_assign::call(Call_expression* call) +{ + Func_expression* fn = call->fn()->func_expression(); + Function_type* fntype = call->get_function_type(); + bool indirect = false; + + // Interface method calls or closure calls are indirect calls. + if (fntype == NULL + || (fntype->is_method() + && fntype->receiver()->type()->interface_type() != NULL) + || fn == NULL + || (fn->named_object()->is_function() + && fn->named_object()->func_value()->enclosing() != NULL)) + indirect = true; + + Node* call_node = Node::make_node(call); + std::vector<Node*> arg_nodes; + if (call->fn()->interface_field_reference_expression() != NULL) + { + Interface_field_reference_expression* ifre = + call->fn()->interface_field_reference_expression(); + Node* field_node = Node::make_node(ifre->expr()); + arg_nodes.push_back(field_node); + } + + if (call->args() != NULL) + { + for (Expression_list::const_iterator p = call->args()->begin(); + p != call->args()->end(); + ++p) + arg_nodes.push_back(Node::make_node(*p)); + } + + if (indirect) + { + // We don't know what happens to the parameters through indirect calls. + // Be conservative and assume they all flow to theSink. + for (std::vector<Node*>::iterator p = arg_nodes.begin(); + p != arg_nodes.end(); + ++p) + { + this->assign(this->context_->sink(), *p); + } + + this->context_->init_retvals(call_node, fntype); + return; + } + + // If FN is an untagged function. + if (fn != NULL + && fn->named_object()->is_function() + && !fntype->is_tagged()) + { + Function* f = fn->named_object()->func_value(); + const Bindings* callee_bindings = f->block()->bindings(); + + const Typed_identifier_list* results = fntype->results(); + if (results != NULL) + { + // Setup output list on this call node. + Node::Escape_state* state = call_node->state(this->context_, NULL); + for (Typed_identifier_list::const_iterator p1 = results->begin(); + p1 != results->end(); + ++p1) + { + if (p1->name().empty() || Gogo::is_sink_name(p1->name())) + continue; + + Named_object* result_no = + callee_bindings->lookup_local(p1->name()); + go_assert(result_no != NULL); + Node* result_node = Node::make_node(result_no); + state->retvals.push_back(result_node); + } + } + + std::vector<Node*>::iterator p = arg_nodes.begin(); + if (fntype->is_method() + && fntype->receiver()->type()->has_pointer()) + { + std::string rcvr_name = fntype->receiver()->name(); + if (rcvr_name.empty() || Gogo::is_sink_name(rcvr_name)) + ; + else + { + Named_object* rcvr_no = + callee_bindings->lookup_local(fntype->receiver()->name()); + go_assert(rcvr_no != NULL); + Node* rcvr_node = Node::make_node(rcvr_no); + this->assign(rcvr_node, *p); + } + ++p; + } + + const Typed_identifier_list* til = fntype->parameters(); + if (til != NULL) + { + for (Typed_identifier_list::const_iterator p1 = til->begin(); + p1 != til->end(); + ++p1, ++p) + { + if (p1->name().empty() || Gogo::is_sink_name(p1->name())) + continue; + + Named_object* param_no = + callee_bindings->lookup_local(p1->name()); + go_assert(param_no != NULL); + Expression* arg = (*p)->expr(); + if (arg->var_expression() != NULL + && arg->var_expression()->named_object() == param_no) + continue; + + Node* param_node = Node::make_node(param_no); + this->assign(param_node, *p); + } + + for (; p != arg_nodes.end(); ++p) + { + this->assign(this->context_->sink(), *p); + } + } + + return; + } + + Node::Escape_state* call_state = call_node->state(this->context_, NULL); + this->context_->init_retvals(call_node, fntype); + + // Receiver. + std::vector<Node*>::iterator p = arg_nodes.begin(); + if (fntype->is_method() + && fntype->receiver()->type()->has_pointer() + && p != arg_nodes.end()) + { + // First argument to call will be the receiver. + std::string* note = fntype->receiver()->note(); + if (fntype->receiver()->type()->points_to() == NULL + && (*p)->expr()->unary_expression() != NULL + && (*p)->expr()->unary_expression()->op() == OPERATOR_AND) + { + // This is a call to a value method that has been lowered into a call + // to a pointer method. Gccgo generates a pointer method for all + // method calls and takes the address of the value passed as the + // receiver then immediately dereferences it within the function. + // In this case, the receiver does not escape. + } + else + { + if (!Type::are_identical(fntype->receiver()->type(), + (*p)->expr()->type(), true, NULL)) + { + // This will be converted later, preemptively track it instead + // of its conversion expression which will show up in a later pass. + this->context_->track(*p); + } + this->assign_from_note(note, call_state->retvals, *p); + } + p++; + } + + const Typed_identifier_list* til = fntype->parameters(); + if (til != NULL) + { + for (Typed_identifier_list::const_iterator pn = til->begin(); + pn != til->end() && p != arg_nodes.end(); + ++pn, ++p) + { + if (!Type::are_identical(pn->type(), (*p)->expr()->type(), + true, NULL)) + { + // This will be converted later, preemptively track it instead + // of its conversion expression which will show up in a later pass. + this->context_->track(*p); + } + + // TODO(cmang): Special care for varargs parameter? + Type* t = pn->type(); + if (t != NULL + && t->has_pointer()) + { + std::string* note = pn->note(); + int enc = this->assign_from_note(note, call_state->retvals, *p); + if (enc == Node::ESCAPE_NONE + && (call->is_deferred() + || call->is_concurrent())) + { + // TODO(cmang): Mark the argument as strictly non-escaping. + } + } + } + + for (; p != arg_nodes.end(); ++p) + { + this->assign(this->context_->sink(), *p); + } + } +} + +// Model the assignment of DST to SRC. +// Assert that SRC somehow gets assigned to DST. +// DST might need to be examined for evaluations that happen inside of it. +// For example, in [DST]*f(x) = [SRC]y, we lose track of the indirection and +// DST becomes the sink in our model. + +void +Escape_analysis_assign::assign(Node* dst, Node* src) +{ + if (dst->expr() != NULL) + { + // Analyze the lhs of the assignment. + // Replace DST with this->context_->sink() if we can't track it. + Expression* e = dst->expr(); + switch (e->classification()) + { + case Expression::EXPRESSION_VAR_REFERENCE: + { + // If DST is a global variable, we have no way to track it. + Named_object* var = e->var_expression()->named_object(); + if (var->is_variable() && var->var_value()->is_global()) + dst = this->context_->sink(); + } + break; + + case Expression::EXPRESSION_FIELD_REFERENCE: + { + Expression* strct = e->field_reference_expression()->expr(); + if (strct->heap_expression() != NULL) + { + // When accessing the field of a struct reference, we lose + // track of the indirection. + dst = this->context_->sink(); + break; + } + + // Treat DST.x = SRC as if it were DST = SRC. + Node* struct_node = Node::make_node(strct); + this->assign(struct_node, src); + return; + } + + case Expression::EXPRESSION_ARRAY_INDEX: + { + Array_index_expression* are = e->array_index_expression(); + if (!are->array()->type()->is_slice_type()) + { + // Treat DST[i] = SRC as if it were DST = SRC if DST if a fixed + // array. + Node* array_node = Node::make_node(are->array()); + this->assign(array_node, src); + return; + } + + // Lose track of the slice dereference. + dst = this->context_->sink(); + } + break; + + case Expression::EXPRESSION_UNARY: + // Lose track of the dereference. + if (e->unary_expression()->op() == OPERATOR_MULT) + dst = this->context_->sink(); + break; + + case Expression::EXPRESSION_MAP_INDEX: + { + // Lose track of the map's key and value. + Expression* index = e->map_index_expression()->index(); + Node* index_node = Node::make_node(index); + this->assign(this->context_->sink(), index_node); + + dst = this->context_->sink(); + } + break; + + default: + // TODO(cmang): Add debugging info here: only a few expressions + // should leave DST unmodified. + break; + } + } + + if (src->expr() != NULL) + { + Expression* e = src->expr(); + switch (e->classification()) + { + case Expression::EXPRESSION_VAR_REFERENCE: + // DST = var + case Expression::EXPRESSION_HEAP: + // DST = &T{...}. + case Expression::EXPRESSION_FIXED_ARRAY_CONSTRUCTION: + case Expression::EXPRESSION_SLICE_CONSTRUCTION: + // DST = [...]T{...}. + case Expression::EXPRESSION_MAP_CONSTRUCTION: + // DST = map[T]V{...}. + case Expression::EXPRESSION_STRUCT_CONSTRUCTION: + // DST = T{...}. + case Expression::EXPRESSION_ALLOCATION: + // DST = new(T). + case Expression::EXPRESSION_BOUND_METHOD: + // DST = x.M. + this->flows(dst, src); + break; + + case Expression::EXPRESSION_UNSAFE_CONVERSION: + { + Expression* underlying = e->unsafe_conversion_expression()->expr(); + Node* underlying_node = Node::make_node(underlying); + this->assign(dst, underlying_node); + } + break; + + case Expression::EXPRESSION_ENCLOSED_VAR_REFERENCE: + { + Named_object* var = e->enclosed_var_expression()->variable(); + Node* var_node = Node::make_node(var); + this->flows(dst, var_node); + } + break; + + case Expression::EXPRESSION_CALL: + { + Call_expression* call = e->call_expression(); + Func_expression* fe = call->fn()->func_expression(); + if (fe != NULL && fe->is_runtime_function()) + { + switch (fe->runtime_code()) + { + case Runtime::APPEND: + { + // Append returns the first argument. + // The subsequent arguments are already leaked because + // they are operands to append. + Node* appendee = Node::make_node(call->args()->front()); + this->assign(dst, appendee); + break; + } + + case Runtime::MAKECHAN: + case Runtime::MAKECHANBIG: + case Runtime::MAKEMAP: + case Runtime::MAKEMAPBIG: + case Runtime::MAKESLICE1: + case Runtime::MAKESLICE2: + case Runtime::MAKESLICE1BIG: + case Runtime::MAKESLICE2BIG: + // DST = make(...). + case Runtime::BYTE_ARRAY_TO_STRING: + // DST = string([]byte{...}). + case Runtime::INT_ARRAY_TO_STRING: + // DST = string([]int{...}). + case Runtime::STRING_TO_BYTE_ARRAY: + // DST = []byte(str). + case Runtime::STRING_TO_INT_ARRAY: + // DST = []int(str). + case Runtime::STRING_PLUS: + // DST = str1 + str2 + case Runtime::CONSTRUCT_MAP: + // When building a map literal's backend representation. + // Likely never seen here and covered in + // Expression::EXPRESSION_MAP_CONSTRUCTION. + case Runtime::INT_TO_STRING: + // DST = string(i). + case Runtime::IFACEE2E2: + case Runtime::IFACEI2E2: + case Runtime::IFACEE2I2: + case Runtime::IFACEI2I2: + case Runtime::IFACEE2T2P: + case Runtime::IFACEI2T2P: + case Runtime::IFACEE2T2: + case Runtime::IFACEI2T2: + case Runtime::CONVERT_INTERFACE: + // All versions of interface conversion that might result + // from a type assertion. Some of these are the result of + // a tuple type assertion statement and may not be covered + // by the case in Expression::EXPRESSION_CONVERSION or + // Expression::EXPRESSION_TYPE_GUARD. + this->flows(dst, src); + break; + + default: + break; + } + break; + } + else if (fe != NULL + && fe->named_object()->is_function() + && fe->named_object()->func_value()->is_method() + && (call->is_deferred() + || call->is_concurrent())) + { + // For a method call thunk, lose track of the call and treat it + // as if DST = RECEIVER. + Node* rcvr_node = Node::make_node(call->args()->front()); + this->assign(dst, rcvr_node); + break; + } + + // TODO(cmang): Handle case from issue 4529. + // Node* call_node = Node::make_node(e); + // Node::Escape_state* call_state = call_node->state(this->context_, NULL); + // std::vector<Node*> retvals = call_state->retvals; + // for (std::vector<Node*>::const_iterator p = retvals.begin(); + // p != retvals.end(); + // ++p) + // this->flows(dst, *p); + } + break; + + case Expression::EXPRESSION_FUNC_REFERENCE: + if (e->func_expression()->closure() != NULL) + { + // If SRC is a reference to a function closure, DST flows into + // the underyling closure variable. + Expression* closure = e->func_expression()->closure(); + Node* closure_node = Node::make_node(closure); + this->flows(dst, closure_node); + } + break; + + case Expression::EXPRESSION_FIELD_REFERENCE: + { + // A non-pointer can't escape from a struct. + if (!e->type()->has_pointer()) + break; + } + + case Expression::EXPRESSION_CONVERSION: + case Expression::EXPRESSION_TYPE_GUARD: + case Expression::EXPRESSION_ARRAY_INDEX: + case Expression::EXPRESSION_STRING_INDEX: + { + Expression* left = NULL; + if (e->field_reference_expression() != NULL) + { + left = e->field_reference_expression()->expr(); + if (left->unary_expression() != NULL + && left->unary_expression()->op() == OPERATOR_MULT) + { + // DST = (*x).f + this->flows(dst, src); + break; + } + } + else if (e->conversion_expression() != NULL) + left = e->conversion_expression()->expr(); + else if (e->type_guard_expression() != NULL) + left = e->type_guard_expression()->expr(); + else if (e->array_index_expression() != NULL) + { + Array_index_expression* aie = e->array_index_expression(); + if (e->type()->is_slice_type()) + left = aie->array(); + else if (!aie->array()->type()->is_slice_type()) + { + // Indexing an array preserves the input value. + Node* array_node = Node::make_node(aie->array()); + this->assign(dst, array_node); + break; + } + else + { + this->flows(dst, src); + break; + } + } + else if (e->string_index_expression() != NULL) + { + String_index_expression* sie = e->string_index_expression(); + if (e->type()->is_slice_type()) + left = sie->string(); + else if (!sie->string()->type()->is_slice_type()) + { + // Indexing a string preserves the input value. + Node* string_node = Node::make_node(sie->string()); + this->assign(dst, string_node); + break; + } + else + { + this->flows(dst, src); + break; + } + } + go_assert(left != NULL); + + // Conversions, field access, and slicing all preserve the input + // value. + Node* left_node = Node::make_node(left); + this->assign(dst, left_node); + } + break; + + case Expression::EXPRESSION_BINARY: + { + switch (e->binary_expression()->op()) + { + case OPERATOR_PLUS: + case OPERATOR_MINUS: + case OPERATOR_XOR: + case OPERATOR_MULT: + case OPERATOR_DIV: + case OPERATOR_MOD: + case OPERATOR_LSHIFT: + case OPERATOR_RSHIFT: + case OPERATOR_AND: + case OPERATOR_BITCLEAR: + { + Node* left = Node::make_node(e->binary_expression()->left()); + this->assign(dst, left); + Node* right = Node::make_node(e->binary_expression()->right()); + this->assign(dst, right); + } + break; + + default: + break; + } + } + break; + + case Expression::EXPRESSION_UNARY: + { + switch (e->unary_expression()->op()) + { + case OPERATOR_PLUS: + case OPERATOR_MINUS: + case OPERATOR_XOR: + { + Node* op_node = + Node::make_node(e->unary_expression()->operand()); + this->assign(dst, op_node); + } + break; + + case OPERATOR_MULT: + // DST = *x + case OPERATOR_AND: + // DST = &x + this->flows(dst, src); + break; + + default: + break; + } + } + break; + + case Expression::EXPRESSION_TEMPORARY_REFERENCE: + { + Statement* temp = e->temporary_reference_expression()->statement(); + if (temp != NULL + && temp->temporary_statement()->init() != NULL) + { + Expression* init = temp->temporary_statement()->init(); + Node* init_node = Node::make_node(init); + this->assign(dst, init_node); + } + } + break; + + default: + // TODO(cmang): Add debug info here; this should not be reachable. + // For now, just to be conservative, we'll just say dst flows to src. + break; + } + } +} + +// Model the assignment of DST to an indirection of SRC. + +void +Escape_analysis_assign::assign_deref(Node* dst, Node* src) +{ + if (src->expr() != NULL) + { + switch (src->expr()->classification()) + { + case Expression::EXPRESSION_BOOLEAN: + case Expression::EXPRESSION_STRING: + case Expression::EXPRESSION_INTEGER: + case Expression::EXPRESSION_FLOAT: + case Expression::EXPRESSION_COMPLEX: + case Expression::EXPRESSION_NIL: + case Expression::EXPRESSION_IOTA: + // No need to try indirections on literal values + // or numeric constants. + return; + + default: + break; + } + } + + this->assign(dst, this->context_->add_dereference(src)); +} + +// Model the input-to-output assignment flow of one of a function call's +// arguments, where the flow is encoded in NOTE. + +int +Escape_analysis_assign::assign_from_note(std::string* note, + const std::vector<Node*>& dsts, + Node* src) +{ + int enc = Escape_note::parse_tag(note); + if (src->expr() != NULL) + { + switch (src->expr()->classification()) + { + case Expression::EXPRESSION_BOOLEAN: + case Expression::EXPRESSION_STRING: + case Expression::EXPRESSION_INTEGER: + case Expression::EXPRESSION_FLOAT: + case Expression::EXPRESSION_COMPLEX: + case Expression::EXPRESSION_NIL: + case Expression::EXPRESSION_IOTA: + // There probably isn't a note for a literal value. Literal values + // usually don't escape unless we lost track of the value some how. + return enc; + + default: + break; + } + } + + if (enc == Node::ESCAPE_UNKNOWN) + { + // Lost track of the value. + this->assign(this->context_->sink(), src); + return enc; + } + else if (enc == Node::ESCAPE_NONE) + return enc; + + // If the content inside a parameter (reached via indirection) escapes to + // the heap, mark it. + if ((enc & ESCAPE_CONTENT_ESCAPES) != 0) + this->assign(this->context_->sink(), this->context_->add_dereference(src)); + + int save_enc = enc; + enc >>= ESCAPE_RETURN_BITS; + for (std::vector<Node*>::const_iterator p = dsts.begin(); + enc != 0 && p != dsts.end(); + ++p) + { + // Prefer the lowest-level path to the reference (for escape purposes). + // Two-bit encoding (for example. 1, 3, and 4 bits are other options) + // 01 = 0-level + // 10 = 1-level, (content escapes), + // 11 = 2-level, (content of content escapes). + int bits = enc & ESCAPE_BITS_MASK_FOR_TAG; + if (bits > 0) + { + Node* n = src; + for (int i = 0; i < bits - 1; ++i) + { + // Encode level > 0 as indirections. + n = this->context_->add_dereference(n); + } + this->assign(*p, n); + } + enc >>= ESCAPE_BITS_PER_OUTPUT_IN_TAG; + } + + // If there are too many outputs to fit in the tag, that is handled on the + // encoding end as ESCAPE_HEAP, so there is no need to check here. + return save_enc; +} + +// Record the flow of SRC to DST in DST. + +void +Escape_analysis_assign::flows(Node* dst, Node* src) +{ + // Don't bother capturing the flow from scalars. + if (src->expr() != NULL + && !src->expr()->type()->has_pointer()) + return; + + // Don't confuse a blank identifier with the sink. + if (dst->is_sink() && dst != this->context_->sink()) + return; + + Node::Escape_state* dst_state = dst->state(this->context_, NULL); + Node::Escape_state* src_state = src->state(this->context_, NULL); + if (dst == src + || dst_state == src_state + || dst_state->flows.find(src) != dst_state->flows.end() + || src_state->flows.find(dst) != src_state->flows.end()) + return; + + if (dst_state->flows.empty()) + this->context_->add_dst(dst); + + dst_state->flows.insert(src); +} + // Build a connectivity graph between nodes in the function being analyzed. void -Gogo::assign_connectivity(Escape_context*, Named_object*) +Gogo::assign_connectivity(Escape_context* context, Named_object* fn) { - // TODO(cmang): Model the flow analysis of input parameters and results for a - // function. - // TODO(cmang): Analyze the current function's body. + // Must be defined outside of this package. + if (!fn->is_function()) + return; + + int save_depth = context->loop_depth(); + context->set_loop_depth(1); + + Escape_analysis_assign ea(context, fn); + Function::Results* res = fn->func_value()->result_variables(); + if (res != NULL) + { + for (Function::Results::const_iterator p = res->begin(); + p != res->end(); + ++p) + { + Node* res_node = Node::make_node(*p); + Node::Escape_state* res_state = res_node->state(context, fn); + res_state->loop_depth = 0; + + // If this set of functions is recursive, we lose track of the return values. + // Just say that the result flows to the sink. + if (context->recursive()) + ea.flows(context->sink(), res_node); + } + } + + const Bindings* callee_bindings = fn->func_value()->block()->bindings(); + Function_type* fntype = fn->func_value()->type(); + Typed_identifier_list* params = (fntype->parameters() != NULL + ? fntype->parameters()->copy() + : new Typed_identifier_list); + if (fntype->receiver() != NULL) + params->push_back(*fntype->receiver()); + + for (Typed_identifier_list::const_iterator p = params->begin(); + p != params->end(); + ++p) + { + if (p->name().empty() || Gogo::is_sink_name(p->name())) + continue; + + Named_object* param_no = callee_bindings->lookup_local(p->name()); + go_assert(param_no != NULL); + Node* param_node = Node::make_node(param_no); + Node::Escape_state* param_state = param_node->state(context, fn); + param_state->loop_depth = 1; + + if (!p->type()->has_pointer()) + continue; + + // External function? Parameters must escape unless //go:noescape is set. + // TODO(cmang): Implement //go:noescape directive. + if (fn->package() != NULL) + param_node->set_encoding(Node::ESCAPE_HEAP); + else + param_node->set_encoding(Node::ESCAPE_NONE); + + // TODO(cmang): Track this node in no_escape list. + } + + Escape_analysis_loop el; + fn->func_value()->traverse(&el); + + fn->func_value()->traverse(&ea); + context->set_loop_depth(save_depth); } // Propagate escape information across the nodes modeled in this Analysis_set. Index: gcc/go/gofrontend/escape.h =================================================================== --- gcc/go/gofrontend/escape.h (revision 236804) +++ gcc/go/gofrontend/escape.h (working copy) @@ -206,7 +206,9 @@ class Node void set_encoding(int enc); - // Is this node a sink? + bool + is_big(Escape_context*) const; + bool is_sink() const; @@ -250,6 +252,11 @@ class Node static int max_encoding(int e, int etype); + // Return a modified encoding for an input parameter that flows into an + // output parameter. + static int + note_inout_flows(int e, int index, Level level); + private: // The classification of this Node. Node_classification classification_; Index: gcc/go/gofrontend/gogo.h =================================================================== --- gcc/go/gofrontend/gogo.h (revision 236804) +++ gcc/go/gofrontend/gogo.h (working copy) @@ -2578,7 +2578,7 @@ class Label public: Label(const std::string& name) : name_(name), location_(Linemap::unknown_location()), snapshot_(NULL), - refs_(), is_used_(false), blabel_(NULL) + refs_(), is_used_(false), blabel_(NULL), depth_(DEPTH_UNKNOWN) { } // Return the label's name. @@ -2601,6 +2601,26 @@ class Label set_is_used() { this->is_used_ = true; } + // Return whether this label is looping. + bool + looping() const + { return this->depth_ == DEPTH_LOOPING; } + + // Set this label as looping. + void + set_looping() + { this->depth_ = DEPTH_LOOPING; } + + // Return whether this label is nonlooping. + bool + nonlooping() const + { return this->depth_ == DEPTH_NONLOOPING; } + + // Set this label as nonlooping. + void + set_nonlooping() + { this->depth_ = DEPTH_NONLOOPING; } + // Return the location of the definition. Location location() const @@ -2660,6 +2680,16 @@ class Label is_dummy_label() const { return this->name_ == "_"; } + // A classification of a label's looping depth. + enum Loop_depth + { + DEPTH_UNKNOWN, + // A label never jumped to. + DEPTH_NONLOOPING, + // A label jumped to. + DEPTH_LOOPING + }; + private: // The name of the label. std::string name_; @@ -2675,6 +2705,8 @@ class Label bool is_used_; // The backend representation. Blabel* blabel_; + // The looping depth of this label, for escape analysis. + Loop_depth depth_; }; // An unnamed label. These are used when lowering loops. Index: gcc/go/gofrontend/statements.cc =================================================================== --- gcc/go/gofrontend/statements.cc (revision 236804) +++ gcc/go/gofrontend/statements.cc (working copy) @@ -1793,40 +1793,6 @@ Statement::make_statement(Expression* ex return new Expression_statement(expr, is_ignored); } -// A block statement--a list of statements which may include variable -// definitions. - -class Block_statement : public Statement -{ - public: - Block_statement(Block* block, Location location) - : Statement(STATEMENT_BLOCK, location), - block_(block) - { } - - protected: - int - do_traverse(Traverse* traverse) - { return this->block_->traverse(traverse); } - - void - do_determine_types() - { this->block_->determine_types(); } - - bool - do_may_fall_through() const - { return this->block_->may_fall_through(); } - - Bstatement* - do_get_backend(Translate_context* context); - - void - do_dump_statement(Ast_dump_context*) const; - - private: - Block* block_; -}; - // Convert a block to the backend representation of a statement. Bstatement* @@ -2944,37 +2910,13 @@ Statement::make_continue_statement(Unnam return new Bc_statement(false, label, location); } -// A goto statement. +// Class Goto_statement. -class Goto_statement : public Statement +int +Goto_statement::do_traverse(Traverse*) { - public: - Goto_statement(Label* label, Location location) - : Statement(STATEMENT_GOTO, location), - label_(label) - { } - - protected: - int - do_traverse(Traverse*) - { return TRAVERSE_CONTINUE; } - - void - do_check_types(Gogo*); - - bool - do_may_fall_through() const - { return false; } - - Bstatement* - do_get_backend(Translate_context*); - - void - do_dump_statement(Ast_dump_context*) const; - - private: - Label* label_; -}; + return TRAVERSE_CONTINUE; +} // Check types for a label. There aren't any types per se, but we use // this to give an error if the label was never defined. @@ -3016,35 +2958,21 @@ Statement::make_goto_statement(Label* la return new Goto_statement(label, location); } -// A goto statement to an unnamed label. +// Class Goto_unnamed_statement. -class Goto_unnamed_statement : public Statement +int +Goto_unnamed_statement::do_traverse(Traverse*) { - public: - Goto_unnamed_statement(Unnamed_label* label, Location location) - : Statement(STATEMENT_GOTO_UNNAMED, location), - label_(label) - { } - - protected: - int - do_traverse(Traverse*) - { return TRAVERSE_CONTINUE; } - - bool - do_may_fall_through() const - { return false; } - - Bstatement* - do_get_backend(Translate_context* context) - { return this->label_->get_goto(context, this->location()); } + return TRAVERSE_CONTINUE; +} - void - do_dump_statement(Ast_dump_context*) const; +// Convert the goto unnamed statement to the backend representation. - private: - Unnamed_label* label_; -}; +Bstatement* +Goto_unnamed_statement::do_get_backend(Translate_context* context) +{ + return this->label_->get_goto(context, this->location()); +} // Dump the AST representation for an unnamed goto statement @@ -3109,32 +3037,27 @@ Statement::make_label_statement(Label* l return new Label_statement(label, location); } -// An unnamed label statement. +// Class Unnamed_label_statement. -class Unnamed_label_statement : public Statement -{ - public: - Unnamed_label_statement(Unnamed_label* label) - : Statement(STATEMENT_UNNAMED_LABEL, label->location()), - label_(label) - { } +Unnamed_label_statement::Unnamed_label_statement(Unnamed_label* label) + : Statement(STATEMENT_UNNAMED_LABEL, label->location()), + label_(label) +{ } - protected: - int - do_traverse(Traverse*) - { return TRAVERSE_CONTINUE; } +int +Unnamed_label_statement::do_traverse(Traverse*) +{ + return TRAVERSE_CONTINUE; +} - Bstatement* - do_get_backend(Translate_context* context) - { return this->label_->get_definition(context); } +// Get the backend definition for this unnamed label statement. - void - do_dump_statement(Ast_dump_context*) const; +Bstatement* +Unnamed_label_statement::do_get_backend(Translate_context* context) +{ + return this->label_->get_definition(context); +} - private: - // The label. - Unnamed_label* label_; -}; // Dump the AST representation for an unnamed label definition statement. @@ -5209,7 +5132,9 @@ For_statement::do_lower(Gogo*, Named_obj b->set_end_location(end_loc); - return Statement::make_block_statement(b, loc); + Statement* bs = Statement::make_block_statement(b, loc); + bs->block_statement()->set_is_lowered_for_statement(); + return bs; } // Return the break label, creating it if necessary. Index: gcc/go/gofrontend/statements.h =================================================================== --- gcc/go/gofrontend/statements.h (revision 236804) +++ gcc/go/gofrontend/statements.h (working copy) @@ -19,9 +19,13 @@ class Assignment_statement; class Temporary_statement; class Variable_declaration_statement; class Expression_statement; +class Block_statement; class Return_statement; class Thunk_statement; +class Goto_statement; +class Goto_unnamed_statement; class Label_statement; +class Unnamed_label_statement; class If_statement; class For_statement; class For_range_statement; @@ -366,6 +370,12 @@ class Statement return this->convert<Expression_statement, STATEMENT_EXPRESSION>(); } + // If this is an block statement, return it. Otherwise return + // NULL. + Block_statement* + block_statement() + { return this->convert<Block_statement, STATEMENT_BLOCK>(); } + // If this is a return statement, return it. Otherwise return NULL. Return_statement* return_statement() @@ -376,11 +386,26 @@ class Statement Thunk_statement* thunk_statement(); + // If this is a goto statement, return it. Otherwise return NULL. + Goto_statement* + goto_statement() + { return this->convert<Goto_statement, STATEMENT_GOTO>(); } + + // If this is a goto_unnamed statement, return it. Otherwise return NULL. + Goto_unnamed_statement* + goto_unnamed_statement() + { return this->convert<Goto_unnamed_statement, STATEMENT_GOTO_UNNAMED>(); } + // If this is a label statement, return it. Otherwise return NULL. Label_statement* label_statement() { return this->convert<Label_statement, STATEMENT_LABEL>(); } + // If this is an unnamed_label statement, return it. Otherwise return NULL. + Unnamed_label_statement* + unnamed_label_statement() + { return this->convert<Unnamed_label_statement, STATEMENT_UNNAMED_LABEL>(); } + // If this is an if statement, return it. Otherwise return NULL. If_statement* if_statement() @@ -762,6 +787,50 @@ class Expression_statement : public Stat bool is_ignored_; }; +// A block statement--a list of statements which may include variable +// definitions. + +class Block_statement : public Statement +{ + public: + Block_statement(Block* block, Location location) + : Statement(STATEMENT_BLOCK, location), + block_(block), is_lowered_for_statement_(false) + { } + + void + set_is_lowered_for_statement() + { this->is_lowered_for_statement_ = true; } + + bool + is_lowered_for_statement() + { return this->is_lowered_for_statement_; } + + protected: + int + do_traverse(Traverse* traverse) + { return this->block_->traverse(traverse); } + + void + do_determine_types() + { this->block_->determine_types(); } + + bool + do_may_fall_through() const + { return this->block_->may_fall_through(); } + + Bstatement* + do_get_backend(Translate_context* context); + + void + do_dump_statement(Ast_dump_context*) const; + + private: + Block* block_; + // True if this block statement represents a lowered for statement. + bool is_lowered_for_statement_; +}; + // A send statement. class Send_statement : public Statement @@ -1162,6 +1231,74 @@ class Defer_statement : public Thunk_sta do_dump_statement(Ast_dump_context*) const; }; +// A goto statement. + +class Goto_statement : public Statement +{ + public: + Goto_statement(Label* label, Location location) + : Statement(STATEMENT_GOTO, location), + label_(label) + { } + + // Return the label being jumped to. + Label* + label() const + { return this->label_; } + + protected: + int + do_traverse(Traverse*); + + void + do_check_types(Gogo*); + + bool + do_may_fall_through() const + { return false; } + + Bstatement* + do_get_backend(Translate_context*); + + void + do_dump_statement(Ast_dump_context*) const; + + private: + Label* label_; +}; + +// A goto statement to an unnamed label. + +class Goto_unnamed_statement : public Statement +{ + public: + Goto_unnamed_statement(Unnamed_label* label, Location location) + : Statement(STATEMENT_GOTO_UNNAMED, location), + label_(label) + { } + + Unnamed_label* + unnamed_label() const + { return this->label_; } + + protected: + int + do_traverse(Traverse*); + + bool + do_may_fall_through() const + { return false; } + + Bstatement* + do_get_backend(Translate_context* context); + + void + do_dump_statement(Ast_dump_context*) const; + + private: + Unnamed_label* label_; +}; + // A label statement. class Label_statement : public Statement @@ -1173,7 +1310,7 @@ class Label_statement : public Statement { } // Return the label itself. - const Label* + Label* label() const { return this->label_; } @@ -1192,6 +1329,28 @@ class Label_statement : public Statement Label* label_; }; +// An unnamed label statement. + +class Unnamed_label_statement : public Statement +{ + public: + Unnamed_label_statement(Unnamed_label* label); + + protected: + int + do_traverse(Traverse*); + + Bstatement* + do_get_backend(Translate_context* context); + + void + do_dump_statement(Ast_dump_context*) const; + + private: + // The label. + Unnamed_label* label_; +}; + // An if statement. class If_statement : public Statement Index: gcc/go/gofrontend/types.cc =================================================================== --- gcc/go/gofrontend/types.cc (revision 236804) +++ gcc/go/gofrontend/types.cc (working copy) @@ -4502,10 +4502,7 @@ class Call_multiple_result_type : public protected: bool do_has_pointer() const - { - go_assert(saw_errors()); - return false; - } + { return false; } bool do_compare_is_identity(Gogo*)