This patch by Chris Manghane adds debugging to the escape analysis
code.  This debugging is designed to generate the same sort of output
as the gc Go compiler, for easier comparison of results.  Escape
analysis is still not enabled by default.  Bootstrapped and ran Go
testsuite on x86_64-pc-linux-gnu.  Committed to mainline.

Ian


2016-08-02  Chris Manghane  <cm...@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.
Index: gcc/go/go-c.h
===================================================================
--- gcc/go/go-c.h       (revision 238653)
+++ gcc/go/go-c.h       (working copy)
@@ -34,7 +34,8 @@ extern void go_add_search_path (const ch
 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: gcc/go/go-lang.c
===================================================================
--- gcc/go/go-lang.c    (revision 238653)
+++ gcc/go/go-lang.c    (working copy)
@@ -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: gcc/go/gofrontend/MERGE
===================================================================
--- gcc/go/gofrontend/MERGE     (revision 238743)
+++ gcc/go/gofrontend/MERGE     (working copy)
@@ -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: gcc/go/gofrontend/escape.cc
===================================================================
--- gcc/go/gofrontend/escape.cc (revision 238653)
+++ gcc/go/gofrontend/escape.cc (working copy)
@@ -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* gog
   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*
   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*
 
     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*
 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(Expre
   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(Expre
                  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(Expre
 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_expres
            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_expres
       && 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_expres
 
          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_expres
       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_expres
 
       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_expres
 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
         }
     }
 
+  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,
       || 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
   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
       // 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::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::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
       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
          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
          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
                      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
                  // 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
        {
          // 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: gcc/go/gofrontend/escape.h
===================================================================
--- gcc/go/gofrontend/escape.h  (revision 238653)
+++ gcc/go/gofrontend/escape.h  (working copy)
@@ -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: gcc/go/gofrontend/go.cc
===================================================================
--- gcc/go/gofrontend/go.cc     (revision 238653)
+++ gcc/go/gofrontend/go.cc     (working copy)
@@ -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 po
     ::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: gcc/go/gofrontend/gogo.h
===================================================================
--- gcc/go/gofrontend/gogo.h    (revision 238653)
+++ gcc/go/gofrontend/gogo.h    (working copy)
@@ -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: gcc/go/gofrontend/statements.cc
===================================================================
--- gcc/go/gofrontend/statements.cc     (revision 238653)
+++ gcc/go/gofrontend/statements.cc     (working copy)
@@ -3058,7 +3058,6 @@ Unnamed_label_statement::do_get_backend(
   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_obj
     }
 
   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: gcc/go/lang.opt
===================================================================
--- gcc/go/lang.opt     (revision 238653)
+++ gcc/go/lang.opt     (working copy)
@@ -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

Reply via email to