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

Add llvm.sideeffect to potential infinite loops and recursions #59546

Merged
merged 3 commits into from
Oct 10, 2019

Conversation

sfanxiang
Copy link
Contributor

@sfanxiang sfanxiang commented Mar 30, 2019

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

@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 @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.

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Mar 30, 2019
Copy link
Member

@nagisa nagisa left a 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
Copy link
Member

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.

Copy link
Member

Choose a reason for hiding this comment

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

See #34947

Copy link
Contributor Author

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.

Copy link
Member

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?

Copy link
Contributor Author

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)
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@@ -1,3 +1,5 @@
// ignore-test LLVM can't prove that these loops terminate.
Copy link
Member

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...

Copy link
Member

@nagisa nagisa Mar 30, 2019

Choose a reason for hiding this comment

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

See #45222

src/test/run-pass/non-terminate/infinite-loop.rs Outdated Show resolved Hide resolved
src/test/run-pass/non-terminate/infinite-recursion.rs Outdated Show resolved Hide resolved
@nagisa
Copy link
Member

nagisa commented Mar 30, 2019

@bors try

Lets do a perf run.

@bors
Copy link
Contributor

bors commented Mar 30, 2019

⌛ Trying commit b3848cec68718b9a6382ca35816e2f36ce0394b6 with merge 2d35e031eb67c3fa339df66bc3e24e844faa09e5...

@nagisa nagisa added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). labels Mar 30, 2019
@bors
Copy link
Contributor

bors commented Mar 30, 2019

☀️ Try build successful - checks-travis
Build commit: 2d35e031eb67c3fa339df66bc3e24e844faa09e5

@nagisa
Copy link
Member

nagisa commented Mar 30, 2019

@rust-timer build 2d35e031eb67c3fa339df66bc3e24e844faa09e5

@rust-timer
Copy link
Collaborator

Success: Queued 2d35e031eb67c3fa339df66bc3e24e844faa09e5 with parent 709b72e, comparison URL.

@rust-timer
Copy link
Collaborator

Finished benchmarking try commit 2d35e031eb67c3fa339df66bc3e24e844faa09e5

@sfanxiang sfanxiang force-pushed the interminable-ub branch 4 times, most recently from 39e58a6 to 0ea0e06 Compare March 31, 2019 16:06
@sfanxiang
Copy link
Contributor Author

@nagisa Looking closer at perf, some benchmarks (e.g. keccak) spend quite some time in is_predecessor_of. So I replaced it with a simpler comparison of block index, assuming mir always generates in sequential order. If the assumption is false, the codegen would still be correct but the generated code would be less performant.

@nagisa
Copy link
Member

nagisa commented Mar 31, 2019

Alas, the blocks are not required to be seuential by the time codegen happens.

If the assumption is false, the codegen would still be correct but the generated code would be less performant.

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?

@sfanxiang
Copy link
Contributor Author

sfanxiang commented Apr 1, 2019

@nagisa
By sequential I mean, as long as there isn't a loop, the blocks should always execute in strictly increasing index (bb1 -> bb2, etc.) order. And we generate @llvm.sideeffect when we see equal or decreasing target index (e.g. bb2 -> bb1).

Let's suppose @llvm.sideeffect is not generated, which means all branches go to a strictly increasing index. Because the index is strictly increasing it's impossible to go back to a visited block, therefore can't form a loop. In other words, if @llvm.sideeffect isn't generated, there's no loop, regardless of the sequential assumption.

Now what if the assumption is false? That means the index goes back where there isn't a loop. In that case, an extra @llvm.sideeffect will be generated even when it's not needed, which hurts performance but not correctness. If the assumption is false, and if there's no loop, @llvm.sideeffect may still be generated.

And if the assumption is true, @llvm.sideeffect will be generated when and only when loop exists. These are only for blocks though. Functions always get a sideeffect.

Alas, the blocks are not required to be seuential by the time codegen happens.

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?

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?

It will be generated in bb10 before branching to bb1.

@nagisa
Copy link
Member

nagisa commented Apr 1, 2019

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

@bors
Copy link
Contributor

bors commented Apr 1, 2019

⌛ Trying commit 0ea0e06 with merge ef94533...

bors added a commit that referenced this pull request Apr 1, 2019
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
@bors
Copy link
Contributor

bors commented Apr 1, 2019

☀️ Try build successful - checks-travis
Build commit: ef94533

@oli-obk

This comment has been minimized.

@oli-obk

This comment has been minimized.

@rust-timer

This comment has been minimized.

@oli-obk
Copy link
Contributor

