compiler: add go:notinheap magic comment
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
This commit is contained in:
parent
ef2361cbd8
commit
5f0b897b2e
10 changed files with 178 additions and 23 deletions
|
@ -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.
|
||||
|
|
|
@ -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*
|
||||
|
|
|
@ -3220,6 +3220,9 @@ class Allocation_expression : public Expression
|
|||
do_determine_type(const Type_context*)
|
||||
{ }
|
||||
|
||||
void
|
||||
do_check_types(Gogo*);
|
||||
|
||||
Expression*
|
||||
do_copy();
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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); }
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue