-
Notifications
You must be signed in to change notification settings - Fork 13.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enum discriminants used as indices are subject to bounds checks #36955
Comments
Some more cases. No safe code workaround uses as few instructions as using |
@bluss With 3 or more variants, the matching conversion is equivalent (which is nice in the meantime for people who are squeamish about |
I don't consider myself a compiler/optimizer person, but I'm willing to bet the 2-variant case is causing LLVM to assume C/C++ boolean semantics where any nonzero value is equivalent to The question then becomes, how do we get around LLVM making this assumption without removing the heuristic entirely (which I'm sure a nonnegligible number of programs rely on for correctness). |
The problem is that SimplifyCfg transforms switches to ifs, losing the range metadata, before it eliminates unneeded switches. Also, SimplifyCfg does these optimizations before we do mem2reg, so even if it did things in the correct order that wouldn't work well. |
Resolving #23898 would help fixing this, right? |
It won't, and this issue has a PR open that fixes it. |
Thanks, amazingly quickly fixed |
Case Study
All enum discriminants are statically known, but the optimizer appears not to be able to introspect their max values when they are cast to primitives and used as indices, probably because at the LLVM level, they only ever are primitives. LLVM is never told their maximum value. This means that when using enum discriminants to index arrays/vectors whose lengths are also statically known, bounds checks are emitted even when one would reasonably expect them to be elided.
Minimal example (Playground):
If you look at the emitted assembly (in release mode), you can see that
idx_cast
does a bounds-check.If you add
::std::intrinsics::assume(idx as usize < 2)
, the bounds-check is elided and LLVM appears to use the discriminant as the index directly: https://is.gd/DdX7ddThis also works even if
enum Index
is not#[repr(usize)]
; LLVM simply zero-extends the discriminant: https://is.gd/2JrJtySo as a possible optimization at codegen, whenever an enum is cast to a primitive, we could (or maybe even should) emit
@llvm.assume( [discriminant value] <= [maximum discriminant value] )
.Converting via explicit
match
also allows elision of the bounds check without reintroducing a branch, but has differing behavior between enums with 2 variants and enums of more variants:use 1 if discriminant is 1, otherwise 0
treatment: https://is.gd/VS7JZKassume
solution: https://is.gd/d8dEtYI assume LLVM does the former in the different-width case because it uses fewer cycles than the latter, since they are otherwise functionally equivalent. However, when the enum is
#[repr(usize)]
, the 2-variant version is using 3 extra, unnecessary instructions. But that seems like an LLVM problem more than a codegen problem.The text was updated successfully, but these errors were encountered: