Commit 5f0b897b by Ian Lance Taylor

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
parent ef2361cb
c4adba240f9d5af8ab0534316d6b05bd988c432c 29c61dc3c5151df5de9362b7882ccf04679df976
The first line of this file holds the git revision number of the last The first line of this file holds the git revision number of the last
merge done from the gofrontend repository. merge done from the gofrontend repository.
...@@ -7499,6 +7499,10 @@ Builtin_call_expression::lower_make(Statement_inserter* inserter) ...@@ -7499,6 +7499,10 @@ Builtin_call_expression::lower_make(Statement_inserter* inserter)
} }
Type* type = first_arg->type(); 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_slice = false;
bool is_map = false; bool is_map = false;
bool is_chan = false; bool is_chan = false;
...@@ -8742,6 +8746,9 @@ Builtin_call_expression::do_check_types(Gogo*) ...@@ -8742,6 +8746,9 @@ Builtin_call_expression::do_check_types(Gogo*)
} }
Type* element_type = slice_type->array_type()->element_type(); 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 (this->is_varargs())
{ {
if (!args->back()->type()->is_slice_type() if (!args->back()->type()->is_slice_type()
...@@ -12436,6 +12443,13 @@ Allocation_expression::do_type() ...@@ -12436,6 +12443,13 @@ Allocation_expression::do_type()
return Type::make_pointer_type(this->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. // Make a copy of an allocation expression.
Expression* Expression*
......
...@@ -3220,6 +3220,9 @@ class Allocation_expression : public Expression ...@@ -3220,6 +3220,9 @@ class Allocation_expression : public Expression
do_determine_type(const Type_context*) do_determine_type(const Type_context*)
{ } { }
void
do_check_types(Gogo*);
Expression* Expression*
do_copy(); do_copy();
......
...@@ -1897,6 +1897,11 @@ Lex::skip_cpp_comment() ...@@ -1897,6 +1897,11 @@ Lex::skip_cpp_comment()
// Applies to the next function. Do not inline the function. // Applies to the next function. Do not inline the function.
this->pragmas_ |= GOPRAGMA_NOINLINE; 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") else if (verb == "go:systemstack")
{ {
// Applies to the next function. It must run on the system stack. // Applies to the next function. It must run on the system stack.
......
...@@ -64,7 +64,8 @@ enum GoPragma ...@@ -64,7 +64,8 @@ enum GoPragma
GOPRAGMA_NOWRITEBARRIER = 1 << 6, // No write barriers. GOPRAGMA_NOWRITEBARRIER = 1 << 6, // No write barriers.
GOPRAGMA_NOWRITEBARRIERREC = 1 << 7, // No write barriers here or callees. GOPRAGMA_NOWRITEBARRIERREC = 1 << 7, // No write barriers here or callees.
GOPRAGMA_CGOUNSAFEARGS = 1 << 8, // Pointer to arg is pointer to all. 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. // A token returned from the lexer.
......
...@@ -1310,14 +1310,16 @@ Parse::declaration() ...@@ -1310,14 +1310,16 @@ Parse::declaration()
const Token* token = this->peek_token(); const Token* token = this->peek_token();
unsigned int pragmas = this->lex_->get_and_clear_pragmas(); 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, go_warning_at(token->location(), 0,
"ignoring magic comment before non-function"); "ignoring magic comment before non-function");
if (token->is_keyword(KEYWORD_CONST)) if (token->is_keyword(KEYWORD_CONST))
this->const_decl(); this->const_decl();
else if (token->is_keyword(KEYWORD_TYPE)) else if (token->is_keyword(KEYWORD_TYPE))
this->type_decl(); this->type_decl(pragmas);
else if (token->is_keyword(KEYWORD_VAR)) else if (token->is_keyword(KEYWORD_VAR))
this->var_decl(); this->var_decl();
else if (token->is_keyword(KEYWORD_FUNC)) else if (token->is_keyword(KEYWORD_FUNC))
...@@ -1342,7 +1344,8 @@ Parse::declaration_may_start_here() ...@@ -1342,7 +1344,8 @@ Parse::declaration_may_start_here()
// Decl<P> = P | "(" [ List<P> ] ")" . // Decl<P> = P | "(" [ List<P> ] ")" .
void 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()) if (this->peek_token()->is_eof())
{ {
...@@ -1352,9 +1355,12 @@ Parse::decl(void (Parse::*pfn)(void*), void* varg) ...@@ -1352,9 +1355,12 @@ Parse::decl(void (Parse::*pfn)(void*), void* varg)
} }
if (!this->peek_token()->is_op(OPERATOR_LPAREN)) if (!this->peek_token()->is_op(OPERATOR_LPAREN))
(this->*pfn)(varg); (this->*pfn)(varg, pragmas);
else else
{ {
if (pragmas != 0)
go_warning_at(this->location(), 0,
"ignoring magic //go:... comment before group");
if (!this->advance_token()->is_op(OPERATOR_RPAREN)) if (!this->advance_token()->is_op(OPERATOR_RPAREN))
{ {
this->list(pfn, varg, true); this->list(pfn, varg, true);
...@@ -1378,9 +1384,10 @@ Parse::decl(void (Parse::*pfn)(void*), void* varg) ...@@ -1378,9 +1384,10 @@ Parse::decl(void (Parse::*pfn)(void*), void* varg)
// might follow. This is either a '}' or a ')'. // might follow. This is either a '}' or a ')'.
void 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; Operator follow = follow_is_paren ? OPERATOR_RPAREN : OPERATOR_RCURLY;
while (this->peek_token()->is_op(OPERATOR_SEMICOLON) while (this->peek_token()->is_op(OPERATOR_SEMICOLON)
|| this->peek_token()->is_op(OPERATOR_COMMA)) || this->peek_token()->is_op(OPERATOR_COMMA))
...@@ -1389,7 +1396,7 @@ Parse::list(void (Parse::*pfn)(void*), void* varg, bool follow_is_paren) ...@@ -1389,7 +1396,7 @@ Parse::list(void (Parse::*pfn)(void*), void* varg, bool follow_is_paren)
go_error_at(this->location(), "unexpected comma"); go_error_at(this->location(), "unexpected comma");
if (this->advance_token()->is_op(follow)) if (this->advance_token()->is_op(follow))
break; break;
(this->*pfn)(varg); (this->*pfn)(varg, 0);
} }
} }
...@@ -1508,17 +1515,17 @@ Parse::const_spec(Type** last_type, Expression_list** last_expr_list) ...@@ -1508,17 +1515,17 @@ Parse::const_spec(Type** last_type, Expression_list** last_expr_list)
// TypeDecl = "type" Decl<TypeSpec> . // TypeDecl = "type" Decl<TypeSpec> .
void void
Parse::type_decl() Parse::type_decl(unsigned int pragmas)
{ {
go_assert(this->peek_token()->is_keyword(KEYWORD_TYPE)); go_assert(this->peek_token()->is_keyword(KEYWORD_TYPE));
this->advance_token(); this->advance_token();
this->decl(&Parse::type_spec, NULL); this->decl(&Parse::type_spec, NULL, pragmas);
} }
// TypeSpec = identifier ["="] Type . // TypeSpec = identifier ["="] Type .
void void
Parse::type_spec(void*) Parse::type_spec(void*, unsigned int pragmas)
{ {
const Token* token = this->peek_token(); const Token* token = this->peek_token();
if (!token->is_identifier()) if (!token->is_identifier())
...@@ -1592,6 +1599,15 @@ Parse::type_spec(void*) ...@@ -1592,6 +1599,15 @@ Parse::type_spec(void*)
this->gogo_->define_type(named_type, nt); this->gogo_->define_type(named_type, nt);
go_assert(named_type->package() == NULL); 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 else
{ {
...@@ -1608,15 +1624,19 @@ Parse::var_decl() ...@@ -1608,15 +1624,19 @@ Parse::var_decl()
{ {
go_assert(this->peek_token()->is_keyword(KEYWORD_VAR)); go_assert(this->peek_token()->is_keyword(KEYWORD_VAR));
this->advance_token(); this->advance_token();
this->decl(&Parse::var_spec, NULL); this->decl(&Parse::var_spec, NULL, 0);
} }
// VarSpec = IdentifierList // VarSpec = IdentifierList
// ( CompleteType [ "=" ExpressionList ] | "=" ExpressionList ) . // ( CompleteType [ "=" ExpressionList ] | "=" ExpressionList ) .
void 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. // Get the variable names.
Typed_identifier_list til; Typed_identifier_list til;
this->identifier_list(&til); this->identifier_list(&til);
...@@ -5698,14 +5718,18 @@ Parse::import_decl() ...@@ -5698,14 +5718,18 @@ Parse::import_decl()
{ {
go_assert(this->peek_token()->is_keyword(KEYWORD_IMPORT)); go_assert(this->peek_token()->is_keyword(KEYWORD_IMPORT));
this->advance_token(); this->advance_token();
this->decl(&Parse::import_spec, NULL); this->decl(&Parse::import_spec, NULL, 0);
} }
// ImportSpec = [ "." | PackageName ] PackageFileName . // ImportSpec = [ "." | PackageName ] PackageFileName .
void 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(); const Token* token = this->peek_token();
Location location = token->location(); Location location = token->location();
......
...@@ -182,14 +182,14 @@ class Parse ...@@ -182,14 +182,14 @@ class Parse
void method_spec(Typed_identifier_list*); void method_spec(Typed_identifier_list*);
void declaration(); void declaration();
bool declaration_may_start_here(); bool declaration_may_start_here();
void decl(void (Parse::*)(void*), void*); void decl(void (Parse::*)(void*, unsigned int), void*, unsigned int pragmas);
void list(void (Parse::*)(void*), void*, bool); void list(void (Parse::*)(void*, unsigned int), void*, bool);
void const_decl(); void const_decl();
void const_spec(Type**, Expression_list**); void const_spec(Type**, Expression_list**);
void type_decl(); void type_decl(unsigned int pragmas);
void type_spec(void*); void type_spec(void*, unsigned int pragmas);
void var_decl(); void var_decl();
void var_spec(void*); void var_spec(void*, unsigned int pragmas);
void init_vars(const Typed_identifier_list*, Type*, Expression_list*, void init_vars(const Typed_identifier_list*, Type*, Expression_list*,
bool is_coloneq, Location); bool is_coloneq, Location);
bool init_vars_from_call(const Typed_identifier_list*, Type*, Expression*, bool init_vars_from_call(const Typed_identifier_list*, Type*, Expression*,
...@@ -278,7 +278,7 @@ class Parse ...@@ -278,7 +278,7 @@ class Parse
void goto_stat(); void goto_stat();
void package_clause(); void package_clause();
void import_decl(); void import_decl();
void import_spec(void*); void import_spec(void*, unsigned int pragmas);
void reset_iota(); void reset_iota();
int iota_value(); int iota_value();
......
...@@ -746,6 +746,20 @@ Type::are_convertible(const Type* lhs, const Type* rhs, std::string* reason) ...@@ -746,6 +746,20 @@ Type::are_convertible(const Type* lhs, const Type* rhs, std::string* reason)
if (Type::are_assignable(lhs, rhs, reason)) if (Type::are_assignable(lhs, rhs, reason))
return true; 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 // The types are convertible if they have identical underlying
// types, ignoring struct field tags. // types, ignoring struct field tags.
if ((lhs->named_type() != NULL || rhs->named_type() != NULL) if ((lhs->named_type() != NULL || rhs->named_type() != NULL)
...@@ -5955,6 +5969,24 @@ Struct_type::do_needs_key_update() ...@@ -5955,6 +5969,24 @@ Struct_type::do_needs_key_update()
return false; 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. // Build identity and hash functions for this struct.
// Hash code. // Hash code.
...@@ -8026,6 +8058,10 @@ Map_type::do_verify() ...@@ -8026,6 +8058,10 @@ Map_type::do_verify()
// The runtime support uses "map[void]void". // The runtime support uses "map[void]void".
if (!this->key_type_->is_comparable() && !this->key_type_->is_void_type()) if (!this->key_type_->is_comparable() && !this->key_type_->is_void_type())
go_error_at(this->location_, "invalid map key 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; return true;
} }
...@@ -8540,6 +8576,19 @@ Type::make_map_type(Type* key_type, Type* val_type, Location location) ...@@ -8540,6 +8576,19 @@ Type::make_map_type(Type* key_type, Type* val_type, Location location)
// Class Channel_type. // 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. // Hash code.
unsigned int unsigned int
......
...@@ -636,6 +636,11 @@ class Type ...@@ -636,6 +636,11 @@ class Type
needs_key_update() needs_key_update()
{ return this->do_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. // Return a hash code for this type for the method hash table.
// Types which are equivalent according to are_identical will have // Types which are equivalent according to are_identical will have
// the same hash code. // the same hash code.
...@@ -1051,6 +1056,10 @@ class Type ...@@ -1051,6 +1056,10 @@ class Type
do_needs_key_update() do_needs_key_update()
{ return false; } { return false; }
virtual bool
do_in_heap()
{ return true; }
virtual unsigned int virtual unsigned int
do_hash_for_method(Gogo*) const; do_hash_for_method(Gogo*) const;
...@@ -1343,6 +1352,8 @@ class Type ...@@ -1343,6 +1352,8 @@ class Type
// The GC symbol for this type. This starts out as NULL and // The GC symbol for this type. This starts out as NULL and
// is filled in as needed. // is filled in as needed.
Bvariable* gc_symbol_var_; Bvariable* gc_symbol_var_;
// Whether this type can appear in the heap.
bool in_heap_;
}; };
// Type hash table operations. // Type hash table operations.
...@@ -2417,6 +2428,9 @@ class Struct_type : public Type ...@@ -2417,6 +2428,9 @@ class Struct_type : public Type
bool bool
do_needs_key_update(); do_needs_key_update();
bool
do_in_heap();
unsigned int unsigned int
do_hash_for_method(Gogo*) const; do_hash_for_method(Gogo*) const;
...@@ -2590,6 +2604,10 @@ class Array_type : public Type ...@@ -2590,6 +2604,10 @@ class Array_type : public Type
do_needs_key_update() do_needs_key_update()
{ return this->element_type_->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 unsigned int
do_hash_for_method(Gogo*) const; do_hash_for_method(Gogo*) const;
...@@ -2811,6 +2829,9 @@ class Channel_type : public Type ...@@ -2811,6 +2829,9 @@ class Channel_type : public Type
{ return Type::traverse(this->element_type_, traverse); } { return Type::traverse(this->element_type_, traverse); }
bool bool
do_verify();
bool
do_has_pointer() const do_has_pointer() const
{ return true; } { return true; }
...@@ -3047,7 +3068,7 @@ class Named_type : public Type ...@@ -3047,7 +3068,7 @@ class Named_type : public Type
type_(type), local_methods_(NULL), all_methods_(NULL), type_(type), local_methods_(NULL), all_methods_(NULL),
interface_method_tables_(NULL), pointer_interface_method_tables_(NULL), interface_method_tables_(NULL), pointer_interface_method_tables_(NULL),
location_(location), named_btype_(NULL), dependencies_(), 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_placeholder_(false), is_converted_(false), is_circular_(false),
is_verified_(false), seen_(false), seen_in_compare_is_identity_(false), is_verified_(false), seen_(false), seen_in_compare_is_identity_(false),
seen_in_get_backend_(false), seen_alias_(false) seen_in_get_backend_(false), seen_alias_(false)
...@@ -3079,6 +3100,11 @@ class Named_type : public Type ...@@ -3079,6 +3100,11 @@ class Named_type : public Type
set_is_alias() set_is_alias()
{ this->is_alias_ = true; } { 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 the function in which this type is defined. This will
// return NULL for a type defined in global scope. // return NULL for a type defined in global scope.
const Named_object* const Named_object*
...@@ -3277,6 +3303,10 @@ class Named_type : public Type ...@@ -3277,6 +3303,10 @@ class Named_type : public Type
bool bool
do_needs_key_update(); do_needs_key_update();
bool
do_in_heap()
{ return this->in_heap_ && this->type_->in_heap(); }
unsigned int unsigned int
do_hash_for_method(Gogo*) const; do_hash_for_method(Gogo*) const;
...@@ -3344,6 +3374,9 @@ class Named_type : public Type ...@@ -3344,6 +3374,9 @@ class Named_type : public Type
bool is_visible_; bool is_visible_;
// Whether this type is erroneous. // Whether this type is erroneous.
bool is_error_; 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 // Whether the current value of named_btype_ is a placeholder for
// which the final size of the type is not known. // which the final size of the type is not known.
bool is_placeholder_; bool is_placeholder_;
...@@ -3436,6 +3469,10 @@ class Forward_declaration_type : public Type ...@@ -3436,6 +3469,10 @@ class Forward_declaration_type : public Type
do_needs_key_update() do_needs_key_update()
{ return this->real_type()->needs_key_update(); } { return this->real_type()->needs_key_update(); }
bool
do_in_heap()
{ return this->real_type()->in_heap(); }
unsigned int unsigned int
do_hash_for_method(Gogo* gogo) const do_hash_for_method(Gogo* gogo) const
{ return this->real_type()->hash_for_method(gogo); } { return this->real_type()->hash_for_method(gogo); }
......
...@@ -156,6 +156,13 @@ Write_barriers::variable(Named_object* no) ...@@ -156,6 +156,13 @@ Write_barriers::variable(Named_object* no)
if (!var->has_pre_init() && init->is_static_initializer()) if (!var->has_pre_init() && init->is_static_initializer())
return TRAVERSE_CONTINUE; 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 // Otherwise change the initializer into a pre_init assignment
// statement with a write barrier. // statement with a write barrier.
...@@ -215,6 +222,14 @@ Write_barriers::statement(Block* block, size_t* pindex, Statement* s) ...@@ -215,6 +222,14 @@ Write_barriers::statement(Block* block, size_t* pindex, Statement* s)
if (!var->type()->has_pointer()) if (!var->type()->has_pointer())
break; 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. // Otherwise initialize the variable with a write barrier.
Function* function = this->function_; Function* function = this->function_;
...@@ -345,6 +360,13 @@ Gogo::assign_needs_write_barrier(Expression* lhs) ...@@ -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. // Write barrier needed in other cases.
return true; return true;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment