escape: Add basic debugging.
authorChris Manghane <cmang@google.com>
Tue, 2 Aug 2016 21:43:48 +0000 (21:43 +0000)
committerIan Lance Taylor <ian@gcc.gnu.org>
Tue, 2 Aug 2016 21:43:48 +0000 (21:43 +0000)
    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  <cmang@google.com>

* 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
gcc/go/go-c.h
gcc/go/go-lang.c
gcc/go/gofrontend/MERGE
gcc/go/gofrontend/escape.cc
gcc/go/gofrontend/escape.h
gcc/go/gofrontend/go.cc
gcc/go/gofrontend/gogo.h
gcc/go/gofrontend/statements.cc
gcc/go/lang.opt

index e486a305a0160e7dcdc6a5e380f2803eef0f1b8d..60d6e14640a15a2506b68a4724bb6bdbaf091d11 100644 (file)
@@ -1,3 +1,10 @@
+2016-08-02  Chris Manghane  <cmang@google.com>
+
+       * 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  <cmang@google.com>
 
        * Make-lang.in (GO_OBJS): Add go/escape.o (based on an entirely
index 98b2850be8c91c8374d3dd797c53d1a92039cfda..690d6db7ab96bf132f6a48fbccfa01b8c3c8dcc5 100644 (file)
@@ -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,
index 9c95c8e0bb16462a314f9bad7a1c34229858cdb7..570f5e06e8d8ddeed113af026750269f185f1819 100644 (file)
@@ -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 ();
 
index 69190dd5cda3790982ac2f6af842df369bb7f7da..7e1cc13ef9350f6449858b5652f767c5a82639ed 100644 (file)
@@ -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.
index af8f1e2f9936e22e84e303282978bcb6ad9a63e6..6814c7ea74d57cf34819c297077b9d0145aa4dcf 100644 (file)
@@ -6,12 +6,14 @@
 
 #include <limits>
 #include <stack>
+#include <sstream>
 
 #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 "<S>";
+
+  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<Node*> noesc = context->non_escaping_nodes();
+         for (std::vector<Node*>::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<Node*>::const_iterator p = state->flows.begin();
        p != state->flows.end();
index 2278c47300776af8f061538ddce1fad936b5ea42..e6d1a3d6e1a1d550d415f034f2fd01e05ff263fe 100644 (file)
@@ -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
index cd30cca265741638431ec576d5b6753e753d4f10..a0c74228158cde0235a342b119311fdb492e4654 100644 (file)
@@ -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.
index 765792b06c56818538ff93a5597a2f27d2e84808..07234fbb06c7bd0c99b99d67041bd4fefcf646fa 100644 (file)
@@ -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<Type*> 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_;
 };
index 0fd871f51ad8abe84ee72a10eeab4a00935978e2..9066c016730ab35ed734522fc32521d07674ef5b 100644 (file)
@@ -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_,
index 7d44e44800dfdcbbe0e54f0a031fd1a1fd7eac00..7de9386f92a5617fa607fd0e27811e52dd1c0ba3 100644 (file)
@@ -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