Skip to content

Codegen: Bound check not elided in ref struct Span field index access #72004

Open
@gfoidl

Description

@gfoidl

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

Metadata

Metadata

Assignees

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions