Go patch committed: Enable escape analysis for runtime

Message ID CAOyqgcXG5cuSGw7T1WfS1EmbjQL+q=-YcYQJemDuX=Zy39fy_A@mail.gmail.com
State New
Headers show
Series
  • Go patch committed: Enable escape analysis for runtime
Related show

Commit Message

Ian Lance Taylor Jan. 17, 2018, 9:25 p.m.
This patch to the Go frontend by Cherry Zhang enables escape analysis
for the runtime package in the Go frontend.  The runtime package was
hard-coded non-escape, and the escape analysis was not run for the
runtime package.  This patch removes the hard-code, and lets the
escape analysis decide.  It is not allowed for local variables and
closures in the runtime to be heap allocated. This patch adds the
check that make sure that they indeed do not escape.

The escape analysis is always run when compiling the runtime now.

Bootstrapped and ran Go testsuite on x86_64-pc-linux-gnu.  Committed
to mainline.

I'm almost done.  I promise.

Ian

Comments

Rainer Orth Jan. 18, 2018, 7:53 p.m. | #1
Hi Ian,

> This patch to the Go frontend by Cherry Zhang enables escape analysis

> for the runtime package in the Go frontend.  The runtime package was

> hard-coded non-escape, and the escape analysis was not run for the

> runtime package.  This patch removes the hard-code, and lets the

> escape analysis decide.  It is not allowed for local variables and

> closures in the runtime to be heap allocated. This patch adds the

> check that make sure that they indeed do not escape.

>

> The escape analysis is always run when compiling the runtime now.

>

> Bootstrapped and ran Go testsuite on x86_64-pc-linux-gnu.  Committed

> to mainline.


this broke Solaris bootstrap:

