OSDN Git Service

Fix a bug in the register allocator.
authorNicolas Geoffray <ngeoffray@google.com>
Thu, 15 Jan 2015 15:37:37 +0000 (15:37 +0000)
committerNicolas Geoffray <ngeoffray@google.com>
Fri, 16 Jan 2015 08:37:59 +0000 (08:37 +0000)
When allocating a register blocked by existing intervals,
we need to split inactive intervals at the end of their
lifetime hole, and not at the next intersection. Otherwise,
the allocation for following intervals will not see
that a register is being used by the split interval.

Change-Id: I40cc79dde541c07392a7cf4c6f0b291dd1ce1819

compiler/optimizing/nodes.h
compiler/optimizing/optimizing_unit_test.h
compiler/optimizing/register_allocator.cc
compiler/optimizing/register_allocator.h
compiler/optimizing/register_allocator_test.cc
compiler/optimizing/ssa_liveness_analysis.h

index b98bc70..02b80d7 100644 (file)
@@ -2775,10 +2775,16 @@ class HParallelMove : public HTemplateInstruction<0> {
       : HTemplateInstruction(SideEffects::None()), moves_(arena, kDefaultNumberOfMoves) {}
 
   void AddMove(MoveOperands* move) {
-    if (kIsDebugBuild && move->GetInstruction() != nullptr) {
+    if (kIsDebugBuild) {
+      if (move->GetInstruction() != nullptr) {
+        for (size_t i = 0, e = moves_.Size(); i < e; ++i) {
+          DCHECK_NE(moves_.Get(i)->GetInstruction(), move->GetInstruction())
+            << "Doing parallel moves for the same instruction.";
+        }
+      }
       for (size_t i = 0, e = moves_.Size(); i < e; ++i) {
-        DCHECK_NE(moves_.Get(i)->GetInstruction(), move->GetInstruction())
-          << "Doing parallel moves for the same instruction.";
+        DCHECK(!move->GetDestination().Contains(moves_.Get(i)->GetDestination()))
+            << "Same destination for two moves in a parallel move.";
       }
     }
     moves_.Add(move);
index 04b5634..b3eb1e2 100644 (file)
@@ -45,8 +45,12 @@ namespace art {
 LiveInterval* BuildInterval(const size_t ranges[][2],
                             size_t number_of_ranges,
                             ArenaAllocator* allocator,
-                            int reg = -1) {
-  LiveInterval* interval = LiveInterval::MakeInterval(allocator, Primitive::kPrimInt);
+                            int reg = -1,
+                            HInstruction* defined_by = nullptr) {
+  LiveInterval* interval = LiveInterval::MakeInterval(allocator, Primitive::kPrimInt, defined_by);
+  if (defined_by != nullptr) {
+    defined_by->SetLiveInterval(interval);
+  }
   for (size_t i = number_of_ranges; i > 0; --i) {
     interval->AddRange(ranges[i - 1][0], ranges[i - 1][1]);
   }
index 1efc52b..26c1e3f 100644 (file)
@@ -855,7 +855,9 @@ bool RegisterAllocator::AllocateBlockedReg(LiveInterval* current) {
         DCHECK(!active->IsFixed());
         LiveInterval* split = Split(active, current->GetStart());
         active_.DeleteAt(i);
-        handled_.Add(active);
+        if (split != active) {
+          handled_.Add(active);
+        }
         AddSorted(unhandled_, split);
         break;
       }
@@ -876,9 +878,14 @@ bool RegisterAllocator::AllocateBlockedReg(LiveInterval* current) {
         if (next_intersection != kNoLifetime) {
           if (inactive->IsFixed()) {
             LiveInterval* split = Split(current, next_intersection);
+            DCHECK_NE(split, current);
             AddSorted(unhandled_, split);
           } else {
-            LiveInterval* split = Split(inactive, next_intersection);
+            // Split at the start of `current`, which will lead to splitting
+            // at the end of the lifetime hole of `inactive`.
+            LiveInterval* split = Split(inactive, current->GetStart());
+            // If it's inactive, it must start before the current interval.
+            DCHECK_NE(split, inactive);
             inactive_.DeleteAt(i);
             --i;
             --e;
index c152a8b..0a0c6e6 100644 (file)
@@ -194,6 +194,7 @@ class RegisterAllocator {
   size_t maximum_number_of_live_registers_;
 
   ART_FRIEND_TEST(RegisterAllocatorTest, FreeUntil);
+  ART_FRIEND_TEST(RegisterAllocatorTest, SpillInactive);
 
   DISALLOW_COPY_AND_ASSIGN(RegisterAllocator);
 };
index c2ea80e..0948643 100644 (file)
@@ -738,4 +738,106 @@ TEST(RegisterAllocatorTest, ExpectedExactInRegisterAndSameOutputHint) {
   }
 }
 
+// Test a bug in the register allocator, where allocating a blocked
+// register would lead to spilling an inactive interval at the wrong
+// position.
+TEST(RegisterAllocatorTest, SpillInactive) {
+  ArenaPool pool;
+
+  // Create a synthesized graph to please the register_allocator and
+  // ssa_liveness_analysis code.
+  ArenaAllocator allocator(&pool);
+  HGraph* graph = new (&allocator) HGraph(&allocator);
+  HBasicBlock* entry = new (&allocator) HBasicBlock(graph);
+  graph->AddBlock(entry);
+  graph->SetEntryBlock(entry);
+  HInstruction* one = new (&allocator) HParameterValue(0, Primitive::kPrimInt);
+  HInstruction* two = new (&allocator) HParameterValue(0, Primitive::kPrimInt);
+  HInstruction* three = new (&allocator) HParameterValue(0, Primitive::kPrimInt);
+  HInstruction* four = new (&allocator) HParameterValue(0, Primitive::kPrimInt);
+  entry->AddInstruction(one);
+  entry->AddInstruction(two);
+  entry->AddInstruction(three);
+  entry->AddInstruction(four);
+
+  HBasicBlock* block = new (&allocator) HBasicBlock(graph);
+  graph->AddBlock(block);
+  entry->AddSuccessor(block);
+  block->AddInstruction(new (&allocator) HExit());
+
+  // We create a synthesized user requesting a register, to avoid just spilling the
+  // intervals.
+  HPhi* user = new (&allocator) HPhi(&allocator, 0, 1, Primitive::kPrimInt);
+  user->AddInput(one);
+  user->SetBlock(block);
+  LocationSummary* locations = new (&allocator) LocationSummary(user, LocationSummary::kNoCall);
+  locations->SetInAt(0, Location::RequiresRegister());
+  static constexpr size_t phi_ranges[][2] = {{20, 30}};
+  BuildInterval(phi_ranges, arraysize(phi_ranges), &allocator, -1, user);
+
+  // Create an interval with lifetime holes.
+  static constexpr size_t ranges1[][2] = {{0, 2}, {4, 6}, {8, 10}};
+  LiveInterval* first = BuildInterval(ranges1, arraysize(ranges1), &allocator, -1, one);
+  first->first_use_ = new(&allocator) UsePosition(user, 0, false, 8, first->first_use_);
+  first->first_use_ = new(&allocator) UsePosition(user, 0, false, 7, first->first_use_);
+  first->first_use_ = new(&allocator) UsePosition(user, 0, false, 6, first->first_use_);
+
+  locations = new (&allocator) LocationSummary(first->GetDefinedBy(), LocationSummary::kNoCall);
+  locations->SetOut(Location::RequiresRegister());
+  first = first->SplitAt(1);
+
+  // Create an interval that conflicts with the next interval, to force the next
+  // interval to call `AllocateBlockedReg`.
+  static constexpr size_t ranges2[][2] = {{2, 4}};
+  LiveInterval* second = BuildInterval(ranges2, arraysize(ranges2), &allocator, -1, two);
+  locations = new (&allocator) LocationSummary(second->GetDefinedBy(), LocationSummary::kNoCall);
+  locations->SetOut(Location::RequiresRegister());
+
+  // Create an interval that will lead to splitting the first interval. The bug occured
+  // by splitting at a wrong position, in this case at the next intersection between
+  // this interval and the first interval. We would have then put the interval with ranges
+  // "[0, 2(, [4, 6(" in the list of handled intervals, even though we haven't processed intervals
+  // before lifetime position 6 yet.
+  static constexpr size_t ranges3[][2] = {{2, 4}, {8, 10}};
+  LiveInterval* third = BuildInterval(ranges3, arraysize(ranges3), &allocator, -1, three);
+  third->first_use_ = new(&allocator) UsePosition(user, 0, false, 8, third->first_use_);
+  third->first_use_ = new(&allocator) UsePosition(user, 0, false, 4, third->first_use_);
+  third->first_use_ = new(&allocator) UsePosition(user, 0, false, 3, third->first_use_);
+  locations = new (&allocator) LocationSummary(third->GetDefinedBy(), LocationSummary::kNoCall);
+  locations->SetOut(Location::RequiresRegister());
+  third = third->SplitAt(3);
+
+  // Because the first part of the split interval was considered handled, this interval
+  // was free to allocate the same register, even though it conflicts with it.
+  static constexpr size_t ranges4[][2] = {{4, 6}};
+  LiveInterval* fourth = BuildInterval(ranges4, arraysize(ranges4), &allocator, -1, four);
+  locations = new (&allocator) LocationSummary(fourth->GetDefinedBy(), LocationSummary::kNoCall);
+  locations->SetOut(Location::RequiresRegister());
+
+  x86::CodeGeneratorX86 codegen(graph);
+  SsaLivenessAnalysis liveness(*graph, &codegen);
+
+  RegisterAllocator register_allocator(&allocator, &codegen, liveness);
+  register_allocator.unhandled_core_intervals_.Add(fourth);
+  register_allocator.unhandled_core_intervals_.Add(third);
+  register_allocator.unhandled_core_intervals_.Add(second);
+  register_allocator.unhandled_core_intervals_.Add(first);
+
+  // Set just one register available to make all intervals compete for the same.
+  register_allocator.number_of_registers_ = 1;
+  register_allocator.registers_array_ = allocator.AllocArray<size_t>(1);
+  register_allocator.processing_core_registers_ = true;
+  register_allocator.unhandled_ = &register_allocator.unhandled_core_intervals_;
+  register_allocator.LinearScan();
+
+  // Test that there is no conflicts between intervals.
+  GrowableArray<LiveInterval*> intervals(&allocator, 0);
+  intervals.Add(first);
+  intervals.Add(second);
+  intervals.Add(third);
+  intervals.Add(fourth);
+  ASSERT_TRUE(RegisterAllocator::ValidateIntervals(
+      intervals, 0, 0, codegen, &allocator, true, false));
+}
+
 }  // namespace art
index 74611e1..b632c4d 100644 (file)
@@ -429,7 +429,7 @@ class LiveInterval : public ArenaObject<kArenaAllocMisc> {
     LiveRange* current = first_range_;
     LiveRange* previous = nullptr;
     // Iterate over the ranges, and either find a range that covers this position, or
-    // two ranges in between this position (that is, the position is in a lifetime hole).
+    // two ranges in between this position (that is, the position is in a lifetime hole).
     do {
       if (position >= current->GetEnd()) {
         // Move to next range.
@@ -653,6 +653,8 @@ class LiveInterval : public ArenaObject<kArenaAllocMisc> {
   static constexpr int kNoRegister = -1;
   static constexpr int kNoSpillSlot = -1;
 
+  ART_FRIEND_TEST(RegisterAllocatorTest, SpillInactive);
+
   DISALLOW_COPY_AND_ASSIGN(LiveInterval);
 };