Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upLLVM loop optimization can make safe programs crash #28728
Comments
steveklabnik
added
the
A-LLVM
label
Sep 29, 2015
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ranma42
Sep 29, 2015
Contributor
The LLVM IR of the optimised code is
; Function Attrs: noreturn nounwind readnone uwtable
define internal void @_ZN4main20h5ec738167109b800UaaE() unnamed_addr #0 {
entry-block:
unreachable
}This kind of optimisation breaks the main assumption that should normally hold on uninhabited types: it should be impossible to have a value of that type.
rust-lang/rfcs#1216 proposes to explicitly handle such types in Rust. It might be effective in ensuring that LLVM never has to handle them and in injecting the appropriate code to ensure divergence when needed (IIUIC this could be achieved with appropriate attributes or intrinsic calls).
This topic has also been recently discussed in the LLVM mailing list: http://lists.llvm.org/pipermail/llvm-dev/2015-July/088095.html
|
The LLVM IR of the optimised code is ; Function Attrs: noreturn nounwind readnone uwtable
define internal void @_ZN4main20h5ec738167109b800UaaE() unnamed_addr #0 {
entry-block:
unreachable
}This kind of optimisation breaks the main assumption that should normally hold on uninhabited types: it should be impossible to have a value of that type. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
alexcrichton
Sep 29, 2015
Member
triage: I-nominated
Seems bad! If LLVM doesn't have a way to say "yes, this loop really is infinite" though then we may just have to sit-and-wait for the upstream discussion to settle.
|
triage: I-nominated Seems bad! If LLVM doesn't have a way to say "yes, this loop really is infinite" though then we may just have to sit-and-wait for the upstream discussion to settle. |
rust-highfive
added
the
I-nominated
label
Sep 29, 2015
alexcrichton
added
the
T-compiler
label
Sep 29, 2015
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ranma42
Sep 29, 2015
Contributor
A way to prevent infinite loops from being optimised away is to add unsafe {asm!("" :::: "volatile")} inside of them. This is similar to the llvm.noop.sideeffect intrinsic that has been proposed in the LLVM mailing list, but it might prevent some optimisations.
In order to avoid the performance loss and to still guarantee that diverging functions/loops are not optimised away, I believe that it should be sufficient to insert an empty non-optimisable loop (i.e. loop { unsafe { asm!("" :::: "volatile") } }) if uninhabited values are in scope.
If LLVM optimises the code which should diverge to the point that it does not diverge anymore, such loops will ensure that the control flow is still unable to proceed.
In "lucky" case in which LLVM is unable to optimise the diverging code, such loop will be removed by DCE.
|
A way to prevent infinite loops from being optimised away is to add |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
geofft
Sep 29, 2015
Contributor
Is this related to #18785? That one's about infinite recursion to be UB, but it sounds like the fundamental cause might be similar: LLVM doesn't consider not halting to be a side effect, so if a function has no side effects other than not halting, it's happy to optimize it away.
|
Is this related to #18785? That one's about infinite recursion to be UB, but it sounds like the fundamental cause might be similar: LLVM doesn't consider not halting to be a side effect, so if a function has no side effects other than not halting, it's happy to optimize it away. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
It's the same issue. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
RalfJung
Sep 29, 2015
Member
Yes, looks like it's the same. Further down that issue, they show how to get undef, from which I assume it's not hard to make a (seemingly safe) program crash.
|
Yes, looks like it's the same. Further down that issue, they show how to get |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ranma42
Sep 29, 2015
Contributor
Crash, or, possibly even worse heartbleed https://play.rust-lang.org/?gist=15a325a795244192bdce&version=stable
|
Crash, or, possibly even worse heartbleed https://play.rust-lang.org/?gist=15a325a795244192bdce&version=stable |
bluss
added
the
I-wrong
label
Sep 29, 2015
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
nikomatsakis
Oct 1, 2015
Contributor
So I've been wondering how long until somebody reports this. :) In my opinion, the best solution would of course be if we could tell LLVM not to be so aggressive about potentially infinite loops. Otherwise, the only thing I think we can do is to do a conservative analysis in Rust itself that determines whether:
- the loop will terminate OR
- the loop will have side-effects (I/O operations etc, I forget precisely how this is defined in C)
Either of this should be enough to avoid undefined behavior.
|
So I've been wondering how long until somebody reports this. :) In my opinion, the best solution would of course be if we could tell LLVM not to be so aggressive about potentially infinite loops. Otherwise, the only thing I think we can do is to do a conservative analysis in Rust itself that determines whether:
Either of this should be enough to avoid undefined behavior. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
nikomatsakis
Oct 1, 2015
Contributor
triage: P-medium
We'd like to see what LLVM will do before we invest a lot of effort on our side, and this seems relatively unlikely to cause problems in practice (though I have personally hit this while developing the compiler as well). There are no backwards incomatibility issues to be concerned about.
|
triage: P-medium We'd like to see what LLVM will do before we invest a lot of effort on our side, and this seems relatively unlikely to cause problems in practice (though I have personally hit this while developing the compiler as well). There are no backwards incomatibility issues to be concerned about. |
rust-highfive
added
P-medium
and removed
I-nominated
labels
Oct 1, 2015
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
dotdash
Oct 1, 2015
Contributor
Quoting from the LLVM mailing list discussion:
The implementation may assume that any thread will eventually do one of the following:
- terminate
- make a call to a library I/O function
- access or modify a volatile object, or
- perform a synchronization operation or an atomic operation
[Note: This is intended to allow compiler transformations such as removal of empty loops, even
when termination cannot be proven. — end note ]
|
Quoting from the LLVM mailing list discussion:
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ranma42
Oct 2, 2015
Contributor
@dotdash The excerpt you are quoting comes from the C++ specification; it is basically the answer to "how it [having side effects] is defined in C" (also confirmed by the standard committee: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1528.htm ).
Regarding what is the expected behaviour of the LLVM IR there is some confusion. https://llvm.org/bugs/show_bug.cgi?id=24078 shows that there seems to be no accurate & explicit specification of the semantics of infinite loops in LLVM IR. It aligns with the semantics of C++, most likely for historical reasons and for convenience (I only managed to track down https://groups.google.com/forum/#!topic/llvm-dev/j2vlIECKkdE which apparently refers to a time when infinite loops were not optimised away, some time before the C/C++ specs were updated to allow it).
From the thread it is clear that there is the desire to optimise C++ code as effectively as possible (i.e. also taking into account the opportunity to remove infinite loops), but in the same thread several developers (including some that actively contribute to LLVM) have shown interest in the ability to preserve infinite loops, as they are needed for other languages.
|
@dotdash The excerpt you are quoting comes from the C++ specification; it is basically the answer to "how it [having side effects] is defined in C" (also confirmed by the standard committee: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1528.htm ). Regarding what is the expected behaviour of the LLVM IR there is some confusion. https://llvm.org/bugs/show_bug.cgi?id=24078 shows that there seems to be no accurate & explicit specification of the semantics of infinite loops in LLVM IR. It aligns with the semantics of C++, most likely for historical reasons and for convenience (I only managed to track down https://groups.google.com/forum/#!topic/llvm-dev/j2vlIECKkdE which apparently refers to a time when infinite loops were not optimised away, some time before the C/C++ specs were updated to allow it). From the thread it is clear that there is the desire to optimise C++ code as effectively as possible (i.e. also taking into account the opportunity to remove infinite loops), but in the same thread several developers (including some that actively contribute to LLVM) have shown interest in the ability to preserve infinite loops, as they are needed for other languages. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
dotdash
Oct 2, 2015
Contributor
@ranma42 I'm aware of that, I just quoted that for reference, because one possibility to work-around this would be to detect such loops in rust and add one of the above to it to stop LLVM from performing this optimization.
|
@ranma42 I'm aware of that, I just quoted that for reference, because one possibility to work-around this would be to detect such loops in rust and add one of the above to it to stop LLVM from performing this optimization. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
Is this a soundness issue? If so, we should tag it as such. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
bluss
Nov 30, 2015
Contributor
Yes, following @ranma42's example, this way shows how it readily defeats array bounds checks. playground link
|
Yes, following @ranma42's example, this way shows how it readily defeats array bounds checks. playground link |
bluss
added
I-unsound 💥
and removed
I-wrong
labels
Nov 30, 2015
arielb1
added
I-wrong
and removed
I-unsound 💥
labels
Dec 2, 2015
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
arielb1
Dec 2, 2015
Contributor
The policy is that wrong-code issues that are also soundness issues (i.e. most of them) should be tagged I-wrong.
|
The policy is that wrong-code issues that are also soundness issues (i.e. most of them) should be tagged |
brson
added
I-unsound 💥
E-hard
I-needs-decision
E-medium
and removed
E-hard
labels
Aug 4, 2016
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
nikomatsakis
Aug 4, 2016
Contributor
So just to recap prior discussion, there are really two choices here that I can see:
- Wait for LLVM to provide a solution.
- Introduce no-op asm statements wherever there may be an infinite loop or infinite recursion (#18785).
The latter is kind of bad because it can inhibit optimization, so we'd want to do it somewhat sparingly -- basically wherever we can't prove termination ourselves. You could also imaging tying it a bit more to how LLVM optimizes -- i.e., introducing only if we can detect a scenario that LLVM might consider to be an infinite loop/recursion -- but that would (a) require tracking LLVM and (b) require deeper knowledge than I, at least, possess.
|
So just to recap prior discussion, there are really two choices here that I can see:
The latter is kind of bad because it can inhibit optimization, so we'd want to do it somewhat sparingly -- basically wherever we can't prove termination ourselves. You could also imaging tying it a bit more to how LLVM optimizes -- i.e., introducing only if we can detect a scenario that LLVM might consider to be an infinite loop/recursion -- but that would (a) require tracking LLVM and (b) require deeper knowledge than I, at least, possess. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gnzlbg
Sep 1, 2016
Contributor
Wait for LLVM to provide a solution.
What is the LLVM bug tracking this issue?
What is the LLVM bug tracking this issue? |
jonas-schievink
referenced this issue
Oct 11, 2016
Closed
safe code can implement `unreachable` intrinsic #37088
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
oli-obk
Oct 11, 2016
Contributor
side-note: while true {} exhibits this behaviour. Maybe the lint should be upgraded to error-by-default and get a note stating that this currently can exhibit undefined behaviour?
|
side-note: |
nagisa
referenced this issue
Nov 13, 2016
Closed
panic in program that shouldn't panic when compiling at optimization level 1 or 2 #37747
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ubsan
Nov 30, 2016
Contributor
Also, note that this is invalid for C. LLVM making this argument means that there is a bug in clang.
void foo() { while (1) { } }
void create_null() {
foo();
int i = 0;
while (i < 100) { i += 1; }
}
__attribute__((noreturn))
void use_null() {
__builtin_unreachable();
}
int main() {
create_null();
use_null();
}This crashes with optimizations; this is invalid behavior under the C11 standard:
An iteration statement whose controlling expression is not a constant
expression, [note 156] that performs no input/output operations,
does not access volatile objects, and performs no synchronization or
atomic operations in its body, controlling expression, or (in the case of
a for statement) its expression-3, may be assumed by the
implementation to terminate. [note 157]
156: An omitted controlling expression is replaced by a nonzero constant,
which is a constant expression.
157: This is intended to allow compiler transformations such as
removal of empty loops even when termination cannot be proven.
Note the "whose controlling expression is not a constant expression" - while (1) { }, 1 is a constant expression, and thus may not be removed.
|
Also, note that this is invalid for C. LLVM making this argument means that there is a bug in clang. void foo() { while (1) { } }
void create_null() {
foo();
int i = 0;
while (i < 100) { i += 1; }
}
__attribute__((noreturn))
void use_null() {
__builtin_unreachable();
}
int main() {
create_null();
use_null();
}This crashes with optimizations; this is invalid behavior under the C11 standard:
Note the "whose controlling expression is not a constant expression" - |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
oli-obk
Nov 30, 2016
Contributor
Is the loop removal an optimization pass that we could simply remove?
|
Is the loop removal an optimization pass that we could simply remove? |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
zackw
May 15, 2017
Contributor
Repeating myself from #42009: this bug can, under some circumstances, cause the emission of an externally callable function containing no machine instructions at all. This should never happen. If LLVM deduces that a pub fn can never be called by correct code, it should emit at least a trap instruction as the body of that function.
|
Repeating myself from #42009: this bug can, under some circumstances, cause the emission of an externally callable function containing no machine instructions at all. This should never happen. If LLVM deduces that a |
nagisa
referenced this issue
May 16, 2017
Closed
program segfaults when compiled with opt-level>0 #41888
jonas-schievink
referenced this issue
Jul 2, 2017
Closed
Thread guard panics with release optimizations #43014
Mark-Simulacrum
added
C-bug
and removed
I-wrong
labels
Jul 24, 2017
bstrie
referenced this issue
Sep 17, 2017
Open
borrowed referent of a `&T` sometimes incorrectly allowed #38899
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
steveklabnik
Sep 21, 2017
Member
This was brought up in this blog post today: https://blog.rom1v.com/2017/09/gnirehtet-rewritten-in-rust/
With a simpler reproduction: https://play.rust-lang.org/?gist=e622f8a672fbc57ecc63eb4450d2fc0a&version=stable
|
This was brought up in this blog post today: https://blog.rom1v.com/2017/09/gnirehtet-rewritten-in-rust/ With a simpler reproduction: https://play.rust-lang.org/?gist=e622f8a672fbc57ecc63eb4450d2fc0a&version=stable |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
sunfishcode
Sep 22, 2017
Contributor
The LLVM bug for this is https://bugs.llvm.org/show_bug.cgi?id=965 (opened in 2006).
|
The LLVM bug for this is https://bugs.llvm.org/show_bug.cgi?id=965 (opened in 2006). |
alexcrichton
referenced this issue
Sep 23, 2017
Closed
Infinite recursion can be "optimized" into LLVM undef which produces a garbage value #18785
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
sunfishcode
Sep 27, 2017
Contributor
@zackw LLVM has a flag for that: TrapUnreachable. I haven't tested this, but it looks adding Options.TrapUnreachable = true; to LLVMRustCreateTargetMachine ought to address your concern. It's likely that this has a low enough cost that it could be done by default, though I haven't made any measurements.
@oli-obk It's unfortunately not just a loop-deletion pass. The problem arises from broad assumptions, for example: (a) branches have no side effects, (b) functions that contain no instructions with side effects have no side effects, and (c) calls to functions with no side effects can be moved or deleted.
|
@zackw LLVM has a flag for that: @oli-obk It's unfortunately not just a loop-deletion pass. The problem arises from broad assumptions, for example: (a) branches have no side effects, (b) functions that contain no instructions with side effects have no side effects, and (c) calls to functions with no side effects can be moved or deleted. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
Looks like there's a patch: https://reviews.llvm.org/D38336 |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
bstrie
Nov 2, 2017
Contributor
@sunfishcode , looks like your LLVM patch at https://reviews.llvm.org/D38336 was "accepted" on October 3, can you give an update on what that means regarding LLVM's release process? What's the next step beyond acceptance, and do you have an idea of what future LLVM release will contain this patch?
|
@sunfishcode , looks like your LLVM patch at https://reviews.llvm.org/D38336 was "accepted" on October 3, can you give an update on what that means regarding LLVM's release process? What's the next step beyond acceptance, and do you have an idea of what future LLVM release will contain this patch? |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
sunfishcode
Nov 2, 2017
Contributor
I talked with some people offline who suggested we have an llvmdev thread. The thread is here:
http://lists.llvm.org/pipermail/llvm-dev/2017-October/118558.html
It's now concluded, with the result being that I need to make additional changes. I think the changes will be good, though they'll take me a little more time to do.
|
I talked with some people offline who suggested we have an llvmdev thread. The thread is here: http://lists.llvm.org/pipermail/llvm-dev/2017-October/118558.html It's now concluded, with the result being that I need to make additional changes. I think the changes will be good, though they'll take me a little more time to do. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
Thanks for the update, and thanks so much for your efforts! |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
bstrie
Nov 9, 2017
Contributor
Note that https://reviews.llvm.org/rL317729 has landed in LLVM. This patch is planned to have a follow-up patch which makes infinite loops exhibit defined behavior by default, so AFAICT all we need to do is wait and eventually this will be resolved for us upstream.
|
Note that https://reviews.llvm.org/rL317729 has landed in LLVM. This patch is planned to have a follow-up patch which makes infinite loops exhibit defined behavior by default, so AFAICT all we need to do is wait and eventually this will be resolved for us upstream. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
sunfishcode
Nov 10, 2017
Contributor
@zackw I've now created #45920 to fix the problem of functions containing no code.
@bstrie Yes, the first step is landed, and I'm working on the second step of making LLVM give infinite loops defined behavior by default. It's a complex change, and I don't yet know how long it'll take to finish, but I'll post updates here.
|
@zackw I've now created #45920 to fix the problem of functions containing no code. @bstrie Yes, the first step is landed, and I'm working on the second step of making LLVM give infinite loops defined behavior by default. It's a complex change, and I don't yet know how long it'll take to finish, but I'll post updates here. |
pusherofbrooms
referenced this issue
Nov 12, 2017
Closed
Busy loop is optimized out, preventing blinking #3
added a commit
to pusherofbrooms/blink
that referenced
this issue
Nov 12, 2017
pusherofbrooms
referenced this issue
Nov 12, 2017
Merged
Prevent LLVM from optimizing away busy loop #4
added a commit
to avr-rust/blink
that referenced
this issue
Nov 13, 2017
added a commit
that referenced
this issue
Nov 15, 2017
added a commit
that referenced
this issue
Nov 16, 2017
rkruppe
referenced this issue
Nov 18, 2017
Closed
Bug: Spawn a thread with a empty loop, program will coredump when compile with optimizations #46076
rkruppe
referenced this issue
Jan 18, 2018
Closed
panics are eliminated if panic_fmt have an empty loop and extern "C" #47537
BusyJay
referenced this issue
Jan 30, 2018
Closed
spawn a thread running an empty loop causes coredump #47868
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
jsgf
Feb 8, 2018
Contributor
I couldn't repro this just now: https://play.rust-lang.org/?gist=529bd5ab326f7b627e559f64d514312f&version=stable
|
I couldn't repro this just now: https://play.rust-lang.org/?gist=529bd5ab326f7b627e559f64d514312f&version=stable |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
@jsgf Still repro. Have you selected Release mode? |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
@kennytm Woops, never mind. |
nikic
referenced this issue
May 24, 2018
Closed
Illegal instruction after calling a diverging recursive function #51021
rkruppe
referenced this issue
Jun 8, 2018
Open
Trivial safe AsRef<OsStr> implementation leads to stack overflow in Debug (and maybe memory leaking in Release) #51429
This was referenced Aug 3, 2018
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
japaric
Aug 7, 2018
Member
Note that https://reviews.llvm.org/rL317729 has landed in LLVM. This patch is planned to have a follow-up patch which makes infinite loops exhibit defined behavior by default, so AFAICT all we need to do is wait and eventually this will be resolved for us upstream.
It has been several months since this comment. Anyone knows if the follow-up patch happened or will still happen?
Alternatively, it seems that the llvm.sideeffect intrinsic exists in the LLVM version we are using: could we fix this ourselves by translating Rust infinite loops into LLVM loops that contain the sideeffect intrinsic?
It has been several months since this comment. Anyone knows if the follow-up patch happened or will still happen? Alternatively, it seems that the |
RalfJung commentedSep 29, 2015
The following snippet crashes when compiled in release mode on current stable, beta and nightly:
https://play.rust-lang.org/?gist=1f99432e4f2dccdf7d7e&version=stable
This is based on the following example of LLVM removing a loop that I was made aware of: https://github.com/simnalamburt/snippets/blob/master/rust/src/bin/infinite.rs.
What seems to happen is that since C allows LLVM to remove endless loops that have no side-effect, we end up executing a
matchthat has to arms.