Stream: wg-ffi-unwind

Topic: implementing "abort on FFI call"


nikomatsakis (Nov 13 2019 at 15:21, on Zulip):

So I was thinking about how to measure the impact of a proposal where the C ABI permits unwinding but -- if we are -Cpanic=abort -- we abort if unwinding actually occurs (versus UB).

I think the way to do this would be to modify the no_landing_pads transformation. Right now, it just removes all landing pads. I think what we would actually want is to check if this is a function call and -- in the case where it is -- inspect the ABI and callee. If the ABI is not Rust (or Rust call), and the callee is not known, then we would instead rewrite the unwind to branch to a block with TerminatorKind::Abort.

We might or might not have such a block today, so we can (lazilly, and at most once) create such a block.

nikomatsakis (Nov 13 2019 at 15:21, on Zulip):

There might be some other assertions that need to be adjusted.

nikomatsakis (Nov 13 2019 at 15:43, on Zulip):

I put in some work towards implementing this in the abort-on-ffi-call branch -- not building yet or quite complete, but almost there (I think..?)

nikomatsakis (Nov 13 2019 at 16:19, on Zulip):

OK I think this is building now, but I really have to stop and do other stuff

nikomatsakis (Nov 13 2019 at 16:45, on Zulip):

(OK, it builds, but it doesn't work; I don't quite know what's going on, something to do with the remove_noop_landing_pads pass...)

nikomatsakis (Nov 13 2019 at 16:46, on Zulip):

oh, it's cause the landing pad has nothing to do I guess

nikomatsakis (Nov 14 2019 at 01:47, on Zulip):

ok, I think that is working now -- the MIR looks right, but it's sort of hard to test. I tried hacking up some C++ function that throws even though it's not supposed to, but C++ keeps calling std::terminate since it can't find a catch handler (true even when I put the Rust function sandwiched between two C++ functions, and one of them has a catch)

Kyle Strand (Nov 14 2019 at 03:39, on Zulip):

Wow, the Rust sandwich breaks catch? I did not expect that at all.

nikomatsakis (Nov 14 2019 at 10:34, on Zulip):

I can only presume it has to do with interrupting the ability to walk up the stack and find the catch handler somehow? This is true on linux, at least.

nikomatsakis (Nov 14 2019 at 10:35, on Zulip):

Well hey, it does abort... :)

Amanieu (Nov 14 2019 at 13:48, on Zulip):

@nikomatsakis What does the LLVM IR look like?

gnzlbg (Nov 14 2019 at 13:49, on Zulip):

nikomatsakis 2:47 AM
ok, I think that is working now -- the MIR looks right, but it's sort of hard to test. I tried hacking up some C++ function that throws even though it's not supposed to, but C++ keeps calling std::terminate since it can't find a catch handler (true even when I put the Rust function sandwiched between two C++ functions, and one of them has a catch)

That's by design, noexcept C++ functions have an "abort-on-panic" shim that calls std::terminate

gnzlbg (Nov 14 2019 at 13:50, on Zulip):

@nikomatsakis one needs to create a normal C++ function that throws, and then just use extern "C" { ... }

nikomatsakis (Nov 14 2019 at 13:51, on Zulip):

@gnzlbg I did not declare anything noexcept

nikomatsakis (Nov 14 2019 at 13:51, on Zulip):

I did create a normal C++ function

nikomatsakis (Nov 14 2019 at 13:51, on Zulip):

I'll push the project

nikomatsakis (Nov 14 2019 at 13:51, on Zulip):

@Amanieu I'm not sure :) I have to look how to save the LLVM IR, I forget

gnzlbg (Nov 14 2019 at 13:51, on Zulip):

I'll take a look

nikomatsakis (Nov 14 2019 at 13:56, on Zulip):

@gnzlbg I pushed my example to this branch -- it was the rust-to-cpp and cpp-to-rust (sandwich)

nikomatsakis (Nov 14 2019 at 13:57, on Zulip):

in both cases, I get the same error

