-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WASI unwinding is broken in release #132416
Comments
@rustbot label +A-panic +I-unsound +O-wasi +O-wasm +T-compiler |
...I'm not sure this is what we'd usually call unsound, because of the nature of wasm, but it's definitely not supposed to happen either, sooo |
I'm pretty sure the problem here is type confusion: I throw a |
How did you build this code? Locally I get:
In general wasm32-wasip1 definitely does not support unwinding at all. What exactly is happening I can try to help determine but I'll need to know how to build the code |
Generally speaking, unwinding works just fine, it seems to be nested panics that are broken. |
Ah ok, wasm exception handling is pretty far outside my wheelhouse right now. AFAIK the exception-handling support is not super heavily used in Rust right now (but is more heavily used in Emscripten). In that sense just because a few things work probably doesn't mean that everything works (as this issue seems to indicate). cc @coolreader18 though and #118168 as this might also be related to #121438 |
I might have partially cracked this. rust/compiler/rustc_codegen_ssa/src/codegen_attrs.rs Lines 657 to 664 in a0d98ff
I, uh, don't think it's a good idea to mark the throwing intrinsic as nounwind. This might explain why everything seems to work smoothly without LTO, but with |
Ah, yes, that'd probably be it lol. If leaving off the nounwind for |
Hmm, interestingly this is the llvm-ir for both before and after adding a ; unwind::wasm::_Unwind_RaiseException
; Function Attrs: noreturn nounwind uwtable
define dso_local noundef range(i32 0, 10) i32 @_ZN6unwind4wasm22_Unwind_RaiseException17he22b4a6b99a1dce6E(ptr noundef %exception) unnamed_addr #1 {
start:
tail call void @llvm.wasm.throw(i32 noundef 0, ptr noundef %exception) #4
unreachable
}
; Function Attrs: noreturn
declare dso_local void @llvm.wasm.throw(i32 immarg, ptr) unnamed_addr #2 |
Though, in it's declaration in the ; unwind::wasm::_Unwind_RaiseException
; Function Attrs: uwtable
declare dso_local noundef range(i32 0, 10) i32 @_ZN6unwind4wasm22_Unwind_RaiseException17he22b4a6b99a1dce6E(ptr noundef) unnamed_addr #0 I'm wondering if this maybe isn't a problem with the throw codegen, but instead something around memory management? It seems like it's erroring specifically when it tries to drop the panic payload. If the code is changed to this: struct Dropper;
impl Drop for Dropper {
fn drop(&mut self) {
let _ = std::panic::catch_unwind(|| std::panic::resume_unwind(Box::new(())));
}
}
fn main() {
let _dropper = Dropper;
panic!("Triggering landing pad");
} It runs fine:
|
I don't have a cloned Rust repo at the moment, but did you try replacing |
Oh, that does indeed fix the ; unwind::wasm::_Unwind_RaiseException
; Function Attrs: noreturn uwtable
define dso_local noundef range(i32 0, 10) i32 @_ZN6unwind4wasm22_Unwind_RaiseException17he22b4a6b99a1dce6E(ptr noundef %exception) unnamed_addr #1 !dbg !68 {
start:
#dbg_value(ptr %exception, !72, !DIExpression(), !73)
#dbg_value(ptr %exception, !74, !DIExpression(), !83)
tail call void @llvm.wasm.throw(i32 noundef 0, ptr noundef %exception) #2, !dbg !85
unreachable, !dbg !85
}
; Function Attrs: noreturn
declare dso_local void @llvm.wasm.throw(i32 immarg, ptr) unnamed_addr #2 But it doesn't fix the error. |
Although, removing the nounwind seems to be a combination of both marking it |
Well, now this is just bizarre. struct Dropper;
impl Drop for Dropper {
fn drop(&mut self) {
let _ =
std::panic::catch_unwind(|| std::panic::resume_unwind(Box::new(String::from("aaaaA"))));
}
}
#[inline(never)]
fn main() {
let _dropper = Dropper;
let _ =
std::panic::catch_unwind(|| std::panic::resume_unwind(Box::new(String::from("hello2"))));
let _ =
std::panic::catch_unwind(|| std::panic::resume_unwind(Box::new(String::from("hello3"))));
panic!("Triggering landing pad");
} If there are at least 2 of those |
Best I can tell, the |
Ok, yeah, it's definitely happening after struct Dropper;
impl Drop for Dropper {
fn drop(&mut self) {
println!("before catch_unwind");
let _ =
std::panic::catch_unwind(|| std::panic::resume_unwind(Box::new(String::from("aaaaA"))));
println!("after catch_unwind");
}
}
#[inline(never)]
fn main() {
let _dropper = Dropper;
std::panic::panic_any(())
}
|
Definitely something to do with optimizations and inlining. struct Dropper;
// #[inline(never)] here
fn foo() {
let _ = std::panic::catch_unwind(|| std::panic::resume_unwind(Box::new(String::from("aaaaA"))));
}
impl Drop for Dropper {
// OR #[inline(never)] here prevent the issue from happening.
fn drop(&mut self) {
println!("before catch_unwind");
foo();
println!("after catch_unwind");
}
}
#[inline(never)]
fn main() {
let _dropper = Dropper;
std::panic::panic_any(())
} |
Ok, 100% some sort of miscompilation. struct Dropper;
impl Drop for Dropper {
fn drop(&mut self) {
let _ = std::panic::catch_unwind(|| std::panic::resume_unwind(Box::new(Foo)));
}
}
#[derive(Debug)]
struct Foo;
#[derive(Debug)]
struct Bar;
fn main() {
std::panic::catch_unwind(|| {
let _dropper = Dropper;
std::panic::resume_unwind(Box::new(Bar))
})
.unwrap_or_else(|e| {
dbg!(e.downcast_ref::<Foo>());
assert!(e.is::<Bar>());
});
}
|
@rustbot label +I-miscompile |
As far as I can see from the generated wasm code, there's a spurious |
Looks like an LLVM WASM backend bug to me. The post-opt IR of all the failing examples looks like this, in pseudocode:
The key part here is that the try block has two successors, i.e.
However, the actual codegen is:
These are the same instructions, but arranged in a really wrong way, which leads to the same panic being caught twice. This seems to be a bug in successor handling specifically, and that explains why disabling inlining helps. Perhaps confused basic blocks handling? @rustbot label +A-LLVM |
Actually, this codegen would be valid if #include <cstdio>
__attribute__((noinline)) void print_current() noexcept {
try {
throw;
} catch (int x) {
printf("%d\n", x);
}
}
struct Dropper {
~Dropper() {
try {
throw 1;
} catch (...) {
print_current();
}
}
};
int main() {
try {
Dropper dropper;
throw 2;
} catch (...) {
print_current();
}
} Expected output: |
I've filed an upstream bug: llvm/llvm-project#114600 |
This sounds relevant to T-libs because of the stdarch PR. Is there a practice of removing labels when the topic has been resolved that I'm unaware about? |
No, I didn't mean to remove T-libs but the label UI decided apparently I wanted to. |
WG-prioritization assigning priority (Zulip discussion). @rustbot label -I-prioritize +P-medium |
The LLVM PR merged, so now it's just waiting for that to be merged into rust-lang/llvm-project. |
No, we also have to merge the |
We might want to keep this issue around for a bit longer. Emscripten seems to still have some problems with WASM unwinding (emscripten-core/emscripten#22861), and I'm not sure if they are applicable to Rust too. |
This is target
wasm32-wasip1
withpanic = "unwind"
, running on V8. I tried this code:I expected to see this happen: a panic message, followed by the process exit.
Instead, this happened:
Meta
rustc --version --verbose
:node --version
:Compile with
--release
, run withI'm not sure if this is a rustc bug, an LLVM bug, or a V8 bug, but I thought this might be important to track.
The text was updated successfully, but these errors were encountered: