Repository navigation
Closed
Closed
Copy link
Labels
A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.I-slowIssue: Problems and improvements with respect to performance of generated code.Issue: Problems and improvements with respect to performance of generated code.
Description
I tried this code:
compiler explorer
pub enum Enum {
A(u32),
B(u32),
C(u32),
}
#[no_mangle]
pub fn f1(lhs: &Enum, rhs: &Enum) -> bool {
match (lhs, rhs) {
(Enum::A(lhs), Enum::A(rhs)) => lhs == rhs,
(Enum::B(lhs), Enum::B(rhs)) => lhs == rhs,
(Enum::C(lhs), Enum::C(rhs)) => lhs == rhs,
_ => false,
}
}
#[no_mangle]
pub fn f2(lhs: &Enum, rhs: &Enum) -> bool {
if std::mem::discriminant(lhs) != std::mem::discriminant(rhs) {
return false;
}
match (lhs, rhs) {
(Enum::A(lhs), Enum::A(rhs)) => lhs == rhs,
(Enum::B(lhs), Enum::B(rhs)) => lhs == rhs,
(Enum::C(lhs), Enum::C(rhs)) => lhs == rhs,
_ => false,
}
}
#[no_mangle]
pub fn f3(lhs: &Enum, rhs: &Enum) -> bool {
if std::mem::discriminant(lhs) != std::mem::discriminant(rhs) {
return false;
}
match (lhs, rhs) {
(Enum::A(lhs), Enum::A(rhs)) => lhs == rhs,
(Enum::B(lhs), Enum::B(rhs)) => lhs == rhs,
(Enum::C(lhs), Enum::C(rhs)) => lhs == rhs,
_ => unsafe { std::hint::unreachable_unchecked() },
}
}Code like this is often used for implementations of PartialEq or PartialOrd on enums, so it is important that it be optimized well.
I expected to see this happen:
The functions f1 and f2 should produce equivalent machine code to f3.
Instead, this happened:
f1 and f2 do not produce equivalent machine code.
f1 is missing the early return for when the discriminants do not match.
f2 unnecessarily examines the discriminant of lhs twice!
LLVM should be able to transform f1 into f2, and f2 into f3
Meta
rustc --version --verbose:
rustc 1.76.0-nightly (a96d57bdb 2023-12-15)
binary: rustc
commit-hash: a96d57bdb6d2bb6d233d7d5aaefc2995ab99be01
commit-date: 2023-12-15
host: x86_64-unknown-linux-gnu
release: 1.76.0-nightly
LLVM version: 17.0.6
Backtrace
<backtrace>
Metadata
Metadata
Assignees
Labels
A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.I-slowIssue: Problems and improvements with respect to performance of generated code.Issue: Problems and improvements with respect to performance of generated code.
Activity
Kmeakin commentedon Dec 16, 2023
@rustbot label I-slow
Kmeakin commentedon Dec 16, 2023
Interestingly, when
Enumis a c-like enum,f1produces the same code asf3, butf2doesnt: https://godbolt.org/z/81bGdq6xsKmeakin commentedon Dec 16, 2023
Adding a zero-sized field (like
()) even preventsf1being optimized intof3(https://godbolt.org/z/3dqzTfofa), but an empty field list doesn't (https://godbolt.org/z/bTK9qa9z1)clubby789 commentedon Dec 16, 2023
Looks similar to #113506, which was fixed upstream.Ah, a little different
Throwing
f1into upstreamoptimproves codegen, but still not as good as the optimal case inf3The
PartialEqderive uses essentially the same code asf3because of this issue, so it would be nice to have it work hereKmeakin commentedon Dec 17, 2023
The improvement I'm looking for here is removing unnecessary repeated inspections of enum discriminants, not merging duplicate RHS per se. This improvement would still be useful even if each enum variant has different data in it:
compiler explorer
clubby789 commentedon Jan 10, 2024
Here's some more similar strange behaviour:
demonstrates the repeated inspections, but if we remove the unused
lhs/rhsbindings (or have bindings in only one branch), we get optimal codegen:https://godbolt.org/z/fPMx5bYz6
Seems like MIR optimisations manage to improve it to a point where LLVM can optimise it better
dianqk commentedon Feb 7, 2024
Update: In fact, we just need to re-enable the early otherwise branch optimization.
Hmm, this requires multiple optimizations to work together.
As we merge the two i32, we'll end up with better instructions. See https://llvm.godbolt.org/z/EYG46EaxY.
It may be that part of the optimization fits in MIR, I'll trade off.
@rustbot claim
Add test case for rust-lang#119014
Add test case for rust-lang#119014
Auto merge of rust-lang#121397 - DianQK:early_otherwise_branch_sound,…
Add test case for rust-lang#119014
Auto merge of rust-lang#121397 - DianQK:early_otherwise_branch_sound,…
Add test case for rust-lang#119014
Add test case for rust-lang#119014
Add test case for rust-lang#119014
Auto merge of rust-lang#122387 - DianQK:re-enable-early-otherwise-bra…
Add test case for rust-lang#119014
Add test case for rust-lang#119014
Auto merge of rust-lang#122387 - DianQK:re-enable-early-otherwise-bra…
Add test case for rust-lang#119014
Auto merge of rust-lang#122387 - DianQK:re-enable-early-otherwise-bra…