Go patch committed: Implement //go:nowritebarrierrec

Ian Lance Taylor iant@golang.org
Thu Sep 13 17:58:00 GMT 2018


The libgo runtime package uses special comments //go:nowritebarrier,
//go:nowritebarrierrec, and //go:yeswritebarrierrec to record whether
write barriers are permitted in a function or not.  The Go frontend
already implements //go:nowritebarrier, but it has been treating
//go:nowritebarrierrec as equivalent to //go:nowritebarrier.  The
//go:nowritebarrierrec comment means not only that the function itself
may not have any write barriers, but also that any functions that it
calls directly may not have any write barriers--unless the function
called is marked //go:yeswritebarrierrec.  This patch implements
support for these comments in the Go frontend.  Bootstrapped and ran
Go testsuite on x86_64-pc-linux-gnu.  Committed to mainline.

Ian
-------------- next part --------------
Index: gcc/go/gofrontend/MERGE
===================================================================
--- gcc/go/gofrontend/MERGE	(revision 264282)
+++ gcc/go/gofrontend/MERGE	(working copy)
@@ -1,4 +1,4 @@
-baf07c40960dc4f8df9da97281870d80d4245b18
+f68c03e509b26e7f483f2800eb70a5fbf3f74d0b
 
 The first line of this file holds the git revision number of the last
 merge done from the gofrontend repository.
Index: gcc/go/gofrontend/gogo.h
===================================================================
--- gcc/go/gofrontend/gogo.h	(revision 264245)
+++ gcc/go/gofrontend/gogo.h	(working copy)
@@ -941,6 +941,9 @@ class Gogo
                    std::vector<Bstatement*>&,
                    Bfunction* init_bfunction);
 
+  void
+  propagate_writebarrierrec();
+
   Named_object*
   write_barrier_variable();
 
Index: gcc/go/gofrontend/lex.cc
===================================================================
--- gcc/go/gofrontend/lex.cc	(revision 264245)
+++ gcc/go/gofrontend/lex.cc	(working copy)
@@ -1922,9 +1922,15 @@ Lex::skip_cpp_comment()
       // function that it calls, needs to use any write barriers, it
       // should emit an error instead.
       // FIXME: Should only work when compiling the runtime package.
-      // FIXME: currently treated the same as go:nowritebarrier
       this->pragmas_ |= GOPRAGMA_NOWRITEBARRIERREC;
     }
+  else if (verb == "go:yeswritebarrierrec")
+    {
+      // Applies to the next function.  Disables go:nowritebarrierrec
+      // when looking at callees; write barriers are permitted here.
+      // FIXME: Should only work when compiling the runtime package.
+      this->pragmas_ |= GOPRAGMA_YESWRITEBARRIERREC;
+    }
   else if (verb == "go:cgo_unsafe_args")
     {
       // Applies to the next function.  Taking the address of any
Index: gcc/go/gofrontend/lex.h
===================================================================
--- gcc/go/gofrontend/lex.h	(revision 264245)
+++ gcc/go/gofrontend/lex.h	(working copy)
@@ -63,9 +63,11 @@ enum GoPragma
   GOPRAGMA_SYSTEMSTACK = 1 << 5,	// Must run on system stack.
   GOPRAGMA_NOWRITEBARRIER = 1 << 6,	// No write barriers.
   GOPRAGMA_NOWRITEBARRIERREC = 1 << 7,	// No write barriers here or callees.
-  GOPRAGMA_CGOUNSAFEARGS = 1 << 8,	// Pointer to arg is pointer to all.
-  GOPRAGMA_UINTPTRESCAPES = 1 << 9,	// uintptr(p) escapes.
-  GOPRAGMA_NOTINHEAP = 1 << 10		// type is not in heap.
+  GOPRAGMA_YESWRITEBARRIERREC = 1 << 8,	// Stops nowritebarrierrec.
+  GOPRAGMA_MARK = 1 << 9,		// Marker for nowritebarrierrec.
+  GOPRAGMA_CGOUNSAFEARGS = 1 << 10,	// Pointer to arg is pointer to all.
+  GOPRAGMA_UINTPTRESCAPES = 1 << 11,	// uintptr(p) escapes.
+  GOPRAGMA_NOTINHEAP = 1 << 12		// type is not in heap.
 };
 
 // A token returned from the lexer.
