Replace reduce-memory mode in idle notification with delayed clean-up GC.
authorulan <ulan@chromium.org>
Thu, 2 Jul 2015 15:41:29 +0000 (08:41 -0700)
committerCommit bot <commit-bot@chromium.org>
Thu, 2 Jul 2015 15:41:36 +0000 (15:41 +0000)
BUG=490559
LOG=NO

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

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

12 files changed:
BUILD.gn
src/heap/gc-idle-time-handler.cc
src/heap/gc-idle-time-handler.h
src/heap/heap.cc
src/heap/heap.h
src/heap/memory-reducer.cc [new file with mode: 0644]
src/heap/memory-reducer.h [new file with mode: 0644]
test/cctest/test-api.cc
test/unittests/heap/gc-idle-time-handler-unittest.cc
test/unittests/heap/memory-reducer-unittest.cc [new file with mode: 0644]
test/unittests/unittests.gyp
tools/gyp/v8.gyp

index 882e2bc..1c98eba 100644 (file)
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -855,6 +855,8 @@ source_set("v8_base") {
     "src/heap/mark-compact-inl.h",
     "src/heap/mark-compact.cc",
     "src/heap/mark-compact.h",
+    "src/heap/memory-reducer.cc",
+    "src/heap/memory-reducer.h",
     "src/heap/objects-visiting-inl.h",
     "src/heap/objects-visiting.cc",
     "src/heap/objects-visiting.h",
index ab84a73..efce981 100644 (file)
@@ -50,7 +50,6 @@ void GCIdleTimeHandler::HeapState::Print() {
   PrintF("contexts_disposal_rate=%f ", contexts_disposal_rate);
   PrintF("size_of_objects=%" V8_PTR_PREFIX "d ", size_of_objects);
   PrintF("incremental_marking_stopped=%d ", incremental_marking_stopped);
-  PrintF("can_start_incremental_marking=%d ", can_start_incremental_marking);
   PrintF("sweeping_in_progress=%d ", sweeping_in_progress);
   PrintF("has_low_allocation_rate=%d", has_low_allocation_rate);
   PrintF("mark_compact_speed=%" V8_PTR_PREFIX "d ",
@@ -195,70 +194,15 @@ bool GCIdleTimeHandler::ShouldDoOverApproximateWeakClosure(
 
 
 GCIdleTimeAction GCIdleTimeHandler::NothingOrDone() {
-  if (idle_times_which_made_no_progress_per_mode_ >=
-      kMaxNoProgressIdleTimesPerMode) {
+  if (idle_times_which_made_no_progress_ >= kMaxNoProgressIdleTimes) {
     return GCIdleTimeAction::Done();
   } else {
-    idle_times_which_made_no_progress_per_mode_++;
+    idle_times_which_made_no_progress_++;
     return GCIdleTimeAction::Nothing();
   }
 }
 
 
-// The idle time handler has three modes and transitions between them
-// as shown in the diagram:
-//
-//  kReduceLatency -----> kReduceMemory -----> kDone
-//      ^    ^                  |                |
-//      |    |                  |                |
-//      |    +------------------+                |
-//      |                                        |
-//      +----------------------------------------+
-//
-// In kReduceLatency mode the handler only starts incremental marking
-// if can_start_incremental_marking is false.
-// In kReduceMemory mode the handler can force a new GC cycle by starting
-// incremental marking even if can_start_incremental_marking is false. It can
-// cause at most X idle GCs.
-// In kDone mode the idle time handler does nothing.
-//
-// The initial mode is kReduceLatency.
-//
-// kReduceLatency => kReduceMemory transition happens if there were Y
-// consecutive long idle notifications without any mutator GC. This is our
-// notion of "mutator is idle".
-//
-// kReduceMemory => kDone transition happens after X idle GCs.
-//
-// kReduceMemory => kReduceLatency transition happens if N mutator GCs
-// were performed meaning that the mutator is active.
-//
-// kDone => kReduceLatency transition happens if there were M mutator GCs or
-// context was disposed.
-//
-// X = kMaxIdleMarkCompacts
-// Y = kLongIdleNotificationsBeforeMutatorIsIdle
-// N = #(idle GCs)
-// M = kGCsBeforeMutatorIsActive
-GCIdleTimeAction GCIdleTimeHandler::Compute(double idle_time_in_ms,
-                                            HeapState heap_state) {
-  Mode next_mode = NextMode(heap_state);
-
-  if (next_mode != mode_) {
-    mode_ = next_mode;
-    ResetCounters();
-  }
-
-  UpdateCounters(idle_time_in_ms);
-
-  if (mode_ == kDone) {
-    return GCIdleTimeAction::Done();
-  } else {
-    return Action(idle_time_in_ms, heap_state, mode_ == kReduceMemory);
-  }
-}
-
-
 // The following logic is implemented by the controller:
 // (1) If we don't have any idle time, do nothing, unless a context was
 // disposed, incremental marking is stopped, and the heap is small. Then do
@@ -267,26 +211,18 @@ GCIdleTimeAction GCIdleTimeHandler::Compute(double idle_time_in_ms,
 // we do nothing until the context disposal rate becomes lower.
 // (3) If the new space is almost full and we can affort a scavenge or if the
 // next scavenge will very likely take long, then a scavenge is performed.
-// (4) If there is currently no MarkCompact idle round going on, we start a
-// new idle round if enough garbage was created. Otherwise we do not perform
-// garbage collection to keep system utilization low.
-// (5) If incremental marking is done, we perform a full garbage collection
-// if  we are allowed to still do full garbage collections during this idle
-// round or if we are not allowed to start incremental marking. Otherwise we
-// do not perform garbage collection to keep system utilization low.
-// (6) If sweeping is in progress and we received a large enough idle time
+// (4) If sweeping is in progress and we received a large enough idle time
 // request, we finalize sweeping here.
-// (7) If incremental marking is in progress, we perform a marking step. Note,
+// (5) If incremental marking is in progress, we perform a marking step. Note,
 // that this currently may trigger a full garbage collection.
-GCIdleTimeAction GCIdleTimeHandler::Action(double idle_time_in_ms,
-                                           const HeapState& heap_state,
-                                           bool reduce_memory) {
+GCIdleTimeAction GCIdleTimeHandler::Compute(double idle_time_in_ms,
+                                            HeapState heap_state) {
   if (static_cast<int>(idle_time_in_ms) <= 0) {
     if (heap_state.incremental_marking_stopped) {
       if (ShouldDoContextDisposalMarkCompact(
               heap_state.contexts_disposed,
               heap_state.contexts_disposal_rate)) {
-        return GCIdleTimeAction::FullGC(false);
+        return GCIdleTimeAction::FullGC();
       }
     }
     return GCIdleTimeAction::Nothing();
@@ -307,14 +243,6 @@ GCIdleTimeAction GCIdleTimeHandler::Action(double idle_time_in_ms,
     return GCIdleTimeAction::Scavenge();
   }
 
-  if (heap_state.incremental_marking_stopped && reduce_memory) {
-    if (ShouldDoMarkCompact(static_cast<size_t>(idle_time_in_ms),
-                            heap_state.size_of_objects,
-                            heap_state.mark_compact_speed_in_bytes_per_ms)) {
-      return GCIdleTimeAction::FullGC(reduce_memory);
-    }
-  }
-
   if (heap_state.sweeping_in_progress) {
     if (heap_state.sweeping_completed) {
       return GCIdleTimeAction::FinalizeSweeping();
@@ -323,95 +251,16 @@ GCIdleTimeAction GCIdleTimeHandler::Action(double idle_time_in_ms,
     }
   }
 
-  if (!FLAG_incremental_marking ||
-      (heap_state.incremental_marking_stopped &&
-       !heap_state.can_start_incremental_marking && !reduce_memory)) {
-    return NothingOrDone();
+  if (!FLAG_incremental_marking || heap_state.incremental_marking_stopped) {
+    return GCIdleTimeAction::Done();
   }
 
   size_t step_size = EstimateMarkingStepSize(
       static_cast<size_t>(kIncrementalMarkingStepTimeInMs),
       heap_state.incremental_marking_speed_in_bytes_per_ms);
-  return GCIdleTimeAction::IncrementalMarking(step_size, reduce_memory);
+  return GCIdleTimeAction::IncrementalMarking(step_size);
 }
 
 
-void GCIdleTimeHandler::UpdateCounters(double idle_time_in_ms) {
-  if (mode_ == kReduceLatency) {
-    int gcs = scavenges_ + mark_compacts_;
-    if (gcs > 0) {
-      // There was a GC since the last notification.
-      long_idle_notifications_ = 0;
-      background_idle_notifications_ = 0;
-    }
-    idle_mark_compacts_ = 0;
-    mark_compacts_ = 0;
-    scavenges_ = 0;
-    if (idle_time_in_ms >= kMinBackgroundIdleTime) {
-      background_idle_notifications_++;
-    } else if (idle_time_in_ms >= kMinLongIdleTime) {
-      long_idle_notifications_++;
-    }
-  }
-}
-
-
-void GCIdleTimeHandler::ResetCounters() {
-  long_idle_notifications_ = 0;
-  background_idle_notifications_ = 0;
-  idle_mark_compacts_ = 0;
-  mark_compacts_ = 0;
-  scavenges_ = 0;
-  idle_times_which_made_no_progress_per_mode_ = 0;
-}
-
-
-bool GCIdleTimeHandler::IsMutatorActive(int contexts_disposed,
-                                        int mark_compacts) {
-  return contexts_disposed > 0 ||
-         mark_compacts >= kMarkCompactsBeforeMutatorIsActive;
-}
-
-
-bool GCIdleTimeHandler::IsMutatorIdle(int long_idle_notifications,
-                                      int background_idle_notifications,
-                                      int mutator_gcs) {
-  return mutator_gcs == 0 &&
-         (long_idle_notifications >=
-              kLongIdleNotificationsBeforeMutatorIsIdle ||
-          background_idle_notifications >=
-              kBackgroundIdleNotificationsBeforeMutatorIsIdle);
-}
-
-
-GCIdleTimeHandler::Mode GCIdleTimeHandler::NextMode(
-    const HeapState& heap_state) {
-  DCHECK(mark_compacts_ >= idle_mark_compacts_);
-  int mutator_gcs = scavenges_ + mark_compacts_ - idle_mark_compacts_;
-  switch (mode_) {
-    case kDone:
-      DCHECK(idle_mark_compacts_ == 0);
-      if (IsMutatorActive(heap_state.contexts_disposed, mark_compacts_)) {
-        return kReduceLatency;
-      }
-      break;
-    case kReduceLatency:
-      if (IsMutatorIdle(long_idle_notifications_,
-                        background_idle_notifications_, mutator_gcs)) {
-        return kReduceMemory;
-      }
-      break;
-    case kReduceMemory:
-      if (idle_mark_compacts_ >= kMaxIdleMarkCompacts ||
-          (idle_mark_compacts_ > 0 && !next_gc_likely_to_collect_more_)) {
-        return kDone;
-      }
-      if (mutator_gcs > idle_mark_compacts_) {
-        return kReduceLatency;
-      }
-      break;
-  }
-  return mode_;
-}
 }
 }
index c50d6d3..2d0114b 100644 (file)
@@ -27,7 +27,6 @@ class GCIdleTimeAction {
     result.type = DONE;
     result.parameter = 0;
     result.additional_work = false;
-    result.reduce_memory = false;
     return result;
   }
 
@@ -36,17 +35,14 @@ class GCIdleTimeAction {
     result.type = DO_NOTHING;
     result.parameter = 0;
     result.additional_work = false;
-    result.reduce_memory = false;
     return result;
   }
 
-  static GCIdleTimeAction IncrementalMarking(intptr_t step_size,
-                                             bool reduce_memory) {
+  static GCIdleTimeAction IncrementalMarking(intptr_t step_size) {
     GCIdleTimeAction result;
     result.type = DO_INCREMENTAL_MARKING;
     result.parameter = step_size;
     result.additional_work = false;
-    result.reduce_memory = reduce_memory;
     return result;
   }
 
@@ -55,18 +51,14 @@ class GCIdleTimeAction {
     result.type = DO_SCAVENGE;
     result.parameter = 0;
     result.additional_work = false;
-    // TODO(ulan): add reduce_memory argument and shrink new space size if
-    // reduce_memory = true.
-    result.reduce_memory = false;
     return result;
   }
 
-  static GCIdleTimeAction FullGC(bool reduce_memory) {
+  static GCIdleTimeAction FullGC() {
     GCIdleTimeAction result;
     result.type = DO_FULL_GC;
     result.parameter = 0;
     result.additional_work = false;
-    result.reduce_memory = reduce_memory;
     return result;
   }
 
@@ -75,7 +67,6 @@ class GCIdleTimeAction {
     result.type = DO_FINALIZE_SWEEPING;
     result.parameter = 0;
     result.additional_work = false;
-    result.reduce_memory = false;
     return result;
   }
 
@@ -84,7 +75,6 @@ class GCIdleTimeAction {
   GCIdleTimeActionType type;
   intptr_t parameter;
   bool additional_work;
-  bool reduce_memory;
 };
 
 
@@ -128,6 +118,8 @@ class GCIdleTimeHandler {
   // The maximum idle time when frames are rendered is 16.66ms.
   static const size_t kMaxFrameRenderingIdleTime = 17;
 
+  static const int kMinBackgroundIdleTime = 900;
+
   // We conservatively assume that in the next kTimeUntilNextIdleEvent ms
   // no idle notification happens.
   static const size_t kTimeUntilNextIdleEvent = 100;
@@ -147,28 +139,10 @@ class GCIdleTimeHandler {
 
   static const size_t kMinTimeForOverApproximatingWeakClosureInMs;
 
-  // The number of idle MarkCompact GCs to perform before transitioning to
-  // the kDone mode.
-  static const int kMaxIdleMarkCompacts = 3;
-
-  // The number of mutator MarkCompact GCs before transitioning to the
-  // kReduceLatency mode.
-  static const int kMarkCompactsBeforeMutatorIsActive = 1;
-
-  // Mutator is considered idle if
-  // 1) there are N idle notification with time >= kMinBackgroundIdleTime,
-  // 2) or there are M idle notifications with time >= kMinLongIdleTime
-  // without any mutator GC in between.
-  // Where N = kBackgroundIdleNotificationsBeforeMutatorIsIdle,
-  //       M = kLongIdleNotificationsBeforeMutatorIsIdle
-  static const int kMinLongIdleTime = kMaxFrameRenderingIdleTime + 1;
-  static const int kMinBackgroundIdleTime = 900;
-  static const int kBackgroundIdleNotificationsBeforeMutatorIsIdle = 2;
-  static const int kLongIdleNotificationsBeforeMutatorIsIdle = 50;
   // Number of times we will return a Nothing action in the current mode
   // despite having idle time available before we returning a Done action to
   // ensure we don't keep scheduling idle tasks and making no progress.
-  static const int kMaxNoProgressIdleTimesPerMode = 10;
+  static const int kMaxNoProgressIdleTimes = 10;
 
   class HeapState {
    public:
@@ -178,7 +152,6 @@ class GCIdleTimeHandler {
     double contexts_disposal_rate;
     size_t size_of_objects;
     bool incremental_marking_stopped;
-    bool can_start_incremental_marking;
     bool sweeping_in_progress;
     bool sweeping_completed;
     bool has_low_allocation_rate;
@@ -191,26 +164,11 @@ class GCIdleTimeHandler {
     size_t new_space_allocation_throughput_in_bytes_per_ms;
   };
 
-  GCIdleTimeHandler()
-      : idle_mark_compacts_(0),
-        mark_compacts_(0),
-        scavenges_(0),
-        long_idle_notifications_(0),
-        background_idle_notifications_(0),
-        idle_times_which_made_no_progress_per_mode_(0),
-        next_gc_likely_to_collect_more_(false),
-        mode_(kReduceLatency) {}
+  GCIdleTimeHandler() : idle_times_which_made_no_progress_(0) {}
 
   GCIdleTimeAction Compute(double idle_time_in_ms, HeapState heap_state);
 
-  void NotifyIdleMarkCompact() { ++idle_mark_compacts_; }
-
-  void NotifyMarkCompact(bool next_gc_likely_to_collect_more) {
-    next_gc_likely_to_collect_more_ = next_gc_likely_to_collect_more;
-    ++mark_compacts_;
-  }
-
-  void NotifyScavenge() { ++scavenges_; }
+  void ResetNoProgressCounter() { idle_times_which_made_no_progress_ = 0; }
 
   static size_t EstimateMarkingStepSize(size_t idle_time_in_ms,
                                         size_t marking_speed_in_bytes_per_ms);
@@ -239,36 +197,11 @@ class GCIdleTimeHandler {
       size_t scavenger_speed_in_bytes_per_ms,
       size_t new_space_allocation_throughput_in_bytes_per_ms);
 
-  enum Mode { kReduceLatency, kReduceMemory, kDone };
-
-  Mode mode() { return mode_; }
-
  private:
-  bool IsMutatorActive(int contexts_disposed, int gcs);
-  bool IsMutatorIdle(int long_idle_notifications,
-                     int background_idle_notifications, int gcs);
-  void UpdateCounters(double idle_time_in_ms);
-  void ResetCounters();
-  Mode NextMode(const HeapState& heap_state);
-  GCIdleTimeAction Action(double idle_time_in_ms, const HeapState& heap_state,
-                          bool reduce_memory);
   GCIdleTimeAction NothingOrDone();
 
-  int idle_mark_compacts_;
-  int mark_compacts_;
-  int scavenges_;
-  // The number of long idle notifications with no GC happening
-  // between the notifications.
-  int long_idle_notifications_;
-  // The number of background idle notifications with no GC happening
-  // between the notifications.
-  int background_idle_notifications_;
-  // Idle notifications with no progress in the current mode.
-  int idle_times_which_made_no_progress_per_mode_;
-
-  bool next_gc_likely_to_collect_more_;
-
-  Mode mode_;
+  // Idle notifications with no progress.
+  int idle_times_which_made_no_progress_;
 
   DISALLOW_COPY_AND_ASSIGN(GCIdleTimeHandler);
 };
index 8c507d4..6bea41c 100644 (file)
@@ -20,6 +20,7 @@
 #include "src/heap/gc-idle-time-handler.h"
 #include "src/heap/incremental-marking.h"
 #include "src/heap/mark-compact.h"
+#include "src/heap/memory-reducer.h"
 #include "src/heap/objects-visiting-inl.h"
 #include "src/heap/objects-visiting.h"
 #include "src/heap/store-buffer.h"
@@ -107,8 +108,6 @@ Heap::Heap()
       allocation_timeout_(0),
 #endif  // DEBUG
       old_generation_allocation_limit_(initial_old_generation_size_),
-      idle_old_generation_allocation_limit_(
-          kMinimumOldGenerationAllocationLimit),
       old_gen_exhausted_(false),
       inline_allocation_disabled_(false),
       store_buffer_rebuilder_(store_buffer()),
@@ -144,6 +143,7 @@ Heap::Heap()
       store_buffer_(this),
       marking_(this),
       incremental_marking_(this),
+      memory_reducer_(this),
       full_codegen_bytes_generated_(0),
       crankshaft_codegen_bytes_generated_(0),
       new_space_allocation_counter_(0),
@@ -927,6 +927,11 @@ bool Heap::CollectGarbage(GarbageCollector collector, const char* gc_reason,
   }
 
   bool next_gc_likely_to_collect_more = false;
+  intptr_t committed_memory_before;
+
+  if (collector == MARK_COMPACTOR) {
+    committed_memory_before = CommittedOldGenerationMemory();
+  }
 
   {
     tracer()->Start(collector, gc_reason, collector_reason);
@@ -948,9 +953,20 @@ bool Heap::CollectGarbage(GarbageCollector collector, const char* gc_reason,
     }
 
     if (collector == MARK_COMPACTOR) {
-      gc_idle_time_handler_.NotifyMarkCompact(next_gc_likely_to_collect_more);
-    } else {
-      gc_idle_time_handler_.NotifyScavenge();
+      intptr_t committed_memory_after = CommittedOldGenerationMemory();
+      intptr_t used_memory_after = PromotedSpaceSizeOfObjects();
+      MemoryReducer::Event event;
+      event.type = MemoryReducer::kMarkCompact;
+      event.time_ms = MonotonicallyIncreasingTimeInMs();
+      // Trigger one more GC if
+      // - this GC decreased committed memory,
+      // - there is high fragmentation,
+      // - there are live detached contexts.
+      event.next_gc_likely_to_collect_more =
+          (committed_memory_before - committed_memory_after) > MB ||
+          HasHighFragmentation(used_memory_after, committed_memory_after) ||
+          (detached_contexts()->length() > 0);
+      memory_reducer_.NotifyMarkCompact(event);
     }
 
     tracer()->Stop(collector);
@@ -985,10 +1001,20 @@ int Heap::NotifyContextDisposed(bool dependant_context) {
   AgeInlineCaches();
   set_retained_maps(ArrayList::cast(empty_fixed_array()));
   tracer()->AddContextDisposalTime(base::OS::TimeCurrentMillis());
+  MemoryReducer::Event event;
+  event.type = MemoryReducer::kContextDisposed;
+  event.time_ms = MonotonicallyIncreasingTimeInMs();
+  memory_reducer_.NotifyContextDisposed(event);
   return ++contexts_disposed_;
 }
 
 
+void Heap::StartIdleIncrementalMarking() {
+  gc_idle_time_handler_.ResetNoProgressCounter();
+  incremental_marking()->Start(kReduceMemoryFootprintMask);
+}
+
+
 void Heap::MoveElements(FixedArray* array, int dst_index, int src_index,
                         int len) {
   if (len == 0) return;
@@ -4759,6 +4785,21 @@ bool Heap::HasLowAllocationRate() {
 }
 
 
+bool Heap::HasHighFragmentation() {
+  intptr_t used = PromotedSpaceSizeOfObjects();
+  intptr_t committed = CommittedOldGenerationMemory();
+  return HasHighFragmentation(used, committed);
+}
+
+
+bool Heap::HasHighFragmentation(intptr_t used, intptr_t committed) {
+  const intptr_t kSlack = 16 * MB;
+  // Fragmentation is high if committed > 2 * used + kSlack.
+  // Rewrite the exression to avoid overflow.
+  return committed - used > used + kSlack;
+}
+
+
 void Heap::ReduceNewSpaceSize() {
   if (!FLAG_predictable && HasLowAllocationRate()) {
     new_space_.Shrink();
@@ -4785,7 +4826,6 @@ bool Heap::TryFinalizeIdleIncrementalMarking(
                   static_cast<size_t>(idle_time_in_ms), size_of_objects,
                   final_incremental_mark_compact_speed_in_bytes_per_ms))) {
     CollectAllGarbage(kNoGCFlags, "idle notification: finalize incremental");
-    gc_idle_time_handler_.NotifyIdleMarkCompact();
     return true;
   }
   return false;
@@ -4816,15 +4856,6 @@ GCIdleTimeHandler::HeapState Heap::ComputeHeapState() {
   heap_state.new_space_capacity = new_space_.Capacity();
   heap_state.new_space_allocation_throughput_in_bytes_per_ms =
       tracer()->NewSpaceAllocationThroughputInBytesPerMillisecond();
-  heap_state.has_low_allocation_rate = HasLowAllocationRate();
-  intptr_t limit = old_generation_allocation_limit_;
-  if (heap_state.has_low_allocation_rate) {
-    limit = idle_old_generation_allocation_limit_;
-  }
-  heap_state.can_start_incremental_marking =
-      incremental_marking()->CanBeActivated() &&
-      HeapIsFullEnoughToStartIncrementalMarking(limit) &&
-      !mark_compact_collector()->sweeping_in_progress();
   return heap_state;
 }
 
@@ -4838,10 +4869,7 @@ bool Heap::PerformIdleTimeAction(GCIdleTimeAction action,
       result = true;
       break;
     case DO_INCREMENTAL_MARKING: {
-      if (incremental_marking()->IsStopped()) {
-        incremental_marking()->Start(
-            action.reduce_memory ? kReduceMemoryFootprintMask : kNoGCFlags);
-      }
+      DCHECK(!incremental_marking()->IsStopped());
       double remaining_idle_time_in_ms = 0.0;
       do {
         incremental_marking()->Step(
@@ -4862,17 +4890,9 @@ bool Heap::PerformIdleTimeAction(GCIdleTimeAction action,
       break;
     }
     case DO_FULL_GC: {
-      if (action.reduce_memory) {
-        isolate_->compilation_cache()->Clear();
-      }
-      if (contexts_disposed_) {
-        HistogramTimerScope scope(isolate_->counters()->gc_context());
-        CollectAllGarbage(kNoGCFlags, "idle notification: contexts disposed");
-      } else {
-        CollectAllGarbage(kReduceMemoryFootprintMask,
-                          "idle notification: finalize idle round");
-      }
-      gc_idle_time_handler_.NotifyIdleMarkCompact();
+      DCHECK(contexts_disposed_ > 0);
+      HistogramTimerScope scope(isolate_->counters()->gc_context());
+      CollectAllGarbage(kNoGCFlags, "idle notification: contexts disposed");
       break;
     }
     case DO_SCAVENGE:
@@ -5607,23 +5627,14 @@ void Heap::SetOldGenerationAllocationLimit(intptr_t old_gen_size,
     factor = kMinHeapGrowingFactor;
   }
 
-  // TODO(hpayer): Investigate if idle_old_generation_allocation_limit_ is still
-  // needed after taking the allocation rate for the old generation limit into
-  // account.
-  double idle_factor = Min(factor, kMaxHeapGrowingFactorIdle);
-
   old_generation_allocation_limit_ =
       CalculateOldGenerationAllocationLimit(factor, old_gen_size);
-  idle_old_generation_allocation_limit_ =
-      CalculateOldGenerationAllocationLimit(idle_factor, old_gen_size);
 
   if (FLAG_trace_gc_verbose) {
-    PrintIsolate(
-        isolate_,
-        "Grow: old size: %" V8_PTR_PREFIX "d KB, new limit: %" V8_PTR_PREFIX
-        "d KB (%.1f), new idle limit: %" V8_PTR_PREFIX "d KB (%.1f)\n",
-        old_gen_size / KB, old_generation_allocation_limit_ / KB, factor,
-        idle_old_generation_allocation_limit_ / KB, idle_factor);
+    PrintIsolate(isolate_, "Grow: old size: %" V8_PTR_PREFIX
+                           "d KB, new limit: %" V8_PTR_PREFIX "d KB (%.1f)\n",
+                 old_gen_size / KB, old_generation_allocation_limit_ / KB,
+                 factor);
   }
 }
 
index 3ce381d..4a7ce26 100644 (file)
@@ -16,6 +16,7 @@
 #include "src/heap/gc-tracer.h"
 #include "src/heap/incremental-marking.h"
 #include "src/heap/mark-compact.h"
+#include "src/heap/memory-reducer.h"
 #include "src/heap/objects-visiting.h"
 #include "src/heap/spaces.h"
 #include "src/heap/store-buffer.h"
@@ -831,6 +832,10 @@ class Heap {
   // Notify the heap that a context has been disposed.
   int NotifyContextDisposed(bool dependant_context);
 
+  // Start incremental marking and ensure that idle time handler can perform
+  // incremental steps.
+  void StartIdleIncrementalMarking();
+
   inline void increment_scan_on_scavenge_pages() {
     scan_on_scavenge_pages_++;
     if (FLAG_gc_verbose) {
@@ -1617,6 +1622,10 @@ class Heap {
   // An ArrayBuffer moved from new space to old space.
   void PromoteArrayBuffer(Object* buffer);
 
+  bool HasLowAllocationRate();
+  bool HasHighFragmentation();
+  bool HasHighFragmentation(intptr_t used, intptr_t committed);
+
  protected:
   // Methods made available to tests.
 
@@ -1777,10 +1786,6 @@ class Heap {
   // generation and on every allocation in large object space.
   intptr_t old_generation_allocation_limit_;
 
-  // The allocation limit when there is >16.66ms idle time in the idle time
-  // handler.
-  intptr_t idle_old_generation_allocation_limit_;
-
   // Indicates that an allocation has failed in the old generation since the
   // last GC.
   bool old_gen_exhausted_;
@@ -2257,7 +2262,6 @@ class Heap {
 
   bool HasLowYoungGenerationAllocationRate();
   bool HasLowOldGenerationAllocationRate();
-  bool HasLowAllocationRate();
 
   void ReduceNewSpaceSize();
 
@@ -2324,6 +2328,8 @@ class Heap {
 
   GCIdleTimeHandler gc_idle_time_handler_;
 
+  MemoryReducer memory_reducer_;
+
   // These two counters are monotomically increasing and never reset.
   size_t full_codegen_bytes_generated_;
   size_t crankshaft_codegen_bytes_generated_;
diff --git a/src/heap/memory-reducer.cc b/src/heap/memory-reducer.cc
new file mode 100644 (file)
index 0000000..ff32809
--- /dev/null
@@ -0,0 +1,140 @@
+// Copyright 2015 the V8 project 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 "src/heap/memory-reducer.h"
+
+#include "src/flags.h"
+#include "src/heap/heap.h"
+#include "src/utils.h"
+#include "src/v8.h"
+
+namespace v8 {
+namespace internal {
+
+const int MemoryReducer::kLongDelayMs = 5000;
+const int MemoryReducer::kShortDelayMs = 500;
+const int MemoryReducer::kMaxNumberOfGCs = 3;
+
+
+void MemoryReducer::TimerTask::Run() {
+  Heap* heap = memory_reducer_->heap();
+  Event event;
+  event.type = kTimer;
+  event.time_ms = heap->MonotonicallyIncreasingTimeInMs();
+  event.low_allocation_rate = heap->HasLowAllocationRate();
+  event.can_start_incremental_gc =
+      heap->incremental_marking()->IsStopped() &&
+      heap->incremental_marking()->CanBeActivated();
+  memory_reducer_->NotifyTimer(event);
+}
+
+
+void MemoryReducer::NotifyTimer(const Event& event) {
+  DCHECK_EQ(kTimer, event.type);
+  DCHECK_EQ(kWait, state_.action);
+  state_ = Step(state_, event);
+  if (state_.action == kRun) {
+    DCHECK(heap()->incremental_marking()->IsStopped());
+    DCHECK(FLAG_incremental_marking);
+    heap()->StartIdleIncrementalMarking();
+    if (FLAG_trace_gc_verbose) {
+      PrintIsolate(heap()->isolate(), "Memory reducer: started GC #%d\n",
+                   state_.started_gcs);
+    }
+  } else if (state_.action == kWait) {
+    // Re-schedule the timer.
+    ScheduleTimer(state_.next_gc_start_ms - event.time_ms);
+    if (FLAG_trace_gc_verbose) {
+      PrintIsolate(heap()->isolate(), "Memory reducer: waiting for %.f ms\n",
+                   state_.next_gc_start_ms - event.time_ms);
+    }
+  }
+}
+
+
+void MemoryReducer::NotifyMarkCompact(const Event& event) {
+  DCHECK_EQ(kMarkCompact, event.type);
+  Action old_action = state_.action;
+  state_ = Step(state_, event);
+  if (old_action != kWait && state_.action == kWait) {
+    // If we are transitioning to the WAIT state, start the timer.
+    ScheduleTimer(state_.next_gc_start_ms - event.time_ms);
+  }
+  if (old_action == kRun) {
+    if (FLAG_trace_gc_verbose) {
+      PrintIsolate(heap()->isolate(), "Memory reducer: finished GC #%d (%s)\n",
+                   state_.started_gcs,
+                   state_.action == kWait ? "will do more" : "done");
+    }
+  }
+}
+
+
+void MemoryReducer::NotifyContextDisposed(const Event& event) {
+  DCHECK_EQ(kContextDisposed, event.type);
+  Action old_action = state_.action;
+  state_ = Step(state_, event);
+  if (old_action != kWait && state_.action == kWait) {
+    // If we are transitioning to the WAIT state, start the timer.
+    ScheduleTimer(state_.next_gc_start_ms - event.time_ms);
+  }
+}
+
+
+// For specification of this function see the comment for MemoryReducer class.
+MemoryReducer::State MemoryReducer::Step(const State& state,
+                                         const Event& event) {
+  if (!FLAG_incremental_marking) {
+    return State(kDone, 0, 0);
+  }
+  switch (state.action) {
+    case kDone:
+      if (event.type == kTimer) {
+        return state;
+      } else {
+        DCHECK(event.type == kContextDisposed || event.type == kMarkCompact);
+        return State(kWait, 0, event.time_ms + kLongDelayMs);
+      }
+    case kWait:
+      if (event.type == kContextDisposed) {
+        return state;
+      } else if (event.type == kTimer && event.can_start_incremental_gc &&
+                 event.low_allocation_rate) {
+        if (state.next_gc_start_ms <= event.time_ms) {
+          return State(kRun, state.started_gcs + 1, 0.0);
+        } else {
+          return state;
+        }
+      } else {
+        return State(kWait, state.started_gcs, event.time_ms + kLongDelayMs);
+      }
+    case kRun:
+      if (event.type != kMarkCompact) {
+        return state;
+      } else {
+        if (state.started_gcs < kMaxNumberOfGCs &&
+            (event.next_gc_likely_to_collect_more || state.started_gcs == 1)) {
+          return State(kWait, state.started_gcs, event.time_ms + kShortDelayMs);
+        } else {
+          return State(kDone, 0, 0.0);
+        }
+      }
+  }
+  UNREACHABLE();
+  return State(kDone, 0, 0);  // Make the compiler happy.
+}
+
+
+void MemoryReducer::ScheduleTimer(double delay_ms) {
+  DCHECK(delay_ms > 0);
+  // Leave some room for precision error in task scheduler.
+  const double kSlackMs = 100;
+  v8::Isolate* isolate = reinterpret_cast<v8::Isolate*>(heap()->isolate());
+  V8::GetCurrentPlatform()->CallDelayedOnForegroundThread(
+      isolate, new MemoryReducer::TimerTask(this),
+      (delay_ms + kSlackMs) / 1000.0);
+}
+
+}  // internal
+}  // v8
diff --git a/src/heap/memory-reducer.h b/src/heap/memory-reducer.h
new file mode 100644 (file)
index 0000000..a9ba298
--- /dev/null
@@ -0,0 +1,135 @@
+// Copyright 2015 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef V8_HEAP_memory_reducer_H
+#define V8_HEAP_memory_reducer_H
+
+#include "include/v8-platform.h"
+#include "src/base/macros.h"
+
+namespace v8 {
+namespace internal {
+
+class Heap;
+
+
+// The goal of the MemoryReducer class is to detect transition of the mutator
+// from high allocation phase to low allocation phase and to collect potential
+// garbage created in the high allocation phase.
+//
+// The class implements an automaton with the following states and transitions.
+//
+// States:
+// - DONE
+// - WAIT <started_gcs> <next_gc_start_ms>
+// - RUN <started_gcs>
+// The <started_gcs> is an integer in range from 0..kMaxNumberOfGCs that stores
+// the number of GCs initiated by the MemoryReducer since it left the DONE
+// state.
+// The <next_gc_start_ms> is a double that stores the earliest time the next GC
+// can be initiated by the MemoryReducer.
+// The DONE state means that the MemoryReducer is not active.
+// The WAIT state means that the MemoryReducer is waiting for mutator allocation
+// rate to drop. The check for the allocation rate happens in the timer task
+// callback.
+// The RUN state means that the MemoryReducer started incremental marking and is
+// waiting for it to finish. Incremental marking steps are performed as usual
+// in the idle notification and in the mutator.
+//
+// Transitions:
+// DONE -> WAIT 0 (now_ms + long_delay_ms) happens:
+//     - on context disposal,
+//     - at the end of mark-compact GC initiated by the mutator.
+// This signals that there is potential garbage to be collected.
+//
+// WAIT n x -> WAIT n (now_ms + long_delay_ms) happens:
+//     - on mark-compact GC initiated by the mutator,
+//     - in the timer callback if the mutator allocation rate is high or
+//       incremental GC is in progress.
+//
+// WAIT n x -> RUN (n+1) happens:
+//     - in the timer callback if the mutator allocation rate is low
+//       and now_ms >= x and there is no incremental GC in progress.
+// The MemoryReducer starts incremental marking on this transition.
+//
+// RUN n -> DONE happens:
+//     - at end of the incremental GC initiated by the MemoryReducer if
+//       (n > 1 and there is no more garbage to be collected) or
+//       n == kMaxNumberOfGCs.
+// RUN n -> WAIT n (now_ms + short_delay_ms) happens:
+//     - at end of the incremental GC initiated by the MemoryReducer if
+//       (n == 1 or there is more garbage to be collected) and
+//       n < kMaxNumberOfGCs.
+//
+// now_ms is the current time, long_delay_ms and short_delay_ms are constants.
+class MemoryReducer {
+ public:
+  enum Action { kDone, kWait, kRun };
+
+  struct State {
+    State(Action action, int started_gcs, double next_gc_start_ms)
+        : action(action),
+          started_gcs(started_gcs),
+          next_gc_start_ms(next_gc_start_ms) {}
+    Action action;
+    int started_gcs;
+    double next_gc_start_ms;
+  };
+
+  enum EventType {
+    kTimer,
+    kMarkCompact,
+    kContextDisposed,
+  };
+
+  struct Event {
+    EventType type;
+    double time_ms;
+    bool low_allocation_rate;
+    bool next_gc_likely_to_collect_more;
+    bool can_start_incremental_gc;
+  };
+
+  explicit MemoryReducer(Heap* heap) : heap_(heap), state_(kDone, 0, 0.0) {}
+  // Callbacks.
+  void NotifyTimer(const Event& event);
+  void NotifyMarkCompact(const Event& event);
+  void NotifyScavenge(const Event& event);
+  void NotifyContextDisposed(const Event& event);
+  // The step function that computes the next state from the current state and
+  // the incoming event.
+  static State Step(const State& state, const Event& event);
+  // Posts a timer task that will call NotifyTimer after the given delay.
+  void ScheduleTimer(double delay_ms);
+
+  static const int kLongDelayMs;
+  static const int kShortDelayMs;
+  static const int kMaxNumberOfGCs;
+
+  Heap* heap() { return heap_; }
+
+ private:
+  class TimerTask : public v8::Task {
+   public:
+    explicit TimerTask(MemoryReducer* memory_reducer)
+        : memory_reducer_(memory_reducer) {}
+    virtual ~TimerTask() {}
+
+   private:
+    // v8::Task overrides.
+    void Run() override;
+    MemoryReducer* memory_reducer_;
+    DISALLOW_COPY_AND_ASSIGN(TimerTask);
+  };
+
+  Heap* heap_;
+  State state_;
+
+  DISALLOW_COPY_AND_ASSIGN(MemoryReducer);
+};
+
+}  // namespace internal
+}  // namespace v8
+
+#endif  // V8_HEAP_memory_reducer_H
index 49ee969..4b911f9 100644 (file)
@@ -15294,6 +15294,7 @@ static void CreateGarbageInOldSpace() {
 
 // Test that idle notification can be handled and eventually collects garbage.
 TEST(TestIdleNotification) {
+  if (!i::FLAG_incremental_marking) return;
   const intptr_t MB = 1024 * 1024;
   const double IdlePauseInSeconds = 1.0;
   LocalContext env;
@@ -15304,6 +15305,9 @@ TEST(TestIdleNotification) {
   CHECK_GT(size_with_garbage, initial_size + MB);
   bool finished = false;
   for (int i = 0; i < 200 && !finished; i++) {
+    if (i < 10 && CcTest::heap()->incremental_marking()->IsStopped()) {
+      CcTest::heap()->StartIdleIncrementalMarking();
+    }
     finished = env->GetIsolate()->IdleNotificationDeadline(
         (v8::base::TimeTicks::HighResolutionNow().ToInternalValue() /
          static_cast<double>(v8::base::Time::kMicrosecondsPerSecond)) +
index d4833b3..c75fde4 100644 (file)
@@ -25,7 +25,6 @@ class GCIdleTimeHandlerTest : public ::testing::Test {
     result.contexts_disposal_rate = GCIdleTimeHandler::kHighContextDisposalRate;
     result.size_of_objects = kSizeOfObjects;
     result.incremental_marking_stopped = false;
-    result.can_start_incremental_marking = true;
     result.sweeping_in_progress = false;
     result.sweeping_completed = false;
     result.mark_compact_speed_in_bytes_per_ms = kMarkCompactSpeed;
@@ -38,63 +37,13 @@ class GCIdleTimeHandlerTest : public ::testing::Test {
     return result;
   }
 
-  void TransitionToReduceMemoryMode(
-      const GCIdleTimeHandler::HeapState& heap_state) {
-    handler()->NotifyScavenge();
-    EXPECT_EQ(GCIdleTimeHandler::kReduceLatency, handler()->mode());
-    double idle_time_ms = GCIdleTimeHandler::kMinLongIdleTime;
-    int limit = GCIdleTimeHandler::kLongIdleNotificationsBeforeMutatorIsIdle;
-    bool incremental = !heap_state.incremental_marking_stopped ||
-                       heap_state.can_start_incremental_marking;
-    for (int i = 0; i < limit; i++) {
-      GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-      if (incremental) {
-        EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
-      } else {
-        EXPECT_TRUE(DO_NOTHING == action.type || DONE == action.type);
-      }
-    }
-    handler()->Compute(idle_time_ms, heap_state);
-    EXPECT_EQ(GCIdleTimeHandler::kReduceMemory, handler()->mode());
-  }
-
-  void TransitionToDoneMode(const GCIdleTimeHandler::HeapState& heap_state,
-                            double idle_time_ms,
-                            GCIdleTimeActionType expected) {
-    EXPECT_EQ(GCIdleTimeHandler::kReduceMemory, handler()->mode());
-    int limit = GCIdleTimeHandler::kMaxIdleMarkCompacts;
-    for (int i = 0; i < limit; i++) {
-      GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-      EXPECT_EQ(expected, action.type);
-      EXPECT_TRUE(action.reduce_memory);
-      handler()->NotifyMarkCompact(true);
-      handler()->NotifyIdleMarkCompact();
-    }
-    handler()->Compute(idle_time_ms, heap_state);
-    EXPECT_EQ(GCIdleTimeHandler::kDone, handler()->mode());
-  }
-
-  void TransitionToReduceLatencyMode(
-      const GCIdleTimeHandler::HeapState& heap_state) {
-    EXPECT_EQ(GCIdleTimeHandler::kDone, handler()->mode());
-    int limit = GCIdleTimeHandler::kMarkCompactsBeforeMutatorIsActive;
-    double idle_time_ms = GCIdleTimeHandler::kMinLongIdleTime;
-    for (int i = 0; i < limit; i++) {
-      GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-      EXPECT_EQ(DONE, action.type);
-      handler()->NotifyMarkCompact(true);
-    }
-    handler()->Compute(idle_time_ms, heap_state);
-    EXPECT_EQ(GCIdleTimeHandler::kReduceLatency, handler()->mode());
-  }
-
   static const size_t kSizeOfObjects = 100 * MB;
   static const size_t kMarkCompactSpeed = 200 * KB;
   static const size_t kMarkingSpeed = 200 * KB;
   static const size_t kScavengeSpeed = 100 * KB;
   static const size_t kNewSpaceCapacity = 1 * MB;
   static const size_t kNewSpaceAllocationThroughput = 10 * KB;
-  static const int kMaxNotifications = 1000;
+  static const int kMaxNotifications = 100;
 
  private:
   GCIdleTimeHandler handler_;
@@ -263,11 +212,8 @@ TEST_F(GCIdleTimeHandlerTest, ContextDisposeLowRate) {
   heap_state.contexts_disposed = 1;
   heap_state.incremental_marking_stopped = true;
   double idle_time_ms = 0;
-  for (int mode = 0; mode < 1; mode++) {
-    GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-    EXPECT_EQ(DO_NOTHING, action.type);
-    TransitionToReduceMemoryMode(heap_state);
-  }
+  GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
+  EXPECT_EQ(DO_NOTHING, action.type);
 }
 
 
@@ -278,12 +224,8 @@ TEST_F(GCIdleTimeHandlerTest, ContextDisposeHighRate) {
       GCIdleTimeHandler::kHighContextDisposalRate - 1;
   heap_state.incremental_marking_stopped = true;
   double idle_time_ms = 0;
-  for (int mode = 0; mode < 1; mode++) {
-    GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-    EXPECT_EQ(DO_FULL_GC, action.type);
-    heap_state.contexts_disposal_rate = 0.0;
-    TransitionToReduceMemoryMode(heap_state);
-  }
+  GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
+  EXPECT_EQ(DO_FULL_GC, action.type);
 }
 
 
@@ -293,12 +235,8 @@ TEST_F(GCIdleTimeHandlerTest, AfterContextDisposeZeroIdleTime) {
   heap_state.contexts_disposal_rate = 1.0;
   heap_state.incremental_marking_stopped = true;
   double idle_time_ms = 0;
-  for (int mode = 0; mode < 1; mode++) {
-    GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-    EXPECT_EQ(DO_FULL_GC, action.type);
-    heap_state.contexts_disposal_rate = 0.0;
-    TransitionToReduceMemoryMode(heap_state);
-  }
+  GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
+  EXPECT_EQ(DO_FULL_GC, action.type);
 }
 
 
@@ -307,16 +245,11 @@ TEST_F(GCIdleTimeHandlerTest, AfterContextDisposeSmallIdleTime1) {
   heap_state.contexts_disposed = 1;
   heap_state.contexts_disposal_rate =
       GCIdleTimeHandler::kHighContextDisposalRate;
-  heap_state.incremental_marking_stopped = true;
   size_t speed = heap_state.mark_compact_speed_in_bytes_per_ms;
   double idle_time_ms =
       static_cast<double>(heap_state.size_of_objects / speed - 1);
-  for (int mode = 0; mode < 1; mode++) {
-    GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-    EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
-    heap_state.contexts_disposal_rate = 0.0;
-    TransitionToReduceMemoryMode(heap_state);
-  }
+  GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
+  EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
 }
 
 
@@ -328,12 +261,8 @@ TEST_F(GCIdleTimeHandlerTest, AfterContextDisposeSmallIdleTime2) {
   size_t speed = heap_state.mark_compact_speed_in_bytes_per_ms;
   double idle_time_ms =
       static_cast<double>(heap_state.size_of_objects / speed - 1);
-  for (int mode = 0; mode < 1; mode++) {
-    GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-    EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
-    heap_state.contexts_disposal_rate = 0.0;
-    TransitionToReduceMemoryMode(heap_state);
-  }
+  GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
+  EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
 }
 
 
@@ -341,197 +270,106 @@ TEST_F(GCIdleTimeHandlerTest, IncrementalMarking1) {
   GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
   size_t speed = heap_state.incremental_marking_speed_in_bytes_per_ms;
   double idle_time_ms = 10;
-  for (int mode = 0; mode < 1; mode++) {
-    GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-    EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
-    EXPECT_GT(speed * static_cast<size_t>(idle_time_ms),
-              static_cast<size_t>(action.parameter));
-    EXPECT_LT(0, action.parameter);
-    TransitionToReduceMemoryMode(heap_state);
-  }
+  GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
+  EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
+  EXPECT_GT(speed * static_cast<size_t>(idle_time_ms),
+            static_cast<size_t>(action.parameter));
+  EXPECT_LT(0, action.parameter);
 }
 
 
 TEST_F(GCIdleTimeHandlerTest, IncrementalMarking2) {
   GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
-  heap_state.incremental_marking_stopped = true;
   size_t speed = heap_state.incremental_marking_speed_in_bytes_per_ms;
   double idle_time_ms = 10;
-  for (int mode = 0; mode < 1; mode++) {
-    GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-    EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
-    EXPECT_GT(speed * static_cast<size_t>(idle_time_ms),
-              static_cast<size_t>(action.parameter));
-    EXPECT_LT(0, action.parameter);
-    TransitionToReduceMemoryMode(heap_state);
-  }
+  GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
+  EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
+  EXPECT_GT(speed * static_cast<size_t>(idle_time_ms),
+            static_cast<size_t>(action.parameter));
+  EXPECT_LT(0, action.parameter);
 }
 
 
 TEST_F(GCIdleTimeHandlerTest, NotEnoughTime) {
   GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
   heap_state.incremental_marking_stopped = true;
-  heap_state.can_start_incremental_marking = false;
   size_t speed = heap_state.mark_compact_speed_in_bytes_per_ms;
   double idle_time_ms =
       static_cast<double>(heap_state.size_of_objects / speed - 1);
   GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-  EXPECT_EQ(DO_NOTHING, action.type);
-  TransitionToReduceMemoryMode(heap_state);
-  action = handler()->Compute(idle_time_ms, heap_state);
-  EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
+  EXPECT_EQ(DONE, action.type);
 }
 
 
 TEST_F(GCIdleTimeHandlerTest, FinalizeSweeping) {
   GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
   heap_state.incremental_marking_stopped = true;
-  heap_state.can_start_incremental_marking = false;
-  for (int mode = 0; mode < 1; mode++) {
-    heap_state.sweeping_in_progress = true;
-    heap_state.sweeping_completed = true;
-    double idle_time_ms = 10.0;
-    GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-    EXPECT_EQ(DO_FINALIZE_SWEEPING, action.type);
-    heap_state.sweeping_in_progress = false;
-    heap_state.sweeping_completed = false;
-    TransitionToReduceMemoryMode(heap_state);
-  }
+  heap_state.sweeping_in_progress = true;
+  heap_state.sweeping_completed = true;
+  double idle_time_ms = 10.0;
+  GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
+  EXPECT_EQ(DO_FINALIZE_SWEEPING, action.type);
 }
 
 
 TEST_F(GCIdleTimeHandlerTest, CannotFinalizeSweeping) {
   GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
   heap_state.incremental_marking_stopped = true;
-  heap_state.can_start_incremental_marking = false;
-  for (int mode = 0; mode < 1; mode++) {
-    heap_state.sweeping_in_progress = true;
-    heap_state.sweeping_completed = false;
-    double idle_time_ms = 10.0;
-    GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-    EXPECT_EQ(DO_NOTHING, action.type);
-    heap_state.sweeping_in_progress = false;
-    heap_state.sweeping_completed = false;
-    TransitionToReduceMemoryMode(heap_state);
-  }
+  heap_state.sweeping_in_progress = true;
+  heap_state.sweeping_completed = false;
+  double idle_time_ms = 10.0;
+  GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
+  EXPECT_EQ(DO_NOTHING, action.type);
 }
 
 
 TEST_F(GCIdleTimeHandlerTest, Scavenge) {
   GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
   int idle_time_ms = 10;
-  for (int mode = 0; mode < 1; mode++) {
-    heap_state.used_new_space_size =
-        heap_state.new_space_capacity -
-        (kNewSpaceAllocationThroughput * idle_time_ms);
-    GCIdleTimeAction action =
-        handler()->Compute(static_cast<double>(idle_time_ms), heap_state);
-    EXPECT_EQ(DO_SCAVENGE, action.type);
-    heap_state.used_new_space_size = 0;
-    TransitionToReduceMemoryMode(heap_state);
-  }
+  heap_state.used_new_space_size =
+      heap_state.new_space_capacity -
+      (kNewSpaceAllocationThroughput * idle_time_ms);
+  GCIdleTimeAction action =
+      handler()->Compute(static_cast<double>(idle_time_ms), heap_state);
+  EXPECT_EQ(DO_SCAVENGE, action.type);
+  heap_state.used_new_space_size = 0;
 }
 
 
 TEST_F(GCIdleTimeHandlerTest, ScavengeAndDone) {
   GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
   int idle_time_ms = 10;
-  heap_state.can_start_incremental_marking = false;
-  heap_state.incremental_marking_stopped = true;
-  for (int mode = 0; mode < 1; mode++) {
-    heap_state.used_new_space_size =
-        heap_state.new_space_capacity -
-        (kNewSpaceAllocationThroughput * idle_time_ms);
-    GCIdleTimeAction action =
-        handler()->Compute(static_cast<double>(idle_time_ms), heap_state);
-    EXPECT_EQ(DO_SCAVENGE, action.type);
-    heap_state.used_new_space_size = 0;
-    action = handler()->Compute(static_cast<double>(idle_time_ms), heap_state);
-    EXPECT_EQ(DO_NOTHING, action.type);
-    TransitionToReduceMemoryMode(heap_state);
-  }
-}
-
-
-TEST_F(GCIdleTimeHandlerTest, StopEventually1) {
-  GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
-  heap_state.incremental_marking_stopped = true;
-  heap_state.can_start_incremental_marking = false;
-  double idle_time_ms = GCIdleTimeHandler::kMinLongIdleTime;
-  bool stopped = false;
-  for (int i = 0; i < kMaxNotifications && !stopped; i++) {
-    GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-    if (action.type == DO_INCREMENTAL_MARKING || action.type == DO_FULL_GC) {
-      handler()->NotifyMarkCompact(true);
-      handler()->NotifyIdleMarkCompact();
-    }
-    if (action.type == DONE) stopped = true;
-  }
-  EXPECT_TRUE(stopped);
-}
-
-
-TEST_F(GCIdleTimeHandlerTest, StopEventually2) {
-  GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
-  heap_state.incremental_marking_stopped = true;
-  heap_state.can_start_incremental_marking = false;
-  size_t speed = heap_state.mark_compact_speed_in_bytes_per_ms;
-  double idle_time_ms =
-      static_cast<double>(heap_state.size_of_objects / speed + 1);
-  TransitionToReduceMemoryMode(heap_state);
-  TransitionToDoneMode(heap_state, idle_time_ms, DO_FULL_GC);
-  GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-  EXPECT_EQ(DONE, action.type);
-}
-
-
-TEST_F(GCIdleTimeHandlerTest, StopEventually3) {
-  GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
   heap_state.incremental_marking_stopped = true;
-  heap_state.can_start_incremental_marking = false;
-  double idle_time_ms = 10;
-  TransitionToReduceMemoryMode(heap_state);
-  TransitionToDoneMode(heap_state, idle_time_ms, DO_INCREMENTAL_MARKING);
-  GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
+  heap_state.used_new_space_size =
+      heap_state.new_space_capacity -
+      (kNewSpaceAllocationThroughput * idle_time_ms);
+  GCIdleTimeAction action =
+      handler()->Compute(static_cast<double>(idle_time_ms), heap_state);
+  EXPECT_EQ(DO_SCAVENGE, action.type);
+  heap_state.used_new_space_size = 0;
+  action = handler()->Compute(static_cast<double>(idle_time_ms), heap_state);
   EXPECT_EQ(DONE, action.type);
 }
 
 
-TEST_F(GCIdleTimeHandlerTest, ContinueAfterStop1) {
+TEST_F(GCIdleTimeHandlerTest, DoNotStartIncrementalMarking) {
   GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
   heap_state.incremental_marking_stopped = true;
-  heap_state.can_start_incremental_marking = false;
-  size_t speed = heap_state.mark_compact_speed_in_bytes_per_ms;
-  double idle_time_ms =
-      static_cast<double>(heap_state.size_of_objects / speed + 1);
-  TransitionToReduceMemoryMode(heap_state);
-  TransitionToDoneMode(heap_state, idle_time_ms, DO_FULL_GC);
+  double idle_time_ms = 10.0;
   GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
   EXPECT_EQ(DONE, action.type);
-  TransitionToReduceLatencyMode(heap_state);
-  heap_state.can_start_incremental_marking = true;
-  action = handler()->Compute(idle_time_ms, heap_state);
-  EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
-  EXPECT_FALSE(action.reduce_memory);
-  EXPECT_EQ(GCIdleTimeHandler::kReduceLatency, handler()->mode());
 }
 
 
-TEST_F(GCIdleTimeHandlerTest, ContinueAfterStop2) {
+TEST_F(GCIdleTimeHandlerTest, ContinueAfterStop) {
   GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
   heap_state.incremental_marking_stopped = true;
-  heap_state.can_start_incremental_marking = false;
-  double idle_time_ms = 10;
-  TransitionToReduceMemoryMode(heap_state);
-  TransitionToDoneMode(heap_state, idle_time_ms, DO_INCREMENTAL_MARKING);
+  double idle_time_ms = 10.0;
   GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
   EXPECT_EQ(DONE, action.type);
-  TransitionToReduceLatencyMode(heap_state);
-  heap_state.can_start_incremental_marking = true;
+  heap_state.incremental_marking_stopped = false;
   action = handler()->Compute(idle_time_ms, heap_state);
   EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
-  EXPECT_FALSE(action.reduce_memory);
-  EXPECT_EQ(GCIdleTimeHandler::kReduceLatency, handler()->mode());
 }
 
 
@@ -547,7 +385,6 @@ TEST_F(GCIdleTimeHandlerTest, ZeroIdleTimeNothingToDo) {
 TEST_F(GCIdleTimeHandlerTest, SmallIdleTimeNothingToDo) {
   GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
   heap_state.incremental_marking_stopped = true;
-  heap_state.can_start_incremental_marking = false;
   for (int i = 0; i < kMaxNotifications; i++) {
     GCIdleTimeAction action = handler()->Compute(10, heap_state);
     EXPECT_TRUE(DO_NOTHING == action.type || DONE == action.type);
@@ -555,105 +392,16 @@ TEST_F(GCIdleTimeHandlerTest, SmallIdleTimeNothingToDo) {
 }
 
 
-TEST_F(GCIdleTimeHandlerTest, StayInReduceLatencyModeBecauseOfScavenges) {
-  GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
-  heap_state.incremental_marking_stopped = true;
-  heap_state.can_start_incremental_marking = false;
-  double idle_time_ms = GCIdleTimeHandler::kMinLongIdleTime;
-  int limit = GCIdleTimeHandler::kLongIdleNotificationsBeforeMutatorIsIdle;
-  for (int i = 0; i < kMaxNotifications; i++) {
-    GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-    EXPECT_TRUE(DO_NOTHING == action.type || DONE == action.type);
-    if ((i + 1) % limit == 0) handler()->NotifyScavenge();
-    EXPECT_EQ(GCIdleTimeHandler::kReduceLatency, handler()->mode());
-  }
-}
-
-
-TEST_F(GCIdleTimeHandlerTest, StayInReduceLatencyModeBecauseOfMarkCompacts) {
-  GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
-  heap_state.incremental_marking_stopped = true;
-  heap_state.can_start_incremental_marking = false;
-  double idle_time_ms = GCIdleTimeHandler::kMinLongIdleTime;
-  int limit = GCIdleTimeHandler::kLongIdleNotificationsBeforeMutatorIsIdle;
-  for (int i = 0; i < kMaxNotifications; i++) {
-    GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-    EXPECT_TRUE(DO_NOTHING == action.type || DONE == action.type);
-    if ((i + 1) % limit == 0) handler()->NotifyMarkCompact(true);
-    EXPECT_EQ(GCIdleTimeHandler::kReduceLatency, handler()->mode());
-  }
-}
-
-
-TEST_F(GCIdleTimeHandlerTest, ReduceMemoryToReduceLatency) {
-  GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
-  heap_state.incremental_marking_stopped = true;
-  heap_state.can_start_incremental_marking = false;
-  double idle_time_ms = GCIdleTimeHandler::kMinLongIdleTime;
-  int limit = GCIdleTimeHandler::kMaxIdleMarkCompacts;
-  for (int idle_gc = 0; idle_gc < limit; idle_gc++) {
-    TransitionToReduceMemoryMode(heap_state);
-    GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-    EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
-    EXPECT_TRUE(action.reduce_memory);
-    EXPECT_EQ(GCIdleTimeHandler::kReduceMemory, handler()->mode());
-    for (int i = 0; i < idle_gc; i++) {
-      action = handler()->Compute(idle_time_ms, heap_state);
-      EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
-      EXPECT_TRUE(action.reduce_memory);
-      // ReduceMemory mode should tolerate one mutator GC per idle GC.
-      handler()->NotifyScavenge();
-      // Notify idle GC.
-      handler()->NotifyMarkCompact(true);
-      handler()->NotifyIdleMarkCompact();
-    }
-    // Transition to ReduceLatency mode after doing |idle_gc| idle GCs.
-    handler()->NotifyScavenge();
-    action = handler()->Compute(idle_time_ms, heap_state);
-    EXPECT_EQ(DO_NOTHING, action.type);
-    EXPECT_FALSE(action.reduce_memory);
-    EXPECT_EQ(GCIdleTimeHandler::kReduceLatency, handler()->mode());
-  }
-}
-
-
-TEST_F(GCIdleTimeHandlerTest, ReduceMemoryToDone) {
-  GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
-  heap_state.incremental_marking_stopped = true;
-  heap_state.can_start_incremental_marking = false;
-  double idle_time_ms = GCIdleTimeHandler::kMinLongIdleTime;
-  int limit = GCIdleTimeHandler::kMaxIdleMarkCompacts;
-  TransitionToReduceMemoryMode(heap_state);
-  GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-  EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
-  EXPECT_TRUE(action.reduce_memory);
-  for (int i = 0; i < limit; i++) {
-    action = handler()->Compute(idle_time_ms, heap_state);
-    EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
-    EXPECT_TRUE(action.reduce_memory);
-    EXPECT_EQ(GCIdleTimeHandler::kReduceMemory, handler()->mode());
-    // ReduceMemory mode should tolerate one mutator GC per idle GC.
-    handler()->NotifyScavenge();
-    // Notify idle GC.
-    handler()->NotifyMarkCompact(true);
-    handler()->NotifyIdleMarkCompact();
-  }
-  action = handler()->Compute(idle_time_ms, heap_state);
-  EXPECT_EQ(DONE, action.type);
-}
-
-
 TEST_F(GCIdleTimeHandlerTest, DoneIfNotMakingProgressOnSweeping) {
   // Regression test for crbug.com/489323.
   GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
 
   // Simulate sweeping being in-progress but not complete.
   heap_state.incremental_marking_stopped = true;
-  heap_state.can_start_incremental_marking = false;
   heap_state.sweeping_in_progress = true;
   heap_state.sweeping_completed = false;
   double idle_time_ms = 10.0;
-  for (int i = 0; i < GCIdleTimeHandler::kMaxNoProgressIdleTimesPerMode; i++) {
+  for (int i = 0; i < GCIdleTimeHandler::kMaxNoProgressIdleTimes; i++) {
     GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
     EXPECT_EQ(DO_NOTHING, action.type);
   }
@@ -669,50 +417,11 @@ TEST_F(GCIdleTimeHandlerTest, DoneIfNotMakingProgressOnIncrementalMarking) {
 
   // Simulate incremental marking stopped and not eligible to start.
   heap_state.incremental_marking_stopped = true;
-  heap_state.can_start_incremental_marking = false;
   double idle_time_ms = 10.0;
-  for (int i = 0; i < GCIdleTimeHandler::kMaxNoProgressIdleTimesPerMode; i++) {
-    GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-    EXPECT_EQ(DO_NOTHING, action.type);
-  }
-  // We should return DONE after not making progress for some time.
+  // We should return DONE if we cannot start incremental marking.
   GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
   EXPECT_EQ(DONE, action.type);
 }
 
-
-TEST_F(GCIdleTimeHandlerTest, BackgroundReduceLatencyToReduceMemory) {
-  GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
-  heap_state.incremental_marking_stopped = false;
-  heap_state.can_start_incremental_marking = true;
-  double idle_time_ms = GCIdleTimeHandler::kMinBackgroundIdleTime;
-  handler()->NotifyScavenge();
-  EXPECT_EQ(GCIdleTimeHandler::kReduceLatency, handler()->mode());
-  int limit =
-      GCIdleTimeHandler::kBackgroundIdleNotificationsBeforeMutatorIsIdle;
-  for (int i = 0; i < limit; i++) {
-    GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-    EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
-  }
-  handler()->Compute(idle_time_ms, heap_state);
-  EXPECT_EQ(GCIdleTimeHandler::kReduceMemory, handler()->mode());
-}
-
-
-TEST_F(GCIdleTimeHandlerTest, SkipUselessGCs) {
-  GCIdleTimeHandler::HeapState heap_state = DefaultHeapState();
-  heap_state.incremental_marking_stopped = false;
-  heap_state.can_start_incremental_marking = true;
-  TransitionToReduceMemoryMode(heap_state);
-  EXPECT_EQ(GCIdleTimeHandler::kReduceMemory, handler()->mode());
-  double idle_time_ms = GCIdleTimeHandler::kMinLongIdleTime;
-  GCIdleTimeAction action = handler()->Compute(idle_time_ms, heap_state);
-  EXPECT_EQ(DO_INCREMENTAL_MARKING, action.type);
-  handler()->NotifyMarkCompact(false);
-  handler()->NotifyIdleMarkCompact();
-  action = handler()->Compute(idle_time_ms, heap_state);
-  EXPECT_EQ(DONE, action.type);
-}
-
 }  // namespace internal
 }  // namespace v8
diff --git a/test/unittests/heap/memory-reducer-unittest.cc b/test/unittests/heap/memory-reducer-unittest.cc
new file mode 100644 (file)
index 0000000..7f6785d
--- /dev/null
@@ -0,0 +1,237 @@
+// Copyright 2014 the V8 project 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 <limits>
+
+#include "src/flags.h"
+#include "src/heap/memory-reducer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace v8 {
+namespace internal {
+
+MemoryReducer::State DoneState() {
+  return MemoryReducer::State(MemoryReducer::kDone, 0, 0.0);
+}
+
+
+MemoryReducer::State WaitState(int started_gcs, double next_gc_start_ms) {
+  return MemoryReducer::State(MemoryReducer::kWait, started_gcs,
+                              next_gc_start_ms);
+}
+
+
+MemoryReducer::State RunState(int started_gcs, double next_gc_start_ms) {
+  return MemoryReducer::State(MemoryReducer::kRun, started_gcs,
+                              next_gc_start_ms);
+}
+
+
+MemoryReducer::Event MarkCompactEvent(double time_ms,
+                                      bool next_gc_likely_to_collect_more) {
+  MemoryReducer::Event event;
+  event.type = MemoryReducer::kMarkCompact;
+  event.time_ms = time_ms;
+  event.next_gc_likely_to_collect_more = next_gc_likely_to_collect_more;
+  return event;
+}
+
+
+MemoryReducer::Event MarkCompactEventGarbageLeft(double time_ms) {
+  return MarkCompactEvent(time_ms, true);
+}
+
+
+MemoryReducer::Event MarkCompactEventNoGarbageLeft(double time_ms) {
+  return MarkCompactEvent(time_ms, false);
+}
+
+
+MemoryReducer::Event TimerEvent(double time_ms, bool low_allocation_rate,
+                                bool can_start_incremental_gc) {
+  MemoryReducer::Event event;
+  event.type = MemoryReducer::kTimer;
+  event.time_ms = time_ms;
+  event.low_allocation_rate = low_allocation_rate;
+  event.can_start_incremental_gc = can_start_incremental_gc;
+  return event;
+}
+
+
+MemoryReducer::Event TimerEventLowAllocationRate(double time_ms) {
+  return TimerEvent(time_ms, true, true);
+}
+
+
+MemoryReducer::Event TimerEventHighAllocationRate(double time_ms) {
+  return TimerEvent(time_ms, false, true);
+}
+
+
+MemoryReducer::Event TimerEventPendingGC(double time_ms) {
+  return TimerEvent(time_ms, true, false);
+}
+
+
+MemoryReducer::Event ContextDisposedEvent(double time_ms) {
+  MemoryReducer::Event event;
+  event.type = MemoryReducer::kContextDisposed;
+  event.time_ms = time_ms;
+  return event;
+}
+
+
+TEST(MemoryReducer, FromDoneToDone) {
+  MemoryReducer::State state0(DoneState()), state1(DoneState());
+
+  state1 = MemoryReducer::Step(state0, TimerEventLowAllocationRate(0));
+  EXPECT_EQ(MemoryReducer::kDone, state1.action);
+
+  state1 = MemoryReducer::Step(state0, TimerEventHighAllocationRate(0));
+  EXPECT_EQ(MemoryReducer::kDone, state1.action);
+
+  state1 = MemoryReducer::Step(state0, TimerEventPendingGC(0));
+  EXPECT_EQ(MemoryReducer::kDone, state1.action);
+}
+
+
+TEST(MemoryReducer, FromDoneToWait) {
+  if (!FLAG_incremental_marking) return;
+
+  MemoryReducer::State state0(DoneState()), state1(DoneState());
+
+  state1 = MemoryReducer::Step(state0, MarkCompactEventGarbageLeft(0));
+  EXPECT_EQ(MemoryReducer::kWait, state1.action);
+  EXPECT_EQ(MemoryReducer::kLongDelayMs, state1.next_gc_start_ms);
+  EXPECT_EQ(0, state1.started_gcs);
+
+  state1 = MemoryReducer::Step(state0, MarkCompactEventNoGarbageLeft(0));
+  EXPECT_EQ(MemoryReducer::kWait, state1.action);
+  EXPECT_EQ(MemoryReducer::kLongDelayMs, state1.next_gc_start_ms);
+  EXPECT_EQ(0, state1.started_gcs);
+
+  state1 = MemoryReducer::Step(state0, ContextDisposedEvent(0));
+  EXPECT_EQ(MemoryReducer::kWait, state1.action);
+  EXPECT_EQ(MemoryReducer::kLongDelayMs, state1.next_gc_start_ms);
+  EXPECT_EQ(0, state1.started_gcs);
+}
+
+
+TEST(MemoryReducer, FromWaitToWait) {
+  if (!FLAG_incremental_marking) return;
+
+  MemoryReducer::State state0(WaitState(2, 1000.0)), state1(DoneState());
+
+  state1 = MemoryReducer::Step(state0, ContextDisposedEvent(2000));
+  EXPECT_EQ(MemoryReducer::kWait, state1.action);
+  EXPECT_EQ(state0.next_gc_start_ms, state1.next_gc_start_ms);
+  EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+
+  state1 = MemoryReducer::Step(
+      state0, TimerEventLowAllocationRate(state0.next_gc_start_ms - 1));
+  EXPECT_EQ(MemoryReducer::kWait, state1.action);
+  EXPECT_EQ(state0.next_gc_start_ms, state1.next_gc_start_ms);
+  EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+
+  state1 = MemoryReducer::Step(state0, TimerEventHighAllocationRate(2000));
+  EXPECT_EQ(MemoryReducer::kWait, state1.action);
+  EXPECT_EQ(2000 + MemoryReducer::kLongDelayMs, state1.next_gc_start_ms);
+  EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+
+  state1 = MemoryReducer::Step(state0, TimerEventPendingGC(2000));
+  EXPECT_EQ(MemoryReducer::kWait, state1.action);
+  EXPECT_EQ(2000 + MemoryReducer::kLongDelayMs, state1.next_gc_start_ms);
+  EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+
+  state1 = MemoryReducer::Step(state0, MarkCompactEventGarbageLeft(2000));
+  EXPECT_EQ(MemoryReducer::kWait, state1.action);
+  EXPECT_EQ(2000 + MemoryReducer::kLongDelayMs, state1.next_gc_start_ms);
+  EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+
+  state1 = MemoryReducer::Step(state0, MarkCompactEventNoGarbageLeft(2000));
+  EXPECT_EQ(MemoryReducer::kWait, state1.action);
+  EXPECT_EQ(2000 + MemoryReducer::kLongDelayMs, state1.next_gc_start_ms);
+  EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+}
+
+
+TEST(MemoryReducer, FromWaitToRun) {
+  if (!FLAG_incremental_marking) return;
+
+  MemoryReducer::State state0(WaitState(0, 1000.0)), state1(DoneState());
+
+  state1 = MemoryReducer::Step(
+      state0, TimerEventLowAllocationRate(state0.next_gc_start_ms + 1));
+  EXPECT_EQ(MemoryReducer::kRun, state1.action);
+  EXPECT_EQ(0, state1.next_gc_start_ms);
+  EXPECT_EQ(state0.started_gcs + 1, state1.started_gcs);
+}
+
+
+TEST(MemoryReducer, FromRunToRun) {
+  if (!FLAG_incremental_marking) return;
+
+  MemoryReducer::State state0(RunState(1, 0.0)), state1(DoneState());
+
+  state1 = MemoryReducer::Step(state0, TimerEventLowAllocationRate(2000));
+  EXPECT_EQ(MemoryReducer::kRun, state1.action);
+  EXPECT_EQ(state0.next_gc_start_ms, state1.next_gc_start_ms);
+  EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+
+  state1 = MemoryReducer::Step(state0, TimerEventHighAllocationRate(2000));
+  EXPECT_EQ(MemoryReducer::kRun, state1.action);
+  EXPECT_EQ(state0.next_gc_start_ms, state1.next_gc_start_ms);
+  EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+
+  state1 = MemoryReducer::Step(state0, TimerEventPendingGC(2000));
+  EXPECT_EQ(MemoryReducer::kRun, state1.action);
+  EXPECT_EQ(state0.next_gc_start_ms, state1.next_gc_start_ms);
+  EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+
+  state1 = MemoryReducer::Step(state0, ContextDisposedEvent(2000));
+  EXPECT_EQ(MemoryReducer::kRun, state1.action);
+  EXPECT_EQ(state0.next_gc_start_ms, state1.next_gc_start_ms);
+  EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+}
+
+
+TEST(MemoryReducer, FromRunToDone) {
+  if (!FLAG_incremental_marking) return;
+
+  MemoryReducer::State state0(RunState(2, 0.0)), state1(DoneState());
+
+  state1 = MemoryReducer::Step(state0, MarkCompactEventNoGarbageLeft(2000));
+  EXPECT_EQ(MemoryReducer::kDone, state1.action);
+  EXPECT_EQ(0, state1.next_gc_start_ms);
+  EXPECT_EQ(0, state1.started_gcs);
+
+  state0.started_gcs = MemoryReducer::kMaxNumberOfGCs;
+
+  state1 = MemoryReducer::Step(state0, MarkCompactEventGarbageLeft(2000));
+  EXPECT_EQ(MemoryReducer::kDone, state1.action);
+  EXPECT_EQ(0, state1.next_gc_start_ms);
+  EXPECT_EQ(0, state1.started_gcs);
+}
+
+
+TEST(MemoryReducer, FromRunToWait) {
+  if (!FLAG_incremental_marking) return;
+
+  MemoryReducer::State state0(RunState(2, 0.0)), state1(DoneState());
+
+  state1 = MemoryReducer::Step(state0, MarkCompactEventGarbageLeft(2000));
+  EXPECT_EQ(MemoryReducer::kWait, state1.action);
+  EXPECT_EQ(2000 + MemoryReducer::kShortDelayMs, state1.next_gc_start_ms);
+  EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+
+  state0.started_gcs = 1;
+
+  state1 = MemoryReducer::Step(state0, MarkCompactEventNoGarbageLeft(2000));
+  EXPECT_EQ(MemoryReducer::kWait, state1.action);
+  EXPECT_EQ(2000 + MemoryReducer::kShortDelayMs, state1.next_gc_start_ms);
+  EXPECT_EQ(state0.started_gcs, state1.started_gcs);
+}
+
+}  // namespace internal
+}  // namespace v8
index 09159e6..c7de2bb 100644 (file)
@@ -91,6 +91,7 @@
         'libplatform/task-queue-unittest.cc',
         'libplatform/worker-thread-unittest.cc',
         'heap/gc-idle-time-handler-unittest.cc',
+        'heap/memory-reducer-unittest.cc',
         'heap/heap-unittest.cc',
         'run-all-unittests.cc',
         'test-utils.h',
index b1a30fd..1801525 100644 (file)
         '../../src/heap-snapshot-generator-inl.h',
         '../../src/heap-snapshot-generator.cc',
         '../../src/heap-snapshot-generator.h',
+        '../../src/heap/memory-reducer.cc',
+        '../../src/heap/memory-reducer.h',
         '../../src/heap/gc-idle-time-handler.cc',
         '../../src/heap/gc-idle-time-handler.h',
         '../../src/heap/gc-tracer.cc',