Always use timeGetTime() for TimeTicks::Now() on Windows.
authorbmeurer@chromium.org <bmeurer@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Wed, 2 Oct 2013 13:30:31 +0000 (13:30 +0000)
committerbmeurer@chromium.org <bmeurer@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Wed, 2 Oct 2013 13:30:31 +0000 (13:30 +0000)
This way, we also ensure that timeGetTime() is used for Time::Now(),
and thereby Date.now() even if GetTickCount64() is available.

Also add test coverage for Time::Now(), TimeTicks::Now() and
TimeTicks::HighResNow().

BUG=chromium:288924
TEST=cctest/test-timer
R=hpayer@chromium.org

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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@17080 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/platform-win32.cc
src/platform/elapsed-timer.h
src/platform/time.cc
src/platform/time.h
src/utils/random-number-generator.cc
test/cctest/test-time.cc
test/mjsunit/mjsunit.status
test/mjsunit/timer.js [deleted file]

index 3283dfa..35411bf 100644 (file)
@@ -595,9 +595,7 @@ int OS::GetUserTime(uint32_t* secs,  uint32_t* usecs) {
 // Returns current time as the number of milliseconds since
 // 00:00:00 UTC, January 1, 1970.
 double OS::TimeCurrentMillis() {
-  Win32Time t;
-  t.SetToCurrentTime();
-  return t.ToJSTime();
+  return Time::Now().ToJsTime();
 }
 
 
index 2311db2..9016a22 100644 (file)
@@ -104,7 +104,7 @@ class ElapsedTimer V8_FINAL BASE_EMBEDDED {
 
  private:
   static V8_INLINE TimeTicks Now() {
-    TimeTicks now = TimeTicks::HighResNow();
+    TimeTicks now = TimeTicks::HighResolutionNow();
     ASSERT(!now.IsNull());
     return now;
   }
index ea6dd2c..de0ca16 100644 (file)
 #include "win32-headers.h"
 #endif
 
-#if V8_OS_WIN
-// Prototype for GetTickCount64() procedure.
-extern "C" {
-typedef ULONGLONG (WINAPI *GETTICKCOUNT64PROC)(void);
-}
-#endif
-
 namespace v8 {
 namespace internal {
 
@@ -175,43 +168,43 @@ struct timespec TimeDelta::ToTimespec() const {
 // periodically resync the internal clock to the system clock.
 class Clock V8_FINAL {
  public:
-  Clock() : initial_time_(CurrentWallclockTime()),
-            initial_ticks_(TimeTicks::Now()) {}
+  Clock() : initial_ticks_(GetSystemTicks()), initial_time_(GetSystemTime()) {}
 
   Time Now() {
-    // This must be executed under lock.
-    LockGuard<Mutex> lock_guard(&mutex_);
+    // Time between resampling the un-granular clock for this API (1 minute).
+    const TimeDelta kMaxElapsedTime = TimeDelta::FromMinutes(1);
 
-    // Calculate the time elapsed since we started our timer.
-    TimeDelta elapsed = TimeTicks::Now() - initial_ticks_;
+    LockGuard<Mutex> lock_guard(&mutex_);
 
-    // Check if we don't need to synchronize with the wallclock yet.
-    if (elapsed.InMicroseconds() <= kMaxMicrosecondsToAvoidDrift) {
-      return initial_time_ + elapsed;
+    // Determine current time and ticks.
+    TimeTicks ticks = GetSystemTicks();
+    Time time = GetSystemTime();
+
+    // Check if we need to synchronize with the system clock due to a backwards
+    // time change or the amount of time elapsed.
+    TimeDelta elapsed = ticks - initial_ticks_;
+    if (time < initial_time_ || elapsed > kMaxElapsedTime) {
+      initial_ticks_ = ticks;
+      initial_time_ = time;
+      return time;
     }
 
-    // Resynchronize with the wallclock.
-    initial_ticks_ = TimeTicks::Now();
-    initial_time_ = CurrentWallclockTime();
-    return initial_time_;
+    return initial_time_ + elapsed;
   }
 
   Time NowFromSystemTime() {
-    // This must be executed under lock.
     LockGuard<Mutex> lock_guard(&mutex_);
-
-    // Resynchronize with the wallclock.
-    initial_ticks_ = TimeTicks::Now();
-    initial_time_ = CurrentWallclockTime();
+    initial_ticks_ = GetSystemTicks();
+    initial_time_ = GetSystemTime();
     return initial_time_;
   }
 
  private:
-  // Time between resampling the un-granular clock for this API (1 minute).
-  static const int64_t kMaxMicrosecondsToAvoidDrift =
-      Time::kMicrosecondsPerMinute;
+  static TimeTicks GetSystemTicks() {
+    return TimeTicks::Now();
+  }
 
-  static Time CurrentWallclockTime() {
+  static Time GetSystemTime() {
     FILETIME ft;
     ::GetSystemTimeAsFileTime(&ft);
     return Time::FromFiletime(ft);
@@ -223,9 +216,9 @@ class Clock V8_FINAL {
 };
 
 
-static LazyDynamicInstance<Clock,
-    DefaultCreateTrait<Clock>,
-    ThreadSafeInitOnceTrait>::type clock = LAZY_DYNAMIC_INSTANCE_INITIALIZER;
+static LazyStaticInstance<Clock,
+    DefaultConstructTrait<Clock>,
+    ThreadSafeInitOnceTrait>::type clock = LAZY_STATIC_INSTANCE_INITIALIZER;
 
 
 Time Time::Now() {
@@ -388,6 +381,7 @@ class TickClock {
  public:
   virtual ~TickClock() {}
   virtual int64_t Now() = 0;
+  virtual bool IsHighResolution() = 0;
 };
 
 
@@ -440,42 +434,24 @@ class HighResolutionTickClock V8_FINAL : public TickClock {
     int64_t ticks = (whole_seconds * Time::kMicrosecondsPerSecond) +
         ((leftover_ticks * Time::kMicrosecondsPerSecond) / ticks_per_second_);
 
-    // Make sure we never return 0 here, so that TimeTicks::HighResNow()
+    // Make sure we never return 0 here, so that TimeTicks::HighResolutionNow()
     // will never return 0.
     return ticks + 1;
   }
 
- private:
-  int64_t ticks_per_second_;
-};
-
-
-// The GetTickCount64() API is what we actually want for the regular tick
-// clock, but this is only available starting with Windows Vista.
-class WindowsVistaTickClock V8_FINAL : public TickClock {
- public:
-  explicit WindowsVistaTickClock(GETTICKCOUNT64PROC func) : func_(func) {
-    ASSERT(func_ != NULL);
-  }
-  virtual ~WindowsVistaTickClock() {}
-
-  virtual int64_t Now() V8_OVERRIDE {
-    // Query the current ticks (in ms).
-    ULONGLONG tick_count_ms = (*func_)();
-
-    // Convert to microseconds (make sure to never return 0 here).
-    return (tick_count_ms * Time::kMicrosecondsPerMillisecond) + 1;
+  virtual bool IsHighResolution() V8_OVERRIDE {
+    return true;
   }
 
  private:
-  GETTICKCOUNT64PROC func_;
+  int64_t ticks_per_second_;
 };
 
 
 class RolloverProtectedTickClock V8_FINAL : public TickClock {
  public:
   // We initialize rollover_ms_ to 1 to ensure that we will never
-  // return 0 from TimeTicks::HighResNow() and TimeTicks::Now() below.
+  // return 0 from TimeTicks::HighResolutionNow() and TimeTicks::Now() below.
   RolloverProtectedTickClock() : last_seen_now_(0), rollover_ms_(1) {}
   virtual ~RolloverProtectedTickClock() {}
 
@@ -487,6 +463,9 @@ class RolloverProtectedTickClock V8_FINAL : public TickClock {
     // Note that we do not use GetTickCount() here, since timeGetTime() gives
     // more predictable delta values, as described here:
     // http://blogs.msdn.com/b/larryosterman/archive/2009/09/02/what-s-the-difference-between-gettickcount-and-timegettime.aspx
+    // timeGetTime() provides 1ms granularity when combined with
+    // timeBeginPeriod(). If the host application for V8 wants fast timers, it
+    // can use timeBeginPeriod() to increase the resolution.
     DWORD now = timeGetTime();
     if (now < last_seen_now_) {
       rollover_ms_ += V8_INT64_C(0x100000000);  // ~49.7 days.
@@ -495,6 +474,10 @@ class RolloverProtectedTickClock V8_FINAL : public TickClock {
     return (now + rollover_ms_) * Time::kMicrosecondsPerMillisecond;
   }
 
+  virtual bool IsHighResolution() V8_OVERRIDE {
+    return false;
+  }
+
  private:
   Mutex mutex_;
   DWORD last_seen_now_;
@@ -502,27 +485,10 @@ class RolloverProtectedTickClock V8_FINAL : public TickClock {
 };
 
 
-struct CreateTickClockTrait {
-  static TickClock* Create() {
-    // Try to load GetTickCount64() from kernel32.dll (available since Vista).
-    HMODULE kernel32 = ::GetModuleHandleA("kernel32.dll");
-    ASSERT(kernel32 != NULL);
-    FARPROC proc = ::GetProcAddress(kernel32, "GetTickCount64");
-    if (proc != NULL) {
-      return new WindowsVistaTickClock(
-          reinterpret_cast<GETTICKCOUNT64PROC>(proc));
-    }
-
-    // Fallback to the rollover protected tick clock.
-    return new RolloverProtectedTickClock;
-  }
-};
-
-
-static LazyDynamicInstance<TickClock,
-    CreateTickClockTrait,
+static LazyStaticInstance<RolloverProtectedTickClock,
+    DefaultConstructTrait<RolloverProtectedTickClock>,
     ThreadSafeInitOnceTrait>::type tick_clock =
-        LAZY_DYNAMIC_INSTANCE_INITIALIZER;
+        LAZY_STATIC_INSTANCE_INITIALIZER;
 
 
 struct CreateHighResTickClockTrait {
@@ -560,21 +526,27 @@ TimeTicks TimeTicks::Now() {
 }
 
 
-TimeTicks TimeTicks::HighResNow() {
+TimeTicks TimeTicks::HighResolutionNow() {
   // Make sure we never return 0 here.
   TimeTicks ticks(high_res_tick_clock.Pointer()->Now());
   ASSERT(!ticks.IsNull());
   return ticks;
 }
 
+
+// static
+bool TimeTicks::IsHighResolutionClockWorking() {
+  return high_res_tick_clock.Pointer()->IsHighResolution();
+}
+
 #else  // V8_OS_WIN
 
 TimeTicks TimeTicks::Now() {
-  return HighResNow();
+  return HighResolutionNow();
 }
 
 
-TimeTicks TimeTicks::HighResNow() {
+TimeTicks TimeTicks::HighResolutionNow() {
   int64_t ticks;
 #if V8_OS_MACOSX
   static struct mach_timebase_info info;
@@ -608,6 +580,12 @@ TimeTicks TimeTicks::HighResNow() {
   return TimeTicks(ticks + 1);
 }
 
+
+// static
+bool TimeTicks::IsHighResolutionClockWorking() {
+  return true;
+}
+
 #endif  // V8_OS_WIN
 
 } }  // namespace v8::internal
index 2ce6cdd..25161f8 100644 (file)
@@ -333,7 +333,10 @@ class TimeTicks V8_FINAL BASE_EMBEDDED {
   // resolution.  THIS CALL IS GENERALLY MUCH MORE EXPENSIVE THAN Now() AND
   // SHOULD ONLY BE USED WHEN IT IS REALLY NEEDED.
   // This method never returns a null TimeTicks.
-  static TimeTicks HighResNow();
+  static TimeTicks HighResolutionNow();
+
+  // Returns true if the high-resolution clock is working on this system.
+  static bool IsHighResolutionClockWorking();
 
   // Returns true if this object has not been initialized.
   bool IsNull() const { return ticks_ == 0; }
index 3f657fb..fe27331 100644 (file)
@@ -99,7 +99,7 @@ RandomNumberGenerator::RandomNumberGenerator() {
   // which provides reasonable entropy, see:
   // https://code.google.com/p/v8/issues/detail?id=2905
   int64_t seed = Time::NowFromSystemTime().ToInternalValue() << 24;
-  seed ^= TimeTicks::HighResNow().ToInternalValue() << 16;
+  seed ^= TimeTicks::HighResolutionNow().ToInternalValue() << 16;
   seed ^= TimeTicks::Now().ToInternalValue() << 8;
   SetSeed(seed);
 #endif  // V8_OS_CYGWIN || V8_OS_WIN
index 8b92d8d..28d647a 100644 (file)
@@ -133,7 +133,7 @@ TEST(TimeTicksIsMonotonic) {
   timer.Start();
   while (!timer.HasExpired(TimeDelta::FromMilliseconds(100))) {
     TimeTicks normal_ticks = TimeTicks::Now();
-    TimeTicks highres_ticks = TimeTicks::HighResNow();
+    TimeTicks highres_ticks = TimeTicks::HighResolutionNow();
     CHECK_GE(normal_ticks, previous_normal_ticks);
     CHECK_GE((normal_ticks - previous_normal_ticks).InMicroseconds(), 0);
     CHECK_GE(highres_ticks, previous_highres_ticks);
@@ -142,3 +142,54 @@ TEST(TimeTicksIsMonotonic) {
     previous_highres_ticks = highres_ticks;
   }
 }
+
+
+template <typename T>
+static void ResolutionTest(T (*Now)(), TimeDelta target_granularity) {
+  // We're trying to measure that intervals increment in a VERY small amount
+  // of time -- according to the specified target granularity. Unfortunately,
+  // if we happen to have a context switch in the middle of our test, the
+  // context switch could easily exceed our limit. So, we iterate on this
+  // several times. As long as we're able to detect the fine-granularity
+  // timers at least once, then the test has succeeded.
+  static const TimeDelta kExpirationTimeout = TimeDelta::FromSeconds(1);
+  ElapsedTimer timer;
+  timer.Start();
+  TimeDelta delta;
+  do {
+    T start = Now();
+    T now = start;
+    // Loop until we can detect that the clock has changed. Non-HighRes timers
+    // will increment in chunks, i.e. 15ms. By spinning until we see a clock
+    // change, we detect the minimum time between measurements.
+    do {
+      now = Now();
+      delta = now - start;
+    } while (now <= start);
+    CHECK_NE(static_cast<int64_t>(0), delta.InMicroseconds());
+  } while (delta > target_granularity && !timer.HasExpired(kExpirationTimeout));
+  CHECK_LE(delta, target_granularity);
+}
+
+
+TEST(TimeNowResolution) {
+  // We assume that Time::Now() has at least 16ms resolution.
+  static const TimeDelta kTargetGranularity = TimeDelta::FromMilliseconds(16);
+  ResolutionTest<Time>(&Time::Now, kTargetGranularity);
+}
+
+
+TEST(TimeTicksNowResolution) {
+  // We assume that TimeTicks::Now() has at least 16ms resolution.
+  static const TimeDelta kTargetGranularity = TimeDelta::FromMilliseconds(16);
+  ResolutionTest<TimeTicks>(&TimeTicks::Now, kTargetGranularity);
+}
+
+
+TEST(TimeTicksHighResolutionNowResolution) {
+  if (!TimeTicks::IsHighResolutionClockWorking()) return;
+
+  // We assume that TimeTicks::HighResolutionNow() has sub-ms resolution.
+  static const TimeDelta kTargetGranularity = TimeDelta::FromMilliseconds(1);
+  ResolutionTest<TimeTicks>(&TimeTicks::HighResolutionNow, kTargetGranularity);
+}
index 9a229e8..ec780e5 100644 (file)
   # This test non-deterministically runs out of memory on Windows ia32.
   'regress/regress-crbug-160010': [SKIP],
 
-  # This test fails on Windows XP and Windows Vista.
-  # Issue 288924
-  'timer' : [['system == windows', FAIL]],
-
   ##############################################################################
   # Too slow in debug mode with --stress-opt mode.
   'compiler/regress-stacktrace-methods': [PASS, ['mode == debug', SKIP]],
diff --git a/test/mjsunit/timer.js b/test/mjsunit/timer.js
deleted file mode 100644 (file)
index 0b03550..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2013 the V8 project authors. All rights reserved.
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-//       notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-//       copyright notice, this list of conditions and the following
-//       disclaimer in the documentation and/or other materials provided
-//       with the distribution.
-//     * Neither the name of Google Inc. nor the names of its
-//       contributors may be used to endorse or promote products derived
-//       from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-// Flags: --allow-natives-syntax
-
-// Tests timer milliseconds granularity.
-
-// Don't run this test in gc stress mode. Time differences may be long
-// due to garbage collections.
-%SetFlags("--gc-interval=-1");
-%SetFlags("--nostress-compaction");
-
-(function run() {
-  var start_test = Date.now();
-  // Let the retry run for maximum 100ms to reduce flakiness.
-  for (var start = Date.now(); start - start_test < 100; start = Date.now()) {
-    var end = Date.now();
-    while (end - start == 0) {
-      end = Date.now();
-    }
-    if (end - start == 1) {
-      // Found milliseconds granularity.
-      return;
-    } else {
-      print("Timer difference too big: " + (end - start) + "ms");
-    }
-  }
-  assertTrue(false);
-})()