Change:
<ins> // run-pass // ignore-wasm32-bare compiled with panic=abort by default // ignore-emscripten no threads support // rust-lang/rust#64655: with panic=unwind, a panic from a subroutine // should still run destructors as it unwinds the stack. However, // bugs with how the nounwind LLVM attribute was applied led to this // simple case being mishandled *if* you had fat LTO turned on. // Unlike issue-64655-extern-rust-must-allow-unwind.rs, the issue // embodied in this test cropped up regardless of optimization level. // Therefore it seemed worthy of being enshrined as a dedicated unit // test. // LTO settings cannot be combined with -C prefer-dynamic // no-prefer-dynamic // The revisions just enumerate lto settings (the opt-level appeared irrelevant in practice) // revisions: no thin fat //[no]compile-flags: -C lto=no //[thin]compile-flags: -C lto=thin //[fat]compile-flags: -C lto=fat #![feature(core_panic)] // (For some reason, reproducing the LTO issue requires pulling in std // explicitly this way.) #![no_std] extern crate std; fn main() { use std::sync::atomic::{AtomicUsize, Ordering}; use std::boxed::Box; static SHARED: AtomicUsize = AtomicUsize::new(0); assert_eq!(SHARED.fetch_add(0, Ordering::SeqCst), 0); let old_hook = std::panic::take_hook(); std::panic::set_hook(Box::new(|_| { } )); // no-op on panic. let handle = std::thread::spawn(|| { struct Droppable; impl Drop for Droppable { fn drop(&mut self) { SHARED.fetch_add(1, Ordering::SeqCst); } } let _guard = Droppable; let s = "issue-64655-allow-unwind-when-calling-panic-directly.rs"; core::panicking::panic(&("???", s, 17, 4)); }); let wait = handle.join(); // Reinstate handler to ease observation of assertion failures. std::panic::set_hook(old_hook); assert!(wait.is_err()); assert_eq!(SHARED.fetch_add(0, Ordering::SeqCst), 1); } </ins>

Discussion:
Hi, It looks like calls to are getting removed when using for a release build. It doesn't seem to occur with or . I've got a test case here: , which can be reproduced like this: (more details in the ) For context, I first noticed this in a project where I am using an to communciate between threads, where a producer thread reads messages from the network, and the main thread saves those to a database. I was relying on the fact that the implementation for a channel will in effect close the channel, so the main thread can find out that the producer has failed, and so we should kill the process. What I saw instead was the producer thread panicing, but the main thread would never get woken up. I've seen this on macOS and Linux .
triage: P-high. Assigning to self. Removing nomination label.
Doing some bisection. This was introduced between Rust 1.32 and 1.33
More specifically, this was introduced between nightly-2018-12-08 and nightly-2018-12-14: (unfortunately no nightlies are available between those two points in the rustup archive.)
These are the PR's that bors merged between those two points:
According to my bisection via local builds, this was injected by (!)
(Have I mentioned before that we probably need to be doing more testing of the various LTO configurations? Would it be feasible to run our test suite in all of the lto modes from {, , } ?)
For what its worth, 's PR fixes this (I think; I need to double-check atop a rebased-on-master version). yes, confirmed now atop rebased version
Thanks for tracking this down
Okay I some instrumentation to the compiler and I think I know where the problem here is coming from. Here's the executive summary: PR changed the logic for where we add the LLVM attribute PR has asserted, from the beginning, that some of that new logic was wrong. In particular, it noted that: I myself was confused by this comment, because it looked to me like the PR as written was taking pains to return for abis that match and . So why is the test crate in this issue illustrating the problem outlined by above? First of all, here is a standalone that reproduces the problem for me.

(You still have to turn on to observe the issue, but no other optimizations; e.g. still reproduces the issue for me): Running the compiler with gave me intermediate files that I could disassemble via and then inspect the resulting files. This led me to notice that some functions had on them that I did not expect. Second, I instrumented a local to report (via ) whenever it used either of these two cases: Just imagine the above, with two inserted calls that look like: println!("choosing unwind=false for foreign item {:?} with sig: {:?}", id, sig); and println!("choosing unwind=false for non-foreign {:?} with sig: {:?}", id, sig); And then I reviewed the output this spits out during both the stage1 bootstrapping and the actual compilation of the test case above. Here's the smoking gun from that output, I think: Unfortunately, the impl for does not include the abi (nor the unsafety). I'm guessing we'd see "Rust" there if that were included. namely, we are going through that first branch, already mentioned by , when we compile this declaration: This causes the generated declaration for to be tagged , even though that function is pretty much the function that is going to unwind. (But you need to have a somewhat nasty combination of factors to actually observe the optimization performed by LLVM in this case, which is how I guess this has gone relatively unobserved for so long, apart from what noticed in their own work.)
The short version of my previous comment is that I bet I could make a small change to mark extern fn's with Rust or RustCall ABIs as unwinding, which would have less impact than proposal and thus be a better candidate for backport. Update: Confirmed locally; yes we can fix this via a small targeted change. (Not sure if it will be the right long term solution, but it certainly seems like the right short-term step if only for backport purposes.)