-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Add llvm.sideeffect to potential infinite loops and recursions #59546
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @michaelwoerister (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. Due to 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 the contribution instructions for more information. |
1d1e574
to
b3848ce
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR!
The outcome here is about exactly what I had expected when to me it occurred that llvm.sideeffect
is not a great workaround. I do not see any particularly strong issues with the implementation itself, so r=me on that.
The question is whether we are fine with regressing (at times seriously) the code quality for just about everything else in exchange for solving our most notable longstanding codegen issue, @rust-lang/compiler?
@@ -6,8 +6,8 @@ | |||
#[no_mangle] | |||
pub fn issue_34947(x: i32) -> i32 { | |||
// CHECK: mul | |||
// CHECK-NEXT: mul | |||
// CHECK-NEXT: mul | |||
// CHECK-NEXT: ret |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a regression test to ensure that pow(<constant>)
would unroll the loop properly. This change regresses that and the test adjusted to ignore its original intent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #34947
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think pow(<constant>)
regresses in this case. The codegen simply inserts a bunch of @llvm.sideeffect
in between mul
so we can't do CHECK-NEXT
. The resulting code should be no different.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we then CHECK-NOT
for branches between the multiply instructions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure.
@@ -14,6 +14,6 @@ pub fn helper(_: usize) { | |||
// CHECK-LABEL: @repeat_take_collect | |||
#[no_mangle] | |||
pub fn repeat_take_collect() -> Vec<u8> { | |||
// CHECK: call void @llvm.memset.p0i8.[[USIZE]](i8* {{(nonnull )?}}align 1 %{{[0-9]+}}, i8 42, [[USIZE]] 100000, i1 false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this regressing and now the buffer size given to the intrinsic is not constant anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not exactly. IDK why but we get a single store
and then memset
with size = 99999 here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair, can the test check for this specific pattern please? With {{.*}}
it is possible to regress to, say, 100000 memsets all with 1 byte size without noticing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/test/codegen/issue-45222.rs
Outdated
@@ -1,3 +1,5 @@ | |||
// ignore-test LLVM can't prove that these loops terminate. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a regression as well, right? Not great that even trivial examples like these regress...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #45222
@bors try Lets do a perf run. |
⌛ Trying commit b3848cec68718b9a6382ca35816e2f36ce0394b6 with merge 2d35e031eb67c3fa339df66bc3e24e844faa09e5... |
☀️ Try build successful - checks-travis |
@rust-timer build 2d35e031eb67c3fa339df66bc3e24e844faa09e5 |
Success: Queued 2d35e031eb67c3fa339df66bc3e24e844faa09e5 with parent 709b72e, comparison URL. |
Finished benchmarking try commit 2d35e031eb67c3fa339df66bc3e24e844faa09e5 |
39e58a6
to
0ea0e06
Compare
@nagisa Looking closer at perf, some benchmarks (e.g. keccak) spend quite some time in |
Alas, the blocks are not required to be seuential by the time codegen happens.
I’m not sure I see how: if we have start -> bb10 (loop head) -> bb1 (loop body) -> bb10 then the sideeffect would not get generated at all, would it? |
@nagisa Let's suppose Now what if the assumption is false? That means the index goes back where there isn't a loop. In that case, an extra And if the assumption is true,
I realize it's not required, but I couldn't find where rustc doesn't follow this assumption. Could you give me an example code where sequential code isn't generated sequentially when converted to mir?
It will be generated in bb10 before branching to bb1. |
I see. Well, I guess we do another perf run to see how it fares this time around and perhaps it would be good to collect some benchmarks as well, although I’m not sure of what. Then we can just wait for the decision from the team meeting. @bors try |
Add llvm.sideeffect to potential infinite loops and recursions LLVM assumes that a thread will eventually cause side effect. This is not true in Rust if a loop or recursion does nothing in its body, causing undefined behavior even in common cases like `loop {}`. Inserting llvm.sideeffect fixes the undefined behavior. As a micro-optimization, only insert llvm.sideeffect when jumping back in blocks or calling a function. A patch for LLVM is expected to allow empty non-terminate code by default and fix this issue from LLVM side. #28728
☀️ Try build successful - checks-travis |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
@rust-timer build ef94533 |
@Ekleog Don’t think so. Wouldn’t be great to have a work-around just for a temporary hack. @nikic should we report to LLVM with our perf findings? I’m not entirely sure what the status of the “forward progress guarantees” discussion is at this point, last I remember there was some confusion around the direction in one of the differentials and then complete silence since. |
Discussed in today's design meeting (login-free link). We opted not to schedule this but instead:
@sfanxiang would you be up for modifying this PR to make it so that the |
Of course! |
LLVM assumes that a thread will eventually cause side effect. This is not true in Rust if a loop or recursion does nothing in its body, causing undefined behavior even in common cases like `loop {}`. Inserting llvm.sideeffect fixes the undefined behavior. As a micro-optimization, only insert llvm.sideeffect when jumping back in blocks or calling a function. A patch for LLVM is expected to allow empty non-terminate code by default and fix this issue from LLVM side. rust-lang#28728
3516db1
to
7bcfab0
Compare
The job Click to expand the log.
I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact |
7bcfab0
to
e9acfa3
Compare
@nikomatsakis: This is now guarded under Reviewers: I implemented @nikic's suggestion to insert sideeffect on function entry (sorry @nikic for not paying attention earlier!). Please help make sure there is:
Thank you! |
C: void do_nothing() {} Rust: extern "C" {
fn do_nothing();
}
loop {
unsafe { do_nothing(); }
} would be UB when performing cross language lto because of there would be no more sideeffect on call, right? |
In theory, yes, in practice… not really. The only way to currently observe LLVM exploiting the forward-progress guarantees is by calling a function that contains an infinite loop w/o fwd progress. |
Not on call, but on |
@bors r+ There are a couple of improvements that we might want to do in follow-ups, such as adding an intrinsic for people to insert this into their code unconditionally, at least while this whole thing is behind a flag. Nothing that would block this PR from landing, though. Lets experiment! |
📌 Commit e9acfa3 has been approved by |
💡 This pull request was already approved, no need to approve it again.
|
📌 Commit e9acfa3 has been approved by |
Add llvm.sideeffect to potential infinite loops and recursions LLVM assumes that a thread will eventually cause side effect. This is not true in Rust if a loop or recursion does nothing in its body, causing undefined behavior even in common cases like `loop {}`. Inserting llvm.sideeffect fixes the undefined behavior. As a micro-optimization, only insert llvm.sideeffect when jumping back in blocks or calling a function. A patch for LLVM is expected to allow empty non-terminate code by default and fix this issue from LLVM side. #28728 **UPDATE:** [Mentoring instructions here](#59546 (comment)) to unstall this PR
☀️ Test successful - checks-azure |
@rustbot modify labels to -S-inactive |
LLVM assumes that a thread will eventually cause side effect. This is
not true in Rust if a loop or recursion does nothing in its body,
causing undefined behavior even in common cases like
loop {}
.Inserting llvm.sideeffect fixes the undefined behavior.
As a micro-optimization, only insert llvm.sideeffect when jumping back
in blocks or calling a function.
A patch for LLVM is expected to allow empty non-terminate code by
default and fix this issue from LLVM side.
#28728
UPDATE: Mentoring instructions here to unstall this PR