compiler: omit a couple of write barriers
authorIan Lance Taylor <ian@gcc.gnu.org>
Thu, 13 Sep 2018 00:45:55 +0000 (00:45 +0000)
committerIan Lance Taylor <ian@gcc.gnu.org>
Thu, 13 Sep 2018 00:45:55 +0000 (00:45 +0000)
    Omit a write barrier for
        s = s[0:]
    for a slice s.  In this case the pointer is not changing and no write
    barrier is required.

    Omit a write barrier for
        s = append(s, v)
    in the case where len(s) < cap(s) (and similarly when appending more
    values).  When the slice has enough capacity the pointer is not
    changing and no write barrier is required.

    These changes are required to avoid write barriers in the method
    randomOrder.reset in the runtime package.  That method is called from
    procresize, at a point where we do not want to allocate memory.
    Otherwise that method can use a write barrier, allocate memory, and
    break TestReadMemStats.

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

From-SVN: r264259

gcc/go/gofrontend/MERGE
gcc/go/gofrontend/expressions.cc
gcc/go/gofrontend/expressions.h
gcc/go/gofrontend/statements.cc
gcc/go/gofrontend/statements.h
gcc/go/gofrontend/wb.cc

index 1ef2c8d39d3a11bc52879856116f3fae01a175a4..1afbcb48b34a5c9904d2282a1284184dcfff4bbc 100644 (file)
@@ -1,4 +1,4 @@
-06e688ff6d829c8de3735e9f59b61b373afc596f
+acf852f838e6b99f907d84648be853fa2c374393
 
 The first line of this file holds the git revision number of the last
 merge done from the gofrontend repository.
index efc8eba99082f376278d8d8f7857121757035f5a..a296777bd543bf5e22fd2de8ecd24a5bc6cbdf93 100644 (file)
@@ -131,6 +131,40 @@ Expression::determine_type_no_context()
   this->do_determine_type(&context);
 }
 
+// Return true if two expressions refer to the same variable or struct
+// field.  This can only be true when there are no side effects.
+
+bool
+Expression::is_same_variable(Expression* a, Expression* b)
+{
+  if (a->classification() != b->classification())
+    return false;
+
+  Var_expression* av = a->var_expression();
+  if (av != NULL)
+    return av->named_object() == b->var_expression()->named_object();
+
+  Field_reference_expression* af = a->field_reference_expression();
+  if (af != NULL)
+    {
+      Field_reference_expression* bf = b->field_reference_expression();
+      return (af->field_index() == bf->field_index()
+             && Expression::is_same_variable(af->expr(), bf->expr()));
+    }
+
+  Unary_expression* au = a->unary_expression();
+  if (au != NULL)
+    {
+      Unary_expression* bu = b->unary_expression();
+      return (au->op() == OPERATOR_MULT
+             && bu->op() == OPERATOR_MULT
+             && Expression::is_same_variable(au->operand(),
+                                             bu->operand()));
+    }
+
+  return false;
+}
+
 // Return an expression handling any conversions which must be done during
 // assignment.
 
