Index: runtime/vm/flow_graph_compiler.cc |
diff --git a/runtime/vm/flow_graph_compiler.cc b/runtime/vm/flow_graph_compiler.cc |
index ab31e943ccd049516ce48b17aa8bec12cbfc41d5..2331a9bd2ceed9542784dee8093ef3529e7f7eb8 100644 |
--- a/runtime/vm/flow_graph_compiler.cc |
+++ b/runtime/vm/flow_graph_compiler.cc |
@@ -33,6 +33,7 @@ FlowGraphCompiler::FlowGraphCompiler( |
const ParsedFunction& parsed_function, |
const GrowableArray<BlockEntryInstr*>& block_order, |
bool is_optimizing, |
+ bool is_ssa, |
bool is_leaf) |
: assembler_(assembler), |
parsed_function_(parsed_function), |
@@ -44,13 +45,16 @@ FlowGraphCompiler::FlowGraphCompiler( |
block_info_(block_order.length()), |
deopt_stubs_(), |
is_optimizing_(is_optimizing), |
+ is_ssa_(is_ssa), |
is_dart_leaf_(is_leaf), |
bool_true_(Bool::ZoneHandle(Bool::True())), |
bool_false_(Bool::ZoneHandle(Bool::False())), |
double_class_(Class::ZoneHandle( |
Isolate::Current()->object_store()->double_class())), |
- frame_register_allocator_(this, is_optimizing) { |
+ frame_register_allocator_(this, is_optimizing, is_ssa), |
+ parallel_move_resolver_(this) { |
ASSERT(assembler != NULL); |
+ ASSERT(is_optimizing_ || !is_ssa_); |
} |
@@ -101,9 +105,14 @@ void FlowGraphCompiler::VisitBlocks() { |
for (ForwardInstructionIterator it(entry); !it.Done(); it.Advance()) { |
instr = it.Current(); |
if (FLAG_code_comments) EmitComment(instr); |
- ASSERT(instr->locs() != NULL); |
- EmitInstructionPrologue(instr); |
- instr->EmitNativeCode(this); |
+ if (instr->IsParallelMove()) { |
+ parallel_move_resolver_.EmitNativeCode(instr->AsParallelMove()); |
+ } else { |
+ ASSERT(instr->locs() != NULL); |
+ EmitInstructionPrologue(instr); |
+ pending_deoptimization_env_ = instr->env(); |
+ instr->EmitNativeCode(this); |
+ } |
} |
if (instr->next() != NULL) { |
BlockEntryInstr* successor = instr->next()->AsBlockEntry(); |
@@ -189,10 +198,14 @@ Label* FlowGraphCompiler::AddDeoptStub(intptr_t deopt_id, |
Register reg3) { |
DeoptimizationStub* stub = |
new DeoptimizationStub(deopt_id, deopt_token_pos, try_index, reason); |
- frame_register_allocator()->SpillInDeoptStub(stub); |
- if (reg1 != kNoRegister) stub->Push(reg1); |
- if (reg2 != kNoRegister) stub->Push(reg2); |
- if (reg3 != kNoRegister) stub->Push(reg3); |
+ if (pending_deoptimization_env_ == NULL) { |
+ frame_register_allocator()->SpillInDeoptStub(stub); |
+ if (reg1 != kNoRegister) stub->Push(reg1); |
+ if (reg2 != kNoRegister) stub->Push(reg2); |
+ if (reg3 != kNoRegister) stub->Push(reg3); |
+ } else { |
+ stub->set_deoptimization_env(pending_deoptimization_env_); |
+ } |
deopt_stubs_.Add(stub); |
return stub->entry_label(); |
} |
@@ -476,6 +489,8 @@ void FrameRegisterAllocator::SpillRegister(Register reg) { |
void FrameRegisterAllocator::AllocateRegisters(Instruction* instr) { |
+ if (is_ssa_) return; |
+ |
LocationSummary* locs = instr->locs(); |
bool blocked_registers[kNumberOfCpuRegisters]; |
@@ -492,7 +507,7 @@ void FrameRegisterAllocator::AllocateRegisters(Instruction* instr) { |
// Mark all fixed input, temp and output registers as used. |
for (intptr_t i = 0; i < locs->input_count(); i++) { |
Location loc = locs->in(i); |
- if (loc.kind() == Location::kRegister) { |
+ if (loc.IsRegister()) { |
ASSERT(!blocked_registers[loc.reg()]); |
blocked_registers[loc.reg()] = true; |
if (registers_[loc.reg()] != NULL) { |
@@ -508,14 +523,14 @@ void FrameRegisterAllocator::AllocateRegisters(Instruction* instr) { |
for (intptr_t i = 0; i < locs->temp_count(); i++) { |
Location loc = locs->temp(i); |
- if (loc.kind() == Location::kRegister) { |
+ if (loc.IsRegister()) { |
ASSERT(!blocked_registers[loc.reg()]); |
blocked_registers[loc.reg()] = true; |
blocked_temp_registers[loc.reg()] = true; |
} |
} |
- if (locs->out().kind() == Location::kRegister) { |
+ if (locs->out().IsRegister()) { |
// Fixed output registers are allowed to overlap with |
// temps and inputs. |
blocked_registers[locs->out().reg()] = true; |
@@ -533,9 +548,9 @@ void FrameRegisterAllocator::AllocateRegisters(Instruction* instr) { |
for (intptr_t i = locs->input_count() - 1; i >= 0; i--) { |
Location loc = locs->in(i); |
Register reg = kNoRegister; |
- if (loc.kind() == Location::kRegister) { |
+ if (loc.IsRegister()) { |
reg = loc.reg(); |
- } else if (loc.kind() == Location::kUnallocated) { |
+ } else if (loc.IsUnallocated()) { |
ASSERT(loc.policy() == Location::kRequiresRegister); |
if ((stack_.length() > 0) && !blocked_temp_registers[stack_.Last()]) { |
reg = stack_.Last(); |
@@ -558,7 +573,7 @@ void FrameRegisterAllocator::AllocateRegisters(Instruction* instr) { |
// Allocate all unallocated temp locations. |
for (intptr_t i = 0; i < locs->temp_count(); i++) { |
Location loc = locs->temp(i); |
- if (loc.kind() == Location::kUnallocated) { |
+ if (loc.IsUnallocated()) { |
ASSERT(loc.policy() == Location::kRequiresRegister); |
loc = Location::RegisterLocation( |
AllocateFreeRegister(blocked_registers)); |
@@ -568,7 +583,7 @@ void FrameRegisterAllocator::AllocateRegisters(Instruction* instr) { |
} |
Location result_location = locs->out(); |
- if (result_location.kind() == Location::kUnallocated) { |
+ if (result_location.IsUnallocated()) { |
switch (result_location.policy()) { |
case Location::kRequiresRegister: |
result_location = Location::RegisterLocation( |
@@ -581,13 +596,15 @@ void FrameRegisterAllocator::AllocateRegisters(Instruction* instr) { |
locs->set_out(result_location); |
} |
- if (result_location.kind() == Location::kRegister) { |
+ if (result_location.IsRegister()) { |
SpillRegister(result_location.reg()); |
} |
} |
void FrameRegisterAllocator::Pop(Register dst, Value* val) { |
+ if (is_ssa_) return; |
+ |
if (stack_.length() > 0) { |
ASSERT(keep_values_in_registers_); |
Register src = stack_.Last(); |
@@ -602,6 +619,8 @@ void FrameRegisterAllocator::Pop(Register dst, Value* val) { |
void FrameRegisterAllocator::Push(Register reg, BindInstr* val) { |
+ if (is_ssa_) return; |
+ |
ASSERT(registers_[reg] == NULL); |
if (keep_values_in_registers_) { |
registers_[reg] = val; |
@@ -613,6 +632,8 @@ void FrameRegisterAllocator::Push(Register reg, BindInstr* val) { |
void FrameRegisterAllocator::Spill() { |
+ if (is_ssa_) return; |
+ |
for (int i = 0; i < stack_.length(); i++) { |
Register r = stack_[i]; |
registers_[r] = NULL; |
@@ -623,10 +644,120 @@ void FrameRegisterAllocator::Spill() { |
void FrameRegisterAllocator::SpillInDeoptStub(DeoptimizationStub* stub) { |
+ if (is_ssa_) return; |
+ |
for (int i = 0; i < stack_.length(); i++) { |
stub->Push(stack_[i]); |
} |
} |
+ParallelMoveResolver::ParallelMoveResolver(FlowGraphCompiler* compiler) |
+ : compiler_(compiler), moves_(32) {} |
+ |
+ |
+void ParallelMoveResolver::EmitNativeCode(ParallelMoveInstr* parallel_move) { |
+ ASSERT(moves_.is_empty()); |
+ // Build up a worklist of moves. |
+ BuildInitialMoveList(parallel_move); |
+ |
+ for (int i = 0; i < moves_.length(); ++i) { |
+ MoveOperands move = moves_[i]; |
+ // Skip constants to perform them last. They don't block other moves |
+ // and skipping such moves with register destinations keeps those |
+ // registers free for the whole algorithm. |
+ if (!move.IsEliminated() && !move.src().IsConstant()) PerformMove(i); |
+ } |
+ |
+ // Perform the moves with constant sources. |
+ for (int i = 0; i < moves_.length(); ++i) { |
+ if (!moves_[i].IsEliminated()) { |
+ ASSERT(moves_[i].src().IsConstant()); |
+ EmitMove(i); |
+ } |
+ } |
+ |
+ moves_.Clear(); |
+} |
+ |
+ |
+void ParallelMoveResolver::BuildInitialMoveList( |
+ ParallelMoveInstr* parallel_move) { |
+ // Perform a linear sweep of the moves to add them to the initial list of |
+ // moves to perform, ignoring any move that is redundant (the source is |
+ // the same as the destination, the destination is ignored and |
+ // unallocated, or the move was already eliminated). |
+ const GrowableArray<MoveOperands>& moves = parallel_move->moves(); |
+ for (int i = 0; i < moves.length(); ++i) { |
+ MoveOperands move = moves[i]; |
+ if (!move.IsRedundant()) moves_.Add(move); |
+ } |
+} |
+ |
+ |
+void ParallelMoveResolver::PerformMove(int index) { |
+ // Each call to this function performs a move and deletes it from the move |
+ // graph. We first recursively perform any move blocking this one. We |
+ // mark a move as "pending" on entry to PerformMove in order to detect |
+ // cycles in the move graph. We use operand swaps to resolve cycles, |
+ // which means that a call to PerformMove could change any source operand |
+ // in the move graph. |
+ |
+ ASSERT(!moves_[index].IsPending()); |
+ ASSERT(!moves_[index].IsRedundant()); |
+ |
+ // Clear this move's destination to indicate a pending move. The actual |
+ // destination is saved in a stack-allocated local. Recursion may allow |
+ // multiple moves to be pending. |
+ ASSERT(!moves_[index].src().IsInvalid()); |
+ Location destination = moves_[index].MarkPending(); |
+ |
+ // Perform a depth-first traversal of the move graph to resolve |
+ // dependencies. Any unperformed, unpending move with a source the same |
+ // as this one's destination blocks this one so recursively perform all |
+ // such moves. |
+ for (int i = 0; i < moves_.length(); ++i) { |
+ MoveOperands other_move = moves_[i]; |
+ if (other_move.Blocks(destination) && !other_move.IsPending()) { |
+ // Though PerformMove can change any source operand in the move graph, |
+ // this call cannot create a blocking move via a swap (this loop does |
+ // not miss any). Assume there is a non-blocking move with source A |
+ // and this move is blocked on source B and there is a swap of A and |
+ // B. Then A and B must be involved in the same cycle (or they would |
+ // not be swapped). Since this move's destination is B and there is |
+ // only a single incoming edge to an operand, this move must also be |
+ // involved in the same cycle. In that case, the blocking move will |
+ // be created but will be "pending" when we return from PerformMove. |
+ PerformMove(i); |
+ } |
+ } |
+ |
+ // We are about to resolve this move and don't need it marked as |
+ // pending, so restore its destination. |
+ moves_[index].ClearPending(destination); |
+ |
+ // This move's source may have changed due to swaps to resolve cycles and |
+ // so it may now be the last move in the cycle. If so remove it. |
+ if (moves_[index].src().Equals(destination)) { |
+ moves_[index].Eliminate(); |
+ return; |
+ } |
+ |
+ // The move may be blocked on a (at most one) pending move, in which case |
+ // we have a cycle. Search for such a blocking move and perform a swap to |
+ // resolve it. |
+ for (int i = 0; i < moves_.length(); ++i) { |
+ MoveOperands other_move = moves_[i]; |
+ if (other_move.Blocks(destination)) { |
+ ASSERT(other_move.IsPending()); |
+ EmitSwap(index); |
+ return; |
+ } |
+ } |
+ |
+ // This move is not blocked. |
+ EmitMove(index); |
+} |
+ |
+ |
} // namespace dart |