Add RuntimeHelpers.IsRuntimeAsync intrinsic#130899
Conversation
The interpreter and JIT fold this intrinsic to a constant based on whether the calling function is runtime async.
|
Azure Pipelines: Successfully started running 5 pipeline(s). 10 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
There was a problem hiding this comment.
Pull request overview
This PR introduces a new RuntimeHelpers.IsRuntimeAsync() intrinsic in System.Private.CoreLib, and wires it up so both the CoreCLR JIT and the CoreCLR interpreter fold it to a compile-time constant based on whether the current compilation is for an async-calling-convention method (runtime-async).
Changes:
- Add
RuntimeHelpers.IsRuntimeAsync()as an[Intrinsic]internal API stub in CoreLib. - Extend CoreCLR JIT named-intrinsic plumbing to recognize the method and expand it to
true/falseduring import based oncompIsAsync(). - Extend CoreCLR interpreter named-intrinsic plumbing to recognize and constant-fold the method based on
sig.isAsyncCall().
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs | Adds the new internal [Intrinsic] method stub IsRuntimeAsync(). |
| src/coreclr/jit/namedintrinsiclist.h | Adds a new NamedIntrinsic enum value for RuntimeHelpers.IsRuntimeAsync. |
| src/coreclr/jit/importercalls.cpp | Recognizes and expands the intrinsic during import to a constant using compIsAsync(). |
| src/coreclr/jit/fgbasic.cpp | Marks the intrinsic as foldable during IL stack simulation for jump target discovery. |
| src/coreclr/interpreter/intrinsics.cpp | Maps RuntimeHelpers.IsRuntimeAsync to the new NamedIntrinsic. |
| src/coreclr/interpreter/compiler.cpp | Expands the intrinsic to an ldc.i4 constant based on isAsyncCall(), and treats it as “must expand” for interpretation. |
|
@EgorBot -intel -amd -arm --envvars DOTNET_JitDisasm:ReadLineAsync using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
public class StreamReaderReadLineTests
{
private string _text;
private byte[] _bytes;
[ParamsSource(nameof(GetLineLengthRanges))]
public Range LineLengthRange { get; set; }
public static IEnumerable<Range> GetLineLengthRanges()
{
yield return new Range() { Min = 0, Max = 0 };
yield return new Range() { Min = 1, Max = 1 };
yield return new Range() { Min = 1, Max = 8 };
yield return new Range() { Min = 9, Max = 32 };
yield return new Range() { Min = 33, Max = 128 };
yield return new Range() { Min = 129, Max = 1024 };
yield return new Range() { Min = 1025, Max = 2048 };
yield return new Range() { Min = 0, Max = 1024 };
}
public class Range
{
public int Min { get; set; }
public int Max { get; set; }
public override string ToString() => $"[{Min,4}, {Max,4}]";
}
[GlobalSetup]
public void GlobalSetup()
{
_text = GenerateLinesText(LineLengthRange, 16 * 1024);
_bytes = Encoding.UTF8.GetBytes(_text);
}
[Benchmark]
public async Task ReadLineAsync()
{
using (StreamReader reader = new StreamReader(new MemoryStream(_bytes)))
{
while (await reader.ReadLineAsync() != null) ;
}
}
private static string GenerateLinesText(Range lineLengthRange, int textTargetLength)
{
int min = lineLengthRange.Min;
int max = lineLengthRange.Max;
string newLine = Environment.NewLine;
var sb = new StringBuilder(textTargetLength + max + newLine.Length);
var random = new Random(42);
while (sb.Length < textTargetLength)
{
int charsCount = random.Next(min, max);
for (int c = 0; c < charsCount; c++)
sb.Append((char)random.Next('0', 'z'));
sb.Append(newLine);
}
return sb.ToString();
}
} |
This reverts commit f27814f.
|
Azure Pipelines: Successfully started running 5 pipeline(s). 10 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Is this something we're going to consider making public longer term, or is it one of the internal-only APIs? -- Mostly wondering if I need to be tracking something for api-review, not asking for it to be or not. |
I'm not sure yet. I think if everything was runtime async by default we would not need it. I think time will tell how often we end up needing this. |
The interpreter and JIT fold this intrinsic to a constant based on whether the calling function is runtime async.
The motivation is cases like #130872 and #130689 where there is a worry about potentially regressing async1 callers, especially for .NET 11 where runtime async is opt-in in Roslyn.