Skip to content

unwrap branches are not optimized away. #71257

Description

@nbp

When creating a structure which has a variant type, and that this type is wrapped as an Option<T>, the unwrap branch cannot be removed by LLVM, even after proving that the only path reachable is the Some(…) case and never the None case.

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:

assert!(self.top != 0);
let v = self.pop().unwrap();

This hint LLVM that the self.top == 0 branch does not need to be generated. However, the unwrap condition remains as the None value 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 None value 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:

rustc 1.42.0 (b8cedc004 2020-03-09)
binary: rustc
commit-hash: b8cedc00407a4c56a3bda1ed605c6fc166655447
commit-date: 2020-03-09
host: x86_64-unknown-linux-gnu
release: 1.42.0
LLVM version: 9.0

and

rustc 1.44.0-nightly (7f3df5772 2020-04-16)
binary: rustc
commit-hash: 7f3df5772439eee1c512ed2eb540beef1124d236
commit-date: 2020-04-16
host: x86_64-unknown-linux-gnu
release: 1.44.0-nightly
LLVM version: 9.0

Activity

  1. added
    A-LLVMArea: 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.
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    on Apr 17, 2020
  2. Mark-Simulacrum commented on Apr 17, 2020

    @Mark-Simulacrum
    Member

    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.

  3. added
    E-needs-mcveCall for participation: This issue has a repro, but needs a Minimal Complete and Verifiable Example
    on Apr 17, 2020
  4. nbp commented on Apr 18, 2020

    @nbp
    ContributorAuthor

    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 the None case is mixed with the discriminant type of Foo, which lead the generated code to load the the discriminant, and compare it against the None value (= 2), instead of removing the branch.

            mov     r14, qword ptr [rdi]
            cmp     r14, 2
            je      .LBB6_3
    
  5. nbp commented on Apr 18, 2020

    @nbp
    ContributorAuthor

    I will also note that without the assertion, the case which generate the None branch is folded with the unwrap case and panic. However, the Some branch still has the extra unwrap test, despite the fact that it would never fail.

  6. jrmuizel commented on Jun 10, 2020

    @jrmuizel
    Contributor

    Clang gets this right with the -fstrict-enums option 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 -O2 compiles 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-enums compiles 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-enums

  7. hanna-kruppe commented on Jun 11, 2020

    @hanna-kruppe
    Contributor

    rustc already adds range metadata to the load of the discriminant. It seems it gets lost at some point during optimizations.

  8. jrmuizel commented on Jun 11, 2020

    @jrmuizel
    Contributor

    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-enums

    foo(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 je becomes jge without -fstrict-enums so LLVM has some idea what's going on.

  9. jrmuizel commented on Jun 11, 2020

    @jrmuizel
    Contributor
  10. jrmuizel commented on Jun 11, 2020

    @jrmuizel
    Contributor

    So 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.

  11. alex commented on Dec 29, 2024

    @alex
    Member

    This appears to be fixed: https://rust.godbolt.org/z/38bsj4onG

  12. moxian commented on Mar 25, 2025

    @moxian
    Contributor

    Fixed in #109035 . Might be worth adding an extra test to ensure this does not regress?
    But if not, and the checks in the PR are sufficient, then we can just close this.

    @rustbot label: -E-needs-mcve +S-has-mcve +E-needs-test +C-optimization

  13. added
    C-optimizationCategory: An issue highlighting optimization opportunities or PRs implementing such
    E-needs-testCall 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 issue
    and removed
    E-needs-mcveCall for participation: This issue has a repro, but needs a Minimal Complete and Verifiable Example
    on Mar 25, 2025
  14. added 2 commits that reference this issue on Aug 22, 2025
  15. added a commit that references this issue on Aug 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.C-bugCategory: This is a bug.C-optimizationCategory: 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.I-slowIssue: Problems and improvements with respect to performance of generated code.S-has-mcveStatus: A Minimal Complete and Verifiable Example has been found for this issueT-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