From 844cf65513a3abb7b525a685860e7d7de968f089 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 29 Jan 2019 22:38:31 +0100 Subject: [PATCH] Recover some of the size lost with AsyncMethodBuilder/Task unification (dotnet/coreclr#6913) When we unified the implementations of these across all of our runtimes, we lost all size optimizations people have been doing on the Project N side over the past six years. This restores a bit of the loss. For one sample app with lots of async usage, this removes 2.1 MB of generic instantiations. There is more we can do, but I can't spend time on that right now. These two things jumped out on me when I was looking at it back in December and were an easy fix I wanted to do for a while. Signed-off-by: dotnet-bot Commit migrated from https://github.com/dotnet/coreclr/commit/a19ccc68afa0174a0cfbd58fa241a7f85785f567 --- .../System/Runtime/CompilerServices/AsyncMethodBuilder.cs | 8 ++++++-- .../src/System/Threading/Tasks/Future.cs | 15 ++++++++++----- .../src/System/Threading/Tasks/Task.cs | 4 ++-- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs index ef5d609..1a640c7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs @@ -544,12 +544,16 @@ namespace System.Runtime.CompilerServices where TStateMachine : IAsyncStateMachine { /// Delegate used to invoke on an ExecutionContext when passed an instance of this box type. - private static readonly ContextCallback s_callback = s => + private static readonly ContextCallback s_callback = ExecutionContextCallback; + + // Used to initialize s_callback above. We don't use a lambda for this on purpose: a lambda would + // introduce a new generic type behind the scenes that comes with a hefty size penalty in AOT builds. + private static void ExecutionContextCallback(object s) { Debug.Assert(s is AsyncStateMachineBox); // Only used privately to pass directly to EC.Run Unsafe.As>(s).StateMachine.MoveNext(); - }; + } /// A delegate to the method. private Action _moveNextAction; diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs index 06eb118..7a1711b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs @@ -67,11 +67,16 @@ namespace System.Threading.Tasks private static readonly TaskFactory s_Factory = new TaskFactory(); - // Delegate used by: - // public static Task> WhenAny(IEnumerable> tasks); - // public static Task> WhenAny(params Task[] tasks); - // Used to "cast" from Task to Task>. - internal static readonly Func, Task> TaskWhenAnyCast = completed => (Task)completed.Result; + // Extract rarely used helper for a static method in a separate type so that the Func, Task> + // generic instantiations don't contribute to all Task instantiations, but only those where WhenAny is used. + internal static class TaskWhenAnyCast + { + // Delegate used by: + // public static Task> WhenAny(IEnumerable> tasks); + // public static Task> WhenAny(params Task[] tasks); + // Used to "cast" from Task to Task>. + internal static readonly Func, Task> Value = completed => (Task)completed.Result; + } // Construct a promise-style task without any options. internal Task() : diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs index a6bc8a5..2e2c6d6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @@ -6156,7 +6156,7 @@ namespace System.Threading.Tasks Task intermediate = WhenAny((Task[])tasks); // Return a continuation task with the correct result type - return intermediate.ContinueWith(Task.TaskWhenAnyCast, default, + return intermediate.ContinueWith(Task.TaskWhenAnyCast.Value, default, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default); } @@ -6185,7 +6185,7 @@ namespace System.Threading.Tasks Task intermediate = WhenAny((IEnumerable)tasks); // Return a continuation task with the correct result type - return intermediate.ContinueWith(Task.TaskWhenAnyCast, default, + return intermediate.ContinueWith(Task.TaskWhenAnyCast.Value, default, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default); } #endregion -- 2.7.4