compiler: enable escape analysis for runtime
authorIan Lance Taylor <ian@gcc.gnu.org>
Wed, 17 Jan 2018 21:24:41 +0000 (21:24 +0000)
committerIan Lance Taylor <ian@gcc.gnu.org>
Wed, 17 Jan 2018 21:24:41 +0000 (21:24 +0000)
    The runtime package was hard-coded non-escape, and the escape
    analysis was not run for the runtime package. This CL 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 CL adds the check that make sure that they
    indeed do not escape.

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

    Fixes golang/go#17431

    Reviewed-on: https://go-review.googlesource.com/86246

From-SVN: r256820

gcc/go/gofrontend/MERGE
gcc/go/gofrontend/escape.cc
gcc/go/gofrontend/expressions.cc
gcc/go/gofrontend/parse.cc
gcc/go/gofrontend/wb.cc

index fb7da45eda06937eb8a9444eee94509866c16e8b..e7b959f850e5375b24cf4b48840d1d0aae8621b9 100644 (file)
@@ -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 01329e10eb13a5a54a436ee6b39b943f190c0be6..7bab3cbdd4a82bb0a81fa51f9e197f26da26518d 100644 (file)
@@ -873,13 +873,12 @@ escape_hash_match(std::string suffix, std::string name)
 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*, size_t*, Statement* s)
   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(Expression** pexpr)
       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, Node* dst, Node* src,
          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 d4a207174db62a441ac62a43e754e8ae222c12e2..99fb1b59adee06df9207b5dd402be778c693a6ca 100644 (file)
@@ -3700,7 +3700,7 @@ Expression::make_unsafe_cast(Type* type, Expression* expr,
 // 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_taken(Gogo* gogo)
   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* gogo, Named_object*,
   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* gogo, Named_object* function,
                               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 98f30673d67fe0a9dd3e455fb7c9cea75ada14d5..8162abf342b13a7914907cc05610e035a41cacd7 100644 (file)
@@ -3059,21 +3059,6 @@ Parse::create_closure(Named_object* function, Enclosing_vars* enclosing_vars,
   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 77a59ac4c7c260a76803521e0b4dba3485991ff3..fb3ef4d59020270c1556ffb92b46e3e00bb63bd0 100644 (file)
@@ -54,14 +54,10 @@ Mark_address_taken::expression(Expression** pexpr)
       // 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(Expression** pexpr)
   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);
 }