Extend Task.FromResult default task optimization to 16 byte unmanaged types (#87541)
authorStephen Toub <stoub@microsoft.com>
Thu, 15 Jun 2023 01:14:55 +0000 (21:14 -0400)
committerGitHub <noreply@github.com>
Thu, 15 Jun 2023 01:14:55 +0000 (21:14 -0400)
* Extend Task.FromResult default task optimization to 16 byte unmanaged types

For years, async methods / Task.FromResult has cached a `Task<T>` for `default(T)`, however it was only used when the result value was null.  Earlier in this release we extended that optimization to also use the default task when the value was 1, 2, 4, or 8 bytes (and not a reference type).  This extends that further to also handle types that are 16 bytes, so as to include types like Decimal, Guid, Int128, and DateTimeOffset.

* Fix tests to accomodate additional use of default cached task

src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs
src/libraries/System.Threading.Tasks/tests/System.Runtime.CompilerServices/AsyncTaskMethodBuilderTests.cs
src/libraries/System.Threading.Tasks/tests/Task/TaskRtTests.cs

index 22621aa..cae3361 100644 (file)
@@ -5297,13 +5297,15 @@ namespace System.Threading.Tasks
             }
             else if (!RuntimeHelpers.IsReferenceOrContainsReferences<TResult>())
             {
-                // For other value types, we special-case default(TResult) if we can easily compare bit patterns to default/0.
+                // For other value types, we special-case default(TResult) if we can efficiently compare bit patterns to default/0,
+                // which means any value type that's 1, 2, 4, 8, or 16 bytes in size and that doesn't contain references.
                 // We don't need to go through the equality operator of the TResult because we cached a task for default(TResult),
                 // so we only need to confirm that this TResult has the same bits as default(TResult).
                 if ((sizeof(TResult) == sizeof(byte) && *(byte*)&result == default(byte)) ||
                     (sizeof(TResult) == sizeof(ushort) && *(ushort*)&result == default(ushort)) ||
                     (sizeof(TResult) == sizeof(uint) && *(uint*)&result == default) ||
-                    (sizeof(TResult) == sizeof(ulong) && *(ulong*)&result == default))
+                    (sizeof(TResult) == sizeof(ulong) && *(ulong*)&result == default) ||
+                    (sizeof(TResult) == sizeof(UInt128) && *(UInt128*)&result == default))
                 {
                     return Task<TResult>.s_defaultResultTask;
                 }
index acbd9bd..2ef2b23 100644 (file)
@@ -377,7 +377,7 @@ namespace System.Threading.Tasks.Tests
         [Fact]
         public static void TaskMethodBuilderDecimal_DoesntUseCompletedCache()
         {
-            TaskMethodBuilderT_UsesCompletedCache(0m, shouldBeCached: false);
+            TaskMethodBuilderT_UsesCompletedCache(default(decimal), shouldBeCached: true);
             TaskMethodBuilderT_UsesCompletedCache(0.0m, shouldBeCached: false);
             TaskMethodBuilderT_UsesCompletedCache(42m, shouldBeCached: false);
         }
index b2f6939..f89f6d6 100644 (file)
@@ -470,70 +470,45 @@ namespace System.Threading.Tasks.Tests
 
             // Cached
 
-            foreach (bool result in new[] { false, true })
-            {
-                Assert.Same(Task.FromResult(result), Task.FromResult(result));
-                Assert.Equal(result, Task.FromResult(result).Result);
-            }
+            AssertCached(false);
+            AssertCached(true);
 
             for (int i = -1; i <= 8; i++)
             {
-                Assert.Same(Task.FromResult(i), Task.FromResult(i));
-                Assert.Equal(i, Task.FromResult(i).Result);
+                AssertCached(i);
             }
 
-            Assert.Same(Task.FromResult('\0'), Task.FromResult('\0'));
-            Assert.Equal('\0', Task.FromResult('\0').Result);
-
-            Assert.Same(Task.FromResult((byte)0), Task.FromResult((byte)0));
-            Assert.Equal(0, Task.FromResult((byte)0).Result);
-
-            Assert.Same(Task.FromResult((ushort)0), Task.FromResult((ushort)0));
-            Assert.Equal(0, Task.FromResult((ushort)0).Result);
-
-            Assert.Same(Task.FromResult((uint)0), Task.FromResult((uint)0));
-            Assert.Equal(0u, Task.FromResult((uint)0).Result);
-
-            Assert.Same(Task.FromResult((ulong)0), Task.FromResult((ulong)0));
-            Assert.Equal(0ul, Task.FromResult((ulong)0).Result);
-
-            Assert.Same(Task.FromResult((sbyte)0), Task.FromResult((sbyte)0));
-            Assert.Equal(0, Task.FromResult((sbyte)0).Result);
-
-            Assert.Same(Task.FromResult((short)0), Task.FromResult((short)0));
-            Assert.Equal(0, Task.FromResult((short)0).Result);
-
-            Assert.Same(Task.FromResult((long)0), Task.FromResult((long)0));
-            Assert.Equal(0, Task.FromResult((long)0).Result);
-
-            Assert.Same(Task.FromResult(IntPtr.Zero), Task.FromResult(IntPtr.Zero));
-            Assert.Equal(IntPtr.Zero, Task.FromResult(IntPtr.Zero).Result);
-
-            Assert.Same(Task.FromResult(UIntPtr.Zero), Task.FromResult(UIntPtr.Zero));
-            Assert.Equal(UIntPtr.Zero, Task.FromResult(UIntPtr.Zero).Result);
-
-            Assert.Same(Task.FromResult((Half)default), Task.FromResult((Half)default));
-            Assert.Equal((Half)default, Task.FromResult((Half)default).Result);
-
-            Assert.Same(Task.FromResult((float)default), Task.FromResult((float)default));
-            Assert.Equal((float)default, Task.FromResult((float)default).Result);
-
-            Assert.Same(Task.FromResult((double)default), Task.FromResult((double)default));
-            Assert.Equal((double)default, Task.FromResult((double)default).Result);
-
-            Assert.Same(Task.FromResult((TimeSpan)default), Task.FromResult((TimeSpan)default));
-            Assert.Equal((TimeSpan)default, Task.FromResult((TimeSpan)default).Result);
-
-            Assert.Same(Task.FromResult((DateTime)default), Task.FromResult((DateTime)default));
-            Assert.Equal((DateTime)default, Task.FromResult((DateTime)default).Result);
-
-            Assert.Same(Task.FromResult((object)null), Task.FromResult((object)null));
-            Assert.Null(Task.FromResult((object)null).Result);
-
-            Assert.Same(Task.FromResult((string)null), Task.FromResult((string)null));
-            Assert.Null(Task.FromResult((string)null).Result);
+            AssertCached<byte>();
+            AssertCached<sbyte>();
+            AssertCached<char>();
+            AssertCached<ushort>();
+            AssertCached<short>();
+            AssertCached<uint>();
+            AssertCached<int>();
+            AssertCached<ulong>();
+            AssertCached<long>();
+            AssertCached<nuint>();
+            AssertCached<nint>();
+            AssertCached<Half>();
+            AssertCached<float>();
+            AssertCached<double>();
+            AssertCached<decimal>();
+            AssertCached<TimeSpan>();
+            AssertCached<DateTime>();
+            AssertCached<Guid>();
+            AssertCached<Int128>();
+            AssertCached<UInt128>();
+            AssertCached<DayOfWeek>();
+            AssertCached<string>();
+            AssertCached<object>();
+
+            static void AssertCached<T>(T value = default)
+            {
+                Assert.Same(Task.FromResult(value), Task.FromResult(value));
+                Assert.Equal(value, Task.FromResult(value).Result);
+            }
 
-            // Not cached
+            // Not currently cached
 
             foreach (int i in new[] { -2, 9, int.MinValue, int.MaxValue })
             {
@@ -541,11 +516,11 @@ namespace System.Threading.Tasks.Tests
                 Assert.Equal(i, Task.FromResult(i).Result);
             }
 
+            // Should never return the same task
+
             Assert.NotSame(Task.FromResult((double)(+0.0)), Task.FromResult((double)(-0.0)));
             Assert.NotSame(Task.FromResult((float)(+0.0)), Task.FromResult((float)(-0.0)));
             Assert.NotSame(Task.FromResult((Half)(+0.0)), Task.FromResult((Half)(-0.0)));
-
-            Assert.NotSame(Task.FromResult((decimal)default), Task.FromResult((decimal)default));
         }
 
         [Fact]