Open
Description
Repro:
internal ref struct Foo
{
private readonly Span<int> _span;
internal Foo(Span<int> span) => _span = span;
internal void Set(int index)
{
if ((uint)index < (uint)_span.Length)
{
_span[index] = 42;
}
}
}
Codegen (x64):
sub rsp,38
xor eax,eax
mov [rsp+28],rax
mov [rsp+30],rax
mov rax,[rcx+8]
test rax,rax
je short M00_L02
lea rdx,[rax+10]
mov eax,[rax+8]
M00_L00:
lea rcx,[rsp+28]
mov [rcx],rdx
mov [rcx+8],eax
cmp dword ptr [rsp+30],3
jbe short M00_L01
lea rdx,[rsp+28]
cmp dword ptr [rdx+8],3 ; redundant check for bound-check -- should be elided
jbe short M00_L03
mov rax,[rdx]
mov dword ptr [rax+0C],2A
M00_L01:
add rsp,38
ret
M00_L02:
xor edx,edx
xor eax,eax
jmp short M00_L00
M00_L03:
call CORINFO_HELP_RNGCHKFAIL
int 3
; Total bytes of code 88
Workaround: fetch the span to a local.
+Span<int> span = _span;
-if ((uint)index < (uint)_span.Length)
+if ((uint)index < (uint)span.Length)
{
- _span[index] = 42;
+ span[index] = 42;
}
When the index is not constant (here 3), the same issue occurs.
Complete listing of code (C#)
using BenchmarkDotNet.Attributes;
BenchmarkDotNet.Running.BenchmarkRunner.Run<Bench>();
[DisassemblyDiagnoser]
[ShortRunJob]
public class Bench
{
private readonly int[] _array = new int[100];
[Benchmark]
public void NoLocalSpan()
{
Foo foo = new(_array);
foo.Set(3);
}
[Benchmark]
public void LocalSpan()
{
Foo1 foo = new(_array);
foo.Set(3);
}
}
internal ref struct Foo
{
private readonly Span<int> _span;
internal Foo(Span<int> span) => _span = span;
internal void Set(int index)
{
if ((uint)index < (uint)_span.Length)
{
_span[index] = 42;
}
}
}
internal ref struct Foo1
{
private readonly Span<int> _span;
internal Foo1(Span<int> span) => _span = span;
internal void Set(int index)
{
Span<int> span = _span;
if ((uint)index < (uint)span.Length)
{
span[index] = 42;
}
}
}
Complete listing of generated code
; Bench.NoLocalSpan()
sub rsp,38
xor eax,eax
mov [rsp+28],rax
mov [rsp+30],rax
mov rax,[rcx+8]
test rax,rax
je short M00_L02
lea rdx,[rax+10]
mov eax,[rax+8]
M00_L00:
lea rcx,[rsp+28]
mov [rcx],rdx
mov [rcx+8],eax
cmp dword ptr [rsp+30],3
jbe short M00_L01
lea rdx,[rsp+28]
cmp dword ptr [rdx+8],3
jbe short M00_L03
mov rax,[rdx]
mov dword ptr [rax+0C],2A
M00_L01:
add rsp,38
ret
M00_L02:
xor edx,edx
xor eax,eax
jmp short M00_L00
M00_L03:
call CORINFO_HELP_RNGCHKFAIL
int 3
; Total bytes of code 88
; Bench.LocalSpan()
sub rsp,18
xor eax,eax
mov [rsp+8],rax
mov [rsp+10],rax
mov rax,[rcx+8]
test rax,rax
je short M00_L02
lea rdx,[rax+10]
mov eax,[rax+8]
M00_L00:
lea rcx,[rsp+8]
mov [rcx],rdx
mov [rcx+8],eax
lea rdx,[rsp+8]
mov rax,[rdx]
mov edx,[rdx+8]
cmp edx,3
jbe short M00_L01
mov dword ptr [rax+0C],2A
M00_L01:
add rsp,18
ret
M00_L02:
xor edx,edx
xor eax,eax
jmp short M00_L00
; Total bytes of code 77
Cf. #67448 (comment)
category:cq
theme:bounds-checks
skill-level:intermediate
cost:small
impact:small