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

book: use abort() over loop {} for panic #38138

Merged
merged 1 commit into from Jan 10, 2017

Conversation

hanna-kruppe
Copy link
Contributor

Due to #28728 loop {} is very risky and can lead to fun debugging experiences such as #38136. Besides, aborting is probably better behavior than an infinite loop.

r? @steveklabnik

@steveklabnik
Copy link
Member

I would like to hear what @rust-lang/libs thinks about this, and possibly other teams. loop {} is usually the idiom here.

@oli-obk
Copy link
Contributor

oli-obk commented Dec 4, 2016

How is loop{} risky? The mentioned loop optimizations only trigger for loops thst have a break/return in them anywhere. Your given example sounds like it is an issue with inline(never) not actually preventing inlining (since moving it to a different crate works, I assume until you use lto)

@hanna-kruppe
Copy link
Contributor Author

hanna-kruppe commented Dec 4, 2016

loop {} is usually the idiom here.

Maybe for producing a ! value in general, but in these cases we specifically have a panic, and it makes much more sense to me to emulate the panic=abort behavior than to get stuck on panics.

The mentioned loop optimizations only trigger for loops thst have a break/return in them anywhere.

I don't know where you get that from. All the examples in that thread involve a loop {} or while true {}, as that is the kind of loop that LLVM optimizes out. I'm sure you could also trigger it on loops like loop if false { break; } or similar ones, as long as they're provably infinite and side-effect-free. It's not a case of LLVM getting confused by redundant control flow instructions or something, it's a case of the C++ standard making infinite loops with no side effects UB and LLVM running with that.

Your given example sounds like it is an issue with inline(never) not actually preventing inlining (since moving it to a different crate works, I assume until you use lto)

I have multiple problem with this perspective:

  • Regardless of how you want to slice it, the code was broken with loop {} and wasn't broken without it.
  • Putting blame on inline(never) or LTO only begins to make sense if we accept that loop {} is broken and search for workarounds. I'd rather just avoid the need for mitigation to begin with.
  • inline(never) isn't really at fault here, it only means that the code shall not be duplicated into callers, it doesn't mean that inter-procedural optimizations and analyses can't look at the function contents.

@oli-obk
Copy link
Contributor

oli-obk commented Dec 4, 2016

In all my experiments, while true {} was broken and loop {} was not. That's also how I interpret the llvm issue. This must be a new issue I don't know about.

Quoting the bug report:

To be completely clear, my understanding of our current policy is this: As a gentleman's agreement, LLVM will not "break" the kinds of obvious infinite loops written by programmers, in C, for the purposes of testing, causing threads to not terminate, etc. We might, however, assume termination otherwise. We don't , however, have a formal policy one way or the other.

And the relevant bug for pure infinite loops https://llvm.org/bugs/show_bug.cgi?id=965 is still open, thus I assume it should not be ub

You are right of course, inline is not the culprit, but it's still a bug that moving the function into another compilation unit changes behaviour.

@hanna-kruppe
Copy link
Contributor Author

hanna-kruppe commented Dec 4, 2016

while true {} and loop {} generate the exact same LLVM IR even in debug mode (modulo debug info an unnecessary alloca outside the loop) on current stable: https://play.rust-lang.org/?gist=c7b70df19ed198055e813dc7f080a2ce&version=stable&backtrace=0
Even if rustc's codegen was dumber, the IR would be the same after some minor clean-up optimization passes.

Your experiments might have been too simplistic (e.g., no surrounding code that could be optimized further by replacing an infinite loop with unreachable) to trigger problems. But if you have an example at hand which is compiled correctly with loop {} and miscompiled with while true {}, it would make an interesting addition to the discussion over in #28728

You are right of course, inline is not the culprit, but it's still a bug that moving the function into another compilation unit changes behaviour.

I don't think that deserves to be called a separate bug. We wouldn't even be asking the question if not for the principal bug (infinite loop == UB). But this is mostly a semantic quibble. In any case, it's not worth a separate issue because the problem disappears if the infinite loop == UB issue is fixed, and the general principle that splitting code into several crates can influence optimizations is well known and unavoidable.

@oli-obk
Copy link
Contributor

oli-obk commented Dec 4, 2016

Your playground link does not use while true

@hanna-kruppe
Copy link
Contributor Author

Ooops, sorry. Fixed.

@oli-obk
Copy link
Contributor

oli-obk commented Dec 4, 2016

See #37088 for an example that used to do ub with while true and no ub with loop.

Edit: i guess the mir optimizations changed that.

@hanna-kruppe
Copy link
Contributor Author

There are multiple programs in #37088, all of them UB if I read the description right, some using loop and others using while true. Can you please put up playgrounds of the one program you mean, once with loop and once with while true?

@oli-obk
Copy link
Contributor

oli-obk commented Dec 4, 2016

I'm on mobile right now and can't reproduce it on the stable playground, so something has changed. I'll try with an older compiler once I'm back on a pc

@steveklabnik
Copy link
Member

Maybe for producing a ! value in general, but in these cases we specifically have a panic, and it makes much more sense to me to emulate the panic=abort behavior than to get stuck on panics.

Almost every implementation and bit of docs I've ever seen on this use loop initially. As I said, maybe my impression here is wrong. I'm also not saying we shouldn't do this, exactly, but that I want to make sure it's actually the right call.

@hanna-kruppe
Copy link
Contributor Author

@steveklabnik

Almost every implementation and bit of docs I've ever seen on this use loop initially.

What exactly do you mean by "this" here? no_std panic implementations? Functions that return !?

@steveklabnik
Copy link
Member

Documentation and examples around no_std panic implementations.