Index: gcc/go/gofrontend/parse.cc
===================================================================
--- gcc/go/gofrontend/parse.cc	(revision 264245)
+++ gcc/go/gofrontend/parse.cc	(working copy)
@@ -2360,7 +2360,10 @@ Parse::function_decl(unsigned int pragma
 	{ GOPRAGMA_NOINLINE, "noinline", false, true, true },
 	{ GOPRAGMA_SYSTEMSTACK, "systemstack", false, true, true },
 	{ GOPRAGMA_NOWRITEBARRIER, "nowritebarrier", false, true, true },
-	{ GOPRAGMA_NOWRITEBARRIERREC, "nowritebarrierrec", false, true, true },
+	{ GOPRAGMA_NOWRITEBARRIERREC, "nowritebarrierrec", false, true,
+	  true },
+	{ GOPRAGMA_YESWRITEBARRIERREC, "yeswritebarrierrec", false, true,
+	  true },
 	{ GOPRAGMA_CGOUNSAFEARGS, "cgo_unsafe_args", false, true, true },
 	{ GOPRAGMA_UINTPTRESCAPES, "uintptrescapes", true, true, true },
       };
Index: gcc/go/gofrontend/wb.cc
===================================================================
--- gcc/go/gofrontend/wb.cc	(revision 264259)
+++ gcc/go/gofrontend/wb.cc	(working copy)
@@ -231,6 +231,133 @@ Check_escape::expression(Expression** pe
   return TRAVERSE_CONTINUE;
 }
 
+// Collect all writebarrierrec functions.  This is used when compiling
+// the runtime package, to propagate //go:nowritebarrierrec.
+
+class Collect_writebarrierrec_functions : public Traverse
+{
+ public:
+  Collect_writebarrierrec_functions(std::vector<Named_object*>* worklist)
+    : Traverse(traverse_functions),
+      worklist_(worklist)
+  { }
+
+ private:
+  int
+  function(Named_object*);
+
+  // The collected functions are put here.
+  std::vector<Named_object*>* worklist_;
+};
+
+int
+Collect_writebarrierrec_functions::function(Named_object* no)
+{
+  if (no->is_function()
+      && no->func_value()->enclosing() == NULL
+      && (no->func_value()->pragmas() & GOPRAGMA_NOWRITEBARRIERREC) != 0)
+    {
+      go_assert((no->func_value()->pragmas() & GOPRAGMA_MARK) == 0);
+      this->worklist_->push_back(no);
+    }
+  return TRAVERSE_CONTINUE;
+}
+
+// Collect all callees of this function.  We only care about locally
+// defined, known, functions.
+
+class Collect_callees : public Traverse
+{
+ public:
+  Collect_callees(std::vector<Named_object*>* worklist)
+    : Traverse(traverse_expressions),
+      worklist_(worklist)
+  { }
+
+ private:
+  int
+  expression(Expression**);
+
+  // The collected callees are put here.
+  std::vector<Named_object*>* worklist_;
+};
+
+int
+Collect_callees::expression(Expression** pexpr)
+{
+  Call_expression* ce = (*pexpr)->call_expression();
+  if (ce != NULL)
+    {
+      Func_expression* fe = ce->fn()->func_expression();
+      if (fe != NULL)
+	{
+	  Named_object* no = fe->named_object();
+	  if (no->package() == NULL && no->is_function())
+	    {
+	      // The function runtime.systemstack is special, in that
+	      // it is a common way to call a function in the runtime:
+	      // mark its argument if we can.
+	      if (Gogo::unpack_hidden_name(no->name()) != "systemstack")
+		this->worklist_->push_back(no);
+	      else if (ce->args()->size() > 0)
+		{
+		  fe = ce->args()->front()->func_expression();
+		  if (fe != NULL)
+		    {
+		      no = fe->named_object();
+		      if (no->package() == NULL && no->is_function())
+			this->worklist_->push_back(no);
+		    }
+		}
+	    }
+	}
+    }
+  return TRAVERSE_CONTINUE;
+}
+
+// When compiling the runtime package, propagate //go:nowritebarrierrec
+// annotations.  A function marked as //go:nowritebarrierrec does not
+// permit write barriers, and also all the functions that it calls,
+// recursively, do not permit write barriers.  Except that a
+// //go:yeswritebarrierrec annotation permits write barriers even if
+// called by a //go:nowritebarrierrec function.  Here we turn
+// //go:nowritebarrierrec into //go:nowritebarrier, as appropriate.
+
+void
+Gogo::propagate_writebarrierrec()
+{
+  std::vector<Named_object*> worklist;
+  Collect_writebarrierrec_functions cwf(&worklist);
+  this->traverse(&cwf);
+
+  Collect_callees cc(&worklist);
+
+  while (!worklist.empty())
+    {
+      Named_object* no = worklist.back();
+      worklist.pop_back();
+
+      unsigned int pragmas = no->func_value()->pragmas();
+      if ((pragmas & GOPRAGMA_MARK) != 0)
+	{
+	  // We've already seen this function.
+	  continue;
+	}
+      if ((pragmas & GOPRAGMA_YESWRITEBARRIERREC) != 0)
+	{
+	  // We don't want to propagate //go:nowritebarrierrec into
+	  // this function or it's callees.
+	  continue;
+	}
+
+      no->func_value()->set_pragmas(pragmas
+				    | GOPRAGMA_NOWRITEBARRIER
+				    | GOPRAGMA_MARK);
+
+      no->func_value()->traverse(&cc);
+    }
+}
+
 // 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
