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

Miscompilation of tuple arguments with mir-opt-level=2 #66971

Closed
bytwise opened this issue Dec 2, 2019 · 8 comments · Fixed by #67015
Closed

Miscompilation of tuple arguments with mir-opt-level=2 #66971

bytwise opened this issue Dec 2, 2019 · 8 comments · Fixed by #67015
Labels
A-mir Area: Mid-level IR (MIR) - https://blog.rust-lang.org/2016/04/19/MIR.html 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

@bytwise
Copy link
Contributor

bytwise commented Dec 2, 2019

This reduced testcase is getting miscompiled with mir-opt-level=2. I bisected it to #64890.

fn encode(this: ((), u8, u8)) {
    // this assert panics although this.2 is 0
    assert!(this.2 == 0);
}

fn emit() {
    encode(((), 0, 0));
}

fn emit_inc() -> Box<()> {
    emit();
    Box::new(())
}

fn main() {
    emit_inc();
}

Rust version:

rustc 1.41.0-nightly (4007d4ef2 2019-12-01)
binary: rustc
commit-hash: 4007d4ef26eab44bdabc2b7574d032152264d3ad
commit-date: 2019-12-01
host: x86_64-apple-darwin
release: 1.41.0-nightly
LLVM version: 9.0
@jonas-schievink jonas-schievink added C-bug Category: This is a bug. I-nominated I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-mir Area: Mid-level IR (MIR) - https://blog.rust-lang.org/2016/04/19/MIR.html requires-nightly This issue requires a nightly compiler in some way. and removed I-nominated labels Dec 2, 2019
@Centril
Copy link
Contributor

Centril commented Dec 3, 2019

cc @wesleywiser @oli-obk

@osa1
Copy link
Contributor

osa1 commented Dec 3, 2019

Slightly simpler reproducer fails the same way:

fn encode(this: ((), u8, u8)) {
    // this assert panics although this.2 is 0
    assert!(this.2 == 0);
}

fn main() {
    encode(((), 0, 0));
}

@osa1
Copy link
Contributor

osa1 commented Dec 3, 2019

I may be talking nonsense (first time looking at a mir dump), but I think in main:

fn  main() -> () {
    let mut _0: ();                      // return place in scope 0 at test.rs:6:11: 6:11
    let _1: ();                          // in scope 0 at test.rs:7:5: 7:23
    let mut _2: ((), u8, u8);            // in scope 0 at test.rs:7:12: 7:22

    bb0: {
        StorageLive(_1);                 // bb0[0]: scope 0 at test.rs:7:5: 7:23
        StorageLive(_2);                 // bb0[1]: scope 0 at test.rs:7:12: 7:22
        (_2.0: ()) = const Scalar(0x00) : (); // bb0[2]: scope 0 at test.rs:7:12: 7:22
                                         // ty::Const
                                         // + ty: ()
                                         // + val: Value(Scalar(0x00))
                                         // mir::Constant
                                         // + span: test.rs:7:12: 7:22
                                         // + literal: Const { ty: (), val: Value(Scalar(0x00)) }
        (_2.1: u8) = const 0u8;          // bb0[3]: scope 0 at test.rs:7:12: 7:22
                                         // ty::Const
                                         // + ty: u8
                                         // + val: Value(Scalar(0x00))
                                         // mir::Constant
                                         // + span: test.rs:7:12: 7:22
                                         // + literal: Const { ty: u8, val: Value(Scalar(0x00)) }
        _1 = const encode(move _2) -> bb1; // bb0[4]: scope 0 at test.rs:7:5: 7:23
                                         // ty::Const
                                         // + ty: fn(((), u8, u8)) {encode}
                                         // + val: Value(Scalar(<ZST>))
                                         // mir::Constant
                                         // + span: test.rs:7:5: 7:11
                                         // + literal: Const { ty: fn(((), u8, u8)) {encode}, val: Value(Scalar(<ZST>)) }
    }

    bb1: {
        StorageDead(_2);                 // bb1[0]: scope 0 at test.rs:7:22: 7:23
        StorageDead(_1);                 // bb1[1]: scope 0 at test.rs:7:23: 7:24
        return;                          // bb1[2]: scope 0 at test.rs:8:2: 8:2
    }
}

