Skip to content

matches!(n, -1 | 1) for signed NonZero suboptimal #84311

Description

@Juici

The code generated when comparing a signed non-zero type to -1 or 1 varies wildly, and appears to generate suboptimal branching code.

A more in-depth analysis of the generated assembly can be found here: https://godbolt.org/z/qxzGrv9nn.

Basic

Looking at these two functions, one would expect them to generate the same assembly.

pub fn is_one_1(len: NonZeroI32) -> bool {
    matches!(len.get(), -1 | 1)
}

pub fn is_one_2(len: NonZeroI32) -> bool {
    len.get() == -1 || len.get() == 1
}

However that is not the case, with is_one_1 in fact branching as seen below. Whilst I have not run benchmarks to see if branching is more performant, one would expect it not to be.

example::is_one_1:
        cmp     edi, 1
        je      .LBB0_3
        cmp     edi, -1
        jne     .LBB0_2
.LBB0_3:
        mov     al, 1
        ret
.LBB0_2:
        xor     eax, eax
        ret

example::is_one_2:
        cmp     edi, -1
        sete    cl
        cmp     edi, 1
        sete    al
        or      al, cl
        ret

What if we include 0 in the comparison?

Since len is a NonZeroI32, it is guaranteed that len.get() != 0 otherwise it is undefined behaviour. This means that the function below should behave the same as is_one_1.

pub fn is_one_3(len: NonZeroI32) -> bool {
    matches!(len.get(), -1 | 0 | 1)
}

In fact the generated assembly for this is the smallest yet.

example::is_one_3:
        add     edi, 1
        cmp     edi, 3
        setb    al
        ret

This assembly is actually also generated when we include 0 in the comparison in the style of is_one_2.

pub fn is_one_4(len: NonZeroI32) -> bool {
    len.get() == -1 || len.get() == 0 || len.get() == 1
}
example::is_one_4:
        add     edi, 1
        cmp     edi, 3
        setb    al
        ret

Expected behaviour

The expected behaviour of the compiler would be to generate assembly that matches between the different implementations of this check.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-codegenArea: Code generationC-bugCategory: This is a bug.E-needs-testCall for participation: An issue has been fixed and does not reproduce, but no test has been added.I-slowIssue: Problems and improvements with respect to performance of generated code.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions