dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.01k stars 4.67k forks source link

Optimize some trivial comparison patterns for unsigned integer types #99757

Open MineCake147E opened 6 months ago

MineCake147E commented 6 months ago

Description

I noticed that RyuJIT doesn't even optimize some simple comparison against unsigned integers.

Case 1: value - N < value is equivalent to value > N - 1 for any N

Consider this code:

using System;
using System.Runtime.CompilerServices;
public static class _____AAAAA
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool Q(nuint a, nuint b)
    {
        return a < b;
    }

    public static bool QTest0(nuint b) => Q(b - 1, b);
    public static bool QTest1(nuint b) => b == 0 || Q(b - 1, b);
}

The QTest0 could just be

xor eax, eax
test rcx, rcx
setne al
ret

but it is:

lea rax, [rcx-1]
cmp rax, rcx
setb al
movzx eax, al
ret

And the QTest1 could just be

mov eax, 1
ret

but it is:

test rcx, rcx
je short L0013
lea rax, [rcx-1]
cmp rax, rcx
setb al
movzx eax, al
ret
mov eax, 1
ret

Case 2: (value - N) != value is equivalent to N != 0

Consider this code:

using System;
using System.Runtime.CompilerServices;
public static class _____AAAAA
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool R(nuint a, nuint b)
    {
        return a != b;
    }

    public static bool RTest0(nuint b) => R(b - 1, b);
    public static bool RTest1(nuint b) => R(b + 1, b);
}

The RyuJIT currently generates the code below:

; Core CLR 8.0.123.58001 on x64

_____AAAAA.R(UIntPtr, UIntPtr)
    L0000: xor eax, eax
    L0002: cmp rcx, rdx
    L0005: setne al
    L0008: ret

_____AAAAA.RTest0(UIntPtr)
    L0000: lea rax, [rcx-1]
    L0004: cmp rax, rcx
    L0007: setne al
    L000a: movzx eax, al
    L000d: ret

_____AAAAA.RTest1(UIntPtr)
    L0000: lea rax, [rcx+1]
    L0004: cmp rax, rcx
    L0007: setne al
    L000a: movzx eax, al
    L000d: ret

In this case, RTest0 and RTest1 could just return true without checking anything.

Configuration

SharpLab

Regression?

Unknown

Data

Analysis

dotnet-policy-service[bot] commented 6 months ago

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch See info in area-owners.md if you want to be subscribed.

EgorBo commented 6 months ago

Are these some real-world patterns?

MineCake147E commented 6 months ago

The code below subtracts the first element from the last element, both from the span-like object (but not actual ReadOnlySpan<T>) span, where the index and Length are both nuint. The indexer introduces some unsigned range checks, despite the length is already checked by span.Length != 0.

if (span.Length != 0)
{
    return span[span.Length - 1] - span[0];
}