| // 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<Call_expression*, Temporary_statement*> 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(Gogo* gogo) |
| : Traverse(traverse_expressions | traverse_variables), |
| gogo_(gogo) |
| { } |
| |
| int |
| expression(Expression**); |
| |
| int |
| variable(Named_object*); |
| |
| private: |
| Gogo* gogo_; |
| }; |
| |
| 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<Named_object*>* worklist) |
| : Traverse(traverse_functions), |
| worklist_(worklist) |
| { } |
| |
| private: |
| int |
| function(Named_object*); |
| |
| // The collected functions are put here. |
| std::vector<Named_object*>* 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<Named_object*>* worklist) |
| : Traverse(traverse_expressions), |
| worklist_(worklist) |
| { } |
| |
| private: |
| int |
| expression(Expression**); |
| |
| // The collected callees are put here. |
| std::vector<Named_object*>* 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<Named_object*> 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); |
| 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_variable()) |
| { |
| // 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_variable() && !rhs->is_constant()) |
| { |
| 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); |
| } |