Skip to content

Consecutive comparisons are not combined into a single comparison (impacts safety checks) #78875

Open
@davidben

Description

@davidben

Clang does not seem to be able to figure out these two functions are the same:

void f(const int *data, size_t size) {
    if (size < 5) __builtin_trap();
    if (size == 5) __builtin_trap();
}

void g(const int *data, size_t size) {
    if (size < 5 || size == 5) __builtin_trap();
}

Given f, it emits two branches. Given g, it figures out this comparison is simply size <= 5:

This came up as I was putting together a fix for #78829, equivalently step 1 of #78771. Making the change described there generally optimized better, except that *(span.begin() + 5) gets slightly worse.

This is because there are two not-quite-redundant preconditions on this operation, whether begin() + 5 is allowed, and whether operator* is allowed. begin() + 5's precondition is size() >= 5. In particular, if size() == 5, it is still okay to utter begin() + 5 because you just get to end(). However, *iter then needs to check if we're at end(), at which point size() == 5 is rejected. Without being able to combine the two failure conditions into size() <= 5, we get two branches instead of one.

Here's a godbolt link with a more extensive set of examples.
https://godbolt.org/z/K6Yz4M8ae

(CC @danakj. I suspect this missed optimization impacts Chromium's existing checked iterator too.)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions