compiler: add go:notinheap magic comment
authorIan Lance Taylor <ian@gcc.gnu.org>
Fri, 23 Jun 2017 16:03:49 +0000 (16:03 +0000)
committerIan Lance Taylor <ian@gcc.gnu.org>
Fri, 23 Jun 2017 16:03:49 +0000 (16:03 +0000)
    Implement go:notinheap as the gc compiler does. A type marked as
    go:notinheap may not live in the heap, and does not require a write
    barrier. Struct and array types that incorporate notinheap types are
    themselves notinheap. Allocating a value of a notinheap type on the
    heap is an error.

    This is not just an optimization. There is code where a write barrier
    may not occur that was getting a write barrier with gccgo but not gc,
    because the types in question were notinheap. The case I found was
    setting the mcache field in exitsyscallfast.

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

From-SVN: r249594

gcc/go/gofrontend/MERGE
gcc/go/gofrontend/expressions.cc
gcc/go/gofrontend/expressions.h
gcc/go/gofrontend/lex.cc
gcc/go/gofrontend/lex.h
gcc/go/gofrontend/parse.cc
gcc/go/gofrontend/parse.h
gcc/go/gofrontend/types.cc
gcc/go/gofrontend/types.h
gcc/go/gofrontend/wb.cc

index d82e012767cd7adec0c665929794645c0d6e07f4..02be610b167085c0a154e6058286857c243aa462 100644 (file)
@@ -1,4 +1,4 @@
-c4adba240f9d5af8ab0534316d6b05bd988c432c
+29c61dc3c5151df5de9362b7882ccf04679df976
 
 The first line of this file holds the git revision number of the last
 merge done from the gofrontend repository.
index 5eec731733fbd0dce2a97d8c7f82d27cacbc3fea..12505b239392b87bd113e355513a5646e6835f7b 100644 (file)
@@ -7499,6 +7499,10 @@ Builtin_call_expression::lower_make(Statement_inserter* inserter)
     }
   Type* type = first_arg->type();
 
+  if (!type->in_heap())
+    go_error_at(first_arg->location(),
+               "can't make slice of go:notinheap type");
+
   bool is_slice = false;
   bool is_map = false;
   bool is_chan = false;
@@ -8742,6 +8746,9 @@ Builtin_call_expression::do_check_types(Gogo*)
          }
 
        Type* element_type = slice_type->array_type()->element_type();
+       if (!element_type->in_heap())
+         go_error_at(args->front()->location(),
+                     "can't append to slice of go:notinheap type");
        if (this->is_varargs())
          {
            if (!args->back()->type()->is_slice_type()
@@ -12436,6 +12443,13 @@ Allocation_expression::do_type()
   return Type::make_pointer_type(this->type_);
 }
 
+void
+Allocation_expression::do_check_types(Gogo*)
+{
+  if (!this->type_->in_heap())
+    go_error_at(this->location(), "can't heap allocate go:notinheap type");
+}
+
 // Make a copy of an allocation expression.
 
 Expression*
index 43fb854e8d1bafcc688efe730eca50ccb35c7d8c..a144ff4168ba22375dc3fc9142547cad2467a630 100644 (file)
@@ -3220,6 +3220,9 @@ class Allocation_expression : public Expression
   do_determine_type(const Type_context*)
   { }
 
+  void
+  do_check_types(Gogo*);
+
   Expression*
   do_copy();
 
index beb365297fe152c409422f4aa8ef3c1e0eb24332..e9f11c2c1c0c5ab1ba93f497927712a92661d6a8 100644 (file)
@@ -1897,6 +1897,11 @@ Lex::skip_cpp_comment()
       // Applies to the next function.  Do not inline the function.
       this->pragmas_ |= GOPRAGMA_NOINLINE;
     }
+  else if (verb == "go:notinheap")
+    {
+      // Applies to the next type.  The type does not live in the heap.
+      this->pragmas_ |= GOPRAGMA_NOTINHEAP;
+    }
   else if (verb == "go:systemstack")
     {
       // Applies to the next function.  It must run on the system stack.
index 0a7a842ba8836badbbd9e3f20fe6bbea8bab8cb3..a8b7091b584b7cf90247cba336cea6eb7c56fd3e 100644 (file)
@@ -64,7 +64,8 @@ enum GoPragma
   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_UINTPTRESCAPES = 1 << 9,    // uintptr(p) escapes.
+  GOPRAGMA_NOTINHEAP = 1 << 10         // type is not in heap.
 };
 
 // A token returned from the lexer.
index 84840fb79c17cf986d2d0583797bf8df86f19279..28b1772f084f044ea477a69e99296fe7f3e4d827 100644 (file)
@@ -1310,14 +1310,16 @@ Parse::declaration()
   const Token* token = this->peek_token();
 
   unsigned int pragmas = this->lex_->get_and_clear_pragmas();
-  if (pragmas != 0 && !token->is_keyword(KEYWORD_FUNC))
+  if (pragmas != 0
+      && !token->is_keyword(KEYWORD_FUNC)
+      && !token->is_keyword(KEYWORD_TYPE))
     go_warning_at(token->location(), 0,
                  "ignoring magic comment before non-function");
 
   if (token->is_keyword(KEYWORD_CONST))
     this->const_decl();
   else if (token->is_keyword(KEYWORD_TYPE))
-    this->type_decl();
+    this->type_decl(pragmas);
   else if (token->is_keyword(KEYWORD_VAR))
     this->var_decl();
   else if (token->is_keyword(KEYWORD_FUNC))
@@ -1342,7 +1344,8 @@ Parse::declaration_may_start_here()
 // Decl<P> = P | "(" [ List<P> ] ")" .
 
 void