@alexcrichton
Copy link
Member

Ideally these intrinsics are forgotten details of the compiler as no std transitions to panic=abort. Unfortunately though doc tests are hard to write as such so we're forced to do something here. Can we just hide these functions and recommend panic=abort?

I don't really like working around this llvm bug but a bug is a bug and we should strive to document working code here.

@hanna-kruppe
Copy link
Contributor Author

hanna-kruppe commented Dec 6, 2016

@alexcrichton I tried to build a no_std executable with panic=abort in place of these intrinsics and couldn't get it to work. Is that option supported today? If so, that indeed sounds like what we should be recommending, even if we can't doctest it (we could still have a run-pass test).

@steveklabnik
Copy link
Member

Yeah, you still have to implement at least rust_begin_panic even with panic=abort.

@alexcrichton
Copy link
Member

@rkruppe to get it to truly work you'll need to recompile libcore/libstd as well because all libraries need to be compiled with -Cpanic=abort to avoid the need for the intrinsics.

@hanna-kruppe
Copy link
Contributor Author

Oh right, of course. Didn't notice that because I'm actually cross-compiling to a custom target so I'm building core as a normal dependency anyway (i.e., I have core = { path="..." } in Cargo.toml). I'll need to experiment a bit more with real #![no_std]` setup then, and then think about how to if and how to put that in the book.

@hanna-kruppe
Copy link
Contributor Author

Alternatively, shouldn't one in principle be able to link to libpanic_unwind in a no-std context? Everything blew up when I tried that on a whim, but it seems like something that ought to work (but again that was in a cross-compiling #![no_core] context), and should remove the need for even rust_begin_panic.

@alexcrichton
Copy link
Member

In theory, yes, linking panic_unwind manually should work, but there's likely some weirdness which would cause problems.

@steveklabnik
Copy link
Member

So: what to do with this patch?

@hanna-kruppe
Copy link
Contributor Author

First of all, I tried panic=abort + linking panic_abort and it still missed a lang item (panic_fmt), as @alexcrichton feared. There are also further issues with recommending panic=abort right now:

As far as I can tell, panic=abort is not explained anywhere in the book. I couldn't even find and detailed explanation of unwinding or panics in general. This makes it problematic to just write 'use panic=abort'.
Additionally, recommending one thing but doc-testing another thing has problems: the panic=abort route could regress without us noticing, and people who click the playground button will be confused.

In conclusion I'm inclined to argue this patch as-is (or at least a close variation, e.g. using libc abort instead of the intrinsic) represents the best we're gonna get for a while, i.e. until someone writes a chapter on panics and extends rustdoc to allow passing -C options to tests.

Copy link
Member

@steveklabnik steveklabnik left a comment

Choose a reason for hiding this comment

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

Sorry for the delay here, @rkruppe . r=me after this nit.

#![no_std]
use core::intrinsics::abort;
Copy link
Member

Choose a reason for hiding this comment

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

could this be use core::intrinsics...

@@ -58,7 +55,7 @@ fn main(argc: isize, argv: *const *const u8) -> isize {
}

#[lang = "eh_personality"] extern fn rust_eh_personality() {}
#[lang = "panic_fmt"] extern fn rust_begin_panic() -> ! { loop {} }
#[lang = "panic_fmt"] extern fn rust_begin_panic() -> ! { unsafe { abort() } }
Copy link
Member

Choose a reason for hiding this comment

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

and here, intrinsics::abort? This is the standard style for importing functions.

Due to rust-lang#28728 loop {} is very risky and can lead to fun debugging experiences like in rust-lang#38136. Besides, aborting is probably better behavior than an infinite loop.
@hanna-kruppe
Copy link
Contributor Author

hanna-kruppe commented Jan 4, 2017

Addressed nit.

@steveklabnik
Copy link
Member

@bors: r+ rollup

thanks!

@bors
Copy link
Contributor

bors commented Jan 4, 2017

📌 Commit 893f42a has been approved by steveklabnik

steveklabnik added a commit to steveklabnik/rust that referenced this pull request Jan 4, 2017
book: use abort() over loop {} for panic

Due to rust-lang#28728 `loop {}` is very risky and can lead to fun debugging experiences such as rust-lang#38136. Besides, aborting is probably better behavior than an infinite loop.

r? @steveklabnik
@bors
Copy link
Contributor

bors commented Jan 9, 2017

⌛ Testing commit 893f42a with merge ec9ae8c...

@bors
Copy link
Contributor

bors commented Jan 9, 2017

💔 Test failed - status-travis

@alexcrichton
Copy link
Member

alexcrichton commented Jan 9, 2017 via email

@bors
Copy link
Contributor

bors commented Jan 9, 2017

⌛ Testing commit 893f42a with merge aa3ae13...

@bors
Copy link
Contributor

bors commented Jan 9, 2017

💔 Test failed - status-appveyor

@alexcrichton
Copy link
Member

alexcrichton commented Jan 9, 2017 via email

@bors
Copy link
Contributor

bors commented Jan 10, 2017

⌛ Testing commit 893f42a with merge 78c892d...

bors added a commit that referenced this pull request Jan 10, 2017
book: use abort() over loop {} for panic

Due to #28728 `loop {}` is very risky and can lead to fun debugging experiences such as #38136. Besides, aborting is probably better behavior than an infinite loop.

r? @steveklabnik
@bors
Copy link
Contributor

bors commented Jan 10, 2017

☀️ Test successful - status-appveyor, status-travis
Approved by: steveklabnik
Pushing 78c892d to master...

@bors bors merged commit 893f42a into rust-lang:master Jan 10, 2017
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.

None yet

5 participants