Change:
In : Assembly: is known to be so the deallocation is optimized away in the normal flow. But in landing pad for unwinding, it still generates a branch to check if 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 of should be completely elided so that can be a tail call.
I'd say the main problem here is that dropinplace isn't inlined. If it were, it would be optimized away.
tracks alternative MIR solution which is to track active enum variants during drop elaboration.
After taking a closer look, the reason it doesn't get inlined is that we explicitly mark it as noinline here: Generally sensible, but backfires here. Though argument promotion doesn't happen either, not sure why that is.
It's aiming at variant which doesn't need drop. I'm not sure if it can also eliminate conditional de-allocation inside drop like .
Apparently because a bitcast is sitting before the load.
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):
Seems this issue has the same root cause as . And the LLVM issue of heavily inlining, mentioned in , which let us mark drop in unwinding noinline, is still NOT fixed. My point is that the MIR approach cannot handle manual drop impl which conditional does nothing. This code cannot have eliminated without inlining.
bors also closing this issue in was unexpected, cc

Discussion:
self.cx } <del> fn do_not_inline(&mut self, _llret: RValue<'gcc>) { </del> <ins> fn apply_attrs_to_cleanup_callsite(&mut self, _llret: RValue<'gcc>) { </ins> unimplemented!(); }

unsafe { llvm::LLVMBuildZExt(self.llbuilder, val, dest_ty, UNNAMED) } } <del> fn do_not_inline(&mut self, llret: &'ll Value) { llvm::Attribute::NoInline.apply_callsite(llvm::AttributePlace::Function, llret); </del> <ins> fn apply_attrs_to_cleanup_callsite(&mut self, llret: &'ll Value) { // Cleanup is always the cold path. llvm::Attribute::Cold.apply_callsite(llvm::AttributePlace::Function, llret); // In LLVM versions with deferred inlining (currently, system LLVM < 14), // inlining drop glue can lead to exponential size blowup, see #41696 and #92110. if !llvm_util::is_rust_llvm() && llvm_util::get_version() < (14, 0, 0) { llvm::Attribute::NoInline.apply_callsite(llvm::AttributePlace::Function, llret); } </ins> } }

pub fn LLVMRustVersionMinor() -> u32; pub fn LLVMRustVersionPatch() -> u32; <ins> pub fn LLVMRustIsRustLLVM() -> bool; </ins> pub fn LLVMRustAddModuleFlag(M: &Module, name: *const c_char, value: u32); pub fn LLVMRustMetadataAsValue<'a>(C: &'a Context, MD: &'a Metadata) -> &'a Value;

} } <ins> /// Returns `true` if this LLVM is Rust's bundled LLVM (and not system LLVM). pub fn is_rust_llvm() -> bool { // Can be called without initializing LLVM unsafe { llvm::LLVMRustIsRustLLVM() } } </ins> pub fn print_passes() { // Can be called without initializing LLVM unsafe {

let llret = bx.call(fn_ty, fn_ptr, &llargs, self.funclet(fx)); bx.apply_attrs_callsite(&fn_abi, llret); if fx.mir[self.bb].is_cleanup { <del> // Cleanup is always the cold path. Don't inline // drop glue. Also, when there is a deeply-nested // struct, there are "symmetry" issues that cause // exponential inlining - see issue #41696. bx.do_not_inline(llret); </del> <ins> bx.apply_attrs_to_cleanup_callsite(llret); </ins> } if let Some((ret_dest, target)) = destination {

) -> Self::Value; fn zext(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value; <del> fn do_not_inline(&mut self, llret: Self::Value); </del> <ins> fn apply_attrs_to_cleanup_callsite(&mut self, llret: Self::Value); </ins> }

extern "C" uint32_t LLVMRustVersionMajor() { return LLVM_VERSION_MAJOR; } <ins> extern "C" bool LLVMRustIsRustLLVM() { #ifdef LLVM_RUSTLLVM return true; #else return false; #endif } </ins> extern "C" void LLVMRustAddModuleFlag(LLVMModuleRef M, const char *Name, uint32_t Value) { unwrap(M)->addModuleFlag(Module::Warning, Name, Value);

<ins> // no-system-llvm: needs #92110 // compile-flags: -Cno-prepopulate-passes #![crate_type = "lib"] // This test checks that drop calls in unwind landing pads // get the `cold` attribute. // CHECK-LABEL: @check_cold // CHECK: call void {{.+}}drop_in_place{{.+}} [[ATTRIBUTES:#[0-9]+]] // CHECK: attributes [[ATTRIBUTES]] = { cold } #[no_mangle] pub fn check_cold(f: fn(), x: Box<u32>) { // this may unwind f(); } </ins>

<ins> // no-system-llvm: needs #92110 + patch for Rust alloc/dealloc functions // compile-flags: -Copt-level=3 #![crate_type = "lib"] // This test checks that we can inline drop_in_place in // unwind landing pads. // Without inlining, the box pointers escape via the call to drop_in_place, // and LLVM will not optimize out the pointer comparison. // With inlining, everything should be optimized out. // See https://github.com/rust-lang/rust/issues/46515 // CHECK-LABEL: @check_no_escape_in_landingpad // CHECK: start: // CHECK-NEXT: ret void #[no_mangle] pub fn check_no_escape_in_landingpad(f: fn()) { let x = &*Box::new(0); let y = &*Box::new(0); if x as *const _ == y as *const _ { f(); } } // Without inlining, the compiler can't tell that // dropping an empty string (in a landing pad) does nothing. // With inlining, the landing pad should be optimized out. // See https://github.com/rust-lang/rust/issues/87055 // CHECK-LABEL: @check_eliminate_noop_drop // CHECK: start: // CHECK-NEXT: call void %g() // CHECK-NEXT: ret void #[no_mangle] pub fn check_eliminate_noop_drop(g: fn()) { let _var = String::new(); g(); } </ins>