Skip to content
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

panic runtime and C-unwind documentation #1226

Open
wants to merge 63 commits into
base: master
Choose a base branch
from

Conversation

BatmanAoD
Copy link
Member

@BatmanAoD BatmanAoD commented May 29, 2022

Tracking issue: rust-lang/rust#74990

@BatmanAoD BatmanAoD marked this pull request as ready for review May 30, 2022 17:35
@BatmanAoD
Copy link
Member Author

BatmanAoD commented May 30, 2022

Hm... not sure how to fix the links to the newly-introduced page. Is there an index page I need to edit?

Edit: I think I found it

@ehuss ehuss added the S-waiting-on-stabilization Waiting for a stabilization PR to be merged in the main Rust repository label Jun 22, 2022
src/items/functions.md Outdated Show resolved Hide resolved
src/items/functions.md Outdated Show resolved Hide resolved
src/behavior-considered-undefined.md Outdated Show resolved Hide resolved
Comment on lines 219 to 224
| panic runtime | ABI | `panic`-unwind | Unforced foreign unwind |
| -------------- | ------------ | ------------------------------------- | ----------------------- |
| `panic=unwind` | `"C-unwind"` | unwind | unwind |
| `panic=unwind` | `"C"` | abort | UB |
| `panic=abort` | `"C-unwind"` | `panic!` aborts | abort |
| `panic=abort` | `"C"` | `panic!` aborts (no unwinding occurs) | UB |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| panic runtime | ABI | `panic`-unwind | Unforced foreign unwind |
| -------------- | ------------ | ------------------------------------- | ----------------------- |
| `panic=unwind` | `"C-unwind"` | unwind | unwind |
| `panic=unwind` | `"C"` | abort | UB |
| `panic=abort` | `"C-unwind"` | `panic!` aborts | abort |
| `panic=abort` | `"C"` | `panic!` aborts (no unwinding occurs) | UB |
| panic runtime | ABI | `panic`-unwind | Unforced foreign unwind |
| -------------- | ------------ | ------------------------------------- | ----------------------- |
| `panic=unwind` | `"C-unwind"` | unwind | unwind |
| `panic=unwind` | `"C"` | abort if unwinding reaches the function | UB if unwinding reaches the function |
| `panic=abort` | `"C-unwind"` | aborts immediately (no unwinding occurs) | abort if unwinding reaches the function |
| `panic=abort` | `"C"` | aborts immediately (no unwinding occurs) | UB if unwinding reaches the function |

I found this a bit confusing. I believe there are subtle differences in terms of where the aborts occur and so forth. I have tried to clarify above, but I think it may be worth further clarifying.

It may also be worth adding some (perhaps non-normative) discussion of implementation:

  • When compiling a function F with panic=unwind and extern "C", the compiler inserts unwinding guards for Rust panics that trigger an abort when unwinding reaches F.

