Stand-alone deferred block splitting. This continues 1256313003.
authormtrofin <mtrofin@chromium.org>
Thu, 6 Aug 2015 16:21:23 +0000 (09:21 -0700)
committerCommit bot <commit-bot@chromium.org>
Thu, 6 Aug 2015 16:21:38 +0000 (16:21 +0000)
BUG=

Review URL: https://codereview.chromium.org/1271703002

Cr-Commit-Position: refs/heads/master@{#30050}

src/compiler/instruction.h
src/compiler/move-optimizer.cc
src/compiler/pipeline.cc
src/compiler/preprocess-live-ranges.cc
src/compiler/register-allocator.cc
src/compiler/register-allocator.h
test/unittests/compiler/instruction-sequence-unittest.cc
test/unittests/compiler/instruction-sequence-unittest.h
test/unittests/compiler/register-allocator-unittest.cc

index a87ef7dc9c469f865919aeef34a79fbaacb5e033..304d7499d386a35361bc4a0b1da45f1a574c4603 100644 (file)
@@ -1085,6 +1085,9 @@ class InstructionSequence final : public ZoneObject {
   const_iterator begin() const { return instructions_.begin(); }
   const_iterator end() const { return instructions_.end(); }
   const InstructionDeque& instructions() const { return instructions_; }
+  int LastInstructionIndex() const {
+    return static_cast<int>(instructions().size()) - 1;
+  }
 
   Instruction* InstructionAt(int index) const {
     DCHECK(index >= 0);
index b869185e60ed2aed9dbc939878d1874e92e4eda6..7c2bbe06b874005e74b3260fbcb8275e28df9978 100644 (file)
@@ -59,6 +59,17 @@ void MoveOptimizer::Run() {
   }
   for (auto block : code()->instruction_blocks()) {
     if (block->PredecessorCount() <= 1) continue;
+    bool has_only_deferred = true;
+    for (RpoNumber pred_id : block->predecessors()) {
+      if (!code()->InstructionBlockAt(pred_id)->IsDeferred()) {
+        has_only_deferred = false;
+        break;
+      }
+    }
+    // This would pull down common moves. If the moves occur in deferred blocks,
+    // and the closest common successor is not deferred, we lose the
+    // optimization of just spilling/filling in deferred blocks.
+    if (has_only_deferred) continue;
     OptimizeMerge(block);
   }
   for (auto gap : to_finalize_) {
index f55b4d35125e2380bc1d94106c531ba548aa8c65..c1e2eb663e0a0d4fccfaf19eb00c42e7f356a4dd 100644 (file)
@@ -1337,13 +1337,9 @@ void Pipeline::AllocateRegisters(const RegisterConfiguration* config,
     Run<PreprocessLiveRangesPhase>();
   }
 
-  if (FLAG_turbo_greedy_regalloc) {
-    Run<AllocateGeneralRegistersPhase<GreedyAllocator>>();
-    Run<AllocateDoubleRegistersPhase<GreedyAllocator>>();
-  } else {
-    Run<AllocateGeneralRegistersPhase<LinearScanAllocator>>();
-    Run<AllocateDoubleRegistersPhase<LinearScanAllocator>>();
-  }
+  // TODO(mtrofin): re-enable greedy once we have bots for range preprocessing.
+  Run<AllocateGeneralRegistersPhase<LinearScanAllocator>>();
+  Run<AllocateDoubleRegistersPhase<LinearScanAllocator>>();
 
   if (FLAG_turbo_frame_elision) {
     Run<LocateSpillSlotsPhase>();
index 81283a898a0f778658dc4b74a980556fb1e5d418..fee3a3d98c7ace09a2c0e0d8d753ac20afd5a026 100644 (file)
@@ -16,13 +16,153 @@ namespace compiler {
   } while (false)
 
 
+namespace {
+
+LiveRange* Split(LiveRange* range, RegisterAllocationData* data,
+                 LifetimePosition pos) {
+  DCHECK(range->Start() < pos && pos < range->End());
+  DCHECK(pos.IsStart() || pos.IsGapPosition() ||
+         (data->code()
+              ->GetInstructionBlock(pos.ToInstructionIndex())
+              ->last_instruction_index() != pos.ToInstructionIndex()));
+  LiveRange* result = data->NewChildRangeFor(range);
+  range->SplitAt(pos, result, data->allocation_zone());
+  TRACE("Split range %d(v%d) @%d => %d.\n", range->id(),
+        range->TopLevel()->id(), pos.ToInstructionIndex(), result->id());
+  return result;
+}
+
+
+LifetimePosition GetSplitPositionForInstruction(const LiveRange* range,
+                                                int instruction_index) {
+  LifetimePosition ret = LifetimePosition::Invalid();
+
+  ret = LifetimePosition::GapFromInstructionIndex(instruction_index);
+  if (range->Start() >= ret || ret >= range->End()) {
+    return LifetimePosition::Invalid();
+  }
+  return ret;
+}
+
+
+LiveRange* SplitRangeAfterBlock(LiveRange* range, RegisterAllocationData* data,
+                                const InstructionBlock* block) {
+  const InstructionSequence* code = data->code();
+  int last_index = block->last_instruction_index();
+  int outside_index = static_cast<int>(code->instructions().size());
+  bool has_handler = false;
+  for (auto successor_id : block->successors()) {
+    const InstructionBlock* successor = code->InstructionBlockAt(successor_id);
+    if (successor->IsHandler()) {
+      has_handler = true;
+    }
+    outside_index = Min(outside_index, successor->first_instruction_index());
+  }
+  int split_at = has_handler ? outside_index : last_index;
+  LifetimePosition after_block =
+      GetSplitPositionForInstruction(range, split_at);
+
+  if (after_block.IsValid()) {
+    return Split(range, data, after_block);
+  }
+
+  return range;
+}
+
+
+int GetFirstInstructionIndex(const UseInterval* interval) {
+  int ret = interval->start().ToInstructionIndex();
+  if (!interval->start().IsGapPosition() && !interval->start().IsStart()) {
+    ++ret;
+  }
+  return ret;
+}
+
+
+bool DoesSubsequenceClobber(const InstructionSequence* code, int start,
+                            int end) {
+  for (int i = start; i <= end; ++i) {
+    if (code->InstructionAt(i)->IsCall()) return true;
+  }
+  return false;
+}
+
+
+void SplitRangeAtDeferredBlocksWithCalls(LiveRange* range,
+                                         RegisterAllocationData* data) {
+  DCHECK(!range->IsFixed());
+  DCHECK(!range->spilled());
+  if (range->TopLevel()->HasSpillOperand()) {
+    TRACE(
+        "Skipping deferred block analysis for live range %d because it has a "
+        "spill operand.\n",
+        range->TopLevel()->id());
+    return;
+  }
+
+  const InstructionSequence* code = data->code();
+  LiveRange* current_subrange = range;
+
+  UseInterval* interval = current_subrange->first_interval();
+
+  while (interval != nullptr) {
+    int first_index = GetFirstInstructionIndex(interval);
+    int last_index = interval->end().ToInstructionIndex();
+
+    if (last_index > code->LastInstructionIndex()) {
+      last_index = code->LastInstructionIndex();
+    }
+
+    interval = interval->next();
+
+    for (int index = first_index; index <= last_index;) {
+      const InstructionBlock* block = code->GetInstructionBlock(index);
+      int last_block_index = static_cast<int>(block->last_instruction_index());
+      int last_covered_index = Min(last_index, last_block_index);
+      int working_index = index;
+      index = block->last_instruction_index() + 1;
+
+      if (!block->IsDeferred() ||
+          !DoesSubsequenceClobber(code, working_index, last_covered_index)) {
+        continue;
+      }
+
+      TRACE("Deferred block B%d clobbers range %d(v%d).\n",
+            block->rpo_number().ToInt(), current_subrange->id(),
+            current_subrange->TopLevel()->id());
+      LifetimePosition block_start =
+          GetSplitPositionForInstruction(current_subrange, working_index);
+      LiveRange* block_and_after = nullptr;
+      if (block_start.IsValid()) {
+        block_and_after = Split(current_subrange, data, block_start);
+      } else {
+        block_and_after = current_subrange;
+      }
+      LiveRange* next = SplitRangeAfterBlock(block_and_after, data, block);
+      if (next != current_subrange) interval = next->first_interval();
+      current_subrange = next;
+      break;
+    }
+  }
+}
+}
+
+
 void PreprocessLiveRanges::PreprocessRanges() {
   SplitRangesAroundDeferredBlocks();
 }
 
 
-void PreprocessLiveRanges::SplitRangesAroundDeferredBlocks() {}
-
+void PreprocessLiveRanges::SplitRangesAroundDeferredBlocks() {
+  size_t live_range_count = data()->live_ranges().size();
+  for (size_t i = 0; i < live_range_count; i++) {
+    LiveRange* range = data()->live_ranges()[i];
+    if (range != nullptr && !range->IsEmpty() && !range->spilled() &&
+        !range->IsFixed() && !range->IsChild()) {
+      SplitRangeAtDeferredBlocksWithCalls(range, data());
+    }
+  }
+}
 
 }  // namespace compiler
 }  // namespace internal
index 7836970d84b4d75ce99ddece8c7f8d4d8314b082..666268aca0c73c091ffb59253b06805fb5cd8a90 100644 (file)
@@ -256,7 +256,8 @@ LiveRange::LiveRange(int id, MachineType machine_type)
       last_processed_use_(nullptr),
       current_hint_position_(nullptr),
       size_(kInvalidSize),
-      weight_(kInvalidWeight) {
+      weight_(kInvalidWeight),
+      spilled_in_deferred_block_(false) {
   DCHECK(AllocatedOperand::IsSupportedMachineType(machine_type));
   bits_ = SpillTypeField::encode(SpillType::kNoSpillType) |
           AssignedRegisterField::encode(kUnassignedRegister) |
@@ -319,12 +320,90 @@ void LiveRange::SpillAtDefinition(Zone* zone, int gap_index,
 }
 
 
+bool LiveRange::TryCommitSpillInDeferredBlock(
+    InstructionSequence* code, const InstructionOperand& spill_operand) {
+  DCHECK(!IsChild());
+
+  if (!FLAG_turbo_preprocess_ranges || IsEmpty() || HasNoSpillType() ||
+      spill_operand.IsConstant() || spill_operand.IsImmediate()) {
+    return false;
+  }
+
+  int count = 0;
+  for (const LiveRange* child = this; child != nullptr; child = child->next()) {
+    int first_instr = child->Start().ToInstructionIndex();
+
+    // If the range starts at instruction end, the first instruction index is
+    // the next one.
+    if (!child->Start().IsGapPosition() && !child->Start().IsStart()) {
+      ++first_instr;
+    }
+
+    // We only look at where the range starts. It doesn't matter where it ends:
+    // if it ends past this block, then either there is a phi there already,
+    // or ResolveControlFlow will adapt the last instruction gap of this block
+    // as if there were a phi. In either case, data flow will be correct.
+    const InstructionBlock* block = code->GetInstructionBlock(first_instr);
+
+    // If we have slot uses in a subrange, bail out, because we need the value
+    // on the stack before that use.
+    bool has_slot_use = child->NextSlotPosition(child->Start()) != nullptr;
+    if (!block->IsDeferred()) {
+      if (child->spilled() || has_slot_use) {
+        TRACE(
+            "Live Range %d must be spilled at definition: found a "
+            "slot-requiring non-deferred child range %d.\n",
+            TopLevel()->id(), child->id());
+        return false;
+      }
+    } else {
+      if (child->spilled() || has_slot_use) ++count;
+    }
+  }
+  if (count == 0) return false;
+
+  spill_start_index_ = -1;
+  spilled_in_deferred_block_ = true;
+
+  TRACE("Live Range %d will be spilled only in deferred blocks.\n", id());
+  // If we have ranges that aren't spilled but require the operand on the stack,
+  // make sure we insert the spill.
+  for (const LiveRange* child = this; child != nullptr; child = child->next()) {
+    if (!child->spilled() &&
+        child->NextSlotPosition(child->Start()) != nullptr) {
+      auto instr = code->InstructionAt(child->Start().ToInstructionIndex());
+      // Insert spill at the end to let live range connections happen at START.
+      auto move =
+          instr->GetOrCreateParallelMove(Instruction::END, code->zone());
+      InstructionOperand assigned = child->GetAssignedOperand();
+      if (TopLevel()->has_slot_use()) {
+        bool found = false;
+        for (auto move_op : *move) {
+          if (move_op->IsEliminated()) continue;
+          if (move_op->source().Equals(assigned) &&
+              move_op->destination().Equals(spill_operand)) {
+            found = true;
+            break;
+          }
+        }
+        if (found) continue;
+      }
+
+      move->AddMove(assigned, spill_operand);
+    }
+  }
+
+  return true;
+}
+
+
 void LiveRange::CommitSpillsAtDefinition(InstructionSequence* sequence,
                                          const InstructionOperand& op,
                                          bool might_be_duplicated) {
   DCHECK_IMPLIES(op.IsConstant(), spills_at_definition_ == nullptr);
   DCHECK(!IsChild());
   auto zone = sequence->zone();
+
   for (auto to_spill = spills_at_definition_; to_spill != nullptr;
        to_spill = to_spill->next) {
     auto instr = sequence->InstructionAt(to_spill->gap_index);
@@ -416,6 +495,16 @@ UsePosition* LiveRange::NextRegisterPosition(LifetimePosition start) const {
 }
 
 
+UsePosition* LiveRange::NextSlotPosition(LifetimePosition start) const {
+  for (UsePosition* pos = NextUsePosition(start); pos != nullptr;
+       pos = pos->next()) {
+    if (pos->type() != UsePositionType::kRequiresSlot) continue;
+    return pos;
+  }
+  return nullptr;
+}
+
+
 bool LiveRange::CanBeSpilled(LifetimePosition pos) const {
   // We cannot spill a live range that has a use requiring a register
   // at the current or the immediate next position.
@@ -1544,7 +1633,15 @@ void LiveRangeBuilder::ProcessInstructions(const InstructionBlock* block,
         int out_vreg = ConstantOperand::cast(output)->virtual_register();
         live->Remove(out_vreg);
       }
-      Define(curr_position, output);
+      if (block->IsHandler() && index == block_start) {
+        // The register defined here is blocked from gap start - it is the
+        // exception value.
+        // TODO(mtrofin): should we explore an explicit opcode for
+        // the first instruction in the handler?
+        Define(LifetimePosition::GapFromInstructionIndex(index), output);
+      } else {
+        Define(curr_position, output);
+      }
     }
 
     if (instr->ClobbersRegisters()) {
@@ -2532,9 +2629,25 @@ void OperandAssigner::CommitAssignment() {
       data()->GetPhiMapValueFor(range->id())->CommitAssignment(assigned);
     }
     if (!range->IsChild() && !spill_operand.IsInvalid()) {
-      range->CommitSpillsAtDefinition(
-          data()->code(), spill_operand,
-          range->has_slot_use() || range->spilled());
+      // If this top level range has a child spilled in a deferred block, we use
+      // the range and control flow connection mechanism instead of spilling at
+      // definition. Refer to the ConnectLiveRanges and ResolveControlFlow
+      // phases. Normally, when we spill at definition, we do not insert a
+      // connecting move when a successor child range is spilled - because the
+      // spilled range picks up its value from the slot which was assigned at
+      // definition. For ranges that are determined to spill only in deferred
+      // blocks, we let ConnectLiveRanges and ResolveControlFlow insert such
+      // moves between ranges. Because of how the ranges are split around
+      // deferred blocks, this amounts to spilling and filling inside such
+      // blocks.
+      if (!range->TryCommitSpillInDeferredBlock(data()->code(),
+                                                spill_operand)) {
+        // Spill at definition if the range isn't spilled only in deferred
+        // blocks.
+        range->CommitSpillsAtDefinition(
+            data()->code(), spill_operand,
+            range->has_slot_use() || range->spilled());
+      }
     }
   }
 }
@@ -2631,10 +2744,13 @@ void ReferenceMapPopulator::PopulateReferenceMaps() {
 
       // Check if the live range is spilled and the safe point is after
       // the spill position.
-      if (!spill_operand.IsInvalid() &&
-          safe_point >= range->spill_start_index()) {
+      int spill_index = range->IsSpilledOnlyInDeferredBlocks()
+                            ? cur->Start().ToInstructionIndex()
+                            : range->spill_start_index();
+
+      if (!spill_operand.IsInvalid() && safe_point >= spill_index) {
         TRACE("Pointer for range %d (spilled at %d) at safe point %d\n",
-              range->id(), range->spill_start_index(), safe_point);
+              range->id(), spill_index, safe_point);
         map->RecordReference(AllocatedOperand::cast(spill_operand));
       }
 
@@ -2831,7 +2947,8 @@ void LiveRangeConnector::ResolveControlFlow(Zone* local_zone) {
         const auto* pred_block = code()->InstructionBlockAt(pred);
         array->Find(block, pred_block, &result);
         if (result.cur_cover_ == result.pred_cover_ ||
-            result.cur_cover_->spilled())
+            (!result.cur_cover_->TopLevel()->IsSpilledOnlyInDeferredBlocks() &&
+             result.cur_cover_->spilled()))
           continue;
         auto pred_op = result.pred_cover_->GetAssignedOperand();
         auto cur_op = result.cur_cover_->GetAssignedOperand();
@@ -2870,12 +2987,13 @@ void LiveRangeConnector::ConnectRanges(Zone* local_zone) {
   DelayedInsertionMap delayed_insertion_map(local_zone);
   for (auto first_range : data()->live_ranges()) {
     if (first_range == nullptr || first_range->IsChild()) continue;
+    bool connect_spilled = first_range->IsSpilledOnlyInDeferredBlocks();
     for (auto second_range = first_range->next(); second_range != nullptr;
          first_range = second_range, second_range = second_range->next()) {
       auto pos = second_range->Start();
       // Add gap move if the two live ranges touch and there is no block
       // boundary.
-      if (second_range->spilled()) continue;
+      if (!connect_spilled && second_range->spilled()) continue;
       if (first_range->End() != pos) continue;
       if (data()->IsBlockBoundary(pos) &&
           !CanEagerlyResolveControlFlow(GetInstructionBlock(code(), pos))) {
index 1a4f91b1695102c13562b5d95284324216f34a5c..2e63d36e122dd11a8f5d4b9a869aef19b149a3cf 100644 (file)
@@ -334,6 +334,9 @@ class LiveRange final : public ZoneObject {
   // range and which follows both start and last processed use position
   UsePosition* NextRegisterPosition(LifetimePosition start) const;
 
+  // Returns the first use position requiring stack slot, or nullptr.
+  UsePosition* NextSlotPosition(LifetimePosition start) const;
+
   // Returns use position for which register is beneficial in this live
   // range and which follows both start and last processed use position
   UsePosition* NextUsePositionRegisterIsBeneficial(
@@ -401,6 +404,16 @@ class LiveRange final : public ZoneObject {
   void CommitSpillsAtDefinition(InstructionSequence* sequence,
                                 const InstructionOperand& operand,
                                 bool might_be_duplicated);
+  // This must be applied on top level ranges.
+  // If all the children of this range are spilled in deferred blocks, and if
+  // for any non-spilled child with a use position requiring a slot, that range
+  // is contained in a deferred block, mark the range as
+  // IsSpilledOnlyInDeferredBlocks, so that we avoid spilling at definition,
+  // and instead let the LiveRangeConnector perform the spills within the
+  // deferred blocks. If so, we insert here spills for non-spilled ranges
+  // with slot use positions.
+  bool TryCommitSpillInDeferredBlock(InstructionSequence* code,
+                                     const InstructionOperand& spill_operand);
 
   void SetSpillStartIndex(int start) {
     spill_start_index_ = Min(start, spill_start_index_);
@@ -437,6 +450,10 @@ class LiveRange final : public ZoneObject {
   float weight() const { return weight_; }
   void set_weight(float weight) { weight_ = weight; }
 
+  bool IsSpilledOnlyInDeferredBlocks() const {
+    return spilled_in_deferred_block_;
+  }
+
   static const int kInvalidSize = -1;
   static const float kInvalidWeight;
   static const float kMaxWeight;
@@ -489,6 +506,10 @@ class LiveRange final : public ZoneObject {
   // greedy: a metric for resolving conflicts between ranges with an assigned
   // register and ranges that intersect them and need a register.
   float weight_;
+
+  // TODO(mtrofin): generalize spilling after definition, currently specialized
+  // just for spill in a single deferred block.
+  bool spilled_in_deferred_block_;
   DISALLOW_COPY_AND_ASSIGN(LiveRange);
 };
 
@@ -922,14 +943,24 @@ class ReferenceMapPopulator final : public ZoneObject {
 };
 
 
+// Insert moves of the form
+//
+//          Operand(child_(k+1)) = Operand(child_k)
+//
+// where child_k and child_(k+1) are consecutive children of a range (so
+// child_k->next() == child_(k+1)), and Operand(...) refers to the
+// assigned operand, be it a register or a slot.
 class LiveRangeConnector final : public ZoneObject {
  public:
   explicit LiveRangeConnector(RegisterAllocationData* data);
 
-  // Phase 8: reconnect split ranges with moves.
+  // Phase 8: reconnect split ranges with moves, when the control flow
+  // between the ranges is trivial (no branches).
   void ConnectRanges(Zone* local_zone);
 
-  // Phase 9: insert moves to connect ranges across basic blocks.
+  // Phase 9: insert moves to connect ranges across basic blocks, when the
+  // control flow between them cannot be trivially resolved, such as joining
+  // branches.
   void ResolveControlFlow(Zone* local_zone);
 
  private:
@@ -938,6 +969,7 @@ class LiveRangeConnector final : public ZoneObject {
   Zone* code_zone() const { return code()->zone(); }
 
   bool CanEagerlyResolveControlFlow(const InstructionBlock* block) const;
+
   void ResolveControlFlow(const InstructionBlock* block,
                           const InstructionOperand& cur_op,
                           const InstructionBlock* pred,
index 1ff441f746c3de3670450c4fdfd3fc9110f9da0d..65a7f299c5565703e5f07022facef32546c2f512 100644 (file)
@@ -93,9 +93,9 @@ void InstructionSequenceTest::EndLoop() {
 }
 
 
-void InstructionSequenceTest::StartBlock() {
+void InstructionSequenceTest::StartBlock(bool deferred) {
   block_returns_ = false;
-  NewBlock();
+  NewBlock(deferred);
 }
 
 
@@ -408,7 +408,7 @@ InstructionOperand InstructionSequenceTest::ConvertOutputOp(VReg vreg,
 }
 
 
-InstructionBlock* InstructionSequenceTest::NewBlock() {
+InstructionBlock* InstructionSequenceTest::NewBlock(bool deferred) {
   CHECK(current_block_ == nullptr);
   Rpo rpo = Rpo::FromInt(static_cast<int>(instruction_blocks_.size()));
   Rpo loop_header = Rpo::Invalid();
@@ -430,7 +430,7 @@ InstructionBlock* InstructionSequenceTest::NewBlock() {
   }
   // Construct instruction block.
   auto instruction_block = new (zone())
-      InstructionBlock(zone(), rpo, loop_header, loop_end, false, false);
+      InstructionBlock(zone(), rpo, loop_header, loop_end, deferred, false);
   instruction_blocks_.push_back(instruction_block);
   current_block_ = instruction_block;
   sequence()->StartBlock(rpo);
index eaab0d32429deb6ad60a6fc17ea60b9339924b0e..54317ede21c6107ac04977456012a33a3c98fc35 100644 (file)
@@ -126,7 +126,7 @@ class InstructionSequenceTest : public TestWithIsolateAndZone {
 
   void StartLoop(int loop_blocks);
   void EndLoop();
-  void StartBlock();
+  void StartBlock(bool deferred = false);
   Instruction* EndBlock(BlockCompletion completion = FallThrough());
 
   TestOperand Imm(int32_t imm = 0);
@@ -203,7 +203,7 @@ class InstructionSequenceTest : public TestWithIsolateAndZone {
   InstructionOperand* ConvertInputs(size_t input_size, TestOperand* inputs);
   InstructionOperand ConvertInputOp(TestOperand op);
   InstructionOperand ConvertOutputOp(VReg vreg, TestOperand op);
-  InstructionBlock* NewBlock();
+  InstructionBlock* NewBlock(bool deferred = false);
   void WireBlock(size_t block_offset, int jump_offset);
 
   Instruction* Emit(InstructionCode code, size_t outputs_size = 0,
index 873b4ecd2aca2a240c83266058f15c65e7965e41..23a118b6ad157783b13d7fe85cc3fd7613adabde 100644 (file)
@@ -9,6 +9,75 @@ namespace v8 {
 namespace internal {
 namespace compiler {
 
+
+namespace {
+
+// We can't just use the size of the moves collection, because of
+// redundant moves which need to be discounted.
+int GetMoveCount(const ParallelMove& moves) {
+  int move_count = 0;
+  for (auto move : moves) {
+    if (move->IsEliminated() || move->IsRedundant()) continue;
+    ++move_count;
+  }
+  return move_count;
+}
+
+
+bool AreOperandsOfSameType(
+    const AllocatedOperand& op,
+    const InstructionSequenceTest::TestOperand& test_op) {
+  bool test_op_is_reg =
+      (test_op.type_ ==
+           InstructionSequenceTest::TestOperandType::kFixedRegister ||
+       test_op.type_ == InstructionSequenceTest::TestOperandType::kRegister);
+
+  return (op.IsRegister() && test_op_is_reg) ||
+         (op.IsStackSlot() && !test_op_is_reg);
+}
+
+
+bool AllocatedOperandMatches(
+    const AllocatedOperand& op,
+    const InstructionSequenceTest::TestOperand& test_op) {
+  return AreOperandsOfSameType(op, test_op) &&
+         (op.index() == test_op.value_ ||
+          test_op.value_ == InstructionSequenceTest::kNoValue);
+}
+
+
+int GetParallelMoveCount(int instr_index, Instruction::GapPosition gap_pos,
+                         const InstructionSequence* sequence) {
+  const ParallelMove* moves =
+      sequence->InstructionAt(instr_index)->GetParallelMove(gap_pos);
+  if (moves == nullptr) return 0;
+  return GetMoveCount(*moves);
+}
+
+
+bool IsParallelMovePresent(int instr_index, Instruction::GapPosition gap_pos,
+                           const InstructionSequence* sequence,
+                           const InstructionSequenceTest::TestOperand& src,
+                           const InstructionSequenceTest::TestOperand& dest) {
+  const ParallelMove* moves =
+      sequence->InstructionAt(instr_index)->GetParallelMove(gap_pos);
+  EXPECT_NE(nullptr, moves);
+
+  bool found_match = false;
+  for (auto move : *moves) {
+    if (move->IsEliminated() || move->IsRedundant()) continue;
+    if (AllocatedOperandMatches(AllocatedOperand::cast(move->source()), src) &&
+        AllocatedOperandMatches(AllocatedOperand::cast(move->destination()),
+                                dest)) {
+      found_match = true;
+      break;
+    }
+  }
+  return found_match;
+}
+}
+
+
 class RegisterAllocatorTest : public InstructionSequenceTest {
  public:
   void Allocate() {
@@ -492,6 +561,144 @@ TEST_F(RegisterAllocatorTest, RegressionLoadConstantBeforeSpill) {
 }
 
 
+TEST_F(RegisterAllocatorTest, DiamondWithCallFirstBlock) {
+  StartBlock();
+  auto x = EmitOI(Reg(0));
+  EndBlock(Branch(Reg(x), 1, 2));
+
+  StartBlock();
+  EmitCall(Slot(-1));
+  auto occupy = EmitOI(Reg(0));
+  EndBlock(Jump(2));
+
+  StartBlock();
+  EndBlock(FallThrough());
+
+  StartBlock();
+  Use(occupy);
+  Return(Reg(x));
+  EndBlock();
+  Allocate();
+}
+
+
+TEST_F(RegisterAllocatorTest, DiamondWithCallSecondBlock) {
+  StartBlock();
+  auto x = EmitOI(Reg(0));
+  EndBlock(Branch(Reg(x), 1, 2));
+
+  StartBlock();
+  EndBlock(Jump(2));
+
+  StartBlock();
+  EmitCall(Slot(-1));
+  auto occupy = EmitOI(Reg(0));
+  EndBlock(FallThrough());
+
+  StartBlock();
+  Use(occupy);
+  Return(Reg(x));
+  EndBlock();
+  Allocate();
+}
+
+
+TEST_F(RegisterAllocatorTest, SingleDeferredBlockSpill) {
+  StartBlock();  // B0
+  auto var = EmitOI(Reg(0));
+  EndBlock(Branch(Reg(var), 1, 2));
+
+  StartBlock();  // B1
+  EndBlock(Jump(2));
+
+  StartBlock(true);  // B2
+  EmitCall(Slot(-1), Slot(var));
+  EndBlock();
+
+  StartBlock();  // B3
+  EmitNop();
+  EndBlock();
+
+  StartBlock();  // B4
+  Return(Reg(var, 0));
+  EndBlock();
+
+  Allocate();
+
+  const int var_def_index = 1;
+  const int call_index = 3;
+  int expect_no_moves =
+      FLAG_turbo_preprocess_ranges ? var_def_index : call_index;
+  int expect_spill_move =
+      FLAG_turbo_preprocess_ranges ? call_index : var_def_index;
+
+  // We should have no parallel moves at the "expect_no_moves" position.
+  EXPECT_EQ(
+      0, GetParallelMoveCount(expect_no_moves, Instruction::START, sequence()));
+
+  // The spill should be performed at the position expect_spill_move.
+  EXPECT_TRUE(IsParallelMovePresent(expect_spill_move, Instruction::START,
+                                    sequence(), Reg(0), Slot(0)));
+}
+
+
+TEST_F(RegisterAllocatorTest, MultipleDeferredBlockSpills) {
+  if (!FLAG_turbo_preprocess_ranges) return;
+
+  StartBlock();  // B0
+  auto var1 = EmitOI(Reg(0));
+  auto var2 = EmitOI(Reg(1));
+  auto var3 = EmitOI(Reg(2));
+  EndBlock(Branch(Reg(var1, 0), 1, 2));
+
+  StartBlock(true);  // B1
+  EmitCall(Slot(-2), Slot(var1));
+  EndBlock(Jump(2));
+
+  StartBlock(true);  // B2
+  EmitCall(Slot(-1), Slot(var2));
+  EndBlock();
+
+  StartBlock();  // B3
+  EmitNop();
+  EndBlock();
+
+  StartBlock();  // B4
+  Return(Reg(var3, 2));
+  EndBlock();
+
+  const int def_of_v2 = 3;
+  const int call_in_b1 = 4;
+  const int call_in_b2 = 6;
+  const int end_of_b1 = 5;
+  const int end_of_b2 = 7;
+  const int start_of_b3 = 8;
+
+  Allocate();
+  // TODO(mtrofin): at the moment, the linear allocator spills var1 and var2,
+  // so only var3 is spilled in deferred blocks. Greedy avoids spilling 1&2.
+  // Expand the test once greedy is back online with this facility.
+  const int var3_reg = 2;
+  const int var3_slot = 2;
+
+  EXPECT_FALSE(IsParallelMovePresent(def_of_v2, Instruction::START, sequence(),
+                                     Reg(var3_reg), Slot()));
+  EXPECT_TRUE(IsParallelMovePresent(call_in_b1, Instruction::START, sequence(),
+                                    Reg(var3_reg), Slot(var3_slot)));
+  EXPECT_TRUE(IsParallelMovePresent(end_of_b1, Instruction::START, sequence(),
+                                    Slot(var3_slot), Reg()));
+
+  EXPECT_TRUE(IsParallelMovePresent(call_in_b2, Instruction::START, sequence(),
+                                    Reg(var3_reg), Slot(var3_slot)));
+  EXPECT_TRUE(IsParallelMovePresent(end_of_b2, Instruction::START, sequence(),
+                                    Slot(var3_slot), Reg()));
+
+
+  EXPECT_EQ(0,
+            GetParallelMoveCount(start_of_b3, Instruction::START, sequence()));
+}
+
+
 namespace {
 
 enum class ParameterType { kFixedSlot, kSlot, kRegister, kFixedRegister };