@@ -7421,7 +7455,7 @@ Builtin_call_expression::do_flatten(Gogo* gogo, Named_object* function,
       break;
 
     case BUILTIN_APPEND:
-      return this->flatten_append(gogo, function, inserter);
+      return this->flatten_append(gogo, function, inserter, NULL, NULL);
 
     case BUILTIN_COPY:
       {
@@ -7658,11 +7692,18 @@ Builtin_call_expression::lower_make(Statement_inserter* inserter)
 
 // Flatten a call to the predeclared append function.  We do this in
 // the flatten phase, not the lowering phase, so that we run after
-// type checking and after order_evaluations.
+// type checking and after order_evaluations.  If ASSIGN_LHS is not
+// NULL, this append is the right-hand-side of an assignment and
+// ASSIGN_LHS is the left-hand-side; in that case, set LHS directly
+// rather than returning a slice.  This lets us omit a write barrier
+// in common cases like a = append(a, ...) when the slice does not
+// need to grow.  ENCLOSING is not NULL iff ASSIGN_LHS is not NULL.
 
 Expression*
 Builtin_call_expression::flatten_append(Gogo* gogo, Named_object* function,
-                                       Statement_inserter* inserter)
+                                       Statement_inserter* inserter,
+                                       Expression* assign_lhs,
+                                       Block* enclosing)
 {
   if (this->is_error_expression())
     return this;
@@ -7679,6 +7720,8 @@ Builtin_call_expression::flatten_append(Gogo* gogo, Named_object* function,
   if (args->size() == 1)
     {
       // append(s) evaluates to s.
+      if (assign_lhs != NULL)
+       return NULL;
       return args->front();
     }
 
@@ -7795,14 +7838,46 @@ Builtin_call_expression::flatten_append(Gogo* gogo, Named_object* function,
   // FIXME: Mark this index as not requiring bounds checks.
   ref = Expression::make_index(ref, zero, ref2, NULL, loc);
 
-  Expression* rhs = Expression::make_conditional(cond, call, ref, loc);
+  if (assign_lhs == NULL)
+    {
+      Expression* rhs = Expression::make_conditional(cond, call, ref, loc);
+
+      gogo->lower_expression(function, inserter, &rhs);
+      gogo->flatten_expression(function, inserter, &rhs);
 
-  gogo->lower_expression(function, inserter, &rhs);
-  gogo->flatten_expression(function, inserter, &rhs);
+      ref = Expression::make_temporary_reference(s1tmp, loc);
+      Statement* assign = Statement::make_assignment(ref, rhs, loc);
+      inserter->insert(assign);
+    }
+  else
+    {
+      gogo->lower_expression(function, inserter, &cond);
+      gogo->flatten_expression(function, inserter, &cond);
+      gogo->lower_expression(function, inserter, &call);
+      gogo->flatten_expression(function, inserter, &call);
+      gogo->lower_expression(function, inserter, &ref);
+      gogo->flatten_expression(function, inserter, &ref);
 
-  Expression* lhs = Expression::make_temporary_reference(s1tmp, loc);
-  Statement* assign = Statement::make_assignment(lhs, rhs, loc);
-  inserter->insert(assign);
+      Block* then_block = new Block(enclosing, loc);
+      Assignment_statement* assign =
+       Statement::make_assignment(assign_lhs, call, loc);
+      then_block->add_statement(assign);
+
+      Block* else_block = new Block(enclosing, loc);
+      assign = Statement::make_assignment(assign_lhs->copy(), ref, loc);
+      // This assignment will not change the pointer value, so it does
+      // not need a write barrier.
+      assign->set_omit_write_barrier();
+      else_block->add_statement(assign);
+
+      Statement* s = Statement::make_if_statement(cond, then_block,
+                                                 else_block, loc);
+      inserter->insert(s);
+
+      ref = Expression::make_temporary_reference(s1tmp, loc);
+      assign = Statement::make_assignment(ref, assign_lhs->copy(), loc);
+      inserter->insert(assign);
+    }
 
   if (this->is_varargs())
     {
@@ -7839,12 +7914,17 @@ Builtin_call_expression::flatten_append(Gogo* gogo, Named_object* function,
          Expression* off = Expression::make_integer_ul(i, int_type, loc);
          ref2 = Expression::make_binary(OPERATOR_PLUS, ref2, off, loc);
          // FIXME: Mark this index as not requiring bounds checks.
-         lhs = Expression::make_index(ref, ref2, NULL, NULL, loc);
+         Expression* lhs = Expression::make_index(ref, ref2, NULL, NULL,
+                                                  loc);
          gogo->lower_expression(function, inserter, &lhs);
          gogo->flatten_expression(function, inserter, &lhs);
          // The flatten pass runs after the write barrier pass, so we
          // need to insert a write barrier here if necessary.
-         if (!gogo->assign_needs_write_barrier(lhs))
+         // However, if ASSIGN_LHS is not NULL, we have been called
+         // directly before the write barrier pass.
+         Statement* assign;
+         if (assign_lhs != NULL
+             || !gogo->assign_needs_write_barrier(lhs))
            assign = Statement::make_assignment(lhs, *pa, loc);
          else
            {
@@ -7856,6 +7936,9 @@ Builtin_call_expression::flatten_append(Gogo* gogo, Named_object* function,
        }
     }
 
+  if (assign_lhs != NULL)
+    return NULL;
+
   return Expression::make_temporary_reference(s1tmp, loc);
 }
 
index 5fa417159462b7d0e97c54576051a1ce36c03838..f53cc6e62aa45ae2ffc996caa42622f3f0241f1c 100644 (file)
@@ -869,6 +869,11 @@ class Expression
   bool
   is_local_variable() const;
 
+  // Return true if two expressions refer to the same variable or
+  // struct field.
+  static bool
+  is_same_variable(Expression*, Expression*);
+
   // Make the builtin function descriptor type, so that it can be
   // converted.
   static void
@@ -2402,6 +2407,10 @@ class Builtin_call_expression : public Call_expression
   static bool
   array_len_is_constant(Expression* expr);
 
+  Expression*
+  flatten_append(Gogo*, Named_object*, Statement_inserter*, Expression*,
+                Block*);
+
  protected:
   // This overrides Call_expression::do_lower.
   Expression*
@@ -2459,8 +2468,6 @@ class Builtin_call_expression : public Call_expression
   Expression*
   lower_make(Statement_inserter*);
 
-  Expression* flatten_append(Gogo*, Named_object*, Statement_inserter*);
-
   bool
   check_int_value(Expression*, bool is_length, bool* small);
 
index c94d8cf369e3080371fe8596435d16d06f1cdc14..19a07d462baeb19dda3afde9564734f88b587f02 100644 (file)
@@ -686,7 +686,7 @@ Assignment_statement::do_traverse_assignments(Traverse_assignments* tassign)
 }
 
 // Lower an assignment to a map index expression to a runtime function
-// call.
+// call.  Mark some slice assignments as not requiring a write barrier.
 
 Statement*
 Assignment_statement::do_lower(Gogo*, Named_object*, Block* enclosing,
@@ -750,6 +750,21 @@ Assignment_statement::do_lower(Gogo*, Named_object*, Block* enclosing,
       return Statement::make_block_statement(b, loc);
     }
 
+  // An assignment of the form s = s[:n] does not require a write
+  // barrier, because the pointer value will not change.
+  Array_index_expression* aie = this->rhs_->array_index_expression();
+  if (aie != NULL
+      && aie->end() != NULL
+      && Expression::is_same_variable(this->lhs_, aie->array()))
+    {
+      Numeric_constant nc;
+      unsigned long ival;
+      if (aie->start()->numeric_constant_value(&nc)
+         && nc.to_unsigned_long(&ival) == Numeric_constant::NC_UL_VALID
+         && ival == 0)
+       this->omit_write_barrier_ = true;
+    }
+
   return this;
 }
 
@@ -876,7 +891,7 @@ Assignment_statement::do_dump_statement(Ast_dump_context* ast_dump_context)
 
 // Make an assignment statement.
 
-Statement*
+Assignment_statement*
 Statement::make_assignment(Expression* lhs, Expression* rhs,
                           Location location)
 {
index 852ab43ce2a2e5eab6285efb548bc2995fcfc694..c9ab4d6fac338d2ef3f66f51dbbc52a170a99c0d 100644 (file)
@@ -148,7 +148,7 @@ class Statement
   make_temporary(Type*, Expression*, Location);
 
   // Make an assignment statement.
-  static Statement*
+  static Assignment_statement*
   make_assignment(Expression*, Expression*, Location);
 
   // Make an assignment operation (+=, etc.).
@@ -562,7 +562,7 @@ class Assignment_statement : public Statement
   Assignment_statement(Expression* lhs, Expression* rhs,
                       Location location)
     : Statement(STATEMENT_ASSIGNMENT, location),
-      lhs_(lhs), rhs_(rhs)
+      lhs_(lhs), rhs_(rhs), omit_write_barrier_(false)
   { }
 
   Expression*
@@ -573,6 +573,14 @@ class Assignment_statement : public Statement
   rhs() const
   { return this->rhs_; }
 
+  bool
+  omit_write_barrier() const
+  { return this->omit_write_barrier_; }
+
+  void
+  set_omit_write_barrier()
+  { this->omit_write_barrier_ = true; }
+
  protected:
   int
   do_traverse(Traverse* traverse);
@@ -603,6 +611,8 @@ class Assignment_statement : public Statement
   Expression* lhs_;
   // Right hand side--the rvalue.
   Expression* rhs_;
+  // True if we can omit a write barrier from this assignment.
+  bool omit_write_barrier_;
 };
 
 // A statement which creates and initializes a temporary variable.
index 99f467ef90d7a30d573ada789c836e3b3bb1853f..58524cdb3223bc067d7244b14a55aee1d9b1db62 100644 (file)
 #include "runtime.h"
 #include "gogo.h"
 
-// Mark variables whose addresses are taken.  This has to be done
-// before the write barrier pass and after the escape analysis pass.
-// It would be nice to do this elsewhere but there isn't an obvious
-// place.
+// Mark variables whose addresses are taken and do some other
+// cleanups.  This has to be done before the write barrier pass and
+// after the escape analysis pass.  It would be nice to do this
+// elsewhere but there isn't an obvious place.
 
 class Mark_address_taken : public Traverse
 {
  public:
   Mark_address_taken(Gogo* gogo)
-    : Traverse(traverse_expressions),
-      gogo_(gogo)
+    : Traverse(traverse_functions
+              | traverse_statements
+              | traverse_expressions),
+      gogo_(gogo), function_(NULL)
   { }
 
+  int
+  function(Named_object*);
+
+  int
+  statement(Block*, size_t*, Statement*);
+
   int
   expression(Expression**);
 
  private:
+  // General IR.
   Gogo* gogo_;
+  // The function we are traversing.
+  Named_object* function_;
 };
 
+// Record a function.
+
+int
+Mark_address_taken::function(Named_object* no)
+{
+  go_assert(this->function_ == NULL);
+  this->function_ = no;
+  int t = no->func_value()->traverse(this);
+  this->function_ = NULL;
+
+  if (t == TRAVERSE_EXIT)
+    return t;
+  return TRAVERSE_SKIP_COMPONENTS;
+}
+
+// Traverse a statement.
+
+int
+Mark_address_taken::statement(Block* block, size_t* pindex, Statement* s)
+{
+  // If this is an assignment of the form s = append(s, ...), expand
+  // it now, so that we can assign it to the left hand side in the
+  // middle of the expansion and possibly skip a write barrier.
+  Assignment_statement* as = s->assignment_statement();
+  if (as != NULL && !as->lhs()->is_sink_expression())
+    {
+      Call_expression* rce = as->rhs()->call_expression();
+      if (rce != NULL
+         && rce->builtin_call_expression() != NULL
+         && (rce->builtin_call_expression()->code()
+             == Builtin_call_expression::BUILTIN_APPEND)
+          && Expression::is_same_variable(as->lhs(), rce->args()->front()))
+       {
+         Statement_inserter inserter = Statement_inserter(block, pindex);
+         Expression* a =
+           rce->builtin_call_expression()->flatten_append(this->gogo_,
+                                                          this->function_,
+                                                          &inserter,
+                                                          as->lhs(),
+                                                          block);
+         go_assert(a == NULL);
+         // That does the assignment, so remove this statement.
+         Expression* e = Expression::make_boolean(true, s->location());
+         Statement* dummy = Statement::make_statement(e, true);
+         block->replace_statement(*pindex, dummy);
+       }
+    }
+  return TRAVERSE_CONTINUE;
+}
+
 // Mark variable addresses taken.
 
 int
@@ -387,6 +448,10 @@ Write_barriers::statement(Block* block, size_t* pindex, Statement* s)
     case Statement::STATEMENT_ASSIGNMENT:
       {
        Assignment_statement* as = s->assignment_statement();
+
+       if (as->omit_write_barrier())
+         break;
+
        Expression* lhs = as->lhs();
        Expression* rhs = as->rhs();