I am also be misunderstanding what's going on. I was a bit surprised to see "UB" for unforced-foreign-unwind with C=unwind. I guess that this table is combining two scenarios:

  • what happens when you call a C++ function declared as extern "C", and it unwinds (UB, we haven't compiled any guards)
  • what happens when an extern "C" Rust function invokes some C++ function that throws (probably, in practice, an abort, but perhaps we have simplified to call it UB?)

Is that right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's only UB for a foreign function declared as extern "C" to unwind.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nbdd0121 what happens when an extern "C" Rust function unwinds? I believe we insert an abort guard, but this table doesn't clarify that, right? Or maybe I don't understand what it's trying to convey. I'm imagining a scenario like

extern "C-unwind" fn throws();

extern "C" fn rust_fn() {
    throws(); // unwinds
}

In this case, I presume you get an abort -- and I think we guarantee that? But the way I read this table, it would be listed as UB.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm....I don't know if the panic abort guard would currently catch and abort in that case, or if it relies on the personality function to only abort on true Rust panics. I agree that the behavior in the table as-written is UB.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nbdd0121 what happens when an extern "C" Rust function unwinds? I believe we insert an abort guard, but this table doesn't clarify that, right? Or maybe I don't understand what it's trying to convey. I'm imagining a scenario like

extern "C-unwind" fn throws();

extern "C" fn rust_fn() {
    throws(); // unwinds
}

In this case, I presume you get an abort -- and I think we guarantee that? But the way I read this table, it would be listed as UB.

Unwinding out from extern "C" functions (defined in either Rust or foreign language) is UB.
In the case you listed, we insert guard to prevent unwinding from actually leaving a Rust extern "C" functions, therefore the function does not unwind, so UB is prevented; in this case we never unwinds out from a extern "C" Rust functions.

If you define a extern "C-unwind" Rust function and transmute it to extern "C" and then call it, it's not UB if unwinding does not happen, and it's UB if unwinding happens.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nikomatsakis With the change to the verbiage above, explaining that the table entries are specifically describing behavior at function boundaries, do you still want to make a change here?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check whether the notes I suggested to add under the table are correct.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nikomatsakis Can I resolve this comment thread now?

src/linkage.md Outdated Show resolved Hide resolved
src/items/functions.md Outdated Show resolved Hide resolved
src/items/functions.md Outdated Show resolved Hide resolved
src/items/functions.md Outdated Show resolved Hide resolved
src/items/functions.md Outdated Show resolved Hide resolved
@BatmanAoD
Copy link
Member Author

Sorry for the delay; I think I've addressed all comments.

@BatmanAoD
Copy link
Member Author

@tmandry @nikomatsakis I'm not sure you saw my comments & changes last week, but I think this is ready for re-review.

@nbdd0121
Copy link
Contributor

Could you squash the commits?

@BatmanAoD
Copy link
Member Author

@nbdd0121 Can that be done on merge? I've heard that GitHub sometimes has trouble with PR branches that receive force-pushes.

src/linkage.md Outdated Show resolved Hide resolved
src/items/functions.md Outdated Show resolved Hide resolved
src/panic.md Outdated Show resolved Hide resolved
src/panic.md Outdated Show resolved Hide resolved
src/panic.md Outdated Show resolved Hide resolved
src/panic.md Outdated Show resolved Hide resolved
src/panic.md Outdated Show resolved Hide resolved
src/panic.md Outdated Show resolved Hide resolved
src/panic.md Outdated Show resolved Hide resolved
src/items/functions.md Outdated Show resolved Hide resolved
@BatmanAoD
Copy link
Member Author

I think I've resolved all open questions and concerns. Is there anything else needed from me at the moment?

@nbdd0121
Copy link
Contributor

This really needs rebasing now.

@BatmanAoD
Copy link
Member Author

@nbdd0121 Done!

@BatmanAoD
Copy link
Member Author

@tmandry two changes since your review:

  • I added the new ABIs to items/external-blocks
  • I added that catching an exception or unwind from the "wrong" language is UB

@BatmanAoD
Copy link
Member Author

@tmandry @ehuss can this be merged, since the partial stabilization was a while ago now?

src/items/external-blocks.md Outdated Show resolved Hide resolved
src/items/functions.md Outdated Show resolved Hide resolved
src/items/functions.md Outdated Show resolved Hide resolved
src/runtime.md Outdated Show resolved Hide resolved
@traviscross traviscross removed the S-waiting-on-review Status: The marked PR is awaiting review from the PR author. label Aug 30, 2024
src/items/functions.md Outdated Show resolved Hide resolved
@traviscross traviscross added the S-waiting-on-review Status: The marked PR is awaiting review from the PR author. label Aug 30, 2024
@traviscross
Copy link
Contributor

This looks reasonable to me, though I'm also relying on the strength of the many earlier reviews.

Last call for comments. Otherwise we'll get this merged in the coming days.

No crate may be linked with [the `panic=abort` runtime][panic-runtime] if it has
both of the following characteristics:

* It contains a call to an `-unwind` foreign function or function pointer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This includes "Rust" foreign function calls, right? Like a separate copy of the Rust runtime, built with the same compiler and hence ABI compatible.

In the other file you carefully define unwinding and non-unwinding ABIs; can that be reused here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm not sure what you're asking with regard to runtimes. This is talking about the ABI strings C-unwind, system-unwind, etc; yes, the code being invoked may be Rust code.

What verbiage are you suggesting be reused? I don't think listing all the ABI strings seems necessary, but I can add a link to the functions.md section explaining unwinding vs non-unwinding ABIs, with the caveat that the Rust ABI itself doesn't prevent linking crates with different panic strategies.

Copy link
Member

@RalfJung RalfJung Sep 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm saying that this needs to also include extern "Rust" fn foreign function calls, but the current wording does not seem to cover those functions. I'm not sure what is the best way to fix that, but functions.md introduces the concept of an ""unwinding ABI" and that seems to also be the right concept here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, Rust calls are specifically excluded here; fn is entirely equivalent to extern "Rust" fn. So including extern "Rust" here would effectively mean "it's never legal to link panic=abort crates against panic=unwind crates," which is clearly not true.

... possibly I am still misunderstanding the question?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. What makes "Rust" different from the other unwinding APIs?

IIRC the argument was that if we link in the abort runtime, Rust itself will never trigger unwinding, so if your program has no other "*-unwind" functions anywhere (meaning there's no foreign-language unwinding) then there can be no unwinding in general?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh, I thought I remembered how and why this rule was fashioned, but now that I'm thinking about it, I don't think I understand the problem it was intended to solve.

Part of what the "call to...foreign function" verbiage seems to be trying to capture is that the function call is actually linked to code from a language other than Rust, e.g. C++; because indeed, if that's the case, all bets are off about whether an unwind will occur or not. But I think that would apply equally to an opaque binary shared-object that happens to have been compiled from Rust code and linked against the panic=unwind runtime (so that in the final program, both panic=unwind and panic=abort runtimes will be used).

If you compile with panic=unwind and you use extern "C" (or other non-unwinding calls) everywhere that you call into the other language runtime (be that C++, Rust, or something else), then you're safe: the panic=unwind crate has shims at each extern "C" boundary that will catch a panic and abort before it "escapes" into any stack frames compiled with panic=abort. But the extern "C-unwind" boundaries will have no such shims.

So I guess maybe extern "Rust" isn't an exception after all, if it's actually used to call into an opaque shared object that might unwind; but is that even possible? Can you use an extern "Rust" call as an entrypoint into a "foreign" binary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually that may simply not be allowed? (Though that should probably be explicit.)

https://youtu.be/3pYHCGYJbw0?si=GbpPYif_OqI1UJrn

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we allow this when both sides were built with the exact same Rust compiler. But maybe we should then also demand that the same -Cpanic was used.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pure-Rust case is okay even if one object was built with unwind=abort and the other with unwind=panic, as long as the only panic runtime linked is the abort runtime. This should be ensured even without Cargo just by duplicate-symbol linker errors. (And I'm pretty sure that compiling for panic=abort and then using the panic=unwind runtime is impossible with rustc, and if it were somehow managed, panicking would certainly be UB.)

So I think this additional restriction really does only matter in the case as described, where there's only one Rust panic runtime, but a different language, such as C++, may introduce panics; in this case, you're safe only if all the Rust stack frames that a C++ exception might touch are compiled with exception support. The phrasing in the PR ensures this because extern "C" will safely abort if a C++ exception comes through, if that frame was compiled with panic=unwind.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be ensured even without Cargo just by duplicate-symbol linker errors.

How that?

And I'm pretty sure that compiling for panic=abort and then using the panic=unwind runtime is impossible with rustc

Why is it impossible? What happens if I build one crate (type lib) with -Cpanic=abort, and then include it in a bin crate with -Cpanic=unind? Do we have a test for that.

src/items/functions.md Outdated Show resolved Hide resolved
src/panic.md Show resolved Hide resolved
src/panic.md Show resolved Hide resolved
src/destructors.md Outdated Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: The marked PR is awaiting review from the PR author.
Projects
None yet
Development

Successfully merging this pull request may close these issues.