|  | // 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(this->lookup_global("byte")->type_value(), | 
|  | 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); | 
|  | } |