nikomatsakis (Nov 14 2019 at 13:57, on Zulip):

looking at the c++ referenece manual, it suggested that if there is no catch block then C++ will terminate (which fits what I'm seeing)

nikomatsakis (Nov 14 2019 at 13:57, on Zulip):

it works without Rust in between

nikomatsakis (Nov 14 2019 at 13:57, on Zulip):

(that is, the throw gets caught)

nikomatsakis (Nov 14 2019 at 13:59, on Zulip):

@Amanieu I think the llvm output is in this gist

nikomatsakis (Nov 14 2019 at 13:59, on Zulip):

I guess it has nounwind and other stuff

nikomatsakis (Nov 14 2019 at 13:59, on Zulip):

oh wait

nikomatsakis (Nov 14 2019 at 13:59, on Zulip):

that wasn't built w/ my branch

nikomatsakis (Nov 14 2019 at 14:00, on Zulip):

llvm output from my branch

nikomatsakis (Nov 14 2019 at 14:02, on Zulip):

(this is a debug build)

Amanieu (Nov 14 2019 at 14:04, on Zulip):

@nikomatsakis Your function needs the uwtable attribute, otherwise dwarf tables aren't generated and the unwinder doesn't know how to unwind past your function to find the actual catch block in the caller.

nikomatsakis (Nov 14 2019 at 14:05, on Zulip):

@Amanieu ok -- would it make sense to have that only for the "leaf functions" that actually invoke FFI?

nikomatsakis (Nov 14 2019 at 14:05, on Zulip):

(after all, I'm going to catch any unwinding and trap)

Amanieu (Nov 14 2019 at 14:06, on Zulip):

Also in optimized builds the landing pad is probably going to be optimized away by LLVM as unreachable since triple_input is nounwind.

nikomatsakis (Nov 14 2019 at 14:07, on Zulip):

oh yeah, I need to supress that I guess

Amanieu (Nov 14 2019 at 14:07, on Zulip):

I think that just having no uwtable is good enough in practice to abort. The shim should only be needed on targets that require uwtable to be present for other reasons

Amanieu (Nov 14 2019 at 14:08, on Zulip):

Hmm, actually this only works for targets that use dwarf unwinding. SjLj unwinding doesn't use unwinding tables and always works. And I have no idea wtf SEH will do here.

Amanieu (Nov 14 2019 at 14:10, on Zulip):

The good news is, the overhead for abort on extern "C" unwinding is going to be 0 in most cases since it already aborts :)

gnzlbg (Nov 14 2019 at 14:13, on Zulip):

looking at the c++ referenece manual, it suggested that if there is no catch block then C++ will terminate (which fits what I'm seeing)

This makes sense. When unwinding hits a thread boundary, C++ calls std::terminate.

nikomatsakis (Nov 14 2019 at 14:13, on Zulip):

Hmm, actually this only works for targets that use dwarf unwinding. SjLj unwinding doesn't use unwinding tables and always works. And I have no idea wtf SEH will do here.

yeah I have been meaning to try this on windows -- I'm actually running windows, so that should be easy enough, except I've never tried to build Rust outside of WSL :laughter_tears:

nikomatsakis (Nov 14 2019 at 14:14, on Zulip):

I think that just having no uwtable is good enough in practice to abort. The shim should only be needed on targets that require uwtable to be present for other reasons

I don't quite follow-- I think you're saying the current branch status itself is fine, since either (a) the c++ code needs uwtable and it aborts when it doesn't find it (as today) or (b) some other environments may not need uwtable, in which case they will get trapped by the unwinding?

nikomatsakis (Nov 14 2019 at 14:15, on Zulip):

anyway i'd like to see the tra in action so I'll try to add the various attributes (and remove "nounwind") later on

Amanieu (Nov 14 2019 at 14:18, on Zulip):

Windows and Android have the requires_uwtable target attribute set, which means that unwinding tables are always generated even with panic=abort. For those platforms we will need to perform extra work to abort on unwind.

Last update: Dec 12 2019 at 01:30UTC