Bounds checks not eliminated when compared to Min

Open
#116,161 10 comments 2 reactions 1 assignee View on GitHub

@EgorBo is already working on this.

Since May 30, 2025.

Assessment

This issue has not been assessed yet.

Description

area-CodeGen-coreclr

Repro:

internal class Program
{
    private static void Main(string[] args) { }

    private static int Get1(int i, int size, int[] items)
    {
        if ((uint)i < (uint)size && (uint)i < (uint)items.Length)
        {
            return items[i];
        }

        return -1;
    }

    private static int Get2(int i, int size, int[] items)
    {
        if ((uint)i < Math.Min((uint)size, (uint)items.Length))
        {
            return items[i];
        }

        return -1;
    }

    private static int Get3(int i, int size, int[] items)
    {
        if ((uint)i < ((uint)size < (uint)items.Length ? (uint)size : (uint)items.Length))
        {
            return items[i];
        }

        return -1;
    }
}

Get1 eliminates the bounds check, but incurs both comparisons against size and items.Length, resulting in two cmp/ja pairs:

; Method Program:Get1(int,int,int[]):int (FullOpts)
G_M000_IG01:                ;; offset=0x0000

G_M000_IG02:                ;; offset=0x0000
       cmp      ecx, edx
       jae      SHORT G_M000_IG04

G_M000_IG03:                ;; offset=0x0004
       mov      eax, dword ptr [r8+0x08]
       cmp      eax, ecx
       ja       SHORT G_M000_IG06

G_M000_IG04:                ;; offset=0x000C
       mov      eax, -1

G_M000_IG05:                ;; offset=0x0011
       ret      

G_M000_IG06:                ;; offset=0x0012
       mov      eax, ecx
       mov      eax, dword ptr [r8+4*rax+0x10]

G_M000_IG07:                ;; offset=0x0019
       ret      
; Total bytes of code: 26

Get2 and Get3 both combine the size/items.Length checks via a cmov, but incur a bounds check, resulting in a cmov/cmp/jb and cmp/jae:

; Method Program:Get3(int,int,int[]):int (FullOpts)
G_M000_IG01:                ;; offset=0x0000
       sub      rsp, 40

G_M000_IG02:                ;; offset=0x0004
       mov      r10d, dword ptr [r8+0x08]
       cmp      r10d, edx
       cmovbe   edx, r10d
       cmp      ecx, edx
       jb       SHORT G_M000_IG05

G_M000_IG03:                ;; offset=0x0013
       mov      eax, -1

G_M000_IG04:                ;; offset=0x0018
       add      rsp, 40
       ret      

G_M000_IG05:                ;; offset=0x001D
       cmp      ecx, r10d
       jae      SHORT G_M000_IG07
       mov      eax, ecx
       mov      eax, dword ptr [r8+4*rax+0x10]

G_M000_IG06:                ;; offset=0x0029
       add      rsp, 40
       ret      

G_M000_IG07:                ;; offset=0x002E
       call     CORINFO_HELP_RNGCHKFAIL
       int3     
; Total bytes of code: 52

Seems if the JIT could recognize that the bounds check could be eliminated, then Get2/Get3 could eliminate the extra cmp/jae.

Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from dotnet/runtime

All issues in dotnet/runtime

Similar issues

More C# issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.