oli-obk commented Apr 1, 2019

@rust-timer build ef94533

@nagisa
Copy link
Member

nagisa commented Sep 27, 2019

@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.

@nikomatsakis
Copy link
Contributor

nikomatsakis commented Sep 27, 2019

Discussed in today's design meeting (login-free link). We opted not to schedule this but instead:

  • to merge the PR with a -Z flag
  • and to get some measurements from real-world applications

@sfanxiang would you be up for modifying this PR to make it so that the llvm.sideeffect instructions are gated on a -Z flag?

@sfanxiang
Copy link
Contributor Author

@nikomatsakis

would you be up for modifying this PR to make it so that the llvm.sideeffect instructions are gated on a -Z flag?

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
@rust-highfive
Copy link
Collaborator

The job mingw-check of your PR failed (pretty log, raw log). Through arcane magic we have determined that the following fragments from the build log may contain information about the problem.

Click to expand the log.
2019-09-27T20:52:08.8246863Z ##[command]git remote add origin https://github.com/rust-lang/rust
2019-09-27T20:52:08.8500384Z ##[command]git config gc.auto 0
2019-09-27T20:52:08.8575068Z ##[command]git config --get-all http.https://github.com/rust-lang/rust.extraheader
2019-09-27T20:52:08.8633381Z ##[command]git config --get-all http.proxy
2019-09-27T20:52:08.8793219Z ##[command]git -c http.extraheader="AUTHORIZATION: basic ***" fetch --force --tags --prune --progress --no-recurse-submodules --depth=2 origin +refs/heads/*:refs/remotes/origin/* +refs/pull/59546/merge:refs/remotes/pull/59546/merge
---
2019-09-27T21:02:16.6746549Z     Checking rustc_plugin_impl v0.0.0 (/checkout/src/librustc_plugin)
2019-09-27T21:02:16.9564058Z     Checking rustc_resolve v0.0.0 (/checkout/src/librustc_resolve)
2019-09-27T21:02:19.7579127Z     Checking rustc_privacy v0.0.0 (/checkout/src/librustc_privacy)
2019-09-27T21:02:20.3598241Z     Checking rustc_codegen_ssa v0.0.0 (/checkout/src/librustc_codegen_ssa)
2019-09-27T21:02:20.4020509Z error: expected one of `)`, `,`, `.`, `::`, `?`, or an operator, found `{`
2019-09-27T21:02:20.4020930Z    --> src/librustc_codegen_ssa/mir/block.rs:160:65
2019-09-27T21:02:20.4021435Z     |
2019-09-27T21:02:20.4021828Z 160 |         if (bx.tcx().sess.opts.debugging_opts.insert_sideeffect {
2019-09-27T21:02:20.4022132Z     |            -                                                   -^
2019-09-27T21:02:20.4022459Z     |            |                                                   |
2019-09-27T21:02:20.4022774Z     |            unclosed delimiter                                  help: `)` may belong here
2019-09-27T21:02:20.4048653Z error: expected expression, found `)`
2019-09-27T21:02:20.4048987Z    --> src/librustc_codegen_ssa/mir/block.rs:170:5
2019-09-27T21:02:20.4049349Z     |
2019-09-27T21:02:20.4049748Z 170 |     }
2019-09-27T21:02:20.4049748Z 170 |     }
2019-09-27T21:02:20.4050005Z     |     ^ expected expression
2019-09-27T21:02:20.4050413Z 
2019-09-27T21:02:20.4087363Z error: expected one of `)`, `,`, or `|`, found `target`
2019-09-27T21:02:20.4087682Z    --> src/librustc_codegen_ssa/mir/block.rs:824:24
2019-09-27T21:02:20.4087902Z     |
2019-09-27T21:02:20.4088229Z 824 |         if let Some((_ target)) = destination.as_ref() {
2019-09-27T21:02:20.4088566Z     |                        ^^^^^^ expected one of `)`, `,`, or `|` here
2019-09-27T21:02:20.7108048Z error: unnecessary parentheses around `if` condition
2019-09-27T21:02:20.7108445Z    --> src/librustc_codegen_ssa/mir/block.rs:160:12
2019-09-27T21:02:20.7108672Z     |
2019-09-27T21:02:20.7108672Z     |
2019-09-27T21:02:20.7109386Z 160 |         if (bx.tcx().sess.opts.debugging_opts.insert_sideeffect {
2019-09-27T21:02:20.7109715Z     |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove these parentheses
2019-09-27T21:02:20.7110268Z     = note: `-D unused-parens` implied by `-D warnings`
2019-09-27T21:02:20.7110304Z 
2019-09-27T21:02:21.8230339Z error[E0308]: mismatched types
2019-09-27T21:02:21.8231817Z    --> src/librustc_codegen_ssa/mir/block.rs:587:66
2019-09-27T21:02:21.8231817Z    --> src/librustc_codegen_ssa/mir/block.rs:587:66
2019-09-27T21:02:21.8233046Z     |
2019-09-27T21:02:21.8233847Z 587 |                     helper.maybe_sideeffect(self.mir, &mut bx, &[target]);
2019-09-27T21:02:21.8234533Z     |                                                                  ^^^^^^ expected struct `rustc::mir::BasicBlock`, found reference
2019-09-27T21:02:21.8236256Z     = note: expected type `rustc::mir::BasicBlock`
2019-09-27T21:02:21.8236791Z                found type `&rustc::mir::BasicBlock`
2019-09-27T21:02:21.8236972Z 
2019-09-27T21:02:21.8561286Z error[E0308]: mismatched types
2019-09-27T21:02:21.8561286Z error[E0308]: mismatched types
2019-09-27T21:02:21.8562440Z    --> src/librustc_codegen_ssa/mir/block.rs:825:58
2019-09-27T21:02:21.8562845Z     |
2019-09-27T21:02:21.8563494Z 825 |             helper.maybe_sideeffect(self.mir, &mut bx, &[target]);
2019-09-27T21:02:21.8564047Z     |                                                          ^^^^^^ expected struct `rustc::mir::BasicBlock`, found reference
2019-09-27T21:02:21.8564839Z     = note: expected type `rustc::mir::BasicBlock`
2019-09-27T21:02:21.8565260Z                found type `&rustc::mir::BasicBlock`
2019-09-27T21:02:21.8565436Z 
2019-09-27T21:02:23.2788207Z error: aborting due to 6 previous errors
---
2019-09-27T21:02:25.8124526Z == clock drift check ==
2019-09-27T21:02:25.8142333Z   local time: Fri Sep 27 21:02:25 UTC 2019
2019-09-27T21:02:25.9886761Z   network time: Fri, 27 Sep 2019 21:02:25 GMT
2019-09-27T21:02:25.9887439Z == end clock drift check ==
2019-09-27T21:02:27.2198010Z ##[error]Bash exited with code '1'.
2019-09-27T21:02:27.2257883Z ##[section]Starting: Checkout
2019-09-27T21:02:27.2259943Z ==============================================================================
2019-09-27T21:02:27.2259999Z Task         : Get sources
2019-09-27T21:02:27.2260048Z Description  : Get sources from a repository. Supports Git, TfsVC, and SVN repositories.

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 @TimNN. (Feature Requests)

@sfanxiang
Copy link
Contributor Author

@nikomatsakis: This is now guarded under -Z insert-sideeffect. Feel free to suggest a better name.

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:

  • no missing llvm.sideeffect (especially during panic/unwind)
  • no unnecessary llvm.sideeffect

Thank you!

@bjorn3
Copy link
Member

bjorn3 commented Sep 28, 2019

I implemented @nikic's suggestion to insert sideeffect on function entry

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?

@nagisa
Copy link
Member

nagisa commented Sep 28, 2019

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.

@sfanxiang
Copy link
Contributor Author

@bjorn3

there would be no more sideeffect on call, right?

Not on call, but on loop. IMHO there shouldn't be UB on Rust side even in theory with this model.

@nagisa
Copy link
Member

nagisa commented Oct 10, 2019

@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!

@bors
Copy link
Contributor

bors commented Oct 10, 2019

📌 Commit e9acfa3 has been approved by nagisa

@bors bors added the S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. label Oct 10, 2019
@bors
Copy link
Contributor

bors commented Oct 10, 2019

💡 This pull request was already approved, no need to approve it again.

@bors
Copy link
Contributor

bors commented Oct 10, 2019

📌 Commit e9acfa3 has been approved by nagisa

@bors
Copy link
Contributor

bors commented Oct 10, 2019

⌛ Testing commit e9acfa3 with merge 58b5491...

bors added a commit that referenced this pull request Oct 10, 2019
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
@bors
Copy link
Contributor

bors commented Oct 10, 2019

☀️ Test successful - checks-azure
Approved by: nagisa
Pushing 58b5491 to master...

@crlf0710
Copy link
Member

@rustbot modify labels to -S-inactive

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
E-mentor Call for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion. merged-by-bors This PR was explicitly merged by bors. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.