From 7295570dd4a8099c010d511ffb3327fd8b0e36d7 Mon Sep 17 00:00:00 2001 From: Chris Manghane Date: Tue, 2 Aug 2016 21:43:48 +0000 Subject: [PATCH] escape: Add basic debugging. Emit basic debug information when compiling with the flag -fgo-debug-escape#. Reviewed-on: https://go-review.googlesource.com/22376 2016-08-02 Chris Manghane * lang.opt: Add -fgo-debug-escape option. * go-c.h (go_create_gogo): Add debug_escape_level parameter. * go-lang.c (go_langhook_init): Pass go_debug_escape_level to go_create_gogo. From-SVN: r239002 --- gcc/go/ChangeLog | 7 + gcc/go/go-c.h | 3 +- gcc/go/go-lang.c | 2 +- gcc/go/gofrontend/MERGE | 2 +- gcc/go/gofrontend/escape.cc | 517 +++++++++++++++++++++++++++++++- gcc/go/gofrontend/escape.h | 15 + gcc/go/gofrontend/go.cc | 4 +- gcc/go/gofrontend/gogo.h | 28 +- gcc/go/gofrontend/statements.cc | 2 +- gcc/go/lang.opt | 4 + 10 files changed, 577 insertions(+), 7 deletions(-) diff --git a/gcc/go/ChangeLog b/gcc/go/ChangeLog index e486a305a01..60d6e14640a 100644 --- a/gcc/go/ChangeLog +++ b/gcc/go/ChangeLog @@ -1,3 +1,10 @@ +2016-08-02 Chris Manghane + + * lang.opt: Add -fgo-debug-escape option. + * go-c.h (go_create_gogo): Add debug_escape_level parameter. + * go-lang.c (go_langhook_init): Pass go_debug_escape_level to + go_create_gogo. + 2016-05-06 Chris Manghane * Make-lang.in (GO_OBJS): Add go/escape.o (based on an entirely diff --git a/gcc/go/go-c.h b/gcc/go/go-c.h index 98b2850be8c..690d6db7ab9 100644 --- a/gcc/go/go-c.h +++ b/gcc/go/go-c.h @@ -34,7 +34,8 @@ extern void go_add_search_path (const char*); extern void go_create_gogo (int int_type_size, int pointer_size, const char* pkgpath, const char *prefix, const char *relative_import_path, - bool check_divide_zero, bool check_divide_overflow); + bool check_divide_zero, bool check_divide_overflow, + int debug_escape_level); extern void go_parse_input_files (const char**, unsigned int, bool only_check_syntax, diff --git a/gcc/go/go-lang.c b/gcc/go/go-lang.c index 9c95c8e0bb1..570f5e06e8d 100644 --- a/gcc/go/go-lang.c +++ b/gcc/go/go-lang.c @@ -101,7 +101,7 @@ go_langhook_init (void) go_type_for_size). */ go_create_gogo (INT_TYPE_SIZE, POINTER_SIZE, go_pkgpath, go_prefix, go_relative_import_path, go_check_divide_zero, - go_check_divide_overflow); + go_check_divide_overflow, go_debug_escape_level); build_common_builtin_nodes (); diff --git a/gcc/go/gofrontend/MERGE b/gcc/go/gofrontend/MERGE index 69190dd5cda..7e1cc13ef93 100644 --- a/gcc/go/gofrontend/MERGE +++ b/gcc/go/gofrontend/MERGE @@ -1,4 +1,4 @@ -d4b47fef149fc905ae6b418934f6be8cf6be433e +89a0b3a04f80df388242166b8835f12e82ceb194 The first line of this file holds the git revision number of the last merge done from the gofrontend repository. diff --git a/gcc/go/gofrontend/escape.cc b/gcc/go/gofrontend/escape.cc index af8f1e2f993..6814c7ea74d 100644 --- a/gcc/go/gofrontend/escape.cc +++ b/gcc/go/gofrontend/escape.cc @@ -6,12 +6,14 @@ #include #include +#include #include "gogo.h" #include "types.h" #include "expressions.h" #include "statements.h" #include "escape.h" +#include "ast-dump.h" // class Node. @@ -47,6 +49,305 @@ Node::location() const return Linemap::unknown_location(); } +// To match the cmd/gc debug output, strip away the packed prefixes on functions +// and variable/expressions. + +std::string +strip_packed_prefix(Gogo* gogo, const std::string& s) +{ + std::string packed_prefix = "." + gogo->pkgpath() + "."; + std::string fmt = s; + for (size_t pos = fmt.find(packed_prefix); + pos != std::string::npos; + pos = fmt.find(packed_prefix)) + fmt.erase(pos, packed_prefix.length()); + return fmt; +} + +// A helper for debugging; return this node's AST formatted string. +// This is an implementation of gc's Nconv with obj.FmtShort. + +std::string +Node::ast_format(Gogo* gogo) const +{ + std::ostringstream ss; + if (this->is_sink()) + ss << ".sink"; + else if (this->object() != NULL) + { + Named_object* no = this->object(); + if (no->is_function() && no->func_value()->enclosing() != NULL) + return "func literal"; + ss << no->name(); + } + else if (this->expr() != NULL) + { + Expression* e = this->expr(); + bool is_call = e->call_expression() != NULL; + if (is_call) + e->call_expression()->fn(); + Func_expression* fe = e->func_expression();; + + bool is_closure = fe != NULL && fe->closure() != NULL; + if (is_closure) + { + if (is_call) + return "(func literal)()"; + return "func literal"; + } + Ast_dump_context::dump_to_stream(this->expr(), &ss); + } + else + { + Statement* s = this->statement(); + Goto_unnamed_statement* unnamed = s->goto_unnamed_statement(); + if (unnamed != NULL) + { + Statement* derived = unnamed->unnamed_label()->derived_from(); + if (derived != NULL) + { + switch (derived->classification()) + { + case Statement::STATEMENT_FOR: + case Statement::STATEMENT_FOR_RANGE: + return "for loop"; + break; + + case Statement::STATEMENT_SWITCH: + return "switch"; + break; + + case Statement::STATEMENT_TYPE_SWITCH: + return "type switch"; + break; + + default: + break; + } + } + } + Ast_dump_context::dump_to_stream(s, &ss); + } + + return strip_packed_prefix(gogo, ss.str()); +} + +// A helper for debugging; return this node's detailed format string. +// This is an implementation of gc's Jconv with obj.FmtShort. + +std::string +Node::details() const +{ + std::stringstream details; + + if (!this->is_sink()) + details << " l(" << LOCATION_LINE(this->location().gcc_location()) << ")"; + + bool is_varargs = false; + bool is_address_taken = false; + bool is_in_heap = false; + bool is_assigned = false; + std::string class_name; + + Expression* e = this->expr(); + Named_object* node_object = NULL; + if (this->object() != NULL) + node_object = this->object(); + else if (e != NULL && e->var_expression() != NULL) + node_object = e->var_expression()->named_object(); + + if (node_object) + { + // TODO(cmang): For named variables and functions, we want to output + // the function depth. + if (node_object->is_variable()) + { + Variable* var = node_object->var_value(); + is_varargs = var->is_varargs_parameter(); + is_address_taken = (var->is_address_taken() + || var->is_non_escaping_address_taken()); + is_in_heap = var->is_in_heap(); + is_assigned = var->init() != NULL; + + if (var->is_global()) + class_name = "PEXTERN"; + else if (var->is_parameter()) + class_name = "PPARAM"; + else if (var->is_closure()) + class_name = "PPARAMREF"; + else + class_name = "PAUTO"; + } + else if (node_object->is_result_variable()) + class_name = "PPARAMOUT"; + else if (node_object->is_function() + || node_object->is_function_declaration()) + class_name = "PFUNC"; + } + else if (e != NULL && e->enclosed_var_expression() != NULL) + { + Named_object* enclosed = e->enclosed_var_expression()->variable(); + if (enclosed->is_variable()) + { + Variable* var = enclosed->var_value(); + is_address_taken = (var->is_address_taken() + || var->is_non_escaping_address_taken()); + } + else + { + Result_variable* var = enclosed->result_var_value(); + is_address_taken = (var->is_address_taken() + || var->is_non_escaping_address_taken()); + } + class_name = "PPARAMREF"; + } + + if (!class_name.empty()) + { + details << " class(" << class_name; + if (is_in_heap) + details << ",heap"; + details << ")"; + } + + switch ((this->encoding() & ESCAPE_MASK)) + { + case Node::ESCAPE_UNKNOWN: + break; + + case Node::ESCAPE_HEAP: + details << " esc(h)"; + break; + + case Node::ESCAPE_SCOPE: + details << " esc(s)"; + break; + + case Node::ESCAPE_NONE: + details << " esc(no)"; + break; + + case Node::ESCAPE_NEVER: + details << " esc(N)"; + break; + + default: + details << " esc(" << this->encoding() << ")"; + break; + } + + if (this->state_ != NULL && this->state_->loop_depth != 0) + details << " ld(" << this->state_->loop_depth << ")"; + + if (is_varargs) + details << " isddd(1)"; + if (is_address_taken) + details << " addrtaken"; + if (is_assigned) + details << " assigned"; + + return details.str(); +} + +std::string +Node::op_format() const +{ + std::stringstream op; + Ast_dump_context adc(&op, false); + if (this->expr() != NULL) + { + Expression* e = this->expr(); + switch (e->classification()) + { + case Expression::EXPRESSION_UNARY: + adc.dump_operator(e->unary_expression()->op()); + break; + + case Expression::EXPRESSION_BINARY: + adc.dump_operator(e->binary_expression()->op()); + break; + + case Expression::EXPRESSION_CALL: + op << "function call"; + break; + + case Expression::EXPRESSION_FUNC_REFERENCE: + if (e->func_expression()->is_runtime_function()) + { + switch (e->func_expression()->runtime_code()) + { + case Runtime::PANIC: + op << "panic"; + + case Runtime::APPEND: + op << "append"; + break; + + case Runtime::COPY: + op << "copy"; + 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: + op << "make"; + break; + + case Runtime::DEFER: + op << "defer"; + break; + + case Runtime::RECOVER: + op << "recover"; + break; + + case Runtime::CLOSE: + op << "close"; + break; + + default: + break; + } + } + break; + + case Expression::EXPRESSION_ALLOCATION: + op << "new"; + break; + + case Expression::EXPRESSION_RECEIVE: + op << "<-"; + break; + + default: + break; + } + } + + if (this->statement() != NULL) + { + switch (this->statement()->classification()) + { + case Statement::STATEMENT_DEFER: + op << "defer"; + break; + + case Statement::STATEMENT_RETURN: + op << "return"; + break; + + default: + break; + } + } + return op.str(); +} + // Return this node's state, creating it if has not been initialized. Node::Escape_state* @@ -255,6 +556,41 @@ Escape_context::Escape_context(Gogo* gogo, bool recursive) state->loop_depth = -1; } +std::string +debug_function_name(Named_object* fn) +{ + if (fn == NULL) + return ""; + + if (!fn->is_function() + || fn->func_value()->enclosing() == NULL) + return Gogo::unpack_hidden_name(fn->name()); + + // Closures are named ".$nested#" where # starts from 0 to distinguish + // between closures. The cmd/gc closures are named in the format + // "enclosing.func#" where # starts from 1. If this is a closure, format + // its name to match cmd/gc. + Named_object* enclosing = fn->func_value()->enclosing(); + + // Extract #. + std::string name = Gogo::unpack_hidden_name(fn->name()); + int closure_num = (int)strtol(name.substr(6).c_str(), NULL, 0); + closure_num++; + + name = Gogo::unpack_hidden_name(enclosing->name()); + char buf[200]; + snprintf(buf, sizeof buf, "%s.func%d", name.c_str(), closure_num); + return buf; +} + +// Return the name of the current function. + +std::string +Escape_context::current_function_name() const +{ + return debug_function_name(this->current_function_); +} + // Initialize the dummy return values for this Node N using the results // in FNTYPE. @@ -381,6 +717,21 @@ Gogo::analyze_escape() ++fn) this->tag_function(context, *fn); + if (this->debug_escape_level() != 0) + { + std::vector noesc = context->non_escaping_nodes(); + for (std::vector::const_iterator n = noesc.begin(); + n != noesc.end(); + ++n) + { + Node::Escape_state* state = (*n)->state(context, NULL); + if (((*n)->encoding() & ESCAPE_MASK) == int(Node::ESCAPE_NONE)) + inform((*n)->location(), "%s %s does not escape", + strip_packed_prefix(this, debug_function_name(state->fn)).c_str(), + (*n)->ast_format(this).c_str()); + } + // TODO(cmang): Which objects in context->noesc actually don't escape. + } delete context; } } @@ -668,6 +1019,20 @@ Escape_analysis_assign::statement(Block*, size_t*, Statement* s) if (is_for_statement) this->context_->decrease_loop_depth(); + Gogo* gogo = this->context_->gogo(); + int debug_level = gogo->debug_escape_level(); + if (debug_level > 1 + && s->unnamed_label_statement() == NULL + && s->expression_statement() == NULL + && !s->is_block_statement()) + { + Node* n = Node::make_node(s); + std::string fn_name = this->context_->current_function_name(); + inform(s->location(), "[%d] %s esc: %s", + this->context_->loop_depth(), fn_name.c_str(), + n->ast_format(gogo).c_str()); + } + switch (s->classification()) { case Statement::STATEMENT_VARIABLE_DECLARATION: @@ -689,8 +1054,19 @@ Escape_analysis_assign::statement(Block*, size_t*, Statement* s) case Statement::STATEMENT_LABEL: { - if (s->label_statement()->label()->looping()) + Label_statement* label_stmt = s->label_statement(); + if (label_stmt->label()->looping()) this->context_->increase_loop_depth(); + + if (debug_level > 1) + { + std::string label_type = (label_stmt->label()->looping() + ? "looping" + : "nonlooping"); + inform(s->location(), "%s %s label", + label_stmt->label()->name().c_str(), + label_type.c_str()); + } } break; @@ -767,11 +1143,16 @@ Escape_analysis_assign::statement(Block*, size_t*, Statement* s) int Escape_analysis_assign::expression(Expression** pexpr) { + Gogo* gogo = this->context_->gogo(); + int debug_level = gogo->debug_escape_level(); + // Big stuff escapes unconditionally. Node* n = Node::make_node(*pexpr); if ((n->encoding() & ESCAPE_MASK) != int(Node::ESCAPE_HEAP) && n->is_big(this->context_)) { + if (debug_level > 1) + inform((*pexpr)->location(), "too large for stack"); n->set_encoding(Node::ESCAPE_HEAP); (*pexpr)->address_taken(true); this->assign(this->context_->sink(), n); @@ -780,6 +1161,15 @@ Escape_analysis_assign::expression(Expression** pexpr) if ((*pexpr)->func_expression() == NULL) (*pexpr)->traverse_subexpressions(this); + if (debug_level > 1) + { + Node* n = Node::make_node(*pexpr); + std::string fn_name = this->context_->current_function_name(); + inform((*pexpr)->location(), "[%d] %s esc: %s", + this->context_->loop_depth(), fn_name.c_str(), + n->ast_format(gogo).c_str()); + } + switch ((*pexpr)->classification()) { case Expression::EXPRESSION_CALL: @@ -808,6 +1198,10 @@ Escape_analysis_assign::expression(Expression** pexpr) Node* appended = Node::make_node(call->args()->back()); this->assign_deref(this->context_->sink(), appended); + if (debug_level > 2) + error_at((*pexpr)->location(), + "special treatment of append(slice1, slice2...)"); + // The content of the original slice leaks as well. Node* appendee = Node::make_node(call->args()->back()); this->assign_deref(this->context_->sink(), appendee); @@ -1060,6 +1454,9 @@ Escape_analysis_assign::expression(Expression** pexpr) void Escape_analysis_assign::call(Call_expression* call) { + Gogo* gogo = this->context_->gogo(); + int debug_level = gogo->debug_escape_level(); + Func_expression* fn = call->fn()->func_expression(); Function_type* fntype = call->get_function_type(); bool indirect = false; @@ -1099,6 +1496,10 @@ Escape_analysis_assign::call(Call_expression* call) p != arg_nodes.end(); ++p) { + if (debug_level > 2) + inform(call->location(), + "esccall:: indirect call <- %s, untracked", + (*p)->ast_format(gogo).c_str()); this->assign(this->context_->sink(), *p); } @@ -1111,6 +1512,10 @@ Escape_analysis_assign::call(Call_expression* call) && fn->named_object()->is_function() && !fntype->is_tagged()) { + if (debug_level > 2) + inform(call->location(), "esccall:: %s in recursive group", + call_node->ast_format(gogo).c_str()); + Function* f = fn->named_object()->func_value(); const Bindings* callee_bindings = f->block()->bindings(); @@ -1176,6 +1581,9 @@ Escape_analysis_assign::call(Call_expression* call) for (; p != arg_nodes.end(); ++p) { + if (debug_level > 2) + inform(call->location(), "esccall:: ... <- %s, untracked", + (*p)->ast_format(gogo).c_str()); this->assign(this->context_->sink(), *p); } } @@ -1183,7 +1591,13 @@ Escape_analysis_assign::call(Call_expression* call) return; } + if (debug_level > 2) + inform(call->location(), "esccall:: %s not recursive", + call_node->ast_format(gogo).c_str()); + Node::Escape_state* call_state = call_node->state(this->context_, NULL); + if (!call_state->retvals.empty()) + error("esc already decorated call %s", call_node->ast_format(gogo).c_str()); this->context_->init_retvals(call_node, fntype); // Receiver. @@ -1251,6 +1665,9 @@ Escape_analysis_assign::call(Call_expression* call) for (; p != arg_nodes.end(); ++p) { + if (debug_level > 2) + inform(call->location(), "esccall:: ... <- %s, untracked", + (*p)->ast_format(gogo).c_str()); this->assign(this->context_->sink(), *p); } } @@ -1265,6 +1682,17 @@ Escape_analysis_assign::call(Call_expression* call) void Escape_analysis_assign::assign(Node* dst, Node* src) { + Gogo* gogo = this->context_->gogo(); + int debug_level = gogo->debug_escape_level(); + if (debug_level > 1) + inform(dst->location(), "[%d] %s escassign: %s(%s)[%s] = %s(%s)[%s]", + this->context_->loop_depth(), + strip_packed_prefix(gogo, this->context_->current_function_name()).c_str(), + dst->ast_format(gogo).c_str(), dst->details().c_str(), + dst->op_format().c_str(), + src->ast_format(gogo).c_str(), src->details().c_str(), + src->op_format().c_str()); + if (dst->expr() != NULL) { // Analyze the lhs of the assignment. @@ -1684,6 +2112,10 @@ Escape_analysis_assign::assign_from_note(std::string* note, } } + if (this->context_->gogo()->debug_escape_level() > 2) + inform(src->location(), "assignfromtag:: src= em=%s", + Escape_note::make_tag(enc).c_str()); + if (enc == Node::ESCAPE_UNKNOWN) { // Lost track of the value. @@ -1750,6 +2182,11 @@ Escape_analysis_assign::flows(Node* dst, Node* src) || src_state->flows.find(dst) != src_state->flows.end()) return; + Gogo* gogo = this->context_->gogo(); + if (gogo->debug_escape_level() > 2) + inform(Linemap::unknown_location(), "flows:: %s <- %s", + dst->ast_format(gogo).c_str(), src->ast_format(gogo).c_str()); + if (dst_state->flows.empty()) this->context_->add_dst(dst); @@ -1905,6 +2342,22 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, src_state->level = level; int mod_loop_depth = std::max(extra_loop_depth, src_state->loop_depth); + Gogo* gogo = this->context_->gogo(); + int debug_level = gogo->debug_escape_level(); + if (debug_level > 1) + inform(Linemap::unknown_location(), + "escwalk: level:{%d %d} depth:%d " + "op=%s %s(%s) " + "scope:%s[%d] " + "extraloopdepth=%d", + level.value(), level.suffix_value(), this->context_->pdepth(), + src->op_format().c_str(), + src->ast_format(gogo).c_str(), + src->details().c_str(), + debug_function_name(src_state->fn).c_str(), + src_state->loop_depth, + extra_loop_depth); + this->context_->increase_pdepth(); // Input parameter flowing into output parameter? @@ -1934,6 +2387,20 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, // 2. return &in // 3. tmp := in; return &tmp // 4. return *in + if (debug_level != 0) + { + if (debug_level == 1) + inform(src->location(), + "leaking param: %s to result %s level=%d", + src->ast_format(gogo).c_str(), dst->ast_format(gogo).c_str(), + level.value()); + else + inform(src->location(), + "leaking param: %s to result %s level={%d %d}", + src->ast_format(gogo).c_str(), dst->ast_format(gogo).c_str(), + level.value(), level.suffix_value()); + } + if ((src->encoding() & ESCAPE_MASK) != Node::ESCAPE_RETURN) { int enc = @@ -1968,6 +2435,9 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, Node::max_encoding((src->encoding() | ESCAPE_CONTENT_ESCAPES), Node::ESCAPE_NONE); src->set_encoding(enc); + if (debug_level != 0) + inform(src->location(), "mark escaped content: %s", + src->ast_format(gogo).c_str()); } // A src object leaks if its value or address is assigned to a dst object @@ -1987,9 +2457,14 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, Node::max_encoding((src->encoding() | ESCAPE_CONTENT_ESCAPES), Node::ESCAPE_NONE); src->set_encoding(enc); + if (debug_level != 0) + inform(src->location(), "leaking param content: %s", + src->ast_format(gogo).c_str()); } else { + if (debug_level != 0) + inform(src->location(), "leaking param"); src->set_encoding(Node::ESCAPE_SCOPE); } } @@ -1998,6 +2473,10 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, Expression* e = src->expr(); if (e->enclosed_var_expression() != NULL) { + if (src_leaks && debug_level != 0) + inform(src->location(), "leaking closure reference %s", + src->ast_format(gogo).c_str()); + Node* enclosed_node = Node::make_node(e->enclosed_var_expression()->variable()); this->flood(level, dst, enclosed_node, -1); @@ -2020,6 +2499,23 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, if (src_leaks) { src->set_encoding(Node::ESCAPE_HEAP); + if (debug_level != 0) + { + inform(underlying->location(), "moved to heap: %s", + underlying_node->ast_format(gogo).c_str()); + + if (debug_level > 1) + inform(src->location(), + "%s escapes to heap, level={%d %d}, " + "dst.eld=%d, src.eld=%d", + src->ast_format(gogo).c_str(), level.value(), + level.suffix_value(), dst_state->loop_depth, + mod_loop_depth); + else + inform(src->location(), "%s escapes to heap", + src->ast_format(gogo).c_str()); + } + this->flood(level.decrease(), dst, underlying_node, mod_loop_depth); extra_loop_depth = mod_loop_depth; @@ -2046,6 +2542,9 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, if (src_leaks) { src->set_encoding(Node::ESCAPE_HEAP); + if (debug_level != 0) + inform(src->location(), "%s escapes to heap", + src->ast_format(gogo).c_str()); extra_loop_depth = mod_loop_depth; } } @@ -2089,6 +2588,9 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, if (src_leaks) { src->set_encoding(Node::ESCAPE_HEAP); + if (debug_level != 0) + inform(src->location(), "%s escapes to heap", + src->ast_format(gogo).c_str()); extra_loop_depth = mod_loop_depth; } break; @@ -2104,6 +2606,9 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, // A closure or bound method; we lost track of actual function // so if this leaks, this call must be done on the heap. src->set_encoding(Node::ESCAPE_HEAP); + if (debug_level != 0) + inform(src->location(), "%s escapes to heap", + src->ast_format(gogo).c_str()); } } } @@ -2111,6 +2616,9 @@ Escape_analysis_flood::flood(Level level, Node* dst, Node* src, { // Calls to Runtime::NEW get lowered into an allocation expression. src->set_encoding(Node::ESCAPE_HEAP); + if (debug_level != 0) + inform(src->location(), "%s escapes to heap", + src->ast_format(gogo).c_str()); } else if ((e->field_reference_expression() != NULL && e->field_reference_expression()->expr()->unary_expression() == NULL) @@ -2184,6 +2692,13 @@ void Gogo::propagate_escape(Escape_context* context, Node* dst) { Node::Escape_state* state = dst->state(context, NULL); + Gogo* gogo = context->gogo(); + if (gogo->debug_escape_level() > 1) + inform(Linemap::unknown_location(), "escflood:%d: dst %s scope:%s[%d]", + context->flood_id(), dst->ast_format(gogo).c_str(), + debug_function_name(state->fn).c_str(), + state->loop_depth); + Escape_analysis_flood eaf(context); for (std::set::const_iterator p = state->flows.begin(); p != state->flows.end(); diff --git a/gcc/go/gofrontend/escape.h b/gcc/go/gofrontend/escape.h index 2278c473007..e6d1a3d6e1a 100644 --- a/gcc/go/gofrontend/escape.h +++ b/gcc/go/gofrontend/escape.h @@ -193,6 +193,17 @@ class Node Location location() const; + // Return this node's AST formatted string. + std::string + ast_format(Gogo*) const; + + // Return this node's detailed format string. + std::string + details() const; + + std::string + op_format() const; + // Return this node's escape state. Escape_state* state(Escape_context* context, Named_object* fn); @@ -343,6 +354,10 @@ class Escape_context set_current_function(Named_object* fn) { this->current_function_ = fn; } + // Return the name of the current function. + std::string + current_function_name() const; + // Return true if this is the context for a mutually recursive set of functions. bool recursive() const diff --git a/gcc/go/gofrontend/go.cc b/gcc/go/gofrontend/go.cc index cd30cca2657..a0c74228158 100644 --- a/gcc/go/gofrontend/go.cc +++ b/gcc/go/gofrontend/go.cc @@ -22,7 +22,8 @@ GO_EXTERN_C void go_create_gogo(int int_type_size, int pointer_size, const char *pkgpath, const char *prefix, const char *relative_import_path, - bool check_divide_by_zero, bool check_divide_overflow) + bool check_divide_by_zero, bool check_divide_overflow, + int debug_escape_level) { go_assert(::gogo == NULL); Linemap* linemap = go_get_linemap(); @@ -39,6 +40,7 @@ go_create_gogo(int int_type_size, int pointer_size, const char *pkgpath, ::gogo->set_check_divide_by_zero(check_divide_by_zero); if (check_divide_overflow) ::gogo->set_check_divide_overflow(check_divide_overflow); + ::gogo->set_debug_escape_level(debug_escape_level); } // Parse the input files. diff --git a/gcc/go/gofrontend/gogo.h b/gcc/go/gofrontend/gogo.h index 765792b06c5..07234fbb06c 100644 --- a/gcc/go/gofrontend/gogo.h +++ b/gcc/go/gofrontend/gogo.h @@ -239,6 +239,16 @@ class Gogo set_check_divide_overflow(bool b) { this->check_divide_overflow_ = b; } + // Return the level of escape analysis debug information to emit. + int + debug_escape_level() const + { return this->debug_escape_level_; } + + // Set the level of escape analysis debugging from a command line option. + void + set_debug_escape_level(int level) + { this->debug_escape_level_ = level; } + // Return the priority to use for the package we are compiling. // This is two more than the largest priority of any package we // import. @@ -786,6 +796,9 @@ class Gogo // Whether or not to check for division overflow, from the // -fgo-check-divide-overflow option. bool check_divide_overflow_; + // The level of escape analysis debug information to emit, from the + // -fgo-debug-escape option. + int debug_escape_level_; // A list of types to verify. std::vector verify_types_; // A list of interface types defined while parsing. @@ -2715,7 +2728,7 @@ class Unnamed_label { public: Unnamed_label(Location location) - : location_(location), blabel_(NULL) + : location_(location), derived_from_(NULL), blabel_(NULL) { } // Get the location where the label is defined. @@ -2728,6 +2741,16 @@ class Unnamed_label set_location(Location location) { this->location_ = location; } + // Get the top level statement this unnamed label is derived from. + Statement* + derived_from() const + { return this->derived_from_; } + + // Set the top level statement this unnamed label is derived from. + void + set_derived_from(Statement* s) + { this->derived_from_ = s; } + // Return a statement which defines this label. Bstatement* get_definition(Translate_context*); @@ -2743,6 +2766,9 @@ class Unnamed_label // The location where the label is defined. Location location_; + // The top-level statement this unnamed label was derived/lowered from. + // This is NULL is this label is not the top-level of a lowered statement. + Statement* derived_from_; // The backend representation of this label. Blabel* blabel_; }; diff --git a/gcc/go/gofrontend/statements.cc b/gcc/go/gofrontend/statements.cc index 0fd871f51ad..9066c016730 100644 --- a/gcc/go/gofrontend/statements.cc +++ b/gcc/go/gofrontend/statements.cc @@ -3058,7 +3058,6 @@ Unnamed_label_statement::do_get_backend(Translate_context* context) return this->label_->get_definition(context); } - // Dump the AST representation for an unnamed label definition statement. void @@ -5091,6 +5090,7 @@ For_statement::do_lower(Gogo*, Named_object*, Block* enclosing, } Unnamed_label* top = new Unnamed_label(this->location()); + top->set_derived_from(this); b->add_statement(Statement::make_unnamed_label_statement(top)); s = Statement::make_block_statement(this->statements_, diff --git a/gcc/go/lang.opt b/gcc/go/lang.opt index 7d44e44800d..7de9386f92a 100644 --- a/gcc/go/lang.opt +++ b/gcc/go/lang.opt @@ -69,6 +69,10 @@ frequire-return-statement Go Var(go_require_return_statement) Init(1) Warning Functions which return values must end with return statements. +fgo-debug-escape +Go Joined UInteger Var(go_debug_escape_level) Init(0) +Emit debugging information related to the escape analysis pass when run with -fgo-optimize-allocs. + o Go Joined Separate ; Documented in common.opt -- 2.30.2