From 5f0b897b2ee47bf3845d924dd5bc3beb949bc78b Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Fri, 23 Jun 2017 16:03:49 +0000 Subject: [PATCH] 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 --- gcc/go/gofrontend/MERGE | 2 +- gcc/go/gofrontend/expressions.cc | 14 +++++++++ gcc/go/gofrontend/expressions.h | 3 ++ gcc/go/gofrontend/lex.cc | 5 +++ gcc/go/gofrontend/lex.h | 3 +- gcc/go/gofrontend/parse.cc | 52 +++++++++++++++++++++++--------- gcc/go/gofrontend/parse.h | 12 ++++---- gcc/go/gofrontend/types.cc | 49 ++++++++++++++++++++++++++++++ gcc/go/gofrontend/types.h | 39 +++++++++++++++++++++++- gcc/go/gofrontend/wb.cc | 22 ++++++++++++++ 10 files changed, 178 insertions(+), 23 deletions(-) diff --git a/gcc/go/gofrontend/MERGE b/gcc/go/gofrontend/MERGE index d82e012767c..02be610b167 100644 --- a/gcc/go/gofrontend/MERGE +++ b/gcc/go/gofrontend/MERGE @@ -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. diff --git a/gcc/go/gofrontend/expressions.cc b/gcc/go/gofrontend/expressions.cc index 5eec731733f..12505b23939 100644 --- a/gcc/go/gofrontend/expressions.cc +++ b/gcc/go/gofrontend/expressions.cc @@ -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* diff --git a/gcc/go/gofrontend/expressions.h b/gcc/go/gofrontend/expressions.h index 43fb854e8d1..a144ff4168b 100644 --- a/gcc/go/gofrontend/expressions.h +++ b/gcc/go/gofrontend/expressions.h @@ -3220,6 +3220,9 @@ class Allocation_expression : public Expression do_determine_type(const Type_context*) { } + void + do_check_types(Gogo*); + Expression* do_copy(); diff --git a/gcc/go/gofrontend/lex.cc b/gcc/go/gofrontend/lex.cc index beb365297fe..e9f11c2c1c0 100644 --- a/gcc/go/gofrontend/lex.cc +++ b/gcc/go/gofrontend/lex.cc @@ -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. diff --git a/gcc/go/gofrontend/lex.h b/gcc/go/gofrontend/lex.h index 0a7a842ba88..a8b7091b584 100644 --- a/gcc/go/gofrontend/lex.h +++ b/gcc/go/gofrontend/lex.h @@ -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. diff --git a/gcc/go/gofrontend/parse.cc b/gcc/go/gofrontend/parse.cc index 84840fb79c1..28b1772f084 100644 --- a/gcc/go/gofrontend/parse.cc +++ b/gcc/go/gofrontend/parse.cc @@ -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 | "(" [ List

] ")" . 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 . 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(); diff --git a/gcc/go/gofrontend/parse.h b/gcc/go/gofrontend/parse.h index e13dcc97545..e416072830d 100644 --- a/gcc/go/gofrontend/parse.h +++ b/gcc/go/gofrontend/parse.h @@ -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(); diff --git a/gcc/go/gofrontend/types.cc b/gcc/go/gofrontend/types.cc index b9ad41e9644..b2756fde048 100644 --- a/gcc/go/gofrontend/types.cc +++ b/gcc/go/gofrontend/types.cc @@ -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 diff --git a/gcc/go/gofrontend/types.h b/gcc/go/gofrontend/types.h index 3f6240b9fd2..aeb04d6c50f 100644 --- a/gcc/go/gofrontend/types.h +++ b/gcc/go/gofrontend/types.h @@ -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); } diff --git a/gcc/go/gofrontend/wb.cc b/gcc/go/gofrontend/wb.cc index 5a49961aba8..cbefc11c816 100644 --- a/gcc/go/gofrontend/wb.cc +++ b/gcc/go/gofrontend/wb.cc @@ -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; } -- 2.30.2