dotnet / dotnet/runtime

JIT: (bug) Span<T>.get_Item intrinsic evaluates the index before the receiver

Closed
#133,963 1 comment 1 reaction 2 assignees 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

The optimized importer expansion for `Span.get_Item` and `ReadOnlySpan.get_Item` can spill/evaluate the index before the receiver, violating IL evaluation order and producing wrong results when the receiver mutates state read by the index.

### Minimal Repro

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

public class Program
{
static int s_idx;

[MethodImpl(MethodImplOptions.NoInlining)]
static ref Span GetSpan(ref Span s) { s_idx = 1; return ref s; }

[MethodImpl(MethodImplOptions.NoInlining)]
static ref ReadOnlySpan GetROSpan(ref ReadOnlySpan s) { s_idx = 1; return ref s; }

[MethodImpl(MethodImplOptions.NoInlining)]
static int[] GetArr(int[] a) { s_idx = 1; return a; }

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
static int TestSpan(ref Span s) => GetSpan(ref s)[s_idx];

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
static int TestROSpan(ref ReadOnlySpan s) => GetROSpan(ref s)[s_idx];

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
static int TestArr(int[] a) => GetArr(a)[s_idx];

public static void Main()
{
int[] data = { 10, 20 };
Span span = data;
ReadOnlySpan rospan = data;

s_idx = 0;
Console.WriteLine("span: " + TestSpan(ref span));
s_idx = 0;
Console.WriteLine("rosp: " + TestROSpan(ref rospan));
s_idx = 0;
Console.WriteLine("arr : " + TestArr(data));
}
}
```

### Expected

```text
span: 20
rosp: 20
arr : 20
```

### Actual

```text
span: 10
rosp: 10
arr : 20
```

### Notes

The intrinsic path pops receiver and index, then clones/spills the index first; since the receiver has already been popped, that spill is appended before the side-effecting receiver evaluation.
The array indexer path preserves the required receiver-before-index order, so it returns `20`.

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.