dotnet / dotnet/runtime

JIT: (bug) call argument evaluation reverses two volatile reads

Open
#133,524 1 comment 0 reactions 1 assignee Claimed by @EgorBo View on GitHub
area-CodeGen-coreclr
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

## Repro

With a monotonically increasing value, the first `Volatile.Read` can never observe a larger value than the second.

```csharp
using System;
using System.Runtime.CompilerServices;
using System.Threading;

class Program {
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
static bool Test(ref int value) =>
Sink(Volatile.Read(ref value), Volatile.Read(ref value) + 1);

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
static bool Sink(int first, int secondPlusOne) => first > secondPlusOne - 1;

static void Main() {
int value = 0, stop = 0;
var publisher = new Thread(() => {
for (int i = 1; i <= 100_000_000 && Volatile.Read(ref stop) == 0; i++)
Volatile.Write(ref value, i);
});
publisher.Start();
int reversed = 0;
for (int i = 0; i < 2_000_000; i++)
if (Test(ref value))
reversed++;
Volatile.Write(ref stop, 1);
publisher.Join();
Console.WriteLine($"Reversed monotonic reads: {reversed}; expected 0");
}
}
```

## Expected

```
Reversed monotonic reads: 0; expected 0
```

## Actual

```
Reversed monotonic reads: 413; expected 0
```

The count is timing-dependent but was non-zero on every run (9, 199, 413).

The generated code for a two-reference variant shows the reversal deterministically
(`Sink(Volatile.Read(ref first), Volatile.Read(ref second) + 1)` under `DOTNET_JitDisasm`):

```asm
mov edx, dword ptr [rdx] ; read for the SECOND argument
inc edx
mov ecx, dword ptr [rcx] ; read for the FIRST argument
call Sink
```

## Platform

Windows x64, .NET 11.0.0-rc.1.26425.128.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.