Repository navigation
unwrap branches are not optimized away. #71257
Description
Activity
- addedA-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.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.Relevant to the compiler team, which will review and decide on the PR/issue.
on Apr 17, 2020 In the trivial case, https://rust.godbolt.org/z/3TaPU2, this doesn't seem to reproduce, so I'm going to go ahead and mark as E-needs-mcve. I suspect that this might be a case of insufficient inlining or something like that.
- addedE-needs-mcveCall for participation: This issue has a repro, but needs a Minimal Complete and Verifiable ExampleCall for participation: This issue has a repro, but needs a Minimal Complete and Verifiable Example
on Apr 17, 2020 I can reproduce it with the following test case: ( https://rust.godbolt.org/z/t_9kCR )
use std::vec::Vec; pub enum Foo { First(usize), Second(usize), } pub fn foo(mut v: Vec<Foo>) -> Foo { assert!(v.len() == 1); v.pop().unwrap() }
In the generated code we see the branch for the assertion, which remove the None case from
pop. However, the unwrap condition is still present, because theNonecase is mixed with the discriminant type ofFoo, which lead the generated code to load the the discriminant, and compare it against theNonevalue (= 2), instead of removing the branch.mov r14, qword ptr [rdi] cmp r14, 2 je .LBB6_3I will also note that without the assertion, the case which generate the
Nonebranch is folded with the unwrap case and panic. However, theSomebranch still has the extra unwrap test, despite the fact that it would never fail.Clang gets this right with the
-fstrict-enumsoption which attaches range metadata to the enum load from the the Vec.e.g.
#include <stdlib.h> enum Foo { A, B }; struct Vec { size_t len; Foo *data; }; static int pop(Vec &v) { if (v.len) { return v.data[v.len-1]; } return (int)Foo::B + 1; } Foo foo(Vec &v) { if (v.len == 0) { exit(1); } int k = pop(v); if (k > 1) { exit(3); } return (Foo)k; }
with
-O2compiles to something similar to the Rust code:foo(Vec&): # @foo(Vec&) pushq %rax movq (%rdi), %rax testq %rax, %rax je .LBB0_3 movq 8(%rdi), %rcx movl -4(%rcx,%rax,4), %eax cmpl $2, %eax jge .LBB0_4 popq %rcx retq .LBB0_3: movl $1, %edi callq exit .LBB0_4: movl $3, %edi callq exit
but with
-O2 -fstrict-enumscompiles to the desired:foo(Vec&): # @foo(Vec&) pushq %rax movq (%rdi), %rax testq %rax, %rax je .LBB0_2 movq 8(%rdi), %rcx movl -4(%rcx,%rax,4), %eax popq %rcx retq .LBB0_2: movl $1, %edi callq exit
It seems like it would be reasonable for Rust to always act like
-fstrict-enumsReacted by est31, jD91mZM2, Brian Misiak, Nicolas B. Pierron, luben karavelov, Lu, Wangshan, Kornel and Jakub Beránekrustc already adds range metadata to the load of the discriminant. It seems it gets lost at some point during optimizations.
Hmm so it does. It seems like LLVM is pretty fragile here.
#include <stdlib.h> enum Foo { A, B, C, }; Foo foo(Foo* data) { Foo k = data[1]; if (k > Foo::C) { exit(3); } return k; }
produces the following with
-fstrict-enumsfoo(Foo*): # @foo(Foo*) pushq %rax movl 4(%rdi), %eax cmpl $3, %eax je .LBB0_2 popq %rcx retq .LBB0_2: movl $3, %edi callq exit
Interestingly enough the
jebecomesjgewithout-fstrict-enumsso LLVM has some idea what's going on.I filed an LLVM bug:
https://bugs.llvm.org/show_bug.cgi?id=46279So it turns out that https://bugs.llvm.org/show_bug.cgi?id=46279 was me making an incorrect assumption about enums in C++. I looked more closely at the rust issue and filed #73258 which probably needs to be fixed before making progress on this one.
Reacted by KornelThis appears to be fixed: https://rust.godbolt.org/z/38bsj4onG
- addedC-optimizationCategory: An issue highlighting optimization opportunities or PRs implementing suchCategory: An issue highlighting optimization opportunities or PRs implementing suchE-needs-testCall for participation: An issue has been fixed and does not reproduce, but no test has been added.Call for participation: An issue has been fixed and does not reproduce, but no test has been added.S-has-mcveStatus: A Minimal Complete and Verifiable Example has been found for this issueStatus: A Minimal Complete and Verifiable Example has been found for this issueand removedE-needs-mcveCall for participation: This issue has a repro, but needs a Minimal Complete and Verifiable ExampleCall for participation: This issue has a repro, but needs a Minimal Complete and Verifiable Example
on Mar 25, 2025 - added a commit that references this issue
on Aug 23, 2025
When creating a structure which has a variant type, and that this type is wrapped as an
Option<T>, theunwrapbranch cannot be removed by LLVM, even after proving that the only path reachable is theSome(…)case and never theNonecase.I tried this code:
https://github.com/mozilla-spidermonkey/jsparagus/blob/330722aaa4f7c6f39422b7daae7084ea8cbcbead/crates/parser/src/queue_stack.rs#L141-L153
Calling the previous function as:
This hint LLVM that the
self.top == 0branch does not need to be generated. However, the unwrap condition remains as theNonevalue is folded within the variant and that LLVM does not know about it.I think the Rust compiler should give range analysis information that the
Nonevalue would never be part of the variant, and as such let LLVM remove the unwrapping condition from the generated code.Meta
Tested with both
rustc --version --verbose:and