_2.2 should've been initialized similar to _2.1 (something like (_2.2: u8) = const 0u8;), but it's not.

@osa1
Copy link
Contributor

osa1 commented Dec 3, 2019

Looking at MIR dumps, before constant propagation this is how _2 is initialized:

        _2 = (move _3, const 0u8, const 0u8); // bb0[4]: scope 0 at test.rs:7:12: 7:22
                                         // ty::Const
                                         // + ty: u8
                                         // + val: Value(Scalar(0x00))
                                         // mir::Constant
                                         // + span: test.rs:7:17: 7:18
                                         // + literal: Const { ty: u8, val: Value(Scalar(0x00)) }
                                         // ty::Const
                                         // + ty: u8
                                         // + val: Value(Scalar(0x00))
                                         // mir::Constant
                                         // + span: test.rs:7:20: 7:21
                                         // + literal: Const { ty: u8, val: Value(Scalar(0x00)) }

After:

        _2 = (const Scalar(0x00) : (), const 0u8); // bb0[4]: scope 0 at test.rs:7:12: 7:22
                                         // ty::Const
                                         // + ty: ()
                                         // + val: Value(Scalar(0x00))
                                         // mir::Constant
                                         // + span: test.rs:7:12: 7:22
                                         // + literal: Const { ty: (), val: Value(Scalar(0x00)) }
                                         // ty::Const
                                         // + ty: u8
                                         // + val: Value(Scalar(0x00))
                                         // mir::Constant
                                         // + span: test.rs:7:12: 7:22
                                         // + literal: Const { ty: u8, val: Value(Scalar(0x00)) }

So the last field disappears.

@osa1
Copy link
Contributor

osa1 commented Dec 3, 2019

I think the bug is in these lines:

    fn replace_with_const(
        &mut self,
        rval: &mut Rvalue<'tcx>,
        value: Const<'tcx>,
        source_info: SourceInfo,
    ) {
        ...
                Immediate::ScalarPair(
                    ScalarMaybeUndef::Scalar(one),
                    ScalarMaybeUndef::Scalar(two)
                ) => {
                    let ty = &value.layout.ty.kind;
                    if let ty::Tuple(substs) = ty {
                        debug!("substs: {:#?}", substs);
                        *rval = Rvalue::Aggregate(
                            Box::new(AggregateKind::Tuple),
                            vec![
                                self.operand_from_scalar(
                                    one, substs[0].expect_ty(), source_info.span
                                ),
                                self.operand_from_scalar(
                                    two, substs[1].expect_ty(), source_info.span
                                ),
                            ],
                        );
                    }
        ...

In this program when visiting main substs is [ (), u8, u8 ] so the rvalue needs to be [substs[1], substs[2]] not [substs[0], substs[1]].

@osa1
Copy link
Contributor

osa1 commented Dec 3, 2019

Confirmed that commenting out this block of code fixes the issue.

Not sure how to proceed. If we want to do this optimization for scalar pairs
then I think we'll have to filter out zero-sized types the value's type in
subst. Would that make sense?

@oli-obk
Copy link
Contributor

oli-obk commented Dec 4, 2019

Since we are making the decision on something that came from a layout, but then use the type to get at said field, we should probably figure out a layout based scheme for this. You can ask the layout for a field count, then iterate and use the field method to get a layout for each field. If it's zst, create a value from nothing, if not, fetch the next value from the scalar pair. If there are three fields, we need three entries even if one of them is zst

@osa1
Copy link
Contributor

osa1 commented Dec 4, 2019

Thanks @oli-obk. I have a fix, PR coming.

Centril added a commit to Centril/rust that referenced this issue Dec 11, 2019
Fix constant propagation for scalar pairs

We now only propagate a scalar pair if the Rvalue is a tuple with two scalars. This for example avoids propagating a (u8, u8) value when Rvalue has type `((), u8, u8)` (see the regression test). While this is a correct thing to do, implementation is tricky and will be done later.

Fixes rust-lang#66971
Fixes rust-lang#66339
Fixes rust-lang#67019
@bors bors closed this as completed in 2404a06 Dec 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-mir Area: Mid-level IR (MIR) - https://blog.rust-lang.org/2016/04/19/MIR.html 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

Successfully merging a pull request may close this issue.

5 participants