diff --git a/AK/Tests/CMakeLists.txt b/AK/Tests/CMakeLists.txt index 6314b008638..1aed6c50deb 100644 --- a/AK/Tests/CMakeLists.txt +++ b/AK/Tests/CMakeLists.txt @@ -41,6 +41,7 @@ set(AK_TEST_SOURCES TestString.cpp TestStringUtils.cpp TestStringView.cpp + TestTime.cpp TestTrie.cpp TestTypeTraits.cpp TestTypedTransfer.cpp diff --git a/AK/Tests/TestTime.cpp b/AK/Tests/TestTime.cpp new file mode 100644 index 00000000000..e63fab35cc1 --- /dev/null +++ b/AK/Tests/TestTime.cpp @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2021, Ben Wiederhake + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. 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. + * + * 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 HOLDER 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. + */ + +#include + +#include +#include + +#define EXPECT_TIME(t, s, ns) \ + do { \ + auto ts = (t).to_timespec(); \ + EXPECT_EQ(ts.tv_sec, (s)); \ + EXPECT_EQ(ts.tv_nsec, (ns)); \ + } while (0) + +TEST_CASE(is_sane) +{ + auto t0 = Time::from_seconds(0); + auto t2 = Time::from_seconds(2); + auto t5 = Time::from_seconds(5); + auto tn3 = Time::from_seconds(-3); + EXPECT(t0 == t0); + EXPECT(t2 == t2); + EXPECT(t5 == t5); + EXPECT(t0 != t2); + EXPECT(t2 != tn3); + EXPECT(t2 != t5); + EXPECT_TIME(t0, 0, 0); + EXPECT_TIME(t2, 2, 0); + EXPECT_TIME(t5, 5, 0); + EXPECT_TIME(t2 + t5, 7, 0); + EXPECT_TIME(tn3 + t2, -1, 0); + EXPECT_TIME(tn3 + t5, 2, 0); +} + +TEST_CASE(limits) +{ + EXPECT_TIME(Time::min(), (i64)-0x8000'0000'0000'0000, 0); + EXPECT_TIME(Time::max(), 0x7fff'ffff'ffff'ffff, 999'999'999); +} + +TEST_CASE(seconds_parsing) +{ + EXPECT_TIME(Time::from_seconds(0), 0, 0); + EXPECT_TIME(Time::from_seconds(42), 42, 0); + EXPECT_TIME(Time::from_seconds(-1), -1, 0); + + // "6.4.4.1.5: The type of an integer constant is the first of the corresponding list in which its value can be represented." + // In the case of "0x8000'0000", the list is "int, unsigned int, …", and unsigned int (u32) matches. + // Then the unary minus: On unsigned 32-bit integers, -0x8000'0000 == 0x8000'0000, which only then is made signed again. + // So we would pass a medium-large *positive* number to 'from_seconds', which is not what we want to test here. + // That's why this is the only place that needs an "LL" suffix. + EXPECT_TIME(Time::from_seconds(-0x8000'0000LL), -0x8000'0000LL, 0); + EXPECT_TIME(Time::from_seconds(-0x8000'0000'0000'0000), (i64)-0x8000'0000'0000'0000, 0); + EXPECT_TIME(Time::from_seconds(0x7fff'ffff'ffff'ffff), 0x7fff'ffff'ffff'ffff, 0); +} + +TEST_CASE(timespec_parsing) +{ + EXPECT_TIME(Time::from_timespec(timespec { 2, 4 }), 2, 4); + EXPECT_TIME(Time::from_timespec(timespec { 1234, 5678 }), 1234, 5678); + + EXPECT_TIME(Time::from_timespec(timespec { 0, 1'000'000'000 }), 1, 0); + EXPECT_TIME(Time::from_timespec(timespec { 8, 2'000'000'000 }), 10, 0); + EXPECT_TIME(Time::from_timespec(timespec { 0, 2'147'483'647 }), 2, 147'483'647); + + EXPECT_TIME(Time::from_timespec(timespec { 1, -1 }), 0, 999'999'999); + EXPECT_TIME(Time::from_timespec(timespec { 0, -1 }), -1, 999'999'999); + EXPECT_TIME(Time::from_timespec(timespec { -1, 0 }), -1, 0); + EXPECT_TIME(Time::from_timespec(timespec { -1, 1'000'000'001 }), 0, 1); + EXPECT_TIME(Time::from_timespec(timespec { -2, 2'000'000'003 }), 0, 3); + EXPECT_TIME(Time::from_timespec(timespec { -2, 1'999'999'999 }), -1, 999'999'999); + + EXPECT_TIME(Time::from_timespec(timespec { 0x7fff'ffff'ffff'fffe, 999'999'998 }), 0x7fff'ffff'ffff'fffe, 999'999'998); + EXPECT_TIME(Time::from_timespec(timespec { 0x7fff'ffff'ffff'fffe, 1'999'999'998 }), 0x7fff'ffff'ffff'ffff, 999'999'998); + EXPECT_TIME(Time::from_timespec(timespec { 0x7fff'ffff'ffff'fffe, 1'999'999'999 }), 0x7fff'ffff'ffff'ffff, 999'999'999); + EXPECT_TIME(Time::from_timespec(timespec { 0x7fff'ffff'ffff'fffe, 2'000'000'000 }), 0x7fff'ffff'ffff'ffff, 999'999'999); + + EXPECT_TIME(Time::from_timespec(timespec { -0x7fff'ffff'ffff'fffe, -1 }), -0x7fff'ffff'ffff'ffff, 999'999'999); + EXPECT_TIME(Time::from_timespec(timespec { -0x7fff'ffff'ffff'fffe, -999'999'999 }), -0x7fff'ffff'ffff'ffff, 1); + EXPECT_TIME(Time::from_timespec(timespec { -0x7fff'ffff'ffff'fffe, -1'999'999'999 }), (i64)-0x8000'0000'0000'0000, 1); + EXPECT_TIME(Time::from_timespec(timespec { -0x7fff'ffff'ffff'fffe, -2'000'000'000 }), (i64)-0x8000'0000'0000'0000, 0); + EXPECT_TIME(Time::from_timespec(timespec { -0x7fff'ffff'ffff'fffe, -2'000'000'001 }), (i64)-0x8000'0000'0000'0000, 0); +} + +TEST_CASE(timeval_parsing) +{ + EXPECT_TIME(Time::from_timeval(timeval { 2, 4 }), 2, 4'000); + EXPECT_TIME(Time::from_timeval(timeval { 1234, 5'678 }), 1234, 5'678'000); + EXPECT_TIME(Time::from_timeval(timeval { -123, -45'678 }), -124, 954'322'000); + + EXPECT_TIME(Time::from_timeval(timeval { 0, 1'000'000 }), 1, 0); + EXPECT_TIME(Time::from_timeval(timeval { 0, 1'000'000'000 }), 1'000, 0); + EXPECT_TIME(Time::from_timeval(timeval { 8, 2'000'000 }), 10, 0); + EXPECT_TIME(Time::from_timeval(timeval { 0, 2'147'483'647 }), 2'147, 483'647'000); + + EXPECT_TIME(Time::from_timeval(timeval { 1, -1 }), 0, 999'999'000); + EXPECT_TIME(Time::from_timeval(timeval { 0, -1 }), -1, 999'999'000); + EXPECT_TIME(Time::from_timeval(timeval { -1, 0 }), -1, 0); + EXPECT_TIME(Time::from_timeval(timeval { -1, 1'000'001 }), 0, 1'000); + EXPECT_TIME(Time::from_timeval(timeval { -2, 2'000'003 }), 0, 3'000); + EXPECT_TIME(Time::from_timeval(timeval { -2, 1'999'999 }), -1, 999'999'000); + + EXPECT_TIME(Time::from_timeval(timeval { 0x7fff'ffff'ffff'fffe, 999'998 }), 0x7fff'ffff'ffff'fffe, 999'998'000); + EXPECT_TIME(Time::from_timeval(timeval { 0x7fff'ffff'ffff'fffe, 1'999'998 }), 0x7fff'ffff'ffff'ffff, 999'998'000); + EXPECT_TIME(Time::from_timeval(timeval { 0x7fff'ffff'ffff'fffe, 1'999'999 }), 0x7fff'ffff'ffff'ffff, 999'999'000); + EXPECT_TIME(Time::from_timeval(timeval { 0x7fff'ffff'ffff'fffe, 2'000'000 }), 0x7fff'ffff'ffff'ffff, 999'999'999); + + EXPECT_TIME(Time::from_timeval(timeval { -0x7fff'ffff'ffff'fffe, -1 }), -0x7fff'ffff'ffff'ffff, 999'999'000); + EXPECT_TIME(Time::from_timeval(timeval { -0x7fff'ffff'ffff'fffe, -999'999 }), -0x7fff'ffff'ffff'ffff, 1'000); + EXPECT_TIME(Time::from_timeval(timeval { -0x7fff'ffff'ffff'fffe, -1'999'999 }), (i64)-0x8000'0000'0000'0000, 1'000); + EXPECT_TIME(Time::from_timeval(timeval { -0x7fff'ffff'ffff'fffe, -2'000'000 }), (i64)-0x8000'0000'0000'0000, 0); + EXPECT_TIME(Time::from_timeval(timeval { -0x7fff'ffff'ffff'fffe, -2'000'001 }), (i64)-0x8000'0000'0000'0000, 0); +} + +#define TIME(s, ns) \ + Time::from_timespec(timespec { (s), (ns) }) + +TEST_CASE(addition) +{ +#define EXPECT_ADDITION(s1, ns1, s2, ns2, sr, nsr) \ + EXPECT_TIME(TIME(s1, ns1) + TIME(s2, ns2), sr, nsr); \ + EXPECT_TIME(TIME(s2, ns2) + TIME(s1, ns1), sr, nsr); + + EXPECT_ADDITION(11, 123'456'789, 22, 900'000'000, 34, 23'456'789); + + EXPECT_ADDITION(0, 0, 9223372036854775807LL, 999'999'998, 0x7fff'ffff'ffff'ffff, 999'999'998); + EXPECT_ADDITION(0, 1, 9223372036854775807LL, 999'999'998, 0x7fff'ffff'ffff'ffff, 999'999'999); + EXPECT_ADDITION(0, 2, 9223372036854775807LL, 999'999'998, 0x7fff'ffff'ffff'ffff, 999'999'999); + + EXPECT_ADDITION(0x80, 40, 0x7fff'ffff'ffff'ff7f, 999'999'958, 0x7fff'ffff'ffff'ffff, 999'999'998); + EXPECT_ADDITION(0x80, 41, 0x7fff'ffff'ffff'ff7f, 999'999'958, 0x7fff'ffff'ffff'ffff, 999'999'999); + EXPECT_ADDITION(0x80, 42, 0x7fff'ffff'ffff'ff7f, 999'999'958, 0x7fff'ffff'ffff'ffff, 999'999'999); + + EXPECT_ADDITION(-2, 5, -3, 7, -5, 12); + EXPECT_ADDITION(-2, 999'999'995, -3, 999'999'997, -4, 999'999'992); + + EXPECT_ADDITION(-0x7fff'ffff'ffff'ffff, 999'999'995, -1, 6, -0x7fff'ffff'ffff'ffff, 1); + EXPECT_ADDITION(-0x7fff'ffff'ffff'ffff, 999'999'995, -2, 6, (i64)-0x8000'0000'0000'0000, 1); + EXPECT_ADDITION(-0x7fff'ffff'ffff'ffff, 999'999'995, -2, 5, (i64)-0x8000'0000'0000'0000, 0); + EXPECT_ADDITION(-0x7fff'ffff'ffff'ffff, 999'999'995, -2, 4, (i64)-0x8000'0000'0000'0000, 0); + + EXPECT_ADDITION((i64)-0x8000'0000'0000'0000, 999'999'995, 0x7fff'ffff'ffff'ffff, 4, -1, 999'999'999); + EXPECT_ADDITION((i64)-0x8000'0000'0000'0000, 999'999'995, 0x7fff'ffff'ffff'ffff, 5, 0, 0); + EXPECT_ADDITION((i64)-0x8000'0000'0000'0000, 999'999'995, 0x7fff'ffff'ffff'ffff, 6, 0, 1); +#undef EXPECT_ADDITION +} + +TEST_CASE(subtraction) +{ +#define EXPECT_SUBTRACTION(s1, ns1, s2, ns2, sr, nsr) \ + EXPECT_TIME(TIME(s1, ns1) - TIME(s2, ns2), sr, nsr); + + EXPECT_SUBTRACTION(5, 0, 3, 0, 2, 0); + EXPECT_SUBTRACTION(0, 0, 0, 0, 0, 0); + EXPECT_SUBTRACTION(0, 5, 0, 3, 0, 2); + EXPECT_SUBTRACTION(0x7fff'ffff'ffff'ffff, 999'999'999, 8, 123, 0x7fff'ffff'ffff'fff7, 999'999'876); + + EXPECT_SUBTRACTION(1, 0, 0, 999'999'999, 0, 1); + EXPECT_SUBTRACTION(0x7fff'ffff'ffff'ffff, 0, 1, 999'999'999, 0x7fff'ffff'ffff'fffd, 1); + + EXPECT_SUBTRACTION(3, 0, 5, 0, -2, 0); + EXPECT_SUBTRACTION(0, 3, 0, 5, -1, 999'999'998); + EXPECT_SUBTRACTION(0, 0, 0x7fff'ffff'ffff'ffff, 999'999'999, (i64)-0x8000'0000'0000'0000, 1); + EXPECT_SUBTRACTION(0, 0, (i64)-0x8000'0000'0000'0000, 0, 0x7fff'ffff'ffff'ffff, 999'999'999); + EXPECT_SUBTRACTION(-1, 999'999'999, (i64)-0x8000'0000'0000'0000, 0, 0x7fff'ffff'ffff'ffff, 999'999'999); + EXPECT_SUBTRACTION(-1, 999'999'998, (i64)-0x8000'0000'0000'0000, 0, 0x7fff'ffff'ffff'ffff, 999'999'998); + + EXPECT_SUBTRACTION(123, 456, 123, 455, 0, 1); + EXPECT_SUBTRACTION(123, 456, 123, 456, 0, 0); + EXPECT_SUBTRACTION(123, 456, 123, 457, -1, 999'999'999); + + EXPECT_SUBTRACTION(124, 456, 123, 455, 1, 1); + EXPECT_SUBTRACTION(124, 456, 123, 456, 1, 0); + EXPECT_SUBTRACTION(124, 456, 123, 457, 0, 999'999'999); + + EXPECT_SUBTRACTION(-0x7fff'ffff'ffff'ffff, 999'999'995, 1, 999'999'994, (i64)-0x8000'0000'0000'0000, 1); + EXPECT_SUBTRACTION(-0x7fff'ffff'ffff'ffff, 999'999'995, 1, 999'999'995, (i64)-0x8000'0000'0000'0000, 0); + EXPECT_SUBTRACTION(-0x7fff'ffff'ffff'ffff, 999'999'995, 1, 999'999'996, (i64)-0x8000'0000'0000'0000, 0); +} + +TEST_MAIN(Time) diff --git a/AK/Time.cpp b/AK/Time.cpp index 067c333ea95..a00e59fa7f4 100644 --- a/AK/Time.cpp +++ b/AK/Time.cpp @@ -25,8 +25,18 @@ */ #include +#include +#include #include +// Make a reasonable guess as to which timespec/timeval definition to use. +// It doesn't really matter, since both are identical. +#ifdef KERNEL +# include +#else +# include +#endif + namespace AK { int day_of_year(int year, unsigned month, int day) @@ -61,4 +71,127 @@ unsigned day_of_week(int year, unsigned month, int day) return (year + year / 4 - year / 100 + year / 400 + seek_table[month - 1] + day) % 7; } + +ALWAYS_INLINE static i32 sane_mod(i32& numerator, i32 denominator) +{ + VERIFY(2 <= denominator && denominator <= 1'000'000'000); + // '%' in C/C++ does not work in the obvious way: + // For example, -9 % 7 is -2, not +5. + // However, we want a representation like "(-2)*7 + (+5)". + i32 dividend = numerator / denominator; + numerator %= denominator; + if (numerator < 0) { + // Does not overflow: different signs. + numerator += denominator; + // Does not underflow: denominator >= 2. + dividend -= 1; + } + return dividend; +} +Time Time::from_timespec(const struct timespec& ts) +{ + i32 nsecs = ts.tv_nsec; + i32 extra_secs = sane_mod(nsecs, 1'000'000'000); + return Time::from_half_sanitized(ts.tv_sec, extra_secs, nsecs); +} +Time Time::from_timeval(const struct timeval& tv) +{ + i32 usecs = tv.tv_usec; + i32 extra_secs = sane_mod(usecs, 1'000'000); + VERIFY(0 <= usecs && usecs < 1'000'000); + return Time::from_half_sanitized(tv.tv_sec, extra_secs, usecs * 1'000); +} + +timespec Time::to_timespec() const +{ + VERIFY(m_nanoseconds < 1'000'000'000); + return { static_cast(m_seconds), static_cast(m_nanoseconds) }; +} +timeval Time::to_timeval() const +{ + VERIFY(m_nanoseconds < 1'000'000'000); + return { static_cast(m_seconds), static_cast(m_nanoseconds) / 1000 }; +} + +Time Time::operator+(const Time& other) const +{ + VERIFY(m_nanoseconds < 1'000'000'000); + VERIFY(other.m_nanoseconds < 1'000'000'000); + + u32 new_nsecs = m_nanoseconds + other.m_nanoseconds; + u32 extra_secs = new_nsecs / 1'000'000'000; + new_nsecs %= 1'000'000'000; + + i64 this_secs = m_seconds; + i64 other_secs = other.m_seconds; + // We would like to just add "this_secs + other_secs + extra_secs". + // However, computing this naively may overflow even though the result is in-bounds. + // Example in 8-bit: (-127) + (-2) + (+1) = (-128), which fits in an i8. + // Example in 8-bit, the other way around: (-2) + (127) + (+1) = 126. + // So we do something more sophisticated: + if (extra_secs) { + VERIFY(extra_secs == 1); + if (this_secs != 0x7fff'ffff'ffff'ffff) { + this_secs += 1; + } else if (other_secs != 0x7fff'ffff'ffff'ffff) { + other_secs += 1; + } else { + /* If *both* are INT64_MAX, then adding them will overflow in any case. */ + return Time::max(); + } + extra_secs = 0; + } + + Checked new_secs { this_secs }; + new_secs += other_secs; + if (new_secs.has_overflow()) { + if (other_secs > 0) + return Time::max(); + else + return Time::min(); + } + + return Time { new_secs.value(), new_nsecs }; +} +Time Time::operator-(const Time& other) const +{ + VERIFY(m_nanoseconds < 1'000'000'000); + VERIFY(other.m_nanoseconds < 1'000'000'000); + + if (other.m_nanoseconds) + return *this + Time((i64) ~(u64)other.m_seconds, 1'000'000'000 - other.m_nanoseconds); + + if (other.m_seconds != (i64)-0x8000'0000'0000'0000) + return *this + Time(-other.m_seconds, 0); + + // Only remaining case: We want to subtract -0x8000'0000'0000'0000 seconds, + // i.e. add a very large number. + + if (m_seconds >= 0) + return Time::max(); + return Time { (m_seconds + 0x4000'0000'0000'0000) + 0x4000'0000'0000'0000, m_nanoseconds }; +} + +Time Time::from_half_sanitized(i64 seconds, i32 extra_seconds, u32 nanoseconds) +{ + VERIFY(nanoseconds < 1'000'000'000); + + if ((seconds <= 0 && extra_seconds > 0) || (seconds >= 0 && extra_seconds < 0)) { + // Opposite signs mean that we can definitely add them together without fear of overflowing i64: + seconds += extra_seconds; + extra_seconds = 0; + } + + // Now the only possible way to become invalid is overflowing i64 towards positive infinity: + if (Checked::addition_would_overflow(seconds, extra_seconds)) { + if (seconds < 0) { + return Time::min(); + } else { + return Time::max(); + } + } + + return Time { seconds + extra_seconds, nanoseconds }; +} + } diff --git a/AK/Time.h b/AK/Time.h index 50f1929fe28..0a5e86e1b96 100644 --- a/AK/Time.h +++ b/AK/Time.h @@ -27,6 +27,12 @@ #pragma once #include +#include + +// Kernel and Userspace pull in the definitions from different places. +// Avoid trying to figure out which one. +struct timeval; +struct timespec; namespace AK { @@ -66,6 +72,46 @@ inline int years_to_days_since_epoch(int year) return days; } +/* + * Represents a time amount in a "safe" way. + * Minimum: 0 seconds, 0 nanoseconds + * Maximum: 2**63-1 seconds, 999'999'999 nanoseconds + * If any operation (e.g. 'from_timeval' or operator-) would over- or underflow, the closest legal value is returned instead. + * Inputs (e.g. to 'from_timespec') are allowed to be in non-normal form (e.g. "1 second, 2'012'345'678 nanoseconds" or "1 second, -2 microseconds"). + * Outputs (e.g. from 'to_timeval') are always in normal form. + */ +class Time { +public: + Time(const Time&) = default; + + static Time from_seconds(i64 seconds) { return Time(seconds, 0); }; + static Time from_timespec(const struct timespec&); + static Time from_timeval(const struct timeval&); + static Time min() { return Time(-0x8000'0000'0000'0000LL, 0); }; + static Time zero() { return Time(0, 0); }; + static Time max() { return Time(0x7fff'ffff'ffff'ffffLL, 999'999'999); }; + + timespec to_timespec() const; + timeval to_timeval() const; + + bool operator==(const Time& other) const { return this->m_seconds == other.m_seconds && this->m_nanoseconds == other.m_nanoseconds; } + bool operator!=(const Time& other) const { return !(*this == other); } + Time operator+(const Time& other) const; + Time operator-(const Time& other) const; + +private: + explicit Time(i64 seconds, u32 nanoseconds) + : m_seconds(seconds) + , m_nanoseconds(nanoseconds) + { + } + + static Time from_half_sanitized(i64 seconds, i32 extra_seconds, u32 nanoseconds); + + i64 m_seconds; + u32 m_nanoseconds; // Always less than 1'000'000'000 +}; + template inline void timeval_sub(const TimevalType& a, const TimevalType& b, TimevalType& result) { @@ -178,6 +224,7 @@ using AK::day_of_year; using AK::days_in_month; using AK::days_in_year; using AK::is_leap_year; +using AK::Time; using AK::timespec_add; using AK::timespec_add_timeval; using AK::timespec_sub;