Stream: wg-ffi-unwind

Topic: Allow unwinding from extern "C" by default


Amanieu (Oct 22 2019 at 06:06, on Zulip):

I've been looking into how C++ deals with exceptions in FFI and come up with this idea: what if we made extern "C" allow unwinding by default, unless specifically marked with a #[no_unwind] attribute.

Amanieu (Oct 22 2019 at 06:08, on Zulip):

This matches the behavior of C++ in a few ways:
- extern "C" functions in C++ are allowed to throw exceptions.
- Functions declared noexcept cannot throw exceptions
- Functions defined noexcept will call std::terminate if an uncaught exception reaches them.

Kyle Strand (Oct 22 2019 at 06:55, on Zulip):

I believe this matches the advice given to me by nlewycky when I consulted them about the unwinding issue some time ago. (I don't think they've personally chimed in on any of the issues or RFCs except to describe their personal use case here: https://github.com/rust-lang/rfcs/pull/2753#issuecomment-527587083)

Kyle Strand (Oct 22 2019 at 06:56, on Zulip):

nlewycky is an ex-LLVM dev, which I expect informs their opinion on the matter.

Kyle Strand (Oct 22 2019 at 06:58, on Zulip):

That said, extern "C" is already substantially different from its C++ counterpart, because it doesn't prevent name-mangling. (#[no_mangle] is arguably closer to the semantics of C++'s extern "C" than Rust's extern "C" is.)

Amanieu (Oct 22 2019 at 06:59, on Zulip):

I just feel that we should have the safe version be the default, and if you really want to tell the optimizer that an FFI function does not unwind then you can add an attribute.

Kyle Strand (Oct 22 2019 at 07:04, on Zulip):

Well....

Kyle Strand (Oct 22 2019 at 07:04, on Zulip):

Have you read much or all of the discussions in RFCs 2699 and 2753?

Kyle Strand (Oct 22 2019 at 07:06, on Zulip):

The main arguments against that view, as I understood them, were that (1) _exposing_ unwinding (from Rust extern "C" functions) would be _less_ safe than aborting, and (2) having a different assumption about unwinding for extern "C" depending on whether the function is being "imported" vs defined would be inconsistent and therefore confusing.

Kyle Strand (Oct 22 2019 at 07:07, on Zulip):

I am of the opinion that the "consistently conservative" behavior would be to prohibit _exposing_ unwinding by default (i.e. Rust extern "C" functions would have the abort logic), but to assume that "imported" extern "C" functions may unwind (and that the caller therefore needs a landing pad).

Kyle Strand (Oct 22 2019 at 07:08, on Zulip):

Let me get the link to the prior discussion....

Kyle Strand (Oct 22 2019 at 07:15, on Zulip):

Here's where I brought up the idea that the defaults should be opposites: https://github.com/rust-lang/rfcs/pull/2753#issuecomment-528945332

Kyle Strand (Oct 22 2019 at 07:16, on Zulip):

I'm not sure anyone in either RFC recommended making the default unwind(allow) for both cases.

Kyle Strand (Oct 22 2019 at 07:16, on Zulip):

A major concern there is that without -fexceptions, C code on most major non-Windows platforms can't properly support unwinding.

Amanieu (Oct 22 2019 at 07:25, on Zulip):

Not having -fexceptions doesn't block unwinding though, unwinding through C code still works fine.

Amanieu (Oct 22 2019 at 07:25, on Zulip):

Unwinding just skips frames with no unwind info.

Amanieu (Oct 22 2019 at 07:29, on Zulip):

Hmm technically LLVM says it's UB because we use the nounwind attribute

Kyle Strand (Oct 22 2019 at 07:29, on Zulip):

for any ABI specifier where we don't abort on unwind, we'd stop emitting nounwind

Kyle Strand (Oct 22 2019 at 07:30, on Zulip):

...I did not realize that it's well-defined behavior to not have landing pads in intermediate frames.

Kyle Strand (Oct 22 2019 at 07:31, on Zulip):

That definitely changes my understanding of -fexceptions. I had thought that without -fexceptions, the behavior of a C++ throw crossing over C frames would be undefined.

Kyle Strand (Oct 22 2019 at 07:31, on Zulip):

....in an actually-dangerous way, not just a "the C++ standard can't state what the C language does, and C doesn't mention exceptions, therefore it's undefined" way.

Kyle Strand (Oct 22 2019 at 07:34, on Zulip):

Here is Centril's response to my statement (linked above) that there should be opposite defaults: https://github.com/rust-lang/rfcs/pull/2753#issuecomment-529084321

Kyle Strand (Oct 22 2019 at 07:35, on Zulip):

Though note that he's objecting to treating extern "C" function pointers (rather than import declarations) as unwind-allowed-by-default

Kyle Strand (Oct 22 2019 at 07:35, on Zulip):

...but I think that the one would imply the other, since at the point of usage, function pointers have less associate information.

Kyle Strand (Oct 22 2019 at 07:36, on Zulip):

There is also a separate argument in that thread about why extern "C" shouldn't unwind, namely, because of the name "C", since the C language doesn't have exceptions.

Kyle Strand (Oct 22 2019 at 07:37, on Zulip):

Since C can in fact have unwinding (e.g. in Windows with SEH) and extern "C" is taken from C++ (not from C), I don't really agree with that particular argument.

Kyle Strand (Oct 22 2019 at 07:46, on Zulip):

Can you think of any scenario where emitting a panic from Rust into foreign (C, C++, D, etc) code compiled with the corresponding backend would cause undefined behavior? (Assuming that the foreign code has no RAII or finally constructs, and assuming that it doesn't attempt to catch or otherwise interact with the unwind operation)

Kyle Strand (Oct 22 2019 at 07:50, on Zulip):

If not, and if we have some confidence that this is considered "well defined" for the various platforms, then I would be strongly inclined to just change the (non) specification of Rust's extern "C" unwinding from "undefined" to "implementation defined".

Kyle Strand (Oct 22 2019 at 07:51, on Zulip):

...and providing some kind of unwind(abort) annotation, for use anywhere in the language, since that seems potentially useful.

Kyle Strand (Oct 22 2019 at 07:54, on Zulip):

With that said, I expect (based on the conversations in those RFCs) that not all the stakeholders would agree with this direction.

Kyle Strand (Oct 22 2019 at 07:56, on Zulip):

Additionally, one benefit of having separate ABI strings for functions that can unwind versus those that can't is that the type system can ensure that the information is preserved even via function pointers. Of course, that also carries with it the downside of introducing a new incompatible function pointer type.

Kyle Strand (Oct 22 2019 at 08:02, on Zulip):

Early on when this issue was being discussed, I asked on StackOverflow about "how undefined" the behavior _even with_ -fexceptions is. Here's one of the answers: https://stackoverflow.com/a/55818675/1858225

Kyle Strand (Oct 22 2019 at 08:03, on Zulip):

I consider that answer unhelpful and probably wrong, but I'm certainly not an expert.

Kyle Strand (Oct 22 2019 at 08:04, on Zulip):

In any case, it's an example of an attitude that makes me hesitant to push for letting exceptions escape from extern "C" in Rust by default.

Amanieu (Oct 22 2019 at 08:08, on Zulip):

Technically a Rust panic unwinding into C code (compiled without -fexceptions) would be UB. But in practice it would work as expected with the unwinder simply skipping the C frames.

Kyle Strand (Oct 22 2019 at 08:09, on Zulip):

Does the Itanium ABI not specify what happens when there aren't landing pads?

Kyle Strand (Oct 22 2019 at 08:09, on Zulip):

(in frames being crossed by an exception)

Amanieu (Oct 22 2019 at 08:09, on Zulip):

Nothing happens, it just continues unwinding.

Amanieu (Oct 22 2019 at 08:10, on Zulip):

Let me double check

Kyle Strand (Oct 22 2019 at 08:10, on Zulip):

If it does specify that, then wouldn't the behavior be implementation-defined on platforms supporting that ABI, as long as Rust says it is?

Amanieu (Oct 22 2019 at 08:11, on Zulip):

OK, so here's what happens. First of all, the C code must have unwinding tables. This is usually on by default, even without -fexceptions.

Kyle Strand (Oct 22 2019 at 08:12, on Zulip):

Ah! Okay, so that seems important - without unwinding tables, the behavior would be undefined even at the platform level, correct?

Amanieu (Oct 22 2019 at 08:12, on Zulip):

Without unwinding tables the C code is basically treated as "end-of-stack"

Kyle Strand (Oct 22 2019 at 08:12, on Zulip):

What does that mean?

Kyle Strand (Oct 22 2019 at 08:13, on Zulip):

Excluding the case of missing unwinding tables, I guess what I'm trying to say is that it seems like this is only UB because the various language standards don't say otherwise, but most (or all?) of the platforms providing the ABIs seem to actually provide well-defined behavior.

Amanieu (Oct 22 2019 at 08:13, on Zulip):

No handler found, unwinding fails, std::terminate is called (in C++)

Kyle Strand (Oct 22 2019 at 08:13, on Zulip):

So, still not UB!

Amanieu (Oct 22 2019 at 08:13, on Zulip):

If you search for nounwind in LLVM's LangRef, it talks about UB.

Kyle Strand (Oct 22 2019 at 08:13, on Zulip):

Right, I'm excluding nounwind in my thought process here

Amanieu (Oct 22 2019 at 08:14, on Zulip):

When LLVM compiles C code it uses nounwind everywhere. So it would be UB for a Rust panic to unwind through that code.

Kyle Strand (Oct 22 2019 at 08:14, on Zulip):

It seems like exceptions are perhaps much less dangerous and scary than I had been led to believe, at the ABI/platform level

Kyle Strand (Oct 22 2019 at 08:14, on Zulip):

Ahhh.

Amanieu (Oct 22 2019 at 08:14, on Zulip):

(LLVM does generate unwind tables for C code though)

Amanieu (Oct 22 2019 at 08:22, on Zulip):

But then again, the only thing LLVM uses nounwind for is optimizing away "dead" landing pads. Which there aren't any in C anyways. So it will work in practice.

Kyle Strand (Oct 22 2019 at 08:25, on Zulip):

Hm, I can't find a reference for LLVM emitting nounwind on C code; does it still do so even with -fexceptions? (Surely not?)

Amanieu (Oct 22 2019 at 08:36, on Zulip):

Try it on godbolt

Amanieu (Oct 22 2019 at 08:36, on Zulip):

https://godbolt.org/z/9eRIQs

gnzlbg (Oct 22 2019 at 12:11, on Zulip):

@Amanieu LLVM nounwind does not mean "does not unwind"

gnzlbg (Oct 22 2019 at 12:11, on Zulip):

nounwind functions are allowed to unwind

gnzlbg (Oct 22 2019 at 12:13, on Zulip):

the more relevant questions are whether you want the unwind to run destructors or call abort

gnzlbg (Oct 22 2019 at 12:13, on Zulip):

notice that LLVM also has the nouwtable attribute as well

gnzlbg (Oct 22 2019 at 12:14, on Zulip):

also, when you talk about extern "C" you are not being specific if what you are proposing applies to function definitions, function declarations, or both

gnzlbg (Oct 22 2019 at 12:15, on Zulip):

Does extern "C" { #[no_unwind] fn foo(); } abort on unwinding under your proposal ? If so, how would that be implemented ?

gnzlbg (Oct 22 2019 at 12:19, on Zulip):

Finally, C++ noexcept is part of the C++ type system, but #[no_unwind] would not be.

gnzlbg (Oct 22 2019 at 12:21, on Zulip):

Would it be possible for users to somehow specify at a call site that calling a function does not unwind ?

Amanieu (Oct 22 2019 at 12:56, on Zulip):

Does extern "C" { #[no_unwind] fn foo(); } abort on unwinding under your proposal ? If so, how would that be implemented ?

It would be UB on unwind (same as extern noexcept in C++).

Amanieu (Oct 22 2019 at 12:57, on Zulip):

(In practice it still unwinds but destructors may have been optimized out by LLVM assuming that unwinding can't happen)

Amanieu (Oct 22 2019 at 13:18, on Zulip):

Also regarding LLVM nounwind:

If the function does raise an exception, its runtime behavior is undefined.

gnzlbg (Oct 22 2019 at 13:18, on Zulip):

Keep reading

Amanieu (Oct 22 2019 at 13:19, on Zulip):

However, functions marked nounwind may still trap or generate asynchronous exceptions.

But we're talking about normal exceptions here.

gnzlbg (Oct 22 2019 at 13:19, on Zulip):

Keep reading

gnzlbg (Oct 22 2019 at 13:20, on Zulip):

In particular the note about SEH

Amanieu (Oct 22 2019 at 13:20, on Zulip):

Eh sure, but even then that's debatable.

Amanieu (Oct 22 2019 at 13:20, on Zulip):

Keep in mind we do use SJLJ unwinding on at least one target (iOS ARM32)

gnzlbg (Oct 22 2019 at 13:20, on Zulip):

notice that longjmp on windows uses SEH, and it is correct to longjmp over noexcept functions

gnzlbg (Oct 22 2019 at 13:21, on Zulip):

which are nounwind

Amanieu (Oct 22 2019 at 13:24, on Zulip):

Sure, but the key point here is that LLVM will optimize away destructors with the assumption that they are unreachable.

Amanieu (Oct 22 2019 at 13:24, on Zulip):

I don't know how we can explain that except through UB.

gnzlbg (Oct 22 2019 at 13:24, on Zulip):

That's why I mentioned that the key thing about nounwind is whether destructors should or should not be run on unwinding

gnzlbg (Oct 22 2019 at 13:24, on Zulip):

Not running destructors is ok, e.g., if the frame does not contain any destructors to run

gnzlbg (Oct 22 2019 at 13:26, on Zulip):

When calling a function that's nounwind, if the function is actually unwound, destructors might or might not be run (it depends, its even possible that only some destructors would be run, but not all), but there is a lot of code for which the distinction does not matter.

Amanieu (Oct 22 2019 at 13:27, on Zulip):

Except that LLVM is clearly using the UB rule to allow itself to optimize away unreachable destructors. I don't think we should rely on LLVM's interpretation of UB remaining consistent.

gnzlbg (Oct 22 2019 at 13:27, on Zulip):

We do not need to guarantee that destructors are / aren't run consistently

gnzlbg (Oct 22 2019 at 13:28, on Zulip):

We just need to say that the behavior is unspecified, and that means that either all destructors run, no destructors run, or some destructors run, and that implementations do not need to write down nor guarantee what they do.

gnzlbg (Oct 22 2019 at 13:28, on Zulip):

In practice, if your frame doesn't contain destructors, or the destructors are not required for safety, and you are ok with some or all of them not running, then you are fine.

gnzlbg (Oct 22 2019 at 13:30, on Zulip):

(This is what we could say at the Rust side - how we lower this to LLVM would be a different matter, but on platforms such as windows, making them nounwind might be fine, because LLVM says that the behavior there is implementation-defined)

Amanieu (Oct 22 2019 at 13:33, on Zulip):

I disagree with your interpretation of LLVM's paragraph. I read it as: unwinding with an exception is UB, but unwinding with a trap or asynchronous exception is implementation-defined.

Amanieu (Oct 22 2019 at 13:34, on Zulip):

LLVM can't remove the destructors unless it can prove that the destructors are only reachable through UB.

gnzlbg (Oct 22 2019 at 13:40, on Zulip):

So are you arguing that SEH is synchronous ?

gnzlbg (Oct 22 2019 at 13:41, on Zulip):

Or how do you interpret the behavior of nounwind on windows, where SEH exceptions are asynchronous, and that's the mechanism that longjmp and C++ use ?

Amanieu (Oct 22 2019 at 13:41, on Zulip):

It can be either. If you raise an exception yourself by calling RaiseException then it is. If you trigger an exception through a trap then it isn't.

gnzlbg (Oct 22 2019 at 13:43, on Zulip):

RaiseException is what longjmp uses

Amanieu (Oct 22 2019 at 13:43, on Zulip):

Synchronous exception = something you explicitly throw
Trap = a faulting instruction
Asynchronous exception = an exceptions injected into your thread from another thread

Amanieu (Oct 22 2019 at 13:45, on Zulip):

Hmm longjmp is wierd.

gnzlbg (Oct 22 2019 at 13:46, on Zulip):

So under your interpretation, because longjmp on windows uses a synchronous exception, it would be UB for it to unwind out of a noexcept function (which is nounwind). However, AFAICT from LLVM and the MSVC docs, it isn't, and that's actually guaranteed to work

gnzlbg (Oct 22 2019 at 13:47, on Zulip):

MSVC mentions here:

If the code is compiled by using /EHs or /EHsc and the function that contains the longjmp call is noexcept then local objects in that function may not be destructed during the stack unwind.

Amanieu (Oct 22 2019 at 13:48, on Zulip):

I think we would need a confirmation from LLVM devs to guarantee that the only consequence of misusing nounwind is that destructors may not get called.

Amanieu (Oct 22 2019 at 13:48, on Zulip):

And yes, longjmp is considered a synchronous exception by my definition.

gnzlbg (Oct 22 2019 at 13:48, on Zulip):

The same happens in the Itanium-C++ ABI (for the Itanium target), where longjmp uses Unwind_ForceUnwind to unwind through frames, and it can actually unwind through noexcept functions

gnzlbg (Oct 22 2019 at 13:50, on Zulip):

Notice that while it is UB to do that if the frames contain destructors, it isn't if they don't, yet that means that one would need to be able to unwind with a Unwind_ForceUnwind through a nounwind function.

Amanieu (Oct 22 2019 at 13:50, on Zulip):

Yes, this is arguably a mistake in LLVM's definition of nounwind.

gnzlbg (Oct 22 2019 at 13:51, on Zulip):

I think what you propose for extern "C" is fine, but we should focus on what semantics make sense for Rust first, and worry what LLVM-IR we generate about it "on the side"

gnzlbg (Oct 22 2019 at 13:51, on Zulip):

In particular for #[no_unwind]

gnzlbg (Oct 22 2019 at 13:52, on Zulip):

It would be fine to say that unwinding from a #[no_unwind] function is UB in Rust

gnzlbg (Oct 22 2019 at 13:52, on Zulip):

and if someone uses longjmp, then they just can't use #[no_unwind]

Amanieu (Oct 22 2019 at 13:53, on Zulip):

I'm happy with that.

gnzlbg (Oct 22 2019 at 13:54, on Zulip):

I'm happy to I think.

Amanieu (Oct 22 2019 at 13:54, on Zulip):

By the way I've just done a little searching and it seems that pthread_exit (which uses unwinding in glibc) has poor interactions with noexcept.

Amanieu (Oct 22 2019 at 13:54, on Zulip):

But I think this has been fixed now.

gnzlbg (Oct 22 2019 at 13:54, on Zulip):

The issue of leaving the Rust unwinding ABI unspecified could be then just solved by enforcing the translation at the extern "C" ABI layer

gnzlbg (Oct 22 2019 at 13:55, on Zulip):

By the way I've just done a little searching and it seems that pthread_exit (which uses unwinding in glibc) has poor interactions with noexcept.

I think that this is simpler to workaround in Rust, since these exceptions are foreign, and we can just not catch them ever, not even by the abort-on-panic shims at the #[no_unwind] extern "C" boundary

gnzlbg (Oct 22 2019 at 13:56, on Zulip):

That might mean that when such a function unwinds due to a pthread_exit, not all destructors might be run though :/

gnzlbg (Oct 22 2019 at 13:56, on Zulip):

So then we are back to whether there are some situations for which a #[no_unwind] function can unwind

gnzlbg (Oct 22 2019 at 13:58, on Zulip):

That's a bad idea, if someone calls pthread_exit, then no function in the stack can be #[no_unwind], I'd be fine with that

Amanieu (Oct 22 2019 at 14:08, on Zulip):
#include <pthread.h>

void* thread(void*) noexcept {
  pthread_exit(NULL);
}

int main() {
  pthread_t t;
  pthread_create(&t, NULL, thread, NULL);
  pthread_join(t, NULL);
}
Amanieu (Oct 22 2019 at 14:09, on Zulip):

This program crashes with:

terminate called without an active exception
[1]    601165 abort (core dumped)  ./a.out
gnzlbg (Oct 22 2019 at 14:56, on Zulip):

Should it crash?

gnzlbg (Oct 22 2019 at 14:56, on Zulip):

(What if instead of phtread_exit, a cancellation point + pthread_cancel are used ?)

Amanieu (Oct 22 2019 at 15:22, on Zulip):

If you remove the noexcept it works fine. What happens is that noexcept catches any exceptions from within the function and calls std::terminate.

Amanieu (Oct 22 2019 at 15:22, on Zulip):

A cancellation point does the same thing as pthread_exit, they're both forced unwinds.

Amanieu (Oct 22 2019 at 15:31, on Zulip):

By the way all of this only applies to glibc, musl doesn't use unwinding for cancellation.

gnzlbg (Oct 22 2019 at 15:41, on Zulip):

I understand why the call to std::terminate happens, what I'm asking if that is _by design_.

gnzlbg (Oct 22 2019 at 15:41, on Zulip):

For example, we can in Rust make #[no_unwind] functions abort when longjmp is used on Windows

gnzlbg (Oct 22 2019 at 15:41, on Zulip):

Or when pthread_exit and similar are used on Linux

gnzlbg (Oct 22 2019 at 15:42, on Zulip):

but we can also just unwind those functions just like msvc does for longjmp+noexcept in C++ code

gnzlbg (Oct 22 2019 at 15:43, on Zulip):

In fact, we actually do that for extern "C" functions that are nounwind on safe Rust.

gnzlbg (Oct 22 2019 at 15:44, on Zulip):

If the unwind is due to a longjmp on windows, instead of aborting on abort-on-panic-shims, we let the unwind continue

gnzlbg (Oct 22 2019 at 15:44, on Zulip):

and some libraries like rlua rely on this working

gnzlbg (Oct 22 2019 at 15:45, on Zulip):

on Linux, we could do the same, e.g., if the unwind is due to a force unwind, not abort on abort-on-panic-shims

gnzlbg (Oct 22 2019 at 15:54, on Zulip):

@Amanieu an alternative is making #[no_unwind] functions always abort on unwinding including for force unwinds, asynchronous exceptions, etc. and maybe say that longjmps over these are UB.

Kyle Strand (Oct 23 2019 at 21:45, on Zulip):

@centril do you have some time to skim this thread and let us know if you have any immediate objections to the proposal of letting unwinding through extern "C" happen, and considering it implementation-defined, pending LLVM confirmation that the behavior on their end is well defined?

centril (Oct 23 2019 at 21:56, on Zulip):

I do have objections.

- It's debatable whether allowing extern "C" code to unwind is a breaking change or not (there's an argument that you cannot do negative reasoning about undefined behavior but to some extent I think such reasoning is legitimate).
- Existing code is written with an assumption that extern "C" emits nounwind for code size and other benefits.
- Making unwind-allowed the default makes it all the more likely that we freeze the unwinding implementation for Rust.
- "Implementation defined" entails that e.g. for the x86_64-unknown-linux-gnutarget, the compiler team is free to define the behavior. Given that we currently have one backend and one compiler I think that makes it in practice not implementation defined but defined for the whole language. I also think we should be free to change things for important tier-1 targets.
- Given the back & forth with respect to what LLVMs actual semantics are, I would want to see whatever guarantees we want from LLVM written directly into LangRef.
- no_unwind is an attribute; this does not seem to cover function pointers.

centril (Oct 23 2019 at 21:57, on Zulip):

Also... we have a soundness hole to fix... (sooner rather than later)

Amanieu (Oct 23 2019 at 22:09, on Zulip):

I don't think the "one backend and one compiler" issue really applies here: unwinding is pretty well standardized with SEH on Windows and libunwind/dwarf for all other platforms.

Kyle Strand (Oct 23 2019 at 22:18, on Zulip):

Re: soundness, can we re-open RalfJ's PR to stop emitting nounwind?

Kyle Strand (Oct 23 2019 at 22:18, on Zulip):

I am not sure I understand how this could be considered a breaking change. Does rustc actually make any promises with respect to optimization regressions? That would be surprising to me.

centril (Oct 23 2019 at 22:25, on Zulip):

Re: soundness, can we re-open RalfJ's PR to stop emitting nounwind?

I'm not going to say more than I've already said about this other than to note that @gnzlbg has a PR which fixes the soundness hole without changing the implementation UB to not UB.

centril (Oct 23 2019 at 22:27, on Zulip):

Does rustc actually make any promises with respect to optimization regressions? That would be surprising to me.

The reasoning here is that a set of behaviors are spec/implementation-UB and people could write code based on this assumption that such code will not occur (negative reasoning) for soundness.

centril (Oct 23 2019 at 22:27, on Zulip):

You could argue that such negative reasoning is dubious

Kyle Strand (Oct 23 2019 at 23:47, on Zulip):

I understand how allowing extern "C" to unwind now and then prohibiting that in the future would be a breaking change, but that's not being proposed; essentially, the proposal here is to treat extern "C" the way we were planning to treat extern "C unwind".

What I don't understand is your first bullet point, saying that permitting the unwind _now_ would be a breaking change. I can't think of any way that code could rely on the current behavior (not aborting but emitting nounwind) in a way that would be broken by no longer emitting nounwind. Am I misunderstanding that bullet point?

Kyle Strand (Oct 23 2019 at 23:49, on Zulip):

Also, re: freezing the implementation, if we were to proceed with this plan, it would include requiring a translation layer at an extern "C" boundary if the default Rust panic mechanism ever changes, just like extern "C unwind" would have.

Amanieu (Oct 24 2019 at 07:32, on Zulip):

I think that what @centril is saying is that code may currently assume that FFI doesn't unwind and rely on this invariant for safety. If we change this to allow unwinding then such code may become unsound.

gnzlbg (Oct 24 2019 at 07:33, on Zulip):

That's a good argument, but I think its incorrect

gnzlbg (Oct 24 2019 at 07:33, on Zulip):

Currently, unwinding through extern "C" is UB

gnzlbg (Oct 24 2019 at 07:33, on Zulip):

This does not mean that code can be written to assume it will never happen

gnzlbg (Oct 24 2019 at 07:34, on Zulip):

UB is "we make no guarantees" and that includes "we do not gurantee that this will be UB forever"

gnzlbg (Oct 24 2019 at 07:34, on Zulip):

So defining that "UB" to say "unwinding is ok" is allowed

gnzlbg (Oct 24 2019 at 07:34, on Zulip):

Code that assumes otherwise is already broken

gnzlbg (Oct 24 2019 at 07:42, on Zulip):

I don't think the "one backend and one compiler" issue really applies here: unwinding is pretty well standardized with SEH on Windows and libunwind/dwarf for all other platforms.

GCC 3 to GCC 4 changed the linux unwinding ABI from SJLJ to DWARF

gnzlbg (Oct 24 2019 at 07:42, on Zulip):

That wasn't that long ago

gnzlbg (Oct 24 2019 at 07:44, on Zulip):

I'd rather not commit to a solution that assumes that something like this will never happen again

Josh Triplett (Oct 25 2019 at 04:04, on Zulip):

I just read through this whole thread, and the idea of allowing unwinding by default seems reasonable to me.

Josh Triplett (Oct 25 2019 at 04:05, on Zulip):

Thank you for the detailed analysis, @Amanieu .

Kyle Strand (Oct 25 2019 at 09:48, on Zulip):

For what it's worth, I think opening the RFC with extern "C unwind" (and possibly even landing it) would not necessarily prevent us from making extern "C" match the proposed "C unwind" behavior. That's the main reason why I think we should proceed with "C unwind" for now.

Last update: Nov 15 2019 at 10:15UTC