Skip to content

Unable to optimize out branches in drop function in unwind landing pad #87055

Description

@oxalica

In this code:

pub fn foo(g: fn()) {
    let _var: Option<Box<i32>> = None;
    g();
}

Assembly:

core::ptr::drop_in_place<core::option::Option<alloc::boxed::Box<i32>>>:
        mov     rdi, qword ptr [rdi]
        test    rdi, rdi
        je      .LBB0_1
        mov     esi, 4
        mov     edx, 4
        jmp     qword ptr [rip + __rust_dealloc@GOTPCREL]
.LBB0_1:
        ret

example::foo:
        push    rbx
        sub     rsp, 16
        mov     qword ptr [rsp + 8], 0
        call    rdi
        add     rsp, 16
        pop     rbx
        ret
        mov     rbx, rax
        lea     rdi, [rsp + 8]
        call    core::ptr::drop_in_place<core::option::Option<alloc::boxed::Box<i32>>>
        mov     rdi, rbx
        call    _Unwind_Resume@PLT
        ud2

DW.ref.rust_eh_personality:
        .quad   rust_eh_personality

_var is known to be None so the deallocation is optimized away in the normal flow. But in landing pad for unwinding, it still generates a branch to check if _var is null. But it's a local variable which is not able to be modified in anywhere else and should be known to be null.

I'm expecting the drop of _var should be completely elided so that g() can be a tail call.

Activity

  1. nikic commented on Jul 11, 2021

    @nikic
    Contributor

    I'd say the main problem here is that drop_in_place isn't inlined. If it were, it would be optimized away.

  2. added
    I-slowIssue: Problems and improvements with respect to performance of generated code.
    A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.
    on Jul 11, 2021
  3. tmiasko commented on Jul 11, 2021

    @tmiasko
    Contributor

    #69715 tracks alternative MIR solution which is to track active enum variants during drop elaboration.

  4. nikic commented on Jul 11, 2021

    @nikic
    Contributor

    After taking a closer look, the reason it doesn't get inlined is that we explicitly mark it as noinline here:

    bx.do_not_inline(llret);
    Generally sensible, but backfires here.

    Though argument promotion doesn't happen either, not sure why that is.

  5. oxalica commented on Jul 11, 2021

    @oxalica
    ContributorAuthor

    #69715 tracks alternative MIR solution which is to track active enum variants during drop elaboration.

    It's aiming at enum variant which doesn't need drop. I'm not sure if it can also eliminate conditional de-allocation inside drop like drop(String::new()).

  6. nikic commented on Jul 11, 2021

    @nikic
    Contributor

    Though argument promotion doesn't happen either, not sure why that is.

    Apparently because a bitcast is sitting before the load.

    ; not promoted
      %0 = bitcast i32** %_1 to {}**
      %1 = load {}*, {}** %0, align 8
    ; promoted
      %0 = load i32*, i32** %_1, align 8
      %1 = bitcast i32* %0 to {}*
    
  7. tmiasko commented on Jul 11, 2021

    @tmiasko
    Contributor

    #69715 tracks alternative MIR solution which is to track active enum variants during drop elaboration.

    It's aiming at enum variant which doesn't need drop. I'm not sure if it can also eliminate conditional de-allocation inside drop like drop(String::new()).

    The idea is to solve a data flow problem that answers which enum variants might reach a drop terminator. In the case here, we know that Option doesn't implement Drop, and that the only variant that might reach drop is None which doesn't contain anything that needs to be dropped either. Thus it would be valid to remove the drop terminator.

    This is de-facto implemented as part of const validation which statically ensures that there are no live drops. Following const variant already compiles successfully (even though destructors cannot be evaluated at compile-time):

    pub const fn foo() {
        let _var: Option<Box<i32>> = None;
        g();
    }
    
    pub const fn g() {}
  8. oxalica commented on Jul 11, 2021

    @oxalica
    ContributorAuthor

    we explicitly mark it as noinline here

    Seems this issue has the same root cause as #46515.

    And the LLVM issue of heavily inlining, mentioned in #41696, which let us mark drop in unwinding noinline, is still NOT fixed. https://bugs.llvm.org/show_bug.cgi?id=33072

    The idea is to solve a data flow problem that answers which enum variants might reach a drop terminator.

    My point is that the MIR approach cannot handle manual drop impl which conditional does nothing. This code cannot have drop eliminated without inlining.

    pub fn foo(g: fn()) {
        let _var = String::new(); // This does not alloc.
        g();
    }
  9. 11 remaining items

  10. added
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    on Apr 5, 2023
  11. added a commit that references this issue on Jun 27, 2023
  12. added 2 commits that reference this issue on Oct 2, 2023
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.A-codegenArea: Code generationI-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.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions