Skip to content

Miscompile in DestinationPropagation? #146383

Closed

Description

@saethlin

Reduced example from rustlantis, which is accepted by Miri using Tree Borrows without optimizations enabled:

#![feature(custom_mir, core_intrinsics)]
#![allow(internal_features)]

use std::intrinsics::mir::*;

fn dump_var<T>(_: T) {}

#[custom_mir(dialect = "runtime")]
fn main() {
    mir!{
        let _1: (u8,);
        let _2: (u8,);
        let _3: (u8,);
        let _4: (u8,);
        let _5: ();
        {
            _3.0 = 0;
            _1 = _3;
            _4 = (_1.0, );
            _1 = _4;
            Call(_5 = dump_var(_1), ReturnTo(bb1), UnwindUnreachable())
        }
        bb1 = {
            Return()
        }
    }
}

I'm sure this is caused by DestinationPropagation, because

cargo +nightly miri run

accepts this code, but

MIRIFLAGS=-Zmir-enable-passes=+DestinationPropagation cargo +nightly miri run

reports UB (ignore the span, it's confusing because the source code that's displayed is the pre-optimization MIR):

error: Undefined Behavior: reading memory at alloc125[0x0..0x1], but memory is uninitialized at [0x0..0x1], and this operation requires initialized memory
  --> src/main.rs:19:13
   |
19 |             _4 = (_1.0, );
   |             ^^^^^^^^^^^^^ Undefined Behavior occurred here
   |
   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
   = note: BACKTRACE:
   = note: inside `main` at src/main.rs:19:13: 19:26

The errant transformation rewrite the entire body:

-// MIR for `main` before DestinationPropagation
+// MIR for `main` after DestinationPropagation
 
 fn main() -> () {
     let mut _0: ();
     let mut _1: (u8,);
     let mut _2: (u8,);
     let mut _3: (u8,);
     let mut _4: (u8,);
     let mut _5: ();
 
     bb0: {
-        (_3.0: u8) = const 0_u8;
-        _1 = copy _3;
-        _4 = (copy (_1.0: u8),);
-        _1 = copy _4;
-        _5 = dump_var::<(u8,)>(copy _1) -> [return: bb1, unwind unreachable];
+        (_4.0: u8) = const 0_u8;
+        nop;
+        _4 = (copy (_4.0: u8),);
+        nop;
+        _5 = dump_var::<(u8,)>(copy _4) -> [return: bb1, unwind unreachable];
     }
 
     bb1: {
         return;
     }
 }

This is probably #145541 cc @cjgillot @Amanieu

Activity

  1. added
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    C-bugCategory: This is a bug.
    I-miscompileIssue: Correct Rust code lowers to incorrect machine code
    A-rustlantisArea: A miscompilation found by Rustlantis
    on Sep 9, 2025
  2. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Sep 9, 2025
  3. added
    S-has-mcveStatus: A Minimal Complete and Verifiable Example has been found for this issue
    P-highHigh priority
    and removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    P-highHigh priority
    on Sep 10, 2025
  4. changed the title [-]Miscompile in DestinationPropagation[/-] [+]Miscompile in DestinationPropagation?[/+] on Sep 10, 2025
  5. saethlin commented on Sep 10, 2025

    @saethlin
    MemberAuthor

    Actually, I'm confused. This program executes UB?

    #![feature(custom_mir, core_intrinsics)]
    #![allow(internal_features)]
    
    use std::intrinsics::mir::*;
    
    #[custom_mir(dialect = "runtime")]
    fn main() {
        mir!{
            let _1: (u8,);
            {
                _1.0 = 0_u8;
                _1 = (_1.0, );
                Return()
            }
        }
    }

    Miri says:

    error: Undefined Behavior: reading memory at alloc153[0x0..0x1], but memory is uninitialized at [0x0..0x1], and this operation requires initialized memory
      --> src/main.rs:12:13
       |
    12 |             _1 = (_1.0, );
       |             ^^^^^^^^^^^^^ Undefined Behavior occurred here
       |
    

    @RalfJung does this UB report make sense to you?

  6. saethlin commented on Sep 10, 2025

    @saethlin
    MemberAuthor

    The UB report occurs because of this:

    fn write_aggregate(
    &mut self,
    kind: &mir::AggregateKind<'tcx>,
    operands: &IndexSlice<FieldIdx, mir::Operand<'tcx>>,
    dest: &PlaceTy<'tcx, M::Provenance>,
    ) -> InterpResult<'tcx> {
    self.write_uninit(dest)?; // make sure all the padding ends up as uninit

    Miri accepts this code without that write_uninit call. But I'm not sure if this self-assignment is supposed to be disallowed because it's an overlapping copy_nonoverlapping.

  7. tmiasko commented on Sep 10, 2025

    @tmiasko
    Contributor

    I would expect an aggregate assignment with an overlapping memory to have undefined behavior analogously to an assignment. If anything, this looks like a situation where Miri undefined behavior detection is incomplete.

    Regardless, this is definitely an issue in destination propagation:

    $ cat c.rs
    #![feature(custom_mir, core_intrinsics)]
    #![allow(internal_features)]
    
    use std::intrinsics::mir::*;
    use std::mem::MaybeUninit;
    
    #[custom_mir(dialect = "runtime")]
    fn f() -> (MaybeUninit<[u8; 10]>, MaybeUninit<[u8; 10]>) {
        mir!{
            let _1: (MaybeUninit<[u8; 10]>, MaybeUninit<[u8; 10]>);
            let _2: (MaybeUninit<[u8; 10]>, MaybeUninit<[u8; 10]>);
            let _3: ();
            {
                _1 = const { (MaybeUninit::new([0; 10]), MaybeUninit::new([1; 10])) };
                _2 = _1;
                _1 = (_2.1, _2.0);
                RET = _1;
                Return()
            }
        }
    }
    
    fn main() {
        unsafe {
            let a = f();
            assert_eq!(1, a.0.assume_init()[0]);
            assert_eq!(0, a.1.assume_init()[1]);
        }
    }
    $ rustc c.rs && ./c
    $ rustc c.rs -Zmir-enable-passes=+DestinationPropagation && ./c
    
    thread 'main' (9115) panicked at c.rs:27:9:
    assertion `left == right` failed
      left: 0
     right: 1
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
  8. 11 remaining items

  9. added a commit that references this issue on Sep 16, 2025
  10. added a commit that references this issue on Sep 16, 2025
  11. added a commit that references this issue on Sep 16, 2025
  12. added a commit that references this issue on Sep 16, 2025
  13. added a commit that references this issue on Sep 17, 2025
  14. added a commit that references this issue on Sep 18, 2025
  15. added a commit that references this issue on Sep 18, 2025
  16. added 3 commits that reference this issue on Nov 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-mir-optArea: MIR optimizationsA-rustlantisArea: A miscompilation found by RustlantisC-bugCategory: This is a bug.I-miscompileIssue: Correct Rust code lowers to incorrect machine codeS-has-mcveStatus: A Minimal Complete and Verifiable Example has been found for this issueT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions