From 570b373d102e753a6eee2eec29d43e4362629f5e Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Tue, 18 Oct 2022 12:48:09 -0700 Subject: [PATCH] Move leap seconds DateTime configuration statics into LeapSecondCache class (#77163) This enables AOT initialization of public readonly DateTime statics like DateTime.Min/MaxValue. --- .../System.Private.CoreLib.Shared.projitems | 1 - .../src/System/DateTime.Unix.cs | 2 +- .../src/System/DateTime.Win32.cs | 19 --------- .../src/System/DateTime.Windows.cs | 40 +++++++++++++------ .../src/System/DateTime.cs | 22 +++++----- .../src/System/DateTimeOffset.cs | 6 +-- 6 files changed, 40 insertions(+), 50 deletions(-) delete mode 100644 src/libraries/System.Private.CoreLib/src/System/DateTime.Win32.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 2cdc6ee9731c4..9d02fee390685 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1900,7 +1900,6 @@ - diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTime.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/DateTime.Unix.cs index 0c35b864ff4c6..8dd87e3105d0b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTime.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTime.Unix.cs @@ -5,7 +5,7 @@ namespace System { public readonly partial struct DateTime { - internal const bool s_systemSupportsLeapSeconds = false; + internal static bool SystemSupportsLeapSeconds => false; public static DateTime UtcNow { diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTime.Win32.cs b/src/libraries/System.Private.CoreLib/src/System/DateTime.Win32.cs deleted file mode 100644 index 15aca35c982ae..0000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/DateTime.Win32.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System -{ - public readonly partial struct DateTime - { - private static unsafe bool SystemSupportsLeapSeconds() - { - Interop.NtDll.SYSTEM_LEAP_SECOND_INFORMATION slsi; - - return Interop.NtDll.NtQuerySystemInformation( - Interop.NtDll.SystemLeapSecondInformation, - &slsi, - (uint)sizeof(Interop.NtDll.SYSTEM_LEAP_SECOND_INFORMATION), - null) == 0 && slsi.Enabled != Interop.BOOLEAN.FALSE; - } - } -} diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs index 5ec5c7d0548a4..1b72fccb3f4ec 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs @@ -10,21 +10,21 @@ namespace System { public readonly partial struct DateTime { - internal static readonly bool s_systemSupportsLeapSeconds = SystemSupportsLeapSeconds(); + internal static bool SystemSupportsLeapSeconds => LeapSecondCache.s_systemSupportsLeapSeconds; public static unsafe DateTime UtcNow { get { ulong fileTimeTmp; // mark only the temp local as address-taken - s_pfnGetSystemTimeAsFileTime(&fileTimeTmp); + LeapSecondCache.s_pfnGetSystemTimeAsFileTime(&fileTimeTmp); ulong fileTime = fileTimeTmp; - if (s_systemSupportsLeapSeconds) + if (LeapSecondCache.s_systemSupportsLeapSeconds) { // Query the leap second cache first, which avoids expensive calls to GetFileTimeAsSystemTime. - LeapSecondCache cacheValue = s_leapSecondCache; + LeapSecondCache cacheValue = LeapSecondCache.s_leapSecondCache; ulong ticksSinceStartOfCacheValidityWindow = fileTime - cacheValue.OSFileTimeTicksAtStartOfValidityWindow; if (ticksSinceStartOfCacheValidityWindow < LeapSecondCache.ValidityPeriodInTicks) { @@ -129,7 +129,16 @@ private static DateTime CreateDateTimeFromSystemTime(in Interop.Kernel32.SYSTEMT return new DateTime(ticks); } - private static readonly unsafe delegate* unmanaged[SuppressGCTransition] s_pfnGetSystemTimeAsFileTime = GetGetSystemTimeAsFileTimeFnPtr(); + private static unsafe bool GetSystemSupportsLeapSeconds() + { + Interop.NtDll.SYSTEM_LEAP_SECOND_INFORMATION slsi; + + return Interop.NtDll.NtQuerySystemInformation( + Interop.NtDll.SystemLeapSecondInformation, + &slsi, + (uint)sizeof(Interop.NtDll.SYSTEM_LEAP_SECOND_INFORMATION), + null) == 0 && slsi.Enabled != Interop.BOOLEAN.FALSE; + } private static unsafe delegate* unmanaged[SuppressGCTransition] GetGetSystemTimeAsFileTimeFnPtr() { @@ -184,11 +193,11 @@ private static unsafe DateTime UpdateLeapSecondCacheAndReturnUtcNow() // OS update occurs and a past leap second is added, this limits the window in which our // cache will return incorrect values. - Debug.Assert(s_systemSupportsLeapSeconds); + Debug.Assert(SystemSupportsLeapSeconds); Debug.Assert(LeapSecondCache.ValidityPeriodInTicks < TicksPerDay - TicksPerSecond, "Leap second cache validity window should be less than 23:59:59."); ulong fileTimeNow; - s_pfnGetSystemTimeAsFileTime(&fileTimeNow); + LeapSecondCache.s_pfnGetSystemTimeAsFileTime(&fileTimeNow); // If we reached this point, our leap second cache is stale, and we need to update it. // First, convert the FILETIME to a SYSTEMTIME. @@ -292,7 +301,7 @@ private static unsafe DateTime UpdateLeapSecondCacheAndReturnUtcNow() // Finally, update the cache and return UtcNow. Debug.Assert(fileTimeNow - fileTimeAtStartOfValidityWindow < LeapSecondCache.ValidityPeriodInTicks, "We should be within the validity window."); - Volatile.Write(ref s_leapSecondCache, new LeapSecondCache() + Volatile.Write(ref LeapSecondCache.s_leapSecondCache, new LeapSecondCache() { OSFileTimeTicksAtStartOfValidityWindow = fileTimeAtStartOfValidityWindow, DotnetDateDataAtStartOfValidityWindow = dotnetDateDataAtStartOfValidityWindow @@ -318,11 +327,6 @@ static DateTime LowGranularityNonCachedFallback() } } - // The leap second cache. May be accessed by multiple threads simultaneously. - // Writers must not mutate the object's fields after the reference is published. - // Readers are not required to use volatile semantics. - private static LeapSecondCache s_leapSecondCache = new LeapSecondCache(); - private sealed class LeapSecondCache { // The length of the validity window. Must be less than 23:59:59. @@ -333,6 +337,16 @@ private sealed class LeapSecondCache // The DateTime._dateData value at the beginning of the validity window. internal ulong DotnetDateDataAtStartOfValidityWindow; + + // The leap second cache. May be accessed by multiple threads simultaneously. + // Writers must not mutate the object's fields after the reference is published. + // Readers are not required to use volatile semantics. + internal static LeapSecondCache s_leapSecondCache = new LeapSecondCache(); + + // The configuration of system leap seconds support is intentionally here to avoid blocking + // AOT pre-initialization of public readonly DateTime statics. + internal static readonly bool s_systemSupportsLeapSeconds = GetSystemSupportsLeapSeconds(); + internal static readonly unsafe delegate* unmanaged[SuppressGCTransition] s_pfnGetSystemTimeAsFileTime = GetGetSystemTimeAsFileTimeFnPtr(); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTime.cs b/src/libraries/System.Private.CoreLib/src/System/DateTime.cs index bcf0d2dd8e0df..11c566195daf8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTime.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTime.cs @@ -273,7 +273,7 @@ public DateTime(int year, int month, int day, int hour, int minute, int second, if ((uint)millisecond >= MillisPerSecond) ThrowMillisecondOutOfRange(); if ((uint)kind > (uint)DateTimeKind.Local) ThrowInvalidKind(); - if (second != 60 || !s_systemSupportsLeapSeconds) + if (second != 60 || !SystemSupportsLeapSeconds) { ulong ticks = calendar.ToDateTime(year, month, day, hour, minute, second, millisecond).UTicks; _dateData = ticks | ((ulong)kind << KindShift); @@ -291,7 +291,7 @@ public DateTime(int year, int month, int day, int hour, int minute, int second, // public DateTime(int year, int month, int day, int hour, int minute, int second) { - if (second != 60 || !s_systemSupportsLeapSeconds) + if (second != 60 || !SystemSupportsLeapSeconds) { _dateData = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second); } @@ -307,7 +307,7 @@ public DateTime(int year, int month, int day, int hour, int minute, int second, { if ((uint)kind > (uint)DateTimeKind.Local) ThrowInvalidKind(); - if (second != 60 || !s_systemSupportsLeapSeconds) + if (second != 60 || !SystemSupportsLeapSeconds) { ulong ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second); _dateData = ticks | ((ulong)kind << KindShift); @@ -327,7 +327,7 @@ public DateTime(int year, int month, int day, int hour, int minute, int second, { ArgumentNullException.ThrowIfNull(calendar); - if (second != 60 || !s_systemSupportsLeapSeconds) + if (second != 60 || !SystemSupportsLeapSeconds) { _dateData = calendar.ToDateTime(year, month, day, hour, minute, second, 0).UTicks; } @@ -500,7 +500,7 @@ public DateTime(int year, int month, int day, int hour, int minute, int second, { ArgumentNullException.ThrowIfNull(calendar); - if (second != 60 || !s_systemSupportsLeapSeconds) + if (second != 60 || !SystemSupportsLeapSeconds) { _dateData = calendar.ToDateTime(year, month, day, hour, minute, second, millisecond).UTicks; } @@ -777,7 +777,7 @@ private static ulong Init(int year, int month, int day, int hour, int minute, in if ((uint)millisecond >= MillisPerSecond) ThrowMillisecondOutOfRange(); if ((uint)kind > (uint)DateTimeKind.Local) ThrowInvalidKind(); - if (second != 60 || !s_systemSupportsLeapSeconds) + if (second != 60 || !SystemSupportsLeapSeconds) { ulong ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second); ticks += (uint)millisecond * (uint)TicksPerMillisecond; @@ -1257,12 +1257,10 @@ public static DateTime FromFileTimeUtc(long fileTime) throw new ArgumentOutOfRangeException(nameof(fileTime), SR.ArgumentOutOfRange_FileTimeInvalid); } -#pragma warning disable 162 // Unrechable code on Unix - if (s_systemSupportsLeapSeconds) + if (SystemSupportsLeapSeconds) { return FromFileTimeLeapSecondsAware((ulong)fileTime); } -#pragma warning restore 162 // This is the ticks in Universal time for this fileTime. ulong universalTicks = (ulong)fileTime + FileTimeOffset; @@ -1696,12 +1694,10 @@ public long ToFileTimeUtc() // Treats the input as universal if it is not specified long ticks = ((_dateData & KindLocal) != 0) ? ToUniversalTime().Ticks : Ticks; -#pragma warning disable 162 // Unrechable code on Unix - if (s_systemSupportsLeapSeconds) + if (SystemSupportsLeapSeconds) { return (long)ToFileTimeLeapSecondsAware(ticks); } -#pragma warning restore 162 ticks -= FileTimeOffset; if (ticks < 0) @@ -1967,7 +1963,7 @@ internal static bool TryCreate(int year, int month, int day, int hour, int minut { ticks += TimeToTicks(hour, minute, second) + (uint)millisecond * (uint)TicksPerMillisecond; } - else if (second == 60 && s_systemSupportsLeapSeconds && IsValidTimeWithLeapSeconds(year, month, day, hour, minute, DateTimeKind.Unspecified)) + else if (second == 60 && SystemSupportsLeapSeconds && IsValidTimeWithLeapSeconds(year, month, day, hour, minute, DateTimeKind.Unspecified)) { // if we have leap second (second = 60) then we'll need to check if it is valid time. // if it is valid, then we adjust the second to 59 so DateTime will consider this second is last second diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs index b2289d1377ace..7c05f4ef5216b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs @@ -123,7 +123,7 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se _offsetMinutes = ValidateOffset(offset); int originalSecond = second; - if (second == 60 && DateTime.s_systemSupportsLeapSeconds) + if (second == 60 && DateTime.SystemSupportsLeapSeconds) { // Reset the leap second to 59 for now and then we'll validate it after getting the final UTC time. second = 59; @@ -145,7 +145,7 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se _offsetMinutes = ValidateOffset(offset); int originalSecond = second; - if (second == 60 && DateTime.s_systemSupportsLeapSeconds) + if (second == 60 && DateTime.SystemSupportsLeapSeconds) { // Reset the leap second to 59 for now and then we'll validate it after getting the final UTC time. second = 59; @@ -167,7 +167,7 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se _offsetMinutes = ValidateOffset(offset); int originalSecond = second; - if (second == 60 && DateTime.s_systemSupportsLeapSeconds) + if (second == 60 && DateTime.SystemSupportsLeapSeconds) { // Reset the leap second to 59 for now and then we'll validate it after getting the final UTC time. second = 59;