extern “C unwind” notes
- Target case
- unwinding over FFI integration
- Example use cases
- Rust panics, traverses native frames w/o destructors, panic caught in a Rust frame
- Main design question at hand
- extern “C unwind”
- using “the native” unwinding mechanism, as defined by the target specification
- DWARF on linux, SEH on msvc
- extern “C panic”
- using “the Rust” unwinding mechanism, which may or may not be equal to the target specification
- any use case for this apart from “it’s easiest”?
- only one is Rust loading other Rust code, but that’s really more of a case for a stable Rust ABI (or semi-stable)
- require the Rust unwinding ABI to match the platform
- it would simplify a lot of things, but has cons
- maybe it would be worth it to spell out what its pros / cons are
- What exactly are we guaranteeing at this point?
- The reference says:
- “extern "C" -- This is the same as extern fn foo(); whatever the default your C compiler supports.
- This might be problematic because it might mean that whether extern "C" can “unwind” is platform dependent, e.g., on MSVC the platform compiler is not a C compiler but a C++ compiler. However, it is not unsound to use nounwind for extern "C" on MSVC because nounwind allows letting asynchronous exceptions escape.
- We do not guarantee what happens when a function with an ABI different from "Rust" unwinds anywhere.
- A useful but minimal guarantee would be that FFI boundaries marked with the new ABI do not explicitly disallow system unwinding to proceed across the boundary; beyond that, it’s all implementation-defined behavior
- Possible future situations worth considering
- What if a new unwinding mechanism should arise, where C++ compilers have not yet transitioned, or offer optional support, but we want to adopt it
- We could change our default target uses, if we think we can get away with it
- Equivalent to e.g. adopting newer instruction sets
- This would break any user relying on existent behavior.
- Issue a new target that supports this mechanism and people can opt in
- “rust unwind == native unwind” remains true all the time
- “rust unwind == native unwind” not true, in which case on older target we require shims
- What if C++ changes its unwinding mechanism to one that differs from the platform ?
- Is there a distinction between what C++ uses and the “platform”? eg i686-pc-windows-gnu uses DWARF and i686-pc-windows-msvc uses SEH, despite both being the same architecture and OS
- Good question. On windows C++ uses synchronous exceptions (SEH) in the MSVC targets, and DWARF or SJLJ on the GNU targets (mingw supports both for C++). For all of them, Windows uses asynchronous exceptions which is slightly different from what C++ uses for MSVC, and very different from what the GNU targets use. I’m not really sure “what’s the platform” in these cases, for C++ code to interoperate, code needs to agree on how C++ exceptions are implemented, and this shows in the target triples, but C++ exceptions are not the platform unwinding mechanism; they are something else built on top, and e.g., other parts of the ABI like RTTI, vtables, etc. also need to match for try /catch to work for C++ code. In Rust the panic payload is a Box<dyn Any + ...> so for the Any to work fat pointer layout, vtables, type_id, etc. all need to match as well.
- Interactions not addressed in RFC 2753
- If native code raises an exception, and it traverses Rust stack frames, do destructors run?
- If native code raises an exception, does catch_panic intercept it?
- If Rust code raises a panic, and it traverses native frames, what happens?
- If there are destructors, do they run?
- Double panics: if native code raises an exception, and it traverses Rust stack frames that panic (e.g. on a destructor), does the program abort ?
- Key question perhaps
- Are the points above “undefined behavior” or “implementation defined”?
- Today, it is definitely UB because of the nounwind attribute and attempts to insert the abort shim
- Not clear what the real difference is. Perhaps it is that rustc would have to define (and potentially stabilize) that behavior.
- Perhaps the difference is that we are avoiding LLVM UB — we won’t miscompile the code.
- Analogy: Rust ABI
- Idea:
- spell out the kind of changes that we anticipate and which could happen
- Key desiderata and/or concerns
- We shouldn’t constrain our future behavior, for example to adapt to changes in C and C++.
- e.g., the proposal to alter our behavior to match new C++ proposals