Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/coreclr/vm/interpexec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1288,7 +1288,8 @@ FCIMPL2(ContinuationObject*, AsyncHelpers_ResumeInterpreterContinuation, Continu

TransitionBlock transitionBlock{};
transitionBlock.m_StackPointer = callersStackPointer;
transitionBlock.m_ReturnAddress = (TADDR)&AsyncHelpers_ResumeInterpreterContinuation;
// Keep the return address consistent with the managed R2R frame represented by the caller's stack pointer.
transitionBlock.m_ReturnAddress = GetWasmVirtualIPFromStackPointer(callersStackPointer);
Comment thread
lewing marked this conversation as resolved.

return AsyncHelpers_ResumeInterpreterContinuationWorker(cont, resultStorage, &transitionBlock);
}
Expand Down
5 changes: 4 additions & 1 deletion src/tests/async/Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<Project>
<PropertyGroup>
<!-- Mono does not support runtime async. Wasm tests must explicitly opt in after avoiding unsupported operations such as Task.Yield. -->
<!-- Mono does not support runtime async. Wasm tests must explicitly opt in after avoiding unsupported operations such as synchronous waits. -->
<DisableProjectBuild Condition="'$(RuntimeFlavor)' == 'mono' or ('$(TargetArchitecture)' == 'wasm' and '$(EnableRuntimeAsyncOnWasm)' != 'true')">true</DisableProjectBuild>
</PropertyGroup>
<ItemGroup Condition="'$(MSBuildProjectFullPath)' != '$(MSBuildThisFileDirectory)async.csproj'">
<ProjectReference Include="$(TestLibraryProjectPath)" />
</ItemGroup>
<Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.targets, $(MSBuildThisFileDirectory)..))" />
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class Async2TaskAdapters
{
// Wrapping a runtime-async Task in a ValueTask (ValueTask(Task) constructor) and
// awaiting the resulting ValueTask should suspend and resume correctly.
[Fact]
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public static void TestTaskToValueTask()
{
SynchronizationContext prevContext = SynchronizationContext.Current;
Expand Down Expand Up @@ -83,7 +83,7 @@ private static ValueTask SourceValueTask()
// to the ValueTask<TResult>(TResult result) constructor, wrapping the Task<int> as a value.
// - return new ValueTask<Task<int>>(TaskOfTaskOfIntReturningFunction()) passes a Task<Task<int>>,
// which binds to the ValueTask<TResult>(Task<TResult> task) constructor, an actual async call.
[Fact]
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public static void TestValueTaskOfTaskValueVersusAsync()
{
WrapValueVersusAsync().GetAwaiter().GetResult();
Expand Down
12 changes: 10 additions & 2 deletions src/tests/async/async-versions/async-versions.il
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
// completes synchronously (returns an already-completed task).

.assembly extern System.Runtime { .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A) .ver 4:0:0:0 }
.assembly extern Microsoft.DotNet.XUnitExtensions { .publickeytoken = (31 BF 38 56 AD 36 4E 35) }
.assembly extern TestLibrary { .ver 0:0:0:0 }
.assembly extern xunit.core {}

.assembly 'async-versions'
Expand Down Expand Up @@ -157,7 +159,10 @@

.method public hidebysig static void JmpToSuspendingTest() cil managed
{
.custom instance void [xunit.core]Xunit.FactAttribute::.ctor() = ( 01 00 00 00 )
.custom instance void [Microsoft.DotNet.XUnitExtensions]Xunit.ConditionalFactAttribute::.ctor(class [System.Runtime]System.Type, string[]) = {
type([TestLibrary]TestLibrary.PlatformDetection)
string[1] ('IsMultithreadingSupported')
}
.maxstack 2

call class [System.Runtime]System.Threading.Tasks.Task`1<int32> AsyncVersions::AwaitJmpToSuspending()
Expand Down Expand Up @@ -185,7 +190,10 @@

.method public hidebysig static void TailToSuspendingTest() cil managed
{
.custom instance void [xunit.core]Xunit.FactAttribute::.ctor() = ( 01 00 00 00 )
.custom instance void [Microsoft.DotNet.XUnitExtensions]Xunit.ConditionalFactAttribute::.ctor(class [System.Runtime]System.Type, string[]) = {
type([TestLibrary]TestLibrary.PlatformDetection)
string[1] ('IsMultithreadingSupported')
}
.maxstack 2

call class [System.Runtime]System.Threading.Tasks.Task`1<int32> AsyncVersions::AwaitTailToSuspending()
Expand Down
2 changes: 1 addition & 1 deletion src/tests/async/async.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<EnableRuntimeAsyncOnWasm>true</EnableRuntimeAsyncOnWasm>
</PropertyGroup>
<ItemGroup>
<MergedWrapperProjectReference Include="*/**/*.??proj" GlobalPropertiesToRemove="EnableRuntimeAsyncOnWasm" />
<MergedWrapperProjectReference Include="*/**/*.??proj" />

<!-- Remove manual benchmarks from the test wrapper -->
<MergedWrapperProjectReference Remove="eh-microbench.csproj" />
Expand Down
1 change: 1 addition & 0 deletions src/tests/async/asyncversion-inline/asyncversion-inline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
//
// These tests pin down that the caller still observes its own context after a suspension
// that happened inside such an inlined frame.
[ConditionalClass(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public class Async2AsyncVersionInline
{
private sealed class NamedContext : SynchronizationContext
Expand Down
2 changes: 1 addition & 1 deletion src/tests/async/awaitingnotasync/awaitingnotasync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

public class AwaitNotAsync
{
[Fact]
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]

@jakobbotsch jakobbotsch Sep 16, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really how we want to deal with this for wasm? I do not think it is very reasonable to have to mark all async tests this way if they don't finish synchronously....

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Browser has single thread.

.Wait() is or should be PNSE.

The better way is to make the whole unit test method async and await instead of .Wait().

I'm not sure if some of the tests actually validate .Wait() specifically, those should stay ConditionalFact ... IsMultithreadingSupported

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The better way is to make the whole unit test method async and await instead of .Wait().

Yes, I agree. I think this requires work in the test generator to support Task-returning tests and to hook that up in the way WASM expects.

I'm not sure if some of the tests actually validate .Wait() specifically, those should stay ConditionalFact ... IsMultithreadingSupported

We may start and run async tasks in parallel, but I do not think any runtime async test should be validating Task.Wait() specifically. It is all about waiting for the tasks to finish.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lewing could you please ask your copilot to do that as follow up?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am working on it in #134042

public static void TestEntryPoint()
{
AsyncEntryPoint().Wait();
Expand Down
2 changes: 1 addition & 1 deletion src/tests/async/byref-param/byref-param.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ private static Task<int> HasByrefParam(out int foo)
return Task.FromResult(53);
}

[Fact]
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public static int TestByrefParamWithSuspension()
{
return TestWithSuspension().GetAwaiter().GetResult();
Expand Down
1 change: 1 addition & 0 deletions src/tests/async/collectible-alc/collectible-alc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Threading.Tasks;
using Xunit;

[ActiveIssue("https://github.com/dotnet/runtime/issues/134001", typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsWasm))]
public class Async2CollectibleAlc
{
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsCollectibleAssembliesSupported))]
Expand Down
10 changes: 5 additions & 5 deletions src/tests/async/covariant-return/covariant-returns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ public static void Test1EntryPoint()
Test1().Wait();
}

[Fact]
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public static void Test2EntryPoint()
{
Test2().Wait();
}

[Fact]
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public static void Test2AEntryPoint()
{
Test2A().Wait();
Expand Down Expand Up @@ -183,7 +183,7 @@ public class Program
{
internal static int Result;

[Fact]
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public static void TestCovariantReturnWithoutRuntimeAsync()
{
Result = 0;
Expand Down Expand Up @@ -223,7 +223,7 @@ public class Program
[MethodImpl(MethodImplOptions.NoInlining)]
public static async Task CallInstanceValueType(Base b) => await b.InstanceMethod<int>();

[Fact]
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public static void TestGenericVirtualMethod()
{
CallInstance(new Derived()).GetAwaiter().GetResult();
Expand Down Expand Up @@ -286,7 +286,7 @@ static async Task RunValueType()
Assert.Equal(typeof(int).FullName.Length, x);
}

[Fact]
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public static void TestAsyncInterfaceGenericMethod()
{
Run().GetAwaiter().GetResult();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

public class CseArrayIndexByref
{
[Fact]
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public static void TestEntryPoint()
{
int[] arr = new int[1];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class CustomStructAwaiters
private static int s_safeAwaiterValue;
private static int s_unsafeAwaiterValue;

[Fact]
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public static void TestEntryPoint()
{
Run().Wait();
Expand Down
2 changes: 1 addition & 1 deletion src/tests/async/devirtualize/devirtualize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ static async Task<int> CallOnInterface<T>(T obj) where T : IAsyncInterface
return await obj.GetValue();
}

[Fact]
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public static void TestEntryPoint()
{
Assert.Equal(42, CallOnNewOpenDerived().Result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,44 +11,48 @@ public class RuntimeAsyncDiagnosticIPStackTrace
[Fact]
public static void TestEntryPoint()
{
TaskCompletionSource suspension = new();
Task task = Level1(suspension.Task);
// Exercise both the cold and warm ResumeInterpreterContinuation FCall paths.
for (int i = 0; i < 2; i++)
{
TaskCompletionSource suspension = new();
Task task = Level1(suspension.Task);

Assert.False(task.IsCompleted);
suspension.SetResult();
Assert.True(task.IsCompleted);
Assert.False(task.IsCompleted);
suspension.SetResult();
Assert.True(task.IsCompleted);

InvalidOperationException? exception = null;
try
{
task.GetAwaiter().GetResult();
}
catch (InvalidOperationException ex)
{
exception = ex;
}
InvalidOperationException? exception = null;
try
{
task.GetAwaiter().GetResult();
}
catch (InvalidOperationException ex)
{
exception = ex;
}

Assert.NotNull(exception);
string stackTrace = exception.StackTrace;
Assert.NotNull(stackTrace);
Console.WriteLine(stackTrace);
Assert.NotNull(exception);
string stackTrace = exception.StackTrace;
Assert.NotNull(stackTrace);
Console.WriteLine(stackTrace);

string[] expectedMethods =
[
nameof(ThrowAfterResume),
nameof(Level4),
nameof(Level3),
nameof(Level2),
nameof(Level1),
];
string[] expectedMethods =
[
nameof(ThrowAfterResume),
nameof(Level4),
nameof(Level3),
nameof(Level2),
nameof(Level1),
];

int previousFrame = -1;
foreach (string method in expectedMethods)
{
string frame = $"{nameof(RuntimeAsyncDiagnosticIPStackTrace)}.{method}(";
int currentFrame = stackTrace.IndexOf(frame, StringComparison.Ordinal);
Assert.True(currentFrame > previousFrame, $"Expected '{frame}' after offset {previousFrame} in:{Environment.NewLine}{stackTrace}");
previousFrame = currentFrame;
int previousFrame = -1;
foreach (string method in expectedMethods)
{
string frame = $"{nameof(RuntimeAsyncDiagnosticIPStackTrace)}.{method}(";
int currentFrame = stackTrace.IndexOf(frame, StringComparison.Ordinal);
Assert.True(currentFrame > previousFrame, $"Expected '{frame}' after offset {previousFrame} in:{Environment.NewLine}{stackTrace}");
previousFrame = currentFrame;
}
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/tests/async/execution-context/execution-context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@

public class Async2ExecutionContext
{
[Fact]
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public static void TestDefaultFlow()
{
Test().GetAwaiter().GetResult();
}

[Fact]
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public static void TestSuppressedFlow()
{
TestNoFlowOuter().GetAwaiter().GetResult();
Expand Down Expand Up @@ -120,7 +120,7 @@ private static async Task ChangeThenReturnInlined()
s_local.Value = 123;
}

[Fact]
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public static int TestRestoreTier0ContextInOsr()
{
return TestRestoreTier0ContextInOsrAsync().GetAwaiter().GetResult();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class Async2FibonacceWithYields
{
const int iterations = 3;

[Fact]
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public static void Test()
{
long allocated = GC.GetTotalAllocatedBytes(precise: true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class Async2FibonacciWithYields
const int iterations = 3;
const bool doYields = true;

[Fact]
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public static void Test()
{
long allocated = GC.GetTotalAllocatedBytes(precise: true);
Expand Down
2 changes: 1 addition & 1 deletion src/tests/async/implement/implement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public async Task<int> M1()
}
}

[Fact]
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public static void TestEntryPoint()
{
IBase1 b1 = new Derived1();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// await has to take the enclosing frame's contexts, so that a suspension records that the
// frame resumed. Getting that wrong leaves the enclosing frame believing it never resumed
// and running its context handling again on stale state.
[ConditionalClass(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public class Async2InlinedAsyncVersionFrames
{
// Completes synchronously for even values and asynchronously for odd ones, so a loop
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// A frame that awaited with ConfigureAwait(false) does not want to be brought back onto
// the SynchronizationContext that was current when it suspended. That has to hold for the
// logical return of an inlined async frame too.
[ConditionalClass(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public class Async2InlinedFrameConfigureAwait
{
private sealed class TrackingContext : SynchronizationContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
//
// The inner frames are marked AggressiveInlining so that the inlining actually happens
// rather than being left to the profitability heuristic, which rejects these callees.
[ConditionalClass(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public class Async2InlinedFrameContexts
{
private sealed class NamedContext : SynchronizationContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// the frame suspended first. A suspension makes the resumption skip the entry code that
// captured the frame's contexts, so the context restores that run while the exception
// unwinds must see that the frame resumed.
[ConditionalClass(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public class Async2InlinedFrameExceptions
{
private static async Task SuspendOnlyAsync() => await Task.Yield();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// caller's instead: doing so leaves the inlined frame's own resumed indicator unset, so
// after a suspension its context restore runs as if the frame had never resumed, over
// state that the resumption skipped capturing.
[ConditionalClass(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public class Async2InlinedFrameLdvirtftnAwait
{
private class Dispatcher
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// to get back onto the caller's continuation context. If that means switching contexts the
// transition itself suspends, so everything the caller still needs afterwards has to
// survive that suspension -- including the record of which frames have resumed.
[ConditionalClass(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public class Async2InlinedFrameTransitionSuspends
{
// Never runs the callback inline: every Post goes to a dedicated thread, so getting
Expand Down
1 change: 1 addition & 0 deletions src/tests/async/inst-unbox-thunks/inst-unbox-thunks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Threading.Tasks;
using Xunit;

[ConditionalClass(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public class InstUnBoxThunks
{
class Utility
Expand Down
2 changes: 1 addition & 1 deletion src/tests/async/object/object.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

public class Async2Object
{
[Fact]
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public static int TestEntryPoint()
{
return (int)AsyncTestEntryPoint(100).Result;
Expand Down
2 changes: 1 addition & 1 deletion src/tests/async/objects-captured/objects-captured.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ private static async Task<int> AsyncEntry()
return (int)result - 90;
}

[Fact]
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsMultithreadingSupported))]
public static int Test()
{
return (int)AsyncEntry().Result;
Expand Down
Loading
Loading