// wb.cc -- Add write barriers as needed. // Copyright 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include "go-system.h" #include "go-c.h" #include "go-diagnostics.h" #include "operator.h" #include "lex.h" #include "types.h" #include "expressions.h" #include "statements.h" #include "runtime.h" #include "gogo.h" // 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_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 Mark_address_taken::expression(Expression** pexpr) { Expression* expr = *pexpr; Unary_expression* ue = expr->unary_expression(); if (ue != NULL) ue->check_operand_address_taken(this->gogo_); Array_index_expression* aie = expr->array_index_expression(); if (aie != NULL && aie->end() != NULL && !aie->array()->type()->is_slice_type()) { // Slice of an array. The escape analysis models this with // a child Node representing the address of the array. bool escapes = false; Node* n = Node::make_node(expr); if (n->child() == NULL || (n->child()->encoding() & ESCAPE_MASK) != Node::ESCAPE_NONE) escapes = true; aie->array()->address_taken(escapes); } if (expr->allocation_expression() != NULL) { Node* n = Node::make_node(expr); if ((n->encoding() & ESCAPE_MASK) == Node::ESCAPE_NONE) expr->allocation_expression()->set_allocate_on_stack(); } if (expr->heap_expression() != NULL) { Node* n = Node::make_node(expr); if ((n->encoding() & ESCAPE_MASK) == Node::ESCAPE_NONE) expr->heap_expression()->set_allocate_on_stack(); } if (expr->slice_literal() != NULL) { Node* n = Node::make_node(expr); if ((n->encoding() & ESCAPE_MASK) == Node::ESCAPE_NONE) expr->slice_literal()->set_storage_does_not_escape(); } // Rewrite non-escaping makeslice with constant size to stack allocation. Slice_value_expression* sve = expr->slice_value_expression(); if (sve != NULL) { std::pair p = Expression::find_makeslice_call(sve); Call_expression* call = p.first; Temporary_statement* ts = p.second; if (call != NULL && Node::make_node(call)->encoding() == Node::ESCAPE_NONE) { Expression* len_arg = call->args()->at(1); Expression* cap_arg = call->args()->at(2); Numeric_constant nclen; Numeric_constant nccap; unsigned long vlen; unsigned long vcap; if (len_arg->numeric_constant_value(&nclen) && cap_arg->numeric_constant_value(&nccap) && nclen.to_unsigned_long(&vlen) == Numeric_constant::NC_UL_VALID && nccap.to_unsigned_long(&vcap) == Numeric_constant::NC_UL_VALID) { // Stack allocate an array and make a slice value from it. Location loc = expr->location(); Type* elmt_type = expr->type()->array_type()->element_type(); Expression* len_expr = Expression::make_integer_ul(vcap, cap_arg->type(), loc); Type* array_type = Type::make_array_type(elmt_type, len_expr); Expression* alloc = Expression::make_allocation(array_type, loc); alloc->allocation_expression()->set_allocate_on_stack(); Type* ptr_type = Type::make_pointer_type(elmt_type); Expression* ptr = Expression::make_unsafe_cast(ptr_type, alloc, loc); Expression* slice = Expression::make_slice_value(expr->type(), ptr, len_arg, cap_arg, loc); *pexpr = slice; if (ts != NULL && ts->uses() == 1) ts->set_init(Expression::make_nil(loc)); } } } return TRAVERSE_CONTINUE; } // Check variables and closures do not escape when compiling runtime. class Check_escape : public Traverse { public: Check_escape() : Traverse(traverse_expressions | traverse_variables) { } int expression(Expression**); int variable(Named_object*); }; int Check_escape::variable(Named_object* no) { if ((no->is_variable() && no->var_value()->is_in_heap()) || (no->is_result_variable() && no->result_var_value()->is_in_heap())) go_error_at(no->location(), "%s escapes to heap, not allowed in runtime", no->message_name().c_str()); return TRAVERSE_CONTINUE; } int Check_escape::expression(Expression** pexpr) { Expression* expr = *pexpr; Func_expression* fe = expr->func_expression(); if (fe != NULL && fe->closure() != NULL) { Node* n = Node::make_node(expr); if (n->encoding() == Node::ESCAPE_HEAP) go_error_at(expr->location(), "heap-allocated closure, not allowed in runtime"); } return TRAVERSE_CONTINUE; } // Collect all writebarrierrec functions. This is used when compiling // the runtime package, to propagate //go:nowritebarrierrec. class Collect_writebarrierrec_functions : public Traverse { public: Collect_writebarrierrec_functions(std::vector* worklist) : Traverse(traverse_functions), worklist_(worklist) { } private: int function(Named_object*); // The collected functions are put here. std::vector* worklist_; }; int Collect_writebarrierrec_functions::function(Named_object* no) { if (no->is_function() && no->func_value()->enclosing() == NULL && (no->func_value()->pragmas() & GOPRAGMA_NOWRITEBARRIERREC) != 0) { go_assert((no->func_value()->pragmas() & GOPRAGMA_MARK) == 0); this->worklist_->push_back(no); } return TRAVERSE_CONTINUE; } // Collect all callees of this function. We only care about locally // defined, known, functions. class Collect_callees : public Traverse { public: Collect_callees(std::vector* worklist) : Traverse(traverse_expressions), worklist_(worklist) { } private: int expression(Expression**); // The collected callees are put here. std::vector* worklist_; }; int Collect_callees::expression(Expression** pexpr) { Call_expression* ce = (*pexpr)->call_expression(); if (ce != NULL) { Func_expression* fe = ce->fn()->func_expression(); if (fe != NULL) { Named_object* no = fe->named_object(); if (no->package() == NULL && no->is_function()) { // The function runtime.systemstack is special, in that // it is a common way to call a function in the runtime: // mark its argument if we can. if (Gogo::unpack_hidden_name(no->name()) != "systemstack") this->worklist_->push_back(no); else if (ce->args()->size() > 0) { fe = ce->args()->front()->func_expression(); if (fe != NULL) { no = fe->named_object(); if (no->package() == NULL && no->is_function()) this->worklist_->push_back(no); } } } } } return TRAVERSE_CONTINUE; } // When compiling the runtime package, propagate //go:nowritebarrierrec // annotations. A function marked as //go:nowritebarrierrec does not // permit write barriers, and also all the functions that it calls, // recursively, do not permit write barriers. Except that a // //go:yeswritebarrierrec annotation permits write barriers even if // called by a //go:nowritebarrierrec function. Here we turn // //go:nowritebarrierrec into //go:nowritebarrier, as appropriate. void Gogo::propagate_writebarrierrec() { std::vector worklist; Collect_writebarrierrec_functions cwf(&worklist); this->traverse(&cwf); Collect_callees cc(&worklist); while (!worklist.empty()) { Named_object* no = worklist.back(); worklist.pop_back(); unsigned int pragmas = no->func_value()->pragmas(); if ((pragmas & GOPRAGMA_MARK) != 0) { // We've already seen this function. continue; } if ((pragmas & GOPRAGMA_YESWRITEBARRIERREC) != 0) { // We don't want to propagate //go:nowritebarrierrec into // this function or it's callees. continue; } no->func_value()->set_pragmas(pragmas | GOPRAGMA_NOWRITEBARRIER | GOPRAGMA_MARK); no->func_value()->traverse(&cc); } } // Add write barriers to the IR. This are required by the concurrent // garbage collector. A write barrier is needed for any write of a // pointer into memory controlled by the garbage collector. Write // barriers are not required for writes to local variables that live // on the stack. Write barriers are only required when the runtime // enables them, which can be checked using a run time check on // runtime.writeBarrier.enabled. // // Essentially, for each assignment A = B, where A is or contains a // pointer, and where A is not, or at any rate may not be, a stack // variable, we rewrite it into // if runtime.writeBarrier.enabled { // typedmemmove(typeof(A), &A, &B) // } else { // A = B // } // // The test of runtime.writeBarrier.Enabled is implemented by treating // the variable as a *uint32, and testing *runtime.writeBarrier != 0. // This is compatible with the definition in the runtime package. // // For types that are pointer shared (pointers, maps, chans, funcs), // we replaced the call to typedmemmove with gcWriteBarrier(&A, B). // As far as the GC is concerned, all pointers are the same, so it // doesn't need the type descriptor. // // There are possible optimizations that are not implemented. // // runtime.writeBarrier can only change when the goroutine is // preempted, which in practice means when a call is made into the // runtime package, so we could optimize by only testing it once // between function calls. // // A slice could be handled with a call to gcWriteBarrier plus two // integer moves. // Traverse the IR adding write barriers. class Write_barriers : public Traverse { public: Write_barriers(Gogo* gogo) : Traverse(traverse_functions | traverse_blocks | traverse_variables | traverse_statements), gogo_(gogo), function_(NULL), statements_added_(), nonwb_pointers_() { } int function(Named_object*); int block(Block*); int variable(Named_object*); int statement(Block*, size_t* pindex, Statement*); private: // General IR. Gogo* gogo_; // Current function. Function* function_; // Statements introduced. Statement_inserter::Statements statements_added_; // Within a single block, pointer variables that point to values // that do not need write barriers. Unordered_set(const Named_object*) nonwb_pointers_; }; // Traverse a function. Just record it for later. int Write_barriers::function(Named_object* no) { go_assert(this->function_ == NULL); this->function_ = no->func_value(); int t = this->function_->traverse(this); this->function_ = NULL; if (t == TRAVERSE_EXIT) return t; return TRAVERSE_SKIP_COMPONENTS; } // Traverse a block. Clear anything we know about local pointer // variables. int Write_barriers::block(Block*) { this->nonwb_pointers_.clear(); return TRAVERSE_CONTINUE; } // Insert write barriers for a global variable: ensure that variable // initialization is handled correctly. This is rarely needed, since // we currently don't enable background GC until after all global // variables are initialized. But we do need this if an init function // calls runtime.GC. int Write_barriers::variable(Named_object* no) { // We handle local variables in the variable declaration statement. // We only have to handle global variables here. if (!no->is_variable()) return TRAVERSE_CONTINUE; Variable* var = no->var_value(); if (!var->is_global()) return TRAVERSE_CONTINUE; // Nothing to do if there is no initializer. Expression* init = var->init(); if (init == NULL) return TRAVERSE_CONTINUE; // Nothing to do for variables that do not contain any pointers. if (!var->type()->has_pointer()) return TRAVERSE_CONTINUE; // Nothing to do if the initializer is static. init = Expression::make_cast(var->type(), init, var->location()); 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. // We can't check for a dependency of the variable on itself after // we make this change, because the preinit statement will always // depend on the variable (since it assigns to it). So check for a // self-dependency now. this->gogo_->check_self_dep(no); // Replace the initializer. Location loc = init->location(); Expression* ref = Expression::make_var_reference(no, loc); Statement_inserter inserter(this->gogo_, var, &this->statements_added_); Statement* s = this->gogo_->assign_with_write_barrier(NULL, NULL, &inserter, ref, init, loc); this->statements_added_.insert(s); var->add_preinit_statement(this->gogo_, s); var->clear_init(); return TRAVERSE_CONTINUE; } // Insert write barriers for statements. int Write_barriers::statement(Block* block, size_t* pindex, Statement* s) { if (this->statements_added_.find(s) != this->statements_added_.end()) return TRAVERSE_SKIP_COMPONENTS; switch (s->classification()) { default: break; case Statement::STATEMENT_VARIABLE_DECLARATION: { Variable_declaration_statement* vds = s->variable_declaration_statement(); Named_object* no = vds->var(); Variable* var = no->var_value(); // We may need to emit a write barrier for the initialization // of the variable. // Nothing to do for a variable with no initializer. Expression* init = var->init(); if (init == NULL) break; // Nothing to do if the variable is not in the heap. Only // local variables get declaration statements, and local // variables on the stack do not require write barriers. if (!var->is_in_heap()) { // If this is a pointer variable, and assigning through // the initializer does not require a write barrier, // record that fact. if (var->type()->points_to() != NULL && this->gogo_->is_nonwb_pointer(init, &this->nonwb_pointers_)) this->nonwb_pointers_.insert(no); break; } // Nothing to do if the variable does not contain any pointers. 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_; Location loc = init->location(); Statement_inserter inserter(block, pindex, &this->statements_added_); // Insert the variable declaration statement with no // initializer, so that the variable exists. var->clear_init(); inserter.insert(s); // Create a statement that initializes the variable with a // write barrier. Expression* ref = Expression::make_var_reference(no, loc); Statement* assign = this->gogo_->assign_with_write_barrier(function, block, &inserter, ref, init, loc); this->statements_added_.insert(assign); // Replace the old variable declaration statement with the new // initialization. block->replace_statement(*pindex, assign); } break; case Statement::STATEMENT_ASSIGNMENT: { Assignment_statement* as = s->assignment_statement(); Expression* lhs = as->lhs(); Expression* rhs = as->rhs(); // Keep track of variables whose values do not escape. Var_expression* lhsve = lhs->var_expression(); if (lhsve != NULL && lhsve->type()->points_to() != NULL) { Named_object* no = lhsve->named_object(); if (this->gogo_->is_nonwb_pointer(rhs, &this->nonwb_pointers_)) this->nonwb_pointers_.insert(no); else this->nonwb_pointers_.erase(no); } if (as->omit_write_barrier()) break; // We may need to emit a write barrier for the assignment. if (!this->gogo_->assign_needs_write_barrier(lhs, &this->nonwb_pointers_)) break; // Change the assignment to use a write barrier. Function* function = this->function_; Location loc = as->location(); Statement_inserter inserter = Statement_inserter(block, pindex, &this->statements_added_); Statement* assign = this->gogo_->assign_with_write_barrier(function, block, &inserter, lhs, rhs, loc); this->statements_added_.insert(assign); block->replace_statement(*pindex, assign); } break; } return TRAVERSE_CONTINUE; } // The write barrier pass. void Gogo::add_write_barriers() { if (saw_errors()) return; Mark_address_taken mat(this); this->traverse(&mat); if (this->compiling_runtime() && this->package_name() == "runtime") { this->propagate_writebarrierrec(); Check_escape chk; this->traverse(&chk); } Write_barriers wb(this); this->traverse(&wb); } // Return the runtime.writeBarrier variable. Named_object* Gogo::write_barrier_variable() { static Named_object* write_barrier_var; if (write_barrier_var == NULL) { Location bloc = Linemap::predeclared_location(); Type* bool_type = Type::lookup_bool_type(); Array_type* pad_type = Type::make_array_type(Type::lookup_integer_type("byte"), Expression::make_integer_ul(3, NULL, bloc)); Type* uint64_type = Type::lookup_integer_type("uint64"); Type* wb_type = Type::make_builtin_struct_type(5, "enabled", bool_type, "pad", pad_type, "needed", bool_type, "cgo", bool_type, "alignme", uint64_type); Variable* var = new Variable(wb_type, NULL, true, false, false, bloc); bool add_to_globals; Package* package = this->add_imported_package("runtime", "_", false, "runtime", "runtime", bloc, &add_to_globals); write_barrier_var = Named_object::make_variable("writeBarrier", package, var); } return write_barrier_var; } // Return whether an assignment that sets LHS needs a write barrier. // NONWB_POINTERS is a set of variables that point to values that do // not need write barriers. bool Gogo::assign_needs_write_barrier( Expression* lhs, Unordered_set(const Named_object*)* nonwb_pointers) { // Nothing to do if the variable does not contain any pointers. if (!lhs->type()->has_pointer()) return false; // An assignment to a field or an array index is handled like an // assignment to the struct. while (true) { // 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. We check this // at each level of a struct. if (!lhs->type()->in_heap()) return false; if (lhs->type()->points_to() != NULL && !lhs->type()->points_to()->in_heap()) return false; // For a struct assignment, we don't need a write barrier if all // the field types can not be in the heap. Struct_type* st = lhs->type()->struct_type(); if (st != NULL) { bool in_heap = false; const Struct_field_list* fields = st->fields(); for (Struct_field_list::const_iterator p = fields->begin(); p != fields->end(); p++) { Type* ft = p->type(); if (!ft->has_pointer()) continue; if (!ft->in_heap()) continue; if (ft->points_to() != NULL && !ft->points_to()->in_heap()) continue; in_heap = true; break; } if (!in_heap) return false; } Field_reference_expression* fre = lhs->field_reference_expression(); if (fre != NULL) { lhs = fre->expr(); continue; } Array_index_expression* aie = lhs->array_index_expression(); if (aie != NULL && aie->end() == NULL && !aie->array()->type()->is_slice_type()) { lhs = aie->array(); continue; } break; } // Nothing to do for an assignment to a temporary. if (lhs->temporary_reference_expression() != NULL) return false; // Nothing to do for an assignment to a sink. if (lhs->is_sink_expression()) return false; // Nothing to do for an assignment to a local variable that is not // on the heap. Var_expression* ve = lhs->var_expression(); if (ve != NULL) { Named_object* no = ve->named_object(); if (no->is_variable()) { Variable* var = no->var_value(); if (!var->is_global() && !var->is_in_heap()) return false; } else if (no->is_result_variable()) { Result_variable* rvar = no->result_var_value(); if (!rvar->is_in_heap()) return false; } } // Nothing to do for an assignment to *(convert(&x)) where // x is local variable or a temporary variable. Unary_expression* ue = lhs->unary_expression(); if (ue != NULL && ue->op() == OPERATOR_MULT && this->is_nonwb_pointer(ue->operand(), nonwb_pointers)) return false; // Write barrier needed in other cases. return true; } // Return whether EXPR is the address of a variable that can be set // without a write barrier. That is, if this returns true, then an // assignment to *EXPR does not require a write barrier. // NONWB_POINTERS is a set of variables that point to values that do // not need write barriers. bool Gogo::is_nonwb_pointer(Expression* expr, Unordered_set(const Named_object*)* nonwb_pointers) { while (true) { if (expr->conversion_expression() != NULL) expr = expr->conversion_expression()->expr(); else if (expr->unsafe_conversion_expression() != NULL) expr = expr->unsafe_conversion_expression()->expr(); else break; } Var_expression* ve = expr->var_expression(); if (ve != NULL && nonwb_pointers != NULL && nonwb_pointers->find(ve->named_object()) != nonwb_pointers->end()) return true; Unary_expression* ue = expr->unary_expression(); if (ue == NULL || ue->op() != OPERATOR_AND) return false; if (this->assign_needs_write_barrier(ue->operand(), nonwb_pointers)) return false; return true; } // Return a statement that sets LHS to RHS using a write barrier. // ENCLOSING is the enclosing block. Statement* Gogo::assign_with_write_barrier(Function* function, Block* enclosing, Statement_inserter* inserter, Expression* lhs, Expression* rhs, Location loc) { if (function != NULL && (function->pragmas() & GOPRAGMA_NOWRITEBARRIER) != 0) go_error_at(loc, "write barrier prohibited"); Type* type = lhs->type(); go_assert(type->has_pointer()); Expression* addr; if (lhs->unary_expression() != NULL && lhs->unary_expression()->op() == OPERATOR_MULT) addr = lhs->unary_expression()->operand(); else { addr = Expression::make_unary(OPERATOR_AND, lhs, loc); addr->unary_expression()->set_does_not_escape(); } Temporary_statement* lhs_temp = Statement::make_temporary(NULL, addr, loc); inserter->insert(lhs_temp); lhs = Expression::make_temporary_reference(lhs_temp, loc); if (!Type::are_identical(type, rhs->type(), Type::COMPARE_ERRORS | Type::COMPARE_TAGS, NULL) && rhs->type()->interface_type() != NULL && !rhs->is_multi_eval_safe()) { // May need a temporary for interface conversion. Temporary_statement* temp = Statement::make_temporary(NULL, rhs, loc); inserter->insert(temp); rhs = Expression::make_temporary_reference(temp, loc); } rhs = Expression::convert_for_assignment(this, type, rhs, loc); Temporary_statement* rhs_temp = NULL; if (!rhs->is_multi_eval_safe()) { rhs_temp = Statement::make_temporary(NULL, rhs, loc); inserter->insert(rhs_temp); rhs = Expression::make_temporary_reference(rhs_temp, loc); } Expression* indir = Expression::make_dereference(lhs, Expression::NIL_CHECK_DEFAULT, loc); Statement* assign = Statement::make_assignment(indir, rhs, loc); lhs = Expression::make_temporary_reference(lhs_temp, loc); if (rhs_temp != NULL) rhs = Expression::make_temporary_reference(rhs_temp, loc); Type* unsafe_ptr_type = Type::make_pointer_type(Type::make_void_type()); lhs = Expression::make_unsafe_cast(unsafe_ptr_type, lhs, loc); Type* uintptr_type = Type::lookup_integer_type("uintptr"); Expression* call; switch (type->base()->classification()) { default: go_unreachable(); case Type::TYPE_ERROR: return assign; case Type::TYPE_POINTER: case Type::TYPE_FUNCTION: case Type::TYPE_MAP: case Type::TYPE_CHANNEL: { // These types are all represented by a single pointer. rhs = Expression::make_unsafe_cast(uintptr_type, rhs, loc); call = Runtime::make_call(Runtime::GCWRITEBARRIER, loc, 2, lhs, rhs); } break; case Type::TYPE_STRING: { // Assign the length field directly. Expression* llen = Expression::make_string_info(indir->copy(), Expression::STRING_INFO_LENGTH, loc); Expression* rlen = Expression::make_string_info(rhs, Expression::STRING_INFO_LENGTH, loc); Statement* as = Statement::make_assignment(llen, rlen, loc); inserter->insert(as); // Assign the data field with a write barrier. lhs = Expression::make_string_info(indir->copy(), Expression::STRING_INFO_DATA, loc); rhs = Expression::make_string_info(rhs, Expression::STRING_INFO_DATA, loc); assign = Statement::make_assignment(lhs, rhs, loc); lhs = Expression::make_unary(OPERATOR_AND, lhs, loc); rhs = Expression::make_unsafe_cast(uintptr_type, rhs, loc); call = Runtime::make_call(Runtime::GCWRITEBARRIER, loc, 2, lhs, rhs); } break; case Type::TYPE_INTERFACE: { // Assign the first field directly. // The first field is either a type descriptor or a method table. // Type descriptors are either statically created, or created by // the reflect package. For the latter the reflect package keeps // all references. // Method tables are either statically created or persistently // allocated. // In all cases they don't need a write barrier. Expression* ltab = Expression::make_interface_info(indir->copy(), Expression::INTERFACE_INFO_METHODS, loc); Expression* rtab = Expression::make_interface_info(rhs, Expression::INTERFACE_INFO_METHODS, loc); Statement* as = Statement::make_assignment(ltab, rtab, loc); inserter->insert(as); // Assign the data field with a write barrier. lhs = Expression::make_interface_info(indir->copy(), Expression::INTERFACE_INFO_OBJECT, loc); rhs = Expression::make_interface_info(rhs, Expression::INTERFACE_INFO_OBJECT, loc); assign = Statement::make_assignment(lhs, rhs, loc); lhs = Expression::make_unary(OPERATOR_AND, lhs, loc); rhs = Expression::make_unsafe_cast(uintptr_type, rhs, loc); call = Runtime::make_call(Runtime::GCWRITEBARRIER, loc, 2, lhs, rhs); } break; case Type::TYPE_ARRAY: if (type->is_slice_type()) { // Assign the lenth fields directly. Expression* llen = Expression::make_slice_info(indir->copy(), Expression::SLICE_INFO_LENGTH, loc); Expression* rlen = Expression::make_slice_info(rhs, Expression::SLICE_INFO_LENGTH, loc); Statement* as = Statement::make_assignment(llen, rlen, loc); inserter->insert(as); // Assign the capacity fields directly. Expression* lcap = Expression::make_slice_info(indir->copy(), Expression::SLICE_INFO_CAPACITY, loc); Expression* rcap = Expression::make_slice_info(rhs, Expression::SLICE_INFO_CAPACITY, loc); as = Statement::make_assignment(lcap, rcap, loc); inserter->insert(as); // Assign the data field with a write barrier. lhs = Expression::make_slice_info(indir->copy(), Expression::SLICE_INFO_VALUE_POINTER, loc); rhs = Expression::make_slice_info(rhs, Expression::SLICE_INFO_VALUE_POINTER, loc); assign = Statement::make_assignment(lhs, rhs, loc); lhs = Expression::make_unary(OPERATOR_AND, lhs, loc); rhs = Expression::make_unsafe_cast(uintptr_type, rhs, loc); call = Runtime::make_call(Runtime::GCWRITEBARRIER, loc, 2, lhs, rhs); break; } // fallthrough case Type::TYPE_STRUCT: if (type->is_direct_iface_type()) { rhs = Expression::unpack_direct_iface(rhs, loc); rhs = Expression::make_unsafe_cast(uintptr_type, rhs, loc); call = Runtime::make_call(Runtime::GCWRITEBARRIER, loc, 2, lhs, rhs); } else { // TODO: split assignments for small struct/array? rhs = Expression::make_unary(OPERATOR_AND, rhs, loc); rhs->unary_expression()->set_does_not_escape(); call = Runtime::make_call(Runtime::TYPEDMEMMOVE, loc, 3, Expression::make_type_descriptor(type, loc), lhs, rhs); } break; } return this->check_write_barrier(enclosing, assign, Statement::make_statement(call, false)); } // Return a statement that tests whether write barriers are enabled // and executes either the efficient code or the write barrier // function call, depending. Statement* Gogo::check_write_barrier(Block* enclosing, Statement* without, Statement* with) { Location loc = without->location(); Named_object* wb = this->write_barrier_variable(); // We pretend that writeBarrier is a uint32, so that we do a // 32-bit load. That is what the gc toolchain does. Type* void_type = Type::make_void_type(); Type* unsafe_pointer_type = Type::make_pointer_type(void_type); Type* uint32_type = Type::lookup_integer_type("uint32"); Type* puint32_type = Type::make_pointer_type(uint32_type); Expression* ref = Expression::make_var_reference(wb, loc); ref = Expression::make_unary(OPERATOR_AND, ref, loc); ref = Expression::make_cast(unsafe_pointer_type, ref, loc); ref = Expression::make_cast(puint32_type, ref, loc); ref = Expression::make_dereference(ref, Expression::NIL_CHECK_NOT_NEEDED, loc); Expression* zero = Expression::make_integer_ul(0, ref->type(), loc); Expression* cond = Expression::make_binary(OPERATOR_EQEQ, ref, zero, loc); Block* then_block = new Block(enclosing, loc); then_block->add_statement(without); Block* else_block = new Block(enclosing, loc); else_block->add_statement(with); return Statement::make_if_statement(cond, then_block, else_block, loc); }