Skip to content

Commit e33d7ef

Browse files
authored
Simplify Task.FromResult (#79091)
* Simplify Task.FromResult - Remove unnecessary IsValueType check - Use pointers instead of Unsafe.As / object casts * Remove a couple of additional Unsafe.As calls
1 parent ab35b89 commit e33d7ef

File tree

1 file changed

+28
-33
lines changed
  • src/libraries/System.Private.CoreLib/src/System/Threading/Tasks

1 file changed

+28
-33
lines changed

src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5216,8 +5216,9 @@ private static int WaitAnyCore(Task[] tasks, int millisecondsTimeout, Cancellati
52165216
/// <param name="result">The result to store into the completed task.</param>
52175217
/// <returns>The successfully completed task.</returns>
52185218
[MethodImpl(MethodImplOptions.AggressiveInlining)] // method looks long, but for a given TResult it results in a relatively small amount of asm
5219-
public static Task<TResult> FromResult<TResult>(TResult result)
5219+
public static unsafe Task<TResult> FromResult<TResult>(TResult result)
52205220
{
5221+
#pragma warning disable 8500 // address of / sizeof of managed types
52215222
// The goal of this function is to be give back a cached task if possible, or to otherwise give back a new task.
52225223
// To give back a cached task, we need to be able to evaluate the incoming result value, and we need to avoid as
52235224
// much overhead as possible when doing so, as this function is invoked as part of the return path from every async
@@ -5229,48 +5230,42 @@ public static Task<TResult> FromResult<TResult>(TResult result)
52295230
// null reference types and default(Nullable<T>)
52305231
return Task<TResult>.s_defaultResultTask;
52315232
}
5232-
else if (typeof(TResult).IsValueType) // help the JIT avoid the value type branches for ref types
5233+
5234+
// For Boolean, we cache all possible values.
5235+
if (typeof(TResult) == typeof(bool)) // only the relevant branches are kept for each value-type generic instantiation
52335236
{
5234-
// For Boolean, we cache all possible values.
5235-
if (typeof(TResult) == typeof(bool)) // only the relevant branches are kept for each value-type generic instantiation
5236-
{
5237-
bool value = (bool)(object)result!;
5238-
Task<bool> task = value ? TaskCache.s_trueTask : TaskCache.s_falseTask;
5239-
return Unsafe.As<Task<TResult>>(task); // UnsafeCast avoids type check we know will succeed
5240-
}
5241-
// For Int32, we cache a range of common values, [-1,9).
5242-
else if (typeof(TResult) == typeof(int))
5237+
Task<bool> task = *(bool*)&result ? TaskCache.s_trueTask : TaskCache.s_falseTask;
5238+
return *(Task<TResult>*)&task;
5239+
}
5240+
5241+
// For Int32, we cache a range of common values, [-1,9).
5242+
if (typeof(TResult) == typeof(int))
5243+
{
5244+
// Compare to constants to avoid static field access if outside of cached range.
5245+
int value = *(int*)&result;
5246+
if ((uint)(value - TaskCache.InclusiveInt32Min) < (TaskCache.ExclusiveInt32Max - TaskCache.InclusiveInt32Min))
52435247
{
5244-
// Compare to constants to avoid static field access if outside of cached range.
5245-
int value = (int)(object)result!;
5246-
if ((uint)(value - TaskCache.InclusiveInt32Min) < (TaskCache.ExclusiveInt32Max - TaskCache.InclusiveInt32Min))
5247-
{
5248-
Task<int> task = TaskCache.s_int32Tasks[value - TaskCache.InclusiveInt32Min];
5249-
return Unsafe.As<Task<TResult>>(task); // Unsafe.As avoids a type check we know will succeed
5250-
}
5248+
Task<int> task = TaskCache.s_int32Tasks[value - TaskCache.InclusiveInt32Min];
5249+
return *(Task<TResult>*)&task;
52515250
}
5251+
}
5252+
else if (!RuntimeHelpers.IsReferenceOrContainsReferences<TResult>())
5253+
{
52525254
// For other value types, we special-case default(TResult) if we can easily compare bit patterns to default/0.
5253-
else if (!RuntimeHelpers.IsReferenceOrContainsReferences<TResult>())
5255+
// We don't need to go through the equality operator of the TResult because we cached a task for default(TResult),
5256+
// so we only need to confirm that this TResult has the same bits as default(TResult).
5257+
if ((sizeof(TResult) == sizeof(byte) && *(byte*)&result == default(byte)) ||
5258+
(sizeof(TResult) == sizeof(ushort) && *(ushort*)&result == default(ushort)) ||
5259+
(sizeof(TResult) == sizeof(uint) && *(uint*)&result == default) ||
5260+
(sizeof(TResult) == sizeof(ulong) && *(ulong*)&result == default))
52545261
{
5255-
unsafe
5256-
{
5257-
#pragma warning disable 8500 // sizeof of managed types
5258-
// We don't need to go through the equality operator of the TResult because we cached a task for default(TResult),
5259-
// so we only need to confirm that this TResult has the same bits as default(TResult).
5260-
if ((sizeof(TResult) == sizeof(byte) && Unsafe.As<TResult, byte>(ref result) == default(byte)) ||
5261-
(sizeof(TResult) == sizeof(ushort) && Unsafe.As<TResult, ushort>(ref result) == default(ushort)) ||
5262-
(sizeof(TResult) == sizeof(uint) && Unsafe.As<TResult, uint>(ref result) == default) ||
5263-
(sizeof(TResult) == sizeof(ulong) && Unsafe.As<TResult, ulong>(ref result) == default))
5264-
{
5265-
return Task<TResult>.s_defaultResultTask;
5266-
}
5267-
#pragma warning restore 8500
5268-
}
5262+
return Task<TResult>.s_defaultResultTask;
52695263
}
52705264
}
52715265

52725266
// No cached task is available. Manufacture a new one for this result.
52735267
return new Task<TResult>(result);
5268+
#pragma warning restore 8500
52745269
}
52755270

52765271
/// <summary>Creates a <see cref="Task{TResult}"/> that's completed exceptionally with the specified exception.</summary>

0 commit comments

Comments
 (0)