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

Mark extern functions as nounwind + other unwinding-based improvements #21186

Closed
wants to merge 7 commits into from

Conversation

Aatch
Copy link
Contributor

@Aatch Aatch commented Jan 15, 2015

This PR does a few things:

  • Marks functions with a non-Rust ABI as nounwind.
  • Catches unwinding at FFI boundary in extern fn foo() { ... } functions and aborts. This prevents us from unwinding from Rust code into C code.
  • Adds a #[can_unwind] attribute for extern functions. This is for when we know a non-Rust ABI function will have to unwind. Used mainly in the unwinding implementation.
  • Adds a #[nounwind] attribute. It is intended for when a function that won't unwind is used cross-crate, but using #[inline] is not appropriate. It is currently used for the oom function in liballoc

Fixes #18510
Fixes #18512

r? @eddyb

@rust-highfive
Copy link
Collaborator

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @eddyb (or someone else) soon.

If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. The way Github handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes.

Please see CONTRIBUTING.md for more information.

@alexcrichton
Copy link
Member

These seem like they might be potentially dangerous attributes to be playing around with, so could they be behind a feature gate for now? We may also want to scrutinize the naming of these attributes in the future before allowing full use of them.

Also, can you be sure to add some positive and negative tests for the cases added here? Out of curiosity, do you have statistics about code size in before/after cases as well?


let pad_ret_val = builder.landing_pad(pad_ty, llpersonality, 1us);
builder.add_clause(pad_ret_val, C_null(Type::i8p(ccx)));
let trap = ccx.get_intrinsic(&("llvm.trap"));
Copy link
Member

Choose a reason for hiding this comment

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

It may be a little unfortunate to call llvm.trap here as unwinding across an extern fn (while not safe) may be a common newbie mistake to make. This unfortunately doesn't really aid in debugging as well as it basically just crashes the process.

We may want to explore a new lang item here which has the chance to print something, or perhaps a different strategy.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, though there are several other places do that do similarly-helpful things, so a more general-purpose solution might be more appropriate.

Also, a trap will still be caught by a debugger and allow for a backtrace to be printed, so it's at least better than whatever happens when you do unwind into C.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah I was thinking that this could take the same strategy as divide-by-zero or out-of-bounds only hardwired to an abort function instead of a panic function. I didn't know about the debugger though, that's pretty slick!

@alexcrichton
Copy link
Member

Thanks for this! Seems like our FFI is about to get even better!

@Aatch
Copy link
Contributor Author

Aatch commented Jan 15, 2015

Hmm, looks like we were already assuming that extern couldn't unwind and therefore the cleanups weren't be added for them.

@Aatch
Copy link
Contributor Author

Aatch commented Jan 16, 2015

Updated and added a test, should be good to go now.

@huonw
Copy link
Member

huonw commented Jan 16, 2015

It seems mildly inconsistent to call them #[can_unwind] and #[nounwind]? Also, it seems that the latter should, strictly speaking, be #[unsafe_no_unwind]?

@alexcrichton
Copy link
Member

Could you add a test as well for the feature gates? (make sure they're actually gating)

@@ -653,6 +653,8 @@ impl LintPass for UnusedAttributes {
"no_debug",
"omit_gdb_pretty_printer_section",
"unsafe_no_drop_flag",
"can_unwind",
"nounwind",
Copy link
Member

Choose a reason for hiding this comment

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

s/nounwind/unsafe_no_unwind

@alexcrichton
Copy link
Member

This looks pretty great to me, nice work @Aatch! I'm basically happy with the implementation, but I would like to canvas for some more opinions specifically about:

  • What do others think about abort-by-default when unwinding past an extern fn?
  • What do others think about abort only using llvm.trap (no message printing) in that case?
  • What do others think about an opt-in #[can_unwind] vs an opt-out?

@Aatch
Copy link
Contributor Author

Aatch commented Jan 16, 2015

Just to put my answers in (biased as they may be):

  • The majority of extern fns are intended for use with a non-Rust program, normally C, that will not understand unwinding. There is no sane way for a program to unwind across those boundaries.
  • I would like a way to print a better message, but as I mentioned before, we already need a general-purpose way to just print out a message in these cases. Notably OOM just aborts without a message too.
  • See the first point, it's going to be pretty rare that you want unwinding across these boundaries. Note that I only realised we needed it because I broke the unwinding code. To put this another way, the only time we expect there to be unwinding out of an extern fn is when that function is part of the unwinding machinery itself.

@alexcrichton
Copy link
Member

To be clear, I totally agree with your answers to questions 1/3, just wanna make sure we're all on the same page :)

For point 2, I do think it's ok to land now, I would just much rather have a nicer message. I've spent hours tracking core dumps before only to realize it was OOM and it have been super duper helpful if a message was actually printed!

@alexcrichton
Copy link
Member

Ok, we chatted about this today in the weekly meeting and here's an update. First off, thanks again for the PR! We're all pretty excited to take advantage of the optimizations LLVM provides. For now, however, we're thinking we may want step back and make an RFC for this PR before landing.

The primary change we're worried about is the difference in unwinding semantics when unwinding past an extern fn and we think that there may be some design space to explore before committing to one strategy. We had some fleeting ideas such as using a similar strategy for unwinding in destructors perhaps. Regardless, we're a little unsure that these are the semantics we'd want to commit to, so we'd just want to canvas some more feedback before moving forward in this respect.

I'd be more than willing to help out in writing up, proofing, or otherwise just reviewing an RFC for this area. Feel free to ping me on IRC about this whenever!

To move forward this this specific PR I think we can take a slightly altered strategy as well. In the past we've accepted implementation details which improved the efficiency of standard library primitives (e.g. NonZero) before we were ready to commit to a public API (it's entirely an implementation detail). Along these lines I think that at bare minimum the attributes this PR adds can be accepted pretty quickly (due to being behind a feature gate). I think we may also be able to take advantage of flagging FFI declarations (extern { ... } blocks) as nounwind for LLVM itself, due to unwinding being undefined behavior in that scenario today.

Does that make sense for moving forward? Again, thanks for the work in this area, it's definitely much appreciated!

Functions that use an ABI other than the Rust one are marked as
nounwind.

Rust functions that use non-Rust ABI are also marked nounwind. To allow
for this, the unwinding is caught and we abort the process.

Added a `#[can_unwind]` attribute that allows functions using a non-Rust
ABI to unwind without aborting. This is required for the unwinding code
in libstd to function.
Adds a nounwind attribute that allows functions to be marked as not
unwinding. Most useful for cross-crate usage when the nounwind LLVM
attribute cannot be inferred.

Also fixes marking extern fns as nounwind when the function decl comes
from another crate.
We were not allowing for the possibility that we might unwind out of a
foreign abi function. Now that we allow for some of these functions to
unwind, generate the proper code so clean-ups are performed.

This also allowed me to write a test for the functionality, which I have
now done.
@steveklabnik
Copy link
Member

What's the status of this PR? It's quite old at this point.

@alexcrichton
Copy link
Member

@steveklabnik this is blocked on rust-lang/rfcs#638

@alexcrichton
Copy link
Member

I'm going to close this in favor of the associated RFC while we work that out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

mark C ABI functions as nounwind prevent unwinding past FFI boundaries in code generation
7 participants