diff --git a/docs/design/coreclr/botr/clr-abi.md b/docs/design/coreclr/botr/clr-abi.md index f225b7897caaea..7e6ef388a8fca8 100644 --- a/docs/design/coreclr/botr/clr-abi.md +++ b/docs/design/coreclr/botr/clr-abi.md @@ -96,6 +96,34 @@ There is no defined/enforced/declared ordering between the generic parameter and call(["this" pointer] [return buffer pointer] [generics context|varargs cookie] [userargs]*) ``` +## Async + +Async calling convention is additive to other calling conventions when supported. The set of scenarios is constrained to regular static/virtual calls and does not, for example, support PInvokes or varargs. At the minimum ordinary static calls, calls with `this` parameter or generic hidden parameters are supported. + +Async calling convention adds an extra `Continuation` parameter and an extra return, which sematically takes precedence when not `null`. A non-null `Continuation` upon return signals that the computation is not complete and the formal result is not ready. A non-null argument means that the function is resuming and should extract the state from the `Continuation` and continue execution (while ignoring all other arguments). + +The `Continuation` is a managed object and needs to be tracked accordingly. The GC info includes the continuation result as live at Async call sites. + +### Returning `Continuation` +To return `Continuation` we use a volatile/calee-trash register that cannot be used to return the actual result. + +| arch | `REG_ASYNC_CONTINUATION_RET` | +| ------------- | ------------- | +| x86 | ecx | +| x64 | rcx | +| arm | r2 | +| arm64 | x2 | +| risc-v | a2 | + +### Passing `Continuation` argument +The `Continuation` parameter is passed at the same position as generic instantiation parameter or immediately after, if both present. + +``` +call(["this" pointer] [return buffer pointer] [generics context] [continuation] [userargs]) // not x86 + +call(["this" pointer] [return buffer pointer] [userargs] [generics context] [continuation]) // x86 +``` + ## AMD64-only: by-value value types Just like native, AMD64 has implicit-byrefs. Any structure (value type in IL parlance) that is not 1, 2, 4, or 8 bytes in size (i.e., 3, 5, 6, 7, or >= 9 bytes in size) that is declared to be passed by value, is instead passed by reference. For JIT generated code, it follows the native ABI where the passed-in reference is a pointer to a compiler generated temp local on the stack. However, there are some cases within remoting or reflection where apparently stackalloc is too hard, and so they pass in pointers within the GC heap, thus the JITed code must report these implicit byref parameters as interior pointers (BYREFs in JIT parlance), in case the callee is one of these reflection paths. Similarly, all writes must use checked write barriers. @@ -729,4 +757,4 @@ MyStruct Test2() // We can use memset here return default; } -``` \ No newline at end of file +``` diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index 4fe5ec6a517af2..a4ddab439f6c07 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -205,6 +205,7 @@ + diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs new file mode 100644 index 00000000000000..b2043e975345f1 --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs @@ -0,0 +1,409 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers.Binary; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Runtime.Versioning; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Runtime.CompilerServices +{ + internal struct ExecutionAndSyncBlockStore + { + // Store current ExecutionContext and SynchronizationContext as "previousXxx". + // This allows us to restore them and undo any Context changes made in stateMachine.MoveNext + // so that they won't "leak" out of the first await. + public ExecutionContext? _previousExecutionCtx; + public SynchronizationContext? _previousSyncCtx; + public Thread _thread; + + public void Push() + { + _thread = Thread.CurrentThread; + _previousExecutionCtx = _thread._executionContext; + _previousSyncCtx = _thread._synchronizationContext; + } + + public void Pop() + { + // The common case is that these have not changed, so avoid the cost of a write barrier if not needed. + if (_previousSyncCtx != _thread._synchronizationContext) + { + // Restore changed SynchronizationContext back to previous + _thread._synchronizationContext = _previousSyncCtx; + } + + ExecutionContext? currentExecutionCtx = _thread._executionContext; + if (_previousExecutionCtx != currentExecutionCtx) + { + ExecutionContext.RestoreChangedContextToThread(_thread, _previousExecutionCtx, currentExecutionCtx); + } + } + } + + [Flags] + internal enum CorInfoContinuationFlags + { + // Whether or not the continuation expects the result to be boxed and + // placed in the GCData array at index 0. Not set if the callee is void. + CORINFO_CONTINUATION_RESULT_IN_GCDATA = 1, + // If this bit is set the continuation resumes inside a try block and thus + // if an exception is being propagated, needs to be resumed. The exception + // should be placed at index 0 or 1 depending on whether the continuation + // also expects a result. + CORINFO_CONTINUATION_NEEDS_EXCEPTION = 2, + // If this bit is set the continuation has an OSR IL offset saved in the + // beginning of 'Data'. + CORINFO_CONTINUATION_OSR_IL_OFFSET_IN_DATA = 4, + } + + internal sealed unsafe class Continuation + { + public Continuation? Next; + public delegate* Resume; + public uint State; + public CorInfoContinuationFlags Flags; + + // Data and GCData contain the state of the continuation. + // Note: The JIT is ultimately responsible for laying out these arrays. + // However, other parts of the system depend on the layout to + // know where to locate or place various pieces of data: + // + // 1. Resumption stubs need to know where to place the return value + // inside the next continuation. If the return value has GC references + // then it is boxed and placed at GCData[0]; otherwise, it is placed + // inside Data at offset 0 if + // CORINFO_CONTINUATION_OSR_IL_OFFSET_IN_DATA is NOT set and otherwise + // at offset 4. + // + // 2. Likewise, Finalize[Value]TaskReturningThunk needs to know from + // where to extract the return value. + // + // 3. The dispatcher needs to know where to place the exception inside + // the next continuation with a handler. Continuations with handlers + // have CORINFO_CONTINUATION_NEEDS_EXCEPTION set. The exception is + // placed at GCData[0] if CORINFO_CONTINUATION_RESULT_IN_GCDATA is NOT + // set, and otherwise at GCData[1]. + // + public byte[]? Data; + public object?[]? GCData; + } + + public static partial class AsyncHelpers + { + // This is the "magic" method on wich other "Await" methods are built. + // Calling this from an Async method returns the continuation to the caller thus + // explicitly initiates suspension. + [Intrinsic] + private static void AsyncSuspend(Continuation continuation) => throw new UnreachableException(); + + // Used during suspensions to hold the continuation chain and on what we are waiting. + // Methods like FinalizeTaskReturningThunk will unlink the state and wrap into a Task. + private struct RuntimeAsyncAwaitState + { + public Continuation? SentinelContinuation; + public INotifyCompletion? Notifier; + } + + [ThreadStatic] + private static RuntimeAsyncAwaitState t_runtimeAsyncAwaitState; + + private static Continuation AllocContinuation(Continuation prevContinuation, nuint numGCRefs, nuint dataSize) + { + Continuation newContinuation = new Continuation { Data = new byte[dataSize], GCData = new object[numGCRefs] }; + prevContinuation.Next = newContinuation; + return newContinuation; + } + + private static unsafe Continuation AllocContinuationMethod(Continuation prevContinuation, nuint numGCRefs, nuint dataSize, MethodDesc* method) + { + LoaderAllocator loaderAllocator = RuntimeMethodHandle.GetLoaderAllocator(new RuntimeMethodHandleInternal((IntPtr)method)); + object?[] gcData; + if (loaderAllocator != null) + { + gcData = new object[numGCRefs + 1]; + gcData[numGCRefs] = loaderAllocator; + } + else + { + gcData = new object[numGCRefs]; + } + + Continuation newContinuation = new Continuation { Data = new byte[dataSize], GCData = gcData }; + prevContinuation.Next = newContinuation; + return newContinuation; + } + + private static unsafe Continuation AllocContinuationClass(Continuation prevContinuation, nuint numGCRefs, nuint dataSize, MethodTable* methodTable) + { + IntPtr loaderAllocatorHandle = methodTable->GetLoaderAllocatorHandle(); + object?[] gcData; + if (loaderAllocatorHandle != IntPtr.Zero) + { + gcData = new object[numGCRefs + 1]; + gcData[numGCRefs] = GCHandle.FromIntPtr(loaderAllocatorHandle).Target; + } + else + { + gcData = new object[numGCRefs]; + } + + Continuation newContinuation = new Continuation { Data = new byte[dataSize], GCData = gcData }; + prevContinuation.Next = newContinuation; + return newContinuation; + } + + // Used to box the return value before storing into caller's continuation + // if the value is an object-containing struct. + // We are allocating a box directly instead of relying on regular boxing because we want + // to store structs without changing layout, including nullables. + private static unsafe object AllocContinuationResultBox(void* ptr) + { + MethodTable* pMT = (MethodTable*)ptr; + Debug.Assert(pMT->IsValueType); + // We need no type/cctor checks since we will be storing an instance that already exists. + return RuntimeTypeHandle.InternalAllocNoChecks((MethodTable*)pMT); + } + + // wrapper to await a notifier + private struct AwaitableProxy : ICriticalNotifyCompletion + { + private readonly INotifyCompletion _notifier; + + public AwaitableProxy(INotifyCompletion notifier) + { + _notifier = notifier; + } + + public bool IsCompleted => false; + + public void OnCompleted(Action action) + { + _notifier!.OnCompleted(action); + } + + public AwaitableProxy GetAwaiter() { return this; } + + public void UnsafeOnCompleted(Action action) + { + if (_notifier is ICriticalNotifyCompletion criticalNotification) + { + criticalNotification.UnsafeOnCompleted(action); + } + else + { + _notifier!.OnCompleted(action); + } + } + + public void GetResult() { } + } + + private static Continuation UnlinkHeadContinuation(out AwaitableProxy awaitableProxy) + { + ref RuntimeAsyncAwaitState state = ref t_runtimeAsyncAwaitState; + awaitableProxy = new AwaitableProxy(state.Notifier!); + state.Notifier = null; + + Continuation sentinelContinuation = state.SentinelContinuation!; + Continuation head = sentinelContinuation.Next!; + sentinelContinuation.Next = null; + return head; + } + + // When a Task-returning thunk gets a continuation result + // it calls here to make a Task that awaits on the current async state. + // NOTE: This cannot be Runtime Async. Must use C# state machine or make one by hand. + private static async Task FinalizeTaskReturningThunk(Continuation continuation) + { + Continuation finalContinuation = new Continuation(); + + // Note that the exact location the return value is placed is tied + // into getAsyncResumptionStub in the VM, so do not change this + // without also changing that code (and the JIT). + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + finalContinuation.Flags = CorInfoContinuationFlags.CORINFO_CONTINUATION_RESULT_IN_GCDATA | CorInfoContinuationFlags.CORINFO_CONTINUATION_NEEDS_EXCEPTION; + finalContinuation.GCData = new object[1]; + } + else + { + finalContinuation.Flags = CorInfoContinuationFlags.CORINFO_CONTINUATION_NEEDS_EXCEPTION; + finalContinuation.Data = new byte[Unsafe.SizeOf()]; + } + + continuation.Next = finalContinuation; + + while (true) + { + Continuation headContinuation = UnlinkHeadContinuation(out var awaitableProxy); + await awaitableProxy; + Continuation? finalResult = DispatchContinuations(headContinuation); + if (finalResult != null) + { + Debug.Assert(finalResult == finalContinuation); + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + if (typeof(T).IsValueType) + { + return Unsafe.As(ref finalResult.GCData![0]!.GetRawData()); + } + + return Unsafe.As(ref finalResult.GCData![0]!); + } + else + { + return Unsafe.As(ref finalResult.Data![0]); + } + } + } + } + + private static async Task FinalizeTaskReturningThunk(Continuation continuation) + { + Continuation finalContinuation = new Continuation + { + Flags = CorInfoContinuationFlags.CORINFO_CONTINUATION_NEEDS_EXCEPTION, + }; + continuation.Next = finalContinuation; + + while (true) + { + Continuation headContinuation = UnlinkHeadContinuation(out var awaitableProxy); + await awaitableProxy; + Continuation? finalResult = DispatchContinuations(headContinuation); + if (finalResult != null) + { + Debug.Assert(finalResult == finalContinuation); + return; + } + } + } + + private static async ValueTask FinalizeValueTaskReturningThunk(Continuation continuation) + { + Continuation finalContinuation = new Continuation(); + + // Note that the exact location the return value is placed is tied + // into getAsyncResumptionStub in the VM, so do not change this + // without also changing that code (and the JIT). + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + finalContinuation.Flags = CorInfoContinuationFlags.CORINFO_CONTINUATION_RESULT_IN_GCDATA | CorInfoContinuationFlags.CORINFO_CONTINUATION_NEEDS_EXCEPTION; + finalContinuation.GCData = new object[1]; + } + else + { + finalContinuation.Flags = CorInfoContinuationFlags.CORINFO_CONTINUATION_NEEDS_EXCEPTION; + finalContinuation.Data = new byte[Unsafe.SizeOf()]; + } + + continuation.Next = finalContinuation; + + while (true) + { + Continuation headContinuation = UnlinkHeadContinuation(out var awaitableProxy); + await awaitableProxy; + Continuation? finalResult = DispatchContinuations(headContinuation); + if (finalResult != null) + { + Debug.Assert(finalResult == finalContinuation); + if (RuntimeHelpers.IsReferenceOrContainsReferences()) + { + if (typeof(T).IsValueType) + { + return Unsafe.As(ref finalResult.GCData![0]!.GetRawData()); + } + + return Unsafe.As(ref finalResult.GCData![0]!); + } + else + { + return Unsafe.As(ref finalResult.Data![0]); + } + } + } + } + + private static async ValueTask FinalizeValueTaskReturningThunk(Continuation continuation) + { + Continuation finalContinuation = new Continuation + { + Flags = CorInfoContinuationFlags.CORINFO_CONTINUATION_NEEDS_EXCEPTION, + }; + continuation.Next = finalContinuation; + + while (true) + { + Continuation headContinuation = UnlinkHeadContinuation(out var awaitableProxy); + await awaitableProxy; + Continuation? finalResult = DispatchContinuations(headContinuation); + if (finalResult != null) + { + Debug.Assert(finalResult == finalContinuation); + return; + } + } + } + + // Return a continuation object if that is the one which has the final + // result of the Task, if the real output of the series of continuations was + // an exception, it is allowed to propagate out. + // OR + // return NULL to indicate that this isn't yet done. + private static unsafe Continuation? DispatchContinuations(Continuation? continuation) + { + Debug.Assert(continuation != null); + + while (true) + { + Continuation? newContinuation; + try + { + newContinuation = continuation.Resume(continuation); + } + catch (Exception ex) + { + continuation = UnwindToPossibleHandler(continuation); + if (continuation.Resume == null) + { + throw; + } + + continuation.GCData![(continuation.Flags & CorInfoContinuationFlags.CORINFO_CONTINUATION_RESULT_IN_GCDATA) != 0 ? 1 : 0] = ex; + continue; + } + + if (newContinuation != null) + { + newContinuation.Next = continuation.Next; + return null; + } + + continuation = continuation.Next; + Debug.Assert(continuation != null); + + if (continuation.Resume == null) + { + return continuation; // Return the result containing Continuation + } + } + } + + private static Continuation UnwindToPossibleHandler(Continuation continuation) + { + while (true) + { + Debug.Assert(continuation.Next != null); + continuation = continuation.Next; + if ((continuation.Flags & CorInfoContinuationFlags.CORINFO_CONTINUATION_NEEDS_EXCEPTION) != 0) + return continuation; + } + } + } +} diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index d60b48ec0e552e..629774bd0df2a2 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -930,6 +930,9 @@ public uint GetNumInstanceFieldBytesIfContainsGCPointers() Debug.Assert((BaseSize - (nuint)(2 * sizeof(IntPtr)) == GetNumInstanceFieldBytes())); return BaseSize - (uint)(2 * sizeof(IntPtr)); } + + [MethodImpl(MethodImplOptions.InternalCall)] + public extern IntPtr GetLoaderAllocatorHandle(); } // Subset of src\vm\typedesc.h @@ -963,7 +966,7 @@ internal static ref byte MaskStaticsPointer(ref byte staticsPtr) { fixed (byte* p = &staticsPtr) { - return ref Unsafe.AsRef((byte*)((nuint)p & ~(nuint)DynamicStaticsInfo.ISCLASSNOTINITED)); + return ref Unsafe.AsRef((byte*)((nuint)p & ~(nuint)DynamicStaticsInfo.ISCLASSNOTINITED)); } } @@ -1150,8 +1153,8 @@ public static bool CanCastToForReflection(TypeHandle srcTH, TypeHandle destTH) CastResult.CanCast => true, CastResult.CannotCast => false, - // Reflection allows T to be cast to Nullable. - // See ObjIsInstanceOfCore() + // Reflection allows T to be cast to Nullable. + // See ObjIsInstanceOfCore() _ => CanCastToWorker(srcTH, destTH, nullableCast: true) }; } diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index 1d70bed0c21445..8e03b99165d557 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -299,6 +299,13 @@ internal static object InternalAlloc(RuntimeType type) [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeTypeHandle_InternalAlloc")] private static unsafe partial void InternalAlloc(MethodTable* pMT, ObjectHandleOnStack result); + internal static object InternalAllocNoChecks(MethodTable* pMT) + { + object? result = null; + InternalAllocNoChecks(pMT, ObjectHandleOnStack.Create(ref result)); + return result!; + } + internal static object InternalAllocNoChecks(RuntimeType type) { Debug.Assert(!type.GetNativeTypeHandle().IsTypeDesc); diff --git a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs index 5b33c503678275..a6494bf27883e7 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs @@ -1598,6 +1598,9 @@ internal static void MulticastDebuggerTraceHelper(object o, int count) [Intrinsic] [MethodImpl(MethodImplOptions.InternalCall)] internal static extern IntPtr NextCallReturnAddress(); + + [Intrinsic] + internal static Continuation? AsyncCallContinuation() => null; } // class StubHelpers #if FEATURE_COMINTEROP diff --git a/src/coreclr/clrdefinitions.cmake b/src/coreclr/clrdefinitions.cmake index 695c275492dc63..b953dac5d862a2 100644 --- a/src/coreclr/clrdefinitions.cmake +++ b/src/coreclr/clrdefinitions.cmake @@ -161,6 +161,8 @@ if(FEATURE_OBJCMARSHAL) add_compile_definitions(FEATURE_OBJCMARSHAL) endif() +# add_compile_definitions(FEATURE_RUNTIME_ASYNC) + add_compile_definitions($<$>>:FEATURE_PROFAPI_ATTACH_DETACH>) add_definitions(-DFEATURE_READYTORUN) diff --git a/src/coreclr/inc/corcompile.h b/src/coreclr/inc/corcompile.h index 57ca94832e6be7..472b0973664a63 100644 --- a/src/coreclr/inc/corcompile.h +++ b/src/coreclr/inc/corcompile.h @@ -185,6 +185,7 @@ enum EncodeMethodSigFlags ENCODE_METHOD_SIG_Constrained = 0x20, ENCODE_METHOD_SIG_OwnerType = 0x40, ENCODE_METHOD_SIG_UpdateContext = 0x80, + ENCODE_METHOD_SIG_AsyncVariant = 0x100, }; enum EncodeFieldSigFlags diff --git a/src/coreclr/inc/corhdr.h b/src/coreclr/inc/corhdr.h index 1e47d7557d319e..0bd7755e3b0d5e 100644 --- a/src/coreclr/inc/corhdr.h +++ b/src/coreclr/inc/corhdr.h @@ -640,13 +640,15 @@ typedef enum CorMethodImpl miNoOptimization = 0x0040, // Method may not be optimized. miAggressiveOptimization = 0x0200, // Method may contain hot code and should be aggressively optimized. + miAsync = 0x2000, // Method requires async state machine rewrite. + // These are the flags that are allowed in MethodImplAttribute's Value // property. This should include everything above except the code impl // flags (which are used for MethodImplAttribute's MethodCodeType field). miUserMask = miManagedMask | miForwardRef | miPreserveSig | miInternalCall | miSynchronized | miNoInlining | miAggressiveInlining | - miNoOptimization | miAggressiveOptimization, + miNoOptimization | miAggressiveOptimization | miAsync, miMaxMethodImplVal = 0xffff, // Range check value } CorMethodImpl; @@ -670,6 +672,7 @@ typedef enum CorMethodImpl #define IsMiAggressiveInlining(x) ((x) & miAggressiveInlining) #define IsMiNoOptimization(x) ((x) & miNoOptimization) #define IsMiAggressiveOptimization(x) (((x) & (miAggressiveOptimization | miNoOptimization)) == miAggressiveOptimization) +#define IsMiAsync(x) ((x) & miAsync) // PinvokeMap attr bits, used by DefinePinvokeMap. typedef enum CorPinvokeMap diff --git a/src/coreclr/inc/corhlprpriv.h b/src/coreclr/inc/corhlprpriv.h index 7df77ea0ca6281..e960eb416fc984 100644 --- a/src/coreclr/inc/corhlprpriv.h +++ b/src/coreclr/inc/corhlprpriv.h @@ -573,6 +573,16 @@ class CQuickArrayList : protected CQuickArray return m_curSize; } + T* Ptr() + { + return (T*) CQuickBytesBase::Ptr(); + } + + const T* Ptr() const + { + return (T*) CQuickBytesBase::Ptr(); + } + void Shrink() { CQuickArray::Shrink(m_curSize); diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 16018bb403117e..02d108c853ebb1 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -405,6 +405,7 @@ enum CorInfoHelpFunc CORINFO_HELP_THROW, // Throw an exception object CORINFO_HELP_RETHROW, // Rethrow the currently active exception + CORINFO_HELP_THROWEXACT, // Throw an exception object, preserving stack trace CORINFO_HELP_USER_BREAKPOINT, // For a user program to break to the debugger CORINFO_HELP_RNGCHKFAIL, // array bounds check failed CORINFO_HELP_OVERFLOW, // throw an overflow exception @@ -578,7 +579,7 @@ enum CorInfoHelpFunc CORINFO_HELP_STACK_PROBE, // Probes each page of the allocated stack frame CORINFO_HELP_PATCHPOINT, // Notify runtime that code has reached a patchpoint - CORINFO_HELP_PARTIAL_COMPILATION_PATCHPOINT, // Notify runtime that code has reached a part of the method that wasn't originally jitted. + CORINFO_HELP_PATCHPOINT_FORCED, // Notify runtime that code has reached a part of the method that needs to transition CORINFO_HELP_CLASSPROFILE32, // Update 32-bit class profile for a call site CORINFO_HELP_CLASSPROFILE64, // Update 64-bit class profile for a call site @@ -594,6 +595,10 @@ enum CorInfoHelpFunc CORINFO_HELP_VALIDATE_INDIRECT_CALL, // CFG: Validate function pointer CORINFO_HELP_DISPATCH_INDIRECT_CALL, // CFG: Validate and dispatch to pointer + CORINFO_HELP_ALLOC_CONTINUATION, + CORINFO_HELP_ALLOC_CONTINUATION_METHOD, + CORINFO_HELP_ALLOC_CONTINUATION_CLASS, + CORINFO_HELP_COUNT, }; @@ -668,6 +673,7 @@ enum CorInfoCallConv CORINFO_CALLCONV_HASTHIS = 0x20, CORINFO_CALLCONV_EXPLICITTHIS=0x40, CORINFO_CALLCONV_PARAMTYPE = 0x80, // Passed last. Same as CORINFO_GENERICS_CTXT_FROM_PARAMTYPEARG + CORINFO_CALLCONV_ASYNCCALL = 0x100, // Is this a call to an async function? }; // Represents the calling conventions supported with the extensible calling convention syntax @@ -715,7 +721,6 @@ enum CorInfoOptions CORINFO_GENERICS_CTXT_FROM_METHODDESC | CORINFO_GENERICS_CTXT_FROM_METHODTABLE), CORINFO_GENERICS_CTXT_KEEP_ALIVE = 0x00000100, // Keep the generics context alive throughout the method even if there is no explicit use, and report its location to the CLR - }; // @@ -992,6 +997,7 @@ struct CORINFO_SIG_INFO unsigned totalILArgs() { return (numArgs + (hasImplicitThis() ? 1 : 0)); } bool isVarArg() { return ((getCallConv() == CORINFO_CALLCONV_VARARG) || (getCallConv() == CORINFO_CALLCONV_NATIVEVARARG)); } bool hasTypeArg() { return ((callConv & CORINFO_CALLCONV_PARAMTYPE) != 0); } + bool isAsyncCall() { return ((callConv & CORINFO_CALLCONV_ASYNCCALL) != 0); } }; struct CORINFO_METHOD_INFO @@ -1397,6 +1403,9 @@ enum CorInfoTokenKind // token comes from devirtualizing a method CORINFO_TOKENKIND_DevirtualizedMethod = 0x800 | CORINFO_TOKENKIND_Method, + + // token comes from runtime async awaiting pattern + CORINFO_TOKENKIND_Await = 0x2000 | CORINFO_TOKENKIND_Method, }; struct CORINFO_RESOLVED_TOKEN @@ -1689,6 +1698,42 @@ struct CORINFO_EE_INFO CORINFO_OS osType; }; +enum CorInfoContinuationFlags +{ + // Whether or not the continuation expects the result to be boxed and + // placed in the GCData array at index 0. Not set if the callee is void. + CORINFO_CONTINUATION_RESULT_IN_GCDATA = 1, + // If this bit is set the continuation resumes inside a try block and thus + // if an exception is being propagated, needs to be resumed. The exception + // should be placed at index 0 or 1 depending on whether the continuation + // also expects a result. + CORINFO_CONTINUATION_NEEDS_EXCEPTION = 2, + // If this bit is set the continuation has an OSR IL offset saved in the + // beginning of 'Data'. + CORINFO_CONTINUATION_OSR_IL_OFFSET_IN_DATA = 4, +}; + +struct CORINFO_ASYNC_INFO +{ + // Class handle for System.Runtime.CompilerServices.Continuation + CORINFO_CLASS_HANDLE continuationClsHnd; + // 'Next' field + CORINFO_FIELD_HANDLE continuationNextFldHnd; + // 'Resume' field + CORINFO_FIELD_HANDLE continuationResumeFldHnd; + // 'State' field + CORINFO_FIELD_HANDLE continuationStateFldHnd; + // 'Flags' field + CORINFO_FIELD_HANDLE continuationFlagsFldHnd; + // 'Data' field + CORINFO_FIELD_HANDLE continuationDataFldHnd; + // 'GCData' field + CORINFO_FIELD_HANDLE continuationGCDataFldHnd; + // Whether or not the continuation needs to be allocated through the + // helper that also takes a method handle + bool continuationsNeedMethodHandle; +}; + // Flags passed from JIT to runtime. enum CORINFO_GET_TAILCALL_HELPERS_FLAGS { @@ -2952,6 +2997,10 @@ class ICorStaticInfo CORINFO_EE_INFO *pEEInfoOut ) = 0; + virtual void getAsyncInfo( + CORINFO_ASYNC_INFO* pAsyncInfoOut + ) = 0; + /*********************************************************************************/ // // Diagnostic methods @@ -3287,6 +3336,8 @@ class ICorDynamicInfo : public ICorStaticInfo CORINFO_TAILCALL_HELPERS* pResult ) = 0; + virtual CORINFO_METHOD_HANDLE getAsyncResumptionStub() = 0; + // Optionally, convert calli to regular method call. This is for PInvoke argument marshalling. virtual bool convertPInvokeCalliToCall( CORINFO_RESOLVED_TOKEN * pResolvedToken, diff --git a/src/coreclr/inc/corjitflags.h b/src/coreclr/inc/corjitflags.h index 920739ff0496c0..86d9b44a797c9f 100644 --- a/src/coreclr/inc/corjitflags.h +++ b/src/coreclr/inc/corjitflags.h @@ -63,7 +63,7 @@ class CORJIT_FLAGS CORJIT_FLAG_RELATIVE_CODE_RELOCS = 29, // JIT should generate PC-relative address computations instead of EE relocation records CORJIT_FLAG_SOFTFP_ABI = 30, // Enable armel calling convention #endif - + CORJIT_FLAG_ASYNC = 31, // Generate Code for use as an async function }; CORJIT_FLAGS() diff --git a/src/coreclr/inc/icorjitinfoimpl_generated.h b/src/coreclr/inc/icorjitinfoimpl_generated.h index 9f8d08e75791cf..73e330058f9d2d 100644 --- a/src/coreclr/inc/icorjitinfoimpl_generated.h +++ b/src/coreclr/inc/icorjitinfoimpl_generated.h @@ -495,6 +495,9 @@ bool runWithSPMIErrorTrap( void getEEInfo( CORINFO_EE_INFO* pEEInfoOut) override; +void getAsyncInfo( + CORINFO_ASYNC_INFO* pAsyncInfoOut) override; + mdMethodDef getMethodDefFromMethod( CORINFO_METHOD_HANDLE hMethod) override; @@ -657,6 +660,8 @@ bool getTailCallHelpers( CORINFO_GET_TAILCALL_HELPERS_FLAGS flags, CORINFO_TAILCALL_HELPERS* pResult) override; +CORINFO_METHOD_HANDLE getAsyncResumptionStub() override; + bool convertPInvokeCalliToCall( CORINFO_RESOLVED_TOKEN* pResolvedToken, bool mustConvert) override; diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index 364918107d1317..c31eda46943e08 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* caad5bcc-1f14-4f63-81e6-61e7e88535d0 */ - 0xcaad5bcc, - 0x1f14, - 0x4f63, - {0x81, 0xe6, 0x61, 0xe7, 0xe8, 0x85, 0x35, 0xd0} - }; +constexpr GUID JITEEVersionIdentifier = { /* 13de8232-0528-42ca-b2b8-4fc592a9adb9 */ + 0x13de8232, + 0x0528, + 0x42ca, + {0xb2, 0xb8, 0x4f, 0xc5, 0x92, 0xa9, 0xad, 0xb9} +}; #endif // JIT_EE_VERSIONING_GUID_H diff --git a/src/coreclr/inc/jithelpers.h b/src/coreclr/inc/jithelpers.h index 8543f647c93b52..3ea5ed3f7af3b7 100644 --- a/src/coreclr/inc/jithelpers.h +++ b/src/coreclr/inc/jithelpers.h @@ -145,6 +145,7 @@ // Exceptions DYNAMICJITHELPER(CORINFO_HELP_THROW, IL_Throw, METHOD__NIL) DYNAMICJITHELPER(CORINFO_HELP_RETHROW, IL_Rethrow, METHOD__NIL) + DYNAMICJITHELPER(CORINFO_HELP_THROWEXACT, IL_ThrowExact, METHOD__NIL) DYNAMICJITHELPER(CORINFO_HELP_USER_BREAKPOINT, NULL, METHOD__DEBUGGER__USERBREAKPOINT) DYNAMICJITHELPER_NOINDIRECT(CORINFO_HELP_RNGCHKFAIL, NULL, METHOD__THROWHELPERS__THROWINDEXOUTOFRANGEEXCEPTION) DYNAMICJITHELPER_NOINDIRECT(CORINFO_HELP_OVERFLOW, NULL, METHOD__THROWHELPERS__THROWOVERFLOWEXCEPTION) @@ -342,7 +343,7 @@ #endif JITHELPER(CORINFO_HELP_PATCHPOINT, JIT_Patchpoint, METHOD__NIL) - JITHELPER(CORINFO_HELP_PARTIAL_COMPILATION_PATCHPOINT, JIT_PartialCompilationPatchpoint, METHOD__NIL) + JITHELPER(CORINFO_HELP_PATCHPOINT_FORCED, JIT_PatchpointForced, METHOD__NIL) JITHELPER(CORINFO_HELP_CLASSPROFILE32, JIT_ClassProfile32, METHOD__NIL) JITHELPER(CORINFO_HELP_CLASSPROFILE64, JIT_ClassProfile64, METHOD__NIL) @@ -367,6 +368,10 @@ JITHELPER(CORINFO_HELP_DISPATCH_INDIRECT_CALL, NULL, METHOD__NIL) #endif + DYNAMICJITHELPER(CORINFO_HELP_ALLOC_CONTINUATION, NULL, METHOD__ASYNC_HELPERS__ALLOC_CONTINUATION) + DYNAMICJITHELPER(CORINFO_HELP_ALLOC_CONTINUATION_METHOD, NULL, METHOD__ASYNC_HELPERS__ALLOC_CONTINUATION_METHOD) + DYNAMICJITHELPER(CORINFO_HELP_ALLOC_CONTINUATION_CLASS, NULL, METHOD__ASYNC_HELPERS__ALLOC_CONTINUATION_CLASS) + #undef JITHELPER #undef DYNAMICJITHELPER #undef JITHELPER diff --git a/src/coreclr/inc/patchpointinfo.h b/src/coreclr/inc/patchpointinfo.h index 02b9fd89f338ae..bdff46def7ef99 100644 --- a/src/coreclr/inc/patchpointinfo.h +++ b/src/coreclr/inc/patchpointinfo.h @@ -38,6 +38,7 @@ struct PatchpointInfo void Initialize(unsigned localCount, int totalFrameSize) { m_calleeSaveRegisters = 0; + m_tier0Version = 0; m_totalFrameSize = totalFrameSize; m_numberOfLocals = localCount; m_genericContextArgOffset = -1; @@ -50,6 +51,7 @@ struct PatchpointInfo void Copy(const PatchpointInfo* original) { m_calleeSaveRegisters = original->m_calleeSaveRegisters; + m_tier0Version = original->m_tier0Version; m_genericContextArgOffset = original->m_genericContextArgOffset; m_keptAliveThisOffset = original->m_keptAliveThisOffset; m_securityCookieOffset = original->m_securityCookieOffset; @@ -173,6 +175,16 @@ struct PatchpointInfo m_calleeSaveRegisters = registerMask; } + PCODE GetTier0EntryPoint() const + { + return m_tier0Version; + } + + void SetTier0EntryPoint(PCODE ip) + { + m_tier0Version = ip; + } + private: enum { @@ -181,6 +193,7 @@ struct PatchpointInfo }; uint64_t m_calleeSaveRegisters; + PCODE m_tier0Version; unsigned m_numberOfLocals; int m_totalFrameSize; int m_genericContextArgOffset; diff --git a/src/coreclr/jit/ICorJitInfo_names_generated.h b/src/coreclr/jit/ICorJitInfo_names_generated.h index 714b33aebb211e..cf50808bb3efe8 100644 --- a/src/coreclr/jit/ICorJitInfo_names_generated.h +++ b/src/coreclr/jit/ICorJitInfo_names_generated.h @@ -123,6 +123,7 @@ DEF_CLR_API(getHFAType) DEF_CLR_API(runWithErrorTrap) DEF_CLR_API(runWithSPMIErrorTrap) DEF_CLR_API(getEEInfo) +DEF_CLR_API(getAsyncInfo) DEF_CLR_API(getMethodDefFromMethod) DEF_CLR_API(printMethodName) DEF_CLR_API(getMethodNameFromMetadata) @@ -160,6 +161,7 @@ DEF_CLR_API(getFieldThreadLocalStoreID) DEF_CLR_API(GetDelegateCtor) DEF_CLR_API(MethodCompileComplete) DEF_CLR_API(getTailCallHelpers) +DEF_CLR_API(getAsyncResumptionStub) DEF_CLR_API(convertPInvokeCalliToCall) DEF_CLR_API(notifyInstructionSetUsage) DEF_CLR_API(updateEntryPointForTailCall) diff --git a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp index f8d1496fb86100..bcc31380d5ce14 100644 --- a/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp +++ b/src/coreclr/jit/ICorJitInfo_wrapper_generated.hpp @@ -1172,6 +1172,14 @@ void WrapICorJitInfo::getEEInfo( API_LEAVE(getEEInfo); } +void WrapICorJitInfo::getAsyncInfo( + CORINFO_ASYNC_INFO* pAsyncInfoOut) +{ + API_ENTER(getAsyncInfo); + wrapHnd->getAsyncInfo(pAsyncInfoOut); + API_LEAVE(getAsyncInfo); +} + mdMethodDef WrapICorJitInfo::getMethodDefFromMethod( CORINFO_METHOD_HANDLE hMethod) { @@ -1546,6 +1554,14 @@ bool WrapICorJitInfo::getTailCallHelpers( return temp; } +CORINFO_METHOD_HANDLE WrapICorJitInfo::getAsyncResumptionStub() +{ + API_ENTER(getAsyncResumptionStub); + CORINFO_METHOD_HANDLE temp = wrapHnd->getAsyncResumptionStub(); + API_LEAVE(getAsyncResumptionStub); + return temp; +} + bool WrapICorJitInfo::convertPInvokeCalliToCall( CORINFO_RESOLVED_TOKEN* pResolvedToken, bool mustConvert) diff --git a/src/coreclr/jit/patchpoint.cpp b/src/coreclr/jit/patchpoint.cpp index ab695c0a5c3bb1..3649ee4e72d638 100644 --- a/src/coreclr/jit/patchpoint.cpp +++ b/src/coreclr/jit/patchpoint.cpp @@ -238,8 +238,7 @@ class PatchpointTransformer // call PartialCompilationPatchpointHelper(ilOffset) // GenTree* ilOffsetNode = compiler->gtNewIconNode(ilOffset, TYP_INT); - GenTreeCall* helperCall = - compiler->gtNewHelperCallNode(CORINFO_HELP_PARTIAL_COMPILATION_PATCHPOINT, TYP_VOID, ilOffsetNode); + GenTreeCall* helperCall = compiler->gtNewHelperCallNode(CORINFO_HELP_PATCHPOINT_FORCED, TYP_VOID, ilOffsetNode); compiler->fgNewStmtAtEnd(block, helperCall); } diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs b/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs index 04bca41e476671..d1733990dc533c 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs @@ -96,6 +96,7 @@ which is the right helper to use to allocate an object of a given type. */ CORINFO_HELP_THROW, // Throw an exception object CORINFO_HELP_RETHROW, // Rethrow the currently active exception + CORINFO_HELP_THROWEXACT, // Throw an exception object, preserving stack trace CORINFO_HELP_USER_BREAKPOINT, // For a user program to break to the debugger CORINFO_HELP_RNGCHKFAIL, // array bounds check failed CORINFO_HELP_OVERFLOW, // throw an overflow exception @@ -268,7 +269,7 @@ which is the right helper to use to allocate an object of a given type. */ CORINFO_HELP_STACK_PROBE, // Probes each page of the allocated stack frame CORINFO_HELP_PATCHPOINT, // Notify runtime that code has reached a patchpoint - CORINFO_HELP_PARTIAL_COMPILATION_PATCHPOINT, // Notify runtime that code has reached a part of the method that wasn't originally jitted. + CORINFO_HELP_PATCHPOINT_FORCED, // Notify runtime that code has reached a part of the method that needs to transition CORINFO_HELP_CLASSPROFILE32, // Update 32-bit class profile for a call site CORINFO_HELP_CLASSPROFILE64, // Update 64-bit class profile for a call site @@ -284,6 +285,10 @@ which is the right helper to use to allocate an object of a given type. */ CORINFO_HELP_VALIDATE_INDIRECT_CALL, // CFG: Validate function pointer CORINFO_HELP_DISPATCH_INDIRECT_CALL, // CFG: Validate and dispatch to pointer + CORINFO_HELP_ALLOC_CONTINUATION, + CORINFO_HELP_ALLOC_CONTINUATION_METHOD, + CORINFO_HELP_ALLOC_CONTINUATION_CLASS, + CORINFO_HELP_COUNT, } } diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index e32abefb429f0c..6cdb10a5b17b80 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -3356,6 +3356,11 @@ private void getEEInfo(ref CORINFO_EE_INFO pEEInfoOut) pEEInfoOut.osType = TargetToOs(_compilation.NodeFactory.Target); } + private void getAsyncInfo(ref CORINFO_ASYNC_INFO pAsyncInfoOut) + { + throw new NotImplementedException(); + } + private mdToken getMethodDefFromMethod(CORINFO_METHOD_STRUCT_* hMethod) { MethodDesc method = HandleToObject(hMethod); @@ -3683,6 +3688,13 @@ private bool getTailCallHelpers(ref CORINFO_RESOLVED_TOKEN callToken, CORINFO_SI #endif } +#pragma warning disable CA1822 // Mark members as static + private CORINFO_METHOD_STRUCT_* getAsyncResumptionStub() +#pragma warning restore CA1822 // Mark members as static + { + return null; + } + private byte[] _code; private byte[] _coldCode; private int _codeAlignment; diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs index 996cad062a0aba..8b1a6f5d8d0442 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl_generated.cs @@ -1769,6 +1769,20 @@ private static void _getEEInfo(IntPtr thisHandle, IntPtr* ppException, CORINFO_E } } + [UnmanagedCallersOnly] + private static void _getAsyncInfo(IntPtr thisHandle, IntPtr* ppException, CORINFO_ASYNC_INFO* pAsyncInfoOut) + { + var _this = GetThis(thisHandle); + try + { + _this.getAsyncInfo(ref *pAsyncInfoOut); + } + catch (Exception ex) + { + *ppException = _this.AllocException(ex); + } + } + [UnmanagedCallersOnly] private static mdToken _getMethodDefFromMethod(IntPtr thisHandle, IntPtr* ppException, CORINFO_METHOD_STRUCT_* hMethod) { @@ -2314,6 +2328,21 @@ private static byte _getTailCallHelpers(IntPtr thisHandle, IntPtr* ppException, } } + [UnmanagedCallersOnly] + private static CORINFO_METHOD_STRUCT_* _getAsyncResumptionStub(IntPtr thisHandle, IntPtr* ppException) + { + var _this = GetThis(thisHandle); + try + { + return _this.getAsyncResumptionStub(); + } + catch (Exception ex) + { + *ppException = _this.AllocException(ex); + return default; + } + } + [UnmanagedCallersOnly] private static byte _convertPInvokeCalliToCall(IntPtr thisHandle, IntPtr* ppException, CORINFO_RESOLVED_TOKEN* pResolvedToken, byte mustConvert) { @@ -2608,7 +2637,7 @@ private static uint _getJitFlags(IntPtr thisHandle, IntPtr* ppException, CORJIT_ private static IntPtr GetUnmanagedCallbacks() { - void** callbacks = (void**)Marshal.AllocCoTaskMem(sizeof(IntPtr) * 176); + void** callbacks = (void**)Marshal.AllocCoTaskMem(sizeof(IntPtr) * 178); callbacks[0] = (delegate* unmanaged)&_isIntrinsic; callbacks[1] = (delegate* unmanaged)&_notifyMethodInfoUsage; @@ -2729,63 +2758,65 @@ private static IntPtr GetUnmanagedCallbacks() callbacks[116] = (delegate* unmanaged)&_runWithErrorTrap; callbacks[117] = (delegate* unmanaged)&_runWithSPMIErrorTrap; callbacks[118] = (delegate* unmanaged)&_getEEInfo; - callbacks[119] = (delegate* unmanaged)&_getMethodDefFromMethod; - callbacks[120] = (delegate* unmanaged)&_printMethodName; - callbacks[121] = (delegate* unmanaged)&_getMethodNameFromMetadata; - callbacks[122] = (delegate* unmanaged)&_getMethodHash; - callbacks[123] = (delegate* unmanaged)&_getSystemVAmd64PassStructInRegisterDescriptor; - callbacks[124] = (delegate* unmanaged)&_getSwiftLowering; - callbacks[125] = (delegate* unmanaged)&_getFpStructLowering; - callbacks[126] = (delegate* unmanaged)&_getThreadTLSIndex; - callbacks[127] = (delegate* unmanaged)&_getAddrOfCaptureThreadGlobal; - callbacks[128] = (delegate* unmanaged)&_getHelperFtn; - callbacks[129] = (delegate* unmanaged)&_getFunctionEntryPoint; - callbacks[130] = (delegate* unmanaged)&_getFunctionFixedEntryPoint; - callbacks[131] = (delegate* unmanaged)&_getMethodSync; - callbacks[132] = (delegate* unmanaged)&_getLazyStringLiteralHelper; - callbacks[133] = (delegate* unmanaged)&_embedModuleHandle; - callbacks[134] = (delegate* unmanaged)&_embedClassHandle; - callbacks[135] = (delegate* unmanaged)&_embedMethodHandle; - callbacks[136] = (delegate* unmanaged)&_embedFieldHandle; - callbacks[137] = (delegate* unmanaged)&_embedGenericHandle; - callbacks[138] = (delegate* unmanaged)&_getLocationOfThisType; - callbacks[139] = (delegate* unmanaged)&_getAddressOfPInvokeTarget; - callbacks[140] = (delegate* unmanaged)&_GetCookieForPInvokeCalliSig; - callbacks[141] = (delegate* unmanaged)&_canGetCookieForPInvokeCalliSig; - callbacks[142] = (delegate* unmanaged)&_getJustMyCodeHandle; - callbacks[143] = (delegate* unmanaged)&_GetProfilingHandle; - callbacks[144] = (delegate* unmanaged)&_getCallInfo; - callbacks[145] = (delegate* unmanaged)&_getStaticFieldContent; - callbacks[146] = (delegate* unmanaged)&_getObjectContent; - callbacks[147] = (delegate* unmanaged)&_getStaticFieldCurrentClass; - callbacks[148] = (delegate* unmanaged)&_getVarArgsHandle; - callbacks[149] = (delegate* unmanaged)&_canGetVarArgsHandle; - callbacks[150] = (delegate* unmanaged)&_constructStringLiteral; - callbacks[151] = (delegate* unmanaged)&_emptyStringLiteral; - callbacks[152] = (delegate* unmanaged)&_getFieldThreadLocalStoreID; - callbacks[153] = (delegate* unmanaged)&_GetDelegateCtor; - callbacks[154] = (delegate* unmanaged)&_MethodCompileComplete; - callbacks[155] = (delegate* unmanaged)&_getTailCallHelpers; - callbacks[156] = (delegate* unmanaged)&_convertPInvokeCalliToCall; - callbacks[157] = (delegate* unmanaged)&_notifyInstructionSetUsage; - callbacks[158] = (delegate* unmanaged)&_updateEntryPointForTailCall; - callbacks[159] = (delegate* unmanaged)&_allocMem; - callbacks[160] = (delegate* unmanaged)&_reserveUnwindInfo; - callbacks[161] = (delegate* unmanaged)&_allocUnwindInfo; - callbacks[162] = (delegate* unmanaged)&_allocGCInfo; - callbacks[163] = (delegate* unmanaged)&_setEHcount; - callbacks[164] = (delegate* unmanaged)&_setEHinfo; - callbacks[165] = (delegate* unmanaged)&_logMsg; - callbacks[166] = (delegate* unmanaged)&_doAssert; - callbacks[167] = (delegate* unmanaged)&_reportFatalError; - callbacks[168] = (delegate* unmanaged)&_getPgoInstrumentationResults; - callbacks[169] = (delegate* unmanaged)&_allocPgoInstrumentationBySchema; - callbacks[170] = (delegate* unmanaged)&_recordCallSite; - callbacks[171] = (delegate* unmanaged)&_recordRelocation; - callbacks[172] = (delegate* unmanaged)&_getRelocTypeHint; - callbacks[173] = (delegate* unmanaged)&_getExpectedTargetArchitecture; - callbacks[174] = (delegate* unmanaged)&_getJitFlags; - callbacks[175] = (delegate* unmanaged)&_getSpecialCopyHelper; + callbacks[119] = (delegate* unmanaged)&_getAsyncInfo; + callbacks[120] = (delegate* unmanaged)&_getMethodDefFromMethod; + callbacks[121] = (delegate* unmanaged)&_printMethodName; + callbacks[122] = (delegate* unmanaged)&_getMethodNameFromMetadata; + callbacks[123] = (delegate* unmanaged)&_getMethodHash; + callbacks[124] = (delegate* unmanaged)&_getSystemVAmd64PassStructInRegisterDescriptor; + callbacks[125] = (delegate* unmanaged)&_getSwiftLowering; + callbacks[126] = (delegate* unmanaged)&_getFpStructLowering; + callbacks[127] = (delegate* unmanaged)&_getThreadTLSIndex; + callbacks[128] = (delegate* unmanaged)&_getAddrOfCaptureThreadGlobal; + callbacks[129] = (delegate* unmanaged)&_getHelperFtn; + callbacks[130] = (delegate* unmanaged)&_getFunctionEntryPoint; + callbacks[131] = (delegate* unmanaged)&_getFunctionFixedEntryPoint; + callbacks[132] = (delegate* unmanaged)&_getMethodSync; + callbacks[133] = (delegate* unmanaged)&_getLazyStringLiteralHelper; + callbacks[134] = (delegate* unmanaged)&_embedModuleHandle; + callbacks[135] = (delegate* unmanaged)&_embedClassHandle; + callbacks[136] = (delegate* unmanaged)&_embedMethodHandle; + callbacks[137] = (delegate* unmanaged)&_embedFieldHandle; + callbacks[138] = (delegate* unmanaged)&_embedGenericHandle; + callbacks[139] = (delegate* unmanaged)&_getLocationOfThisType; + callbacks[140] = (delegate* unmanaged)&_getAddressOfPInvokeTarget; + callbacks[141] = (delegate* unmanaged)&_GetCookieForPInvokeCalliSig; + callbacks[142] = (delegate* unmanaged)&_canGetCookieForPInvokeCalliSig; + callbacks[143] = (delegate* unmanaged)&_getJustMyCodeHandle; + callbacks[144] = (delegate* unmanaged)&_GetProfilingHandle; + callbacks[145] = (delegate* unmanaged)&_getCallInfo; + callbacks[146] = (delegate* unmanaged)&_getStaticFieldContent; + callbacks[147] = (delegate* unmanaged)&_getObjectContent; + callbacks[148] = (delegate* unmanaged)&_getStaticFieldCurrentClass; + callbacks[149] = (delegate* unmanaged)&_getVarArgsHandle; + callbacks[150] = (delegate* unmanaged)&_canGetVarArgsHandle; + callbacks[151] = (delegate* unmanaged)&_constructStringLiteral; + callbacks[152] = (delegate* unmanaged)&_emptyStringLiteral; + callbacks[153] = (delegate* unmanaged)&_getFieldThreadLocalStoreID; + callbacks[154] = (delegate* unmanaged)&_GetDelegateCtor; + callbacks[155] = (delegate* unmanaged)&_MethodCompileComplete; + callbacks[156] = (delegate* unmanaged)&_getTailCallHelpers; + callbacks[157] = (delegate* unmanaged)&_getAsyncResumptionStub; + callbacks[158] = (delegate* unmanaged)&_convertPInvokeCalliToCall; + callbacks[159] = (delegate* unmanaged)&_notifyInstructionSetUsage; + callbacks[160] = (delegate* unmanaged)&_updateEntryPointForTailCall; + callbacks[161] = (delegate* unmanaged)&_allocMem; + callbacks[162] = (delegate* unmanaged)&_reserveUnwindInfo; + callbacks[163] = (delegate* unmanaged)&_allocUnwindInfo; + callbacks[164] = (delegate* unmanaged)&_allocGCInfo; + callbacks[165] = (delegate* unmanaged)&_setEHcount; + callbacks[166] = (delegate* unmanaged)&_setEHinfo; + callbacks[167] = (delegate* unmanaged)&_logMsg; + callbacks[168] = (delegate* unmanaged)&_doAssert; + callbacks[169] = (delegate* unmanaged)&_reportFatalError; + callbacks[170] = (delegate* unmanaged)&_getPgoInstrumentationResults; + callbacks[171] = (delegate* unmanaged)&_allocPgoInstrumentationBySchema; + callbacks[172] = (delegate* unmanaged)&_recordCallSite; + callbacks[173] = (delegate* unmanaged)&_recordRelocation; + callbacks[174] = (delegate* unmanaged)&_getRelocTypeHint; + callbacks[175] = (delegate* unmanaged)&_getExpectedTargetArchitecture; + callbacks[176] = (delegate* unmanaged)&_getJitFlags; + callbacks[177] = (delegate* unmanaged)&_getSpecialCopyHelper; return (IntPtr)callbacks; } diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index ea5ff67d9d8057..9e75ee782fab1b 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -872,6 +872,18 @@ public struct InlinedCallFrameInfo public CORINFO_OS osType; } + public unsafe struct CORINFO_ASYNC_INFO + { + // Class handle for System.Runtime.CompilerServices.Continuation + public CORINFO_CLASS_STRUCT_* continuationClsHnd; + // 'Next' field + public CORINFO_FIELD_STRUCT_* continuationNextFldHnd; + // 'Data' field + public CORINFO_FIELD_STRUCT_* continuationDataFldHnd; + // 'GCData' field + public CORINFO_FIELD_STRUCT_* continuationGCDataFldHnd; + } + // Flags passed from JIT to runtime. public enum CORINFO_GET_TAILCALL_HELPERS_FLAGS { diff --git a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt index d397042f89c7f7..6030b745b013e4 100644 --- a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt +++ b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/ThunkInput.txt @@ -82,6 +82,7 @@ CORINFO_SIG_INFO* CORINFO_RESOLVED_TOKEN*,ref CORINFO_RESOLVED_TOKEN CORINFO_RESOLVED_TOKEN_PTR,CORINFO_RESOLVED_TOKEN*,CORINFO_RESOLVED_TOKEN*,CORINFO_RESOLVED_TOKEN* CORINFO_EE_INFO*,ref CORINFO_EE_INFO +CORINFO_ASYNC_INFO*,ref CORINFO_ASYNC_INFO CORINFO_TAILCALL_HELPERS*,ref CORINFO_TAILCALL_HELPERS CORINFO_SWIFT_LOWERING*,ref CORINFO_SWIFT_LOWERING CORINFO_FPSTRUCT_LOWERING*,ref CORINFO_FPSTRUCT_LOWERING @@ -283,6 +284,7 @@ FUNCTIONS [ManualNativeWrapper] bool runWithErrorTrap(ICorJitInfo::errorTrapFunction function, void* parameter); [ManualNativeWrapper] bool runWithSPMIErrorTrap(ICorJitInfo::errorTrapFunction function, void* parameter); void getEEInfo(CORINFO_EE_INFO* pEEInfoOut); + void getAsyncInfo(CORINFO_ASYNC_INFO* pAsyncInfoOut); mdMethodDef getMethodDefFromMethod(CORINFO_METHOD_HANDLE hMethod); size_t printMethodName(CORINFO_METHOD_HANDLE ftn, char* buffer, size_t bufferSize, size_t* pRequiredBufferSize) const char* getMethodNameFromMetadata(CORINFO_METHOD_HANDLE ftn, const char **className, const char **namespaceName, const char **enclosingClassNames, size_t maxEnclosingClassNames); @@ -320,6 +322,7 @@ FUNCTIONS CORINFO_METHOD_HANDLE GetDelegateCtor(CORINFO_METHOD_HANDLE methHnd, CORINFO_CLASS_HANDLE clsHnd, CORINFO_METHOD_HANDLE targetMethodHnd, DelegateCtorArgs * pCtorData); void MethodCompileComplete(CORINFO_METHOD_HANDLE methHnd); bool getTailCallHelpers(CORINFO_RESOLVED_TOKEN* callToken, CORINFO_SIG_INFO* sig, CORINFO_GET_TAILCALL_HELPERS_FLAGS flags, CORINFO_TAILCALL_HELPERS* pResult); + CORINFO_METHOD_HANDLE getAsyncResumptionStub(); bool convertPInvokeCalliToCall(CORINFO_RESOLVED_TOKEN * pResolvedToken, bool mustConvert); bool notifyInstructionSetUsage(CORINFO_InstructionSet instructionSet,bool supportEnabled); void updateEntryPointForTailCall(CORINFO_CONST_LOOKUP* entryPoint); diff --git a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h index 06188f952cb324..729a09de403eac 100644 --- a/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h +++ b/src/coreclr/tools/aot/jitinterface/jitinterface_generated.h @@ -130,6 +130,7 @@ struct JitInterfaceCallbacks bool (* runWithErrorTrap)(void * thisHandle, CorInfoExceptionClass** ppException, ICorJitInfo::errorTrapFunction function, void* parameter); bool (* runWithSPMIErrorTrap)(void * thisHandle, CorInfoExceptionClass** ppException, ICorJitInfo::errorTrapFunction function, void* parameter); void (* getEEInfo)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_EE_INFO* pEEInfoOut); + void (* getAsyncInfo)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_ASYNC_INFO* pAsyncInfoOut); mdMethodDef (* getMethodDefFromMethod)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE hMethod); size_t (* printMethodName)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE ftn, char* buffer, size_t bufferSize, size_t* pRequiredBufferSize); const char* (* getMethodNameFromMetadata)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE ftn, const char** className, const char** namespaceName, const char** enclosingClassNames, size_t maxEnclosingClassNames); @@ -167,6 +168,7 @@ struct JitInterfaceCallbacks CORINFO_METHOD_HANDLE (* GetDelegateCtor)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE methHnd, CORINFO_CLASS_HANDLE clsHnd, CORINFO_METHOD_HANDLE targetMethodHnd, DelegateCtorArgs* pCtorData); void (* MethodCompileComplete)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_METHOD_HANDLE methHnd); bool (* getTailCallHelpers)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_RESOLVED_TOKEN* callToken, CORINFO_SIG_INFO* sig, CORINFO_GET_TAILCALL_HELPERS_FLAGS flags, CORINFO_TAILCALL_HELPERS* pResult); + CORINFO_METHOD_HANDLE (* getAsyncResumptionStub)(void * thisHandle, CorInfoExceptionClass** ppException); bool (* convertPInvokeCalliToCall)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_RESOLVED_TOKEN* pResolvedToken, bool mustConvert); bool (* notifyInstructionSetUsage)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_InstructionSet instructionSet, bool supportEnabled); void (* updateEntryPointForTailCall)(void * thisHandle, CorInfoExceptionClass** ppException, CORINFO_CONST_LOOKUP* entryPoint); @@ -1350,6 +1352,14 @@ class JitInterfaceWrapper : public ICorJitInfo if (pException != nullptr) throw pException; } + virtual void getAsyncInfo( + CORINFO_ASYNC_INFO* pAsyncInfoOut) +{ + CorInfoExceptionClass* pException = nullptr; + _callbacks->getAsyncInfo(_thisHandle, &pException, pAsyncInfoOut); + if (pException != nullptr) throw pException; +} + virtual mdMethodDef getMethodDefFromMethod( CORINFO_METHOD_HANDLE hMethod) { @@ -1724,6 +1734,14 @@ class JitInterfaceWrapper : public ICorJitInfo return temp; } + virtual CORINFO_METHOD_HANDLE getAsyncResumptionStub() +{ + CorInfoExceptionClass* pException = nullptr; + CORINFO_METHOD_HANDLE temp = _callbacks->getAsyncResumptionStub(_thisHandle, &pException); + if (pException != nullptr) throw pException; + return temp; +} + virtual bool convertPInvokeCalliToCall( CORINFO_RESOLVED_TOKEN* pResolvedToken, bool mustConvert) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h index 18fd918a7a5cea..cefd14edde954f 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/agnostic.h @@ -193,6 +193,18 @@ struct Agnostic_CORINFO_EE_INFO DWORD osType; }; +struct Agnostic_CORINFO_ASYNC_INFO +{ + DWORDLONG continuationClsHnd; + DWORDLONG continuationNextFldHnd; + DWORDLONG continuationResumeFldHnd; + DWORDLONG continuationStateFldHnd; + DWORDLONG continuationFlagsFldHnd; + DWORDLONG continuationDataFldHnd; + DWORDLONG continuationGCDataFldHnd; + DWORD continuationsNeedMethodHandle; +}; + struct Agnostic_GetOSRInfo { DWORD index; diff --git a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h index 50e9720aefaa2a..f81a8680ab5d74 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h @@ -79,6 +79,7 @@ LWM(GetDefaultEqualityComparerClass, DWORDLONG, DWORDLONG) LWM(GetSZArrayHelperEnumeratorClass, DWORDLONG, DWORDLONG) LWM(GetDelegateCtor, Agnostic_GetDelegateCtorIn, Agnostic_GetDelegateCtorOut) LWM(GetEEInfo, DWORD, Agnostic_CORINFO_EE_INFO) +LWM(GetAsyncInfo, DWORD, Agnostic_CORINFO_ASYNC_INFO) LWM(GetEHinfo, DLD, Agnostic_CORINFO_EH_CLAUSE) LWM(GetStaticFieldContent, DLDDD, DD) LWM(GetObjectContent, DLDD, DD) @@ -130,6 +131,7 @@ LWM(GetSystemVAmd64PassStructInRegisterDescriptor, DWORDLONG, Agnostic_GetSystem LWM(GetSwiftLowering, DWORDLONG, Agnostic_GetSwiftLowering) LWM(GetFpStructLowering, DWORDLONG, Agnostic_GetFpStructLowering) LWM(GetTailCallHelpers, Agnostic_GetTailCallHelpers, Agnostic_CORINFO_TAILCALL_HELPERS) +LWM(GetAsyncResumptionStub, DWORD, DWORDLONG) LWM(UpdateEntryPointForTailCall, Agnostic_CORINFO_CONST_LOOKUP, Agnostic_CORINFO_CONST_LOOKUP) LWM(GetSpecialCopyHelper, DWORDLONG, DWORDLONG) LWM(GetThreadTLSIndex, DWORD, DLD) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp index 0db57a809952d8..e414a1d0b00c21 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp @@ -4459,6 +4459,48 @@ void MethodContext::repGetEEInfo(CORINFO_EE_INFO* pEEInfoOut) pEEInfoOut->osType = (CORINFO_OS)value.osType; } +void MethodContext::recGetAsyncInfo(const CORINFO_ASYNC_INFO* pAsyncInfo) +{ + if (GetAsyncInfo == nullptr) + GetAsyncInfo = new LightWeightMap(); + + Agnostic_CORINFO_ASYNC_INFO value; + ZeroMemory(&value, sizeof(value)); + + value.continuationClsHnd = CastHandle(pAsyncInfo->continuationClsHnd); + value.continuationNextFldHnd = CastHandle(pAsyncInfo->continuationNextFldHnd); + value.continuationResumeFldHnd = CastHandle(pAsyncInfo->continuationResumeFldHnd); + value.continuationStateFldHnd = CastHandle(pAsyncInfo->continuationStateFldHnd); + value.continuationFlagsFldHnd = CastHandle(pAsyncInfo->continuationFlagsFldHnd); + value.continuationDataFldHnd = CastHandle(pAsyncInfo->continuationDataFldHnd); + value.continuationGCDataFldHnd = CastHandle(pAsyncInfo->continuationGCDataFldHnd); + value.continuationsNeedMethodHandle = pAsyncInfo->continuationsNeedMethodHandle ? 1 : 0; + + GetAsyncInfo->Add(0, value); + DEBUG_REC(dmpGetAsyncInfo(0, value)); +} +void MethodContext::dmpGetAsyncInfo(DWORD key, const Agnostic_CORINFO_ASYNC_INFO& value) +{ + printf("GetAsyncInfo key %u value contClsHnd-%016" PRIX64 " contNextFldHnd-%016" PRIX64 " contResumeFldHnd-%016" PRIX64 + " contStateFldHnd-%016" PRIX64 " contFlagsFldHnd-%016" PRIX64 " contDataFldHnd-%016" PRIX64 " contGCDataFldHnd-%016" PRIX64 " contsNeedMethodHandle-%d", + key, value.continuationClsHnd, value.continuationNextFldHnd, value.continuationResumeFldHnd, + value.continuationStateFldHnd, value.continuationFlagsFldHnd, value.continuationDataFldHnd, + value.continuationGCDataFldHnd, value.continuationsNeedMethodHandle); +} +void MethodContext::repGetAsyncInfo(CORINFO_ASYNC_INFO* pAsyncInfoOut) +{ + Agnostic_CORINFO_ASYNC_INFO value = LookupByKeyOrMissNoMessage(GetAsyncInfo, 0); + pAsyncInfoOut->continuationClsHnd = (CORINFO_CLASS_HANDLE)value.continuationClsHnd; + pAsyncInfoOut->continuationNextFldHnd = (CORINFO_FIELD_HANDLE)value.continuationNextFldHnd; + pAsyncInfoOut->continuationResumeFldHnd = (CORINFO_FIELD_HANDLE)value.continuationResumeFldHnd; + pAsyncInfoOut->continuationStateFldHnd = (CORINFO_FIELD_HANDLE)value.continuationStateFldHnd; + pAsyncInfoOut->continuationFlagsFldHnd = (CORINFO_FIELD_HANDLE)value.continuationFlagsFldHnd; + pAsyncInfoOut->continuationDataFldHnd = (CORINFO_FIELD_HANDLE)value.continuationDataFldHnd; + pAsyncInfoOut->continuationGCDataFldHnd = (CORINFO_FIELD_HANDLE)value.continuationGCDataFldHnd; + pAsyncInfoOut->continuationsNeedMethodHandle = value.continuationsNeedMethodHandle != 0; + DEBUG_REP(dmpGetAsyncInfo(0, value)); +} + void MethodContext::recGetGSCookie(GSCookie* pCookieVal, GSCookie** ppCookieVal) { if (GetGSCookie == nullptr) @@ -6941,6 +6983,25 @@ bool MethodContext::repGetTailCallHelpers( return true; } + +void MethodContext::recGetAsyncResumptionStub(CORINFO_METHOD_HANDLE hnd) +{ + if (GetAsyncResumptionStub == nullptr) + GetAsyncResumptionStub = new LightWeightMap(); + + GetAsyncResumptionStub->Add(0, CastHandle(hnd)); + DEBUG_REC(dmpGetAsyncResumptionStub(CastHandle(hnd))); +} +void MethodContext::dmpGetAsyncResumptionStub(DWORD key, DWORDLONG hnd) +{ + printf("GetAsyncResumptionStub key-%u, value-%016" PRIX64, key, hnd); +} +CORINFO_METHOD_HANDLE MethodContext::repGetAsyncResumptionStub() +{ + DWORDLONG hnd = LookupByKeyOrMissNoMessage(GetAsyncResumptionStub, 0); + return (CORINFO_METHOD_HANDLE)hnd; +} + void MethodContext::recUpdateEntryPointForTailCall( const CORINFO_CONST_LOOKUP& origEntryPoint, const CORINFO_CONST_LOOKUP& newEntryPoint) diff --git a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h index dd9f4abc0f0d4f..1a25a3a24c5a96 100644 --- a/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h +++ b/src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h @@ -574,6 +574,10 @@ class MethodContext void dmpGetEEInfo(DWORD key, const Agnostic_CORINFO_EE_INFO& value); void repGetEEInfo(CORINFO_EE_INFO* pEEInfoOut); + void recGetAsyncInfo(const CORINFO_ASYNC_INFO* pAsyncInfo); + void dmpGetAsyncInfo(DWORD key, const Agnostic_CORINFO_ASYNC_INFO& value); + void repGetAsyncInfo(CORINFO_ASYNC_INFO* pAsyncInfoOut); + void recGetGSCookie(GSCookie* pCookieVal, GSCookie** ppCookieVal); void dmpGetGSCookie(DWORD key, DLDL value); void repGetGSCookie(GSCookie* pCookieVal, GSCookie** ppCookieVal); @@ -871,6 +875,10 @@ class MethodContext CORINFO_GET_TAILCALL_HELPERS_FLAGS flags, CORINFO_TAILCALL_HELPERS* pResult); + void recGetAsyncResumptionStub(CORINFO_METHOD_HANDLE hnd); + void dmpGetAsyncResumptionStub(DWORD key, DWORDLONG handle); + CORINFO_METHOD_HANDLE repGetAsyncResumptionStub(); + void recUpdateEntryPointForTailCall(const CORINFO_CONST_LOOKUP& origEntryPoint, const CORINFO_CONST_LOOKUP& newEntryPoint); void dmpUpdateEntryPointForTailCall(const Agnostic_CORINFO_CONST_LOOKUP& origEntryPoint, const Agnostic_CORINFO_CONST_LOOKUP& newEntryPoint); void repUpdateEntryPointForTailCall(CORINFO_CONST_LOOKUP* entryPoint); @@ -1208,6 +1216,8 @@ enum mcPackets Packet_GetMethodInstantiationArgument = 227, Packet_GetInstantiatedEntry = 228, Packet_NotifyInstructionSetUsage = 229, + Packet_GetAsyncInfo = 230, + Packet_GetAsyncResumptionStub = 231, }; void SetDebugDumpVariables(); diff --git a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp index ff0a2b1c6fa57a..835d6a22d448de 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp @@ -1364,6 +1364,13 @@ void interceptor_ICJI::getEEInfo(CORINFO_EE_INFO* pEEInfoOut) mc->recGetEEInfo(pEEInfoOut); } +void interceptor_ICJI::getAsyncInfo(CORINFO_ASYNC_INFO* pAsyncInfo) +{ + mc->cr->AddCall("getAsyncInfo"); + original_ICorJitInfo->getAsyncInfo(pAsyncInfo); + mc->recGetAsyncInfo(pAsyncInfo); +} + /*********************************************************************************/ // // Diagnostic methods @@ -1779,6 +1786,14 @@ bool interceptor_ICJI::getTailCallHelpers( return result; } +CORINFO_METHOD_HANDLE interceptor_ICJI::getAsyncResumptionStub() +{ + mc->cr->AddCall("getAsyncResumptionStub"); + CORINFO_METHOD_HANDLE stub = original_ICorJitInfo->getAsyncResumptionStub(); + mc->recGetAsyncResumptionStub(stub); + return stub; +} + void interceptor_ICJI::updateEntryPointForTailCall(CORINFO_CONST_LOOKUP* entryPoint) { mc->cr->AddCall("updateEntryPointForTailCall"); diff --git a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp index d831c6899ae854..60d344324e98cd 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-counter/icorjitinfo_generated.cpp @@ -962,6 +962,13 @@ void interceptor_ICJI::getEEInfo( original_ICorJitInfo->getEEInfo(pEEInfoOut); } +void interceptor_ICJI::getAsyncInfo( + CORINFO_ASYNC_INFO* pAsyncInfoOut) +{ + mcs->AddCall("getAsyncInfo"); + original_ICorJitInfo->getAsyncInfo(pAsyncInfoOut); +} + mdMethodDef interceptor_ICJI::getMethodDefFromMethod( CORINFO_METHOD_HANDLE hMethod) { @@ -1272,6 +1279,12 @@ bool interceptor_ICJI::getTailCallHelpers( return original_ICorJitInfo->getTailCallHelpers(callToken, sig, flags, pResult); } +CORINFO_METHOD_HANDLE interceptor_ICJI::getAsyncResumptionStub() +{ + mcs->AddCall("getAsyncResumptionStub"); + return original_ICorJitInfo->getAsyncResumptionStub(); +} + bool interceptor_ICJI::convertPInvokeCalliToCall( CORINFO_RESOLVED_TOKEN* pResolvedToken, bool mustConvert) diff --git a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp index 41f5bb46db0fa3..c04554b5844f63 100644 --- a/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp +++ b/src/coreclr/tools/superpmi/superpmi-shim-simple/icorjitinfo_generated.cpp @@ -843,6 +843,12 @@ void interceptor_ICJI::getEEInfo( original_ICorJitInfo->getEEInfo(pEEInfoOut); } +void interceptor_ICJI::getAsyncInfo( + CORINFO_ASYNC_INFO* pAsyncInfoOut) +{ + original_ICorJitInfo->getAsyncInfo(pAsyncInfoOut); +} + mdMethodDef interceptor_ICJI::getMethodDefFromMethod( CORINFO_METHOD_HANDLE hMethod) { @@ -1116,6 +1122,11 @@ bool interceptor_ICJI::getTailCallHelpers( return original_ICorJitInfo->getTailCallHelpers(callToken, sig, flags, pResult); } +CORINFO_METHOD_HANDLE interceptor_ICJI::getAsyncResumptionStub() +{ + return original_ICorJitInfo->getAsyncResumptionStub(); +} + bool interceptor_ICJI::convertPInvokeCalliToCall( CORINFO_RESOLVED_TOKEN* pResolvedToken, bool mustConvert) diff --git a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp index 8d922586923465..98c22710cbbb9b 100644 --- a/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp +++ b/src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp @@ -1188,6 +1188,12 @@ void MyICJI::getEEInfo(CORINFO_EE_INFO* pEEInfoOut) jitInstance->mc->repGetEEInfo(pEEInfoOut); } +void MyICJI::getAsyncInfo(CORINFO_ASYNC_INFO* pAsyncInfo) +{ + jitInstance->mc->cr->AddCall("getAsyncInfo"); + jitInstance->mc->repGetAsyncInfo(pAsyncInfo); +} + /*********************************************************************************/ // // Diagnostic methods @@ -1513,6 +1519,12 @@ bool MyICJI::getTailCallHelpers( return jitInstance->mc->repGetTailCallHelpers(callToken, sig, flags, pResult); } +CORINFO_METHOD_HANDLE MyICJI::getAsyncResumptionStub() +{ + jitInstance->mc->cr->AddCall("getAsyncResumptionStub"); + return jitInstance->mc->repGetAsyncResumptionStub();; +} + bool MyICJI::convertPInvokeCalliToCall(CORINFO_RESOLVED_TOKEN* pResolvedToken, bool fMustConvert) { jitInstance->mc->cr->AddCall("convertPInvokeCalliToCall"); diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index 7fbeffa86df59d..915a0e2fff25b8 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -380,6 +380,7 @@ set(VM_SOURCES_WKS threadsuspend.cpp typeparse.cpp unsafeaccessors.cpp + asyncthunks.cpp weakreferencenative.cpp yieldprocessornormalized.cpp ${VM_SOURCES_GDBJIT} diff --git a/src/coreclr/vm/amd64/AsmHelpers.asm b/src/coreclr/vm/amd64/AsmHelpers.asm index c1315be15ad7fa..cb1156e38b3264 100644 --- a/src/coreclr/vm/amd64/AsmHelpers.asm +++ b/src/coreclr/vm/amd64/AsmHelpers.asm @@ -203,10 +203,11 @@ NESTED_ENTRY OnHijackTripThread, _TEXT push rax ; make room for the real return address (Rip) push rdx PUSH_CALLEE_SAVED_REGISTERS + push_vol_reg rcx push_vol_reg rax mov rcx, rsp - alloc_stack 38h ; make extra room for xmm0, argument home slots and align the SP + alloc_stack 30h ; make extra room for xmm0 and argument home slots save_xmm128_postrsp xmm0, 20h @@ -216,8 +217,9 @@ NESTED_ENTRY OnHijackTripThread, _TEXT movdqa xmm0, [rsp + 20h] - add rsp, 38h + add rsp, 30h pop rax + pop rcx POP_CALLEE_SAVED_REGISTERS pop rdx ret ; return to the correct place, adjusted by our caller @@ -463,11 +465,11 @@ NESTED_ENTRY JIT_Patchpoint, _TEXT NESTED_END JIT_Patchpoint, _TEXT ; first arg register holds iloffset, which needs to be moved to the second register, and the first register filled with NULL -LEAF_ENTRY JIT_PartialCompilationPatchpoint, _TEXT +LEAF_ENTRY JIT_PatchpointForced, _TEXT mov rdx, rcx xor rcx, rcx jmp JIT_Patchpoint -LEAF_END JIT_PartialCompilationPatchpoint, _TEXT +LEAF_END JIT_PatchpointForced, _TEXT endif ; FEATURE_TIERED_COMPILATION diff --git a/src/coreclr/vm/amd64/cgenamd64.cpp b/src/coreclr/vm/amd64/cgenamd64.cpp index 1ba7aae2916bd7..a97b9439236080 100644 --- a/src/coreclr/vm/amd64/cgenamd64.cpp +++ b/src/coreclr/vm/amd64/cgenamd64.cpp @@ -339,7 +339,6 @@ void HijackFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFloats pRD->pCurrentContextPointers->Rsi = NULL; pRD->pCurrentContextPointers->Rdi = NULL; #endif - pRD->pCurrentContextPointers->Rcx = NULL; #ifdef UNIX_AMD64_ABI pRD->pCurrentContextPointers->Rdx = (PULONG64)&m_Args->Rdx; #else // UNIX_AMD64_ABI @@ -351,6 +350,7 @@ void HijackFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFloats pRD->pCurrentContextPointers->R11 = NULL; pRD->pCurrentContextPointers->Rax = (PULONG64)&m_Args->Rax; + pRD->pCurrentContextPointers->Rcx = (PULONG64)&m_Args->Rcx; SyncRegDisplayToCurrentContext(pRD); } diff --git a/src/coreclr/vm/amd64/cgencpu.h b/src/coreclr/vm/amd64/cgencpu.h index 825b116095a76e..1f118ca0e806a2 100644 --- a/src/coreclr/vm/amd64/cgencpu.h +++ b/src/coreclr/vm/amd64/cgencpu.h @@ -524,6 +524,11 @@ struct HijackArgs ULONG64 ReturnValue[2]; }; #endif // !FEATURE_MULTIREG_RETURN + union + { + ULONG64 Rcx; + ULONG64 AsyncRet; + }; CalleeSavedRegisters Regs; #ifdef TARGET_WINDOWS ULONG64 Rsp; diff --git a/src/coreclr/vm/amd64/unixasmhelpers.S b/src/coreclr/vm/amd64/unixasmhelpers.S index 10ab11933caee4..35f765e940164b 100644 --- a/src/coreclr/vm/amd64/unixasmhelpers.S +++ b/src/coreclr/vm/amd64/unixasmhelpers.S @@ -152,13 +152,16 @@ NESTED_ENTRY OnHijackTripThread, _TEXT, NoHandler PUSH_CALLEE_SAVED_REGISTERS + // Push rcx for the async continuation + push_register rcx + // Push rdx for the second half of the return value push_register rdx // Push rax again - this is where integer/pointer return values are returned push_register rax mov rdi, rsp - alloc_stack 0x28 + alloc_stack 0x20 // First float return register movdqa [rsp], xmm0 @@ -171,9 +174,11 @@ NESTED_ENTRY OnHijackTripThread, _TEXT, NoHandler movdqa xmm0, [rsp] movdqa xmm1, [rsp+0x10] - free_stack 0x28 + free_stack 0x20 + pop_register rax pop_register rdx + pop_register rcx POP_CALLEE_SAVED_REGISTERS ret @@ -205,10 +210,10 @@ NESTED_ENTRY JIT_Patchpoint, _TEXT, NoHandler NESTED_END JIT_Patchpoint, _TEXT // first arg register holds iloffset, which needs to be moved to the second register, and the first register filled with NULL -LEAF_ENTRY JIT_PartialCompilationPatchpoint, _TEXT +LEAF_ENTRY JIT_PatchpointForced, _TEXT mov rsi, rdi xor rdi, rdi jmp C_FUNC(JIT_Patchpoint) -LEAF_END JIT_PartialCompilationPatchpoint, _TEXT +LEAF_END JIT_PatchpointForced, _TEXT #endif // FEATURE_TIERED_COMPILATION diff --git a/src/coreclr/vm/arm/asmhelpers.S b/src/coreclr/vm/arm/asmhelpers.S index e577b1828ae8a3..973de5db340ea7 100644 --- a/src/coreclr/vm/arm/asmhelpers.S +++ b/src/coreclr/vm/arm/asmhelpers.S @@ -884,22 +884,19 @@ DelayLoad_Helper\suffix: // ------------------------------------------------------------------ // Hijack function for functions which return a value type NESTED_ENTRY OnHijackTripThread, _TEXT, NoHandler - PROLOG_PUSH "{r0,r4-r11,lr}" + // saving r1 as well, as it can have partial return value when return is > 32 bits + PROLOG_PUSH "{r0,r1,r2,r4-r11,lr}" PROLOG_VPUSH "{d0-d3}" // saving as d0-d3 can have the floating point return value - PROLOG_PUSH "{r1}" // saving as r1 can have partial return value when return is > 32 bits - alloc_stack 4 // 8 byte align CHECK_STACK_ALIGNMENT - add r0, sp, #40 + add r0, sp, #32 bl C_FUNC(OnHijackWorker) - free_stack 4 - EPILOG_POP "{r1}" EPILOG_VPOP "{d0-d3}" - EPILOG_POP "{r0,r4-r11,pc}" + EPILOG_POP "{r0,r1,r2,r4-r11,pc}" NESTED_END OnHijackTripThread, _TEXT #endif diff --git a/src/coreclr/vm/arm/cgencpu.h b/src/coreclr/vm/arm/cgencpu.h index 914c37d6fea63d..6ae9d7e3b580d5 100644 --- a/src/coreclr/vm/arm/cgencpu.h +++ b/src/coreclr/vm/arm/cgencpu.h @@ -919,6 +919,16 @@ struct HijackArgs // this is only used by functions OnHijackWorker() }; + // saving r1 as well, as it can have partial return value when return is > 32 bits + // also keeps the struct size 8-byte aligned. + DWORD R1; + + union + { + DWORD R2; + size_t AsyncRet; + }; + // // Non-volatile Integer registers // diff --git a/src/coreclr/vm/arm/stubs.cpp b/src/coreclr/vm/arm/stubs.cpp index cb665654d0cac4..e28af8bcd67aaa 100644 --- a/src/coreclr/vm/arm/stubs.cpp +++ b/src/coreclr/vm/arm/stubs.cpp @@ -1592,10 +1592,17 @@ void HijackFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFloats pRD->IsCallerSPValid = FALSE; pRD->pCurrentContext->Pc = m_ReturnAddress; - pRD->pCurrentContext->Sp = PTR_TO_TADDR(m_Args) + sizeof(struct HijackArgs); + size_t s = sizeof(struct HijackArgs); + _ASSERTE(s%4 == 0); // HijackArgs contains register values and hence will be a multiple of 4 + // stack must be multiple of 8. So if s is not multiple of 8 then there must be padding of 4 bytes + s = s + s%8; + pRD->pCurrentContext->Sp = PTR_TO_TADDR(m_Args) + s ; pRD->pCurrentContext->R0 = m_Args->R0; + pRD->pCurrentContext->R2 = m_Args->R2; + pRD->volatileCurrContextPointers.R0 = &m_Args->R0; + pRD->volatileCurrContextPointers.R2 = &m_Args->R2; pRD->pCurrentContext->R4 = m_Args->R4; pRD->pCurrentContext->R5 = m_Args->R5; diff --git a/src/coreclr/vm/arm64/asmhelpers.S b/src/coreclr/vm/arm64/asmhelpers.S index 13a18cf7855390..f974a29352bbf7 100644 --- a/src/coreclr/vm/arm64/asmhelpers.S +++ b/src/coreclr/vm/arm64/asmhelpers.S @@ -301,7 +301,7 @@ NESTED_END TheUMEntryPrestub, _TEXT // ------------------------------------------------------------------ // Hijack function for functions which return a scalar type or a struct (value type) NESTED_ENTRY OnHijackTripThread, _TEXT, NoHandler - PROLOG_SAVE_REG_PAIR_INDEXED fp, lr, -176 + PROLOG_SAVE_REG_PAIR_INDEXED fp, lr, -192 // Spill callee saved registers PROLOG_SAVE_REG_PAIR x19, x20, 16 PROLOG_SAVE_REG_PAIR x21, x22, 32 @@ -312,9 +312,12 @@ NESTED_ENTRY OnHijackTripThread, _TEXT, NoHandler // save any integral return value(s) stp x0, x1, [sp, #96] + // save async continuation return value + str x2, [sp, #112] + // save any FP/HFA return value(s) - stp q0, q1, [sp, #112] - stp q2, q3, [sp, #144] + stp q0, q1, [sp, #128] + stp q2, q3, [sp, #160] mov x0, sp bl C_FUNC(OnHijackWorker) @@ -322,16 +325,19 @@ NESTED_ENTRY OnHijackTripThread, _TEXT, NoHandler // restore any integral return value(s) ldp x0, x1, [sp, #96] + // restore async continuation return value + ldr x2, [sp, #112] + // restore any FP/HFA return value(s) - ldp q0, q1, [sp, #112] - ldp q2, q3, [sp, #144] + ldp q0, q1, [sp, #128] + ldp q2, q3, [sp, #160] EPILOG_RESTORE_REG_PAIR x19, x20, 16 EPILOG_RESTORE_REG_PAIR x21, x22, 32 EPILOG_RESTORE_REG_PAIR x23, x24, 48 EPILOG_RESTORE_REG_PAIR x25, x26, 64 EPILOG_RESTORE_REG_PAIR x27, x28, 80 - EPILOG_RESTORE_REG_PAIR_INDEXED fp, lr, 176 + EPILOG_RESTORE_REG_PAIR_INDEXED fp, lr, 192 EPILOG_RETURN NESTED_END OnHijackTripThread, _TEXT @@ -760,11 +766,11 @@ NESTED_ENTRY JIT_Patchpoint, _TEXT, NoHandler NESTED_END JIT_Patchpoint, _TEXT // first arg register holds iloffset, which needs to be moved to the second register, and the first register filled with NULL -LEAF_ENTRY JIT_PartialCompilationPatchpoint, _TEXT +LEAF_ENTRY JIT_PatchpointForced, _TEXT mov x1, x0 mov x0, #0 b C_FUNC(JIT_Patchpoint) -LEAF_END JIT_PartialCompilationPatchpoint, _TEXT +LEAF_END JIT_PatchpointForced, _TEXT #endif // FEATURE_TIERED_COMPILATION diff --git a/src/coreclr/vm/arm64/asmhelpers.asm b/src/coreclr/vm/arm64/asmhelpers.asm index bd4f60ed08ea83..9d57250de9953c 100644 --- a/src/coreclr/vm/arm64/asmhelpers.asm +++ b/src/coreclr/vm/arm64/asmhelpers.asm @@ -640,7 +640,7 @@ COMToCLRDispatchHelper_RegSetup ; ------------------------------------------------------------------ ; Hijack function for functions which return a scalar type or a struct (value type) NESTED_ENTRY OnHijackTripThread - PROLOG_SAVE_REG_PAIR fp, lr, #-176! + PROLOG_SAVE_REG_PAIR fp, lr, #-192! ; Spill callee saved registers PROLOG_SAVE_REG_PAIR x19, x20, #16 PROLOG_SAVE_REG_PAIR x21, x22, #32 @@ -651,9 +651,12 @@ COMToCLRDispatchHelper_RegSetup ; save any integral return value(s) stp x0, x1, [sp, #96] + ; save async continuation return value + str x2, [sp, #112] + ; save any FP/HFA/HVA return value(s) - stp q0, q1, [sp, #112] - stp q2, q3, [sp, #144] + stp q0, q1, [sp, #128] + stp q2, q3, [sp, #160] mov x0, sp bl OnHijackWorker @@ -661,16 +664,19 @@ COMToCLRDispatchHelper_RegSetup ; restore any integral return value(s) ldp x0, x1, [sp, #96] + ; restore async continuation return value + ldr x2, [sp, #112] + ; restore any FP/HFA/HVA return value(s) - ldp q0, q1, [sp, #112] - ldp q2, q3, [sp, #144] + ldp q0, q1, [sp, #128] + ldp q2, q3, [sp, #160] EPILOG_RESTORE_REG_PAIR x19, x20, #16 EPILOG_RESTORE_REG_PAIR x21, x22, #32 EPILOG_RESTORE_REG_PAIR x23, x24, #48 EPILOG_RESTORE_REG_PAIR x25, x26, #64 EPILOG_RESTORE_REG_PAIR x27, x28, #80 - EPILOG_RESTORE_REG_PAIR fp, lr, #176! + EPILOG_RESTORE_REG_PAIR fp, lr, #192! EPILOG_RETURN NESTED_END @@ -1162,7 +1168,7 @@ __HelperNakedFuncName SETS "$helper":CC:"Naked" NESTED_END // first arg register holds iloffset, which needs to be moved to the second register, and the first register filled with NULL - LEAF_ENTRY JIT_PartialCompilationPatchpoint + LEAF_ENTRY JIT_PatchpointForced mov x1, x0 mov x0, #0 b JIT_Patchpoint diff --git a/src/coreclr/vm/arm64/cgencpu.h b/src/coreclr/vm/arm64/cgencpu.h index c016515df827ce..9f6f86532ffddb 100644 --- a/src/coreclr/vm/arm64/cgencpu.h +++ b/src/coreclr/vm/arm64/cgencpu.h @@ -533,6 +533,12 @@ struct HijackArgs size_t ReturnValue[2]; }; union + { + DWORD64 X2; + size_t AsyncRet; + }; + DWORD64 Pad; + union { struct { NEON128 Q0; diff --git a/src/coreclr/vm/arm64/stubs.cpp b/src/coreclr/vm/arm64/stubs.cpp index a6695fb0895b1b..751595b9f528b3 100644 --- a/src/coreclr/vm/arm64/stubs.cpp +++ b/src/coreclr/vm/arm64/stubs.cpp @@ -768,9 +768,11 @@ void HijackFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFloats pRD->pCurrentContext->X0 = m_Args->X0; pRD->pCurrentContext->X1 = m_Args->X1; + pRD->pCurrentContext->X2 = m_Args->X2; pRD->volatileCurrContextPointers.X0 = &m_Args->X0; pRD->volatileCurrContextPointers.X1 = &m_Args->X1; + pRD->volatileCurrContextPointers.X1 = &m_Args->X2; pRD->pCurrentContext->X19 = m_Args->X19; pRD->pCurrentContext->X20 = m_Args->X20; diff --git a/src/coreclr/vm/array.cpp b/src/coreclr/vm/array.cpp index 9c2f495a844c04..698e3a7697eda2 100644 --- a/src/coreclr/vm/array.cpp +++ b/src/coreclr/vm/array.cpp @@ -465,7 +465,7 @@ MethodTable* Module::CreateArrayMethodTable(TypeHandle elemTypeHnd, CorElementTy + 3; // for rank specific Get, Set, Address MethodDescChunk * pChunks = MethodDescChunk::CreateChunk(pAllocator->GetHighFrequencyHeap(), - dwMethodDescs, mcArray, FALSE /* fNonVtableSlot*/, FALSE /* fNativeCodeSlot */, + dwMethodDescs, mcArray, FALSE /* fNonVtableSlot*/, FALSE /* fNativeCodeSlot */, FALSE /* HasAsyncMethodData */, pMT, pamTracker); pClass->SetChunks(pChunks); diff --git a/src/coreclr/vm/asyncthunks.cpp b/src/coreclr/vm/asyncthunks.cpp new file mode 100644 index 00000000000000..8b951aae1c277b --- /dev/null +++ b/src/coreclr/vm/asyncthunks.cpp @@ -0,0 +1,623 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// =========================================================================== +// File: asyncthunks.cpp +// + +// =========================================================================== +// This file contains the implementation for creating and using prestubs +// =========================================================================== +// + +#include "common.h" + +bool MethodDesc::TryGenerateAsyncThunk(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder) +{ + STANDARD_VM_CONTRACT; + _ASSERTE(resolver != NULL); + _ASSERTE(methodILDecoder != NULL); + _ASSERTE(*resolver == NULL && *methodILDecoder == NULL); + _ASSERTE(IsIL()); + _ASSERTE(GetRVA() == 0); + + if (!IsAsyncThunkMethod()) + { + return false; + } + + MethodDesc *pAsyncOtherVariant = this->GetAsyncOtherVariant(); + _ASSERTE(!IsWrapperStub() && !pAsyncOtherVariant->IsWrapperStub()); + + MetaSig msig(this); + + SigTypeContext sigContext(pAsyncOtherVariant); + ILStubLinker sl( + GetModule(), + GetSignature(), + &sigContext, + pAsyncOtherVariant, + (ILStubLinkerFlags)ILSTUB_LINKER_FLAG_NONE); + + if (IsAsyncMethod()) + { + EmitAsyncMethodThunk(pAsyncOtherVariant, msig, &sl); + } + else + { + EmitTaskReturningThunk(pAsyncOtherVariant, msig, &sl); + } + + NewHolder ilResolver = new ILStubResolver(); + // Initialize the resolver target details. + ilResolver->SetStubMethodDesc(this); + ilResolver->SetStubTargetMethodDesc(pAsyncOtherVariant); + + // Generate all IL associated data for JIT + { + UINT maxStack; + size_t cbCode = sl.Link(&maxStack); + DWORD cbSig = sl.GetLocalSigSize(); + + COR_ILMETHOD_DECODER* pILHeader = ilResolver->AllocGeneratedIL(cbCode, cbSig, maxStack); + BYTE* pbBuffer = (BYTE*)pILHeader->Code; + BYTE* pbLocalSig = (BYTE*)pILHeader->LocalVarSig; + _ASSERTE(cbSig == pILHeader->cbLocalVarSig); + + size_t numEH = sl.GetNumEHClauses(); + if (numEH > 0) + { + sl.WriteEHClauses(ilResolver->AllocEHSect(numEH)); + } + + sl.GenerateCode(pbBuffer, cbCode); + sl.GetLocalSig(pbLocalSig, cbSig); + + // Store the token lookup map + ilResolver->SetTokenLookupMap(sl.GetTokenLookupMap()); + ilResolver->SetJitFlags(CORJIT_FLAGS(CORJIT_FLAGS::CORJIT_FLAG_IL_STUB)); + + *resolver = (DynamicResolver*)ilResolver; + *methodILDecoder = pILHeader; + } + + ilResolver.SuppressRelease(); + return true; +} + +void MethodDesc::EmitTaskReturningThunk(MethodDesc* pAsyncOtherVariant, MetaSig& thunkMsig, ILStubLinker* pSL) +{ + _ASSERTE(!pAsyncOtherVariant->IsAsyncThunkMethod()); + + ILCodeStream* pCode = pSL->NewCodeStream(ILStubLinker::kDispatch); + + unsigned continuationLocal = pCode->NewLocal(LocalDesc(CoreLibBinder::GetClass(CLASS__CONTINUATION))); + + TypeHandle thTaskRet = thunkMsig.GetRetTypeHandleThrowing(); + + bool isValueTask = thTaskRet.GetMethodTable()->IsValueType(); + + LocalDesc returnLocalDesc(thTaskRet); + DWORD returnLocal = pCode->NewLocal(returnLocalDesc); + + TypeHandle thLogicalRetType; + DWORD logicalResultLocal = UINT_MAX; + if (thTaskRet.GetNumGenericArgs() > 0) + { + thLogicalRetType = thTaskRet.GetMethodTable()->GetInstantiation()[0]; + logicalResultLocal = pCode->NewLocal(LocalDesc(thLogicalRetType)); + } + + LocalDesc exceptionLocalDesc(CoreLibBinder::GetClass(CLASS__EXCEPTION)); + DWORD exceptionLocal = pCode->NewLocal(exceptionLocalDesc); + + LocalDesc executionAndSyncBlockStoreLocalDesc(CoreLibBinder::GetClass(CLASS__EXECUTIONANDSYNCBLOCKSTORE)); + DWORD executionAndSyncBlockStoreLocal = pCode->NewLocal(executionAndSyncBlockStoreLocalDesc); + + ILCodeLabel* pNoExceptionLabel = pCode->NewCodeLabel(); + ILCodeLabel* pReturnResultLabel = pCode->NewCodeLabel(); + ILCodeLabel* pSuspendedLabel = pCode->NewCodeLabel(); + + pCode->EmitLDLOCA(executionAndSyncBlockStoreLocal); + pCode->EmitCALL(pCode->GetToken(CoreLibBinder::GetMethod(METHOD__EXECUTIONANDSYNCBLOCKSTORE__PUSH)), 1, 0); + + { + pCode->BeginTryBlock(); + pCode->EmitNOP("Separate try blocks"); + { + pCode->BeginTryBlock(); + + DWORD localArg = 0; + if (thunkMsig.HasThis()) + { + pCode->EmitLDARG(localArg++); + } + + for (UINT iArg = 0; iArg < thunkMsig.NumFixedArgs(); iArg++) + { + pCode->EmitLDARG(localArg++); + } + + int token; + _ASSERTE(!pAsyncOtherVariant->IsWrapperStub()); + if (pAsyncOtherVariant->HasClassOrMethodInstantiation()) + { + // For generic code emit generic signatures. + int typeSigToken = mdTokenNil; + if (pAsyncOtherVariant->HasClassInstantiation()) + { + SigBuilder typeSigBuilder; + typeSigBuilder.AppendElementType(ELEMENT_TYPE_GENERICINST); + typeSigBuilder.AppendElementType(ELEMENT_TYPE_INTERNAL); + // TODO: (async) Encoding potentially shared method tables in + // signatures of tokens seems odd, but this hits assert + // with the typical method table. + typeSigBuilder.AppendPointer(pAsyncOtherVariant->GetMethodTable()); + DWORD numClassTypeArgs = pAsyncOtherVariant->GetNumGenericClassArgs(); + typeSigBuilder.AppendData(numClassTypeArgs); + for (DWORD i = 0; i < numClassTypeArgs; ++i) + { + typeSigBuilder.AppendElementType(ELEMENT_TYPE_VAR); + typeSigBuilder.AppendData(i); + } + + DWORD typeSigLen; + PCCOR_SIGNATURE typeSig = (PCCOR_SIGNATURE)typeSigBuilder.GetSignature(&typeSigLen); + typeSigToken = pCode->GetSigToken(typeSig, typeSigLen); + } + + if (pAsyncOtherVariant->HasMethodInstantiation()) + { + SigBuilder methodSigBuilder; + DWORD numMethodTypeArgs = pAsyncOtherVariant->GetNumGenericMethodArgs(); + methodSigBuilder.AppendByte(IMAGE_CEE_CS_CALLCONV_GENERICINST); + methodSigBuilder.AppendData(numMethodTypeArgs); + for (DWORD i = 0; i < numMethodTypeArgs; ++i) + { + methodSigBuilder.AppendElementType(ELEMENT_TYPE_MVAR); + methodSigBuilder.AppendData(i); + } + + DWORD sigLen; + PCCOR_SIGNATURE sig = (PCCOR_SIGNATURE)methodSigBuilder.GetSignature(&sigLen); + int methodSigToken = pCode->GetSigToken(sig, sigLen); + token = pCode->GetToken(pAsyncOtherVariant, typeSigToken, methodSigToken); + } + else + { + token = pCode->GetToken(pAsyncOtherVariant, typeSigToken); + } + } + else + { + token = pCode->GetToken(pAsyncOtherVariant); + } + + pCode->EmitCALL(token, localArg, logicalResultLocal != UINT_MAX ? 1 : 0); + + if (logicalResultLocal != UINT_MAX) + pCode->EmitSTLOC(logicalResultLocal); + pCode->EmitCALL(METHOD__STUBHELPERS__ASYNC_CALL_CONTINUATION, 0, 1); + pCode->EmitSTLOC(continuationLocal); + pCode->EmitLEAVE(pNoExceptionLabel); + pCode->EndTryBlock(); + } + // Catch + { + pCode->BeginCatchBlock(pCode->GetToken(CoreLibBinder::GetClass(CLASS__EXCEPTION))); + + int fromExceptionToken; + if (logicalResultLocal != UINT_MAX) + { + MethodDesc* fromExceptionMD; + if (isValueTask) + fromExceptionMD = CoreLibBinder::GetMethod(METHOD__VALUETASK__FROM_EXCEPTION_1); + else + fromExceptionMD = CoreLibBinder::GetMethod(METHOD__TASK__FROM_EXCEPTION_1); + + fromExceptionMD = FindOrCreateAssociatedMethodDesc(fromExceptionMD, fromExceptionMD->GetMethodTable(), FALSE, Instantiation(&thLogicalRetType, 1), FALSE); + + fromExceptionToken = GetTokenForGenericMethodCallWithAsyncReturnType(pCode, fromExceptionMD); + } + else + { + MethodDesc* fromExceptionMD; + if (isValueTask) + fromExceptionMD = CoreLibBinder::GetMethod(METHOD__VALUETASK__FROM_EXCEPTION); + else + fromExceptionMD = CoreLibBinder::GetMethod(METHOD__TASK__FROM_EXCEPTION); + + fromExceptionToken = pCode->GetToken(fromExceptionMD); + } + pCode->EmitCALL(fromExceptionToken, 1, 1); + pCode->EmitSTLOC(returnLocal); + pCode->EmitLEAVE(pReturnResultLabel); + pCode->EndCatchBlock(); + } + pCode->EndTryBlock(); + } + // + { + pCode->BeginFinallyBlock(); + pCode->EmitLDLOCA(executionAndSyncBlockStoreLocal); + pCode->EmitCALL(pCode->GetToken(CoreLibBinder::GetMethod(METHOD__EXECUTIONANDSYNCBLOCKSTORE__POP)), 1, 0); + pCode->EmitENDFINALLY(); + pCode->EndFinallyBlock(); + } + + pCode->EmitLabel(pNoExceptionLabel); + pCode->EmitLDLOC(continuationLocal); + pCode->EmitBRTRUE(pSuspendedLabel); + if (logicalResultLocal != UINT_MAX) + { + pCode->EmitLDLOC(logicalResultLocal); + MethodDesc* md; + if (isValueTask) + md = CoreLibBinder::GetMethod(METHOD__VALUETASK__FROM_RESULT_T); + else + md = CoreLibBinder::GetMethod(METHOD__TASK__FROM_RESULT_T); + md = FindOrCreateAssociatedMethodDesc(md, md->GetMethodTable(), FALSE, Instantiation(&thLogicalRetType, 1), FALSE); + + int fromResultToken = GetTokenForGenericMethodCallWithAsyncReturnType(pCode, md); + pCode->EmitCALL(fromResultToken, 1, 1); + } + else + { + if (isValueTask) + pCode->EmitCALL(METHOD__VALUETASK__GET_COMPLETED_TASK, 0, 1); + else + pCode->EmitCALL(METHOD__TASK__GET_COMPLETED_TASK, 0, 1); + } + + pCode->EmitSTLOC(returnLocal); + pCode->EmitLabel(pReturnResultLabel); + pCode->EmitLDLOC(returnLocal); + pCode->EmitRET(); + + pCode->EmitLabel(pSuspendedLabel); + + int finalizeTaskReturningThunkToken; + if (logicalResultLocal != UINT_MAX) + { + MethodDesc* md; + if (isValueTask) + md = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__FINALIZE_VALUETASK_RETURNING_THUNK_1); + else + md = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__FINALIZE_TASK_RETURNING_THUNK_1); + + md = FindOrCreateAssociatedMethodDesc(md, md->GetMethodTable(), FALSE, Instantiation(&thLogicalRetType, 1), FALSE); + finalizeTaskReturningThunkToken = GetTokenForGenericMethodCallWithAsyncReturnType(pCode, md); + } + else + { + MethodDesc* md; + if (isValueTask) + md = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__FINALIZE_VALUETASK_RETURNING_THUNK); + else + md = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__FINALIZE_TASK_RETURNING_THUNK); + finalizeTaskReturningThunkToken = pCode->GetToken(md); + } + pCode->EmitLDLOC(continuationLocal); + pCode->EmitCALL(finalizeTaskReturningThunkToken, 1, 1); + pCode->EmitRET(); +} + +// Given an async thunk method, return a SigPointer to the unwrapped result type. For +// example, for Task Foo() this returns the signature representing +// (MVAR 0). For Task, it returns the signature representing (int). +SigPointer MethodDesc::GetAsyncThunkResultTypeSig() +{ + _ASSERTE(IsAsyncThunkMethod()); + PCCOR_SIGNATURE pSigRaw; + DWORD cSig; + if (FAILED(GetMDImport()->GetSigOfMethodDef(GetMemberDef(), &cSig, &pSigRaw))) + { + _ASSERTE(!"Loaded MethodDesc should not fail to get signature"); + pSigRaw = NULL; + cSig = 0; + } + + SigPointer pSig(pSigRaw, cSig); + uint32_t callConvInfo; + IfFailThrow(pSig.GetCallingConvInfo(&callConvInfo)); + + if ((callConvInfo & IMAGE_CEE_CS_CALLCONV_GENERIC) != 0) + { + // GenParamCount + IfFailThrow(pSig.GetData(NULL)); + } + + // ParamCount + IfFailThrow(pSig.GetData(NULL)); + + // ReturnType comes now. Skip the modifiers (like modreqs in async signatures). + IfFailThrow(pSig.SkipCustomModifiers()); + + CorElementType etype; + IfFailThrow(pSig.PeekElemType(&etype)); + + // here we should have something Task or ValueTask + _ASSERTE(etype == ELEMENT_TYPE_GENERICINST); + + // GENERICINST + + // ELEMENT_TYPE_GENERICINST + IfFailThrow(pSig.GetElemType(NULL)); + + // Task`1/ValueTask`1 + IfFailThrow(pSig.SkipExactlyOne()); + + // argCnt + IfFailThrow(pSig.GetData(NULL)); + + // Get the start of the return type + PCCOR_SIGNATURE returnTypeSig; + uint32_t tailLength; + pSig.GetSignature(&returnTypeSig, &tailLength); + + // Skip to the end of the return type so we can get the length. + IfFailThrow(pSig.SkipExactlyOne()); + + PCCOR_SIGNATURE returnTypeSigEnd; + pSig.GetSignature(&returnTypeSigEnd, &tailLength); + + return SigPointer(returnTypeSig, (DWORD)(returnTypeSigEnd - returnTypeSig)); +} + +// Given a method Foo, return a MethodSpec token for Foo instantiated +// with the result type from the current async method's return type. For +// example, if "this" represents Task> Foo(), and "md" is +// Task.FromResult, this returns a MethodSpec representing +// Task.FromResult>. +int MethodDesc::GetTokenForGenericMethodCallWithAsyncReturnType(ILCodeStream* pCode, MethodDesc* md) +{ + if (!md->HasClassOrMethodInstantiation()) + { + return pCode->GetToken(md); + } + + // We never get here with a class instantiation currently. + _ASSERTE(!md->HasClassInstantiation()); + + SigBuilder methodSigBuilder; + methodSigBuilder.AppendByte(IMAGE_CEE_CS_CALLCONV_GENERICINST); + methodSigBuilder.AppendData(1); + SigPointer retTypeSig = GetAsyncThunkResultTypeSig(); + PCCOR_SIGNATURE retTypeSigRaw; + uint32_t retTypeSigLen; + retTypeSig.GetSignature(&retTypeSigRaw, &retTypeSigLen); + methodSigBuilder.AppendBlob((const PVOID)retTypeSigRaw, retTypeSigLen); + + DWORD methodSigLen; + PCCOR_SIGNATURE methodSig = (PCCOR_SIGNATURE)methodSigBuilder.GetSignature(&methodSigLen); + int methodSigToken = pCode->GetSigToken(methodSig, methodSigLen); + + return pCode->GetToken(md, mdTokenNil, methodSigToken); +} + +// Given a method Bar.Foo, return a MethodSpec token for Bar.Foo +// instantiated with the result type from the current async method's return +// type. For example, if "this" represents Task> Foo(), and +// "md" is TaskAwaiter.GetResult(), this returns a MethodSpec representing +// TaskAwaiter>.GetResult(). +int MethodDesc::GetTokenForGenericTypeMethodCallWithAsyncReturnType(ILCodeStream* pCode, MethodDesc* md) +{ + if (!md->HasClassOrMethodInstantiation()) + { + return pCode->GetToken(md); + } + + // We never get here with a method instantiation currently. + _ASSERTE(!md->HasMethodInstantiation()); + + SigBuilder typeSigBuilder; + typeSigBuilder.AppendData(ELEMENT_TYPE_GENERICINST); + typeSigBuilder.AppendData(ELEMENT_TYPE_INTERNAL); + // TODO: (async) Encoding potentially shared method tables in + // signatures of tokens seems odd, but this hits assert + // with the typical method table. + typeSigBuilder.AppendPointer(md->GetMethodTable()); + typeSigBuilder.AppendData(1); + + SigPointer retTypeSig = GetAsyncThunkResultTypeSig(); + PCCOR_SIGNATURE retTypeSigRaw; + uint32_t retTypeSigLen; + retTypeSig.GetSignature(&retTypeSigRaw, &retTypeSigLen); + + typeSigBuilder.AppendBlob((const PVOID)retTypeSigRaw, retTypeSigLen); + + DWORD typeSigLen; + PCCOR_SIGNATURE typeSig = (PCCOR_SIGNATURE)typeSigBuilder.GetSignature(&typeSigLen); + int typeSigToken = pCode->GetSigToken(typeSig, typeSigLen); + + return pCode->GetToken(md, typeSigToken); +} + +void MethodDesc::EmitAsyncMethodThunk(MethodDesc* pAsyncOtherVariant, MetaSig& msig, ILStubLinker* pSL) +{ + _ASSERTE(!pAsyncOtherVariant->IsAsyncThunkMethod()); + _ASSERTE(!pAsyncOtherVariant->IsVoid()); + + // TODO: (async) we may now be able to just do "AsyncHelpers.Await(other(arg))", + // but would need to make sure it is not "optimized" back to calling this same thunk. + + // Implement IL that is effectively the following + /* + { + TaskAwaiter awaiter = other(arg).GetAwaiter(); + if (!awaiter.IsCompleted) + { + // Magic function which will suspend the current run of async methods + AsyncHelpers.UnsafeAwaitAwaiter>(awaiter); + } + return awaiter.GetResult(); + } + */ + ILCodeStream* pCode = pSL->NewCodeStream(ILStubLinker::kDispatch); + + TypeHandle thTaskAwaiter; + MethodTable* pMTTask; + MethodDesc* mdGetAwaiter; + MethodDesc* mdIsCompleted; + MethodDesc* mdGetResult; + + if (msig.IsReturnTypeVoid()) + { + pMTTask = CoreLibBinder::GetClass(CLASS__TASK); + thTaskAwaiter = CoreLibBinder::GetClass(CLASS__TASK_AWAITER); + mdGetAwaiter = CoreLibBinder::GetMethod(METHOD__TASK__GET_AWAITER); + mdIsCompleted = CoreLibBinder::GetMethod(METHOD__TASK_AWAITER__GET_ISCOMPLETED); + mdGetResult = CoreLibBinder::GetMethod(METHOD__TASK_AWAITER__GET_RESULT); + } + else + { + TypeHandle thLogicalRetType = msig.GetRetTypeHandleThrowing(); + MethodTable* pMTTaskOpen = CoreLibBinder::GetClass(CLASS__TASK_1); + pMTTask = ClassLoader::LoadGenericInstantiationThrowing(pMTTaskOpen->GetModule(), pMTTaskOpen->GetCl(), Instantiation(&thLogicalRetType, 1)).GetMethodTable(); + MethodTable* pMTTaskAwaiterOpen = CoreLibBinder::GetClass(CLASS__TASK_AWAITER_1); + thTaskAwaiter = ClassLoader::LoadGenericInstantiationThrowing(pMTTaskAwaiterOpen->GetModule(), pMTTaskAwaiterOpen->GetCl(), Instantiation(&thLogicalRetType, 1)); + mdGetAwaiter = CoreLibBinder::GetMethod(METHOD__TASK_1__GET_AWAITER); + mdGetAwaiter = pMTTask->GetParallelMethodDesc(mdGetAwaiter); + mdIsCompleted = CoreLibBinder::GetMethod(METHOD__TASK_AWAITER_1__GET_ISCOMPLETED); + mdIsCompleted = thTaskAwaiter.GetMethodTable()->GetParallelMethodDesc(mdIsCompleted); + mdGetResult = CoreLibBinder::GetMethod(METHOD__TASK_AWAITER_1__GET_RESULT); + mdGetResult = thTaskAwaiter.GetMethodTable()->GetParallelMethodDesc(mdGetResult); + } + + DWORD localArg = 0; + ILCodeLabel* pGetResultLabel = pCode->NewCodeLabel(); + + LocalDesc awaiterLocalDesc(thTaskAwaiter); + DWORD awaiterLocal = pCode->NewLocal(awaiterLocalDesc); + + if (msig.HasThis()) + { + pCode->EmitLDARG(localArg++); + } + for (UINT iArg = 0; iArg < msig.NumFixedArgs(); iArg++) + { + pCode->EmitLDARG(localArg++); + } + + int token; + _ASSERTE(!pAsyncOtherVariant->IsWrapperStub()); + if (pAsyncOtherVariant->HasClassOrMethodInstantiation()) + { + // For generic code emit generic signatures. + int typeSigToken = mdTokenNil; + if (pAsyncOtherVariant->HasClassInstantiation()) + { + SigBuilder typeSigBuilder; + typeSigBuilder.AppendElementType(ELEMENT_TYPE_GENERICINST); + typeSigBuilder.AppendElementType(ELEMENT_TYPE_INTERNAL); + // TODO: (async) Encoding potentially shared method tables in + // signatures of tokens seems odd, but this hits assert + // with the typical method table. + typeSigBuilder.AppendPointer(pAsyncOtherVariant->GetMethodTable()); + DWORD numClassTypeArgs = pAsyncOtherVariant->GetNumGenericClassArgs(); + typeSigBuilder.AppendData(numClassTypeArgs); + for (DWORD i = 0; i < numClassTypeArgs; ++i) + { + typeSigBuilder.AppendElementType(ELEMENT_TYPE_VAR); + typeSigBuilder.AppendData(i); + } + + DWORD typeSigLen; + PCCOR_SIGNATURE typeSig = (PCCOR_SIGNATURE)typeSigBuilder.GetSignature(&typeSigLen); + typeSigToken = pCode->GetSigToken(typeSig, typeSigLen); + } + + if (pAsyncOtherVariant->HasMethodInstantiation()) + { + SigBuilder methodSigBuilder; + DWORD numMethodTypeArgs = pAsyncOtherVariant->GetNumGenericMethodArgs(); + methodSigBuilder.AppendByte(IMAGE_CEE_CS_CALLCONV_GENERICINST); + methodSigBuilder.AppendData(numMethodTypeArgs); + for (DWORD i = 0; i < numMethodTypeArgs; ++i) + { + methodSigBuilder.AppendElementType(ELEMENT_TYPE_MVAR); + methodSigBuilder.AppendData(i); + } + + DWORD sigLen; + PCCOR_SIGNATURE sig = (PCCOR_SIGNATURE)methodSigBuilder.GetSignature(&sigLen); + int methodSigToken = pCode->GetSigToken(sig, sigLen); + token = pCode->GetToken(pAsyncOtherVariant, typeSigToken, methodSigToken); + } + else + { + token = pCode->GetToken(pAsyncOtherVariant, typeSigToken); + } + } + else + { + token = pCode->GetToken(pAsyncOtherVariant); + } + + pCode->EmitCALL(token, localArg, 1); + + int getAwaiterToken; + int getIsCompletedToken; + int getResultToken; + if (!msig.IsReturnTypeVoid()) + { + getAwaiterToken = GetTokenForGenericTypeMethodCallWithAsyncReturnType(pCode, mdGetAwaiter); + getIsCompletedToken = GetTokenForGenericTypeMethodCallWithAsyncReturnType(pCode, mdIsCompleted); + getResultToken = GetTokenForGenericTypeMethodCallWithAsyncReturnType(pCode, mdGetResult); + } + else + { + getAwaiterToken = pCode->GetToken(mdGetAwaiter); + getIsCompletedToken = pCode->GetToken(mdIsCompleted); + getResultToken = pCode->GetToken(mdGetResult); + } + + pCode->EmitCALLVIRT(getAwaiterToken, 1, 1); + pCode->EmitSTLOC(awaiterLocal); + pCode->EmitLDLOCA(awaiterLocal); + pCode->EmitCALL(getIsCompletedToken, 1, 1); + pCode->EmitBRTRUE(pGetResultLabel); + pCode->EmitLDLOC(awaiterLocal); + + int awaitAwaiterToken = GetTokenForAwaitAwaiterInstantiatedOverTaskAwaiterType(pCode, thTaskAwaiter); + pCode->EmitCALL(awaitAwaiterToken, 1, 0); + pCode->EmitLabel(pGetResultLabel); + + pCode->EmitLDLOCA(awaiterLocal); + pCode->EmitCALL(getResultToken, 1, mdGetResult->IsVoid() ? 0 : 1); + + pCode->EmitRET(); +} + +// Get a token for AsyncHelpers.UnsafeAwaitAwaiter>() +// with T substituted by the return type of the async method. +int MethodDesc::GetTokenForAwaitAwaiterInstantiatedOverTaskAwaiterType(ILCodeStream* pCode, TypeHandle taskAwaiterType) +{ + MethodDesc* awaitAwaiter = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__UNSAFE_AWAIT_AWAITER_1); + TypeHandle thInstantiations[]{ taskAwaiterType }; + awaitAwaiter = FindOrCreateAssociatedMethodDesc(awaitAwaiter, awaitAwaiter->GetMethodTable(), FALSE, Instantiation(thInstantiations, 1), FALSE); + + if (!taskAwaiterType.IsSharedByGenericInstantiations()) + { + return pCode->GetToken(awaitAwaiter); + } + + SigBuilder methodSigBuilder; + methodSigBuilder.AppendByte(IMAGE_CEE_CS_CALLCONV_GENERICINST); + methodSigBuilder.AppendData(1); + SigPointer retTypeSig = GetAsyncThunkResultTypeSig(); + PCCOR_SIGNATURE retTypeSigRaw; + uint32_t retTypeSigLen; + retTypeSig.GetSignature(&retTypeSigRaw, &retTypeSigLen); + + methodSigBuilder.AppendElementType(ELEMENT_TYPE_GENERICINST); + methodSigBuilder.AppendElementType(ELEMENT_TYPE_INTERNAL); + methodSigBuilder.AppendPointer(taskAwaiterType.GetMethodTable()); + methodSigBuilder.AppendData(1); + methodSigBuilder.AppendBlob((const PVOID)retTypeSigRaw, retTypeSigLen); + + DWORD methodSigLen; + PCCOR_SIGNATURE methodSig = (PCCOR_SIGNATURE)methodSigBuilder.GetSignature(&methodSigLen); + int methodSigToken = pCode->GetSigToken(methodSig, methodSigLen); + + return pCode->GetToken(awaitAwaiter, mdTokenNil, methodSigToken); +} diff --git a/src/coreclr/vm/binder.cpp b/src/coreclr/vm/binder.cpp index c60ced8f4cb26c..939aa29359649f 100644 --- a/src/coreclr/vm/binder.cpp +++ b/src/coreclr/vm/binder.cpp @@ -441,6 +441,12 @@ void CoreLibBinder::BuildConvertedSignature(const BYTE* pSig, SigBuilder * pSigB argCount = 0; } + if ((callConv & IMAGE_CEE_CS_CALLCONV_GENERIC) != 0) + { + unsigned genericArgCount = *pSig++; + pSigBuilder->AppendData(genericArgCount); + } + // <= because we want to include the return value or the field for (unsigned i = 0; i <= argCount; i++) { if (ConvertType(pSig, pSigBuilder)) diff --git a/src/coreclr/vm/callhelpers.cpp b/src/coreclr/vm/callhelpers.cpp index 26c2ff0c03c229..f575a21899185c 100644 --- a/src/coreclr/vm/callhelpers.cpp +++ b/src/coreclr/vm/callhelpers.cpp @@ -327,7 +327,7 @@ void MethodDescCallSite::CallTargetWorker(const ARG_SLOT *pArguments, ARG_SLOT * ENABLE_FORBID_GC_LOADER_USE_IN_THIS_SCOPE(); _ASSERTE(isCallConv(m_methodSig.GetCallingConvention(), IMAGE_CEE_CS_CALLCONV_DEFAULT)); - _ASSERTE(!(m_methodSig.GetCallingConventionInfo() & CORINFO_CALLCONV_PARAMTYPE)); + _ASSERTE(!m_methodSig.HasGenericContextArg()); #ifdef DEBUGGING_SUPPORTED if (CORDebuggerTraceCall()) diff --git a/src/coreclr/vm/callingconvention.h b/src/coreclr/vm/callingconvention.h index 5b167300ec856d..98e6eee147fcd6 100644 --- a/src/coreclr/vm/callingconvention.h +++ b/src/coreclr/vm/callingconvention.h @@ -2148,6 +2148,18 @@ class ArgIteratorBase return m_pSig->IsVarArg() || m_pSig->IsTreatAsVarArg(); } + BOOL IsAsyncCall() + { + LIMITED_METHOD_CONTRACT; + return m_pSig->IsAsyncCall(); + } + + BOOL HasAsyncContinuation() + { + LIMITED_METHOD_CONTRACT; + return m_pSig->HasAsyncContinuation(); + } + DWORD NumFixedArgs() { LIMITED_METHOD_CONTRACT; diff --git a/src/coreclr/vm/class.cpp b/src/coreclr/vm/class.cpp index 1834d34953b156..68349c9fe168c5 100644 --- a/src/coreclr/vm/class.cpp +++ b/src/coreclr/vm/class.cpp @@ -661,6 +661,18 @@ HRESULT EEClass::AddMethodDesc( PCCOR_SIGNATURE sig; if (FAILED(hr = pImport->GetSigOfMethodDef(methodDef, &sigLen, &sig))) return hr; + + SigParser sigParser(sig, sigLen); + ULONG offsetOfAsyncDetails; + bool isValueTask; + MethodReturnKind returnKind = ClassifyMethodReturnKind(sigParser, pModule, &offsetOfAsyncDetails, &isValueTask); + if (returnKind != MethodReturnKind::NormalMethod) + { + // TODO: (async) revisit and examine if this can be supported + LOG((LF_ENC, LL_INFO100, "**Error** EnC for Async methods is NYI")); + return E_FAIL; + } + uint32_t callConv = CorSigUncompressData(sig); DWORD classification = (callConv & IMAGE_CEE_CS_CALLCONV_GENERIC) ? mcInstantiated @@ -679,6 +691,7 @@ HRESULT EEClass::AddMethodDesc( classification, TRUE, // fNonVtableSlot TRUE, // fNativeCodeSlot + FALSE, /* HasAsyncMethodData */ pMT, &dummyAmTracker); @@ -726,7 +739,9 @@ HRESULT EEClass::AddMethodDesc( TRUE, // fEnC 0, // RVA - non-zero only for NDirect pImport, - NULL + NULL, + Signature(), + AsyncMethodKind::NotAsync COMMA_INDEBUG(debug_szMethodName) COMMA_INDEBUG(pMT->GetDebugClassName()) COMMA_INDEBUG(NULL) diff --git a/src/coreclr/vm/clrtocomcall.cpp b/src/coreclr/vm/clrtocomcall.cpp index a9505696385123..d8b0924563e475 100644 --- a/src/coreclr/vm/clrtocomcall.cpp +++ b/src/coreclr/vm/clrtocomcall.cpp @@ -229,6 +229,13 @@ I4ARRAYREF SetUpWrapperInfo(MethodDesc *pMD) WrapperTypeArr = (I4ARRAYREF)AllocatePrimitiveArray(ELEMENT_TYPE_I4, numArgs); GCX_PREEMP(); + + + // TODO: (async) revisit and examine if this needs to be supported somehow + if (pMD->IsAsyncMethod()) + { + ThrowHR(COR_E_NOTSUPPORTED); + } // Collects ParamDef information in an indexed array where element 0 represents // the return type. @@ -504,6 +511,12 @@ UINT32 CLRToCOMLateBoundWorker( LPCUTF8 strMemberName; ULONG uSemantic; + // TODO: (async) revisit and examine if this needs to be supported somehow + if (pItfMD->IsAsyncMethod()) + { + ThrowHR(COR_E_NOTSUPPORTED); + } + // See if there is property information for this member. hr = pItfMT->GetMDImport()->GetPropertyInfoForMethodDef(pItfMD->GetMemberDef(), &propToken, &strMemberName, &uSemantic); if (hr != S_OK) diff --git a/src/coreclr/vm/clsload.cpp b/src/coreclr/vm/clsload.cpp index 1a3a6ad4343619..7d5d4916773d26 100644 --- a/src/coreclr/vm/clsload.cpp +++ b/src/coreclr/vm/clsload.cpp @@ -2801,7 +2801,10 @@ TypeHandle ClassLoader::PublishType(const TypeKey *pTypeKey, TypeHandle typeHnd) { MethodDesc * pMD = it.GetMethodDesc(); CONSISTENCY_CHECK(pMD != NULL && pMD->GetMethodTable() == pMT); - if (!pMD->IsUnboxingStub()) + // For {Task-returning, Async} variants of the same definition + // we associate the methoddef with the Task-returning variant since it + // matches the methadata signature. + if (!pMD->IsUnboxingStub() && !pMD->IsAsyncVariantMethod()) { pModule->EnsuredStoreMethodDef(pMD->GetMemberDef(), pMD); } diff --git a/src/coreclr/vm/codeversion.cpp b/src/coreclr/vm/codeversion.cpp index cc8a516a7dc4a2..9b95ef12f9de83 100644 --- a/src/coreclr/vm/codeversion.cpp +++ b/src/coreclr/vm/codeversion.cpp @@ -163,7 +163,7 @@ void NativeCodeVersionNode::SetOptimizationTier(NativeCodeVersion::OptimizationT #ifdef FEATURE_ON_STACK_REPLACEMENT -PatchpointInfo* NativeCodeVersionNode::GetOSRInfo(unsigned * ilOffset) const +PatchpointInfo* NativeCodeVersionNode::GetOSRInfo(unsigned * ilOffset) { LIMITED_METHOD_DAC_CONTRACT; *ilOffset = m_ilOffset; diff --git a/src/coreclr/vm/codeversion.h b/src/coreclr/vm/codeversion.h index b9dc76b90e3066..7735a1039d43c6 100644 --- a/src/coreclr/vm/codeversion.h +++ b/src/coreclr/vm/codeversion.h @@ -289,7 +289,7 @@ class NativeCodeVersionNode #endif #ifdef FEATURE_ON_STACK_REPLACEMENT - PatchpointInfo * GetOSRInfo(unsigned * ilOffset) const; + PatchpointInfo * GetOSRInfo(unsigned * ilOffset); #endif private: diff --git a/src/coreclr/vm/comdelegate.cpp b/src/coreclr/vm/comdelegate.cpp index e02caee100bede..eeb57d770bbdad 100644 --- a/src/coreclr/vm/comdelegate.cpp +++ b/src/coreclr/vm/comdelegate.cpp @@ -1963,7 +1963,7 @@ Stub* COMDelegate::GetInvokeMethodStub(EEImplMethodDesc* pMD) MetaSig sig(pMD); - BYTE callConv = sig.GetCallingConventionInfo(); + unsigned callConv = sig.GetCallingConventionInfo(); if (callConv != (IMAGE_CEE_CS_CALLCONV_HASTHIS | IMAGE_CEE_CS_CALLCONV_DEFAULT)) COMPlusThrow(kInvalidProgramException); diff --git a/src/coreclr/vm/commodule.cpp b/src/coreclr/vm/commodule.cpp index e9850d49a678e0..887788b199ad7e 100644 --- a/src/coreclr/vm/commodule.cpp +++ b/src/coreclr/vm/commodule.cpp @@ -302,7 +302,14 @@ extern "C" INT32 QCALLTYPE ModuleBuilder_GetMemberRefOfMethodInfo(QCall::ModuleH COMPlusThrow(kNotSupportedException); } - if (pMeth->GetMethodTable()->GetModule() == pModule) + // TODO: (async) revisit and examine if this needs to be supported somehow + if (pMeth->IsAsyncVariantMethod()) + { + _ASSERTE(!"Async variants should be hidden from reflection."); + COMPlusThrow(kNotSupportedException); + } + + if ((pMeth->GetMethodTable()->GetModule() == pModule)) { // If the passed in method is defined in the same module, just return the MethodDef token memberRefE = pMeth->GetMemberDef(); diff --git a/src/coreclr/vm/commtmemberinfomap.cpp b/src/coreclr/vm/commtmemberinfomap.cpp index df96c0f44f74d5..5bae713c57549b 100644 --- a/src/coreclr/vm/commtmemberinfomap.cpp +++ b/src/coreclr/vm/commtmemberinfomap.cpp @@ -688,6 +688,10 @@ void ComMTMemberInfoMap::GetMethodPropsForMeth( // Generally don't munge function into a getter. rProps[ix].bFunction2Getter = FALSE; + // TODO: (async) revisit and examine if this needs to be supported somehow + if (pMeth->IsAsyncMethod()) + ThrowHR(COR_E_NOTSUPPORTED); + // See if there is property information for this member. hr = pMeth->GetMDImport()->GetPropertyInfoForMethodDef(pMeth->GetMemberDef(), &pd, &pPropName, &uSemantic); IfFailThrow(hr); @@ -1604,6 +1608,11 @@ void ComMTMemberInfoMap::PopulateMemberHashtable() // We are dealing with a method. MethodDesc *pMD = pProps->pMeth; + // TODO: (async) revisit and examine if this needs to be supported somehow + if (pMD->IsAsyncMethod()) + { + ThrowHR(COR_E_NOTSUPPORTED); // Probably this isn't right, and instead should be a skip, but a throw makes it easier to find if this is wrong + } EEModuleTokenPair Key(pMD->GetMemberDef(), pMD->GetModule()); m_TokenToComMTMethodPropsMap.InsertValue(&Key, (HashDatum)pProps); } diff --git a/src/coreclr/vm/comtoclrcall.cpp b/src/coreclr/vm/comtoclrcall.cpp index 0f51af428e8b04..547738f37c6338 100644 --- a/src/coreclr/vm/comtoclrcall.cpp +++ b/src/coreclr/vm/comtoclrcall.cpp @@ -986,6 +986,9 @@ void ComCallMethodDesc::InitNativeInfo() MethodTable * pMT = pMD->GetMethodTable(); IMDInternalImport * pInternalImport = pMT->GetMDImport(); + // TODO: (async) revisit and examine if this needs to be supported somehow + if (pMD->IsAsyncMethod()) + ThrowHR(COR_E_NOTSUPPORTED); mdMethodDef md = pMD->GetMemberDef(); diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp index d039226c328cad..93992773a4a387 100644 --- a/src/coreclr/vm/comutilnative.cpp +++ b/src/coreclr/vm/comutilnative.cpp @@ -1851,6 +1851,14 @@ FCIMPL1(MethodTable*, MethodTableNative::InstantiationArg0, MethodTable* mt); } FCIMPLEND +FCIMPL1(OBJECTHANDLE, MethodTableNative::GetLoaderAllocatorHandle, MethodTable *mt) +{ + FCALL_CONTRACT; + + return mt->GetLoaderAllocatorObjectHandle(); +} +FCIMPLEND + extern "C" BOOL QCALLTYPE MethodTable_AreTypesEquivalent(MethodTable* mta, MethodTable* mtb) { QCALL_CONTRACT; diff --git a/src/coreclr/vm/comutilnative.h b/src/coreclr/vm/comutilnative.h index 1f5fda9ed02ccc..8f229352b439a4 100644 --- a/src/coreclr/vm/comutilnative.h +++ b/src/coreclr/vm/comutilnative.h @@ -264,6 +264,7 @@ class MethodTableNative { static FCDECL1(CorElementType, GetPrimitiveCorElementType, MethodTable* mt); static FCDECL2(MethodTable*, GetMethodTableMatchingParentClass, MethodTable* mt, MethodTable* parent); static FCDECL1(MethodTable*, InstantiationArg0, MethodTable* mt); + static FCDECL1(OBJECTHANDLE, GetLoaderAllocatorHandle, MethodTable* mt); }; extern "C" BOOL QCALLTYPE MethodTable_AreTypesEquivalent(MethodTable* mta, MethodTable* mtb); diff --git a/src/coreclr/vm/corelib.cpp b/src/coreclr/vm/corelib.cpp index c271a057d12d68..1e28694d8c5935 100644 --- a/src/coreclr/vm/corelib.cpp +++ b/src/coreclr/vm/corelib.cpp @@ -157,6 +157,11 @@ enum _gsigc { const BYTE gsige_IM_ ## varname[] = { (BYTE) -gsigl_IM_ ## varname, \ IMAGE_CEE_CS_CALLCONV_HASTHIS, gsigc_IM_ ## varname, retval args }; +#define _GM(varname, conv, n, args, retval) extern const BYTE gsige_GM_ ## varname[]; \ + HardCodedMetaSig gsig_GM_ ## varname = { gsige_GM_ ## varname }; \ + const BYTE gsige_GM_ ## varname[] = { (BYTE) -gsigl_GM_ ## varname, \ + conv | IMAGE_CEE_CS_CALLCONV_GENERIC, n, gsigc_GM_ ## varname, retval args }; + #define _Fld(varname, val) extern const BYTE gsige_Fld_ ## varname[]; \ HardCodedMetaSig gsig_Fld_ ## varname = { gsige_Fld_ ## varname }; \ const BYTE gsige_Fld_ ## varname[] = { (BYTE) -gsigl_Fld_ ## varname, \ @@ -166,6 +171,7 @@ enum _gsigc { #undef _SM #undef _IM +#undef _GM #undef _Fld #ifdef _DEBUG diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 256c0e4dee4762..fe86ea43697e35 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -343,6 +343,32 @@ DEFINE_METHOD(TYPE_INIT_EXCEPTION, STR_EX_CTOR, .ctor, DEFINE_CLASS(THREAD_START_EXCEPTION,Threading, ThreadStartException) DEFINE_METHOD(THREAD_START_EXCEPTION,EX_CTOR, .ctor, IM_Exception_RetVoid) +DEFINE_CLASS(VALUETASK_1, Tasks, ValueTask`1) + +DEFINE_CLASS(VALUETASK, Tasks, ValueTask) +DEFINE_METHOD(VALUETASK, FROM_EXCEPTION, FromException, SM_Exception_RetValueTask) +DEFINE_METHOD(VALUETASK, FROM_EXCEPTION_1, FromException, GM_Exception_RetValueTaskOfT) +DEFINE_METHOD(VALUETASK, FROM_RESULT_T, FromResult, GM_T_RetValueTaskOfT) +DEFINE_METHOD(VALUETASK, GET_COMPLETED_TASK, get_CompletedTask, SM_RetValueTask) + +DEFINE_CLASS(TASK_1, Tasks, Task`1) +DEFINE_METHOD(TASK_1, GET_AWAITER, GetAwaiter, NoSig) + +DEFINE_CLASS(TASK, Tasks, Task) +DEFINE_METHOD(TASK, FROM_EXCEPTION, FromException, SM_Exception_RetTask) +DEFINE_METHOD(TASK, FROM_EXCEPTION_1, FromException, GM_Exception_RetTaskOfT) +DEFINE_METHOD(TASK, FROM_RESULT_T, FromResult, GM_T_RetTaskOfT) +DEFINE_METHOD(TASK, GET_COMPLETED_TASK, get_CompletedTask, SM_RetTask) +DEFINE_METHOD(TASK, GET_AWAITER, GetAwaiter, NoSig) + +DEFINE_CLASS(TASK_AWAITER_1, CompilerServices, TaskAwaiter`1) +DEFINE_METHOD(TASK_AWAITER_1, GET_ISCOMPLETED, get_IsCompleted, NoSig) +DEFINE_METHOD(TASK_AWAITER_1, GET_RESULT, GetResult, NoSig) + +DEFINE_CLASS(TASK_AWAITER, CompilerServices, TaskAwaiter) +DEFINE_METHOD(TASK_AWAITER, GET_ISCOMPLETED, get_IsCompleted, NoSig) +DEFINE_METHOD(TASK_AWAITER, GET_RESULT, GetResult, NoSig) + DEFINE_CLASS(TYPE_HANDLE, System, RuntimeTypeHandle) DEFINE_CLASS(RT_TYPE_HANDLE, System, RuntimeTypeHandle) DEFINE_METHOD(RT_TYPE_HANDLE, PVOID_CTOR, .ctor, IM_RuntimeType_RetVoid) @@ -651,6 +677,10 @@ DEFINE_CLASS(RESOURCE_MANAGER, Resources, ResourceManager) DEFINE_CLASS(RTFIELD, Reflection, RtFieldInfo) DEFINE_METHOD(RTFIELD, GET_FIELDESC, GetFieldDesc, IM_RetIntPtr) +DEFINE_CLASS(EXECUTIONANDSYNCBLOCKSTORE, CompilerServices, ExecutionAndSyncBlockStore) +DEFINE_METHOD(EXECUTIONANDSYNCBLOCKSTORE, PUSH, Push, NoSig) +DEFINE_METHOD(EXECUTIONANDSYNCBLOCKSTORE, POP, Pop, NoSig) + DEFINE_CLASS(RUNTIME_HELPERS, CompilerServices, RuntimeHelpers) DEFINE_METHOD(RUNTIME_HELPERS, IS_BITWISE_EQUATABLE, IsBitwiseEquatable, NoSig) DEFINE_METHOD(RUNTIME_HELPERS, GET_RAW_DATA, GetRawData, NoSig) @@ -664,6 +694,17 @@ DEFINE_METHOD(RUNTIME_HELPERS, DISPATCH_TAILCALLS, DispatchTailCalls, DEFINE_METHOD(RUNTIME_HELPERS, COPY_CONSTRUCT, CopyConstruct, NoSig) #endif // FEATURE_IJW +DEFINE_CLASS(ASYNC_HELPERS, CompilerServices, AsyncHelpers) +DEFINE_METHOD(ASYNC_HELPERS, ALLOC_CONTINUATION, AllocContinuation, NoSig) +DEFINE_METHOD(ASYNC_HELPERS, ALLOC_CONTINUATION_METHOD, AllocContinuationMethod, NoSig) +DEFINE_METHOD(ASYNC_HELPERS, ALLOC_CONTINUATION_CLASS, AllocContinuationClass, NoSig) +DEFINE_METHOD(ASYNC_HELPERS, ALLOC_CONTINUATION_RESULT_BOX, AllocContinuationResultBox, SM_VoidPtr_RetObj) +DEFINE_METHOD(ASYNC_HELPERS, FINALIZE_TASK_RETURNING_THUNK, FinalizeTaskReturningThunk, SM_Continuation_RetTask) +DEFINE_METHOD(ASYNC_HELPERS, FINALIZE_TASK_RETURNING_THUNK_1, FinalizeTaskReturningThunk, GM_Continuation_RetTaskOfT) +DEFINE_METHOD(ASYNC_HELPERS, FINALIZE_VALUETASK_RETURNING_THUNK, FinalizeValueTaskReturningThunk, SM_Continuation_RetValueTask) +DEFINE_METHOD(ASYNC_HELPERS, FINALIZE_VALUETASK_RETURNING_THUNK_1, FinalizeValueTaskReturningThunk, GM_Continuation_RetValueTaskOfT) +DEFINE_METHOD(ASYNC_HELPERS, UNSAFE_AWAIT_AWAITER_1, UnsafeAwaitAwaiter, GM_T_RetVoid) + DEFINE_CLASS(SPAN_HELPERS, System, SpanHelpers) DEFINE_METHOD(SPAN_HELPERS, MEMSET, Fill, SM_RefByte_Byte_UIntPtr_RetVoid) DEFINE_METHOD(SPAN_HELPERS, MEMZERO, ClearWithoutReferences, SM_RefByte_UIntPtr_RetVoid) @@ -765,6 +806,14 @@ DEFINE_CLASS_U(CompilerServices, TailCallTls, TailCallTls) DEFINE_FIELD_U(Frame, TailCallTls, m_frame) DEFINE_FIELD_U(ArgBuffer, TailCallTls, m_argBuffer) +DEFINE_CLASS(CONTINUATION, CompilerServices, Continuation) +DEFINE_FIELD(CONTINUATION, NEXT, Next) +DEFINE_FIELD(CONTINUATION, RESUME, Resume) +DEFINE_FIELD(CONTINUATION, STATE, State) +DEFINE_FIELD(CONTINUATION, FLAGS, Flags) +DEFINE_FIELD(CONTINUATION, DATA, Data) +DEFINE_FIELD(CONTINUATION, GCDATA, GCData) + DEFINE_CLASS(RUNTIME_WRAPPED_EXCEPTION, CompilerServices, RuntimeWrappedException) DEFINE_METHOD(RUNTIME_WRAPPED_EXCEPTION, OBJ_CTOR, .ctor, IM_Obj_RetVoid) DEFINE_FIELD(RUNTIME_WRAPPED_EXCEPTION, WRAPPED_EXCEPTION, _wrappedException) @@ -981,6 +1030,7 @@ DEFINE_METHOD(STUBHELPERS, VALIDATE_BYREF, Validate DEFINE_METHOD(STUBHELPERS, GET_STUB_CONTEXT, GetStubContext, SM_RetIntPtr) DEFINE_METHOD(STUBHELPERS, LOG_PINNED_ARGUMENT, LogPinnedArgument, SM_IntPtr_IntPtr_RetVoid) DEFINE_METHOD(STUBHELPERS, NEXT_CALL_RETURN_ADDRESS, NextCallReturnAddress, SM_RetIntPtr) +DEFINE_METHOD(STUBHELPERS, ASYNC_CALL_CONTINUATION, AsyncCallContinuation, SM_RetContinuation) DEFINE_METHOD(STUBHELPERS, SAFE_HANDLE_ADD_REF, SafeHandleAddRef, SM_SafeHandle_RefBool_RetIntPtr) DEFINE_METHOD(STUBHELPERS, SAFE_HANDLE_RELEASE, SafeHandleRelease, SM_SafeHandle_RetVoid) diff --git a/src/coreclr/vm/dispatchinfo.cpp b/src/coreclr/vm/dispatchinfo.cpp index bade7f6ce88636..9d9db8ee1d5f19 100644 --- a/src/coreclr/vm/dispatchinfo.cpp +++ b/src/coreclr/vm/dispatchinfo.cpp @@ -448,7 +448,13 @@ ComMTMethodProps * DispatchMemberInfo::GetMemberProps(OBJECTREF MemberInfoObj, C ARG_SLOT GetMethodHandleArg = ObjToArgSlot(MemberInfoObj); MethodDesc* pMeth = (MethodDesc*) getMethodHandle.Call_RetLPVOID(&GetMethodHandleArg); if (pMeth) + { + // TODO: (async) revisit and examine if this needs to be supported somehow + if (pMeth->IsAsyncMethod()) + ThrowHR(COR_E_NOTSUPPORTED); + pMemberProps = pMemberMap->GetMethodProps(pMeth->GetMemberDef(), pMeth->GetModule()); + } } else if (CoreLibBinder::IsClass(pMemberInfoClass, CLASS__RT_FIELD_INFO)) { @@ -828,6 +834,10 @@ void DispatchMemberInfo::SetUpMethodMarshalerInfo(MethodDesc *pMD, BOOL bReturnV GCX_PREEMP(); + // TODO: (async) revisit and examine if this needs to be supported somehow + if (pMD->IsAsyncMethod()) + ThrowHR(COR_E_NOTSUPPORTED); + MetaSig msig(pMD); LPCSTR szName; USHORT usSequence; @@ -2578,6 +2588,12 @@ bool DispatchInfo::IsPropertyAccessorVisible(bool fIsSetter, OBJECTREF* pMemberI // Check to see if the new method is a property accessor. mdToken tkMember = mdTokenNil; + // TODO: (async) revisit and examine if this needs to be supported somehow + if (pMDForProperty->IsAsyncVariantMethod()) + { + return false; + } + if (pMDForProperty->GetMDImport()->GetPropertyInfoForMethodDef(pMDForProperty->GetMemberDef(), &tkMember, NULL, NULL) == S_OK) { if (IsMemberVisibleFromCom(pMDForProperty->GetMethodTable(), tkMember, pMDForProperty->GetMemberDef())) diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp index 858c42329c0720..4c053fa31d48d5 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -105,6 +105,9 @@ StubSigDesc::StubSigDesc(MethodDesc *pMD) m_sig = pMD->GetSignature(); m_pModule = pMD->GetModule(); // Used for token resolution. + // TODO: (async) revisit and examine if this needs to be supported somehow + _ASSERTE(!pMD->IsAsyncMethod()); + m_tkMethodDef = pMD->GetMemberDef(); SigTypeContext::InitTypeContext(pMD, &m_typeContext); m_pMetadataModule = pMD->GetModule(); @@ -132,6 +135,7 @@ StubSigDesc::StubSigDesc(MethodDesc* pMD, const Signature& sig, Module* pModule, if (pMD != NULL) { + _ASSERTE(!pMD->IsAsyncMethod()); m_tkMethodDef = pMD->GetMemberDef(); SigTypeContext::InitTypeContext(pMD, &m_typeContext); m_pMetadataModule = pMD->GetModule(); @@ -1086,7 +1090,11 @@ class ILStubState : public StubState DWORD dwToken = 0; if (pTargetMD) + { + // TODO: (async) revisit and examine if this needs to be supported somehow + _ASSERTE(!pTargetMD->IsAsyncVariantMethod()); dwToken = pTargetMD->GetMemberDef(); + } // @@ -2710,6 +2718,10 @@ void PInvokeStaticSigInfo::DllImportInit( IMDInternalImport *pInternalImport = pMD->GetMDImport(); CorPinvokeMap mappingFlags = pmMaxValue; mdModuleRef modref = mdModuleRefNil; + // TODO: (async) revisit and examine if this needs to be supported somehow + if (pMD->IsAsyncMethod()) + ThrowHR(COR_E_NOTSUPPORTED); + if (FAILED(pInternalImport->GetPinvokeMap(pMD->GetMemberDef(), (DWORD*)&mappingFlags, ppEntryPointName, &modref))) { InitCallConv(CallConvWinApiSentinel, pMD); @@ -2987,6 +2999,10 @@ namespace CorInfoCallConvExtension callConvLocal; IMDInternalImport* pInternalImport = pMD->GetMDImport(); CorPinvokeMap mappingFlags = pmMaxValue; + // TODO: (async) revisit and examine if this needs to be supported somehow + if (pMD->IsAsyncMethod()) + ThrowHR(COR_E_NOTSUPPORTED); + HRESULT hr = pInternalImport->GetPinvokeMap(pMD->GetMemberDef(), (DWORD*)&mappingFlags, NULL /*pszImportName*/, NULL /*pmrImportDLL*/); if (FAILED(hr)) return false; @@ -3247,6 +3263,10 @@ BOOL NDirect::MarshalingRequired( mdMethodDef methodToken = mdMethodDefNil; if (pMD != NULL) { + // TODO: (async) revisit and examine if this needs to be supported somehow + if (pMD->IsAsyncMethod()) + ThrowHR(COR_E_NOTSUPPORTED); + methodToken = pMD->GetMemberDef(); } CollateParamTokens(pMDImport, methodToken, numArgs - 1, pParamTokenArray); @@ -6040,6 +6060,10 @@ PCODE GetILStubForCalli(VASigCookie *pVASigCookie, MethodDesc *pMD) { PInvokeStaticSigInfo sigInfo(pMD); + // TODO: (async) revisit and examine if this needs to be supported somehow + if (pMD->IsAsyncMethod()) + ThrowHR(COR_E_NOTSUPPORTED); + md = pMD->GetMemberDef(); nlFlags = sigInfo.GetLinkFlags(); nlType = sigInfo.GetCharSet(); diff --git a/src/coreclr/vm/dllimport.h b/src/coreclr/vm/dllimport.h index 4f6b791748ea93..b53222626757c8 100644 --- a/src/coreclr/vm/dllimport.h +++ b/src/coreclr/vm/dllimport.h @@ -204,6 +204,7 @@ enum ILStubTypes ILSTUB_STATIC_VIRTUAL_DISPATCH_STUB = 0x8000000A, ILSTUB_DELEGATE_INVOKE_METHOD = 0x8000000B, ILSTUB_DELEGATE_SHUFFLE_THUNK = 0x8000000C, + ILSTUB_ASYNC_RESUME = 0x8000000D, }; #ifdef FEATURE_COMINTEROP @@ -242,6 +243,7 @@ inline bool SF_IsInstantiatingStub (DWORD dwStubFlags) { LIMITED_METHOD_CON inline bool SF_IsTailCallStoreArgsStub (DWORD dwStubFlags) { LIMITED_METHOD_CONTRACT; return (dwStubFlags == ILSTUB_TAILCALL_STOREARGS); } inline bool SF_IsTailCallCallTargetStub (DWORD dwStubFlags) { LIMITED_METHOD_CONTRACT; return (dwStubFlags == ILSTUB_TAILCALL_CALLTARGET); } inline bool SF_IsDelegateShuffleThunk (DWORD dwStubFlags) { LIMITED_METHOD_CONTRACT; return (dwStubFlags == ILSTUB_DELEGATE_SHUFFLE_THUNK); } +inline bool SF_IsAsyncResumeStub (DWORD dwStubFlags) { LIMITED_METHOD_CONTRACT; return (dwStubFlags == ILSTUB_ASYNC_RESUME); } inline bool SF_IsCOMStub (DWORD dwStubFlags) { LIMITED_METHOD_CONTRACT; return COM_ONLY(dwStubFlags < NDIRECTSTUB_FL_INVALID && 0 != (dwStubFlags & NDIRECTSTUB_FL_COM)); } inline bool SF_IsCOMLateBoundStub (DWORD dwStubFlags) { LIMITED_METHOD_CONTRACT; return COM_ONLY(dwStubFlags < NDIRECTSTUB_FL_INVALID && 0 != (dwStubFlags & NDIRECTSTUB_FL_COMLATEBOUND)); } @@ -263,6 +265,11 @@ inline bool SF_IsSharedStub(DWORD dwStubFlags) return false; } + if (SF_IsAsyncResumeStub(dwStubFlags)) + { + return false; + } + return true; } diff --git a/src/coreclr/vm/dynamicmethod.cpp b/src/coreclr/vm/dynamicmethod.cpp index 2fc4698f2b3b4e..4b9df31b3e21e7 100644 --- a/src/coreclr/vm/dynamicmethod.cpp +++ b/src/coreclr/vm/dynamicmethod.cpp @@ -164,7 +164,7 @@ void DynamicMethodTable::AddMethodsToList() // allocate as many chunks as needed to hold the methods // MethodDescChunk* pChunk = MethodDescChunk::CreateChunk(pHeap, 0 /* one chunk of maximum size */, - mcDynamic, TRUE /* fNonVtableSlot */, TRUE /* fNativeCodeSlot */, m_pMethodTable, &amt); + mcDynamic, TRUE /* fNonVtableSlot */, TRUE /* fNativeCodeSlot */, FALSE /* HasAsyncMethodData */, m_pMethodTable, &amt); if (m_DynamicMethodList) RETURN; int methodCount = pChunk->GetCount(); diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 9fcd6082b5edc6..7fa6ae288bb697 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -345,6 +345,7 @@ FCFuncStart(gMethodTableFuncs) FCFuncElement("GetPrimitiveCorElementType", MethodTableNative::GetPrimitiveCorElementType) FCFuncElement("GetMethodTableMatchingParentClass", MethodTableNative::GetMethodTableMatchingParentClass) FCFuncElement("InstantiationArg0", MethodTableNative::InstantiationArg0) + FCFuncElement("GetLoaderAllocatorHandle", MethodTableNative::GetLoaderAllocatorHandle) FCFuncEnd() FCFuncStart(gStubHelperFuncs) diff --git a/src/coreclr/vm/encee.cpp b/src/coreclr/vm/encee.cpp index 10c61a190250e5..80ff5d5da4e429 100644 --- a/src/coreclr/vm/encee.cpp +++ b/src/coreclr/vm/encee.cpp @@ -344,6 +344,13 @@ HRESULT EditAndContinueModule::UpdateMethod(MethodDesc *pMethod) } CONTRACTL_END; + if (pMethod->HasAsyncMethodData()) + { + // TODO: (async) revisit and examine if this can be supported + LOG((LF_ENC, LL_INFO100, "**Error** EnC for Async methods is NYI")); + return E_FAIL; + } + // Notify the debugger of the update if (CORDebuggerAttached()) { diff --git a/src/coreclr/vm/frames.cpp b/src/coreclr/vm/frames.cpp index cb81a38449b456..8a2d4605eaa1aa 100644 --- a/src/coreclr/vm/frames.cpp +++ b/src/coreclr/vm/frames.cpp @@ -1357,7 +1357,8 @@ void HijackFrame::GcScanRoots_Impl(promote_func *fn, ScanContext* sc) { LIMITED_METHOD_CONTRACT; - ReturnKind returnKind = m_Thread->GetHijackReturnKind(); + bool hasAsyncRet; + ReturnKind returnKind = m_Thread->GetHijackReturnKind(&hasAsyncRet); _ASSERTE(IsValidReturnKind(returnKind)); int regNo = 0; @@ -1395,6 +1396,15 @@ void HijackFrame::GcScanRoots_Impl(promote_func *fn, ScanContext* sc) regNo++; } while (moreRegisters); + + if (hasAsyncRet) + { + PTR_PTR_Object objPtr = dac_cast(&m_Args->AsyncRet); + LOG((LF_GC, INFO3, "Hijack Frame Promoting Async Continuation Return" FMT_ADDR "to", + DBG_ADDR(OBJECTREF_TO_UNCHECKED_OBJECTREF(*objPtr)))); + (*fn)(objPtr, sc, CHECK_APP_DOMAIN); + LOG((LF_GC, INFO3, FMT_ADDR "\n", DBG_ADDR(OBJECTREF_TO_UNCHECKED_OBJECTREF(*objPtr)))); + } } #endif // TARGET_X86 #endif // FEATURE_HIJACK @@ -1506,6 +1516,9 @@ void TransitionFrame::PromoteCallerStack(promote_func* fn, ScanContext* sc) if (pFunction->RequiresInstArg() && !SuppressParamTypeArg()) msig.SetHasParamTypeArg(); + if (pFunction->IsAsyncMethod()) + msig.SetIsAsyncCall(); + PromoteCallerStackHelper (fn, sc, pFunction, &msig); } else @@ -2339,9 +2352,17 @@ void ComputeCallRefMap(MethodDesc* pMD, // See code:getMethodSigInternal // assert(!isDispatchCell || !pMD->RequiresInstArg() || pMD->GetMethodTable()->IsInterface()); - if (pMD->RequiresInstArg() && !isDispatchCell) + if (!isDispatchCell) { - msig.SetHasParamTypeArg(); + if (pMD->RequiresInstArg()) + { + msig.SetHasParamTypeArg(); + } + + if (pMD->IsAsyncMethod()) + { + msig.SetIsAsyncCall(); + } } ArgIterator argit(&msig); diff --git a/src/coreclr/vm/gcenv.ee.common.cpp b/src/coreclr/vm/gcenv.ee.common.cpp index 65ad60e67df6cc..21723724cc5f02 100644 --- a/src/coreclr/vm/gcenv.ee.common.cpp +++ b/src/coreclr/vm/gcenv.ee.common.cpp @@ -441,7 +441,9 @@ StackWalkAction GcStackCrawlCallBack(CrawlFrame* pCF, VOID* pData) if (paramContextType == GENERIC_PARAM_CONTEXT_METHODDESC) { MethodDesc *pMDReal = dac_cast(pCF->GetParamTypeArg()); - _ASSERTE((pMDReal != NULL) || !pCF->IsFrameless()); + // Async methods may be in a state when the context is not yet restored from continuation. + // We will allow that. The context is reachable via continuation in such case. + _ASSERTE((pMDReal != NULL) || !pCF->IsFrameless() || pMD->IsAsyncMethod()); if (pMDReal != NULL) { GcReportLoaderAllocator(gcctx->f, gcctx->sc, pMDReal->GetLoaderAllocator()); @@ -450,7 +452,9 @@ StackWalkAction GcStackCrawlCallBack(CrawlFrame* pCF, VOID* pData) else if (paramContextType == GENERIC_PARAM_CONTEXT_METHODTABLE) { MethodTable *pMTReal = dac_cast(pCF->GetParamTypeArg()); - _ASSERTE((pMTReal != NULL) || !pCF->IsFrameless()); + // Async methods may be in a state when the context is not yet restored from continuation. + // We will allow that. The context is reachable via continuation in such case. + _ASSERTE((pMTReal != NULL) || !pCF->IsFrameless() || pMD->IsAsyncMethod()); if (pMTReal != NULL) { GcReportLoaderAllocator(gcctx->f, gcctx->sc, pMTReal->GetLoaderAllocator()); diff --git a/src/coreclr/vm/genericdict.cpp b/src/coreclr/vm/genericdict.cpp index eea8def4f4ed9c..0ad8a2926c7771 100644 --- a/src/coreclr/vm/genericdict.cpp +++ b/src/coreclr/vm/genericdict.cpp @@ -889,6 +889,7 @@ Dictionary::PopulateEntry( uint32_t methodSlot = -1; BOOL fRequiresDispatchStub = 0; + BOOL isAsyncVariant = 0; if (isReadyToRunModule) { @@ -900,6 +901,7 @@ Dictionary::PopulateEntry( isInstantiatingStub = ((methodFlags & ENCODE_METHOD_SIG_InstantiatingStub) != 0) || (kind == MethodEntrySlot); isUnboxingStub = ((methodFlags & ENCODE_METHOD_SIG_UnboxingStub) != 0); fMethodNeedsInstantiation = ((methodFlags & ENCODE_METHOD_SIG_MethodInstantiation) != 0); + isAsyncVariant = ((methodFlags & ENCODE_METHOD_SIG_AsyncVariant) != 0); if (methodFlags & ENCODE_METHOD_SIG_OwnerType) { @@ -950,6 +952,10 @@ Dictionary::PopulateEntry( _ASSERTE(pZapSigContext->pInfoModule->IsFullModule()); pMethod = MemberLoader::GetMethodDescFromMethodDef(static_cast(pZapSigContext->pInfoModule), TokenFromRid(rid, mdtMethodDef), FALSE); } + if (isAsyncVariant) + { + pMethod = pMethod->GetAsyncOtherVariant(); + } } if (ownerType.IsNull()) @@ -993,6 +999,7 @@ Dictionary::PopulateEntry( isInstantiatingStub = ((methodFlags & ENCODE_METHOD_SIG_InstantiatingStub) != 0); isUnboxingStub = ((methodFlags & ENCODE_METHOD_SIG_UnboxingStub) != 0); fMethodNeedsInstantiation = ((methodFlags & ENCODE_METHOD_SIG_MethodInstantiation) != 0); + isAsyncVariant = ((methodFlags & ENCODE_METHOD_SIG_AsyncVariant) != 0); if ((methodFlags & ENCODE_METHOD_SIG_SlotInsteadOfToken) != 0) { @@ -1034,6 +1041,12 @@ Dictionary::PopulateEntry( // The RID map should have been filled out if we fully loaded the class pMethod = pMethodDefMT->GetModule()->LookupMethodDef(token); + + if (isAsyncVariant) + { + pMethod = pMethod->GetAsyncOtherVariant(); + } + _ASSERTE(pMethod != NULL); pMethod->CheckRestore(); } @@ -1063,6 +1076,8 @@ Dictionary::PopulateEntry( break; } + _ASSERTE((!!isAsyncVariant) == pMethod->IsAsyncVariantMethod()); + Instantiation inst; // Instantiate the method if needed, or create a stub to a static method in a generic class. @@ -1107,6 +1122,8 @@ Dictionary::PopulateEntry( inst, (!isInstantiatingStub && !isUnboxingStub)); + _ASSERTE((!!isAsyncVariant) == pMethod->IsAsyncVariantMethod()); + if (kind == ConstrainedMethodEntrySlot) { if (isReadyToRunModule) diff --git a/src/coreclr/vm/genmeth.cpp b/src/coreclr/vm/genmeth.cpp index af6c53ee9d9bc7..196a0b405dc27a 100644 --- a/src/coreclr/vm/genmeth.cpp +++ b/src/coreclr/vm/genmeth.cpp @@ -91,6 +91,7 @@ static MethodDesc* CreateMethodDesc(LoaderAllocator *pAllocator, classification, TRUE /* fNonVtableSlot*/, fNativeCodeSlot, + pTemplateMD->HasAsyncMethodData(), pMT, pamTracker, pLoaderModule); @@ -118,6 +119,10 @@ static MethodDesc* CreateMethodDesc(LoaderAllocator *pAllocator, { pMD->SetIsIntrinsic(); } + if (pTemplateMD->HasAsyncMethodData()) + { + pMD->SetHasAsyncMethodData(); + } #ifdef FEATURE_METADATA_UPDATER if (pTemplateMD->IsEnCAddedMethod()) @@ -129,6 +134,11 @@ static MethodDesc* CreateMethodDesc(LoaderAllocator *pAllocator, pMD->SetMemberDef(token); pMD->SetSlot(pTemplateMD->GetSlot()); + if (pTemplateMD->HasAsyncMethodData()) + { + *pMD->GetAddrOfAsyncMethodData() = pTemplateMD->GetAsyncMethodData(); + } + #ifdef _DEBUG // more info here pMD->m_pszDebugMethodSignature = ""; @@ -236,6 +246,7 @@ static MethodDesc * FindTightlyBoundWrappedMethodDesc_DEBUG(MethodDesc * pMD) mdMethodDef methodDef = pMD->GetMemberDef(); Module *pModule = pMD->GetModule(); + bool isAsyncVariantMethod = pMD->IsAsyncVariantMethod(); MethodTable::MethodIterator it(pMD->GetCanonicalMethodTable()); it.MoveToEnd(); @@ -246,7 +257,8 @@ static MethodDesc * FindTightlyBoundWrappedMethodDesc_DEBUG(MethodDesc * pMD) if (pCurMethod && !pCurMethod->IsUnboxingStub()) { if ((pCurMethod->GetMemberDef() == methodDef) && - (pCurMethod->GetModule() == pModule)) + (pCurMethod->GetModule() == pModule) && + (pCurMethod->IsAsyncVariantMethod() == isAsyncVariantMethod)) { return pCurMethod; } @@ -274,6 +286,7 @@ static MethodDesc * FindTightlyBoundUnboxingStub_DEBUG(MethodDesc * pMD) mdMethodDef methodDef = pMD->GetMemberDef(); Module *pModule = pMD->GetModule(); + bool isAsyncVariantMethod = pMD->IsAsyncVariantMethod(); MethodTable::MethodIterator it(pMD->GetCanonicalMethodTable()); it.MoveToEnd(); @@ -282,7 +295,8 @@ static MethodDesc * FindTightlyBoundUnboxingStub_DEBUG(MethodDesc * pMD) MethodDesc* pCurMethod = it.GetMethodDesc(); if (pCurMethod && pCurMethod->IsUnboxingStub()) { if ((pCurMethod->GetMemberDef() == methodDef) && - (pCurMethod->GetModule() == pModule)) { + (pCurMethod->GetModule() == pModule) && + (pCurMethod->IsAsyncVariantMethod() == isAsyncVariantMethod)) { return pCurMethod; } } @@ -348,7 +362,8 @@ InstantiatedMethodDesc::NewInstantiatedMethodDesc(MethodTable *pExactMT, pNewMD = FindLoadedInstantiatedMethodDesc(pExactMT, pGenericMDescInRepMT->GetMemberDef(), methodInst, - getWrappedCode); + getWrappedCode, + pGenericMDescInRepMT->IsAsyncVariantMethod()); // Crst goes out of scope here // We don't need to hold the crst while we build the MethodDesc, but we reacquire it later @@ -468,7 +483,8 @@ InstantiatedMethodDesc::NewInstantiatedMethodDesc(MethodTable *pExactMT, InstantiatedMethodDesc *pOldMD = FindLoadedInstantiatedMethodDesc(pExactMT, pGenericMDescInRepMT->GetMemberDef(), methodInst, - getWrappedCode); + getWrappedCode, + pGenericMDescInRepMT->IsAsyncVariantMethod()); if (pOldMD == NULL) { @@ -542,7 +558,8 @@ InstantiatedMethodDesc::FindOrCreateExactClassMethod(MethodTable *pExactMT, InstantiatedMethodDesc *pInstMD = FindLoadedInstantiatedMethodDesc(pExactMT, pCanonicalMD->GetMemberDef(), Instantiation(), - FALSE); + FALSE, + pCanonicalMD->IsAsyncVariantMethod()); if (pInstMD == NULL) { @@ -564,7 +581,8 @@ InstantiatedMethodDesc* InstantiatedMethodDesc::FindLoadedInstantiatedMethodDesc(MethodTable *pExactOrRepMT, mdMethodDef methodDef, Instantiation methodInst, - BOOL getWrappedCode) + BOOL getWrappedCode, + BOOL asyncThunk) { CONTRACT(InstantiatedMethodDesc *) { @@ -598,7 +616,8 @@ InstantiatedMethodDesc::FindLoadedInstantiatedMethodDesc(MethodTable *pExactOrRe methodDef, FALSE /* not forceBoxedEntryPoint */, methodInst, - getWrappedCode); + getWrappedCode, + asyncThunk); if (resultMD != NULL) RETURN((InstantiatedMethodDesc*) resultMD); @@ -723,6 +742,7 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, BOOL allowInstParam, BOOL forceRemotableMethod, BOOL allowCreate, + AsyncVariantLookup asyncVariantLookup, ClassLoadLevel level) { CONTRACT(MethodDesc*) @@ -758,7 +778,8 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, if (!pDefMD->HasClassOrMethodInstantiation() && methodInst.IsEmpty() && !forceBoxedEntryPoint && - !pDefMD->IsUnboxingStub()) + !pDefMD->IsUnboxingStub() && + asyncVariantLookup == AsyncVariantLookup::MatchingAsyncVariant) { // Make sure that pDefMD->GetMethodTable() and pExactMT are related types even // if we took the fast path. @@ -787,7 +808,7 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, COMPlusThrowHR(COR_E_TYPELOAD); } - if (pDefMD->HasClassOrMethodInstantiation() || !methodInst.IsEmpty()) + if (pDefMD->HasClassOrMethodInstantiation() || !methodInst.IsEmpty() || asyncVariantLookup == AsyncVariantLookup::AsyncOtherVariant) { // General checks related to generics: arity (if any) must match and generic method // instantiation (if any) must be well-formed. @@ -797,7 +818,7 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, COMPlusThrowHR(COR_E_BADIMAGEFORMAT); } - pMDescInCanonMT = pExactMT->GetCanonicalMethodTable()->GetParallelMethodDesc(pDefMD); + pMDescInCanonMT = pExactMT->GetCanonicalMethodTable()->GetParallelMethodDesc(pDefMD, asyncVariantLookup); if (!allowCreate && !pMDescInCanonMT->GetMethodTable()->IsFullyLoaded()) { @@ -873,7 +894,8 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, methodDef, TRUE /* forceBoxedEntryPoint */, Instantiation(), - FALSE /* no inst param */); + FALSE /* no inst param */, + pMDescInCanonMT->IsAsyncVariantMethod()); // If we didn't find it then create it... if (!pResultMD) @@ -891,7 +913,8 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, methodDef, TRUE, Instantiation(), - FALSE); + FALSE, + pMDescInCanonMT->IsAsyncVariantMethod()); if (pResultMD == NULL) { AllocMemTracker amt; @@ -937,7 +960,8 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, methodDef, TRUE, /* forceBoxedEntryPoint */ methodInst, - FALSE /* no inst param */); + FALSE /* no inst param */, + pMDescInCanonMT->IsAsyncVariantMethod()); if (!pResultMD) { @@ -954,11 +978,12 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, pExactMT, FALSE /* not Unboxing */, methodInst, - FALSE); + FALSE, FALSE, TRUE, asyncVariantLookup); _ASSERTE(pNonUnboxingStub->GetClassification() == mcInstantiated); _ASSERTE(!pNonUnboxingStub->RequiresInstArg()); _ASSERTE(!pNonUnboxingStub->IsUnboxingStub()); + _ASSERTE(pNonUnboxingStub->IsAsyncVariantMethod() == pMDescInCanonMT->IsAsyncVariantMethod()); // Enter the critical section *after* we've found or created the non-unboxing instantiating stub (else we'd have a race, // and its possible that the non-unboxing instantiating stub may be in a different loader module than pLoaderModule @@ -970,7 +995,8 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, methodDef, TRUE, /* forceBoxedEntryPoint */ methodInst, - FALSE /* no inst param */); + FALSE /* no inst param */, + pNonUnboxingStub->IsAsyncVariantMethod()); if (pResultMD == NULL) { @@ -1111,7 +1137,8 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, InstantiatedMethodDesc::FindLoadedInstantiatedMethodDesc(pExactMT->GetCanonicalMethodTable(), methodDef, Instantiation(repInst, methodInst.GetNumArgs()), - TRUE); + TRUE, + pMDescInCanonMT->IsAsyncVariantMethod()); // No - so create one. if (pInstMD == NULL) @@ -1135,7 +1162,8 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, InstantiatedMethodDesc::FindLoadedInstantiatedMethodDesc(pExactMT, methodDef, methodInst, - FALSE); + FALSE, + pMDescInCanonMT->IsAsyncVariantMethod()); // No - so create one. Go fetch the shared one first if (pInstMD == NULL) @@ -1154,6 +1182,7 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, /* allowInstParam */ TRUE, /* forceRemotableMethod */ FALSE, /* allowCreate */ TRUE, + asyncVariantLookup, /* level */ level); _ASSERTE(pWrappedMD->IsSharedByGenericInstantiations()); @@ -1174,7 +1203,8 @@ MethodDesc::FindOrCreateAssociatedMethodDesc(MethodDesc* pDefMD, InstantiatedMethodDesc::FindLoadedInstantiatedMethodDesc(pExactMT, methodDef, methodInst, - FALSE); + FALSE, + pMDescInCanonMT->IsAsyncVariantMethod()); // No - so create one. if (pInstMD == NULL) diff --git a/src/coreclr/vm/i386/asmhelpers.S b/src/coreclr/vm/i386/asmhelpers.S index 204f9c7a878f1c..ca2803e80bdf63 100644 --- a/src/coreclr/vm/i386/asmhelpers.S +++ b/src/coreclr/vm/i386/asmhelpers.S @@ -1118,6 +1118,32 @@ LEAF_ENTRY IL_Throw, _TEXT ret 4 LEAF_END IL_Throw, _TEXT +//========================================================================== +// Capture a transition block with register values and call the IL_ThrowExact +// implementation written in C. +// +// Input state: +// ECX = Pointer to exception object +//========================================================================== +LEAF_ENTRY IL_ThrowExact, _TEXT + STUB_PROLOG + + mov edx, esp + + #define STACK_ALIGN_PADDING 4 + sub esp, STACK_ALIGN_PADDING + + CHECK_STACK_ALIGNMENT + + call C_FUNC(IL_ThrowExact_x86) + + add esp, STACK_ALIGN_PADDING + #undef STACK_ALIGN_PADDING + + STUB_EPILOG + ret 4 +LEAF_END IL_ThrowExact, _TEXT + //========================================================================== // Capture a transition block with register values and call the IL_Rethrow // implementation written in C. diff --git a/src/coreclr/vm/i386/asmhelpers.asm b/src/coreclr/vm/i386/asmhelpers.asm index e64931d6405882..cbc2e21a2697fc 100644 --- a/src/coreclr/vm/i386/asmhelpers.asm +++ b/src/coreclr/vm/i386/asmhelpers.asm @@ -92,6 +92,7 @@ endif ifdef FEATURE_EH_FUNCLETS EXTERN @IL_Throw_x86@8:PROC +EXTERN @IL_ThrowExact_x86@8:PROC EXTERN @IL_Rethrow_x86@4:PROC endif ; FEATURE_EH_FUNCLETS @@ -1906,6 +1907,23 @@ FASTCALL_FUNC IL_Throw, 4 ret 4 FASTCALL_ENDFUNC IL_Throw +;========================================================================== +; Capture a transition block with register values and call the IL_ThrowExact +; implementation written in C. +; +; Input state: +; ECX = Pointer to exception object +;========================================================================== +FASTCALL_FUNC IL_ThrowExact, 4 + STUB_PROLOG + + mov edx, esp + call @IL_ThrowExact_x86@8 + + STUB_EPILOG + ret 4 +FASTCALL_ENDFUNC IL_ThrowExact + ;========================================================================== ; Capture a transition block with register values and call the IL_Rethrow ; implementation written in C. diff --git a/src/coreclr/vm/i386/cgencpu.h b/src/coreclr/vm/i386/cgencpu.h index 50ae84f4bed81d..86fda62df1550f 100644 --- a/src/coreclr/vm/i386/cgencpu.h +++ b/src/coreclr/vm/i386/cgencpu.h @@ -405,7 +405,11 @@ struct HijackArgs DWORD Esi; DWORD Ebx; DWORD Edx; - DWORD Ecx; + union + { + DWORD Ecx; + size_t AsyncRet; + }; union { DWORD Eax; diff --git a/src/coreclr/vm/ilstubcache.cpp b/src/coreclr/vm/ilstubcache.cpp index 655a6b2d169f3f..dd968b92abe64c 100644 --- a/src/coreclr/vm/ilstubcache.cpp +++ b/src/coreclr/vm/ilstubcache.cpp @@ -158,6 +158,7 @@ namespace case DynamicMethodDesc::StubTailCallCallTarget: return "IL_STUB_CallTailCallTarget"; case DynamicMethodDesc::StubVirtualStaticMethodDispatch: return "IL_STUB_VirtualStaticMethodDispatch"; case DynamicMethodDesc::StubDelegateShuffleThunk: return "IL_STUB_DelegateShuffleThunk"; + case DynamicMethodDesc::StubAsyncResume: return "IL_STUB_AsyncResume"; default: UNREACHABLE_MSG("Unknown stub type"); } @@ -183,6 +184,7 @@ MethodDesc* ILStubCache::CreateNewMethodDesc(LoaderHeap* pCreationHeap, MethodTa mcDynamic, TRUE /* fNonVtableSlot */, TRUE /* fNativeCodeSlot */, + FALSE /* HasAsyncMethodData */, pMT, pamTracker); @@ -276,6 +278,11 @@ MethodDesc* ILStubCache::CreateNewMethodDesc(LoaderHeap* pCreationHeap, MethodTa pMD->SetILStubType(DynamicMethodDesc::StubTailCallCallTarget); } else + if (SF_IsAsyncResumeStub(dwStubFlags)) + { + pMD->SetILStubType(DynamicMethodDesc::StubAsyncResume); + } + else #ifdef FEATURE_COMINTEROP if (SF_IsCOMStub(dwStubFlags)) { diff --git a/src/coreclr/vm/instmethhash.cpp b/src/coreclr/vm/instmethhash.cpp index cdb8b8617dec88..63058191523c52 100644 --- a/src/coreclr/vm/instmethhash.cpp +++ b/src/coreclr/vm/instmethhash.cpp @@ -118,7 +118,8 @@ MethodDesc* InstMethodHashTable::FindMethodDesc(TypeHandle declaringType, mdMethodDef token, BOOL unboxingStub, Instantiation inst, - BOOL getSharedNotStub) + BOOL getSharedNotStub, + bool isAsyncVariant) { CONTRACTL { @@ -160,6 +161,11 @@ MethodDesc* InstMethodHashTable::FindMethodDesc(TypeHandle declaringType, continue; // Next iteration of the for loop } + if (pMD->IsAsyncVariantMethod() != isAsyncVariant) + { + continue; + } + if (!inst.IsEmpty()) { Instantiation candidateInst = pMD->GetMethodInstantiation(); @@ -202,7 +208,7 @@ BOOL InstMethodHashTable::ContainsMethodDesc(MethodDesc* pMD) return FindMethodDesc( pMD->GetMethodTable(), pMD->GetMemberDef(), pMD->IsUnboxingStub(), - pMD->GetMethodInstantiation(), pMD->RequiresInstArg()) != NULL; + pMD->GetMethodInstantiation(), pMD->RequiresInstArg(), pMD->IsAsyncVariantMethod()) != NULL; } #endif // #ifndef DACCESS_COMPILE diff --git a/src/coreclr/vm/instmethhash.h b/src/coreclr/vm/instmethhash.h index e7844088637047..dc77d892ee63fc 100644 --- a/src/coreclr/vm/instmethhash.h +++ b/src/coreclr/vm/instmethhash.h @@ -109,7 +109,8 @@ class InstMethodHashTable : public DacEnumerableHashTable &rDef { pDeclaringMT = pProps->pMeth->GetMethodTable(); tkMb = pProps->pMeth->GetMemberDef(); + // TODO: (async) revisit and examine if this needs to be supported somehow + if (pProps->pMeth->IsAsyncMethod()) + ThrowHR(COR_E_NOTSUPPORTED); + cbCur = GetStringizedMethodDef(pDeclaringMT, tkMb, rDef, cbCur); } else @@ -2490,6 +2494,10 @@ BOOL IsMethodVisibleFromCom(MethodDesc *pMD) mdProperty pd; LPCUTF8 pPropName; ULONG uSemantic; + // TODO: (async) revisit and examine if this needs to be supported somehow + if (pMD->IsAsyncMethod()) + return false; + mdMethodDef md = pMD->GetMemberDef(); // See if there is property information for this member. diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index 0734859b1b2c26..c4f6c032050126 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -52,6 +52,7 @@ #include "onstackreplacement.h" #include "pgo.h" #include "pgo_formatprocessing.h" +#include "patchpointinfo.h" #ifndef FEATURE_EH_FUNCLETS #include "excep.h" @@ -1484,6 +1485,51 @@ HCIMPL0(void, IL_Rethrow) } HCIMPLEND +#if defined(TARGET_X86) && defined(FEATURE_EH_FUNCLETS) +EXTERN_C FCDECL1(void, IL_ThrowExact, Object* obj); +EXTERN_C HCIMPL2(void, IL_ThrowExact_x86, Object* obj, TransitionBlock* transitionBlock) +#else +HCIMPL1(void, IL_ThrowExact, Object* obj) +#endif +{ + FCALL_CONTRACT; + + /* Make no assumptions about the current machine state */ + ResetCurrentContext(); + + FC_GC_POLL_NOT_NEEDED(); // throws always open up for GC + + OBJECTREF oref = ObjectToOBJECTREF(obj); + GetThread()->GetExceptionState()->SetRaisingForeignException(); + +#ifdef FEATURE_EH_FUNCLETS + Thread *pThread = GetThread(); + + SoftwareExceptionFrame exceptionFrame; +#ifdef TARGET_X86 + exceptionFrame.UpdateContextFromTransitionBlock(transitionBlock); +#else + RtlCaptureContext(exceptionFrame.GetContext()); +#endif + exceptionFrame.InitAndLink(pThread); + + FC_CAN_TRIGGER_GC(); + DispatchManagedException(oref, exceptionFrame.GetContext()); + FC_CAN_TRIGGER_GC_END(); + UNREACHABLE(); +#else + HELPER_METHOD_FRAME_BEGIN_ATTRIB_NOPOLL(Frame::FRAME_ATTR_EXCEPTION); // Set up a frame +#if defined(_DEBUG) && defined(TARGET_X86) + __helperframe.EnsureInit(NULL); + g_ExceptionEIP = (LPVOID)__helperframe.GetReturnAddress(); +#endif // defined(_DEBUG) && defined(TARGET_X86) + + RaiseTheExceptionInternalOnly(oref, FALSE); + HELPER_METHOD_FRAME_END(); +#endif +} +HCIMPLEND + #ifndef STATUS_STACK_BUFFER_OVERRUN // Not defined yet in CESDK includes # define STATUS_STACK_BUFFER_OVERRUN ((NTSTATUS)0xC0000409L) #endif @@ -2347,7 +2393,6 @@ extern "C" void JIT_PatchpointWorkerWorkerWithPolicy(TransitionBlock * pTransiti ::SetLastError(dwLastError); } - #else HCIMPL2(void, JIT_Patchpoint, int* counter, int ilOffset) @@ -2360,7 +2405,7 @@ HCIMPL2(void, JIT_Patchpoint, int* counter, int ilOffset) } HCIMPLEND -HCIMPL1(VOID, JIT_PartialCompilationPatchpoint, int ilOffset) +HCIMPL1(VOID, JIT_PatchpointForced, int ilOffset) { // Stub version if OSR feature is disabled // diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index a95e48b5ce82b6..eac05399f5bdf7 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -59,6 +59,7 @@ #endif #include "tailcallhelp.h" +#include "patchpointinfo.h" // The Stack Overflow probe takes place in the COOPERATIVE_TRANSITION_BEGIN() macro // @@ -1087,6 +1088,16 @@ void CEEInfo::resolveToken(/* IN, OUT */ CORINFO_RESOLVED_TOKEN * pResolvedToken th = ClassLoader::LoadArrayTypeThrowing(th); break; + case CORINFO_TOKENKIND_Await: + // in rare cases a method that returns Task is not actually TaskReturning (i.e. returns T). + // we cannot resolve to an Async variant in such case. + // return NULL, so that caller would re-resolve as a regular method call + pMD = pMD->IsTaskReturningMethod() ? + pMD->GetAsyncOtherVariant(/*allowInstParam*/FALSE): + NULL; + + break; + default: // Disallow ELEMENT_TYPE_BYREF and ELEMENT_TYPE_VOID if (et == ELEMENT_TYPE_BYREF || et == ELEMENT_TYPE_VOID) @@ -3214,6 +3225,10 @@ void CEEInfo::ComputeRuntimeLookupForSharedGenericToken(DictionaryEntryKind entr methodFlags |= ENCODE_METHOD_SIG_SlotInsteadOfToken; } + if (pTemplateMD->IsAsyncVariantMethod()) + { + methodFlags |= ENCODE_METHOD_SIG_AsyncVariant; + } sigBuilder.AppendData(methodFlags); @@ -4870,6 +4885,9 @@ static void getMethodSigInternal( sigRet->callConv = (CorInfoCallConv) (sigRet->callConv | CORINFO_CALLCONV_PARAMTYPE); } + if (ftn->IsAsyncMethod()) + sigRet->callConv = (CorInfoCallConv)(sigRet->callConv | CORINFO_CALLCONV_ASYNCCALL); + // We want the calling convention bit to be consistant with the method attribute bit _ASSERTE( (IsMdStatic(ftn->GetAttrs()) == 0) == ((sigRet->callConv & CORINFO_CALLCONV_HASTHIS) != 0) ); } @@ -7540,7 +7558,7 @@ static void getMethodInfoHelper( methInfo->EHcount = (unsigned short)EHCount; localSig = pResolver->GetLocalSig(); } - else if (ftn->TryGenerateUnsafeAccessor(&cxt.TransientResolver, &cxt.Header)) + else if (ftn->TryGenerateTransientILImplementation(&cxt.TransientResolver, &cxt.Header)) { scopeHnd = cxt.CreateScopeHandle(); @@ -7614,6 +7632,9 @@ static void getMethodInfoHelper( if (ftn->RequiresInstArg()) methInfo->args.callConv = (CorInfoCallConv)(methInfo->args.callConv | CORINFO_CALLCONV_PARAMTYPE); + if (ftn->IsAsyncMethod()) + methInfo->args.callConv = (CorInfoCallConv)(methInfo->args.callConv | CORINFO_CALLCONV_ASYNCCALL); + _ASSERTE((IsMdStatic(ftn->GetAttrs()) == 0) == ((methInfo->args.callConv & CORINFO_CALLCONV_HASTHIS) != 0)); /* And its local variables */ @@ -10202,6 +10223,28 @@ void CEEInfo::getEEInfo(CORINFO_EE_INFO *pEEInfoOut) EE_TO_JIT_TRANSITION(); } +void CEEInfo::getAsyncInfo(CORINFO_ASYNC_INFO* pAsyncInfoOut) +{ + CONTRACTL { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } CONTRACTL_END; + + JIT_TO_EE_TRANSITION(); + + pAsyncInfoOut->continuationClsHnd = CORINFO_CLASS_HANDLE(CoreLibBinder::GetClass(CLASS__CONTINUATION)); + pAsyncInfoOut->continuationNextFldHnd = CORINFO_FIELD_HANDLE(CoreLibBinder::GetField(FIELD__CONTINUATION__NEXT)); + pAsyncInfoOut->continuationResumeFldHnd = CORINFO_FIELD_HANDLE(CoreLibBinder::GetField(FIELD__CONTINUATION__RESUME)); + pAsyncInfoOut->continuationStateFldHnd = CORINFO_FIELD_HANDLE(CoreLibBinder::GetField(FIELD__CONTINUATION__STATE)); + pAsyncInfoOut->continuationFlagsFldHnd = CORINFO_FIELD_HANDLE(CoreLibBinder::GetField(FIELD__CONTINUATION__FLAGS)); + pAsyncInfoOut->continuationDataFldHnd = CORINFO_FIELD_HANDLE(CoreLibBinder::GetField(FIELD__CONTINUATION__DATA)); + pAsyncInfoOut->continuationGCDataFldHnd = CORINFO_FIELD_HANDLE(CoreLibBinder::GetField(FIELD__CONTINUATION__GCDATA)); + pAsyncInfoOut->continuationsNeedMethodHandle = m_pMethodBeingCompiled->GetLoaderAllocator()->CanUnload(); + + EE_TO_JIT_TRANSITION(); +} + // Return details about EE internal data structures uint32_t CEEInfo::getThreadTLSIndex(void **ppIndirection) { @@ -10920,6 +10963,16 @@ void CEEJitInfo::WriteCodeBytes() } } +void CEEJitInfo::PublishFinalCodeAddress(PCODE addr) +{ + LIMITED_METHOD_CONTRACT; + + if (m_finalCodeAddressSlot != NULL) + { + *m_finalCodeAddressSlot = addr; + } +} + /*********************************************************************/ void CEEJitInfo::BackoutJitData(EECodeGenManager * jitMgr) { @@ -11275,7 +11328,7 @@ void CInterpreterJitInfo::SetDebugInfo(PTR_BYTE pDebugInfo) } #endif // FEATURE_INTERPRETER -void CEECodeGenInfo::CompressDebugInfo() +void CEECodeGenInfo::CompressDebugInfo(PCODE nativeEntry) { CONTRACTL { THROWS; @@ -11295,6 +11348,9 @@ void CEECodeGenInfo::CompressDebugInfo() if ((m_iOffsetMapping == 0) && (m_iNativeVarInfo == 0) && (patchpointInfo == NULL) && (m_numInlineTreeNodes == 0) && (m_numRichOffsetMappings == 0)) return; + if (patchpointInfo != NULL) + patchpointInfo->SetTier0EntryPoint(nativeEntry); + JIT_TO_EE_TRANSITION(); EX_TRY @@ -12687,20 +12743,34 @@ void CEECodeGenInfo::getEHinfo( JIT_TO_EE_TRANSITION(); + MethodDesc* pMD = GetMethod(ftn); if (IsDynamicMethodHandle(ftn)) { - GetMethod(ftn)->AsDynamicMethodDesc()->GetResolver()->GetEHInfo(EHnumber, clause); + pMD->AsDynamicMethodDesc()->GetResolver()->GetEHInfo(EHnumber, clause); } - else if (ftn == CORINFO_METHOD_HANDLE(m_pMethodBeingCompiled)) + else if (pMD == m_pMethodBeingCompiled) { getEHinfoHelper(ftn, EHnumber, clause, m_ILHeader); } - else + else if (pMD->HasILHeader()) { - MethodDesc* method = GetMethod(ftn); - COR_ILMETHOD_DECODER header(method->GetILHeader(), method->GetMDImport(), NULL); + COR_ILMETHOD_DECODER header(pMD->GetILHeader(), pMD->GetMDImport(), NULL); getEHinfoHelper(ftn, EHnumber, clause, &header); } + else if (pMD->IsIL() && pMD->GetRVA() == 0) + { + TransientMethodDetails* details; + if (!FindTransientMethodDetails(pMD, &details)) + { + _ASSERTE(!"Expected to be able to find transient method details in getEHinfo"); + } + + getEHinfoHelper(ftn, EHnumber, clause, details->Header); + } + else + { + _ASSERTE(!"No IL header; cannot get EH info for function"); + } EE_TO_JIT_TRANSITION(); } @@ -12769,7 +12839,7 @@ CorJitResult invokeCompileMethodHelper(EECodeGenManager *jitMgr, // if (SUCCEEDED(ret) && !comp->JitAgain()) { - comp->CompressDebugInfo(); + comp->CompressDebugInfo((PCODE)*nativeEntry); comp->MethodCompileComplete(info->ftn); } @@ -12919,6 +12989,9 @@ static CORJIT_FLAGS GetCompileFlags(PrepareCodeConfig* prepareConfig, MethodDesc // flags.Add(CEEInfo::GetBaseCompileFlags(ftn)); + if (ftn->IsAsyncMethod()) + flags.Add(CORJIT_FLAGS::CORJIT_FLAG_ASYNC); + // // Get CPU specific flags // @@ -13301,6 +13374,9 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config, CORINFO_METHOD_INFO methodInfo; getMethodInfoHelper(cxt, &methodInfo); + if (ILHeader == nullptr) + ILHeader = cxt.Header; + // If it's generic then we can only enter through an instantiated MethodDesc _ASSERTE(!ftn->IsGenericMethodDefinition()); @@ -13399,6 +13475,8 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config, ret |= THUMB_CODE; #endif + jitInfo.PublishFinalCodeAddress(ret); + // We are done break; } @@ -14477,6 +14555,360 @@ bool CEEInfo::getTailCallHelpers(CORINFO_RESOLVED_TOKEN* callToken, return success; } +static Signature AllocateSignature(LoaderAllocator* alloc, SigBuilder& sigBuilder, AllocMemTracker* pamTracker) +{ + DWORD sigLen; + PCCOR_SIGNATURE builderSig = (PCCOR_SIGNATURE)sigBuilder.GetSignature(&sigLen); + PVOID newBlob = pamTracker->Track(alloc->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sigLen))); + memcpy(newBlob, builderSig, sigLen); + + return Signature((PCCOR_SIGNATURE)newBlob, sigLen); +} + +static Signature BuildResumptionStubSignature(LoaderAllocator* alloc, AllocMemTracker* pamTracker) +{ + SigBuilder sigBuilder; + sigBuilder.AppendByte(IMAGE_CEE_CS_CALLCONV_DEFAULT); + sigBuilder.AppendData(1); // 1 argument + sigBuilder.AppendElementType(ELEMENT_TYPE_OBJECT); // return type + sigBuilder.AppendElementType(ELEMENT_TYPE_OBJECT); // continuation + + return AllocateSignature(alloc, sigBuilder, pamTracker); +} + +static Signature BuildResumptionStubCalliSignature(MetaSig& msig, MethodTable* mt, LoaderAllocator* alloc, AllocMemTracker* pamTracker) +{ + unsigned numArgs = 0; + if (msig.HasGenericContextArg()) + { + numArgs++; + } + + numArgs++; // Continuation + + numArgs += msig.NumFixedArgs(); + + SigBuilder sigBuilder; + BYTE callConv = IMAGE_CEE_CS_CALLCONV_DEFAULT; + if (msig.HasThis()) + { + callConv |= IMAGE_CEE_CS_CALLCONV_HASTHIS; + } + sigBuilder.AppendByte(callConv); + sigBuilder.AppendData(numArgs); + + auto appendTypeHandle = [&](TypeHandle th) { + _ASSERTE(!th.IsByRef()); + CorElementType ty = th.GetSignatureCorElementType(); + if (CorTypeInfo::IsObjRef(ty)) + { + // Especially to normalize System.__Canon. + sigBuilder.AppendElementType(ELEMENT_TYPE_OBJECT); + } + else if (CorTypeInfo::IsPrimitiveType(ty)) + { + sigBuilder.AppendElementType(ty); + } + else + { + sigBuilder.AppendElementType(ELEMENT_TYPE_INTERNAL); + sigBuilder.AppendPointer(th.AsPtr()); + } + }; + + appendTypeHandle(msig.GetRetTypeHandleThrowing()); // return type +#ifndef TARGET_X86 + if (msig.HasGenericContextArg()) + { + sigBuilder.AppendElementType(ELEMENT_TYPE_I); + } + + sigBuilder.AppendElementType(ELEMENT_TYPE_OBJECT); // continuation +#endif + + msig.Reset(); + CorElementType ty; + while ((ty = msig.NextArg()) != ELEMENT_TYPE_END) + { + TypeHandle tyHnd = msig.GetLastTypeHandleThrowing(); + appendTypeHandle(tyHnd); + } + +#ifdef TARGET_X86 + if (msig.HasGenericContextArg()) + { + sigBuilder.AppendElementType(ELEMENT_TYPE_I); + } + + sigBuilder.AppendElementType(ELEMENT_TYPE_OBJECT); // continuation +#endif + + return AllocateSignature(alloc, sigBuilder, pamTracker); +} + +CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub() +{ + CONTRACTL{ + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + } CONTRACTL_END; + + MethodDesc* md = m_pMethodBeingCompiled; + + LoaderAllocator* loaderAlloc = md->GetLoaderAllocator(); + AllocMemTracker amTracker; + + Signature stubSig = BuildResumptionStubSignature(md->GetLoaderAllocator(), &amTracker); + + MetaSig msig(md); + Signature calliSig = BuildResumptionStubCalliSignature(msig, md->GetMethodTable(), md->GetLoaderAllocator(), &amTracker); + + SigTypeContext emptyCtx; + ILStubLinker sl(md->GetModule(), stubSig, &emptyCtx, NULL, ILSTUB_LINKER_FLAG_NONE); + + ILCodeStream* pCode = sl.NewCodeStream(ILStubLinker::kDispatch); + + int numArgs = 0; + + if (msig.HasThis()) + { + if (md->GetMethodTable()->IsValueType()) + { + pCode->EmitLDC(0); + pCode->EmitCONV_U(); + } + else + { + pCode->EmitLDNULL(); + } + + numArgs++; + } + +#ifndef TARGET_X86 + if (msig.HasGenericContextArg()) + { + pCode->EmitLDC(0); + numArgs++; + } + + // Continuation + pCode->EmitLDARG(0); + numArgs++; +#endif + + msig.Reset(); + CorElementType ty; + while ((ty = msig.NextArg()) != ELEMENT_TYPE_END) + { + TypeHandle tyHnd = msig.GetLastTypeHandleThrowing(); + DWORD loc = pCode->NewLocal(LocalDesc(tyHnd)); + pCode->EmitLDLOCA(loc); + pCode->EmitINITOBJ(pCode->GetToken(tyHnd)); + pCode->EmitLDLOC(loc); + numArgs++; + } + +#ifdef TARGET_X86 + if (msig.HasGenericContextArg()) + { + pCode->EmitLDC(0); + numArgs++; + } + + // Continuation + pCode->EmitLDARG(0); + numArgs++; +#endif + + // Resumption stubs are uniquely coupled to the code version (since the + // continuation is), so we need to make sure we always keep calling the + // same version here. + PrepareCodeConfig* config = GetThread()->GetCurrentPrepareCodeConfig(); + NativeCodeVersion ncv = config->GetCodeVersion(); + if (ncv.GetOptimizationTier() == NativeCodeVersion::OptimizationTier1OSR) + { +#ifdef FEATURE_ON_STACK_REPLACEMENT + // The OSR version needs to resume in the tier0 version. The tier0 + // version will handle setting up the frame that the OSR version + // expects and then delegating back into the OSR version (knowing to do + // so through information stored in the continuation). + _ASSERTE(m_pPatchpointInfoFromRuntime != NULL); + pCode->EmitLDC((DWORD_PTR)m_pPatchpointInfoFromRuntime->GetTier0EntryPoint()); +#else + _ASSERTE(!"Unexpected optimization tier with OSR disabled"); +#endif + } + else + { + { + m_finalCodeAddressSlot = (PCODE*)amTracker.Track(m_pMethodBeingCompiled->GetLoaderAllocator()->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(PCODE)))); + } + + pCode->EmitLDC((DWORD_PTR)m_finalCodeAddressSlot); + pCode->EmitLDIND_I(); + } + + pCode->EmitCALLI(pCode->GetSigToken(calliSig.GetRawSig(), calliSig.GetRawSigLen()), numArgs, msig.IsReturnTypeVoid() ? 0 : 1); + + DWORD resultLoc = UINT_MAX; + TypeHandle resultTypeHnd; + if (!msig.IsReturnTypeVoid()) + { + resultTypeHnd = msig.GetRetTypeHandleThrowing(); + resultLoc = pCode->NewLocal(LocalDesc(resultTypeHnd)); + pCode->EmitSTLOC(resultLoc); + } + + TypeHandle continuationTypeHnd = CoreLibBinder::GetClass(CLASS__CONTINUATION); + DWORD newContinuationLoc = pCode->NewLocal(LocalDesc(continuationTypeHnd)); + pCode->EmitCALL(METHOD__STUBHELPERS__ASYNC_CALL_CONTINUATION, 0, 1); + pCode->EmitSTLOC(newContinuationLoc); + + if (!msig.IsReturnTypeVoid()) + { + ILCodeLabel* doneResult = pCode->NewCodeLabel(); + pCode->EmitLDLOC(newContinuationLoc); + pCode->EmitBRTRUE(doneResult); + + // Load 'next' of current continuation + pCode->EmitLDARG(0); + pCode->EmitLDFLD(FIELD__CONTINUATION__NEXT); + + // Result is placed in GCData[0] if it has GC references (potentially boxing it). + bool isOrContainsGCPointers = false; + if (CorTypeInfo::IsObjRef(resultTypeHnd.GetInternalCorElementType()) || (resultTypeHnd.IsValueType() && resultTypeHnd.AsMethodTable()->ContainsGCPointers())) + { + // Load 'gcdata' of next continuation + pCode->EmitLDFLD(FIELD__CONTINUATION__GCDATA); + + // Now we have the GC array. At the first index is the result. + pCode->EmitLDC(0); + + // NOTE: that we are not using regular boxing (in EmitBOX sense) and allocate our own box instances via a helper. + // There are two reasons: + // - resultTypeHnd may be a nullable type and have different layout in boxed/unboxed forms. + // We do not want to deal with that. + // - resultTypeHnd may contain __Canon fields. Regular boxing would not allow that, but this box is used for a very + // specific internal purpose where we only require that the GC layout of the box matches the data + // that we store in it, thus we want to allow __Canon. + if (resultTypeHnd.IsValueType()) + { + // make a box and dup the ref + MethodDesc* md = CoreLibBinder::GetMethod(METHOD__ASYNC_HELPERS__ALLOC_CONTINUATION_RESULT_BOX); + pCode->EmitLDC((DWORD_PTR)resultTypeHnd.AsMethodTable()); + pCode->EmitCALL(pCode->GetToken(md), 1, 1); + pCode->EmitDUP(); + // dst is the offset of the first field in the box + pCode->EmitLDFLDA(FIELD__RAW_DATA__DATA); + // load the result + pCode->EmitLDLOC(resultLoc); + // store into the box + pCode->EmitSTOBJ(pCode->GetToken(resultTypeHnd)); + } + else + { + // load the result + pCode->EmitLDLOC(resultLoc); + } + + // Store the result. + pCode->EmitSTELEM_REF(); + } + else + { + // Otherwise it goes into Data, either at offset 0 or 4 depending + // on CORINFO_CONTINUATION_OSR_IL_OFFSET_IN_DATA. + ILCodeLabel* hasOsrILOffset = pCode->NewCodeLabel(); + + unsigned nextContinuationLcl = pCode->NewLocal(LocalDesc(continuationTypeHnd)); + pCode->EmitSTLOC(nextContinuationLcl); + + // Load 'flags' of next continuation + pCode->EmitLDLOC(nextContinuationLcl); + pCode->EmitLDFLD(FIELD__CONTINUATION__FLAGS); + pCode->EmitLDC(CORINFO_CONTINUATION_OSR_IL_OFFSET_IN_DATA); + pCode->EmitAND(); + pCode->EmitBRTRUE(hasOsrILOffset); + + // Load 'data' of next continuation + pCode->EmitLDLOC(nextContinuationLcl); + pCode->EmitLDFLD(FIELD__CONTINUATION__DATA); + // Load address of array at index 0 + pCode->EmitLDC(0); + pCode->EmitLDELEMA(pCode->GetToken(CoreLibBinder::GetClass(CLASS__BYTE))); + + // Store at index 0. + pCode->EmitLDLOC(resultLoc); + pCode->EmitSTOBJ(pCode->GetToken(resultTypeHnd)); + + pCode->EmitBR(doneResult); + + pCode->EmitLabel(hasOsrILOffset); + + // Load 'data' of next continuation + pCode->EmitLDLOC(nextContinuationLcl); + pCode->EmitLDFLD(FIELD__CONTINUATION__DATA); + // Load address of array at index 4 + pCode->EmitLDC(4); + pCode->EmitLDELEMA(pCode->GetToken(CoreLibBinder::GetClass(CLASS__BYTE))); + + // Store at index 4. + pCode->EmitLDLOC(resultLoc); + pCode->EmitUNALIGNED(1); + pCode->EmitSTOBJ(pCode->GetToken(resultTypeHnd)); + } + + pCode->EmitLabel(doneResult); + } + + pCode->EmitLDLOC(newContinuationLoc); + pCode->EmitRET(); + + MethodDesc* result = + ILStubCache::CreateAndLinkNewILStubMethodDesc( + md->GetLoaderAllocator(), + md->GetLoaderModule()->GetILStubCache()->GetOrCreateStubMethodTable(md->GetLoaderModule()), + ILSTUB_ASYNC_RESUME, + md->GetModule(), + stubSig.GetRawSig(), stubSig.GetRawSigLen(), + &emptyCtx, + &sl); + + amTracker.SuppressRelease(); + + const char* optimizationTierName = nullptr; + switch (ncv.GetOptimizationTier()) + { + case NativeCodeVersion::OptimizationTier0: optimizationTierName = "Tier0"; break; + case NativeCodeVersion::OptimizationTier1: optimizationTierName = "Tier1"; break; + case NativeCodeVersion::OptimizationTier1OSR: optimizationTierName = "Tier1OSR"; break; + case NativeCodeVersion::OptimizationTierOptimized: optimizationTierName = "Optimized"; break; + case NativeCodeVersion::OptimizationTier0Instrumented: optimizationTierName = "Tier0Instrumented"; break; + case NativeCodeVersion::OptimizationTier1Instrumented: optimizationTierName = "Tier1Instrumented"; break; + default: optimizationTierName = "UnknownTier"; break; + } + + char name[256]; + int numWritten = sprintf_s(name, ARRAY_SIZE(name), "IL_STUB_AsyncResume_%s_%s", m_pMethodBeingCompiled->GetName(), optimizationTierName); + if (numWritten != -1) + { + AllocMemTracker amTracker; + void* allocedMem = amTracker.Track(m_pMethodBeingCompiled->GetLoaderAllocator()->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(numWritten + 1))); + memcpy(allocedMem, name, (size_t)(numWritten + 1)); + result->AsDynamicMethodDesc()->SetMethodName((LPCUTF8)allocedMem); + amTracker.SuppressRelease(); + } + +#ifdef _DEBUG + LOG((LF_STUBS, LL_INFO1000, "ASYNC: Resumption stub %s created\n", name)); + sl.LogILStub(CORJIT_FLAGS()); +#endif + + return CORINFO_METHOD_HANDLE(result); +} + bool CEEInfo::convertPInvokeCalliToCall(CORINFO_RESOLVED_TOKEN * pResolvedToken, bool fMustConvert) { return false; @@ -14670,6 +15102,12 @@ PatchpointInfo* CEEInfo::getOSRInfo(unsigned* ilOffset) UNREACHABLE(); // only called on derived class. } +CORINFO_METHOD_HANDLE CEEInfo::getAsyncResumptionStub() +{ + LIMITED_METHOD_CONTRACT; + UNREACHABLE(); // only called on derived class. +} + void* CEEInfo::getHelperFtn(CorInfoHelpFunc ftnNum, /* IN */ void ** ppIndirection) /* OUT */ { diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index 0974e0cd0cdeeb..5d562dcbfbef6e 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -85,7 +85,7 @@ BOOL LoadDynamicInfoEntry(Module *currentModule, // These must be implemented in assembly and generate a TransitionBlock then calling JIT_PatchpointWorkerWithPolicy in order to actually be used. EXTERN_C FCDECL2(void, JIT_Patchpoint, int* counter, int ilOffset); -EXTERN_C FCDECL1(void, JIT_PartialCompilationPatchpoint, int ilOffset); +EXTERN_C FCDECL1(void, JIT_PatchpointForced, int ilOffset); // // JIT HELPER ALIASING FOR PORTABILITY. @@ -611,7 +611,7 @@ class CEECodeGenInfo : public CEEInfo ULONG32 cMap, ICorDebugInfo::OffsetMapping *pMap) override final; void setVars(CORINFO_METHOD_HANDLE ftn, ULONG32 cVars, ICorDebugInfo::NativeVarInfo *vars) override final; - void CompressDebugInfo(); + void CompressDebugInfo(PCODE nativeEntry); virtual void SetDebugInfo(PTR_BYTE pDebugInfo) = 0; virtual PatchpointInfo* GetPatchpointInfo() @@ -877,6 +877,8 @@ class CEEJitInfo final : public CEECodeGenInfo } #endif + void PublishFinalCodeAddress(PCODE addr); + CEEJitInfo(MethodDesc* fd, COR_ILMETHOD_DECODER* header, EECodeGenManager* jm, bool allowInlining = true) : CEECodeGenInfo(fd, header, jm, allowInlining) @@ -900,6 +902,7 @@ class CEEJitInfo final : public CEECodeGenInfo m_pPatchpointInfoFromRuntime(NULL), m_ilOffset(0) #endif + , m_finalCodeAddressSlot(NULL) { CONTRACTL { @@ -950,6 +953,8 @@ class CEEJitInfo final : public CEECodeGenInfo void setPatchpointInfo(PatchpointInfo* patchpointInfo) override; PatchpointInfo* getOSRInfo(unsigned* ilOffset) override; + virtual CORINFO_METHOD_HANDLE getAsyncResumptionStub() override final; + protected : #ifdef FEATURE_PGO @@ -994,6 +999,7 @@ protected : PatchpointInfo * m_pPatchpointInfoFromRuntime; unsigned m_ilOffset; #endif + PCODE* m_finalCodeAddressSlot; }; diff --git a/src/coreclr/vm/loongarch64/asmhelpers.S b/src/coreclr/vm/loongarch64/asmhelpers.S index 4d136993665af1..ee33ad8599eebe 100644 --- a/src/coreclr/vm/loongarch64/asmhelpers.S +++ b/src/coreclr/vm/loongarch64/asmhelpers.S @@ -1098,11 +1098,11 @@ NESTED_ENTRY JIT_Patchpoint, _TEXT, NoHandler NESTED_END JIT_Patchpoint, _TEXT // first arg register holds iloffset, which needs to be moved to the second register, and the first register filled with NULL -LEAF_ENTRY JIT_PartialCompilationPatchpoint, _TEXT +LEAF_ENTRY JIT_PatchpointForced, _TEXT move $a1, $a0 li.d $a0, 0 b C_FUNC(JIT_Patchpoint) -LEAF_END JIT_PartialCompilationPatchpoint, _TEXT +LEAF_END JIT_PatchpointForced, _TEXT #endif // FEATURE_TIERED_COMPILATION diff --git a/src/coreclr/vm/memberload.cpp b/src/coreclr/vm/memberload.cpp index 202008d3ef0bcc..6868b484091d02 100644 --- a/src/coreclr/vm/memberload.cpp +++ b/src/coreclr/vm/memberload.cpp @@ -781,6 +781,7 @@ MemberLoader::GetMethodDescFromMemberDefOrRefOrSpec( allowInstParam, /* forceRemotableMethod */ FALSE, /* allowCreate */ TRUE, + AsyncVariantLookup::MatchingAsyncVariant, /* level */ owningTypeLoadLevel); } // MemberLoader::GetMethodDescFromMemberDefOrRefOrSpec diff --git a/src/coreclr/vm/metasig.h b/src/coreclr/vm/metasig.h index d90b956b31aafc..840cd137c94e96 100644 --- a/src/coreclr/vm/metasig.h +++ b/src/coreclr/vm/metasig.h @@ -74,7 +74,7 @@ #endif #define SM(varname, args, retval) METASIG_BODY( SM_ ## varname, args retval ) #define IM(varname, args, retval) METASIG_BODY( IM_ ## varname, args retval ) -#define GM(varname, n, conv, args, retval) METASIG_BODY( GM_ ## varname, args retval ) +#define GM(varname, conv, n, args, retval) METASIG_BODY( GM_ ## varname, args retval ) #define Fld(varname, val) METASIG_BODY( Fld_ ## varname, val ) #endif @@ -563,6 +563,28 @@ DEFINE_METASIG_T(IM(Dec_RetVoid, g(DECIMAL), v)) DEFINE_METASIG_T(IM(Currency_RetVoid, g(CURRENCY), v)) DEFINE_METASIG_T(SM(RefDec_RetVoid, r(g(DECIMAL)), v)) +DEFINE_METASIG_T(IM(Exception_RetTaskOfT, C(EXCEPTION), GI(C(TASK_1), 1, G(0)))) +DEFINE_METASIG_T(IM(T_RetTaskOfT, G(0), GI(C(TASK_1), 1, G(0)))) + +DEFINE_METASIG_T(IM(Exception_RetTask, C(EXCEPTION), C(TASK))) +DEFINE_METASIG_T(IM(RetTask, _, C(TASK))) + +DEFINE_METASIG_T(IM(Exception_RetValueTaskOfT, C(EXCEPTION), GI(g(VALUETASK_1), 1, G(0)))) +DEFINE_METASIG_T(IM(T_RetValueTaskOfT, G(0), GI(g(VALUETASK_1), 1, G(0)))) + +DEFINE_METASIG_T(IM(Exception_RetValueTask, C(EXCEPTION), g(VALUETASK))) +DEFINE_METASIG_T(IM(RetValueTask, _, g(VALUETASK))) + +DEFINE_METASIG_T(GM(Exception_RetTaskOfT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, C(EXCEPTION), GI(C(TASK_1), 1, M(0)))) +DEFINE_METASIG_T(GM(T_RetTaskOfT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, M(0), GI(C(TASK_1), 1, M(0)))) +DEFINE_METASIG_T(GM(Exception_RetValueTaskOfT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, C(EXCEPTION), GI(g(VALUETASK_1), 1, M(0)))) +DEFINE_METASIG_T(GM(T_RetValueTaskOfT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, M(0), GI(g(VALUETASK_1), 1, M(0)))) + +DEFINE_METASIG_T(SM(RetTask, , C(TASK))) +DEFINE_METASIG_T(SM(RetValueTask, , g(VALUETASK))) +DEFINE_METASIG_T(SM(Exception_RetTask, C(EXCEPTION), C(TASK))) +DEFINE_METASIG_T(SM(Exception_RetValueTask, C(EXCEPTION), g(VALUETASK))) + DEFINE_METASIG(GM(RefT_T_T_RetT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, r(M(0)) M(0) M(0), M(0))) DEFINE_METASIG(SM(RefObject_Object_Object_RetObject, r(j) j j, j)) DEFINE_METASIG(SM(RefByte_Byte_Byte_RetByte, r(b) b b, b)) @@ -605,6 +627,13 @@ DEFINE_METASIG(SM(PtrByte_RetStr, P(b), s)) DEFINE_METASIG(SM(Str_RetPtrByte, s, P(b))) DEFINE_METASIG(SM(PtrByte_RetVoid, P(b), v)) +DEFINE_METASIG_T(SM(RetContinuation, , C(CONTINUATION))) +DEFINE_METASIG(GM(T_RetVoid, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, M(0), v)) +DEFINE_METASIG_T(SM(Continuation_RetTask, C(CONTINUATION), C(TASK))) +DEFINE_METASIG_T(GM(Continuation_RetTaskOfT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, C(CONTINUATION), GI(C(TASK_1), 1, M(0)))) +DEFINE_METASIG_T(SM(Continuation_RetValueTask, C(CONTINUATION), g(VALUETASK))) +DEFINE_METASIG_T(GM(Continuation_RetValueTaskOfT, IMAGE_CEE_CS_CALLCONV_DEFAULT, 1, C(CONTINUATION), GI(g(VALUETASK_1), 1, M(0)))) + // Undefine macros in case we include the file again in the compilation unit #undef DEFINE_METASIG diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index 198881236a2c3a..c0d2fbe3c69f99 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -82,6 +82,7 @@ const BYTE MethodDesc::s_ClassificationSizeTable[] = { // This extended part of the table is used for faster MethodDesc size lookup. // We index using optional slot flags into it METHOD_DESC_SIZES(sizeof(NonVtableSlot)), + METHOD_DESC_SIZES(sizeof(MethodImpl)), METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl)), @@ -89,6 +90,15 @@ const BYTE MethodDesc::s_ClassificationSizeTable[] = { METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(NativeCodeSlot)), METHOD_DESC_SIZES(sizeof(MethodImpl) + sizeof(NativeCodeSlot)), METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(NativeCodeSlot)), + + METHOD_DESC_SIZES(sizeof(AsyncMethodData)), + METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(AsyncMethodData)), + METHOD_DESC_SIZES(sizeof(MethodImpl) + sizeof(AsyncMethodData)), + METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(AsyncMethodData)), + METHOD_DESC_SIZES(sizeof(NativeCodeSlot) + sizeof(AsyncMethodData)), + METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(NativeCodeSlot) + sizeof(AsyncMethodData)), + METHOD_DESC_SIZES(sizeof(MethodImpl) + sizeof(NativeCodeSlot) + sizeof(AsyncMethodData)), + METHOD_DESC_SIZES(sizeof(NonVtableSlot) + sizeof(MethodImpl) + sizeof(NativeCodeSlot) + sizeof(AsyncMethodData)), }; #ifndef FEATURE_COMINTEROP @@ -123,7 +133,8 @@ SIZE_T MethodDesc::SizeOf() (mdfClassification | mdfHasNonVtableSlot | mdfMethodImpl - | mdfHasNativeCodeSlot)]; + | mdfHasNativeCodeSlot + | mdfHasAsyncMethodData)]; return size; } @@ -423,6 +434,15 @@ void MethodDesc::GetSig(PCCOR_SIGNATURE *ppSig, DWORD *pcSig) } } + // Async variant methods have alternative signatures that do not match metadata. + if (IsAsyncVariantMethod()) + { + Signature sig = GetAddrOfAsyncMethodData()->sig; + *ppSig = sig.GetRawSig(); + *pcSig = sig.GetRawSigLen(); + return; + } + GetSigFromMetadata(GetMDImport(), ppSig, pcSig); PREFIX_ASSUME(*ppSig != NULL); } @@ -450,6 +470,7 @@ void MethodDesc::GetSigFromMetadata(IMDInternalImport * importer, } CONTRACTL_END + _ASSERTE(!IsAsyncVariantMethod()); if (FAILED(importer->GetSigOfMethodDef(GetMemberDef(), pcSig, ppSig))) { // Class loader already asked for signature, so this should always succeed (unless there's a // bug or a new code path) @@ -722,7 +743,7 @@ BOOL MethodDesc::HasSameMethodDefAs(MethodDesc * pMD) if (this == pMD) return TRUE; - return (GetMemberDef() == pMD->GetMemberDef()) && (GetModule() == pMD->GetModule()); + return (GetMemberDef() == pMD->GetMemberDef()) && (GetModule() == pMD->GetModule() && pMD->IsAsyncVariantMethod() == IsAsyncVariantMethod()); } //******************************************************************************* @@ -1078,6 +1099,18 @@ PTR_PCODE MethodDesc::GetAddrOfNativeCodeSlot() return (PTR_PCODE)(dac_cast(this) + size); } +//******************************************************************************* +PTR_AsyncMethodData MethodDesc::GetAddrOfAsyncMethodData() const +{ + WRAPPER_NO_CONTRACT; + + _ASSERTE(HasAsyncMethodData()); + + SIZE_T size = s_ClassificationSizeTable[m_wFlags & (mdfClassification | mdfHasNonVtableSlot | mdfMethodImpl | mdfHasNativeCodeSlot)]; + + return dac_cast(dac_cast(this) + size); +} + //******************************************************************************* BOOL MethodDesc::IsVoid() { @@ -1122,6 +1155,13 @@ ULONG MethodDesc::GetRVA() return 0; } + // Between two Async variants of the same method only one represents the actual IL. + // It is the variant that is not a thunk. + if (IsAsyncThunkMethod()) + { + return 0; + } + if (GetMemberDef() & 0x00FFFFFF) { Module *pModule = GetModule(); @@ -1809,7 +1849,7 @@ MethodDesc* MethodDesc::StripMethodInstantiation() //******************************************************************************* MethodDescChunk *MethodDescChunk::CreateChunk(LoaderHeap *pHeap, DWORD methodDescCount, - DWORD classification, BOOL fNonVtableSlot, BOOL fNativeCodeSlot, MethodTable *pInitialMT, AllocMemTracker *pamTracker, Module *pLoaderModule) + DWORD classification, BOOL fNonVtableSlot, BOOL fNativeCodeSlot, BOOL fAsyncMethodData, MethodTable *pInitialMT, AllocMemTracker *pamTracker, Module *pLoaderModule) { CONTRACT(MethodDescChunk *) { @@ -1833,6 +1873,9 @@ MethodDescChunk *MethodDescChunk::CreateChunk(LoaderHeap *pHeap, DWORD methodDes if (fNativeCodeSlot) oneSize += sizeof(MethodDesc::NativeCodeSlot); + if (fAsyncMethodData) + oneSize += sizeof(AsyncMethodData); + _ASSERTE((oneSize & MethodDesc::ALIGNMENT_MASK) == 0); DWORD maxMethodDescsPerChunk = (DWORD)(MethodDescChunk::MaxSizeOfMethodDescs / oneSize); @@ -1875,6 +1918,8 @@ MethodDescChunk *MethodDescChunk::CreateChunk(LoaderHeap *pHeap, DWORD methodDes pMD->SetHasNonVtableSlot(); if (fNativeCodeSlot) pMD->SetHasNativeCodeSlot(); + if (fAsyncMethodData) + pMD->SetHasAsyncMethodData(); _ASSERTE(pMD->SizeOf() == oneSize); @@ -2287,6 +2332,111 @@ MethodDesc* NonVirtualEntry2MethodDesc(PCODE entryPoint) } } +static void GetNameOfTypeDefOrRef(Module* pModule, mdToken tk, LPCSTR* pName, LPCSTR* pNamespace) +{ + *pName = ""; + *pNamespace = ""; + if (TypeFromToken(tk) == mdtTypeDef) + { + IfFailThrow(pModule->GetMDImport()->GetNameOfTypeDef(tk, pName, pNamespace)); + } + else if (TypeFromToken(tk) == mdtTypeRef) + { + IfFailThrow(pModule->GetMDImport()->GetNameOfTypeRef(tk, pNamespace, pName)); + } +} + +bool IsTypeDefOrRefImplementedInSystemModule(Module* pModule, mdToken tk) +{ + if (TypeFromToken(tk) == mdtTypeDef) + { + if (pModule->IsSystem()) + { + return true; + } + } + else if (TypeFromToken(tk) == mdtTypeRef) + { + mdToken tkTypeDef; + Module* pModuleOfTypeDef; + + ClassLoader::ResolveTokenToTypeDefThrowing(pModule, tk, &pModuleOfTypeDef, &tkTypeDef); + if (pModuleOfTypeDef->IsSystem()) + { + return true; + } + } + + return false; +} + +MethodReturnKind ClassifyMethodReturnKind(SigPointer sig, Module* pModule, ULONG* offsetOfAsyncDetails, bool *isValueTask) +{ + // Without FEATURE_RUNTIME_ASYNC every declared method is classified as a NormalMethod. + // Thus code that handles runtime async scenarios becomes unreachable. +#ifdef FEATURE_RUNTIME_ASYNC + PCCOR_SIGNATURE initialSig = sig.GetPtr(); + uint32_t data; + IfFailThrow(sig.GetCallingConvInfo(&data)); + if (data & IMAGE_CEE_CS_CALLCONV_GENERIC) + { + // Skip over generic argument count + IfFailThrow(sig.GetData(&data)); + } + + // skip argument count + IfFailThrow(sig.GetData(&data)); + + // now look at return type + // NOTE: this will skip modifiers + CorElementType elemType; + IfFailThrow(sig.GetElemType(&elemType)); + + // can't reason about ELEMENT_TYPE_INTERNAL, but should not see it in metadata + if (elemType == ELEMENT_TYPE_INTERNAL) + ThrowHR(COR_E_BADIMAGEFORMAT); + + *offsetOfAsyncDetails = (ULONG)(sig.GetPtr() - initialSig) - 1; + LPCSTR name, _namespace; + mdToken tk; + if (elemType == ELEMENT_TYPE_GENERICINST) + { + IfFailThrow(sig.GetElemType(&elemType)); + // can't reason about ELEMENT_TYPE_INTERNAL, but should not see it in metadata + if (elemType == ELEMENT_TYPE_INTERNAL) + ThrowHR(COR_E_BADIMAGEFORMAT); + + *isValueTask = (elemType == ELEMENT_TYPE_VALUETYPE); + IfFailThrow(sig.GetToken(&tk)); + IfFailThrow(sig.GetData(&data)); + if (data == 1) + { + // This might be System.Threading.Tasks.Task`1 + GetNameOfTypeDefOrRef(pModule, tk, &name, &_namespace); + if ((strcmp(name, *isValueTask ? "ValueTask`1" : "Task`1") == 0) && strcmp(_namespace, "System.Threading.Tasks") == 0) + { + if (IsTypeDefOrRefImplementedInSystemModule(pModule, tk)) + return MethodReturnKind::GenericTaskReturningMethod; + } + } + } + else if ((elemType == ELEMENT_TYPE_CLASS) || (elemType == ELEMENT_TYPE_VALUETYPE)) + { + IfFailThrow(sig.GetToken(&tk)); + *isValueTask = (elemType == ELEMENT_TYPE_VALUETYPE); + // This might be System.Threading.Tasks.Task or ValueTask + GetNameOfTypeDefOrRef(pModule, tk, &name, &_namespace); + if ((strcmp(name, *isValueTask ? "ValueTask" : "Task") == 0) && strcmp(_namespace, "System.Threading.Tasks") == 0) + { + if (IsTypeDefOrRefImplementedInSystemModule(pModule, tk)) + return MethodReturnKind::NonGenericTaskReturningMethod; + } + } +#endif // FEATURE_RUNTIME_ASYNC + + return MethodReturnKind::NormalMethod; +} + //******************************************************************************* BOOL MethodDesc::IsPointingToPrestub() { @@ -2843,7 +2993,11 @@ bool MethodDesc::DetermineAndSetIsEligibleForTieredCompilation() !IsWrapperStub() && // Functions with NoOptimization or AggressiveOptimization don't participate in tiering - !IsJitOptimizationLevelRequested()) + !IsJitOptimizationLevelRequested() && + + // Tiering the async thunk methods doesn't make sense + !IsAsyncThunkMethod() + ) { InterlockedUpdateFlags3(enum_flag3_IsEligibleForTieredCompilation, TRUE); return true; diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 8fe4987c5d305b..ba8068197dee68 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -55,6 +55,74 @@ EXTERN_C VOID STDCALL NDirectImportThunk(); #define METHOD_TOKEN_RANGE_BIT_COUNT (24 - METHOD_TOKEN_REMAINDER_BIT_COUNT) #define METHOD_TOKEN_RANGE_MASK ((1 << METHOD_TOKEN_RANGE_BIT_COUNT) - 1) +enum class AsyncMethodKind +{ + // Regular methods not returning tasks + // These are "normal" methods that do not get other variants. + // Note: Generic T-returning methods are NotAsync, even if T could be a Task. + NotAsync, + + // Regular methods that return Task/ValueTask + // Such method has its actual IL body and there also a synthetic variant that is an + // Async-callable think. (AsyncVariantThunk) + TaskReturning, + + // Task-returning methods marked as MethodImpl::Async in metadata. + // Such method has a body that is a thunk that forwards to an Async implementation variant + // which owns the original IL. (AsyncVariantImpl) + RuntimeAsync, + + //============================================================= + // On {TaskReturning, AsyncVariantThunk} and {RuntimeAsync, AsyncVariantImpl} pairs: + // + // When we see a Task-returning method we create 2 method varaints that logically match the same method definition. + // One variant has the same signature/callconv as the defining method and another is a matching Async variant. + // Depending on whether the definition was a runtime async method or an ordinary Task-returning method, + // the IL body belongs to one of the variants and another variant is a synthetic thunk. + // + // The signature of the Async variant is derived from the original signature by replacing Task return type with + // modreq'd element type: + // Example: "Task Foo();" ===> "modreq(Task`) int Foo();" + // Example: "ValueTask Bar();" ===> "modreq(ValueTask) void Bar();" + // + // The reason for this encoding is that: + // - it uses parts of original signature, as-is, thus does not need to look for or construct anything + // - it "unwraps" the element type. + // - it is reversible. In particular nonconflicting signatures will map to nonconflicting ones. + // + // Async methods are called with CORINFO_CALLCONV_ASYNCCALL call convention. + // + // It is possible to get from one variant to another via GetAsyncOtherVariant. + // + // NOTE: not all Async methods are "variants" from a pair, see AsyncExplicitImpl below. + //============================================================= + + // The following methods use special calling convention (CORINFO_CALLCONV_ASYNCCALL) + // These methods are emitted by the JIT as resumable state machines and also take an extra + // parameter and extra return - the continuation object. + + // Async methods with actual IL implementation of a MethodImpl::Async method. + AsyncVariantImpl, + + // Async methods with synthetic bodies that forward to a TaskReturning method. + AsyncVariantThunk, + + // Methods that are explicitly declared as Async in metadata while not Task returning. + // This is a special case used in a few infrastructure methods like `Await`. + // Such methods do not get non-Async variants/thunks and can only be called from another Async method. + // NOTE: These methods have the original signature and it is not possible to tell if the method is Async + // from the signature alone, thus all these methods are also JIT intrinsics. + AsyncExplicitImpl, +}; + +struct AsyncMethodData +{ + AsyncMethodKind kind; + Signature sig; +}; + +typedef DPTR(struct AsyncMethodData) PTR_AsyncMethodData; + //============================================================= // Splits methoddef token into two pieces for // storage inside a methoddesc. @@ -128,8 +196,8 @@ enum MethodDescFlags // Has slot for native code mdfHasNativeCodeSlot = 0x0020, - // Method was added via Edit And Continue - mdfEnCAddedMethod = 0x0040, + // HasAsyncMethodData + mdfHasAsyncMethodData = 0x0040, // Method is static mdfStatic = 0x0080, @@ -166,6 +234,28 @@ struct MethodDescCodeData final }; using PTR_MethodDescCodeData = DPTR(MethodDescCodeData); +enum class AsyncVariantLookup +{ + MatchingAsyncVariant = 0, + AsyncOtherVariant +}; + +enum class MethodReturnKind +{ + NormalMethod, + GenericTaskReturningMethod, + NonGenericTaskReturningMethod +}; + +bool IsTypeDefOrRefImplementedInSystemModule(Module* pModule, mdToken tk); +MethodReturnKind ClassifyMethodReturnKind(SigPointer sig, Module* pModule, ULONG* offsetOfAsyncDetails, bool *isValueTask); + +inline bool IsTaskReturning(MethodReturnKind input) +{ + return (input == MethodReturnKind::GenericTaskReturningMethod) || + (input == MethodReturnKind::NonGenericTaskReturningMethod); +} + // The size of this structure needs to be a multiple of MethodDesc::ALIGNMENT // // @GENERICS: @@ -1399,6 +1489,10 @@ class MethodDesc BOOL SetNativeCodeInterlocked(PCODE addr, PCODE pExpected = 0); PTR_PCODE GetAddrOfNativeCodeSlot(); + PTR_AsyncMethodData GetAddrOfAsyncMethodData() const; +#ifndef DACCESS_COMPILE + const AsyncMethodData& GetAsyncMethodData() { _ASSERTE(HasAsyncMethodData()); return *GetAddrOfAsyncMethodData(); } +#endif BOOL MayHaveNativeCode(); @@ -1546,6 +1640,7 @@ class MethodDesc BOOL allowInstParam, BOOL forceRemotableMethod = FALSE, BOOL allowCreate = TRUE, + AsyncVariantLookup variantLookup = AsyncVariantLookup::MatchingAsyncVariant, ClassLoadLevel level = CLASS_LOADED); // Normalize methoddesc for reflection @@ -1553,6 +1648,11 @@ class MethodDesc TypeHandle instType, Instantiation methodInst); + MethodDesc* GetAsyncOtherVariant(BOOL allowInstParam = TRUE) + { + return FindOrCreateAssociatedMethodDesc(this, GetMethodTable(), FALSE, GetMethodInstantiation(), allowInstParam, FALSE, TRUE, AsyncVariantLookup::AsyncOtherVariant); + } + // True if a MD is an funny BoxedEntryPointStub (not from the method table) or // an MD for a generic instantiation...In other words the MethodDescs and the // MethodTable are guaranteed to be "tightly-knit", i.e. if one is present in @@ -1680,6 +1780,7 @@ class MethodDesc enum_flag4_ComputedRequiresStableEntryPoint = 0x01, enum_flag4_RequiresStableEntryPoint = 0x02, enum_flag4_TemporaryEntryPointAssigned = 0x04, + enum_flag4_EnCAddedMethod = 0x08, }; void InterlockedSetFlags4(BYTE mask, BYTE newValue); @@ -1745,17 +1846,90 @@ class MethodDesc m_wFlags |= mdfHasNativeCodeSlot; } + // In the runtime "Async" refers to methods callable via CORINFO_CALLCONV_ASYNCCALL + inline bool IsAsyncMethod() const + { + LIMITED_METHOD_DAC_CONTRACT; + if (!HasAsyncMethodData()) + return false; + auto asyncKind = GetAddrOfAsyncMethodData()->kind; + return asyncKind == AsyncMethodKind::AsyncVariantThunk || + asyncKind == AsyncMethodKind::AsyncVariantImpl || + asyncKind == AsyncMethodKind::AsyncExplicitImpl; + } + + // Is this an Async variant method? + // If yes, the method has another Task-returning variant. + inline bool IsAsyncVariantMethod() const + { + LIMITED_METHOD_DAC_CONTRACT; + if (!HasAsyncMethodData()) + return false; + auto asyncKind = GetAddrOfAsyncMethodData()->kind; + return asyncKind == AsyncMethodKind::AsyncVariantThunk || + asyncKind == AsyncMethodKind::AsyncVariantImpl; + } + + // Is this a small(ish) synthetic Task/async adapter to an async/Task implementation? + // If yes, the method has another variant, which has the actual user-defined method body. + inline bool IsAsyncThunkMethod() const + { + LIMITED_METHOD_DAC_CONTRACT; + if (!HasAsyncMethodData()) + return false; + + auto asyncType = GetAddrOfAsyncMethodData()->kind; + return asyncType == AsyncMethodKind::AsyncVariantThunk || + asyncType == AsyncMethodKind::RuntimeAsync; + } + + inline bool IsTaskReturningMethod() const + { + LIMITED_METHOD_DAC_CONTRACT; + if (!HasAsyncMethodData()) + return false; + auto asyncKind = GetAddrOfAsyncMethodData()->kind; + return asyncKind == AsyncMethodKind::RuntimeAsync || + asyncKind == AsyncMethodKind::TaskReturning; + } + + inline bool IsStructMethodOperatingOnCopy() + { + if (!GetMethodTable()->IsValueType() || IsStatic()) + return false; + + if (!HasAsyncMethodData()) + return false; + + // Only async methods backed by actual user code operate on copies. + // Thunks with runtime-supplied implementation do not. + return GetAddrOfAsyncMethodData()->kind == AsyncMethodKind::AsyncVariantImpl; + } + + inline bool HasAsyncMethodData() const + { + return (m_wFlags & mdfHasAsyncMethodData) != 0; + } + + inline void SetHasAsyncMethodData() + { + LIMITED_METHOD_CONTRACT; + m_wFlags |= mdfHasAsyncMethodData; + } + #ifdef FEATURE_METADATA_UPDATER inline BOOL IsEnCAddedMethod() { LIMITED_METHOD_DAC_CONTRACT; - return (m_wFlags & mdfEnCAddedMethod) != 0; + return (m_bFlags4 & enum_flag4_EnCAddedMethod) != 0; } inline void SetIsEnCAddedMethod() { LIMITED_METHOD_CONTRACT; - m_wFlags |= mdfEnCAddedMethod; +#ifndef DACCESS_COMPILE + InterlockedUpdateFlags4(enum_flag4_EnCAddedMethod, TRUE); +#endif } #else inline BOOL IsEnCAddedMethod() @@ -1886,9 +2060,18 @@ class MethodDesc PCODE JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, JitListLockEntry* pEntry); PCODE JitCompileCodeLocked(PrepareCodeConfig* pConfig, COR_ILMETHOD_DECODER* pilHeader, JitListLockEntry* pLockEntry, ULONG* pSizeOfCode); + bool TryGenerateAsyncThunk(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder); + bool TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder); + void EmitTaskReturningThunk(MethodDesc* pAsyncOtherVariant, MetaSig& thunkMsig, ILStubLinker* pSL); + void EmitAsyncMethodThunk(MethodDesc* pAsyncOtherVariant, MetaSig& msig, ILStubLinker* pSL); + SigPointer GetAsyncThunkResultTypeSig(); + int GetTokenForGenericMethodCallWithAsyncReturnType(ILCodeStream* pCode, MethodDesc* md); + int GetTokenForGenericTypeMethodCallWithAsyncReturnType(ILCodeStream* pCode, MethodDesc* md); + int GetTokenForAwaitAwaiterInstantiatedOverTaskAwaiterType(ILCodeStream* pCode, TypeHandle taskAwaiterType); public: + static void CreateDerivedTargetSigWithExtraParams(MetaSig& msig, SigBuilder* stubSigBuilder); + bool TryGenerateTransientILImplementation(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder); void GenerateFunctionPointerCall(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder); - bool TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder); #endif // DACCESS_COMPILE #ifdef HAVE_GCCOVER @@ -2241,6 +2424,7 @@ class MethodDescChunk DWORD classification, BOOL fNonVtableSlot, BOOL fNativeCodeSlot, + BOOL fAsyncMethodData, MethodTable *initialMT, class AllocMemTracker *pamTracker, Module* pLoaderModule = NULL); @@ -2524,6 +2708,8 @@ class DynamicMethodDesc : public StoredSigMethodDesc StubDelegateInvokeMethod, + StubAsyncResume, + StubLast }; @@ -2594,6 +2780,12 @@ class DynamicMethodDesc : public StoredSigMethodDesc return m_pszMethodName; } + void SetMethodName(PTR_CUTF8 name) + { + LIMITED_METHOD_DAC_CONTRACT; + m_pszMethodName = name; + } + // Based on the current flags, compute the equivalent as COR metadata. WORD GetAttrs() const { @@ -3459,7 +3651,8 @@ class InstantiatedMethodDesc final : public MethodDesc static InstantiatedMethodDesc* FindLoadedInstantiatedMethodDesc(MethodTable *pMT, mdMethodDef methodDef, Instantiation methodInst, - BOOL getSharedNotStub); + BOOL getSharedNotStub, + BOOL asyncThunk); private: diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index 0b00bf1647b3fd..7bd2dc24da486a 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -5534,6 +5534,7 @@ namespace FALSE, // allowInstParam TRUE, // forceRemoteableMethod TRUE, // allowCreate + AsyncVariantLookup::MatchingAsyncVariant, level // level ); } @@ -7777,7 +7778,8 @@ namespace { MethodDesc* pMD = it.GetMethodDesc(); if (pMD->GetMemberDef() == tkMethod - && pMD->GetModule() == mod) + && pMD->GetModule() == mod + && pMD->IsAsyncVariantMethod() == pDefMD->IsAsyncVariantMethod()) { return pMD; } @@ -7787,7 +7789,7 @@ namespace } } -MethodDesc* MethodTable::GetParallelMethodDesc(MethodDesc* pDefMD) +MethodDesc* MethodTable::GetParallelMethodDesc(MethodDesc* pDefMD, AsyncVariantLookup asyncVariantLookup) { CONTRACTL { @@ -7797,12 +7799,36 @@ MethodDesc* MethodTable::GetParallelMethodDesc(MethodDesc* pDefMD) } CONTRACTL_END; + if (asyncVariantLookup == AsyncVariantLookup::MatchingAsyncVariant) + { #ifdef FEATURE_METADATA_UPDATER - if (pDefMD->IsEnCAddedMethod()) - return GetParallelMethodDescForEnC(this, pDefMD); + if (pDefMD->IsEnCAddedMethod()) + return GetParallelMethodDescForEnC(this, pDefMD); #endif // FEATURE_METADATA_UPDATER - return GetMethodDescForSlot_NoThrow(pDefMD->GetSlot()); // TODO! We should probably use the throwing variant where possible + return GetMethodDescForSlot_NoThrow(pDefMD->GetSlot()); // TODO! We should probably use the throwing variant where possible + } + else + { + // Slow path for finding the Async variant (or not-Async variant, if we start from Async one) + // This could be optimized with some trickery around slot numbers, but doing so is ... confusing, so I'm not implementing this yet + mdMethodDef tkMethod = pDefMD->GetMemberDef(); + Module* mod = pDefMD->GetModule(); + bool isAsyncVariantMethod = pDefMD->IsAsyncVariantMethod(); + + MethodTable::IntroducedMethodIterator it(this); + for (; it.IsValid(); it.Next()) + { + MethodDesc* pMD = it.GetMethodDesc(); + if (pMD->GetMemberDef() == tkMethod + && pMD->GetModule() == mod + && pMD->IsAsyncVariantMethod() != isAsyncVariantMethod) + { + return pMD; + } + } + return NULL; + } } #ifndef DACCESS_COMPILE @@ -8179,9 +8205,28 @@ MethodTable::TryResolveVirtualStaticMethodOnThisType(MethodTable* pInterfaceType { COMPlusThrow(kTypeLoadException, E_FAIL); } + + bool differsByAsyncVariant = false; if (!pMethodDecl->HasSameMethodDefAs(pInterfaceMD)) { - continue; + if (pMethodDecl->GetMemberDef() == pInterfaceMD->GetMemberDef() && + pMethodDecl->GetModule() == pInterfaceMD->GetModule() && + pMethodDecl->IsAsyncVariantMethod() != pInterfaceMD->IsAsyncVariantMethod()) + { + differsByAsyncVariant = true; + pMethodDecl = pMethodDecl->GetAsyncOtherVariant(); + if (verifyImplemented) + { + // if only asked to verify, return pMethodDecl as a success (not NULL) + // otherwise GetAsyncOtherVariant down below will trigger verifying again and we will keep coming here + _ASSERTE(pMethodDecl != NULL); + return pMethodDecl; + } + } + else + { + continue; + } } // Spec requires that all body token for MethodImpls that refer to static virtual implementation methods must be MethodDef tokens. @@ -8207,6 +8252,11 @@ MethodTable::TryResolveVirtualStaticMethodOnThisType(MethodTable* pInterfaceType COMPlusThrow(kTypeLoadException, E_FAIL); } + if (differsByAsyncVariant) + { + pMethodImpl = pMethodImpl->GetAsyncOtherVariant(); + } + if (!verifyImplemented && instantiateMethodParameters) { pMethodImpl = pMethodImpl->FindOrCreateAssociatedMethodDesc( @@ -8217,6 +8267,7 @@ MethodTable::TryResolveVirtualStaticMethodOnThisType(MethodTable* pInterfaceType /* allowInstParam */ FALSE, /* forceRemotableMethod */ FALSE, /* allowCreate */ TRUE, + AsyncVariantLookup::MatchingAsyncVariant, /* level */ level); } if (pMethodImpl != nullptr) diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index 19dddae79d2350..78009bcf95dd23 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -64,6 +64,7 @@ class ClassFactoryBase; #endif // FEATURE_COMINTEROP_UNMANAGED_ACTIVATION class ArgDestination; enum class WellKnownAttribute : DWORD; +enum class AsyncVariantLookup; struct MethodTableAuxiliaryData; typedef DPTR(MethodTableAuxiliaryData) PTR_MethodTableAuxiliaryData; @@ -1753,7 +1754,7 @@ class MethodTable MethodTable * GetRestoredSlotMT(DWORD slot); // Used to map methods on the same slot between instantiations. - MethodDesc * GetParallelMethodDesc(MethodDesc * pDefMD); + MethodDesc * GetParallelMethodDesc(MethodDesc * pDefMD, AsyncVariantLookup asyncVariantLookup = (AsyncVariantLookup)0); //------------------------------------------------------------------- // BoxedEntryPoint MethodDescs. diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index b4d3c742debd92..7e8fd211d361f0 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -910,22 +910,26 @@ MethodTableBuilder::MethodSignature::GetMethodAttributes() const STANDARD_VM_CONTRACT; IMDInternalImport * pIMD = GetModule()->GetMDImport(); + DWORD cSig; + PCCOR_SIGNATURE pSig; if (TypeFromToken(GetToken()) == mdtMethodDef) { - DWORD cSig; - if (FAILED(pIMD->GetNameAndSigOfMethodDef(GetToken(), &m_pSig, &cSig, &m_szName))) + if (FAILED(pIMD->GetNameAndSigOfMethodDef(GetToken(), &pSig, &cSig, &m_szName))) { // We have empty name or signature on error, do nothing } - m_cSig = static_cast(cSig); } else { CONSISTENCY_CHECK(TypeFromToken(m_tok) == mdtMemberRef); - DWORD cSig; - if (FAILED(pIMD->GetNameAndSigOfMemberRef(GetToken(), &m_pSig, &cSig, &m_szName))) + if (FAILED(pIMD->GetNameAndSigOfMemberRef(GetToken(), &pSig, &cSig, &m_szName))) { // We have empty name or signature on error, do nothing } + } + // Don't overwrite signature that may have already been provided for AsyncThunk method + if (m_cSig == 0) + { m_cSig = static_cast(cSig); + m_pSig = pSig; } } @@ -988,9 +992,14 @@ MethodTableBuilder::bmtRTMethod::bmtRTMethod( MethodDesc * pMD) : m_pOwningType(pOwningType), m_pMD(pMD), - m_methodSig(pMD->GetModule(), - pMD->GetMemberDef(), - &pOwningType->GetSubstitution()) + m_methodSig(pMD->IsAsyncVariantMethod() + ? MethodSignature(pMD->GetModule(), + pMD->GetMemberDef(), + pMD->GetSignature(), + &pOwningType->GetSubstitution()) + : MethodSignature(pMD->GetModule(), + pMD->GetMemberDef(), + &pOwningType->GetSubstitution())) { CONTRACTL { @@ -1015,9 +1024,45 @@ MethodTableBuilder::bmtMDMethod::bmtMDMethod( m_dwImplAttrs(dwImplAttrs), m_dwRVA(dwRVA), m_type(type), + m_asyncMethodKind(AsyncMethodKind::NotAsync), + m_implType(implType), + m_methodSig(pOwningType->GetModule(), + tok, + &pOwningType->GetSubstitution()), + m_pMD(NULL), + m_pUnboxedMD(NULL), + m_slotIndex(INVALID_SLOT_INDEX), + m_unboxedSlotIndex(INVALID_SLOT_INDEX) + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + } + + MethodTableBuilder::bmtMDMethod::bmtMDMethod( + bmtMDType * pOwningType, + mdMethodDef tok, + DWORD dwDeclAttrs, + DWORD dwImplAttrs, + DWORD dwRVA, + Signature sig, + AsyncMethodKind asyncMethodKind, + MethodClassification type, + METHOD_IMPL_TYPE implType) + : m_pOwningType(pOwningType), + m_dwDeclAttrs(dwDeclAttrs), + m_dwImplAttrs(dwImplAttrs), + m_dwRVA(dwRVA), + m_type(type), + m_asyncMethodKind(asyncMethodKind), m_implType(implType), m_methodSig(pOwningType->GetModule(), tok, + sig, &pOwningType->GetSubstitution()), m_pMD(NULL), m_pUnboxedMD(NULL), @@ -2645,6 +2690,7 @@ HRESULT MethodTableBuilder::FindMethodDeclarationForMethodImpl( #pragma warning(push) #pragma warning(disable:21000) // Suppress PREFast warning about overly large function #endif // _PREFAST_ + //--------------------------------------------------------------------------------------- // // Used by BuildMethodTable @@ -2701,6 +2747,10 @@ MethodTableBuilder::EnumerateClassMethods() BuildMethodTableThrowException(IDS_CLASSLOAD_TOO_MANY_METHODS); bmtMethod->m_cMaxDeclaredMethods = (SLOT_INDEX)cMethAndGaps; +#ifdef FEATURE_RUNTIME_ASYNC + // TODO: (async) the index is uint16 and can potentially overflow. This needs to be more robust. + bmtMethod->m_cMaxDeclaredMethods *= 2; +#endif bmtMethod->m_cDeclaredMethods = 0; bmtMethod->m_rgDeclaredMethods = new (GetStackingAllocator()) bmtMDMethod *[bmtMethod->m_cMaxDeclaredMethods]; @@ -2729,7 +2779,7 @@ MethodTableBuilder::EnumerateClassMethods() { BuildMethodTableThrowException(BFA_METHOD_TOKEN_OUT_OF_RANGE); } - if (!bmtProp->fNoSanityChecks && FAILED(pMDInternalImport->GetSigOfMethodDef(tok, &cMemberSignature, &pMemberSignature))) + if (FAILED(pMDInternalImport->GetSigOfMethodDef(tok, &cMemberSignature, &pMemberSignature))) { BuildMethodTableThrowException(hr, BFA_BAD_SIGNATURE, mdMethodDefNil); } @@ -2775,6 +2825,14 @@ MethodTableBuilder::EnumerateClassMethods() } } + SigParser sig(pMemberSignature, cMemberSignature); + + ULONG offsetOfAsyncDetails = 0; + bool returnsValueTask = false; + MethodReturnKind returnKind = IsDelegate() ? + MethodReturnKind::NormalMethod : + ClassifyMethodReturnKind(sig, GetModule(), &offsetOfAsyncDetails, &returnsValueTask); + bool hasGenericMethodArgsComputed = false; bool hasGenericMethodArgs = this->GetModule()->m_pMethodIsGenericMap->IsGeneric(tok, &hasGenericMethodArgsComputed); if (!hasGenericMethodArgsComputed) @@ -3310,28 +3368,161 @@ MethodTableBuilder::EnumerateClassMethods() // Create a new bmtMDMethod representing this method and add it to the // declared method list. // + bmtMDMethod *pDeclaredMethod = NULL; + for (int insertCount = 0; insertCount < 2; insertCount++) + { + bmtMDMethod * pNewMethod; + if (insertCount == 0) + { + pNewMethod = new (GetStackingAllocator()) bmtMDMethod( + bmtInternal->pType, + tok, + dwMemberAttrs, + dwImplFlags, + dwMethodRVA, + type, + implType); + + if (IsTaskReturning(returnKind)) + { + // ordinary Task-returning method: + // Declare a TaskReturning method and add an Async variant that is a thunk to the TaskReturing one. + // + // IsMiAsync Task-returning method: + // Declare a RuntimeAsync method and add an Async variant with the actual implementation. + // The RuntimeAsync method becomes a thunk to the implementation helper. + pNewMethod->SetAsyncMethodKind(IsMiAsync(dwImplFlags) ? AsyncMethodKind::RuntimeAsync : AsyncMethodKind::TaskReturning); + } + else + { + if (IsMiAsync(dwImplFlags)) + { + // Explicitly-async methods have special semantics that is useful in the implementation of runtime async itself. + // It should not be valid to declare such methods outside of runtime infrastructure. + if (!IsTypeDefOrRefImplementedInSystemModule(GetModule(), this->GetCl())) + { + BuildMethodTableThrowException(IDS_CLASSLOAD_BADFORMAT); + } + + pNewMethod->SetAsyncMethodKind(AsyncMethodKind::AsyncExplicitImpl); + } + else + { + pNewMethod->SetAsyncMethodKind(AsyncMethodKind::NotAsync); + } + } + + pDeclaredMethod = pNewMethod; + } + else + { + ULONG cAsyncThunkMemberSignature = cMemberSignature; + AsyncMethodKind asyncKind; + ULONG originalTokenOffsetFromAsyncDetailsOffset; + ULONG newTokenOffsetFromAsyncDetailsOffset; + ULONG originalPrefixSize; + ULONG originalSuffixSize; + ULONG newSuffixSize; + ULONG newPrefixSize; + + if (returnKind == MethodReturnKind::NonGenericTaskReturningMethod) + { + cAsyncThunkMemberSignature += 1; + originalTokenOffsetFromAsyncDetailsOffset = 1; + newTokenOffsetFromAsyncDetailsOffset = 1; + asyncKind = IsMiAsync(dwImplFlags) ? AsyncMethodKind::AsyncVariantImpl : AsyncMethodKind::AsyncVariantThunk; + originalPrefixSize = 1; + newPrefixSize = 1; + originalSuffixSize = 0; + newSuffixSize = 1; + } + else if (returnKind == MethodReturnKind::GenericTaskReturningMethod) + { + cAsyncThunkMemberSignature -= 2; + originalTokenOffsetFromAsyncDetailsOffset = 2; + newTokenOffsetFromAsyncDetailsOffset = 1; + asyncKind = IsMiAsync(dwImplFlags)? AsyncMethodKind::AsyncVariantImpl : AsyncMethodKind::AsyncVariantThunk; + originalPrefixSize = 2; + newPrefixSize = 1; + originalSuffixSize = 1; + newSuffixSize = 0; + } + else + { + UNREACHABLE(); + } - bmtMDMethod * pNewMethod = new (GetStackingAllocator()) bmtMDMethod( - bmtInternal->pType, - tok, - dwMemberAttrs, - dwImplFlags, - dwMethodRVA, - type, - implType); + BYTE* pNewMemberSignature = AllocateFromHighFrequencyHeap(S_SIZE_T(cAsyncThunkMemberSignature)); + ULONG tokenLen = CorSigUncompressedDataSize(&pMemberSignature[offsetOfAsyncDetails + originalTokenOffsetFromAsyncDetailsOffset]); + ULONG originalTokenOffset = offsetOfAsyncDetails + originalTokenOffsetFromAsyncDetailsOffset; + ULONG newTokenOffset = offsetOfAsyncDetails + newTokenOffsetFromAsyncDetailsOffset; + ULONG originalRemainingSigOffset = offsetOfAsyncDetails + originalPrefixSize + tokenLen + originalSuffixSize; + ULONG newRemainingSigOffset = offsetOfAsyncDetails + newPrefixSize + tokenLen + newSuffixSize; - bmtMethod->AddDeclaredMethod(pNewMethod); + ULONG initialCopyLen = offsetOfAsyncDetails; + memcpy(pNewMemberSignature, pMemberSignature, initialCopyLen); + memcpy(pNewMemberSignature + newTokenOffset, pMemberSignature + originalTokenOffset, tokenLen); - // - // Update the count of the various types of methods. - // + _ASSERTE((cMemberSignature - originalRemainingSigOffset) == (cAsyncThunkMemberSignature - newRemainingSigOffset)); + memcpy(pNewMemberSignature + newRemainingSigOffset, pMemberSignature + originalRemainingSigOffset, cMemberSignature - originalRemainingSigOffset); - bmtVT->dwMaxVtableSize++; + BYTE elemTypeClassOrValuetype = returnsValueTask ? (BYTE)ELEMENT_TYPE_VALUETYPE : (BYTE)ELEMENT_TYPE_CLASS; - // Increment the number of non-abstract declared methods - if (!IsMdAbstract(dwMemberAttrs)) - { - bmtMethod->dwNumDeclaredNonAbstractMethods++; + // for more info about constructing the signature of an async variant see comments in AsyncMethodKind + if (returnKind == MethodReturnKind::NonGenericTaskReturningMethod) + { + // Incoming sig will look like ... E_T_CLASS/E_T_VALUETYPE + // and needs to be translated to ELEMENT_TYPE_CMOD_REQD E_T_VOID + + // Replace the E_T_CLASS/E_T_VALUETYPE with ELEMENT_TYPE_CMOD_REQD, and then add the E_T_VOID + pNewMemberSignature[offsetOfAsyncDetails] = ELEMENT_TYPE_CMOD_REQD; + pNewMemberSignature[newRemainingSigOffset - 1] = ELEMENT_TYPE_VOID; + } + else + { + _ASSERTE(returnKind == MethodReturnKind::GenericTaskReturningMethod); + // Incoming sig will look something like ... E_T_GENERICINST E_T_CLASS/E_T_VALUETYPE 1 E_T_I4 .... + // And needs to be translated to ELEMENT_TYPE_CMOD_REQD E_T_I4 + + // Replace the ELEMENT_TYPE_GENERICINST with ELEMENT_TYPE_CMOD_REQD, and then remove the 1 which specifies the generic arg count for Task + pNewMemberSignature[offsetOfAsyncDetails] = ELEMENT_TYPE_CMOD_REQD; + } + + Signature newMemberSig(pNewMemberSignature, cAsyncThunkMemberSignature); + pNewMethod = new (GetStackingAllocator()) bmtMDMethod( + bmtInternal->pType, + tok, + dwMemberAttrs, + dwImplFlags, + dwMethodRVA, + newMemberSig, + asyncKind, + type, + implType); + + pNewMethod->SetAsyncOtherVariant(pDeclaredMethod); + pDeclaredMethod->SetAsyncOtherVariant(pNewMethod); + } + + bmtMethod->AddDeclaredMethod(pNewMethod); + + // + // Update the count of the various types of methods. + // + + bmtVT->dwMaxVtableSize++; + + // Increment the number of non-abstract declared methods + if (!IsMdAbstract(dwMemberAttrs)) + { + bmtMethod->dwNumDeclaredNonAbstractMethods++; + } + + // Normal methods only insert a single method + if (!IsTaskReturning(returnKind)) + { + break; + } } } @@ -5151,6 +5342,9 @@ MethodTableBuilder::InitNewMethodDesc( if (NeedsNativeCodeSlot(pMethod)) pNewMD->SetHasNativeCodeSlot(); + if (pMethod->GetAsyncMethodKind() != AsyncMethodKind::NotAsync) + pNewMD->SetHasAsyncMethodData(); + // Now we know the classification we can allocate the correct type of // method desc and perform any classification specific initialization. @@ -5178,6 +5372,13 @@ MethodTableBuilder::InitNewMethodDesc( strcpy_s((char *) pszDebugMethodNameCopy, len, pszDebugMethodName); #endif // _DEBUG + Signature sig; + if (pMethod->IsAsyncVariant()) + { + // async variants do not get the default signature from metadata + sig = pMethod->GetMethodSignature().GetSignatureClass(); + } + // Do the init specific to each classification of MethodDesc & assign some common fields InitMethodDesc(pNewMD, pMethod->GetMethodType(), @@ -5187,7 +5388,9 @@ MethodTableBuilder::InitNewMethodDesc( FALSE, // fEnC pMethod->GetRVA(), GetMDImport(), - pName + pName, + sig, + pMethod->GetAsyncMethodKind() COMMA_INDEBUG(pszDebugMethodNameCopy) COMMA_INDEBUG(GetDebugClassName()) COMMA_INDEBUG("") // FIX this happens on global methods, give better info @@ -5468,7 +5671,7 @@ MethodTableBuilder::PlaceVirtualMethods() // that the name+signature corresponds to. Used by ProcessMethodImpls and ProcessInexactMethodImpls // Always returns the first match that it finds. Affects the ambiguities in code:#ProcessInexactMethodImpls_Ambiguities MethodTableBuilder::bmtMethodHandle -MethodTableBuilder::FindDeclMethodOnInterfaceEntry(bmtInterfaceEntry *pItfEntry, MethodSignature &declSig, bool searchForStaticMethods) +MethodTableBuilder::FindDeclMethodOnInterfaceEntry(bmtInterfaceEntry *pItfEntry, MethodSignature &declSig, AsyncVariantLookup variantLookup, bool searchForStaticMethods) { STANDARD_VM_CONTRACT; @@ -5505,6 +5708,27 @@ MethodTableBuilder::FindDeclMethodOnInterfaceEntry(bmtInterfaceEntry *pItfEntry, } } + if (variantLookup == AsyncVariantLookup::AsyncOtherVariant && !declMethod.IsNull()) + { + bmtRTMethod* declRTMethod = declMethod.AsRTMethod(); + // Other varian may not exist. For example we return Task and the base is generic and returns T. + // Then we return Null. + declMethod = {}; + for (; !slotIt.AtEnd(); slotIt.Next()) + { + bmtRTMethod* slotDeclMethod = slotIt->Decl().AsRTMethod(); + + if ((slotDeclMethod->GetOwningType() == declRTMethod->GetOwningType()) && + (slotDeclMethod->GetMethodDesc()->GetMethodTable() == declRTMethod->GetMethodDesc()->GetMethodTable()) && + (slotDeclMethod->GetMethodDesc()->GetMemberDef() == declRTMethod->GetMethodDesc()->GetMemberDef()) && + (slotDeclMethod->GetMethodDesc()->IsAsyncVariantMethod() != declRTMethod->GetMethodDesc()->IsAsyncVariantMethod())) + { + declMethod = slotIt->Decl(); + break; + } + } + } + return declMethod; } @@ -5573,6 +5797,10 @@ MethodTableBuilder::ProcessInexactMethodImpls() continue; } + AsyncVariantLookup asyncVariantOfDeclToFind = !it->IsAsyncVariant() ? + AsyncVariantLookup::MatchingAsyncVariant : + AsyncVariantLookup::AsyncOtherVariant; + // If this method serves as the BODY of a MethodImpl specification, then // we should iterate all the MethodImpl's for this class and see just how many // of them this method participates in as the BODY. @@ -5627,7 +5855,7 @@ MethodTableBuilder::ProcessInexactMethodImpls() pItfEntry = &bmtInterface->pInterfaceMap[i]; // Search for declmethod on this interface - declMethod = FindDeclMethodOnInterfaceEntry(pItfEntry, declSig); + declMethod = FindDeclMethodOnInterfaceEntry(pItfEntry, declSig, asyncVariantOfDeclToFind); // If we didn't find a match, continue on to next interface in the equivalence set if (declMethod.IsNull()) @@ -5714,6 +5942,10 @@ MethodTableBuilder::ProcessMethodImpls() continue; } + AsyncVariantLookup asyncVariantOfDeclToFind = !it->IsAsyncVariant() ? + AsyncVariantLookup::MatchingAsyncVariant : + AsyncVariantLookup::AsyncOtherVariant; + // If this method serves as the BODY of a MethodImpl specification, then // we should iterate all the MethodImpl's for this class and see just how many // of them this method participates in as the BODY. @@ -5755,7 +5987,7 @@ MethodTableBuilder::ProcessMethodImpls() } CONSISTENCY_CHECK(TypeFromToken(mdDecl) == mdtMethodDef); - declMethod = bmtMethod->FindDeclaredMethodByToken(mdDecl); + declMethod = bmtMethod->FindDeclaredMethodByToken(mdDecl, asyncVariantOfDeclToFind); } else { // We can't call GetDescFromMemberDefOrRef here because this @@ -5889,12 +6121,20 @@ MethodTableBuilder::ProcessMethodImpls() } // 3. Find the matching method. - declMethod = FindDeclMethodOnInterfaceEntry(pItfEntry, declSig, isVirtualStaticOverride); // Search for statics when the impl is non-virtual + declMethod = FindDeclMethodOnInterfaceEntry(pItfEntry, declSig, asyncVariantOfDeclToFind, isVirtualStaticOverride); // Search for statics when the impl is non-virtual } else { GetHalfBakedClass()->SetHasVTableMethodImpl(); - declMethod = FindDeclMethodOnClassInHierarchy(it, pDeclMT, declSig); + declMethod = FindDeclMethodOnClassInHierarchy(it, pDeclMT, declSig, asyncVariantOfDeclToFind); + } + + if (declMethod.IsNull() && asyncVariantOfDeclToFind == AsyncVariantLookup::AsyncOtherVariant) + { + // when implementing/overriding, we may see a Task-returning method + // which matches a T-returning method in the interface/base, which would not have variants. + // in such case the async variant of the Task-returning method does not implement/override anything. + continue; } if (declMethod.IsNull()) @@ -5945,7 +6185,7 @@ MethodTableBuilder::ProcessMethodImpls() } -MethodTableBuilder::bmtMethodHandle MethodTableBuilder::FindDeclMethodOnClassInHierarchy(const DeclaredMethodIterator& it, MethodTable * pDeclMT, MethodSignature &declSig) +MethodTableBuilder::bmtMethodHandle MethodTableBuilder::FindDeclMethodOnClassInHierarchy(const DeclaredMethodIterator& it, MethodTable * pDeclMT, MethodSignature &declSig, AsyncVariantLookup variantLookup) { bmtRTType * pDeclType = NULL; bmtMethodHandle declMethod; @@ -6031,6 +6271,19 @@ MethodTableBuilder::bmtMethodHandle MethodTableBuilder::FindDeclMethodOnClassInH FALSE, iPass == 0 ? &newVisited : NULL)) { + if (variantLookup == AsyncVariantLookup::AsyncOtherVariant) + { + if (pCurMD->IsTaskReturningMethod() || pCurMD->IsAsyncVariantMethod()) + { + pCurMD = pCurMD->GetAsyncOtherVariant(); + } + else + { + declMethod = {}; + break; + } + } + declMethod = (*bmtParent->pSlotTable)[pCurMD->GetSlot()].Decl(); break; } @@ -6058,7 +6311,9 @@ MethodTableBuilder::InitMethodDesc( BOOL fEnC, DWORD RVA, // Only needed for NDirect case IMDInternalImport * pIMDII, // Needed for NDirect, EEImpl(Delegate) cases - LPCSTR pMethodName // Only needed for mcEEImpl (Delegate) case + LPCSTR pMethodName, // Only needed for mcEEImpl (Delegate) case + Signature sig, // Only needed for the Async thunk case + AsyncMethodKind asyncKind COMMA_INDEBUG(LPCUTF8 pszDebugMethodName) COMMA_INDEBUG(LPCUTF8 pszDebugClassName) COMMA_INDEBUG(LPCUTF8 pszDebugMethodSignature) @@ -6217,6 +6472,20 @@ MethodTableBuilder::InitMethodDesc( #endif // !_DEBUG pNewMD->SetSynchronized(); + if (asyncKind != AsyncMethodKind::NotAsync) + { + AsyncMethodData* pAsyncMethodData = pNewMD->GetAddrOfAsyncMethodData(); + pAsyncMethodData->kind = asyncKind; + if (asyncKind == AsyncMethodKind::AsyncVariantThunk || asyncKind == AsyncMethodKind::AsyncVariantImpl) + { + pAsyncMethodData->sig = sig; + } + else + { + _ASSERTE(sig.GetRawSig() == NULL); + } + } + #ifdef _DEBUG pNewMD->m_pszDebugMethodName = (LPUTF8)pszDebugMethodName; pNewMD->m_pszDebugClassName = (LPUTF8)pszDebugClassName; @@ -6948,6 +7217,9 @@ VOID MethodTableBuilder::AllocAndInitMethodDescs() if (NeedsNativeCodeSlot(*it)) size += sizeof(MethodDesc::NativeCodeSlot); + + if (it->GetAsyncMethodKind() != AsyncMethodKind::NotAsync) + size += sizeof(AsyncMethodData); // See comment in AllocAndInitMethodDescChunk if (NeedsTightlyBoundUnboxingStub(*it)) @@ -7075,6 +7347,21 @@ VOID MethodTableBuilder::AllocAndInitMethodDescChunk(COUNT_T startIndex, COUNT_T if (bmtGenerics->GetNumGenericArgs() == 0) { pUnboxedMD->SetHasNonVtableSlot(); + + // By settings HasNonVTableSlot, the following chunks of data have been shifted around. + // This is an example of the fragility noted in the memcpy comment above + if (pUnboxedMD->HasNativeCodeSlot()) + { + *pUnboxedMD->GetAddrOfNativeCodeSlot() = *pMD->GetAddrOfNativeCodeSlot(); + } + if (pUnboxedMD->HasMethodImplSlot()) + { + *pUnboxedMD->GetMethodImpl() = *pMD->GetMethodImpl(); + } + if (pUnboxedMD->HasAsyncMethodData()) + { + *pUnboxedMD->GetAddrOfAsyncMethodData() = *pMD->GetAddrOfAsyncMethodData(); + } } ////////////////////////////////////////////////////////// @@ -7121,7 +7408,6 @@ MethodTableBuilder::NeedsNativeCodeSlot(bmtMDMethod * pMDMethod) { LIMITED_METHOD_CONTRACT; - #ifdef FEATURE_TIERED_COMPILATION // Keep in-sync with MethodDesc::DetermineAndSetIsEligibleForTieredCompilation() if ((g_pConfig->TieredCompilation() && diff --git a/src/coreclr/vm/methodtablebuilder.h b/src/coreclr/vm/methodtablebuilder.h index a361608bd66f5d..17e8a1fbe703d3 100644 --- a/src/coreclr/vm/methodtablebuilder.h +++ b/src/coreclr/vm/methodtablebuilder.h @@ -671,6 +671,31 @@ class MethodTableBuilder INDEBUG(CheckGetMethodAttributes();) } + //----------------------------------------------------------------------------------------- + // This constructor can be used with hard-coded signatures that are used for + // representing async variant methods + MethodSignature( + Module * pModule, + mdToken tok, + Signature sig, + const Substitution * pSubst) + : m_pModule(pModule), + m_tok(tok), + m_szName(NULL), + m_pSig(sig.GetRawSig()), + m_cSig(sig.GetRawSigLen()), + m_pSubst(pSubst), + m_nameHash(INVALID_NAME_HASH) + { + CONTRACTL { + PRECONDITION(CheckPointer(pModule)); + PRECONDITION(TypeFromToken(tok) == mdtMethodDef || + TypeFromToken(tok) == mdtMemberRef); + PRECONDITION(CheckPointer(m_pSig)); + PRECONDITION(m_cSig != 0); + } CONTRACTL_END; + } + //----------------------------------------------------------------------------------------- // This constructor can be used with hard-coded signatures that are used for // locating .ctor and .cctor methods. @@ -742,6 +767,11 @@ class MethodTableBuilder GetSignature() const { WRAPPER_NO_CONTRACT; CheckGetMethodAttributes(); return m_pSig; } + //----------------------------------------------------------------------------------------- + // Returns the metadata signature for the method. + inline Signature GetSignatureClass() const + { WRAPPER_NO_CONTRACT; CheckGetMethodAttributes(); return Signature(m_pSig, (ULONG)m_cSig); } + //----------------------------------------------------------------------------------------- // Returns the signature length. inline size_t @@ -914,6 +944,22 @@ class MethodTableBuilder MethodClassification type, METHOD_IMPL_TYPE implType); + //----------------------------------------------------------------------------------------- + // Constructor. This takes all the information already extracted from metadata interface + // because the place that creates these types already has this data. Alternatively, + // a constructor could be written to take a token and metadata scope instead. Also, + // it might be interesting to move MethodClassification and METHOD_IMPL_TYPE to setter functions. + bmtMDMethod( + bmtMDType * pOwningType, + mdMethodDef tok, + DWORD dwDeclAttrs, + DWORD dwImplAttrs, + DWORD dwRVA, + Signature sig, + AsyncMethodKind thunkKind, + MethodClassification type, + METHOD_IMPL_TYPE implType); + //----------------------------------------------------------------------------------------- // Returns the type that owns the *declaration* of this method. This makes sure that a // method can be properly interpreted in the context of substitutions at any time. @@ -1014,6 +1060,26 @@ class MethodTableBuilder GetRVA() const { LIMITED_METHOD_CONTRACT; return m_dwRVA; } + bool IsAsyncVariant() const + { + return GetAsyncMethodKind() == AsyncMethodKind::AsyncVariantThunk || + GetAsyncMethodKind() == AsyncMethodKind::AsyncVariantImpl; + } + + void SetAsyncMethodKind(AsyncMethodKind kind) + { + m_asyncMethodKind = kind; + } + + AsyncMethodKind GetAsyncMethodKind() const + { + LIMITED_METHOD_CONTRACT; + return m_asyncMethodKind; + } + + bmtMDMethod * GetAsyncOtherVariant() const { return m_asyncOtherVariant; } + void SetAsyncOtherVariant(bmtMDMethod* pAsyncOtherVariant) { m_asyncOtherVariant = pAsyncOtherVariant; } + private: //----------------------------------------------------------------------------------------- bmtMDType * m_pOwningType; @@ -1022,8 +1088,10 @@ class MethodTableBuilder DWORD m_dwImplAttrs; DWORD m_dwRVA; MethodClassification m_type; // Specific MethodDesc flavour + AsyncMethodKind m_asyncMethodKind; METHOD_IMPL_TYPE m_implType; // Whether or not the method is a methodImpl body MethodSignature m_methodSig; + bmtMDMethod* m_asyncOtherVariant = NULL; MethodDesc * m_pMD; // MethodDesc created and assigned to this method MethodDesc * m_pUnboxedMD; // Unboxing MethodDesc if this is a virtual method on a valuetype @@ -1916,14 +1984,22 @@ class MethodTableBuilder // Searches the declared methods for a method with a token value equal to tok. bmtMDMethod * FindDeclaredMethodByToken( - mdMethodDef tok) + mdMethodDef tok, AsyncVariantLookup variantLookup) { LIMITED_METHOD_CONTRACT; for (SLOT_INDEX i = 0; i < m_cDeclaredMethods; ++i) { if ((*this)[i]->GetMethodSignature().GetToken() == tok) { - return (*this)[i]; + auto result = (*this)[i]; + if (variantLookup == AsyncVariantLookup::AsyncOtherVariant) + { + return result->GetAsyncOtherVariant(); + } + else + { + return result; + } } } return NULL; @@ -2609,7 +2685,9 @@ class MethodTableBuilder BOOL fEnC, DWORD RVA, // Only needed for NDirect case IMDInternalImport * pIMDII, // Needed for NDirect, EEImpl(Delegate) cases - LPCSTR pMethodName // Only needed for mcEEImpl (Delegate) case + LPCSTR pMethodName, // Only needed for mcEEImpl (Delegate) case + Signature sig, // Only needed for the Async thunk case + AsyncMethodKind asyncKind COMMA_INDEBUG(LPCUTF8 pszDebugMethodName) COMMA_INDEBUG(LPCUTF8 pszDebugClassName) COMMA_INDEBUG(LPCUTF8 pszDebugMethodSignature)); @@ -2686,13 +2764,13 @@ class MethodTableBuilder // Find the decl method on a given interface entry that matches the method name+signature specified // If none is found, return a null method handle bmtMethodHandle - FindDeclMethodOnInterfaceEntry(bmtInterfaceEntry *pItfEntry, MethodSignature &declSig, bool searchForStaticMethods = false); + FindDeclMethodOnInterfaceEntry(bmtInterfaceEntry *pItfEntry, MethodSignature &declSig, AsyncVariantLookup variantLookup, bool searchForStaticMethods = false); // -------------------------------------------------------------------------------------------- // Find the decl method within the class hierarchy method name+signature specified // If none is found, return a null method handle bmtMethodHandle - FindDeclMethodOnClassInHierarchy(const DeclaredMethodIterator& it, MethodTable * pDeclMT, MethodSignature &declSig); + FindDeclMethodOnClassInHierarchy(const DeclaredMethodIterator& it, MethodTable * pDeclMT, MethodSignature &declSig, AsyncVariantLookup variantLookup); // -------------------------------------------------------------------------------------------- // Throws if an entry already exists that has been MethodImpl'd. Adds the interface slot and diff --git a/src/coreclr/vm/multicorejit.cpp b/src/coreclr/vm/multicorejit.cpp index ab6d2e65fc74ea..98baf1206f1de4 100644 --- a/src/coreclr/vm/multicorejit.cpp +++ b/src/coreclr/vm/multicorejit.cpp @@ -398,6 +398,12 @@ HRESULT MulticoreJitRecorder::WriteOutput(IStream * pStream) } MethodDesc * pMethod = m_JitInfoArray[i].GetMethodDescAndClean(); + if (pMethod->IsAsyncVariantMethod()) + { + // TODO: (async) consider adding support for async variants in the future + skipped++; + continue; + } if (m_JitInfoArray[i].IsGenericMethodInfo()) { diff --git a/src/coreclr/vm/namespace.h b/src/coreclr/vm/namespace.h index 699049b3ea0847..f3ba695d07dc2d 100644 --- a/src/coreclr/vm/namespace.h +++ b/src/coreclr/vm/namespace.h @@ -16,6 +16,7 @@ #define g_RuntimeNS g_SystemNS ".Runtime" #define g_IONS g_SystemNS ".IO" #define g_ThreadingNS g_SystemNS ".Threading" +#define g_TasksNS g_ThreadingNS ".Tasks" #define g_CollectionsNS g_SystemNS ".Collections" #define g_ResourcesNS g_SystemNS ".Resources" #define g_GlobalizationNS g_SystemNS ".Globalization" diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 50a8ce012b5f60..82292363b22928 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -729,6 +729,16 @@ namespace COR_ILMETHOD_DECODER* pHeader = NULL; COR_ILMETHOD* ilHeader = pConfig->GetILHeader(); + + // For a Runtime Async method the methoddef maps to a Task-returning thunk with runtime-provided implementation, + // while the default IL belongs to the Async implementation variant. + // By default the config captures the default methoddesc, which would be a thunk, thus no IL header. + // So, if config provides no header and we see an implementation method desc, then just ask the method desc itself. + if (ilHeader == NULL && pMD->IsAsyncVariantMethod() && !pMD->IsAsyncThunkMethod()) + { + ilHeader = pMD->GetILHeader(); + } + if (ilHeader == NULL) return NULL; @@ -1032,6 +1042,23 @@ PCODE MethodDesc::JitCompileCodeLocked(PrepareCodeConfig* pConfig, COR_ILMETHOD_ return pCode; } +bool MethodDesc::TryGenerateTransientILImplementation(DynamicResolver** resolver, COR_ILMETHOD_DECODER** methodILDecoder) +{ + STANDARD_VM_CONTRACT; + + if (TryGenerateAsyncThunk(resolver, methodILDecoder)) + { + return true; + } + + if (TryGenerateUnsafeAccessor(resolver, methodILDecoder)) + { + return true; + } + + return false; +} + PrepareCodeConfig::PrepareCodeConfig() {} PrepareCodeConfig::PrepareCodeConfig(NativeCodeVersion codeVersion, BOOL needsMulticoreJitNotification, BOOL mayUsePrecompiledCode) : @@ -1368,36 +1395,46 @@ PrepareCodeConfigBuffer::PrepareCodeConfigBuffer(NativeCodeVersion codeVersion) #endif //FEATURE_CODE_VERSIONING -#ifdef FEATURE_INSTANTIATINGSTUB_AS_IL - -// CreateInstantiatingILStubTargetSig: -// This method is used to create the signature of the target of the ILStub -// for instantiating and unboxing stubs, when/where we need to introduce a generic context. -// And since the generic context is a hidden parameter, we're creating a signature that -// looks like non-generic but has one additional parameter right after the thisptr -void CreateInstantiatingILStubTargetSig(MethodDesc *pBaseMD, - SigTypeContext &typeContext, - SigBuilder *stubSigBuilder) +// CreateDerivedTargetSigWithExtraParams: +// This method is used to create the signature of the target of the ILStub for +// instantiating, unboxing, and async variant stubs, when/where we need to +// introduce a generic context/async continuation. +// And since the generic context/async continuations are hidden parameters, +// we're creating a signature that looks like non-generic but with additional +// parameters right after the thisptr +void MethodDesc::CreateDerivedTargetSigWithExtraParams(MetaSig& msig, SigBuilder *stubSigBuilder) { STANDARD_VM_CONTRACT; - MetaSig msig(pBaseMD); BYTE callingConvention = IMAGE_CEE_CS_CALLCONV_DEFAULT; if (msig.HasThis()) callingConvention |= IMAGE_CEE_CS_CALLCONV_HASTHIS; // CallingConvention stubSigBuilder->AppendByte(callingConvention); + unsigned numArgs = msig.NumFixedArgs(); + if (msig.HasGenericContextArg()) + numArgs++; + if (msig.HasAsyncContinuation()) + numArgs++; // ParamCount - stubSigBuilder->AppendData(msig.NumFixedArgs() + 1); // +1 is for context param + stubSigBuilder->AppendData(numArgs); // +1 is for context param // Return type SigPointer pReturn = msig.GetReturnProps(); - pReturn.ConvertToInternalExactlyOne(msig.GetModule(), &typeContext, stubSigBuilder); + pReturn.ConvertToInternalExactlyOne(msig.GetModule(), msig.GetSigTypeContext(), stubSigBuilder); #ifndef TARGET_X86 - // The hidden context parameter - stubSigBuilder->AppendElementType(ELEMENT_TYPE_I); + if (msig.HasGenericContextArg()) + { + // The hidden context parameter + stubSigBuilder->AppendElementType(ELEMENT_TYPE_I); + } + + if (msig.HasAsyncContinuation()) + { + stubSigBuilder->AppendElementType(ELEMENT_TYPE_OBJECT); + } #endif // !TARGET_X86 // Copy rest of the arguments @@ -1405,15 +1442,25 @@ void CreateInstantiatingILStubTargetSig(MethodDesc *pBaseMD, SigPointer pArgs = msig.GetArgProps(); for (unsigned i = 0; i < msig.NumFixedArgs(); i++) { - pArgs.ConvertToInternalExactlyOne(msig.GetModule(), &typeContext, stubSigBuilder); + pArgs.ConvertToInternalExactlyOne(msig.GetModule(), msig.GetSigTypeContext(), stubSigBuilder); } #ifdef TARGET_X86 - // The hidden context parameter - stubSigBuilder->AppendElementType(ELEMENT_TYPE_I); + if (msig.HasGenericContextArg()) + { + // The hidden context parameter + stubSigBuilder->AppendElementType(ELEMENT_TYPE_I); + } + + if (msig.HasAsyncContinuation()) + { + stubSigBuilder->AppendElementType(ELEMENT_TYPE_OBJECT); + } #endif // TARGET_X86 } +#ifdef FEATURE_INSTANTIATINGSTUB_AS_IL + Stub * CreateUnboxingILStubForSharedGenericValueTypeMethods(MethodDesc* pTargetMD) { @@ -1439,28 +1486,28 @@ Stub * CreateUnboxingILStubForSharedGenericValueTypeMethods(MethodDesc* pTargetM ILCodeStream *pCode = sl.NewCodeStream(ILStubLinker::kDispatch); - // 1. Build the new signature + // Build the new signature SigBuilder stubSigBuilder; - CreateInstantiatingILStubTargetSig(pTargetMD, typeContext, &stubSigBuilder); + MethodDesc::CreateDerivedTargetSigWithExtraParams(msig, &stubSigBuilder); - // 2. Emit the method body + // Emit the method body mdToken tokRawData = pCode->GetToken(CoreLibBinder::GetField(FIELD__RAW_DATA__DATA)); - // 2.1 Push the thisptr + // Push the thisptr // We need to skip over the MethodTable* // The trick below will do that. pCode->EmitLoadThis(); pCode->EmitLDFLDA(tokRawData); #if defined(TARGET_X86) - // 2.2 Push the rest of the arguments for x86 + // Push the rest of the arguments for x86 for (unsigned i = 0; i < msig.NumFixedArgs();i++) { pCode->EmitLDARG(i); } #endif - // 2.3 Push the hidden context param + // Push the hidden context param // The context is going to be captured from the thisptr pCode->EmitLoadThis(); pCode->EmitLDFLDA(tokRawData); @@ -1469,17 +1516,17 @@ Stub * CreateUnboxingILStubForSharedGenericValueTypeMethods(MethodDesc* pTargetM pCode->EmitLDIND_I(); #if !defined(TARGET_X86) - // 2.4 Push the rest of the arguments for not x86 + // Push the rest of the arguments for not x86 for (unsigned i = 0; i < msig.NumFixedArgs();i++) { pCode->EmitLDARG(i); } #endif - // 2.5 Push the target address + // Push the target address pCode->EmitLDC((TADDR)pTargetMD->GetMultiCallableAddrOfCode(CORINFO_ACCESS_ANY)); - // 2.6 Do the calli + // Do the calli pCode->EmitCALLI(TOKEN_ILSTUB_TARGET_SIG, msig.NumFixedArgs() + 1, msig.IsReturnTypeVoid() ? 0 : 1); pCode->EmitRET(); @@ -1546,41 +1593,47 @@ Stub * CreateInstantiatingILStub(MethodDesc* pTargetMD, void* pHiddenArg) ILCodeStream *pCode = sl.NewCodeStream(ILStubLinker::kDispatch); - // 1. Build the new signature + // Build the new signature SigBuilder stubSigBuilder; - CreateInstantiatingILStubTargetSig(pTargetMD, typeContext, &stubSigBuilder); + MethodDesc::CreateDerivedTargetSigWithExtraParams(msig, &stubSigBuilder); - // 2. Emit the method body + // Emit the method body if (msig.HasThis()) { - // 2.1 Push the thisptr + // Push the thisptr pCode->EmitLoadThis(); } #if defined(TARGET_X86) - // 2.2 Push the rest of the arguments for x86 + // Push the rest of the arguments for x86 for (unsigned i = 0; i < msig.NumFixedArgs();i++) { pCode->EmitLDARG(i); } #endif // TARGET_X86 - // 2.3 Push the hidden context param + // Push the hidden context param // InstantiatingStub pCode->EmitLDC((TADDR)pHiddenArg); + // Push the async continuation + if (msig.HasAsyncContinuation()) + { + pCode->EmitLDNULL(); + } + #if !defined(TARGET_X86) - // 2.4 Push the rest of the arguments for not x86 + // Push the rest of the arguments for not x86 for (unsigned i = 0; i < msig.NumFixedArgs();i++) { pCode->EmitLDARG(i); } #endif // !TARGET_X86 - // 2.5 Push the target address + // Push the target address pCode->EmitLDC((TADDR)pTargetMD->GetMultiCallableAddrOfCode(CORINFO_ACCESS_ANY)); - // 2.6 Do the calli + // Do the calli pCode->EmitCALLI(TOKEN_ILSTUB_TARGET_SIG, msig.NumFixedArgs() + 1, msig.IsReturnTypeVoid() ? 0 : 1); pCode->EmitRET(); diff --git a/src/coreclr/vm/readytoruninfo.cpp b/src/coreclr/vm/readytoruninfo.cpp index ca416dabc55d78..8ae5bd42a43d0c 100644 --- a/src/coreclr/vm/readytoruninfo.cpp +++ b/src/coreclr/vm/readytoruninfo.cpp @@ -969,6 +969,8 @@ static bool SigMatchesMethodDesc(MethodDesc* pMD, SigPointer &sig, ModuleBase * { STANDARD_VM_CONTRACT; + _ASSERTE(!pMD->IsAsyncVariantMethod()); + ModuleBase *pOrigModule = pModule; ZapSig::Context zapSigContext(pModule, (void *)pModule, ZapSig::NormalTokens); ZapSig::Context * pZapSigContext = &zapSigContext; @@ -1077,6 +1079,10 @@ bool ReadyToRunInfo::GetPgoInstrumentationData(MethodDesc * pMD, BYTE** pAllocat if (ReadyToRunCodeDisabled()) return false; + // TODO: (async) PGO support for async variants + if (pMD->IsAsyncVariantMethod()) + return false; + if (m_pgoInstrumentationDataHashtable.IsNull()) return false; @@ -1149,6 +1155,10 @@ PCODE ReadyToRunInfo::GetEntryPoint(MethodDesc * pMD, PrepareCodeConfig* pConfig if (ReadyToRunCodeDisabled()) goto done; + // TODO: (async) R2R support for async variants + if (pMD->IsAsyncVariantMethod()) + goto done; + ETW::MethodLog::GetR2RGetEntryPointStart(pMD); uint offset; @@ -2312,4 +2322,4 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator, } } #endif // FEATURE_STUBPRECODE_DYNAMIC_HELPERS -#endif // DACCESS_COMPILE \ No newline at end of file +#endif // DACCESS_COMPILE diff --git a/src/coreclr/vm/riscv64/asmhelpers.S b/src/coreclr/vm/riscv64/asmhelpers.S index 37ab3732fac0b9..f160a704b9f05e 100644 --- a/src/coreclr/vm/riscv64/asmhelpers.S +++ b/src/coreclr/vm/riscv64/asmhelpers.S @@ -944,11 +944,11 @@ NESTED_ENTRY JIT_Patchpoint, _TEXT, NoHandler NESTED_END JIT_Patchpoint, _TEXT // first arg register holds iloffset, which needs to be moved to the second register, and the first register filled with NULL -LEAF_ENTRY JIT_PartialCompilationPatchpoint, _TEXT +LEAF_ENTRY JIT_PatchpointForced, _TEXT mv a1, a0 li a0, 0 j C_FUNC(JIT_Patchpoint) -LEAF_END JIT_PartialCompilationPatchpoint, _TEXT +LEAF_END JIT_PatchpointForced, _TEXT #endif // FEATURE_TIERED_COMPILATION diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index 7ac7928f6e2266..7a74f1cefd9313 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -191,6 +191,10 @@ FCIMPL1(MethodDesc *, RuntimeTypeHandle::GetFirstIntroducedMethod, ReflectClassB MethodTable* pMT = typeHandle.AsMethodTable(); MethodDesc* pMethod = MethodTable::IntroducedMethodIterator::GetFirst(pMT); + // do not report async variants to reflection. + while (pMethod && pMethod->IsAsyncVariantMethod()) + pMethod = MethodTable::IntroducedMethodIterator::GetNext(pMethod); + return pMethod; } FCIMPLEND @@ -205,6 +209,9 @@ FCIMPL1(void, RuntimeTypeHandle::GetNextIntroducedMethod, MethodDesc ** ppMethod CONTRACTL_END; MethodDesc *pMethod = MethodTable::IntroducedMethodIterator::GetNext(*ppMethod); + // do not report async variants to reflection. + while (pMethod && pMethod->IsAsyncVariantMethod()) + pMethod = MethodTable::IntroducedMethodIterator::GetNext(pMethod); *ppMethod = pMethod; } @@ -1790,16 +1797,21 @@ extern "C" void QCALLTYPE RuntimeMethodHandle_StripMethodInstantiation(MethodDes } // In the VM there might be more than one MethodDescs for a "method" -// examples are methods on generic types which may have additional instantiating stubs -// and methods on value types which may have additional unboxing stubs. +// examples are methods on generic types which may have additional instantiating stubs, +// methods on value types which may have additional unboxing stubs and +// async variants for task-returning methods // +// For {task-returning, async} variants Reflection hands out only the task-returning variant. +// the async varinat is an implementation detail that conceptually does not exist. +// TODO: (async) the filtering may not cover all scenarios. Review and add tests. +// // For generic methods we always hand out an instantiating stub except for a generic method definition // For non-generic methods on generic types we need an instantiating stub if it's one of the following // - static method on a generic class // - static or instance method on a generic interface // - static or instance method on a generic value type // The Reflection policy is to always hand out instantiating stubs in these cases -// +// // For methods on non-generic value types we can use either the canonical method or the unboxing stub // The Reflection policy is to always hand out unboxing stubs if the methods are virtual methods // The reason for this is that in the current implementation of the class loader, the v-table slots for @@ -1826,6 +1838,10 @@ FCIMPL2(MethodDesc*, RuntimeMethodHandle::GetStubIfNeededInternal, TypeHandle instType = refType->GetType(); + // do not report async variants to reflection. + if (pMethod->IsAsyncVariantMethod()) + return NULL; + // Perf optimization: this logic is actually duplicated in FindOrCreateAssociatedMethodDescForReflection, but since it // is the more common case it's worth the duplicate check here to avoid the helper method frame if (pMethod->HasMethodInstantiation() @@ -1850,6 +1866,12 @@ extern "C" MethodDesc* QCALLTYPE RuntimeMethodHandle_GetStubIfNeededSlow(MethodD GCX_COOP(); + if (pMethod->IsAsyncVariantMethod()) + { + // do not report async variants to reflection. + pMethod = pMethod->GetAsyncOtherVariant(/*allowInstParam*/ false); + } + TypeHandle instType = declaringTypeHandle.AsTypeHandle(); TypeHandle* inst = NULL; diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index 1333e99755eb34..12c4e9eff79d76 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -137,7 +137,7 @@ unsigned GetSizeForCorElementType(CorElementType etyp) #ifndef DACCESS_COMPILE -void SigPointer::ConvertToInternalExactlyOne(Module* pSigModule, SigTypeContext *pTypeContext, SigBuilder * pSigBuilder, BOOL bSkipCustomModifier) +void SigPointer::ConvertToInternalExactlyOne(Module* pSigModule, const SigTypeContext *pTypeContext, SigBuilder * pSigBuilder, BOOL bSkipCustomModifier) { CONTRACTL { @@ -345,7 +345,7 @@ void SigPointer::ConvertToInternalExactlyOne(Module* pSigModule, SigTypeContext } } -void SigPointer::ConvertToInternalSignature(Module* pSigModule, SigTypeContext *pTypeContext, SigBuilder * pSigBuilder, BOOL bSkipCustomModifier) +void SigPointer::ConvertToInternalSignature(Module* pSigModule, const SigTypeContext *pTypeContext, SigBuilder * pSigBuilder, BOOL bSkipCustomModifier) { CONTRACTL { @@ -699,6 +699,8 @@ MetaSig::MetaSig(MethodDesc *pMD, Instantiation classInst, Instantiation methodI if (pMD->RequiresInstArg()) SetHasParamTypeArg(); + if (pMD->IsAsyncMethod()) + SetIsAsyncCall(); } MetaSig::MetaSig(MethodDesc *pMD, TypeHandle declaringType) @@ -720,6 +722,8 @@ MetaSig::MetaSig(MethodDesc *pMD, TypeHandle declaringType) if (pMD->RequiresInstArg()) SetHasParamTypeArg(); + if (pMD->IsAsyncMethod()) + SetIsAsyncCall(); } #ifdef _DEBUG @@ -5289,6 +5293,13 @@ void ReportPointersFromValueTypeArg(promote_func *fn, ScanContext *sc, PTR_Metho ReportPointersFromValueType(fn, sc, pMT, pSrc->GetDestinationAddress()); } +BOOL MetaSig::HasAsyncContinuation() +{ + LIMITED_METHOD_CONTRACT; + + return IsAsyncCall(); +} + //------------------------------------------------------------------ // Perform type-specific GC promotion on the value (based upon the // last type retrieved by NextArg()). diff --git a/src/coreclr/vm/siginfo.hpp b/src/coreclr/vm/siginfo.hpp index 99256bea5072f8..4610b66b6a9b4e 100644 --- a/src/coreclr/vm/siginfo.hpp +++ b/src/coreclr/vm/siginfo.hpp @@ -127,8 +127,8 @@ class SigPointer : public SigParser //========================================================================= - void ConvertToInternalExactlyOne(Module* pSigModule, SigTypeContext *pTypeContext, SigBuilder * pSigBuilder, BOOL bSkipCustomModifier = TRUE); - void ConvertToInternalSignature(Module* pSigModule, SigTypeContext *pTypeContext, SigBuilder * pSigBuilder, BOOL bSkipCustomModifier = TRUE); + void ConvertToInternalExactlyOne(Module* pSigModule, const SigTypeContext *pTypeContext, SigBuilder * pSigBuilder, BOOL bSkipCustomModifier = TRUE); + void ConvertToInternalSignature(Module* pSigModule, const SigTypeContext *pTypeContext, SigBuilder * pSigBuilder, BOOL bSkipCustomModifier = TRUE); void CopySignature(Module* pSigModule, SigBuilder * pSigBuilder, BYTE additionalCallConv); //========================================================================= @@ -683,7 +683,7 @@ class MetaSig // Returns the calling convention & flags (see IMAGE_CEE_CS_CALLCONV_* // defines in cor.h) //---------------------------------------------------------- - BYTE GetCallingConventionInfo() + USHORT GetCallingConventionInfo() { LIMITED_METHOD_DAC_CONTRACT; @@ -729,6 +729,26 @@ class MetaSig return GetCallingConvention() == IMAGE_CEE_CS_CALLCONV_VARARG; } + //---------------------------------------------------------- + // Does it have a generic context argument? + //---------------------------------------------------------- + BOOL HasGenericContextArg() + { + LIMITED_METHOD_CONTRACT; + return m_CallConv & CORINFO_CALLCONV_PARAMTYPE; + } + + //---------------------------------------------------------- + // Is it an async call? + //---------------------------------------------------------- + BOOL IsAsyncCall() + { + LIMITED_METHOD_CONTRACT; + return m_CallConv & CORINFO_CALLCONV_ASYNCCALL; + } + + BOOL HasAsyncContinuation(); + //---------------------------------------------------------- // Is vararg? //---------------------------------------------------------- @@ -1095,6 +1115,12 @@ class MetaSig m_CallConv |= CORINFO_CALLCONV_PARAMTYPE; } + void SetIsAsyncCall() + { + LIMITED_METHOD_CONTRACT; + m_CallConv |= CORINFO_CALLCONV_ASYNCCALL; + } + void SetTreatAsVarArg() { LIMITED_METHOD_CONTRACT; @@ -1132,7 +1158,7 @@ class MetaSig CorElementType m_corNormalizedRetType; BYTE m_flags; - BYTE m_CallConv; + USHORT m_CallConv; }; // class MetaSig BOOL IsTypeRefOrDef(LPCSTR szClassName, Module *pModule, mdToken token); diff --git a/src/coreclr/vm/stubgen.cpp b/src/coreclr/vm/stubgen.cpp index 4692bfb3a4cf57..6f44bae72be4ef 100644 --- a/src/coreclr/vm/stubgen.cpp +++ b/src/coreclr/vm/stubgen.cpp @@ -173,6 +173,21 @@ void ILCodeStream::Emit(ILInstrEnum instr, INT16 iStackDelta, UINT_PTR uArg) pInstrBuffer[idxCurInstr].uInstruction = static_cast(instr); pInstrBuffer[idxCurInstr].iStackDelta = iStackDelta; pInstrBuffer[idxCurInstr].uArg = uArg; + + if(m_buildingEHClauses.GetCount() > 0) + { + ILStubEHClauseBuilder& clause = m_buildingEHClauses[m_buildingEHClauses.GetCount() - 1]; + + if (clause.tryBeginLabel != NULL && clause.tryEndLabel != NULL && + clause.handlerBeginLabel != NULL && clause.kind == ILStubEHClause::kTypedCatch) + { + if (clause.handlerBeginLabel->m_idxLabeledInstruction == idxCurInstr) + { + // Catch clauses start with an exception on the stack + pInstrBuffer[idxCurInstr].iStackDelta++; + } + } + } } ILCodeLabel* ILStubLinker::NewCodeLabel() @@ -1817,6 +1832,12 @@ void ILCodeStream::EmitUNALIGNED(BYTE alignment) Emit(CEE_UNALIGNED, 0, alignment); } +void ILCodeStream::EmitUNBOX(int token) +{ + WRAPPER_NO_CONTRACT; + Emit(CEE_UNBOX, 0, token); +} + void ILCodeStream::EmitUNBOX_ANY(int token) { WRAPPER_NO_CONTRACT; diff --git a/src/coreclr/vm/stubgen.h b/src/coreclr/vm/stubgen.h index ba42eae5be65a4..c848b0665008fa 100644 --- a/src/coreclr/vm/stubgen.h +++ b/src/coreclr/vm/stubgen.h @@ -434,7 +434,6 @@ class TokenLookupMap MODE_ANY; GC_NOTRIGGER; PRECONDITION(pMD != NULL); - PRECONDITION(typeSignature != mdTokenNil); PRECONDITION(methodSignature != mdTokenNil); } CONTRACTL_END; @@ -944,6 +943,7 @@ class ILCodeStream void EmitSUB (); void EmitTHROW (); void EmitUNALIGNED (BYTE alignment); + void EmitUNBOX (int token); void EmitUNBOX_ANY (int token); // Overloads to simplify common usage patterns diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index 8706d4ccf76dd2..387ad45260f167 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -1538,6 +1538,7 @@ Thread::Thread() #ifdef TARGET_X86 m_HijackReturnKind = RT_Illegal; + m_HijackHasAsyncRet = false; #endif m_currentPrepareCodeConfig = nullptr; diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index a1ca4943a6be8c..e582e1dfb21d3b 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -2669,7 +2669,7 @@ friend class DebuggerController; private: #ifdef FEATURE_HIJACK - void HijackThread(ExecutionState *esb X86_ARG(ReturnKind returnKind)); + void HijackThread(ExecutionState *esb X86_ARG(ReturnKind returnKind) X86_ARG(bool hasAsyncRet)); VOID *m_pvHJRetAddr; // original return address (before hijack) VOID **m_ppvHJRetAddrPtr; // place we bashed a new return address @@ -3846,20 +3846,23 @@ friend class DebuggerController; // By the time a frame is scanned by the runtime, m_pHijackReturnKind always // identifies the gc-ness of the return register(s) ReturnKind m_HijackReturnKind; + bool m_HijackHasAsyncRet; public: - ReturnKind GetHijackReturnKind() + ReturnKind GetHijackReturnKind(bool* hasAsyncRet) { LIMITED_METHOD_CONTRACT; + *hasAsyncRet = m_HijackHasAsyncRet; return m_HijackReturnKind; } - void SetHijackReturnKind(ReturnKind returnKind) + void SetHijackReturnKind(ReturnKind returnKind, bool hasAsyncRet) { LIMITED_METHOD_CONTRACT; m_HijackReturnKind = returnKind; + m_HijackHasAsyncRet = hasAsyncRet; } #endif #endif // FEATURE_HIJACK diff --git a/src/coreclr/vm/threadsuspend.cpp b/src/coreclr/vm/threadsuspend.cpp index 26f2502ca34dcf..04132694cb4cc6 100644 --- a/src/coreclr/vm/threadsuspend.cpp +++ b/src/coreclr/vm/threadsuspend.cpp @@ -4551,7 +4551,7 @@ struct ExecutionState }; // Client is responsible for suspending the thread before calling -void Thread::HijackThread(ExecutionState *esb X86_ARG(ReturnKind returnKind)) +void Thread::HijackThread(ExecutionState *esb X86_ARG(ReturnKind returnKind) X86_ARG(bool hasAsyncRet)) { CONTRACTL { NOTHROW; @@ -4576,7 +4576,7 @@ void Thread::HijackThread(ExecutionState *esb X86_ARG(ReturnKind returnKind)) pvHijackAddr = reinterpret_cast(OnHijackFPTripThread); } - SetHijackReturnKind(returnKind); + SetHijackReturnKind(returnKind, hasAsyncRet); #endif // TARGET_X86 // Don't hijack if are in the first level of running a filter/finally/catch. @@ -4916,10 +4916,17 @@ void STDCALL OnHijackWorker(HijackArgs * pArgs) #endif // HIJACK_NONINTERRUPTIBLE_THREADS } -static bool GetReturnAddressHijackInfo(EECodeInfo *pCodeInfo X86_ARG(ReturnKind * returnKind)) +static bool GetReturnAddressHijackInfo(EECodeInfo *pCodeInfo X86_ARG(ReturnKind * returnKind) X86_ARG(bool* hasAsyncRet)) { + X86_ONLY(*hasAsyncRet = false); GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken(); - return pCodeInfo->GetCodeManager()->GetReturnAddressHijackInfo(gcInfoToken X86_ARG(returnKind)); + if (!pCodeInfo->GetCodeManager()->GetReturnAddressHijackInfo(gcInfoToken X86_ARG(returnKind))) + return false; + + MethodDesc* pMD = pCodeInfo->GetMethodDesc(); + X86_ONLY(*hasAsyncRet = pMD->IsAsyncMethod()); + + return true; } #ifndef TARGET_UNIX @@ -5320,9 +5327,10 @@ BOOL Thread::HandledJITCase() EECodeInfo codeInfo(ip); X86_ONLY(ReturnKind returnKind;) - if (GetReturnAddressHijackInfo(&codeInfo X86_ARG(&returnKind))) + X86_ONLY(bool hasAsyncRet;) + if (GetReturnAddressHijackInfo(&codeInfo X86_ARG(&returnKind) X86_ARG(&hasAsyncRet))) { - HijackThread(&esb X86_ARG(returnKind)); + HijackThread(&esb X86_ARG(returnKind) X86_ARG(hasAsyncRet)); } } } @@ -5885,7 +5893,8 @@ void HandleSuspensionForInterruptedThread(CONTEXT *interruptedContext, bool susp return; X86_ONLY(ReturnKind returnKind;) - if (!GetReturnAddressHijackInfo(&codeInfo X86_ARG(&returnKind))) + X86_ONLY(bool hasAsyncRet;) + if (!GetReturnAddressHijackInfo(&codeInfo X86_ARG(&returnKind) X86_ARG(&hasAsyncRet))) { return; } @@ -5899,7 +5908,7 @@ void HandleSuspensionForInterruptedThread(CONTEXT *interruptedContext, bool susp StackWalkerWalkingThreadHolder threadStackWalking(pThread); // Hijack the return address to point to the appropriate routine based on the method's return type. - pThread->HijackThread(&executionState X86_ARG(returnKind)); + pThread->HijackThread(&executionState X86_ARG(returnKind) X86_ARG(hasAsyncRet)); } } diff --git a/src/coreclr/vm/zapsig.cpp b/src/coreclr/vm/zapsig.cpp index f4d1b7962f212f..b7d85f7ad20cf6 100644 --- a/src/coreclr/vm/zapsig.cpp +++ b/src/coreclr/vm/zapsig.cpp @@ -921,12 +921,15 @@ MethodDesc *ZapSig::DecodeMethod(ModuleBase *pInfoModule, // in non-generic structs. BOOL isInstantiatingStub = (methodFlags & ENCODE_METHOD_SIG_InstantiatingStub); BOOL isUnboxingStub = (methodFlags & ENCODE_METHOD_SIG_UnboxingStub); + bool isAsyncVariant = (methodFlags & ENCODE_METHOD_SIG_AsyncVariant) != 0; pMethod = MethodDesc::FindOrCreateAssociatedMethodDesc(pMethod, thOwner.GetMethodTable(), isUnboxingStub, inst, !(isInstantiatingStub || isUnboxingStub) && !actualOwnerRequired, - actualOwnerRequired); + actualOwnerRequired, + TRUE, + isAsyncVariant == pMethod->IsAsyncVariantMethod() ? AsyncVariantLookup::MatchingAsyncVariant : AsyncVariantLookup::AsyncOtherVariant); if (methodFlags & ENCODE_METHOD_SIG_Constrained) { @@ -1216,6 +1219,8 @@ BOOL ZapSig::EncodeMethod( methodFlags |= ENCODE_METHOD_SIG_InstantiatingStub; if (fMethodNeedsInstantiation) methodFlags |= ENCODE_METHOD_SIG_MethodInstantiation; + if (pMethod->IsAsyncVariantMethod()) + methodFlags |= ENCODE_METHOD_SIG_AsyncVariant; // Assume that the owner type is going to be needed methodFlags |= ENCODE_METHOD_SIG_OwnerType; diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 1fba187315a8c6..d3f62e4c34b7d9 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -808,6 +808,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodImplAttributes.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodImplAttributes.cs index eea971f54644ca..4a9cffa04eef69 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodImplAttributes.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodImplAttributes.cs @@ -31,6 +31,7 @@ public enum MethodImplAttributes AggressiveInlining = 0x0100, // Method should be inlined if possible. NoOptimization = 0x0040, // Method may not be optimized. AggressiveOptimization = 0x0200, // Method may contain hot code and should be aggressively optimized. + Async = 0x2000, MaxMethodImplVal = 0xffff, } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs new file mode 100644 index 00000000000000..238b7b42c06dc4 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.cs @@ -0,0 +1,172 @@ +// 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.Reflection; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Runtime.CompilerServices +{ + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + [System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5007", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] + public static partial class AsyncHelpers + { +#if CORECLR + // "BypassReadyToRun" is until AOT/R2R typesystem has support for MethodImpl.Async + // Must be NoInlining because we use AsyncSuspend to manufacture an explicit suspension point. + // It will not capture/restore any local state that is live across it. + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] + public static void AwaitAwaiter(TAwaiter awaiter) where TAwaiter : INotifyCompletion + { + ref AsyncHelpers.RuntimeAsyncAwaitState state = ref AsyncHelpers.t_runtimeAsyncAwaitState; + Continuation? sentinelContinuation = state.SentinelContinuation; + if (sentinelContinuation == null) + state.SentinelContinuation = sentinelContinuation = new Continuation(); + + state.Notifier = awaiter; + AsyncHelpers.AsyncSuspend(sentinelContinuation); + } + + // Must be NoInlining because we use AsyncSuspend to manufacture an explicit suspension point. + // It will not capture/restore any local state that is live across it. + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Async)] + public static void UnsafeAwaitAwaiter(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion + { + ref AsyncHelpers.RuntimeAsyncAwaitState state = ref AsyncHelpers.t_runtimeAsyncAwaitState; + Continuation? sentinelContinuation = state.SentinelContinuation; + if (sentinelContinuation == null) + state.SentinelContinuation = sentinelContinuation = new Continuation(); + + state.Notifier = awaiter; + AsyncHelpers.AsyncSuspend(sentinelContinuation); + } + + [Intrinsic] + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.Async)] + public static T Await(Task task) + { + TaskAwaiter awaiter = task.GetAwaiter(); + if (!awaiter.IsCompleted) + { + UnsafeAwaitAwaiter(awaiter); + } + + return awaiter.GetResult(); + } + + [Intrinsic] + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.Async)] + public static void Await(Task task) + { + TaskAwaiter awaiter = task.GetAwaiter(); + if (!awaiter.IsCompleted) + { + UnsafeAwaitAwaiter(awaiter); + } + + awaiter.GetResult(); + } + + [Intrinsic] + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.Async)] + public static T Await(ValueTask task) + { + ValueTaskAwaiter awaiter = task.GetAwaiter(); + if (!awaiter.IsCompleted) + { + UnsafeAwaitAwaiter(awaiter); + } + + return awaiter.GetResult(); + } + + [Intrinsic] + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.Async)] + public static void Await(ValueTask task) + { + ValueTaskAwaiter awaiter = task.GetAwaiter(); + if (!awaiter.IsCompleted) + { + UnsafeAwaitAwaiter(awaiter); + } + + awaiter.GetResult(); + } + + [Intrinsic] + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.Async)] + public static void Await(ConfiguredTaskAwaitable configuredAwaitable) + { + ConfiguredTaskAwaitable.ConfiguredTaskAwaiter awaiter = configuredAwaitable.GetAwaiter(); + if (!awaiter.IsCompleted) + { + UnsafeAwaitAwaiter(awaiter); + } + + awaiter.GetResult(); + } + + [Intrinsic] + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.Async)] + public static void Await(ConfiguredValueTaskAwaitable configuredAwaitable) + { + ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter awaiter = configuredAwaitable.GetAwaiter(); + if (!awaiter.IsCompleted) + { + UnsafeAwaitAwaiter(awaiter); + } + + awaiter.GetResult(); + } + + [Intrinsic] + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.Async)] + public static T Await(ConfiguredTaskAwaitable configuredAwaitable) + { + ConfiguredTaskAwaitable.ConfiguredTaskAwaiter awaiter = configuredAwaitable.GetAwaiter(); + if (!awaiter.IsCompleted) + { + UnsafeAwaitAwaiter(awaiter); + } + + return awaiter.GetResult(); + } + + [Intrinsic] + [BypassReadyToRun] + [MethodImpl(MethodImplOptions.Async)] + public static T Await(ConfiguredValueTaskAwaitable configuredAwaitable) + { + ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter awaiter = configuredAwaitable.GetAwaiter(); + if (!awaiter.IsCompleted) + { + UnsafeAwaitAwaiter(awaiter); + } + + return awaiter.GetResult(); + } +#else + public static void UnsafeAwaitAwaiter(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion { throw new NotImplementedException(); } + public static void AwaitAwaiter(TAwaiter awaiter) where TAwaiter : INotifyCompletion { throw new NotImplementedException(); } + public static void Await(System.Threading.Tasks.Task task) { throw new NotImplementedException(); } + public static T Await(System.Threading.Tasks.Task task) { throw new NotImplementedException(); } + public static void Await(System.Threading.Tasks.ValueTask task) { throw new NotImplementedException(); } + public static T Await(System.Threading.Tasks.ValueTask task) { throw new NotImplementedException(); } + public static void Await(System.Runtime.CompilerServices.ConfiguredTaskAwaitable configuredAwaitable) { throw new NotImplementedException(); } + public static void Await(System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable configuredAwaitable) { throw new NotImplementedException(); } + public static T Await(System.Runtime.CompilerServices.ConfiguredTaskAwaitable configuredAwaitable) { throw new NotImplementedException(); } + public static T Await(System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable configuredAwaitable) { throw new NotImplementedException(); } +#endif + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/MethodImplOptions.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/MethodImplOptions.cs index e4ae987873b6a3..c7cc6b90c2dd24 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/MethodImplOptions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/MethodImplOptions.cs @@ -16,6 +16,7 @@ public enum MethodImplOptions PreserveSig = 0x0080, AggressiveInlining = 0x0100, AggressiveOptimization = 0x0200, + Async = 0x2000, InternalCall = 0x1000 } } 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 ce4f18b6099baf..1e96a5905b29e2 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 @@ -513,6 +513,7 @@ internal override void InnerInvoke() /// true to attempt to marshal the continuation back to the original context captured; otherwise, false. /// /// An object used to await this task. + [Intrinsic] public new ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) { return new ConfiguredTaskAwaitable(this, continueOnCapturedContext ? ConfigureAwaitOptions.ContinueOnCapturedContext : ConfigureAwaitOptions.None); @@ -522,6 +523,7 @@ internal override void InnerInvoke() /// Options used to configure how awaits on this task are performed. /// An object used to await this task. /// The argument specifies an invalid value. + [Intrinsic] public new ConfiguredTaskAwaitable ConfigureAwait(ConfigureAwaitOptions options) { if ((options & ~(ConfigureAwaitOptions.ContinueOnCapturedContext | 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 d007bc756e8bdb..0f64ba8ea52f94 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 @@ -2448,6 +2448,7 @@ public TaskAwaiter GetAwaiter() /// true to attempt to marshal the continuation back to the original context captured; otherwise, false. /// /// An object used to await this task. + [Intrinsic] public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) { return new ConfiguredTaskAwaitable(this, continueOnCapturedContext ? ConfigureAwaitOptions.ContinueOnCapturedContext : ConfigureAwaitOptions.None); @@ -2457,6 +2458,7 @@ public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) /// Options used to configure how awaits on this task are performed. /// An object used to await this task. /// The argument specifies an invalid value. + [Intrinsic] public ConfiguredTaskAwaitable ConfigureAwait(ConfigureAwaitOptions options) { if ((options & ~(ConfigureAwaitOptions.ContinueOnCapturedContext | diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs index c8d0aec3960bbe..90f86c68159c28 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs @@ -425,6 +425,7 @@ internal static ValueTask DangerousCreateFromTypedValueTask(ValueTask /// true to attempt to marshal the continuation back to the captured context; otherwise, false. /// + [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) => new ConfiguredValueTaskAwaitable(new ValueTask(_obj, _token, continueOnCapturedContext)); @@ -825,6 +826,7 @@ public TResult Result /// /// true to attempt to marshal the continuation back to the captured context; otherwise, false. /// + [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) => new ConfiguredValueTaskAwaitable(new ValueTask(_obj, _result, _token, continueOnCapturedContext)); diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 7b4ed3169e9e7e..84742f4e9a03fe 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -12369,6 +12369,7 @@ public enum MethodImplAttributes AggressiveInlining = 256, AggressiveOptimization = 512, InternalCall = 4096, + Async = 8192, MaxMethodImplVal = 65535, } public abstract partial class MethodInfo : System.Reflection.MethodBase @@ -13669,6 +13670,7 @@ public enum MethodImplOptions PreserveSig = 128, AggressiveInlining = 256, AggressiveOptimization = 512, + Async = 8192, InternalCall = 4096, } [System.AttributeUsageAttribute(System.AttributeTargets.Method, Inherited=false)] @@ -13826,6 +13828,21 @@ public static void RunModuleConstructor(System.ModuleHandle module) { } public delegate void CleanupCode(object? userData, bool exceptionThrown); public delegate void TryCode(object? userData); } + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + [System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SYSLIB5007", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] + public static partial class AsyncHelpers + { + public static void UnsafeAwaitAwaiter(TAwaiter awaiter) where TAwaiter : ICriticalNotifyCompletion { } + public static void AwaitAwaiter(TAwaiter awaiter) where TAwaiter : INotifyCompletion { } + public static void Await(System.Threading.Tasks.Task task) { throw null; } + public static T Await(System.Threading.Tasks.Task task) { throw null; } + public static void Await(System.Threading.Tasks.ValueTask task) { throw null; } + public static T Await(System.Threading.Tasks.ValueTask task) { throw null; } + public static void Await(System.Runtime.CompilerServices.ConfiguredTaskAwaitable configuredAwaitable) { throw null; } + public static void Await(System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable configuredAwaitable) { throw null; } + public static T Await(System.Runtime.CompilerServices.ConfiguredTaskAwaitable configuredAwaitable) { throw null; } + public static T Await(System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable configuredAwaitable) { throw null; } + } public sealed partial class RuntimeWrappedException : System.Exception { public RuntimeWrappedException(object thrownObject) { } diff --git a/src/tests/reflection/ldtoken/ldtokenmember.il b/src/tests/reflection/ldtoken/ldtokenmember.il index 4187a13f4f9068..96cd9fa19dd99c 100644 --- a/src/tests/reflection/ldtoken/ldtokenmember.il +++ b/src/tests/reflection/ldtoken/ldtokenmember.il @@ -21,6 +21,7 @@ .locals init (class [mscorlib]System.Type nonGenericHolderType, class [mscorlib]System.Reflection.FieldInfo staticField, class [mscorlib]System.Reflection.MethodBase staticInt32Method, + class [mscorlib]System.Reflection.MethodBase staticTaskMethod, class [mscorlib]System.Reflection.MethodBase staticMethodT, class [mscorlib]System.Reflection.MethodBase staticMethodU, class [mscorlib]System.Reflection.FieldInfo staticFieldT, @@ -72,7 +73,7 @@ NonGenericStaticFieldOK: stloc staticInt32Method // - // if (staticInt32Method.Name != "StaticField" || staticInt32Method.DeclaringType != nonGenericHolderType) + // if (staticInt32Method.Name != "StaticMethod" || staticInt32Method.DeclaringType != nonGenericHolderType) // return 2; // ldloc staticInt32Method @@ -89,6 +90,33 @@ NonGenericStaticFieldOK: ret NonGenericStaticMethodOK: + // + // MethodInfo staticTaskMethod = infoof(NonGenericHolder.StaticTaskMethod) + // + ldtoken method class [mscorlib]System.Threading.Tasks.Task NonGenericHolder::StaticTaskMethod() + call class [mscorlib]System.Reflection.MethodBase + [mscorlib]System.Reflection.MethodBase::GetMethodFromHandle( + valuetype [mscorlib]System.RuntimeMethodHandle) + stloc staticTaskMethod + + // + // if (staticTaskMethod.Name != "StaticTaskMethod" || staticTaskMethod.DeclaringType != nonGenericHolderType) + // return 2; + // + ldloc staticTaskMethod + callvirt instance string [mscorlib]System.Reflection.MethodBase::get_Name() + ldstr "StaticTaskMethod" + callvirt instance bool [mscorlib]System.Object::Equals(object) + ldloc staticTaskMethod + callvirt instance class [mscorlib]System.Type [mscorlib]System.Reflection.MethodBase::get_DeclaringType() + ldloc nonGenericHolderType + ceq + and + brtrue NonGenericStaticTaskMethodOK + ldc.i4 2 + ret +NonGenericStaticTaskMethodOK: + // // if (staticInt32Method.Equals(infoof(NonGenericHolder.StaticMethod)) // return 3; @@ -315,6 +343,12 @@ OtherWeirdoGenericMethodOwningTypeOK: ret } + .method public hidebysig static class [mscorlib]System.Threading.Tasks.Task StaticTaskMethod() + { + ldnull + ret + } + .method public hidebysig static void GenericMethod() { ret diff --git a/src/tests/reflection/ldtoken/ldtokenmember.ilproj b/src/tests/reflection/ldtoken/ldtokenmember.ilproj index 364fa6e2741f62..cad109dcb652a2 100644 --- a/src/tests/reflection/ldtoken/ldtokenmember.ilproj +++ b/src/tests/reflection/ldtoken/ldtokenmember.ilproj @@ -2,7 +2,6 @@ true - 1