JIT: (bug) Native-int-typed stack address is CSE'd across an await and reused after resumption
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
## Repro
```csharp
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
public static class Program
{
struct Buf { public int E0, E1, E2, E3, E4, E5, E6, E7; }
[MethodImpl(MethodImplOptions.NoInlining)]
static void Sink(ref int x) { x += 1; }
[MethodImpl(MethodImplOptions.NoInlining)]
static async Task Test(int index)
{
Buf buf = default;
Sink(ref Unsafe.Add(ref Unsafe.As(ref buf), index));
await Task.Yield();
Sink(ref Unsafe.Add(ref Unsafe.As(ref buf), index));
return buf.E0 + buf.E1 + buf.E2 + buf.E3 + buf.E4 + buf.E5 + buf.E6 + buf.E7;
}
public static int Main()
{
Console.WriteLine("start");
int r = Test(1).GetAwaiter().GetResult();
Console.WriteLine("result = " + r);
return 100;
}
}
```
Requires `runtime-async=on` in the project file.
## Expected
```
start
result = 2
```
## Actual
```
start
result = 1
```
The second `Sink` call writes through the address computed before the suspension, so the increment
lands in the abandoned frame instead of `buf.E1`. The `Unsafe.As` is what makes the
address native-int-typed rather than byref: writing `Unsafe.Add(ref buf.E0, index)` instead keeps it
byref-typed and prints the correct `result = 2`. Setting `DOTNET_JitNoCSE=1` also restores
`result = 2`.
## Platform
Windows x64, .NET 11 (dotnet/runtime main @ a0b86b1e2a), `DOTNET_TieredCompilation=0`, runtime async
enabled. No special CPU features. Output above is from the Release build and is stable across runs;
on the Checked build the same program instead dies with an access violation after printing `start`
(exit code `-1073741819`).
Contributor guide
Assessment
This issue has not been assessed yet.