| File: src\runtime\src\libraries\System.Private.CoreLib\src\System\Runtime\CompilerServices\AsyncStateMachineDiagnostics.cs | Web Access |
| Project: System.Private.CoreLib.csproj (System.Private.CoreLib) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace System.Runtime.CompilerServices { internal static class AsyncStateMachineDiagnostics<TStateMachine> where TStateMachine : IAsyncStateMachine { #if NATIVEAOT // In NativeAOT we don't have reflection to resolve the method handle and state field offset. // Due to the way the state machine is constructed, we can't get a direct pointer to its MoveNext method // and using the interface dispatch to locate the method at slot 0 is unreliable due to Native AOT optimizations. // The state field is also not guaranteed to be at a specific offset due to auto layout and Native AOT optimizations. // To support this on Native AOT we would need to precompute this information in ILC and emit a // hash table keyed by state machine MethodTable. At runtime we would still need to cache // this data in static fields to avoid lookup cost when walking each continuation frame. // On JIT these static fields are lazy evaluated and cached on initial access, but on Native AOT // they will be pre-allocated, so code should be linked out when diagnostics is not supported. // Given the added complexity on Native AOT, the fact that this is only used for diagnostics, // and that Native AOT currently have limited asyncv1 diagnostics support in tooling, we can // postpone the support until proven needed. public static ulong MethodId { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetState(ref TStateMachine? _) => -1; #else private static readonly ulong s_methodId = ResolveMethodId(); private static readonly int s_resolveStateFieldOffset = ResolveStateFieldOffset(); public static ulong MethodId { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => s_methodId; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetState(ref TStateMachine? stateMachine) { if (typeof(TStateMachine).IsValueType) { // Struct: state field is inline at offset within the struct return Unsafe.As<byte, int>(ref Unsafe.AddByteOffset(ref Unsafe.As<TStateMachine?, byte>(ref stateMachine), (nint)s_resolveStateFieldOffset)); } else { // Class (debug builds): StateMachine is a reference, dereference to get object data if (stateMachine is not null) { return Unsafe.As<byte, int>(ref Unsafe.AddByteOffset(ref RuntimeHelpers.GetRawData(stateMachine), (nint)s_resolveStateFieldOffset)); } } return -1; } [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2090", Justification = "State machine types are always preserved.")] private static ulong ResolveMethodId() { MethodInfo? methodInfo = typeof(TStateMachine).GetMethod("MoveNext", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (methodInfo is not null) { #if MONO return (ulong)(nuint)RuntimeMethodHandle.GetNativeCodeInternal(methodInfo.MethodHandle.Value); #else if (methodInfo is IRuntimeMethodInfo runtimeMethodInfo) { return (ulong)(nuint)RuntimeMethodHandle.GetNativeCodeInternal(runtimeMethodInfo); } #endif } return 0; } [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2090", Justification = "State machine types are always preserved.")] private static int ResolveStateFieldOffset() { FieldInfo? stateField = typeof(TStateMachine).GetField("<>1__state", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (stateField is not null) { #if MONO return stateField.GetFieldOffset(); #else Debug.Assert(stateField is RtFieldInfo, $"Expected RtFieldInfo but got {stateField.GetType().Name}"); return RuntimeFieldHandle.GetInstanceFieldOffset((RtFieldInfo)stateField); #endif } return 0; } #endif } }