Recover some of the size lost with AsyncMethodBuilder/Task unification (dotnet/corecl...
authorMichal Strehovský <MichalStrehovsky@users.noreply.github.com>
Tue, 29 Jan 2019 21:38:31 +0000 (22:38 +0100)
committerJan Kotas <jkotas@microsoft.com>
Wed, 30 Jan 2019 07:24:41 +0000 (23:24 -0800)
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 <dotnet-bot@microsoft.com>
Commit migrated from https://github.com/dotnet/coreclr/commit/a19ccc68afa0174a0cfbd58fa241a7f85785f567

src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs
src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs
src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs

index ef5d609..1a640c7 100644 (file)
@@ -544,12 +544,16 @@ namespace System.Runtime.CompilerServices
             where TStateMachine : IAsyncStateMachine
         {
             /// <summary>Delegate used to invoke on an ExecutionContext when passed an instance of this box type.</summary>
-            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<TStateMachine>);
                 // Only used privately to pass directly to EC.Run
                 Unsafe.As<AsyncStateMachineBox<TStateMachine>>(s).StateMachine.MoveNext();
-            };
+            }
 
             /// <summary>A delegate to the <see cref="MoveNext()"/> method.</summary>
             private Action _moveNextAction;
index 06eb118..7a1711b 100644 (file)
@@ -67,11 +67,16 @@ namespace System.Threading.Tasks
 
         private static readonly TaskFactory<TResult> s_Factory = new TaskFactory<TResult>();
 
-        // Delegate used by:
-        //     public static Task<Task<TResult>> WhenAny<TResult>(IEnumerable<Task<TResult>> tasks);
-        //     public static Task<Task<TResult>> WhenAny<TResult>(params Task<TResult>[] tasks);
-        // Used to "cast" from Task<Task> to Task<Task<TResult>>.
-        internal static readonly Func<Task<Task>, Task<TResult>> TaskWhenAnyCast = completed => (Task<TResult>)completed.Result;
+        // Extract rarely used helper for a static method in a separate type so that the Func<Task<Task>, Task<TResult>>
+        // 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<Task<TResult>> WhenAny<TResult>(IEnumerable<Task<TResult>> tasks);
+            //     public static Task<Task<TResult>> WhenAny<TResult>(params Task<TResult>[] tasks);
+            // Used to "cast" from Task<Task> to Task<Task<TResult>>.
+            internal static readonly Func<Task<Task>, Task<TResult>> Value = completed => (Task<TResult>)completed.Result;
+        }
 
         // Construct a promise-style task without any options. 
         internal Task() :
index a6bc8a5..2e2c6d6 100644 (file)
@@ -6156,7 +6156,7 @@ namespace System.Threading.Tasks
             Task<Task> intermediate = WhenAny((Task[])tasks);
 
             // Return a continuation task with the correct result type
-            return intermediate.ContinueWith(Task<TResult>.TaskWhenAnyCast, default,
+            return intermediate.ContinueWith(Task<TResult>.TaskWhenAnyCast.Value, default,
                 TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
         }
 
@@ -6185,7 +6185,7 @@ namespace System.Threading.Tasks
             Task<Task> intermediate = WhenAny((IEnumerable<Task>)tasks);
 
             // Return a continuation task with the correct result type
-            return intermediate.ContinueWith(Task<TResult>.TaskWhenAnyCast, default,
+            return intermediate.ContinueWith(Task<TResult>.TaskWhenAnyCast.Value, default,
                 TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
         }
         #endregion