From 8ed54b6ea5122f27c9ebcf9e7038506bdd4f1ec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 29 Jan 2019 20:39:34 +0100 Subject: [PATCH] Recover some of the size lost with AsyncMethodBuilder/Task unification 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. --- .../CompilerServices/AsyncMethodBuilder.cs | 8 ++++++-- .../shared/System/Threading/Tasks/Future.cs | 15 ++++++++++----- .../shared/System/Threading/Tasks/Task.cs | 4 ++-- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs index ef5d609a800..1a640c7d377 100644 --- a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs +++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs @@ -544,12 +544,16 @@ private class AsyncStateMachineBox : // SOS DumpAsync command dep 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/System.Private.CoreLib/shared/System/Threading/Tasks/Future.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/Future.cs index 06eb1186ae2..7a1711b409f 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/Tasks/Future.cs +++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/Future.cs @@ -67,11 +67,16 @@ public class Task : Task 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/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs index a6bc8a5a0ca..2e2c6d61bf7 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs +++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs @@ -6156,7 +6156,7 @@ public static Task> WhenAny(params Task[] 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 @@ public static Task> WhenAny(IEnumerable> ta 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