/vol/gcc/src/hg/trunk/local/libgo/go/runtime/netpoll_solaris.go:182:57: error: n escapes to heap, not allowed in runtime
  if port_getn(portfd, &events[0], uint32(len(events)), &n, wait) < 0 {
                                                         ^
/vol/gcc/src/hg/trunk/local/libgo/go/runtime/netpoll_solaris.go:176:11: error: zero escapes to heap, not allowed in runtime
   wait = &zero
           ^
/vol/gcc/src/hg/trunk/local/libgo/go/runtime/netpoll_solaris.go:174:6: error: .runtime.zero escapes to heap, not allowed in runtime
  var zero timespec
      ^
/vol/gcc/src/hg/trunk/local/libgo/go/runtime/netpoll_solaris.go:179:6: error: .runtime.events escapes to heap, not allowed in runtime
  var events [128]portevent
      ^
/vol/gcc/src/hg/trunk/local/libgo/go/runtime/netpoll_solaris.go:181:6: error: .runtime.n escapes to heap, not allowed in runtime
  var n uint32 = 1
      ^

Fixed as follows.  i386-pc-solaris2.11 and sparc-sun-solaris2.11 builds
have completed, make check still running.

	Rainer

-- 
-----------------------------------------------------------------------------
Rainer Orth, Center for Biotechnology, Bielefeld University
diff --git a/libgo/go/runtime/netpoll_solaris.go b/libgo/go/runtime/netpoll_solaris.go
--- a/libgo/go/runtime/netpoll_solaris.go
+++ b/libgo/go/runtime/netpoll_solaris.go
@@ -84,6 +84,7 @@ func port_associate(port, source int32, 
 //extern port_dissociate
 func port_dissociate(port, source int32, object uintptr) int32
 
+//go:noescape
 //extern port_getn
 func port_getn(port int32, evs *portevent, max uint32, nget *uint32, timeout *timespec) int32
Ian Lance Taylor Jan. 19, 2018, 4:10 a.m. | #2
On Thu, Jan 18, 2018 at 11:53 AM, Rainer Orth
<ro@cebitec.uni-bielefeld.de> wrote:
>

>> This patch to the Go frontend by Cherry Zhang enables escape analysis

>> for the runtime package in the Go frontend.  The runtime package was

>> hard-coded non-escape, and the escape analysis was not run for the

>> runtime package.  This patch removes the hard-code, and lets the

>> escape analysis decide.  It is not allowed for local variables and

>> closures in the runtime to be heap allocated. This patch adds the

>> check that make sure that they indeed do not escape.

>>

>> The escape analysis is always run when compiling the runtime now.

>>

>> Bootstrapped and ran Go testsuite on x86_64-pc-linux-gnu.  Committed

>> to mainline.

>

> this broke Solaris bootstrap:

>

> /vol/gcc/src/hg/trunk/local/libgo/go/runtime/netpoll_solaris.go:182:57: error: n escapes to heap, not allowed in runtime

>   if port_getn(portfd, &events[0], uint32(len(events)), &n, wait) < 0 {

>                                                          ^

> /vol/gcc/src/hg/trunk/local/libgo/go/runtime/netpoll_solaris.go:176:11: error: zero escapes to heap, not allowed in runtime

>    wait = &zero

>            ^

> /vol/gcc/src/hg/trunk/local/libgo/go/runtime/netpoll_solaris.go:174:6: error: .runtime.zero escapes to heap, not allowed in runtime

>   var zero timespec

>       ^

> /vol/gcc/src/hg/trunk/local/libgo/go/runtime/netpoll_solaris.go:179:6: error: .runtime.events escapes to heap, not allowed in runtime

>   var events [128]portevent

>       ^

> /vol/gcc/src/hg/trunk/local/libgo/go/runtime/netpoll_solaris.go:181:6: error: .runtime.n escapes to heap, not allowed in runtime

>   var n uint32 = 1

>       ^

>

> Fixed as follows.  i386-pc-solaris2.11 and sparc-sun-solaris2.11 builds

> have completed, make check still running.


Thanks, patch committed.

Ian

Patch

Index: gcc/go/gofrontend/MERGE
===================================================================
--- gcc/go/gofrontend/MERGE	(revision 256810)
+++ gcc/go/gofrontend/MERGE	(working copy)
@@ -1,4 +1,4 @@ 
-3ea7fc3b918210e7248dbc51d90af20639dc4167
+1072286ca9249bd6f75628aead325a66286bcf5b
 
 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 256707)
+++ gcc/go/gofrontend/escape.cc	(working copy)
@@ -873,13 +873,12 @@  escape_hash_match(std::string suffix, st
 void
 Gogo::analyze_escape()
 {
-  if (!optimize_allocation_flag.is_enabled() || saw_errors())
+  if (saw_errors())
     return;
 
-  // Currently runtime is hard-coded to non-escape in various places.
-  // Don't run escape analysis for runtime.
-  // TODO: remove this once it works for runtime.
-  if (this->compiling_runtime() && this->package_name() == "runtime")
+  if (!optimize_allocation_flag.is_enabled()
+      && !this->compiling_runtime())
+    // We always run escape analysis when compiling runtime.
     return;
 
   // Discover strongly connected groups of functions to analyze for escape
@@ -1473,6 +1472,35 @@  Escape_analysis_assign::statement(Block*
   return TRAVERSE_SKIP_COMPONENTS;
 }
 
+// Helper function to emit moved-to-heap diagnostics.
+
+static void
+move_to_heap(Gogo* gogo, Expression *expr)
+{
+  Named_object* no;
+  if (expr->var_expression() != NULL)
+    no = expr->var_expression()->named_object();
+  else if (expr->enclosed_var_expression() != NULL)
+    no = expr->enclosed_var_expression()->variable();
+  else
+    return;
+
+  if ((no->is_variable()
+       && !no->var_value()->is_global())
+      || no->is_result_variable())
+    {
+      Node* n = Node::make_node(expr);
+      if (gogo->debug_escape_level() != 0)
+        go_inform(n->definition_location(),
+                  "moved to heap: %s",
+                  n->ast_format(gogo).c_str());
+      if (gogo->compiling_runtime() && gogo->package_name() == "runtime")
+        go_error_at(expr->location(),
+                    "%s escapes to heap, not allowed in runtime",
+                    n->ast_format(gogo).c_str());
+    }
+}
+
 // Model expressions within a function as assignments and flows between nodes.
 
 int
@@ -1489,13 +1517,7 @@  Escape_analysis_assign::expression(Expre
       if (debug_level > 1)
 	go_inform((*pexpr)->location(), "%s too large for stack",
                   n->ast_format(gogo).c_str());
-      if (debug_level != 0
-          && ((*pexpr)->var_expression() != NULL
-              || (*pexpr)->enclosed_var_expression() != NULL))
-        go_inform(n->definition_location(),
-                  "moved to heap: %s",
-                  n->ast_format(gogo).c_str());
-
+      move_to_heap(gogo, *pexpr);
       n->set_encoding(Node::ESCAPE_HEAP);
       (*pexpr)->address_taken(true);
       this->assign(this->context_->sink(), n);
@@ -2968,25 +2990,20 @@  Escape_analysis_flood::flood(Level level
 	  if (src_leaks)
 	    {
 	      src->set_encoding(Node::ESCAPE_HEAP);
-	      if (debug_level != 0 && osrcesc != src->encoding())
-		{
-                  if (underlying->var_expression() != NULL
-                      || underlying->enclosed_var_expression() != NULL)
-                    go_inform(underlying_node->definition_location(),
-                              "moved to heap: %s",
-                              underlying_node->ast_format(gogo).c_str());
-
-		  if (debug_level > 1)
-		    go_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
-		    go_inform(src->location(), "%s escapes to heap",
-			      src->ast_format(gogo).c_str());
-		}
+              if (osrcesc != src->encoding())
+                {
+                  move_to_heap(gogo, underlying);
+                  if (debug_level > 1)
+                    go_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 if (debug_level > 0)
+                    go_inform(src->location(), "%s escapes to heap",
+                              src->ast_format(gogo).c_str());
+                }
 
 	      this->flood(level.decrease(), dst,
 			  underlying_node, mod_loop_depth);
Index: gcc/go/gofrontend/expressions.cc
===================================================================
--- gcc/go/gofrontend/expressions.cc	(revision 256593)
+++ gcc/go/gofrontend/expressions.cc	(working copy)
@@ -3700,7 +3700,7 @@  Expression::make_unsafe_cast(Type* type,
 // called after escape analysis but before inserting write barriers.
 
 void
-Unary_expression::check_operand_address_taken(Gogo* gogo)
+Unary_expression::check_operand_address_taken(Gogo*)
 {
   if (this->op_ != OPERATOR_AND)
     return;
@@ -3714,13 +3714,6 @@  Unary_expression::check_operand_address_
   if ((n->encoding() & ESCAPE_MASK) == int(Node::ESCAPE_NONE))
     this->escapes_ = false;
 
-  // When compiling the runtime, the address operator does not cause
-  // local variables to escape.  When escape analysis becomes the
-  // default, this should be changed to make it an error if we have an
-  // address operator that escapes.
-  if (gogo->compiling_runtime() && gogo->package_name() == "runtime")
-    this->escapes_ = false;
-
   Named_object* var = NULL;
   if (this->expr_->var_expression() != NULL)
     var = this->expr_->var_expression()->named_object();
@@ -7028,26 +7021,14 @@  Bound_method_expression::do_flatten(Gogo
   vals->push_back(val);
 
   Expression* ret = Expression::make_struct_composite_literal(st, vals, loc);
+  ret = Expression::make_heap_expression(ret, loc);
 
-  if (!gogo->compiling_runtime() || gogo->package_name() != "runtime")
-    {
-      ret = Expression::make_heap_expression(ret, loc);
-      Node* n = Node::make_node(this);
-      if ((n->encoding() & ESCAPE_MASK) == Node::ESCAPE_NONE)
-        ret->heap_expression()->set_allocate_on_stack();
-    }
-  else
-    {
-      // When compiling the runtime, method closures do not escape.
-      // When escape analysis becomes the default, and applies to
-      // method closures, this should be changed to make it an error
-      // if a method closure escapes.
-      Temporary_statement* ctemp = Statement::make_temporary(st, ret, loc);
-      inserter->insert(ctemp);
-      ret = Expression::make_temporary_reference(ctemp, loc);
-      ret = Expression::make_unary(OPERATOR_AND, ret, loc);
-      ret->unary_expression()->set_does_not_escape();
-    }
+  Node* n = Node::make_node(this);
+  if ((n->encoding() & ESCAPE_MASK) == Node::ESCAPE_NONE)
+    ret->heap_expression()->set_allocate_on_stack();
+  else if (gogo->compiling_runtime() && gogo->package_name() == "runtime")
+    go_error_at(loc, "%s escapes to heap, not allowed in runtime",
+                n->ast_format(gogo).c_str());
 
   // If necessary, check whether the expression or any embedded
   // pointers are nil.
@@ -9577,12 +9558,6 @@  Call_expression::lower_varargs(Gogo* gog
 			       Type* varargs_type, size_t param_count,
                                Slice_storage_escape_disp escape_disp)
 {
-  // When compiling the runtime, varargs slices do not escape.  When
-  // escape analysis becomes the default, this should be changed to
-  // make it an error if we have a varargs slice that escapes.
-  if (gogo->compiling_runtime() && gogo->package_name() == "runtime")
-    escape_disp = SLICE_STORAGE_DOES_NOT_ESCAPE;
-
   if (this->varargs_are_lowered_)
     return;
 
Index: gcc/go/gofrontend/parse.cc
===================================================================
--- gcc/go/gofrontend/parse.cc	(revision 256593)
+++ gcc/go/gofrontend/parse.cc	(working copy)
@@ -3059,21 +3059,6 @@  Parse::create_closure(Named_object* func
   Struct_type* st = closure_var->var_value()->type()->deref()->struct_type();
   Expression* cv = Expression::make_struct_composite_literal(st, initializer,
 							     location);
-
-  // When compiling the runtime, closures do not escape.  When escape
-  // analysis becomes the default, and applies to closures, this
-  // should be changed to make it an error if a closure escapes.
-  if (this->gogo_->compiling_runtime()
-      && this->gogo_->package_name() == "runtime")
-    {
-      Temporary_statement* ctemp = Statement::make_temporary(st, cv, location);
-      this->gogo_->add_statement(ctemp);
-      Expression* ref = Expression::make_temporary_reference(ctemp, location);
-      Expression* addr = Expression::make_unary(OPERATOR_AND, ref, location);
-      addr->unary_expression()->set_does_not_escape();
-      return addr;
-    }
-
   return Expression::make_heap_expression(cv, location);
 }
 
Index: gcc/go/gofrontend/wb.cc
===================================================================
--- gcc/go/gofrontend/wb.cc	(revision 256593)
+++ gcc/go/gofrontend/wb.cc	(working copy)
@@ -54,14 +54,10 @@  Mark_address_taken::expression(Expressio
       // Slice of an array. The escape analysis models this with
       // a child Node representing the address of the array.
       bool escapes = false;
-      if (!this->gogo_->compiling_runtime()
-          || this->gogo_->package_name() != "runtime")
-        {
-          Node* n = Node::make_node(expr);
-          if (n->child() == NULL
-              || (n->child()->encoding() & ESCAPE_MASK) != Node::ESCAPE_NONE)
-            escapes = true;
-        }
+      Node* n = Node::make_node(expr);
+      if (n->child() == NULL
+          || (n->child()->encoding() & ESCAPE_MASK) != Node::ESCAPE_NONE)
+        escapes = true;
       aie->array()->address_taken(escapes);
     }
 
@@ -127,6 +123,53 @@  Mark_address_taken::expression(Expressio
   return TRAVERSE_CONTINUE;
 }
 
+// Check variables and closures do not escape when compiling runtime.
+
+class Check_escape : public Traverse
+{
+ public:
+  Check_escape(Gogo* gogo)
+    : Traverse(traverse_expressions | traverse_variables),
+      gogo_(gogo)
+  { }
+
+  int
+  expression(Expression**);
+
+  int
+  variable(Named_object*);
+
+ private:
+  Gogo* gogo_;
+};
+
+int
+Check_escape::variable(Named_object* no)
+{
+  if ((no->is_variable() && no->var_value()->is_in_heap())
+      || (no->is_result_variable()
+          && no->result_var_value()->is_in_heap()))
+    go_error_at(no->location(),
+                "%s escapes to heap, not allowed in runtime",
+                no->name().c_str());
+  return TRAVERSE_CONTINUE;
+}
+
+int
+Check_escape::expression(Expression** pexpr)
+{
+  Expression* expr = *pexpr;
+  Func_expression* fe = expr->func_expression();
+  if (fe != NULL && fe->closure() != NULL)
+    {
+      Node* n = Node::make_node(expr);
+      if (n->encoding() == Node::ESCAPE_HEAP)
+        go_error_at(expr->location(),
+                    "heap-allocated closure, not allowed in runtime");
+    }
+  return TRAVERSE_CONTINUE;
+}
+
 // Add write barriers to the IR.  This are required by the concurrent
 // garbage collector.  A write barrier is needed for any write of a
 // pointer into memory controlled by the garbage collector.  Write
@@ -370,6 +413,12 @@  Gogo::add_write_barriers()
   Mark_address_taken mat(this);
   this->traverse(&mat);
 
+  if (this->compiling_runtime() && this->package_name() == "runtime")
+    {
+      Check_escape chk(this);
+      this->traverse(&chk);
+    }
+
   Write_barriers wb(this);
   this->traverse(&wb);
 }