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 index a7ab5415d25d97..2fde8dac73caa9 100644 --- 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 @@ -10,6 +10,7 @@ using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; +using System.Threading.Tasks.Sources; namespace System.Runtime.CompilerServices { @@ -491,6 +492,50 @@ public static void HandleSuspended(T task) where T : Task, ITaskComplet } else if (calledTask != null) { + if (calledTask is IValueTaskAsTask vtTask) + { + if (!vtTask.IsConfigured) + { + // ValueTaskSource can be configured to use scheduling context or to ignore it. + // The awaiter must inform the source whether it wants to continue on a context, + // but the source may decide to ignore the suggestion. Since the behavior of + // the source takes precedence, we clear the context flags from the awaiting + // continuation (so it will run transparently on what source decides) and tell + // the source that awaiting frame prefers to continue on a context. + // The reason why we do it here is because the continuation chain builds from the + // innermost frame out and when the leaf thunk links the head continuation, + // it does not know if the caller wants to continue in a context. Thus the thunk + // creates an "unconfigured" IValueTaskAsTask and we configure it here. + ValueTaskSourceOnCompletedFlags configFlags = ValueTaskSourceOnCompletedFlags.None; + CorInfoContinuationFlags continuationFlags = headContinuation.Next!.Flags; + + const CorInfoContinuationFlags continueOnContextFlags = + CorInfoContinuationFlags.CORINFO_CONTINUATION_CONTINUE_ON_CAPTURED_SYNCHRONIZATION_CONTEXT | + CorInfoContinuationFlags.CORINFO_CONTINUATION_CONTINUE_ON_CAPTURED_TASK_SCHEDULER; + + if ((continuationFlags & continueOnContextFlags) != 0) + { + // if await has captured some context, inform the source + configFlags |= ValueTaskSourceOnCompletedFlags.UseSchedulingContext; + } + + // clear continuation flags, so that continuation runs transparently + headContinuation.Next!.Flags &= ~continueFlags; + vtTask.Configure(configFlags); + + if (!calledTask.TryAddCompletionAction(task)) + { + // calledTask has already completed and we need to schedule + // the continuation for execution ourselves. + // Restore the continuation flags before doing that. + headContinuation.Next!.Flags = continuationFlags; + ThreadPool.UnsafeQueueUserWorkItemInternal(task, preferLocal: true); + } + + return; + } + } + // Runtime async callable wrapper for task returning // method. This implements the context transparent // forwarding and makes these wrappers minimal cost. diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index e5dc2fe9d8fd06..d95d6c2b7a2857 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -715,7 +715,7 @@ RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableRiscV64Zbb, W("EnableRiscV64 #endif // Runtime-async -RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_RuntimeAsync, W("RuntimeAsync"), 0, "Enables runtime async method support") +RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_RuntimeAsync, W("RuntimeAsync"), 1, "Enables runtime async method support") /// /// Uncategorized diff --git a/src/coreclr/jit/async.cpp b/src/coreclr/jit/async.cpp index ec92f9b23ca6a7..2fa1006fd81d6c 100644 --- a/src/coreclr/jit/async.cpp +++ b/src/coreclr/jit/async.cpp @@ -958,18 +958,25 @@ ContinuationLayout AsyncTransformation::LayOutContinuation(BasicBlock* if (dsc->TypeIs(TYP_STRUCT) || dsc->IsImplicitByRef()) { ClassLayout* layout = dsc->GetLayout(); - assert(!layout->HasGCByRef()); + + // TODO: (async) we do not need to save ByRef-containing locals + // as by the spec an await turns them into zero-inited state. + // For now just store/restore as if there are no gc refs. + // This is mostly to handle the "fake" live-across-await byrefs + // in min-opts, since C#-compiled code by itself does not let + // byrefs be live across awaits. + unsigned objCount = layout->HasGCByRef() ? 0 : layout->GetGCPtrCount(); if (layout->IsCustomLayout()) { inf.Alignment = 1; inf.DataSize = layout->GetSize(); - inf.GCDataCount = layout->GetGCPtrCount(); + inf.GCDataCount = objCount; } else { inf.Alignment = m_comp->info.compCompHnd->getClassAlignmentRequirement(layout->GetClassHandle()); - if ((layout->GetGCPtrCount() * TARGET_POINTER_SIZE) == layout->GetSize()) + if ((objCount * TARGET_POINTER_SIZE) == layout->GetSize()) { inf.DataSize = 0; } @@ -978,7 +985,7 @@ ContinuationLayout AsyncTransformation::LayOutContinuation(BasicBlock* inf.DataSize = layout->GetSize(); } - inf.GCDataCount = layout->GetGCPtrCount(); + inf.GCDataCount = objCount; } } else if (dsc->TypeIs(TYP_REF)) @@ -987,10 +994,17 @@ ContinuationLayout AsyncTransformation::LayOutContinuation(BasicBlock* inf.DataSize = 0; inf.GCDataCount = 1; } + else if (dsc->TypeIs(TYP_BYREF)) + { + // TODO: (async) ByRefs do not need to be saved at all. + // For now pretend they are unmanaged data. + // See the note on `layout->HasGCByRef()` case for justification. + inf.Alignment = TARGET_POINTER_SIZE; + inf.DataSize = TARGET_POINTER_SIZE; + inf.GCDataCount = 0; + } else { - assert(!dsc->TypeIs(TYP_BYREF)); - inf.Alignment = genTypeAlignments[dsc->TypeGet()]; inf.DataSize = genTypeSize(dsc); inf.GCDataCount = 0; @@ -1695,6 +1709,15 @@ void AsyncTransformation::CreateCheckAndSuspendAfterCall(BasicBlock* *remainder = m_comp->fgSplitBlockAfterNode(block, jtrue); JITDUMP(" Remainder is " FMT_BB "\n", (*remainder)->bbNum); + // HACK: Not sure why it can happen, but we may see the end IL for the block + // to increasing after splitting off its tail. + // This tweak is just to avoid asserts later on. + // This is not a real fix. + if (block->bbCodeOffsEnd > (*remainder)->bbCodeOffs) + { + block->bbCodeOffsEnd = (*remainder)->bbCodeOffs; + } + FlowEdge* retBBEdge = m_comp->fgAddRefPred(suspendBB, block); block->SetCond(retBBEdge, block->GetTargetEdge()); diff --git a/src/coreclr/vm/asyncthunks.cpp b/src/coreclr/vm/asyncthunks.cpp index cf4e290bc6e7c4..ccc0c615d23d8f 100644 --- a/src/coreclr/vm/asyncthunks.cpp +++ b/src/coreclr/vm/asyncthunks.cpp @@ -591,7 +591,7 @@ void MethodDesc::EmitAsyncMethodThunk(MethodDesc* pAsyncOtherVariant, MetaSig& m MethodDesc* pMDValueTaskIsCompleted = CoreLibBinder::GetMethod(METHOD__VALUETASK__GET_ISCOMPLETED); MethodDesc* pMDCompletionResult = CoreLibBinder::GetMethod(METHOD__VALUETASK__THROW_IF_COMPLETED_UNSUCCESSFULLY); - MethodDesc* pMDAsTask = CoreLibBinder::GetMethod(METHOD__VALUETASK__AS_TASK); + MethodDesc* pMDAsTask = CoreLibBinder::GetMethod(METHOD__VALUETASK__AS_UNCONFIGURED_TASK); isCompletedToken = pCode->GetToken(pMDValueTaskIsCompleted); completionResultToken = pCode->GetToken(pMDCompletionResult); @@ -604,7 +604,7 @@ void MethodDesc::EmitAsyncMethodThunk(MethodDesc* pAsyncOtherVariant, MetaSig& m MethodDesc* pMDValueTaskIsCompleted = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__GET_ISCOMPLETED); MethodDesc* pMDCompletionResult = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__GET_RESULT); - MethodDesc* pMDAsTask = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__AS_TASK); + MethodDesc* pMDAsTask = CoreLibBinder::GetMethod(METHOD__VALUETASK_1__AS_UNCONFIGURED_TASK); pMDValueTaskIsCompleted = FindOrCreateAssociatedMethodDesc(pMDValueTaskIsCompleted, pMTValueTask, FALSE, Instantiation(), FALSE); pMDCompletionResult = FindOrCreateAssociatedMethodDesc(pMDCompletionResult, pMTValueTask, FALSE, Instantiation(), FALSE); diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index bfc6cc586ca38c..b9429f2591a31c 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -346,7 +346,7 @@ DEFINE_METHOD(THREAD_START_EXCEPTION,EX_CTOR, .ctor, DEFINE_CLASS(VALUETASK_1, Tasks, ValueTask`1) DEFINE_METHOD(VALUETASK_1, GET_ISCOMPLETED, get_IsCompleted, NoSig) DEFINE_METHOD(VALUETASK_1, GET_RESULT, get_Result, NoSig) -DEFINE_METHOD(VALUETASK_1, AS_TASK, AsTask, IM_RetTaskOfT) +DEFINE_METHOD(VALUETASK_1, AS_UNCONFIGURED_TASK, AsUnconfiguredTask, IM_RetTaskOfT) DEFINE_CLASS(VALUETASK, Tasks, ValueTask) DEFINE_METHOD(VALUETASK, FROM_EXCEPTION, FromException, SM_Exception_RetValueTask) @@ -355,7 +355,7 @@ DEFINE_METHOD(VALUETASK, FROM_RESULT_T, FromResult, GM_T_RetValueTaskOfT) DEFINE_METHOD(VALUETASK, GET_COMPLETED_TASK, get_CompletedTask, SM_RetValueTask) DEFINE_METHOD(VALUETASK, GET_ISCOMPLETED, get_IsCompleted, NoSig) DEFINE_METHOD(VALUETASK, THROW_IF_COMPLETED_UNSUCCESSFULLY, ThrowIfCompletedUnsuccessfully, NoSig) -DEFINE_METHOD(VALUETASK, AS_TASK, AsTask, IM_RetTask) +DEFINE_METHOD(VALUETASK, AS_UNCONFIGURED_TASK, AsUnconfiguredTask, IM_RetTask) DEFINE_CLASS(TASK_1, Tasks, Task`1) DEFINE_METHOD(TASK_1, GET_RESULTONSUCCESS, get_ResultOnSuccess, NoSig) diff --git a/src/libraries/Common/src/System/Runtime/CompilerServices/RuntimeAsyncMethodGenerationAttribute.cs b/src/libraries/Common/src/System/Runtime/CompilerServices/RuntimeAsyncMethodGenerationAttribute.cs new file mode 100644 index 00000000000000..2ef6715f442511 --- /dev/null +++ b/src/libraries/Common/src/System/Runtime/CompilerServices/RuntimeAsyncMethodGenerationAttribute.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.CompilerServices; + +[AttributeUsage(AttributeTargets.Method)] +internal sealed class RuntimeAsyncMethodGenerationAttribute(bool runtimeAsync) : Attribute +{ + public bool RuntimeAsync { get; } = runtimeAsync; +} diff --git a/src/libraries/Directory.Build.props b/src/libraries/Directory.Build.props index 33938989e386de..39d5070b001ec6 100644 --- a/src/libraries/Directory.Build.props +++ b/src/libraries/Directory.Build.props @@ -101,4 +101,9 @@ + + $(NoWarn);xUnit1013;CS1998;SYSLIB5007 + $(Features);runtime-async=on + + diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricOuterLoopTests/MetricOuterLoop.Tests.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricOuterLoopTests/MetricOuterLoop.Tests.csproj index 428a2d9bc39145..07cf8d17613c02 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricOuterLoopTests/MetricOuterLoop.Tests.csproj +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricOuterLoopTests/MetricOuterLoop.Tests.csproj @@ -3,7 +3,7 @@ $(NetCoreAppCurrent);$(NetCoreAppCurrent)-browser;$(NetFrameworkCurrent) true - NU1511 + $(NoWarn);NU1511 diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricOuterLoopTests/MetricOuterLoop1.Tests.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricOuterLoopTests/MetricOuterLoop1.Tests.csproj index 6f55c5c3b27dcc..b69139b4912ff2 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricOuterLoopTests/MetricOuterLoop1.Tests.csproj +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricOuterLoopTests/MetricOuterLoop1.Tests.csproj @@ -3,7 +3,7 @@ $(NetCoreAppCurrent);$(NetCoreAppCurrent)-browser;$(NetFrameworkCurrent) true - NU1511 + $(NoWarn);NU1511 diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj index b673f5b94ea7d4..944378b84e89df 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj @@ -3,7 +3,7 @@ $(NetCoreAppCurrent);$(NetCoreAppCurrent)-browser;$(NetFrameworkCurrent) true - NU1511 + $(NoWarn);NU1511 true diff --git a/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj index 084eb74b3dda48..62e063086e3add 100644 --- a/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj +++ b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj @@ -22,6 +22,7 @@ + diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.SslConnectionOptions.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.SslConnectionOptions.cs index 50600b09611688..ebb7141ff1d3d1 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.SslConnectionOptions.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.SslConnectionOptions.cs @@ -66,6 +66,8 @@ public SslConnectionOptions(QuicConnection connection, bool isClient, _certificateChainPolicy = certificateChainPolicy; } + // Roslyn NYI - async in structs. Remove opt-out once supported. + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] internal async Task StartAsyncCertificateValidation(IntPtr certificatePtr, IntPtr chainPtr) { // 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 90f86c68159c28..87185e18eed710 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 @@ -176,7 +176,17 @@ public Task AsTask() return obj == null ? Task.CompletedTask : obj as Task ?? - GetTaskForValueTaskSource(Unsafe.As(obj)); + GetTaskForValueTaskSource(Unsafe.As(obj), ValueTaskSourceOnCompletedFlags.None); + } + + internal Task AsUnconfiguredTask() + { + object? obj = _obj; + Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource); + return + obj == null ? Task.CompletedTask : + obj as Task ?? + GetTaskForValueTaskSource(Unsafe.As(obj), IValueTaskAsTask._unconfigured); } /// Gets a that may be used at any point in the future. @@ -187,7 +197,7 @@ obj as Task ?? /// The is passed in rather than reading and casting /// so that the caller can pass in an object it's already validated. /// - private Task GetTaskForValueTaskSource(IValueTaskSource t) + private Task GetTaskForValueTaskSource(IValueTaskSource t, ValueTaskSourceOnCompletedFlags flags) { ValueTaskSourceStatus status = t.GetStatus(_token); if (status != ValueTaskSourceStatus.Pending) @@ -225,11 +235,11 @@ private Task GetTaskForValueTaskSource(IValueTaskSource t) } } - return new ValueTaskSourceAsTask(t, _token); + return new ValueTaskSourceAsTask(t, _token, flags); } /// Type used to create a to represent a . - private sealed class ValueTaskSourceAsTask : Task + private sealed class ValueTaskSourceAsTask : Task, IValueTaskAsTask { private static readonly Action s_completionAction = static state => { @@ -283,11 +293,28 @@ state is ValueTaskSourceAsTask vsts ? /// The token to pass through to operations on private readonly short _token; - internal ValueTaskSourceAsTask(IValueTaskSource source, short token) + private bool _configured; + + bool IValueTaskAsTask.IsConfigured => _configured; + void IValueTaskAsTask.Configure(ValueTaskSourceOnCompletedFlags flags) => Configure(flags); + + private void Configure(ValueTaskSourceOnCompletedFlags flags) + { + Debug.Assert(!_configured); + + _configured = true; + _source!.OnCompleted(s_completionAction, this, _token, flags); + } + + internal ValueTaskSourceAsTask(IValueTaskSource source, short token, ValueTaskSourceOnCompletedFlags flags) { _token = token; _source = source; - source.OnCompleted(s_completionAction, this, token, ValueTaskSourceOnCompletedFlags.None); + + if (flags != IValueTaskAsTask._unconfigured) + { + Configure(flags); + } } } @@ -585,7 +612,25 @@ public Task AsTask() return t; } - return GetTaskForValueTaskSource(Unsafe.As>(obj)); + return GetTaskForValueTaskSource(Unsafe.As>(obj), ValueTaskSourceOnCompletedFlags.None); + } + + internal Task AsUnconfiguredTask() + { + object? obj = _obj; + Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource); + + if (obj == null) + { + return Task.FromResult(_result!); + } + + if (obj is Task t) + { + return t; + } + + return GetTaskForValueTaskSource(Unsafe.As>(obj), IValueTaskAsTask._unconfigured); } /// Gets a that may be used at any point in the future. @@ -596,7 +641,7 @@ public Task AsTask() /// The is passed in rather than reading and casting /// so that the caller can pass in an object it's already validated. /// - private Task GetTaskForValueTaskSource(IValueTaskSource t) + private Task GetTaskForValueTaskSource(IValueTaskSource t, ValueTaskSourceOnCompletedFlags flags) { ValueTaskSourceStatus status = t.GetStatus(_token); if (status != ValueTaskSourceStatus.Pending) @@ -633,11 +678,11 @@ private Task GetTaskForValueTaskSource(IValueTaskSource t) } } - return new ValueTaskSourceAsTask(t, _token); + return new ValueTaskSourceAsTask(t, _token, flags); } /// Type used to create a to represent a . - private sealed class ValueTaskSourceAsTask : Task + private sealed class ValueTaskSourceAsTask : Task, IValueTaskAsTask { private static readonly Action s_completionAction = static state => { @@ -690,11 +735,28 @@ state is ValueTaskSourceAsTask vsts ? /// The token to pass through to operations on private readonly short _token; - public ValueTaskSourceAsTask(IValueTaskSource source, short token) + private bool _configured; + + bool IValueTaskAsTask.IsConfigured => _configured; + void IValueTaskAsTask.Configure(ValueTaskSourceOnCompletedFlags flags) => Configure(flags); + + private void Configure(ValueTaskSourceOnCompletedFlags flags) + { + Debug.Assert(!_configured); + + _configured = true; + _source!.OnCompleted(s_completionAction, this, _token, flags); + } + + internal ValueTaskSourceAsTask(IValueTaskSource source, short token, ValueTaskSourceOnCompletedFlags flags) { - _source = source; _token = token; - source.OnCompleted(s_completionAction, this, token, ValueTaskSourceOnCompletedFlags.None); + _source = source; + + if (flags != IValueTaskAsTask._unconfigured) + { + Configure(flags); + } } } @@ -848,4 +910,12 @@ public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCaptu return string.Empty; } } + + internal interface IValueTaskAsTask + { + internal const ValueTaskSourceOnCompletedFlags _unconfigured = (ValueTaskSourceOnCompletedFlags)(-1); + + bool IsConfigured { get; } + void Configure(ValueTaskSourceOnCompletedFlags flags); + } } diff --git a/src/libraries/System.Private.Xml.Linq/src/System.Private.Xml.Linq.csproj b/src/libraries/System.Private.Xml.Linq/src/System.Private.Xml.Linq.csproj index 1d1a2acfd55003..cfc61a511f6eb5 100644 --- a/src/libraries/System.Private.Xml.Linq/src/System.Private.Xml.Linq.csproj +++ b/src/libraries/System.Private.Xml.Linq/src/System.Private.Xml.Linq.csproj @@ -8,6 +8,7 @@ + diff --git a/src/libraries/System.Private.Xml.Linq/src/System/Xml/Linq/XLinq.cs b/src/libraries/System.Private.Xml.Linq/src/System/Xml/Linq/XLinq.cs index a5b7cc8c416124..126942ca796dea 100644 --- a/src/libraries/System.Private.Xml.Linq/src/System/Xml/Linq/XLinq.cs +++ b/src/libraries/System.Private.Xml.Linq/src/System/Xml/Linq/XLinq.cs @@ -247,6 +247,8 @@ public void WriteElement(XElement e) } } + // Roslyn NYI - async in structs. Remove opt-out once supported. + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public async Task WriteElementAsync(XElement e, CancellationToken cancellationToken) { PushAncestors(e); @@ -347,6 +349,8 @@ private void WriteEndElement() _resolver.PopScope(); } + // Roslyn NYI - async in structs. Remove opt-out once supported. + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private async Task WriteEndElementAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -360,6 +364,8 @@ private void WriteFullEndElement() _resolver.PopScope(); } + // Roslyn NYI - async in structs. Remove opt-out once supported. + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private async Task WriteFullEndElementAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -386,6 +392,8 @@ private void WriteStartElement(XElement e) } } + // Roslyn NYI - async in structs. Remove opt-out once supported. + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private async Task WriteStartElementAsync(XElement e, CancellationToken cancellationToken) { PushElement(e); diff --git a/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj b/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj index bd3f70ad7a344a..5ccc750456d6ad 100644 --- a/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj +++ b/src/libraries/System.Private.Xml/src/System.Private.Xml.csproj @@ -9,6 +9,7 @@ + diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlWellFormedWriterHelpersAsync.cs b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlWellFormedWriterHelpersAsync.cs index 5ff3b5a7cb98bc..2655d14475f7ab 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlWellFormedWriterHelpersAsync.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlWellFormedWriterHelpersAsync.cs @@ -26,6 +26,8 @@ internal Task WriteFullEndElementAsync(XmlRawWriter rawWriter) private partial struct Namespace { + // Roslyn NYI - async in structs. Remove opt-out once supported. + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] internal async Task WriteDeclAsync(XmlWriter writer, XmlRawWriter? rawWriter) { Debug.Assert(kind == NamespaceKind.NeedToWrite); diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index cf127ba8b569a5..e46451aced35d1 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -23,6 +23,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET + diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/PipeReadBufferState.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/PipeReadBufferState.cs index 45bb1d85351770..d5025bca574fb5 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/PipeReadBufferState.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/PipeReadBufferState.cs @@ -50,6 +50,8 @@ public void Advance(long bytesConsumed) /// Calling ReadCore is relatively expensive, so we minimize the number of times /// we need to call it. /// + // Roslyn NYI - async in structs. Remove opt-out once supported. + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public async ValueTask ReadAsync(PipeReader utf8Json, CancellationToken cancellationToken, bool fillBuffer = true) { Debug.Assert(_sequence.Equals(ReadOnlySequence.Empty), "ReadAsync should only be called when the buffer is empty."); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/StreamReadBufferState.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/StreamReadBufferState.cs index 65573c58bf7f04..b1ad801f8eaee0 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/StreamReadBufferState.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/StreamReadBufferState.cs @@ -60,6 +60,8 @@ public StreamReadBufferState(int initialBufferSize) /// Calling ReadCore is relatively expensive, so we minimize the number of times /// we need to call it. /// + // Roslyn NYI - async in structs. Remove opt-out once supported. + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public readonly async ValueTask ReadAsync(Stream stream, CancellationToken cancellationToken, bool fillBuffer = true) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs index 78414a16d744bf..b18d866e622100 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/WriteStack.cs @@ -281,6 +281,8 @@ public void AddCompletedAsyncDisposable(IAsyncDisposable asyncDisposable) => (CompletedAsyncDisposables ??= new List()).Add(asyncDisposable); // Asynchronously dispose of any AsyncDisposables that have been scheduled for disposal + // Roslyn NYI - async in structs. Remove opt-out once supported. + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public readonly async ValueTask DisposeCompletedAsyncDisposables() { Debug.Assert(CompletedAsyncDisposables?.Count > 0); @@ -361,6 +363,8 @@ static void DisposeFrame(IEnumerator? collectionEnumerator, ref Exception? excep /// Walks the stack cleaning up any leftover I(Async)Disposables /// in the event of an exception on async serialization /// + // Roslyn NYI - async in structs. Remove opt-out once supported. + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public readonly async ValueTask DisposePendingDisposablesOnExceptionAsync() { Exception? exception = null; diff --git a/src/tests/async/Directory.Build.props b/src/tests/async/Directory.Build.props index a1f7d48cf60a7d..f505092384b1b1 100644 --- a/src/tests/async/Directory.Build.props +++ b/src/tests/async/Directory.Build.props @@ -5,7 +5,7 @@ true - $(NoWarn);xUnit1013;CS1998 + $(NoWarn);xUnit1013;CS1998;SYSLIB5007 false $(Features);runtime-async=on diff --git a/src/tests/async/Directory.Build.targets b/src/tests/async/Directory.Build.targets index 5a4d413a9e1f62..61a1b2822ed114 100644 --- a/src/tests/async/Directory.Build.targets +++ b/src/tests/async/Directory.Build.targets @@ -4,10 +4,5 @@ true - - - true - - diff --git a/src/tests/async/struct/struct.cs b/src/tests/async/struct/struct.cs index 7c5062d499cb42..32116fe3cec777 100644 --- a/src/tests/async/struct/struct.cs +++ b/src/tests/async/struct/struct.cs @@ -45,6 +45,8 @@ private struct S public S(int value) => Value = value; + // Roslyn NYI - async in structs. Remove opt-out once supported. + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] public async Task Test() { // TODO: C# compiler is expected to do this, but not in the prototype. @@ -59,6 +61,8 @@ public async Task Test() AssertEqual(102, @this.Value); } + // Roslyn NYI - async in structs. Remove opt-out once supported. + [System.Runtime.CompilerServices.RuntimeAsyncMethodGeneration(false)] private async Task InstanceCall() { // TODO: C# compiler is expected to do this, but not in the prototype. diff --git a/src/tests/async/valuetask Source/valuetaskSource.cs b/src/tests/async/valuetask Source/valuetaskSource.cs new file mode 100644 index 00000000000000..3bf080a1f93a09 --- /dev/null +++ b/src/tests/async/valuetask Source/valuetaskSource.cs @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using System.Threading.Tasks.Sources; +using Xunit; + +public class Async2ValueTaskSource +{ + static string trace; + + [Fact] + static void TestEntry() + { + ValueTask vtUseContext = new ValueTask(new Source(true), 1); + + AwaitConfigDefault(vtUseContext).GetAwaiter().GetResult(); + Assert.Equal("UseSchedulingContext", trace); + AwaitConfigTrue(vtUseContext).GetAwaiter().GetResult(); + Assert.Equal("UseSchedulingContext", trace); + AwaitConfigFalse(vtUseContext).GetAwaiter().GetResult(); + Assert.Equal("None", trace); + + ValueTask vtIgnoreContext = new ValueTask(new Source(true), 1); + AwaitConfigDefault(vtIgnoreContext).GetAwaiter().GetResult(); + Assert.Equal("UseSchedulingContext", trace); + AwaitConfigTrue(vtIgnoreContext).GetAwaiter().GetResult(); + Assert.Equal("UseSchedulingContext", trace); + AwaitConfigFalse(vtIgnoreContext).GetAwaiter().GetResult(); + Assert.Equal("None", trace); + + Console.WriteLine(); + SynchronizationContext.SetSynchronizationContext(new SideeffectingContext()); + + AwaitConfigDefault(vtUseContext).GetAwaiter().GetResult(); + Assert.Equal("UseSchedulingContext Posted", trace); + AwaitConfigTrue(vtUseContext).GetAwaiter().GetResult(); + Assert.Equal("UseSchedulingContext Posted", trace); + AwaitConfigFalse(vtUseContext).GetAwaiter().GetResult(); + Assert.Equal("None Posted", trace); + + AwaitConfigDefault(vtIgnoreContext).GetAwaiter().GetResult(); + Assert.Equal("UseSchedulingContext Posted", trace); + AwaitConfigTrue(vtIgnoreContext).GetAwaiter().GetResult(); + Assert.Equal("UseSchedulingContext Posted", trace); + AwaitConfigFalse(vtIgnoreContext).GetAwaiter().GetResult(); + Assert.Equal("None Posted", trace); + } + + class SideeffectingContext : SynchronizationContext + { + public override void Post(SendOrPostCallback d, object? state) + { + trace += " Posted"; + base.Post(d, state); + } + } + + static async ValueTask AwaitConfigDefault(ValueTask vt) + { + await (vt); + } + + static async ValueTask AwaitConfigTrue(ValueTask vt) + { + await (vt).ConfigureAwait(true); + } + + static async ValueTask AwaitConfigFalse(ValueTask vt) + { + await (vt).ConfigureAwait(false); + } + + class Source : IValueTaskSource + { + private readonly bool _useContext; + public Source(bool useContext) + { + _useContext = useContext; + } + + public void GetResult(short token) + { + } + + public ValueTaskSourceStatus GetStatus(short token) + { + return ValueTaskSourceStatus.Pending; + } + + public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) + { + trace = flags.ToString(); + + var schedulingContext = SynchronizationContext.Current; + if (_useContext && schedulingContext != null) + { + schedulingContext.Post((obj) => continuation(obj), state); + } + else + { + ThreadPool.QueueUserWorkItem((obj) => continuation(obj), state); + } + } + } +} diff --git a/src/tests/async/valuetask Source/valuetaskSource.csproj b/src/tests/async/valuetask Source/valuetaskSource.csproj new file mode 100644 index 00000000000000..9367a79b2edbb1 --- /dev/null +++ b/src/tests/async/valuetask Source/valuetaskSource.csproj @@ -0,0 +1,8 @@ + + + True + + + + + diff --git a/src/tools/illink/src/linker/Linker.Dataflow/MethodBodyScanner.cs b/src/tools/illink/src/linker/Linker.Dataflow/MethodBodyScanner.cs index ae0d2de743cdd9..6926adda57915d 100644 --- a/src/tools/illink/src/linker/Linker.Dataflow/MethodBodyScanner.cs +++ b/src/tools/illink/src/linker/Linker.Dataflow/MethodBodyScanner.cs @@ -694,24 +694,28 @@ protected virtual void Scan(MethodIL methodIL, ref InterproceduralState interpro case Code.Ret: { - - bool hasReturnValue = !methodBody.Method.ReturnsVoid(); - - if (currentStack.Count != (hasReturnValue ? 1 : 0)) - { - WarnAboutInvalidILInMethod(methodIL, operation.Offset); - } - if (hasReturnValue) + // TODO: (async) handle non-generic Task returns. + if (currentStack.Count != 0) { - StackSlot retStackSlot = PopUnknown(currentStack, 1, methodIL, operation.Offset); - // If the return value is a reference, treat it as the value itself for now - // We can handle ref return values better later - MultiValue retValue = DereferenceValue(retStackSlot.Value, locals, ref interproceduralState); - var methodReturnValue = GetReturnValue(methodIL); - HandleReturnValue(methodIL, methodReturnValue, operation, retValue); - ValidateNoReferenceToReference(locals, methodIL, operation.Offset); + bool hasReturnValue = !methodBody.Method.ReturnsVoid(); + + if (currentStack.Count != (hasReturnValue ? 1 : 0)) + { + WarnAboutInvalidILInMethod(methodIL, operation.Offset); + } + if (hasReturnValue) + { + StackSlot retStackSlot = PopUnknown(currentStack, 1, methodIL, operation.Offset); + // If the return value is a reference, treat it as the value itself for now + // We can handle ref return values better later + MultiValue retValue = DereferenceValue(retStackSlot.Value, locals, ref interproceduralState); + var methodReturnValue = GetReturnValue(methodIL); + HandleReturnValue(methodIL, methodReturnValue, operation, retValue); + ValidateNoReferenceToReference(locals, methodIL, operation.Offset); + } + ClearStack(ref currentStack); } - ClearStack(ref currentStack); + break; }