@@ -492,6 +619,8 @@ Gogo::add_write_barriers()
 
   if (this->compiling_runtime() && this->package_name() == "runtime")
     {
+      this->propagate_writebarrierrec();
+
       Check_escape chk(this);
       this->traverse(&chk);
     }
@@ -536,8 +665,8 @@ Gogo::assign_needs_write_barrier(Express
   if (!lhs->type()->has_pointer())
     return false;
 
-  // An assignment to a field is handled like an assignment to the
-  // struct.
+  // An assignment to a field or an array index is handled like an
+  // assignment to the struct.
   while (true)
     {
       // Nothing to do for a type that can not be in the heap, or a
@@ -550,9 +679,22 @@ Gogo::assign_needs_write_barrier(Express
 	return false;
 
       Field_reference_expression* fre = lhs->field_reference_expression();
-      if (fre == NULL)
-	break;
-      lhs = fre->expr();
+      if (fre != NULL)
+	{
+	  lhs = fre->expr();
+	  continue;
+	}
+
+      Array_index_expression* aie = lhs->array_index_expression();
+      if (aie != NULL
+	  && aie->end() == NULL
+	  && !aie->array()->type()->is_slice_type())
+	{
+	  lhs = aie->array();
+	  continue;
+	}
+
+      break;
     }
 
   // Nothing to do for an assignment to a temporary.
@@ -620,9 +762,7 @@ Gogo::assign_with_write_barrier(Function
 				Statement_inserter* inserter, Expression* lhs,
 				Expression* rhs, Location loc)
 {
-  if (function != NULL
-      && ((function->pragmas() & GOPRAGMA_NOWRITEBARRIER) != 0
-	  || (function->pragmas() & GOPRAGMA_NOWRITEBARRIERREC) != 0))
+  if (function != NULL && (function->pragmas() & GOPRAGMA_NOWRITEBARRIER) != 0)
     go_error_at(loc, "write barrier prohibited");
 
   Type* type = lhs->type();


More information about the Gcc-patches mailing list