Skip to content

Ensure IL instantiating/unboxing stubs are transparent in await behavior #121971

@jakobbotsch

Description

@jakobbotsch

We need a way to guarantee that unboxing/instantiating stubs do not introduce intermediate continuations. We used to consider this to just be an optimization, but this is a correctness problem for IValueTaskSource.

Example that shows the misbehavior:

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Sources;

public class Program
{
    public static void Main()
    {
        SynchronizationContext.SetSynchronizationContext(new MySyncContext());
        new Program().TestAsync(new C()).GetAwaiter().GetResult();
    }

    private async Task TestAsync(IFace i)
    {
        await i.Foo<string>(0, 1, 2, 3, 4, 5, 6, 7, "value");
    }

    private struct C : IFace
    {
        public ValueTask Foo<T>(int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, T value)
        {
            return new ValueTask(new Source(), 0);
        }

        private class Source : IValueTaskSource
        {
            public void GetResult(short token)
            {
            }

            public ValueTaskSourceStatus GetStatus(short token)
            {
                return ValueTaskSourceStatus.Pending;
            }

            public void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags)
            {
                Console.WriteLine("Saw " + flags);
                Trace.Assert(flags == ValueTaskSourceOnCompletedFlags.UseSchedulingContext);
            }
        }
    }

    private interface IFace
    {
        ValueTask Foo<T>(int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, T value);
    }

    private class MySyncContext : SynchronizationContext
    {
    }
}

Output:

Async1:  Saw UseSchedulingContext
Runtime async: Saw None

The continuation created by the instantiating stub gets in the way of getting to the right continuation context flags.

cc @VSadov

We need some mechanism to ensure the stubs just directly return the callee continuation on suspension, which is not tied into tailcalling.

It would almost be possible to use AsyncHelpers.AsyncCallContinuation() + AsyncHelpers.AsyncSuspend from the stubs, but the JIT will still do its own state machine expansion. Probably need some new "tail suspend" intrinsic.

Metadata

Metadata

Assignees

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIruntime-async

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions