Skip to content

Commit

Permalink
Move leap seconds DateTime configuration statics into LeapSecondCache…
Browse files Browse the repository at this point in the history
… class (#77163)

This enables AOT initialization of public readonly DateTime statics like DateTime.Min/MaxValue.
  • Loading branch information
jkotas committed Oct 18, 2022
1 parent f7e6cf1 commit 570b373
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1900,7 +1900,6 @@
<Compile Include="$(MSBuildThisFileDirectory)Microsoft\Win32\SafeHandles\SafeRegistryHandle.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\AppDomain.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffer.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\DateTime.Win32.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\DateTime.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.Win32.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.Windows.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
19 changes: 0 additions & 19 deletions src/libraries/System.Private.CoreLib/src/System/DateTime.Win32.cs

This file was deleted.

40 changes: 27 additions & 13 deletions src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -129,7 +129,16 @@ private static DateTime CreateDateTimeFromSystemTime(in Interop.Kernel32.SYSTEMT
return new DateTime(ticks);
}

private static readonly unsafe delegate* unmanaged[SuppressGCTransition]<ulong*, void> 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]<ulong*, void> GetGetSystemTimeAsFileTimeFnPtr()
{
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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]<ulong*, void> s_pfnGetSystemTimeAsFileTime = GetGetSystemTimeAsFileTimeFnPtr();
}
}
}
22 changes: 9 additions & 13 deletions src/libraries/System.Private.CoreLib/src/System/DateTime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
Expand All @@ -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);
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down

0 comments on commit 570b373

Please sign in to comment.