| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| #include "vm/flow_graph.h" |
| |
| #include "vm/bit_vector.h" |
| #include "vm/flow_graph_builder.h" |
| #include "vm/intermediate_language.h" |
| #include "vm/longjump.h" |
| #include "vm/growable_array.h" |
| |
| namespace dart { |
| |
| DECLARE_FLAG(bool, trace_optimization); |
| DECLARE_FLAG(bool, verify_compiler); |
| |
| FlowGraph::FlowGraph(const FlowGraphBuilder& builder, |
| GraphEntryInstr* graph_entry, |
| intptr_t max_block_id) |
| : parent_(), |
| assigned_vars_(), |
| current_ssa_temp_index_(0), |
| max_block_id_(max_block_id), |
| parsed_function_(builder.parsed_function()), |
| num_copied_params_(builder.num_copied_params()), |
| num_non_copied_params_(builder.num_non_copied_params()), |
| num_stack_locals_(builder.num_stack_locals()), |
| graph_entry_(graph_entry), |
| preorder_(), |
| postorder_(), |
| reverse_postorder_(), |
| exits_(NULL), |
| invalid_dominator_tree_(true) { |
| DiscoverBlocks(); |
| } |
| |
| |
| ConstantInstr* FlowGraph::AddConstantToInitialDefinitions( |
| const Object& object) { |
| // Check if the constant is already in the pool. |
| for (intptr_t i = 0; i < graph_entry_->initial_definitions()->length(); ++i) { |
| ConstantInstr* constant = |
| (*graph_entry_->initial_definitions())[i]->AsConstant(); |
| if ((constant != NULL) && (constant->value().raw() == object.raw())) { |
| return constant; |
| } |
| } |
| // Otherwise, allocate and add it to the pool. |
| ConstantInstr* constant = new ConstantInstr(object); |
| constant->set_ssa_temp_index(alloc_ssa_temp_index()); |
| AddToInitialDefinitions(constant); |
| return constant; |
| } |
| |
| void FlowGraph::AddToInitialDefinitions(Definition* defn) { |
| // TODO(zerny): Set previous to the graph entry so it is accessible by |
| // GetBlock. Remove this once there is a direct pointer to the block. |
| defn->set_previous(graph_entry_); |
| graph_entry_->initial_definitions()->Add(defn); |
| } |
| |
| |
| void FlowGraph::DiscoverBlocks() { |
| // Initialize state. |
| preorder_.Clear(); |
| postorder_.Clear(); |
| reverse_postorder_.Clear(); |
| parent_.Clear(); |
| assigned_vars_.Clear(); |
| // Perform a depth-first traversal of the graph to build preorder and |
| // postorder block orders. |
| graph_entry_->DiscoverBlocks(NULL, // Entry block predecessor. |
| &preorder_, |
| &postorder_, |
| &parent_, |
| &assigned_vars_, |
| variable_count(), |
| num_non_copied_params()); |
| // Create an array of blocks in reverse postorder. |
| intptr_t block_count = postorder_.length(); |
| for (intptr_t i = 0; i < block_count; ++i) { |
| reverse_postorder_.Add(postorder_[block_count - i - 1]); |
| } |
| } |
| |
| |
| #ifdef DEBUG |
| // Debugging code to verify the construction of use lists. |
| |
| static intptr_t MembershipCount(Value* use, Value* list) { |
| intptr_t count = 0; |
| while (list != NULL) { |
| if (list == use) ++count; |
| list = list->next_use(); |
| } |
| return count; |
| } |
| |
| |
| static void ResetUseListsInInstruction(Instruction* instr) { |
| Definition* defn = instr->AsDefinition(); |
| if (defn != NULL) { |
| defn->set_input_use_list(NULL); |
| defn->set_env_use_list(NULL); |
| } |
| for (intptr_t i = 0; i < instr->InputCount(); ++i) { |
| Value* use = instr->InputAt(i); |
| use->set_instruction(NULL); |
| use->set_use_index(-1); |
| use->set_next_use(NULL); |
| } |
| if (instr->env() != NULL) { |
| for (Environment::DeepIterator it(instr->env()); !it.Done(); it.Advance()) { |
| Value* use = it.CurrentValue(); |
| use->set_instruction(NULL); |
| use->set_use_index(-1); |
| use->set_next_use(NULL); |
| } |
| } |
| } |
| |
| |
| bool FlowGraph::ResetUseLists() { |
| // Reset initial definitions. |
| for (intptr_t i = 0; i < graph_entry_->initial_definitions()->length(); ++i) { |
| ResetUseListsInInstruction((*graph_entry_->initial_definitions())[i]); |
| } |
| |
| // Reset phis in join entries and the instructions in each block. |
| for (intptr_t i = 0; i < preorder_.length(); ++i) { |
| BlockEntryInstr* entry = preorder_[i]; |
| JoinEntryInstr* join = entry->AsJoinEntry(); |
| if (join != NULL && join->phis() != NULL) { |
| for (intptr_t i = 0; i < join->phis()->length(); ++i) { |
| PhiInstr* phi = (*join->phis())[i]; |
| if (phi != NULL) ResetUseListsInInstruction(phi); |
| } |
| } |
| for (ForwardInstructionIterator it(entry); !it.Done(); it.Advance()) { |
| ResetUseListsInInstruction(it.Current()); |
| } |
| } |
| return true; // Return true so we can ASSERT the reset code. |
| } |
| |
| |
| static void ValidateUseListsInInstruction(Instruction* instr) { |
| ASSERT(instr != NULL); |
| ASSERT(!instr->IsJoinEntry()); |
| for (intptr_t i = 0; i < instr->InputCount(); ++i) { |
| Value* use = instr->InputAt(i); |
| ASSERT(use->use_index() == i); |
| ASSERT(!FLAG_verify_compiler || |
| (1 == MembershipCount(use, use->definition()->input_use_list()))); |
| } |
| if (instr->env() != NULL) { |
| intptr_t use_index = 0; |
| for (Environment::DeepIterator it(instr->env()); !it.Done(); it.Advance()) { |
| Value* use = it.CurrentValue(); |
| ASSERT(use->use_index() == use_index++); |
| ASSERT(!FLAG_verify_compiler || |
| (1 == MembershipCount(use, use->definition()->env_use_list()))); |
| } |
| } |
| Definition* defn = instr->AsDefinition(); |
| if (defn != NULL) { |
| for (Value* use = defn->input_use_list(); |
| use != NULL; |
| use = use->next_use()) { |
| ASSERT(defn == use->definition()); |
| ASSERT(use == use->instruction()->InputAt(use->use_index())); |
| } |
| for (Value* use = defn->env_use_list(); |
| use != NULL; |
| use = use->next_use()) { |
| ASSERT(defn == use->definition()); |
| ASSERT(use == |
| use->instruction()->env()->ValueAtUseIndex(use->use_index())); |
| } |
| } |
| } |
| |
| |
| bool FlowGraph::ValidateUseLists() { |
| // Validate initial definitions. |
| for (intptr_t i = 0; i < graph_entry_->initial_definitions()->length(); ++i) { |
| ValidateUseListsInInstruction((*graph_entry_->initial_definitions())[i]); |
| } |
| |
| // Validate phis in join entries and the instructions in each block. |
| for (intptr_t i = 0; i < preorder_.length(); ++i) { |
| BlockEntryInstr* entry = preorder_[i]; |
| JoinEntryInstr* join = entry->AsJoinEntry(); |
| if (join != NULL && join->phis() != NULL) { |
| for (intptr_t i = 0; i < join->phis()->length(); ++i) { |
| PhiInstr* phi = (*join->phis())[i]; |
| if (phi != NULL) ValidateUseListsInInstruction(phi); |
| } |
| } |
| for (ForwardInstructionIterator it(entry); !it.Done(); it.Advance()) { |
| ValidateUseListsInInstruction(it.Current()); |
| } |
| } |
| return true; // Return true so we can ASSERT validation. |
| } |
| #endif // DEBUG |
| |
| |
| static void ClearUseLists(Definition* defn) { |
| ASSERT(defn != NULL); |
| ASSERT(defn->input_use_list() == NULL); |
| ASSERT(defn->env_use_list() == NULL); |
| defn->set_input_use_list(NULL); |
| defn->set_env_use_list(NULL); |
| } |
| |
| |
| static void RecordInputUses(Instruction* instr) { |
| ASSERT(instr != NULL); |
| for (intptr_t i = 0; i < instr->InputCount(); ++i) { |
| Value* use = instr->InputAt(i); |
| ASSERT(use->instruction() == NULL); |
| ASSERT(use->use_index() == -1); |
| ASSERT(use->next_use() == NULL); |
| DEBUG_ASSERT(!FLAG_verify_compiler || |
| (0 == MembershipCount(use, use->definition()->input_use_list()))); |
| use->set_instruction(instr); |
| use->set_use_index(i); |
| use->AddToInputUseList(); |
| } |
| } |
| |
| |
| static void RecordEnvUses(Instruction* instr) { |
| ASSERT(instr != NULL); |
| if (instr->env() == NULL) return; |
| intptr_t use_index = 0; |
| for (Environment::DeepIterator it(instr->env()); !it.Done(); it.Advance()) { |
| Value* use = it.CurrentValue(); |
| ASSERT(use->instruction() == NULL); |
| ASSERT(use->use_index() == -1); |
| ASSERT(use->next_use() == NULL); |
| DEBUG_ASSERT(!FLAG_verify_compiler || |
| (0 == MembershipCount(use, use->definition()->env_use_list()))); |
| use->set_instruction(instr); |
| use->set_use_index(use_index++); |
| use->AddToEnvUseList(); |
| } |
| } |
| |
| |
| static void ComputeUseListsRecursive(BlockEntryInstr* block) { |
| // Clear phi definitions. |
| JoinEntryInstr* join = block->AsJoinEntry(); |
| if (join != NULL && join->phis() != NULL) { |
| for (intptr_t i = 0; i < join->phis()->length(); ++i) { |
| PhiInstr* phi = (*join->phis())[i]; |
| if (phi != NULL) ClearUseLists(phi); |
| } |
| } |
| // Compute uses on normal instructions. |
| for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) { |
| Instruction* instr = it.Current(); |
| if (instr->IsDefinition()) ClearUseLists(instr->AsDefinition()); |
| RecordInputUses(instr); |
| RecordEnvUses(instr); |
| } |
| // Compute recursively on dominated blocks. |
| for (intptr_t i = 0; i < block->dominated_blocks().length(); ++i) { |
| ComputeUseListsRecursive(block->dominated_blocks()[i]); |
| } |
| // Add phi uses on successor edges. |
| if (block->last_instruction()->SuccessorCount() == 1 && |
| block->last_instruction()->SuccessorAt(0)->IsJoinEntry()) { |
| JoinEntryInstr* join = |
| block->last_instruction()->SuccessorAt(0)->AsJoinEntry(); |
| intptr_t pred_index = join->IndexOfPredecessor(block); |
| ASSERT(pred_index >= 0); |
| if (join->phis() != NULL) { |
| for (intptr_t i = 0; i < join->phis()->length(); ++i) { |
| PhiInstr* phi = (*join->phis())[i]; |
| if (phi == NULL) continue; |
| Value* use = phi->InputAt(pred_index); |
| ASSERT(use->instruction() == NULL); |
| ASSERT(use->use_index() == -1); |
| ASSERT(use->next_use() == NULL); |
| DEBUG_ASSERT(!FLAG_verify_compiler || |
| (0 == MembershipCount(use, use->definition()->input_use_list()))); |
| use->set_instruction(phi); |
| use->set_use_index(pred_index); |
| use->AddToInputUseList(); |
| } |
| } |
| } |
| } |
| |
| |
| void FlowGraph::ComputeUseLists() { |
| DEBUG_ASSERT(ResetUseLists()); |
| // Clear initial definitions. |
| for (intptr_t i = 0; i < graph_entry_->initial_definitions()->length(); ++i) { |
| ClearUseLists((*graph_entry_->initial_definitions())[i]); |
| } |
| ComputeUseListsRecursive(graph_entry_); |
| DEBUG_ASSERT(!FLAG_verify_compiler || ValidateUseLists()); |
| } |
| |
| |
| void FlowGraph::ComputeSSA(intptr_t next_virtual_register_number, |
| GrowableArray<Definition*>* inlining_parameters) { |
| ASSERT((next_virtual_register_number == 0) || (inlining_parameters != NULL)); |
| current_ssa_temp_index_ = next_virtual_register_number; |
| GrowableArray<BitVector*> dominance_frontier; |
| ComputeDominators(&dominance_frontier); |
| InsertPhis(preorder_, assigned_vars_, dominance_frontier); |
| GrowableArray<PhiInstr*> live_phis; |
| // Rename uses to reference inserted phis where appropriate. |
| // Collect phis that reach a non-environment use. |
| Rename(&live_phis, inlining_parameters); |
| // Propagate alive mark transitively from alive phis. |
| MarkLivePhis(&live_phis); |
| } |
| |
| |
| // Compute immediate dominators and the dominance frontier for each basic |
| // block. As a side effect of the algorithm, sets the immediate dominator |
| // of each basic block. |
| // |
| // dominance_frontier: an output parameter encoding the dominance frontier. |
| // The array maps the preorder block number of a block to the set of |
| // (preorder block numbers of) blocks in the dominance frontier. |
| void FlowGraph::ComputeDominators( |
| GrowableArray<BitVector*>* dominance_frontier) { |
| invalid_dominator_tree_ = false; |
| // Use the SEMI-NCA algorithm to compute dominators. This is a two-pass |
| // version of the Lengauer-Tarjan algorithm (LT is normally three passes) |
| // that eliminates a pass by using nearest-common ancestor (NCA) to |
| // compute immediate dominators from semidominators. It also removes a |
| // level of indirection in the link-eval forest data structure. |
| // |
| // The algorithm is described in Georgiadis, Tarjan, and Werneck's |
| // "Finding Dominators in Practice". |
| // See http://www.cs.princeton.edu/~rwerneck/dominators/ . |
| |
| // All arrays are maps between preorder basic-block numbers. |
| intptr_t size = parent_.length(); |
| GrowableArray<intptr_t> idom(size); // Immediate dominator. |
| GrowableArray<intptr_t> semi(size); // Semidominator. |
| GrowableArray<intptr_t> label(size); // Label for link-eval forest. |
| |
| // 1. First pass: compute semidominators as in Lengauer-Tarjan. |
| // Semidominators are computed from a depth-first spanning tree and are an |
| // approximation of immediate dominators. |
| |
| // Use a link-eval data structure with path compression. Implement path |
| // compression in place by mutating the parent array. Each block has a |
| // label, which is the minimum block number on the compressed path. |
| |
| // Initialize idom, semi, and label used by SEMI-NCA. Initialize the |
| // dominance frontier output array. |
| for (intptr_t i = 0; i < size; ++i) { |
| idom.Add(parent_[i]); |
| semi.Add(i); |
| label.Add(i); |
| dominance_frontier->Add(new BitVector(size)); |
| } |
| |
| // Loop over the blocks in reverse preorder (not including the graph |
| // entry). Clear the dominated blocks in the graph entry in case |
| // ComputeDominators is used to recompute them. |
| preorder_[0]->ClearDominatedBlocks(); |
| for (intptr_t block_index = size - 1; block_index >= 1; --block_index) { |
| // Loop over the predecessors. |
| BlockEntryInstr* block = preorder_[block_index]; |
| // Clear the immediately dominated blocks in case ComputeDominators is |
| // used to recompute them. |
| block->ClearDominatedBlocks(); |
| for (intptr_t i = 0, count = block->PredecessorCount(); i < count; ++i) { |
| BlockEntryInstr* pred = block->PredecessorAt(i); |
| ASSERT(pred != NULL); |
| |
| // Look for the semidominator by ascending the semidominator path |
| // starting from pred. |
| intptr_t pred_index = pred->preorder_number(); |
| intptr_t best = pred_index; |
| if (pred_index > block_index) { |
| CompressPath(block_index, pred_index, &parent_, &label); |
| best = label[pred_index]; |
| } |
| |
| // Update the semidominator if we've found a better one. |
| semi[block_index] = Utils::Minimum(semi[block_index], semi[best]); |
| } |
| |
| // Now use label for the semidominator. |
| label[block_index] = semi[block_index]; |
| } |
| |
| // 2. Compute the immediate dominators as the nearest common ancestor of |
| // spanning tree parent and semidominator, for all blocks except the entry. |
| for (intptr_t block_index = 1; block_index < size; ++block_index) { |
| intptr_t dom_index = idom[block_index]; |
| while (dom_index > semi[block_index]) { |
| dom_index = idom[dom_index]; |
| } |
| idom[block_index] = dom_index; |
| preorder_[block_index]->set_dominator(preorder_[dom_index]); |
| preorder_[dom_index]->AddDominatedBlock(preorder_[block_index]); |
| } |
| |
| // 3. Now compute the dominance frontier for all blocks. This is |
| // algorithm in "A Simple, Fast Dominance Algorithm" (Figure 5), which is |
| // attributed to a paper by Ferrante et al. There is no bookkeeping |
| // required to avoid adding a block twice to the same block's dominance |
| // frontier because we use a set to represent the dominance frontier. |
| for (intptr_t block_index = 0; block_index < size; ++block_index) { |
| BlockEntryInstr* block = preorder_[block_index]; |
| intptr_t count = block->PredecessorCount(); |
| if (count <= 1) continue; |
| for (intptr_t i = 0; i < count; ++i) { |
| BlockEntryInstr* runner = block->PredecessorAt(i); |
| while (runner != block->dominator()) { |
| (*dominance_frontier)[runner->preorder_number()]->Add(block_index); |
| runner = runner->dominator(); |
| } |
| } |
| } |
| } |
| |
| |
| void FlowGraph::CompressPath(intptr_t start_index, |
| intptr_t current_index, |
| GrowableArray<intptr_t>* parent, |
| GrowableArray<intptr_t>* label) { |
| intptr_t next_index = (*parent)[current_index]; |
| if (next_index > start_index) { |
| CompressPath(start_index, next_index, parent, label); |
| (*label)[current_index] = |
| Utils::Minimum((*label)[current_index], (*label)[next_index]); |
| (*parent)[current_index] = (*parent)[next_index]; |
| } |
| } |
| |
| |
| void FlowGraph::InsertPhis( |
| const GrowableArray<BlockEntryInstr*>& preorder, |
| const GrowableArray<BitVector*>& assigned_vars, |
| const GrowableArray<BitVector*>& dom_frontier) { |
| const intptr_t block_count = preorder.length(); |
| // Map preorder block number to the highest variable index that has a phi |
| // in that block. Use it to avoid inserting multiple phis for the same |
| // variable. |
| GrowableArray<intptr_t> has_already(block_count); |
| // Map preorder block number to the highest variable index for which the |
| // block went on the worklist. Use it to avoid adding the same block to |
| // the worklist more than once for the same variable. |
| GrowableArray<intptr_t> work(block_count); |
| |
| // Initialize has_already and work. |
| for (intptr_t block_index = 0; block_index < block_count; ++block_index) { |
| has_already.Add(-1); |
| work.Add(-1); |
| } |
| |
| // Insert phis for each variable in turn. |
| GrowableArray<BlockEntryInstr*> worklist; |
| for (intptr_t var_index = 0; var_index < variable_count(); ++var_index) { |
| // Add to the worklist each block containing an assignment. |
| for (intptr_t block_index = 0; block_index < block_count; ++block_index) { |
| if (assigned_vars[block_index]->Contains(var_index)) { |
| work[block_index] = var_index; |
| worklist.Add(preorder[block_index]); |
| } |
| } |
| |
| while (!worklist.is_empty()) { |
| BlockEntryInstr* current = worklist.RemoveLast(); |
| // Ensure a phi for each block in the dominance frontier of current. |
| for (BitVector::Iterator it(dom_frontier[current->preorder_number()]); |
| !it.Done(); |
| it.Advance()) { |
| int index = it.Current(); |
| if (has_already[index] < var_index) { |
| BlockEntryInstr* block = preorder[index]; |
| ASSERT(block->IsJoinEntry()); |
| block->AsJoinEntry()->InsertPhi(var_index, variable_count()); |
| has_already[index] = var_index; |
| if (work[index] < var_index) { |
| work[index] = var_index; |
| worklist.Add(block); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| |
| void FlowGraph::Rename(GrowableArray<PhiInstr*>* live_phis, |
| GrowableArray<Definition*>* inlining_parameters) { |
| // TODO(fschneider): Support catch-entry. |
| if (graph_entry_->SuccessorCount() > 1) { |
| Bailout("Catch-entry support in SSA."); |
| } |
| |
| // Initial renaming environment. |
| GrowableArray<Definition*> env(variable_count()); |
| |
| // Add global constants to the initial definitions. |
| ConstantInstr* constant_null = |
| AddConstantToInitialDefinitions(Object::ZoneHandle()); |
| |
| // Add parameters to the initial definitions and renaming environment. |
| if (inlining_parameters != NULL) { |
| // Use known parameters. |
| ASSERT(parameter_count() == inlining_parameters->length()); |
| for (intptr_t i = 0; i < parameter_count(); ++i) { |
| Definition* defn = (*inlining_parameters)[i]; |
| defn->set_ssa_temp_index(alloc_ssa_temp_index()); // New SSA temp. |
| AddToInitialDefinitions(defn); |
| env.Add(defn); |
| } |
| } else { |
| // Create new parameters. |
| for (intptr_t i = 0; i < parameter_count(); ++i) { |
| ParameterInstr* param = new ParameterInstr(i, graph_entry_); |
| param->set_ssa_temp_index(alloc_ssa_temp_index()); // New SSA temp. |
| AddToInitialDefinitions(param); |
| env.Add(param); |
| } |
| } |
| |
| // Initialize all locals with #null in the renaming environment. |
| for (intptr_t i = parameter_count(); i < variable_count(); ++i) { |
| env.Add(constant_null); |
| } |
| |
| BlockEntryInstr* normal_entry = graph_entry_->SuccessorAt(0); |
| ASSERT(normal_entry != NULL); // Must have entry. |
| RenameRecursive(normal_entry, &env, live_phis); |
| } |
| |
| |
| void FlowGraph::RenameRecursive(BlockEntryInstr* block_entry, |
| GrowableArray<Definition*>* env, |
| GrowableArray<PhiInstr*>* live_phis) { |
| // 1. Process phis first. |
| if (block_entry->IsJoinEntry()) { |
| JoinEntryInstr* join = block_entry->AsJoinEntry(); |
| if (join->phis() != NULL) { |
| for (intptr_t i = 0; i < join->phis()->length(); ++i) { |
| PhiInstr* phi = (*join->phis())[i]; |
| if (phi != NULL) { |
| (*env)[i] = phi; |
| phi->set_ssa_temp_index(alloc_ssa_temp_index()); // New SSA temp. |
| } |
| } |
| } |
| } |
| |
| // 2. Process normal instructions. |
| for (ForwardInstructionIterator it(block_entry); !it.Done(); it.Advance()) { |
| Instruction* current = it.Current(); |
| // Attach current environment to the instructions that can deoptimize and |
| // at goto instructions. Optimizations like LICM expect an environment at |
| // gotos. |
| if (current->CanDeoptimize() || current->IsGoto()) { |
| current->set_env(Environment::From(*env, |
| num_non_copied_params_, |
| parsed_function_.function())); |
| } |
| if (current->CanDeoptimize()) { |
| current->env()->set_deopt_id(current->deopt_id()); |
| } |
| |
| // 2a. Handle uses: |
| // Update expression stack environment for each use. |
| // For each use of a LoadLocal or StoreLocal: Replace it with the value |
| // from the environment. |
| for (intptr_t i = current->InputCount() - 1; i >= 0; --i) { |
| Value* v = current->InputAt(i); |
| // Update expression stack. |
| ASSERT(env->length() > variable_count()); |
| |
| Definition* reaching_defn = env->RemoveLast(); |
| |
| Definition* input_defn = v->definition(); |
| if (input_defn->IsLoadLocal() || input_defn->IsStoreLocal()) { |
| // Remove the load/store from the graph. |
| input_defn->RemoveFromGraph(); |
| // Assert we are not referencing nulls in the initial environment. |
| ASSERT(reaching_defn->ssa_temp_index() != -1); |
| current->SetInputAt(i, new Value(reaching_defn)); |
| } |
| } |
| |
| // Drop pushed arguments for calls. |
| for (intptr_t j = 0; j < current->ArgumentCount(); j++) { |
| env->RemoveLast(); |
| } |
| |
| // 2b. Handle LoadLocal and StoreLocal. |
| // For each LoadLocal: Remove it from the graph. |
| // For each StoreLocal: Remove it from the graph and update the environment. |
| Definition* definition = current->AsDefinition(); |
| if (definition != NULL) { |
| LoadLocalInstr* load = definition->AsLoadLocal(); |
| StoreLocalInstr* store = definition->AsStoreLocal(); |
| if ((load != NULL) || (store != NULL)) { |
| intptr_t index; |
| if (store != NULL) { |
| index = store->local().BitIndexIn(num_non_copied_params_); |
| // Update renaming environment. |
| (*env)[index] = store->value()->definition(); |
| } else { |
| // The graph construction ensures we do not have an unused LoadLocal |
| // computation. |
| ASSERT(definition->is_used()); |
| index = load->local().BitIndexIn(num_non_copied_params_); |
| |
| PhiInstr* phi = (*env)[index]->AsPhi(); |
| if ((phi != NULL) && !phi->is_alive()) { |
| phi->mark_alive(); |
| live_phis->Add(phi); |
| } |
| } |
| // Update expression stack or remove from graph. |
| if (definition->is_used()) { |
| env->Add((*env)[index]); |
| // We remove load/store instructions when we find their use in 2a. |
| } else { |
| it.RemoveCurrentFromGraph(); |
| } |
| } else { |
| // Not a load or store. |
| if (definition->is_used()) { |
| // Assign fresh SSA temporary and update expression stack. |
| definition->set_ssa_temp_index(alloc_ssa_temp_index()); |
| env->Add(definition); |
| } |
| } |
| } |
| |
| // 2c. Handle pushed argument. |
| PushArgumentInstr* push = current->AsPushArgument(); |
| if (push != NULL) { |
| env->Add(push); |
| } |
| } |
| |
| // 3. Process dominated blocks. |
| for (intptr_t i = 0; i < block_entry->dominated_blocks().length(); ++i) { |
| BlockEntryInstr* block = block_entry->dominated_blocks()[i]; |
| GrowableArray<Definition*> new_env(env->length()); |
| new_env.AddArray(*env); |
| RenameRecursive(block, &new_env, live_phis); |
| } |
| |
| // 4. Process successor block. We have edge-split form, so that only blocks |
| // with one successor can have a join block as successor. |
| if ((block_entry->last_instruction()->SuccessorCount() == 1) && |
| block_entry->last_instruction()->SuccessorAt(0)->IsJoinEntry()) { |
| JoinEntryInstr* successor = |
| block_entry->last_instruction()->SuccessorAt(0)->AsJoinEntry(); |
| intptr_t pred_index = successor->IndexOfPredecessor(block_entry); |
| ASSERT(pred_index >= 0); |
| if (successor->phis() != NULL) { |
| for (intptr_t i = 0; i < successor->phis()->length(); ++i) { |
| PhiInstr* phi = (*successor->phis())[i]; |
| if (phi != NULL) { |
| // Rename input operand. |
| phi->SetInputAt(pred_index, new Value((*env)[i])); |
| } |
| } |
| } |
| } |
| } |
| |
| |
| void FlowGraph::MarkLivePhis(GrowableArray<PhiInstr*>* live_phis) { |
| while (!live_phis->is_empty()) { |
| PhiInstr* phi = live_phis->RemoveLast(); |
| for (intptr_t i = 0; i < phi->InputCount(); i++) { |
| Value* val = phi->InputAt(i); |
| PhiInstr* used_phi = val->definition()->AsPhi(); |
| if ((used_phi != NULL) && !used_phi->is_alive()) { |
| used_phi->mark_alive(); |
| live_phis->Add(used_phi); |
| } |
| } |
| } |
| } |
| |
| |
| // Find the natural loop for the back edge m->n and attach loop information |
| // to block n (loop header). The algorithm is described in "Advanced Compiler |
| // Design & Implementation" (Muchnick) p192. |
| static void FindLoop(BlockEntryInstr* m, |
| BlockEntryInstr* n, |
| intptr_t num_blocks) { |
| GrowableArray<BlockEntryInstr*> stack; |
| BitVector* loop = new BitVector(num_blocks); |
| |
| loop->Add(n->preorder_number()); |
| if (n != m) { |
| loop->Add(m->preorder_number()); |
| stack.Add(m); |
| } |
| |
| while (!stack.is_empty()) { |
| BlockEntryInstr* p = stack.RemoveLast(); |
| for (intptr_t i = 0; i < p->PredecessorCount(); ++i) { |
| BlockEntryInstr* q = p->PredecessorAt(i); |
| if (!loop->Contains(q->preorder_number())) { |
| loop->Add(q->preorder_number()); |
| stack.Add(q); |
| } |
| } |
| } |
| n->set_loop_info(loop); |
| if (FLAG_trace_optimization) { |
| for (BitVector::Iterator it(loop); !it.Done(); it.Advance()) { |
| OS::Print(" B%"Pd"\n", it.Current()); |
| } |
| } |
| } |
| |
| |
| void FlowGraph::ComputeLoops(GrowableArray<BlockEntryInstr*>* loop_headers) { |
| ASSERT(loop_headers->is_empty()); |
| for (BlockIterator it = postorder_iterator(); |
| !it.Done(); |
| it.Advance()) { |
| BlockEntryInstr* block = it.Current(); |
| for (intptr_t i = 0; i < block->PredecessorCount(); ++i) { |
| BlockEntryInstr* pred = block->PredecessorAt(i); |
| if (block->Dominates(pred)) { |
| if (FLAG_trace_optimization) { |
| OS::Print("Back edge B%"Pd" -> B%"Pd"\n", pred->block_id(), |
| block->block_id()); |
| } |
| FindLoop(pred, block, preorder_.length()); |
| loop_headers->Add(block); |
| } |
| } |
| } |
| } |
| |
| |
| void FlowGraph::Bailout(const char* reason) const { |
| const char* kFormat = "FlowGraph Bailout: %s %s"; |
| const char* function_name = parsed_function_.function().ToCString(); |
| intptr_t len = OS::SNPrint(NULL, 0, kFormat, function_name, reason) + 1; |
| char* chars = Isolate::Current()->current_zone()->Alloc<char>(len); |
| OS::SNPrint(chars, len, kFormat, function_name, reason); |
| const Error& error = Error::Handle( |
| LanguageError::New(String::Handle(String::New(chars)))); |
| Isolate::Current()->long_jump_base()->Jump(1, error); |
| } |
| |
| |
| // Helper to replace a predecessor block. For each successor of 'old_block', the |
| // predecessors will be reordered to preserve block-order sorting of the |
| // predecessors as well as the phis if the successor is a join. |
| void FlowGraph::ReplacePredecessor(BlockEntryInstr* old_block, |
| BlockEntryInstr* new_block) { |
| // Set the last instruction of the new block to that of the old block. |
| Instruction* last = old_block->last_instruction(); |
| new_block->set_last_instruction(last); |
| // For each successor, update the predecessors. |
| for (intptr_t sidx = 0; sidx < last->SuccessorCount(); ++sidx) { |
| // If the successor is a target, update its predecessor. |
| TargetEntryInstr* target = last->SuccessorAt(sidx)->AsTargetEntry(); |
| if (target != NULL) { |
| target->predecessor_ = new_block; |
| continue; |
| } |
| // If the successor is a join, update each predecessor and the phis. |
| JoinEntryInstr* join = last->SuccessorAt(sidx)->AsJoinEntry(); |
| ASSERT(join != NULL); |
| // Find the old predecessor index. |
| intptr_t old_index = join->IndexOfPredecessor(old_block); |
| intptr_t pred_count = join->PredecessorCount(); |
| ASSERT(old_index >= 0); |
| ASSERT(old_index < pred_count); |
| // Find the new predecessor index while reordering the predecessors. |
| intptr_t new_id = new_block->block_id(); |
| intptr_t new_index = old_index; |
| if (old_block->block_id() < new_id) { |
| // Search upwards, bubbling down intermediate predecessors. |
| for (; new_index < pred_count - 1; ++new_index) { |
| if (join->predecessors_[new_index + 1]->block_id() > new_id) break; |
| join->predecessors_[new_index] = join->predecessors_[new_index + 1]; |
| } |
| } else { |
| // Search downwards, bubbling up intermediate predecessors. |
| for (; new_index > 0; --new_index) { |
| if (join->predecessors_[new_index - 1]->block_id() < new_id) break; |
| join->predecessors_[new_index] = join->predecessors_[new_index - 1]; |
| } |
| } |
| join->predecessors_[new_index] = new_block; |
| // If the new and old predecessor index match there is nothing to update. |
| if ((join->phis() == NULL) || (old_index == new_index)) return; |
| // Otherwise, reorder the predecessor uses in each phi. |
| for (intptr_t i = 0; i < join->phis()->length(); ++i) { |
| PhiInstr* phi = (*join->phis())[i]; |
| if (phi == NULL) continue; |
| ASSERT(pred_count == phi->InputCount()); |
| // Save the predecessor use. |
| Value* pred_use = phi->InputAt(old_index); |
| // Move uses between old and new. |
| intptr_t step = (old_index < new_index) ? 1 : -1; |
| for (intptr_t use_idx = old_index; |
| use_idx != new_index; |
| use_idx += step) { |
| Value* use = phi->InputAt(use_idx + step); |
| phi->SetInputAt(use_idx, use); |
| use->set_use_index(use_idx); |
| } |
| // Write the predecessor use. |
| phi->SetInputAt(new_index, pred_use); |
| pred_use->set_use_index(new_index); |
| } |
| } |
| } |
| |
| |
| // Helper to sort a list of blocks. |
| static int LowestBlockIdFirst(BlockEntryInstr* const* a, |
| BlockEntryInstr* const* b) { |
| return (*a)->block_id() - (*b)->block_id(); |
| } |
| |
| |
| // Inline a flow graph at a call site. |
| // |
| // Assumes the callee graph was computed by BuildGraph with an inlining context |
| // and transformed to SSA with ComputeSSA with a correct virtual register |
| // number, and that the use lists have been correctly computed. |
| // |
| // After inlining the caller graph will correctly have adjusted the pre/post |
| // orders, the dominator tree and the use lists. |
| void FlowGraph::InlineCall(Definition* call, FlowGraph* callee_graph) { |
| ASSERT(call->previous() != NULL); |
| ASSERT(call->next() != NULL); |
| ASSERT(callee_graph->exits() != NULL); |
| ASSERT(callee_graph->graph_entry()->SuccessorCount() == 1); |
| ASSERT(callee_graph->max_block_id() > max_block_id()); |
| ASSERT(callee_graph->max_virtual_register_number() > |
| max_virtual_register_number()); |
| |
| // Adjust the max block id to the max block id of the callee graph. |
| max_block_id_ = callee_graph->max_block_id(); |
| |
| // Adjust the SSA temp index by the callee graph's index. |
| current_ssa_temp_index_ = callee_graph->max_virtual_register_number(); |
| |
| BlockEntryInstr* caller_entry = call->GetBlock(); |
| TargetEntryInstr* callee_entry = callee_graph->graph_entry()->normal_entry(); |
| ZoneGrowableArray<ReturnInstr*>* callee_exits = callee_graph->exits(); |
| |
| // Attach the outer environment on each instruction in the callee graph. |
| for (BlockIterator block_it = callee_graph->postorder_iterator(); |
| !block_it.Done(); |
| block_it.Advance()) { |
| for (ForwardInstructionIterator it(block_it.Current()); |
| !it.Done(); |
| it.Advance()) { |
| Instruction* instr = it.Current(); |
| // TODO(zerny): Avoid creating unnecessary environments. Note that some |
| // optimizations need deoptimization info for non-deoptable instructions, |
| // eg, LICM on GOTOs. |
| if (instr->env() != NULL) call->env()->DeepCopyToOuter(instr); |
| } |
| } |
| |
| // Insert the callee graph into the caller graph. |
| if (callee_exits->is_empty()) { |
| // TODO(zerny): Add support for non-local exits, such as throw. |
| UNREACHABLE(); |
| } else if (callee_exits->length() == 1) { |
| ReturnInstr* exit = (*callee_exits)[0]; |
| ASSERT(exit->previous() != NULL); |
| // For just one exit, replace the uses and remove the call from the graph. |
| call->ReplaceUsesWith(exit->value()->definition()); |
| call->previous()->LinkTo(callee_entry->next()); |
| exit->previous()->LinkTo(call->next()); |
| // In case of control flow, locally update the predecessors, phis and |
| // dominator tree. |
| // TODO(zerny): should we leave the dominator tree since we recompute it |
| // after a full inlining pass? |
| if (callee_graph->preorder().length() > 2) { |
| BlockEntryInstr* exit_block = exit->GetBlock(); |
| // Pictorially, the graph structure is: |
| // |
| // Bc : caller_entry Bi : callee_entry |
| // before_call inlined_head |
| // call ... other blocks ... |
| // after_call Be : exit_block |
| // inlined_foot |
| // And becomes: |
| // |
| // Bc : caller_entry |
| // before_call |
| // inlined_head |
| // ... other blocks ... |
| // Be : exit_block |
| // inlined_foot |
| // after_call |
| // |
| // For 'after_call', caller entry (Bc) is replaced by callee exit (Be). |
| ReplacePredecessor(caller_entry, exit_block); |
| // For 'inlined_head', callee entry (Bi) is replaced by caller entry (Bc). |
| ReplacePredecessor(callee_entry, caller_entry); |
| // The callee exit is now the immediate dominator of blocks whose |
| // immediate dominator was the caller entry. |
| ASSERT(exit_block->dominated_blocks().is_empty()); |
| for (intptr_t i = 0; i < caller_entry->dominated_blocks().length(); ++i) { |
| BlockEntryInstr* block = caller_entry->dominated_blocks()[i]; |
| block->set_dominator(exit_block); |
| exit_block->AddDominatedBlock(block); |
| } |
| // The caller entry is now the immediate dominator of blocks whose |
| // immediate dominator was the callee entry. |
| caller_entry->ClearDominatedBlocks(); |
| for (intptr_t i = 0; i < callee_entry->dominated_blocks().length(); ++i) { |
| BlockEntryInstr* block = callee_entry->dominated_blocks()[i]; |
| block->set_dominator(caller_entry); |
| caller_entry->AddDominatedBlock(block); |
| } |
| } |
| } else { |
| // Sort the list of exits by block id. |
| GrowableArray<BlockEntryInstr*> exits(callee_exits->length()); |
| for (intptr_t i = 0; i < callee_exits->length(); ++i) { |
| exits.Add((*callee_exits)[i]->GetBlock()); |
| } |
| exits.Sort(LowestBlockIdFirst); |
| // Create a join of the returns. |
| JoinEntryInstr* join = |
| new JoinEntryInstr(++max_block_id_, CatchClauseNode::kInvalidTryIndex); |
| for (intptr_t i = 0; i < exits.length(); ++i) { |
| ReturnInstr* exit_instr = exits[i]->last_instruction()->AsReturn(); |
| ASSERT(exit_instr != NULL); |
| exit_instr->previous()->Goto(join); |
| // Directly add the predecessors of the join in ascending block id order. |
| join->predecessors_.Add(exits[i]); |
| } |
| // If the call has uses, create a phi of the returns. |
| if ((call->input_use_list() != NULL) || |
| (call->env_use_list() != NULL)) { |
| // Environment count: length before call - argument count (+ return) |
| intptr_t env_count = call->env()->Length() - call->ArgumentCount(); |
| // Add a phi of the return values. |
| join->InsertPhi(env_count, env_count + 1); |
| PhiInstr* phi = join->phis()->Last(); |
| phi->set_ssa_temp_index(alloc_ssa_temp_index()); |
| phi->mark_alive(); |
| for (intptr_t i = 0; i < exits.length(); ++i) { |
| ReturnInstr* exit_instr = exits[i]->last_instruction()->AsReturn(); |
| ASSERT(exit_instr != NULL); |
| Value* use = exit_instr->value(); |
| phi->SetInputAt(i, use); |
| use->set_instruction(phi); |
| use->set_use_index(i); |
| } |
| // Replace uses of the call with the phi. |
| call->ReplaceUsesWith(phi); |
| } |
| // Remove the call from the graph. |
| call->previous()->LinkTo(callee_entry->next()); |
| join->LinkTo(call->next()); |
| // Replace the blocks after splitting (see comment in the len=1 case above). |
| ReplacePredecessor(caller_entry, join); |
| ReplacePredecessor(callee_entry, caller_entry); |
| // Update the last instruction pointers on each exit (ie, to the new goto). |
| for (intptr_t i = 0; i < exits.length(); ++i) { |
| exits[i]->set_last_instruction( |
| exits[i]->last_instruction()->previous()->next()); |
| } |
| // Mark that the dominator tree is invalid. |
| // TODO(zerny): Compute the dominator frontier locally. |
| invalid_dominator_tree_ = true; |
| } |
| } |
| |
| |
| void FlowGraph::RepairGraphAfterInlining() { |
| DiscoverBlocks(); |
| if (invalid_dominator_tree_) { |
| GrowableArray<BitVector*> dominance_frontier; |
| ComputeDominators(&dominance_frontier); |
| } |
| } |
| |
| |
| intptr_t FlowGraph::InstructionCount() const { |
| intptr_t size = 0; |
| // Iterate each block, skipping the graph entry. |
| for (intptr_t i = 1; i < preorder_.length(); ++i) { |
| for (ForwardInstructionIterator it(preorder_[i]); |
| !it.Done(); |
| it.Advance()) { |
| ++size; |
| } |
| } |
| return size; |
| } |
| |
| |
| } // namespace dart |