Skip to content

Bound checks for readonly byte arrays with byte indexer #13159

@EgorBo

Description

@EgorBo

I see it's quite common for CoreCLR/FX to have quite big static readonly arrays of bytes. e.g.
Char.cs#L41-L74
ParserHelpers.cs#L29-L47
Rune.cs#L32-L42
RegexParser.cs#L1969-L1977
CaseInsensitiveAscii.cs#L13-L40

so I wonder if such a table has 256 elements and we try to access it via a byte indexer we will never go out of bounds, e.g.:

static ReadOnlySpan<byte> s_hexDecodeMap => new byte[] 
//static readonly byte[] s_hexDecodeMap = new byte[]
{
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 0
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 1
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 2
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, // 3
    255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 4
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 5
    255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 6
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 7
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 8
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // 9
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // A
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // B
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // C
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // D
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // E
    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // F
}; // 256 elements


[MethodImpl(MethodImplOptions.AggressiveOptimization)]
static byte GetByte(byte index)
{
    return s_hexDecodeMap[index];
}

Currently generates:

; Method Tests:GetByte(ubyte):ubyte
G_M45444_IG01:
       sub      rsp, 40

G_M45444_IG02:
       movzx    rax, cl
       cmp      eax, 256                 // redundant
       jae      SHORT G_M45444_IG04      // redundant
       movsxd   rax, eax
       mov      rdx, 0xD1FFAB1E
       movzx    rax, byte  ptr [rax+rdx]

G_M45444_IG03:
       add      rsp, 40
       ret      

G_M45444_IG04:                           // redundant
       call     CORINFO_HELP_RNGCHKFAIL  // redundant
       int3                              // redundant
; Total bytes of code: 42

So we don't need bound checks here since index simply can't be >256. Also if a byte map, let's say, has 100-200 elements I guess we can extend it to be 256 bytes (a small memory regression - faster access, no branching). It can be important when you port some performance-critical code/libraries from C++ to C# e.g. https://godbolt.org/z/7kVk_5

category:cq
theme:range-check
skill-level:beginner
cost:small

Metadata

Metadata

Assignees

No one assigned

    Labels

    JitUntriagedCLR JIT issues needing additional triagearea-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