From 23eef94984644ae4666272ef3247c2964dbf2438 Mon Sep 17 00:00:00 2001 From: Joel Linn Date: Wed, 9 Mar 2022 23:49:16 +0100 Subject: [PATCH] [Base] Add chrono support - WinSystemClock is a FILETIME clock without scaling, can convert to system_time - XSystemClock is a FILTETIME clock with scaling applied, can only convert to WinSystemClock --- src/xenia/base/chrono.h | 176 ++++++++++++++++++++++ src/xenia/base/chrono_steady_cast.h | 76 ++++++++++ src/xenia/base/clock.h | 5 +- src/xenia/kernel/xboxkrnl/xboxkrnl_rtl.cc | 10 +- src/xenia/kernel/xclock.h | 80 ---------- 5 files changed, 263 insertions(+), 84 deletions(-) create mode 100644 src/xenia/base/chrono.h create mode 100644 src/xenia/base/chrono_steady_cast.h delete mode 100644 src/xenia/kernel/xclock.h diff --git a/src/xenia/base/chrono.h b/src/xenia/base/chrono.h new file mode 100644 index 000000000..b1c252ac3 --- /dev/null +++ b/src/xenia/base/chrono.h @@ -0,0 +1,176 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2022 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_BASE_CHRONO_H_ +#define XENIA_BASE_CHRONO_H_ + +#include +#include + +// https://github.com/HowardHinnant/date/commit/5ba1c1ad8514362dba596f228eb20eb13f63d948#r33275526 +#define HAS_UNCAUGHT_EXCEPTIONS 1 +#include "third_party/date/include/date/tz.h" + +#include "xenia/base/clock.h" + +namespace xe { +using hundrednano = std::ratio<1, 10000000>; + +namespace chrono { + +using hundrednanoseconds = std::chrono::duration; + +// https://docs.microsoft.com/en-us/windows/win32/sysinfo/converting-a-time-t-value-to-a-file-time +// Don't forget the 89 leap days. +static constexpr std::chrono::seconds seconds_1601_to_1970 = + (396 * 365 + 89) * std::chrono::seconds(60 * 60 * 24); + +// TODO(JoelLinn) define xstead_clock xsystem_clock etc. + +namespace internal { +// Trick to reduce code duplication and keep all the chrono template magic +// working +enum class Domain { + // boring host clock: + Host, + // adheres to guest scaling (differrent speed, changing clock drift etc): + Guest +}; + +template +struct NtSystemClock { + using rep = int64_t; + using period = hundrednano; + using duration = hundrednanoseconds; + using time_point = std::chrono::time_point>; + // This really depends on the context the clock is used in: + // static constexpr bool is_steady = false; + + public: + // The delta between std::chrono::system_clock (Jan 1 1970) and NT file + // time (Jan 1 1601), in seconds. In the spec std::chrono::system_clock's + // epoch is undefined, but C++20 cements it as Jan 1 1970. + static constexpr std::chrono::seconds unix_epoch_delta() { + using std::chrono::steady_clock; + auto filetime_epoch = date::year{1601} / date::month{1} / date::day{1}; + auto system_clock_epoch = date::year{1970} / date::month{1} / date::day{1}; + auto fp = static_cast( + static_cast(filetime_epoch)); + auto sp = static_cast( + static_cast(system_clock_epoch)); + return fp.time_since_epoch() - sp.time_since_epoch(); + } + + public: + static constexpr uint64_t to_file_time(time_point const& tp) noexcept { + return static_cast(tp.time_since_epoch().count()); + } + + static constexpr time_point from_file_time(uint64_t const& tp) noexcept { + return time_point{duration{tp}}; + } + + // To convert XSystemClock to sys, do clock_cast(tp) first + // SFINAE hack https://stackoverflow.com/a/58813009 + template + static constexpr std::enable_if_t + to_sys(const time_point& tp) { + using sys_duration = std::chrono::system_clock::duration; + using sys_time = std::chrono::system_clock::time_point; + + auto dp = tp; + dp += unix_epoch_delta(); + auto cdp = std::chrono::time_point_cast(dp); + return sys_time{cdp.time_since_epoch()}; + } + + template + static constexpr std::enable_if_t + from_sys(const std::chrono::system_clock::time_point& tp) { + auto ctp = std::chrono::time_point_cast(tp); + auto dp = time_point{ctp.time_since_epoch()}; + dp -= unix_epoch_delta(); + return dp; + } + + [[nodiscard]] static time_point now() noexcept { + if constexpr (domain_ == Domain::Host) { + // QueryHostSystemTime() returns windows epoch times even on POSIX + return from_file_time(Clock::QueryHostSystemTime()); + } else if constexpr (domain_ == Domain::Guest) { + return from_file_time(Clock::QueryGuestSystemTime()); + } + } +}; +} // namespace internal + +// Unscaled system clock which can be used for filetime <-> system_clock +// conversion +using WinSystemClock = internal::NtSystemClock; + +// Guest system clock, scaled +using XSystemClock = internal::NtSystemClock; + +} // namespace chrono +} // namespace xe + +namespace date { + +template <> +struct clock_time_conversion<::xe::chrono::WinSystemClock, + ::xe::chrono::XSystemClock> { + using WClock_ = ::xe::chrono::WinSystemClock; + using XClock_ = ::xe::chrono::XSystemClock; + + template + typename WClock_::time_point operator()( + const std::chrono::time_point& t) const { + // Consult chrono_steady_cast.h for explanation on this: + std::atomic_thread_fence(std::memory_order_acq_rel); + auto w_now = WClock_::now(); + auto x_now = XClock_::now(); + std::atomic_thread_fence(std::memory_order_acq_rel); + + auto delta = (t - x_now); + if (!::cvars::clock_no_scaling) { + delta = std::chrono::floor( + delta * xe::Clock::guest_time_scalar()); + } + return w_now + delta; + } +}; + +template <> +struct clock_time_conversion<::xe::chrono::XSystemClock, + ::xe::chrono::WinSystemClock> { + using WClock_ = ::xe::chrono::WinSystemClock; + using XClock_ = ::xe::chrono::XSystemClock; + + template + typename XClock_::time_point operator()( + const std::chrono::time_point& t) const { + // Consult chrono_steady_cast.h for explanation on this: + std::atomic_thread_fence(std::memory_order_acq_rel); + auto w_now = WClock_::now(); + auto x_now = XClock_::now(); + std::atomic_thread_fence(std::memory_order_acq_rel); + + xe::chrono::hundrednanoseconds delta = (t - w_now); + if (!::cvars::clock_no_scaling) { + delta = std::chrono::floor( + delta / xe::Clock::guest_time_scalar()); + } + return x_now + delta; + } +}; + +} // namespace date + +#endif diff --git a/src/xenia/base/chrono_steady_cast.h b/src/xenia/base/chrono_steady_cast.h new file mode 100644 index 000000000..044780787 --- /dev/null +++ b/src/xenia/base/chrono_steady_cast.h @@ -0,0 +1,76 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2022 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_BASE_CHRONO_STEADY_CAST_H_ +#define XENIA_BASE_CHRONO_STEADY_CAST_H_ + +#include + +#include "xenia/base/chrono.h" + +// This is in a separate header because casting to and from steady time points +// usually doesn't make sense and is imprecise. However, NT uses the FileTime +// epoch as a steady clock in waits. In such cases, include this header and use +// clock_cast<>(). + +namespace date { + +// This conveniently works only for Host time domain because Guest needs +// additional scaling. Convert XSystemClock to WinSystemClock first if +// necessary. +template <> +struct clock_time_conversion<::xe::chrono::WinSystemClock, + std::chrono::steady_clock> { + // using NtSystemClock_ = ::xe::chrono::internal::NtSystemClock; + using WinSystemClock_ = ::xe::chrono::WinSystemClock; + using steady_clock_ = std::chrono::steady_clock; + + template + typename WinSystemClock_::time_point operator()( + const std::chrono::time_point& t) const { + // Since there is no known epoch for steady_clock and even if, since it can + // progress differently than other common clocks (e.g. stopping when the + // computer is suspended), we need to use now() which introduces + // imprecision. + // Memory fences to keep the clock fetches close together to + // minimize drift. This pattern was benchmarked to give the lowest + // conversion error: error = sty_tpoint - + // clock_cast(clock_cast(sty_tpoint)); + std::atomic_thread_fence(std::memory_order_acq_rel); + auto steady_now = steady_clock_::now(); + auto nt_now = WinSystemClock_::now(); + std::atomic_thread_fence(std::memory_order_acq_rel); + + auto delta = std::chrono::floor(t - steady_now); + return nt_now + delta; + } +}; + +template <> +struct clock_time_conversion { + using WinSystemClock_ = ::xe::chrono::WinSystemClock; + using steady_clock_ = std::chrono::steady_clock; + + template + steady_clock_::time_point operator()( + const std::chrono::time_point& t) const { + std::atomic_thread_fence(std::memory_order_acq_rel); + auto steady_now = steady_clock_::now(); + auto nt_now = WinSystemClock_::now(); + std::atomic_thread_fence(std::memory_order_acq_rel); + + auto delta = t - nt_now; + return steady_now + delta; + } +}; + +} // namespace date + +#endif diff --git a/src/xenia/base/clock.h b/src/xenia/base/clock.h index eeee36fb8..67a3ebb67 100644 --- a/src/xenia/base/clock.h +++ b/src/xenia/base/clock.h @@ -2,7 +2,7 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2019 Ben Vanik. All rights reserved. * + * Copyright 2022 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ @@ -10,6 +10,7 @@ #ifndef XENIA_BASE_CLOCK_H_ #define XENIA_BASE_CLOCK_H_ +#include #include #include "xenia/base/cvar.h" @@ -24,6 +25,8 @@ DECLARE_bool(clock_source_raw); namespace xe { +// chrono APIs in xenia/base/chrono.h are preferred + class Clock { public: // Host ticks-per-second. Generally QueryHostTickFrequency should be used. diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_rtl.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_rtl.cc index adb07c81b..db8421a64 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_rtl.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_rtl.cc @@ -13,6 +13,7 @@ #include #include "xenia/base/atomic.h" +#include "xenia/base/chrono.h" #include "xenia/base/logging.h" #include "xenia/base/string.h" #include "xenia/base/threading.h" @@ -21,7 +22,6 @@ #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_private.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_threading.h" -#include "xenia/kernel/xclock.h" #include "xenia/kernel/xevent.h" #include "xenia/kernel/xthread.h" @@ -511,7 +511,10 @@ static_assert_size(X_TIME_FIELDS, 16); // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtltimetotimefields void RtlTimeToTimeFields_entry(lpqword_t time_ptr, pointer_t time_fields_ptr) { - auto tp = XClock::to_sys(XClock::from_file_time(time_ptr.value())); + // Use host clock because we don't want scaling to be applied, just conversion + using xe::chrono::WinSystemClock; + auto tp = + WinSystemClock::to_sys(WinSystemClock::from_file_time(time_ptr.value())); auto dp = date::floor(tp); auto year_month_day = date::year_month_day{dp}; auto weekday = date::weekday{dp}; @@ -531,6 +534,7 @@ DECLARE_XBOXKRNL_EXPORT1(RtlTimeToTimeFields, kNone, kImplemented); // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtltimefieldstotime dword_result_t RtlTimeFieldsToTime_entry( pointer_t time_fields_ptr, lpqword_t time_ptr) { + using xe::chrono::WinSystemClock; if (time_fields_ptr->year < 1601 || time_fields_ptr->month < 1 || time_fields_ptr->month > 12 || time_fields_ptr->day < 1 || time_fields_ptr->day > 31 || time_fields_ptr->hour > 23 || @@ -551,7 +555,7 @@ dword_result_t RtlTimeFieldsToTime_entry( time += std::chrono::minutes{time_fields_ptr->minute}; time += std::chrono::seconds{time_fields_ptr->second}; time += std::chrono::milliseconds{time_fields_ptr->milliseconds}; - *time_ptr = XClock::to_file_time(XClock::from_sys(time)); + *time_ptr = WinSystemClock::to_file_time(WinSystemClock::from_sys(time)); return 1; } DECLARE_XBOXKRNL_EXPORT1(RtlTimeFieldsToTime, kNone, kImplemented); diff --git a/src/xenia/kernel/xclock.h b/src/xenia/kernel/xclock.h deleted file mode 100644 index 554e6cd52..000000000 --- a/src/xenia/kernel/xclock.h +++ /dev/null @@ -1,80 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2020 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#ifndef XENIA_KERNEL_XCLOCK_H_ -#define XENIA_KERNEL_XCLOCK_H_ - -#include - -#include "xenia/base/clock.h" - -// https://github.com/HowardHinnant/date/commit/5ba1c1ad8514362dba596f228eb20eb13f63d948#r33275526 -#define HAS_UNCAUGHT_EXCEPTIONS 1 -#include "third_party/date/include/date/date.h" - -namespace xe { -namespace kernel { - -struct XClock { - using rep = int64_t; - using period = std::ratio_multiply, std::nano>; - using duration = std::chrono::duration; - using time_point = std::chrono::time_point; - static constexpr bool is_steady = false; - - static time_point now() noexcept { - return from_file_time(Clock::QueryGuestSystemTime()); - } - - static uint64_t to_file_time(time_point const& tp) noexcept { - return static_cast(tp.time_since_epoch().count()); - } - - static time_point from_file_time(uint64_t const& tp) noexcept { - return time_point{duration{tp}}; - } - - static std::chrono::system_clock::time_point to_sys(time_point const& tp) { - // TODO(gibbed): verify behavior under Linux - using sys_duration = std::chrono::system_clock::duration; - using sys_time = std::chrono::system_clock::time_point; - auto dp = tp; - dp += system_clock_delta(); - auto cdp = std::chrono::time_point_cast(dp); - return sys_time{cdp.time_since_epoch()}; - } - - static time_point from_sys(std::chrono::system_clock::time_point const& tp) { - // TODO(gibbed): verify behavior under Linux - auto ctp = std::chrono::time_point_cast(tp); - auto dp = time_point{ctp.time_since_epoch()}; - dp -= system_clock_delta(); - return dp; - } - - private: - // The delta between std::chrono::system_clock (Jan 1 1970) and Xenon file - // time (Jan 1 1601), in seconds. In the spec std::chrono::system_clock's - // epoch is undefined, but C++20 cements it as Jan 1 1970. - static constexpr std::chrono::seconds system_clock_delta() { - auto filetime_epoch = date::year{1601} / date::month{1} / date::day{1}; - auto system_clock_epoch = date::year{1970} / date::month{1} / date::day{1}; - std::chrono::system_clock::time_point fp{ - static_cast(filetime_epoch)}; - std::chrono::system_clock::time_point sp{ - static_cast(system_clock_epoch)}; - return std::chrono::floor(fp.time_since_epoch() - - sp.time_since_epoch()); - } -}; - -} // namespace kernel -} // namespace xe - -#endif // XENIA_KERNEL_XCLOCK_H_