Bring Time(Delta)::Min/Max() and related helpers to V8.

Copied as-is modulo compile tweaks from Chromium's base.

Copied tests highlighting existing overflow issues with V8's impl...

TimeDelta::Max() will initially be used in V8 to flag events that
never triggered in a TimedHistogram.

Also constexpr'ed a few things while I was in there, it's harmless
at worst and helps a little at best.
Ideally would constexpr all the Time*::From*() methods like in
Chromium but that has inlining implications and I don't know the
impact that could have on V8.

Bug: chromium:807606
Change-Id: If5aa92759d985be070e12af4dd20f0159169048b
Reviewed-on: https://chromium-review.googlesource.com/899342
Reviewed-by: Hannes Payer <hpayer@chromium.org>
Commit-Queue: Gabriel Charette <gab@chromium.org>
Cr-Commit-Position: refs/heads/master@{#51073}
This commit is contained in:
Gabriel Charette 2018-02-02 15:52:03 +01:00 committed by Commit Bot
parent 7a0f8052d0
commit db73d446b9
3 changed files with 309 additions and 33 deletions

View File

@ -143,41 +143,83 @@ TimeDelta TimeDelta::FromNanoseconds(int64_t nanoseconds) {
int TimeDelta::InDays() const {
if (IsMax()) {
// Preserve max to prevent overflow.
return std::numeric_limits<int>::max();
}
return static_cast<int>(delta_ / Time::kMicrosecondsPerDay);
}
int TimeDelta::InHours() const {
if (IsMax()) {
// Preserve max to prevent overflow.
return std::numeric_limits<int>::max();
}
return static_cast<int>(delta_ / Time::kMicrosecondsPerHour);
}
int TimeDelta::InMinutes() const {
if (IsMax()) {
// Preserve max to prevent overflow.
return std::numeric_limits<int>::max();
}
return static_cast<int>(delta_ / Time::kMicrosecondsPerMinute);
}
double TimeDelta::InSecondsF() const {
if (IsMax()) {
// Preserve max to prevent overflow.
return std::numeric_limits<double>::infinity();
}
return static_cast<double>(delta_) / Time::kMicrosecondsPerSecond;
}
int64_t TimeDelta::InSeconds() const {
if (IsMax()) {
// Preserve max to prevent overflow.
return std::numeric_limits<int64_t>::max();
}
return delta_ / Time::kMicrosecondsPerSecond;
}
double TimeDelta::InMillisecondsF() const {
if (IsMax()) {
// Preserve max to prevent overflow.
return std::numeric_limits<double>::infinity();
}
return static_cast<double>(delta_) / Time::kMicrosecondsPerMillisecond;
}
int64_t TimeDelta::InMilliseconds() const {
if (IsMax()) {
// Preserve max to prevent overflow.
return std::numeric_limits<int64_t>::max();
}
return delta_ / Time::kMicrosecondsPerMillisecond;
}
int64_t TimeDelta::InMillisecondsRoundedUp() const {
if (IsMax()) {
// Preserve max to prevent overflow.
return std::numeric_limits<int64_t>::max();
}
return (delta_ + Time::kMicrosecondsPerMillisecond - 1) /
Time::kMicrosecondsPerMillisecond;
}
int64_t TimeDelta::InMicroseconds() const {
if (IsMax()) {
// Preserve max to prevent overflow.
return std::numeric_limits<int64_t>::max();
}
return delta_;
}
int64_t TimeDelta::InNanoseconds() const {
if (IsMax()) {
// Preserve max to prevent overflow.
return std::numeric_limits<int64_t>::max();
}
return delta_ * Time::kNanosecondsPerMicrosecond;
}

View File

@ -47,7 +47,7 @@ class TimeBase;
class V8_BASE_EXPORT TimeDelta final {
public:
TimeDelta() : delta_(0) {}
constexpr TimeDelta() : delta_(0) {}
// Converts units of time to TimeDeltas.
static TimeDelta FromDays(int days);
@ -60,6 +60,27 @@ class V8_BASE_EXPORT TimeDelta final {
}
static TimeDelta FromNanoseconds(int64_t nanoseconds);
// Returns the maximum time delta, which should be greater than any reasonable
// time delta we might compare it to. Adding or subtracting the maximum time
// delta to a time or another time delta has an undefined result.
static constexpr TimeDelta Max();
// Returns the minimum time delta, which should be less than than any
// reasonable time delta we might compare it to. Adding or subtracting the
// minimum time delta to a time or another time delta has an undefined result.
static constexpr TimeDelta Min();
// Returns true if the time delta is zero.
constexpr bool IsZero() const { return delta_ == 0; }
// Returns true if the time delta is the maximum/minimum time delta.
constexpr bool IsMax() const {
return delta_ == std::numeric_limits<int64_t>::max();
}
constexpr bool IsMin() const {
return delta_ == std::numeric_limits<int64_t>::min();
}
// Returns the time delta in some unit. The F versions return a floating
// point value, the "regular" versions return a rounded-down value.
//
@ -73,7 +94,7 @@ class V8_BASE_EXPORT TimeDelta final {
double InMillisecondsF() const;
int64_t InMilliseconds() const;
int64_t InMillisecondsRoundedUp() const;
int64_t InMicroseconds() const { return delta_; }
int64_t InMicroseconds() const;
int64_t InNanoseconds() const;
// Converts to/from Mach time specs.
@ -105,9 +126,7 @@ class V8_BASE_EXPORT TimeDelta final {
delta_ -= other.delta_;
return *this;
}
TimeDelta operator-() const {
return TimeDelta(-delta_);
}
constexpr TimeDelta operator-() const { return TimeDelta(-delta_); }
double TimesOf(const TimeDelta& other) const {
return static_cast<double>(delta_) / static_cast<double>(other.delta_);
@ -137,22 +156,22 @@ class V8_BASE_EXPORT TimeDelta final {
}
// Comparison operators.
bool operator==(const TimeDelta& other) const {
constexpr bool operator==(const TimeDelta& other) const {
return delta_ == other.delta_;
}
bool operator!=(const TimeDelta& other) const {
constexpr bool operator!=(const TimeDelta& other) const {
return delta_ != other.delta_;
}
bool operator<(const TimeDelta& other) const {
constexpr bool operator<(const TimeDelta& other) const {
return delta_ < other.delta_;
}
bool operator<=(const TimeDelta& other) const {
constexpr bool operator<=(const TimeDelta& other) const {
return delta_ <= other.delta_;
}
bool operator>(const TimeDelta& other) const {
constexpr bool operator>(const TimeDelta& other) const {
return delta_ > other.delta_;
}
bool operator>=(const TimeDelta& other) const {
constexpr bool operator>=(const TimeDelta& other) const {
return delta_ >= other.delta_;
}
@ -161,12 +180,21 @@ class V8_BASE_EXPORT TimeDelta final {
// Constructs a delta given the duration in microseconds. This is private
// to avoid confusion by callers with an integer constructor. Use
// FromSeconds, FromMilliseconds, etc. instead.
explicit TimeDelta(int64_t delta) : delta_(delta) {}
explicit constexpr TimeDelta(int64_t delta) : delta_(delta) {}
// Delta in microseconds.
int64_t delta_;
};
// static
constexpr TimeDelta TimeDelta::Max() {
return TimeDelta(std::numeric_limits<int64_t>::max());
}
// static
constexpr TimeDelta TimeDelta::Min() {
return TimeDelta(std::numeric_limits<int64_t>::min());
}
namespace time_internal {
@ -207,12 +235,24 @@ class TimeBase {
// Warning: Be careful when writing code that performs math on time values,
// since it's possible to produce a valid "zero" result that should not be
// interpreted as a "null" value.
bool IsNull() const {
return us_ == 0;
constexpr bool IsNull() const { return us_ == 0; }
// Returns the maximum/minimum times, which should be greater/less than any
// reasonable time with which we might compare it.
static TimeClass Max() {
return TimeClass(std::numeric_limits<int64_t>::max());
}
static TimeClass Min() {
return TimeClass(std::numeric_limits<int64_t>::min());
}
// Returns true if this object represents the maximum time.
bool IsMax() const { return us_ == std::numeric_limits<int64_t>::max(); }
// Returns true if this object represents the maximum/minimum time.
constexpr bool IsMax() const {
return us_ == std::numeric_limits<int64_t>::max();
}
constexpr bool IsMin() const {
return us_ == std::numeric_limits<int64_t>::min();
}
// For serializing only. Use FromInternalValue() to reconstitute. Please don't
// use this and do arithmetic on it, as it is more error prone than using the
@ -272,7 +312,7 @@ class TimeBase {
static TimeClass FromInternalValue(int64_t us) { return TimeClass(us); }
protected:
explicit TimeBase(int64_t us) : us_(us) {}
explicit constexpr TimeBase(int64_t us) : us_(us) {}
// Time value in a microsecond timebase.
int64_t us_;
@ -290,7 +330,7 @@ class TimeBase {
class V8_BASE_EXPORT Time final : public time_internal::TimeBase<Time> {
public:
// Contains the nullptr time. Use Time::Now() to get the current time.
Time() : TimeBase(0) {}
constexpr Time() : TimeBase(0) {}
// Returns the current time. Watch out, the system might adjust its clock
// in which case time will actually go backwards. We don't guarantee that
@ -306,10 +346,6 @@ class V8_BASE_EXPORT Time final : public time_internal::TimeBase<Time> {
// Returns the time for epoch in Unix-like system (Jan 1, 1970).
static Time UnixEpoch() { return Time(0); }
// Returns the maximum time, which should be greater than any reasonable time
// with which we might compare it.
static Time Max() { return Time(std::numeric_limits<int64_t>::max()); }
// Converts to/from POSIX time specs.
static Time FromTimespec(struct timespec ts);
struct timespec ToTimespec() const;
@ -329,7 +365,7 @@ class V8_BASE_EXPORT Time final : public time_internal::TimeBase<Time> {
private:
friend class time_internal::TimeBase<Time>;
explicit Time(int64_t us) : TimeBase(us) {}
explicit constexpr Time(int64_t us) : TimeBase(us) {}
};
V8_BASE_EXPORT std::ostream& operator<<(std::ostream&, const Time&);
@ -352,7 +388,7 @@ inline Time operator+(const TimeDelta& delta, const Time& time) {
class V8_BASE_EXPORT TimeTicks final
: public time_internal::TimeBase<TimeTicks> {
public:
TimeTicks() : TimeBase(0) {}
constexpr TimeTicks() : TimeBase(0) {}
// Platform-dependent tick count representing "right now." When
// IsHighResolution() returns false, the resolution of the clock could be as
@ -374,7 +410,7 @@ class V8_BASE_EXPORT TimeTicks final
// Please use Now() to create a new object. This is for internal use
// and testing. Ticks are in microseconds.
explicit TimeTicks(int64_t ticks) : TimeBase(ticks) {}
explicit constexpr TimeTicks(int64_t ticks) : TimeBase(ticks) {}
};
inline TimeTicks operator+(const TimeDelta& delta, const TimeTicks& ticks) {
@ -389,7 +425,7 @@ inline TimeTicks operator+(const TimeDelta& delta, const TimeTicks& ticks) {
class V8_BASE_EXPORT ThreadTicks final
: public time_internal::TimeBase<ThreadTicks> {
public:
ThreadTicks() : TimeBase(0) {}
constexpr ThreadTicks() : TimeBase(0) {}
// Returns true if ThreadTicks::Now() is supported on this system.
static bool IsSupported();
@ -424,7 +460,7 @@ class V8_BASE_EXPORT ThreadTicks final
// Please use Now() or GetForThread() to create a new object. This is for
// internal use and testing. Ticks are in microseconds.
explicit ThreadTicks(int64_t ticks) : TimeBase(ticks) {}
explicit constexpr ThreadTicks(int64_t ticks) : TimeBase(ticks) {}
#if V8_OS_WIN
// Returns the frequency of the TSC in ticks per second, or 0 if it hasn't

View File

@ -24,6 +24,163 @@
namespace v8 {
namespace base {
TEST(TimeDelta, ZeroMinMax) {
constexpr TimeDelta kZero;
static_assert(kZero.IsZero(), "");
constexpr TimeDelta kMax = TimeDelta::Max();
static_assert(kMax.IsMax(), "");
static_assert(kMax == TimeDelta::Max(), "");
EXPECT_GT(kMax, TimeDelta::FromDays(100 * 365));
static_assert(kMax > kZero, "");
constexpr TimeDelta kMin = TimeDelta::Min();
static_assert(kMin.IsMin(), "");
static_assert(kMin == TimeDelta::Min(), "");
EXPECT_LT(kMin, TimeDelta::FromDays(-100 * 365));
static_assert(kMin < kZero, "");
}
TEST(TimeDelta, MaxConversions) {
// static_assert also confirms constexpr works as intended.
constexpr TimeDelta kMax = TimeDelta::Max();
EXPECT_EQ(kMax.InDays(), std::numeric_limits<int>::max());
EXPECT_EQ(kMax.InHours(), std::numeric_limits<int>::max());
EXPECT_EQ(kMax.InMinutes(), std::numeric_limits<int>::max());
EXPECT_EQ(kMax.InSecondsF(), std::numeric_limits<double>::infinity());
EXPECT_EQ(kMax.InSeconds(), std::numeric_limits<int64_t>::max());
EXPECT_EQ(kMax.InMillisecondsF(), std::numeric_limits<double>::infinity());
EXPECT_EQ(kMax.InMilliseconds(), std::numeric_limits<int64_t>::max());
EXPECT_EQ(kMax.InMillisecondsRoundedUp(),
std::numeric_limits<int64_t>::max());
// TODO(v8-team): Import overflow support from Chromium's base.
// EXPECT_TRUE(TimeDelta::FromDays(std::numeric_limits<int>::max()).IsMax());
// EXPECT_TRUE(
// TimeDelta::FromHours(std::numeric_limits<int>::max()).IsMax());
// EXPECT_TRUE(
// TimeDelta::FromMinutes(std::numeric_limits<int>::max()).IsMax());
// constexpr int64_t max_int = std::numeric_limits<int64_t>::max();
// constexpr int64_t min_int = std::numeric_limits<int64_t>::min();
// EXPECT_TRUE(
// TimeDelta::FromSeconds(max_int / Time::kMicrosecondsPerSecond + 1)
// .IsMax());
// EXPECT_TRUE(TimeDelta::FromMilliseconds(
// max_int / Time::kMillisecondsPerSecond + 1)
// .IsMax());
// EXPECT_TRUE(TimeDelta::FromMicroseconds(max_int).IsMax());
// EXPECT_TRUE(
// TimeDelta::FromSeconds(min_int / Time::kMicrosecondsPerSecond - 1)
// .IsMin());
// EXPECT_TRUE(TimeDelta::FromMilliseconds(
// min_int / Time::kMillisecondsPerSecond - 1)
// .IsMin());
// EXPECT_TRUE(TimeDelta::FromMicroseconds(min_int).IsMin());
// EXPECT_TRUE(
// TimeDelta::FromMicroseconds(std::numeric_limits<int64_t>::min())
// .IsMin());
}
TEST(TimeDelta, NumericOperators) {
constexpr int i = 2;
EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
(TimeDelta::FromMilliseconds(1000) * i));
EXPECT_EQ(TimeDelta::FromMilliseconds(500),
(TimeDelta::FromMilliseconds(1000) / i));
EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
(TimeDelta::FromMilliseconds(1000) *= i));
EXPECT_EQ(TimeDelta::FromMilliseconds(500),
(TimeDelta::FromMilliseconds(1000) /= i));
constexpr int64_t i64 = 2;
EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
(TimeDelta::FromMilliseconds(1000) * i64));
EXPECT_EQ(TimeDelta::FromMilliseconds(500),
(TimeDelta::FromMilliseconds(1000) / i64));
EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
(TimeDelta::FromMilliseconds(1000) *= i64));
EXPECT_EQ(TimeDelta::FromMilliseconds(500),
(TimeDelta::FromMilliseconds(1000) /= i64));
EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
(TimeDelta::FromMilliseconds(1000) * 2));
EXPECT_EQ(TimeDelta::FromMilliseconds(500),
(TimeDelta::FromMilliseconds(1000) / 2));
EXPECT_EQ(TimeDelta::FromMilliseconds(2000),
(TimeDelta::FromMilliseconds(1000) *= 2));
EXPECT_EQ(TimeDelta::FromMilliseconds(500),
(TimeDelta::FromMilliseconds(1000) /= 2));
}
// TODO(v8-team): Import support for overflow from Chromium's base.
TEST(TimeDelta, DISABLED_Overflows) {
// Some sanity checks. static_assert's used were possible to verify constexpr
// evaluation at the same time.
static_assert(TimeDelta::Max().IsMax(), "");
static_assert(-TimeDelta::Max() < TimeDelta(), "");
static_assert(-TimeDelta::Max() > TimeDelta::Min(), "");
static_assert(TimeDelta() > -TimeDelta::Max(), "");
TimeDelta large_delta = TimeDelta::Max() - TimeDelta::FromMilliseconds(1);
TimeDelta large_negative = -large_delta;
EXPECT_GT(TimeDelta(), large_negative);
EXPECT_FALSE(large_delta.IsMax());
EXPECT_FALSE((-large_negative).IsMin());
const TimeDelta kOneSecond = TimeDelta::FromSeconds(1);
// Test +, -, * and / operators.
EXPECT_TRUE((large_delta + kOneSecond).IsMax());
EXPECT_TRUE((large_negative + (-kOneSecond)).IsMin());
EXPECT_TRUE((large_negative - kOneSecond).IsMin());
EXPECT_TRUE((large_delta - (-kOneSecond)).IsMax());
EXPECT_TRUE((large_delta * 2).IsMax());
EXPECT_TRUE((large_delta * -2).IsMin());
// Test +=, -=, *= and /= operators.
TimeDelta delta = large_delta;
delta += kOneSecond;
EXPECT_TRUE(delta.IsMax());
delta = large_negative;
delta += -kOneSecond;
EXPECT_TRUE((delta).IsMin());
delta = large_negative;
delta -= kOneSecond;
EXPECT_TRUE((delta).IsMin());
delta = large_delta;
delta -= -kOneSecond;
EXPECT_TRUE(delta.IsMax());
delta = large_delta;
delta *= 2;
EXPECT_TRUE(delta.IsMax());
// Test operations with Time and TimeTicks.
EXPECT_TRUE((large_delta + Time::Now()).IsMax());
EXPECT_TRUE((large_delta + TimeTicks::Now()).IsMax());
EXPECT_TRUE((Time::Now() + large_delta).IsMax());
EXPECT_TRUE((TimeTicks::Now() + large_delta).IsMax());
Time time_now = Time::Now();
EXPECT_EQ(kOneSecond, (time_now + kOneSecond) - time_now);
EXPECT_EQ(-kOneSecond, (time_now - kOneSecond) - time_now);
TimeTicks ticks_now = TimeTicks::Now();
EXPECT_EQ(-kOneSecond, (ticks_now - kOneSecond) - ticks_now);
EXPECT_EQ(kOneSecond, (ticks_now + kOneSecond) - ticks_now);
}
TEST(TimeDelta, FromAndIn) {
EXPECT_EQ(TimeDelta::FromDays(2), TimeDelta::FromHours(48));
EXPECT_EQ(TimeDelta::FromHours(3), TimeDelta::FromMinutes(180));
@ -54,6 +211,47 @@ TEST(TimeDelta, MachTimespec) {
}
#endif
TEST(Time, Max) {
Time max = Time::Max();
EXPECT_TRUE(max.IsMax());
EXPECT_EQ(max, Time::Max());
EXPECT_GT(max, Time::Now());
EXPECT_GT(max, Time());
}
TEST(Time, MaxConversions) {
Time t = Time::Max();
EXPECT_EQ(std::numeric_limits<int64_t>::max(), t.ToInternalValue());
// TODO(v8-team): Time::FromJsTime() overflows with infinity. Import support
// from Chromium's base.
// t = Time::FromJsTime(std::numeric_limits<double>::infinity());
// EXPECT_TRUE(t.IsMax());
// EXPECT_EQ(std::numeric_limits<double>::infinity(), t.ToJsTime());
#if defined(OS_POSIX)
struct timeval tval;
tval.tv_sec = std::numeric_limits<time_t>::max();
tval.tv_usec = static_cast<suseconds_t>(Time::kMicrosecondsPerSecond) - 1;
t = Time::FromTimeVal(tval);
EXPECT_TRUE(t.IsMax());
tval = t.ToTimeVal();
EXPECT_EQ(std::numeric_limits<time_t>::max(), tval.tv_sec);
EXPECT_EQ(static_cast<suseconds_t>(Time::kMicrosecondsPerSecond) - 1,
tval.tv_usec);
#endif
#if defined(OS_WIN)
FILETIME ftime;
ftime.dwHighDateTime = std::numeric_limits<DWORD>::max();
ftime.dwLowDateTime = std::numeric_limits<DWORD>::max();
t = Time::FromFileTime(ftime);
EXPECT_TRUE(t.IsMax());
ftime = t.ToFileTime();
EXPECT_EQ(std::numeric_limits<DWORD>::max(), ftime.dwHighDateTime);
EXPECT_EQ(std::numeric_limits<DWORD>::max(), ftime.dwLowDateTime);
#endif
}
TEST(Time, JsTime) {
Time t = Time::FromJsTime(700000.3);