-Parse::decl(void (Parse::*pfn)(void*), void* varg)
+Parse::decl(void (Parse::*pfn)(void*, unsigned int), void* varg,
+           unsigned int pragmas)
 {
   if (this->peek_token()->is_eof())
     {
@@ -1352,9 +1355,12 @@ Parse::decl(void (Parse::*pfn)(void*), void* varg)
     }
 
   if (!this->peek_token()->is_op(OPERATOR_LPAREN))
-    (this->*pfn)(varg);
+    (this->*pfn)(varg, pragmas);
   else
     {
+      if (pragmas != 0)
+       go_warning_at(this->location(), 0,
+                     "ignoring magic //go:... comment before group");
       if (!this->advance_token()->is_op(OPERATOR_RPAREN))
        {
          this->list(pfn, varg, true);
@@ -1378,9 +1384,10 @@ Parse::decl(void (Parse::*pfn)(void*), void* varg)
 // might follow.  This is either a '}' or a ')'.
 
 void
-Parse::list(void (Parse::*pfn)(void*), void* varg, bool follow_is_paren)
+Parse::list(void (Parse::*pfn)(void*, unsigned int), void* varg,
+           bool follow_is_paren)
 {
-  (this->*pfn)(varg);
+  (this->*pfn)(varg, 0);
   Operator follow = follow_is_paren ? OPERATOR_RPAREN : OPERATOR_RCURLY;
   while (this->peek_token()->is_op(OPERATOR_SEMICOLON)
         || this->peek_token()->is_op(OPERATOR_COMMA))
@@ -1389,7 +1396,7 @@ Parse::list(void (Parse::*pfn)(void*), void* varg, bool follow_is_paren)
        go_error_at(this->location(), "unexpected comma");
       if (this->advance_token()->is_op(follow))
        break;
-      (this->*pfn)(varg);
+      (this->*pfn)(varg, 0);
     }
 }
 
@@ -1508,17 +1515,17 @@ Parse::const_spec(Type** last_type, Expression_list** last_expr_list)
 // TypeDecl = "type" Decl<TypeSpec> .
 
 void
-Parse::type_decl()
+Parse::type_decl(unsigned int pragmas)
 {
   go_assert(this->peek_token()->is_keyword(KEYWORD_TYPE));
   this->advance_token();
-  this->decl(&Parse::type_spec, NULL);
+  this->decl(&Parse::type_spec, NULL, pragmas);
 }
 
 // TypeSpec = identifier ["="] Type .
 
 void
-Parse::type_spec(void*)
+Parse::type_spec(void*, unsigned int pragmas)
 {
   const Token* token = this->peek_token();
   if (!token->is_identifier())
@@ -1592,6 +1599,15 @@ Parse::type_spec(void*)
 
          this->gogo_->define_type(named_type, nt);
          go_assert(named_type->package() == NULL);
+
+         if ((pragmas & GOPRAGMA_NOTINHEAP) != 0)
+           {
+             nt->set_not_in_heap();
+             pragmas &= ~GOPRAGMA_NOTINHEAP;
+           }
+         if (pragmas != 0)
+           go_warning_at(location, 0,
+                         "ignoring magic //go:... comment before type");
        }
       else
        {
@@ -1608,15 +1624,19 @@ Parse::var_decl()
 {
   go_assert(this->peek_token()->is_keyword(KEYWORD_VAR));
   this->advance_token();
-  this->decl(&Parse::var_spec, NULL);
+  this->decl(&Parse::var_spec, NULL, 0);
 }
 
 // VarSpec = IdentifierList
 //             ( CompleteType [ "=" ExpressionList ] | "=" ExpressionList ) .
 
 void
-Parse::var_spec(void*)
+Parse::var_spec(void*, unsigned int pragmas)
 {
+  if (pragmas != 0)
+    go_warning_at(this->location(), 0,
+                 "ignoring magic //go:... comment before var");
+
   // Get the variable names.
   Typed_identifier_list til;
   this->identifier_list(&til);
@@ -5698,14 +5718,18 @@ Parse::import_decl()
 {
   go_assert(this->peek_token()->is_keyword(KEYWORD_IMPORT));
   this->advance_token();
-  this->decl(&Parse::import_spec, NULL);
+  this->decl(&Parse::import_spec, NULL, 0);
 }
 
 // ImportSpec = [ "." | PackageName ] PackageFileName .
 
 void
-Parse::import_spec(void*)
+Parse::import_spec(void*, unsigned int pragmas)
 {
+  if (pragmas != 0)
+    go_warning_at(this->location(), 0,
+                 "ignoring magic //go:... comment before import");
+
   const Token* token = this->peek_token();
   Location location = token->location();
 
index e13dcc97545259b77db0419ea9d5037c9b5367bb..e416072830d780b3efebd895c228e9d3c3f205a8 100644 (file)
@@ -182,14 +182,14 @@ class Parse
   void method_spec(Typed_identifier_list*);
   void declaration();
   bool declaration_may_start_here();
-  void decl(void (Parse::*)(void*), void*);
-  void list(void (Parse::*)(void*), void*, bool);
+  void decl(void (Parse::*)(void*, unsigned int), void*, unsigned int pragmas);
+  void list(void (Parse::*)(void*, unsigned int), void*, bool);
   void const_decl();
   void const_spec(Type**, Expression_list**);
-  void type_decl();
-  void type_spec(void*);
+  void type_decl(unsigned int pragmas);
+  void type_spec(void*, unsigned int pragmas);
   void var_decl();
-  void var_spec(void*);
+  void var_spec(void*, unsigned int pragmas);
   void init_vars(const Typed_identifier_list*, Type*, Expression_list*,
                 bool is_coloneq, Location);
   bool init_vars_from_call(const Typed_identifier_list*, Type*, Expression*,
@@ -278,7 +278,7 @@ class Parse
   void goto_stat();
   void package_clause();
   void import_decl();
-  void import_spec(void*);
+  void import_spec(void*, unsigned int pragmas);
 
   void reset_iota();
   int iota_value();
index b9ad41e964414a585b36c5e9aa41acc2d5ac5286..b2756fde0482e0ee018dae8b1129851d8ffd11e2 100644 (file)
@@ -746,6 +746,20 @@ Type::are_convertible(const Type* lhs, const Type* rhs, std::string* reason)
   if (Type::are_assignable(lhs, rhs, reason))
     return true;
 
+  // A pointer to a regular type may not be converted to a pointer to
+  // a type that may not live in the heap, except when converting to
+  // unsafe.Pointer.
+  if (lhs->points_to() != NULL
+      && rhs->points_to() != NULL
+      && !rhs->points_to()->in_heap()
+      && lhs->points_to()->in_heap()
+      && !lhs->is_unsafe_pointer_type())
+    {
+      if (reason != NULL)
+       reason->assign(_("conversion from notinheap type to normal type"));
+      return false;
+    }
+
   // The types are convertible if they have identical underlying
   // types, ignoring struct field tags.
   if ((lhs->named_type() != NULL || rhs->named_type() != NULL)
@@ -5955,6 +5969,24 @@ Struct_type::do_needs_key_update()
   return false;
 }
 
+// Return whether this struct type is permitted to be in the heap.
+
+bool
+Struct_type::do_in_heap()
+{
+  const Struct_field_list* fields = this->fields_;
+  if (fields == NULL)
+    return true;
+  for (Struct_field_list::const_iterator pf = fields->begin();
+       pf != fields->end();
+       ++pf)
+    {
+      if (!pf->type()->in_heap())
+       return false;
+    }
+  return true;
+}
+
 // Build identity and hash functions for this struct.
 
 // Hash code.
@@ -8026,6 +8058,10 @@ Map_type::do_verify()
   // The runtime support uses "map[void]void".
   if (!this->key_type_->is_comparable() && !this->key_type_->is_void_type())
     go_error_at(this->location_, "invalid map key type");
+  if (!this->key_type_->in_heap())
+    go_error_at(this->location_, "go:notinheap map key not allowed");
+  if (!this->val_type_->in_heap())
+    go_error_at(this->location_, "go:notinheap map value not allowed");
   return true;
 }
 
@@ -8540,6 +8576,19 @@ Type::make_map_type(Type* key_type, Type* val_type, Location location)
 
 // Class Channel_type.
 
+// Verify.
+
+bool
+Channel_type::do_verify()
+{
+  // We have no location for this error, but this is not something the
+  // ordinary user will see.
+  if (!this->element_type_->in_heap())
+    go_error_at(Linemap::unknown_location(),
+               "chan of go:notinheap type not allowed");
+  return true;
+}
+
 // Hash code.
 
 unsigned int
index 3f6240b9fd25b9e9bf615b6ea761f7e8f53f6045..aeb04d6c50f56cc77689481993824c9f070b982b 100644 (file)
@@ -636,6 +636,11 @@ class Type
   needs_key_update()
   { return this->do_needs_key_update(); }
 
+  // Whether the type is permitted in the heap.
+  bool
+  in_heap()
+  { return this->do_in_heap(); }
+
   // Return a hash code for this type for the method hash table.
   // Types which are equivalent according to are_identical will have
   // the same hash code.
@@ -1051,6 +1056,10 @@ class Type
   do_needs_key_update()
   { return false; }
 
+  virtual bool
+  do_in_heap()
+  { return true; }
+
   virtual unsigned int
   do_hash_for_method(Gogo*) const;
 
@@ -1343,6 +1352,8 @@ class Type
   // The GC symbol for this type.  This starts out as NULL and
   // is filled in as needed.
   Bvariable* gc_symbol_var_;
+  // Whether this type can appear in the heap.
+  bool in_heap_;
 };
 
 // Type hash table operations.
@@ -2417,6 +2428,9 @@ class Struct_type : public Type
   bool
   do_needs_key_update();
 
+  bool
+  do_in_heap();
+
   unsigned int
   do_hash_for_method(Gogo*) const;
 
@@ -2590,6 +2604,10 @@ class Array_type : public Type
   do_needs_key_update()
   { return this->element_type_->needs_key_update(); }
 
+  bool
+  do_in_heap()
+  { return this->length_ == NULL || this->element_type_->in_heap(); }
+
   unsigned int
   do_hash_for_method(Gogo*) const;
 
@@ -2810,6 +2828,9 @@ class Channel_type : public Type
   do_traverse(Traverse* traverse)
   { return Type::traverse(this->element_type_, traverse); }
 
+  bool
+  do_verify();
+
   bool
   do_has_pointer() const
   { return true; }
@@ -3047,7 +3068,7 @@ class Named_type : public Type
       type_(type), local_methods_(NULL), all_methods_(NULL),
       interface_method_tables_(NULL), pointer_interface_method_tables_(NULL),
       location_(location), named_btype_(NULL), dependencies_(),
-      is_alias_(false), is_visible_(true), is_error_(false),
+      is_alias_(false), is_visible_(true), is_error_(false), in_heap_(true),
       is_placeholder_(false), is_converted_(false), is_circular_(false),
       is_verified_(false), seen_(false), seen_in_compare_is_identity_(false),
       seen_in_get_backend_(false), seen_alias_(false)
@@ -3079,6 +3100,11 @@ class Named_type : public Type
   set_is_alias()
   { this->is_alias_ = true; }
 
+  // Mark this type as not permitted in the heap.
+  void
+  set_not_in_heap()
+  { this->in_heap_ = false; }
+
   // Return the function in which this type is defined.  This will
   // return NULL for a type defined in global scope.
   const Named_object*
@@ -3277,6 +3303,10 @@ class Named_type : public Type
   bool
   do_needs_key_update();
 
+  bool
+  do_in_heap()
+  { return this->in_heap_ && this->type_->in_heap(); }
+
   unsigned int
   do_hash_for_method(Gogo*) const;
 
@@ -3344,6 +3374,9 @@ class Named_type : public Type
   bool is_visible_;
   // Whether this type is erroneous.
   bool is_error_;
+  // Whether this type is permitted in the heap.  This is true by
+  // default, false if there is a magic //go:notinheap comment.
+  bool in_heap_;
   // Whether the current value of named_btype_ is a placeholder for
   // which the final size of the type is not known.
   bool is_placeholder_;
@@ -3436,6 +3469,10 @@ class Forward_declaration_type : public Type
   do_needs_key_update()
   { return this->real_type()->needs_key_update(); }
 
+  bool
+  do_in_heap()
+  { return this->real_type()->in_heap(); }
+
   unsigned int
   do_hash_for_method(Gogo* gogo) const
   { return this->real_type()->hash_for_method(gogo); }
index 5a49961aba8cf1bfaafa4e1f59509df440e2b061..cbefc11c816bbb8f9dd11f79714bcb3b6e150d97 100644 (file)
@@ -156,6 +156,13 @@ Write_barriers::variable(Named_object* no)
   if (!var->has_pre_init() && init->is_static_initializer())
     return TRAVERSE_CONTINUE;
 
+  // Nothing to do for a type that can not be in the heap, or a
+  // pointer to a type that can not be in the heap.
+  if (!var->type()->in_heap())
+    return TRAVERSE_CONTINUE;
+  if (var->type()->points_to() != NULL && !var->type()->points_to()->in_heap())
+    return TRAVERSE_CONTINUE;
+
   // Otherwise change the initializer into a pre_init assignment
   // statement with a write barrier.
 
@@ -215,6 +222,14 @@ Write_barriers::statement(Block* block, size_t* pindex, Statement* s)
        if (!var->type()->has_pointer())
          break;
 
+       // Nothing to do for a type that can not be in the heap, or a
+       // pointer to a type that can not be in the heap.
+       if (!var->type()->in_heap())
+         break;
+       if (var->type()->points_to() != NULL
+           && !var->type()->points_to()->in_heap())
+         break;
+
        // Otherwise initialize the variable with a write barrier.
 
        Function* function = this->function_;
@@ -345,6 +360,13 @@ Gogo::assign_needs_write_barrier(Expression* lhs)
        }
     }
 
+  // Nothing to do for a type that can not be in the heap, or a
+  // pointer to a type that can not be in the heap.
+  if (!lhs->type()->in_heap())
+    return false;
+  if (lhs->type()->points_to() != NULL && !lhs->type()->points_to()->in_heap())
+    return false;
+
   // Write barrier needed in other cases.
   return true;
 }