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

Incorrectly unified locals at -Zmir-opt-level=2 #79191

Closed
tmiasko opened this issue Nov 19, 2020 · 7 comments
Closed

Incorrectly unified locals at -Zmir-opt-level=2 #79191

tmiasko opened this issue Nov 19, 2020 · 7 comments
Assignees
Labels
A-mir-opt Area: MIR optimizations A-mir-opt-nrvo Fixed by NRVO C-bug Category: This is a bug. I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness requires-nightly This issue requires a nightly compiler in some way. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@tmiasko
Copy link
Contributor

tmiasko commented Nov 19, 2020

fn main() {
    if f(false) != 1 {
        panic!();
    }
}

#[inline(always)]
fn f(use_y: bool) -> u8 {
    let (x, y) = (1, 2);
    if use_y { y } else { x }
}
$ rustc -Zmir-opt-level=1 a.rs && ./a
$ rustc -Zmir-opt-level=2 a.rs && ./a
thread 'main' panicked at 'explicit panic', a.rs:3:9
--- a.main.005-011.DestinationPropagation.before.mir
+++ a.main.005-011.DestinationPropagation.after.mir
@@ -1,70 +1,70 @@
-// MIR for `main` before DestinationPropagation
+// MIR for `main` after DestinationPropagation
 
 fn main() -> () {
     let mut _0: ();
     let mut _1: bool;
     let mut _2: u8;
     let mut _3: !;
     let _4: ();
     let mut _5: !;
     let mut _6: bool;
     scope 1 (inlined f) {
         debug use_y => _6;
         let _7: u8;
         let mut _8: bool;
         scope 2 {
             debug x => _2;
-            debug y => _7;
+            debug y => _2;
         }
     }
 
     bb0: {
         StorageLive(_1);
-        StorageLive(_2);
+        nop;
         StorageLive(_6);
         _6 = const false;
         _2 = const 1_u8;
-        StorageLive(_7);
-        _7 = const 2_u8;
+        nop;
+        _2 = const 2_u8;
         StorageLive(_8);
         _8 = const false;
         goto -> bb4;
     }
 
     bb1: {
-        StorageDead(_2);
+        nop;
         _0 = const ();
         StorageDead(_1);
         return;
     }
 
     bb2: {
-        StorageDead(_2);
+        nop;
         StorageLive(_4);
         StorageLive(_5);
         begin_panic::<&str>(const "explicit panic");
     }
 
     bb3: {
-        _2 = _7;
+        nop;
         goto -> bb4;
     }
 
     bb4: {
-        StorageDead(_7);
+        nop;
         StorageDead(_8);
         StorageDead(_6);
         _1 = Ne(_2, const 1_u8);
         nop;
         switchInt(move _2) -> [1_u8: bb1, otherwise: bb2];
     }
 }



<!-- TRIAGEBOT_START -->

<!-- TRIAGEBOT_ASSIGN_START -->

<!-- TRIAGEBOT_ASSIGN_DATA_START$${"user":"JakobDegen"}$$TRIAGEBOT_ASSIGN_DATA_END -->

<!-- TRIAGEBOT_ASSIGN_END -->
<!-- TRIAGEBOT_END -->
@tmiasko tmiasko added the C-bug Category: This is a bug. label Nov 19, 2020
@jonas-schievink jonas-schievink added A-mir-opt Area: MIR optimizations A-mir-opt-nrvo Fixed by NRVO I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness requires-nightly This issue requires a nightly compiler in some way. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Nov 19, 2020
@jonas-schievink
Copy link
Contributor

Hmm, seems like it doesn't see the conflict because the assignment is unreachable:

    // init: <unreachable>
    // live: <unreachable>
    bb3: {
        // init: <unreachable>
        // live: <unreachable>
        _2 = _7;                         // scope 2 at borken.rs:2:8: 2:16
        // init: <unreachable>
        // live: <unreachable>
        goto -> bb4;                     // scope 2 at borken.rs:2:8: 2:16
        // init: <unreachable>
        // live: <unreachable>
    }

Seems like we should consider assignments in dead blocks as propagation candidates. But does that fix the whole bug?

@tmiasko
Copy link
Contributor Author

tmiasko commented Nov 27, 2020

I think that the main issue is that the notion of conflicts is based on liveness. A local could be never live but still conflict with others merely because there are dead stores to it.

For example, in code below a will be unified with b (after running destination propagation for the second time after inlining):

#![allow(unused)]

#[inline(never)]
pub fn id<T>(x: T) -> T { x }

pub fn main() {
    assert_eq!(1, g(1));
}

#[inline(never)]
pub fn g(x: usize) -> usize {
    f(x)
}

#[inline(always)]
pub fn f(a: usize) -> usize {
    let mut b = 3;
    b = a;
    b = 4;
    id(a)
}

@jonas-schievink
Copy link
Contributor

Right. I think this briefly came up when implementing the pass, but I never wrote it down anywhere, nor could I produce an example.

Sounds like we need to run a dead store elimination pass before destination propagation, or change the analysis it uses to account for dead stores.

@JakobDegen
Copy link
Contributor

@rustbot claim

@SparrowLii
Copy link
Member

@JakobDegen I'm interested in this, too. Have you done some work? If not, I should be able to submit a PR soon

@JakobDegen
Copy link
Contributor

JakobDegen commented Apr 25, 2022

Yeah, I have a local branch up. Looking to open a PR today. The PR will be a draft though - the opt actually has significantly more problems than are reported in issues, and some of those problems being resolved is blocked on open questions about MIR semantics

bors added a commit to rust-lang-ci/rust that referenced this issue Nov 27, 2022
Fix Dest Prop

Closes rust-lang#82678, rust-lang#79191 .

This was not originally a total re-write of the pass but is has gradually turned into one. Notable changes:

 1. Significant improvements to documentation all around. The top of the file has been extended with a more precise argument for soundness. The code should be fairly readable, and I've done my best to add useful comments wherever possible. I would very much like for the bus factor to not be one on this code.
 3. Improved handling of conflicts that are not visible in normal dataflow.  This was the cause of rust-lang#79191. Handling this correctly requires us to make decision about the semantics and specifically evaluation order of basically all MIR constructs (see specifically rust-lang#68364 rust-lang#71117.  The way this is implemented is based on my preferred resolution to these questions around the semantics of assignment statements.
 4. Some re-architecting to improve performance. More details below.
 5. Possible future improvements to this optimization are documented, and the code is written with the needs of those improvements in mind. The hope is that adding support for more precise analyses will not require a full re-write of this opt, but just localized changes.

### Regarding Performance

The previous approach had some performance issues; letting `l` be the number of locals and `s` be the number of statements/terminators, the runtime of the pass was `O(l^2 * s)`, both in theory and in practice. This version is smarter about not calculating unnecessary things and doing more caching. Our runtime is now dominated by one invocation of `MaybeLiveLocals` for each "round," and the number of rounds is less than 5 in over 90% of cases. This means it's linear-ish in practice.

r? `@oli-obk` who reviewed the last version of this, but review from anyone else would be more than welcome
@tmiasko
Copy link
Contributor Author

tmiasko commented Dec 5, 2022

Fixed in #96451.

@tmiasko tmiasko closed this as completed Dec 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-mir-opt Area: MIR optimizations A-mir-opt-nrvo Fixed by NRVO C-bug Category: This is a bug. I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness requires-nightly This issue requires a nightly compiler in some way. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

4 participants