Skip to content

Miscompile in the GVN transform #130853

Closed

Description

@jwong101

I tried this code:

unsafe fn src(x: &&u8) -> bool {
    let y = **x;
    unknown();
    **x == y
}

static mut SUSSY: *mut u8 = core::ptr::null_mut();

#[inline(never)]
unsafe fn unknown() {
    *SUSSY = 1;
}

fn main() {
    let mut s = 0;
    unsafe {
        SUSSY = core::ptr::addr_of_mut!(s);
        println!("{}", src(&*core::ptr::addr_of!(SUSSY).cast::<&u8>()));
    }
}

I expected to see this happen:

This should print false, as I believe this is DB under both Stacked and Tree borrows(according to MIRI).

Instead, this happened:

It returns false in Debug mode, and the GVN MIR pass makes src() unconditionally return true in Release mode.

$ cargo miri run
Compiling sus v0.1.0 (/Users/jwong3/test/sus)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
     Running `/Users/jwong3/.rustup/toolchains/nightly-aarch64-apple-darwin/bin/cargo-miri runner target/miri/aarch64-apple-darwin/debug/sus`
false
$ cargo run -r
   Compiling sus v0.1.0 (/Users/jwong3/test/sus)
    Finished `release` profile [optimized] target(s) in 0.31s
     Running `target/release/sus`
true

Meta

Here's the MIR before the GVN pass:

// MIR for `src` before GVN

fn src(_1: &&u8) -> bool {
    debug x => _1;
    let mut _0: bool;
    let _2: u8;
    let _3: ();
    let mut _4: u8;
    let mut _5: u8;
    let mut _6: &u8;
    let mut _7: &u8;
    scope 1 {
        debug y => _2;
    }

    bb0: {
        StorageLive(_2);
        _6 = deref_copy (*_1);
        _2 = copy (*_6);
        _3 = unknown() -> [return: bb1, unwind continue];
    }

    bb1: {
        StorageLive(_4);
        _7 = deref_copy (*_1);
        _4 = copy (*_7);
        StorageLive(_5);
        _5 = copy _2;
        _0 = Eq(move _4, move _5);
        StorageDead(_5);
        StorageDead(_4);
        StorageDead(_2);
        return;
    }
}
After
// MIR for `src` after GVN

fn src(_1: &&u8) -> bool {
    debug x => _1;
    let mut _0: bool;
    let _2: u8;
    let _3: ();
    let mut _4: u8;
    let mut _5: u8;
    let mut _6: &u8;
    let mut _7: &u8;
    scope 1 {
        debug y => _2;
    }

    bb0: {
        nop;
        _6 = copy (*_1);
        _2 = copy (*_6);
        _3 = unknown() -> [return: bb1, unwind continue];
    }

    bb1: {
        StorageLive(_4);
        _7 = copy _6;
        _4 = copy _2;
        StorageLive(_5);
        _5 = copy _2;
        _0 = const true;
        StorageDead(_5);
        StorageDead(_4);
        nop;
        return;
    }
}

It would be justified to make src() return true if _6 was dereferenced again in bb1, however, the write in unknown() shouldn't invalidate the actual pointer stored in _1 if my understanding of Stacked Borrows is correct.

This is present in both Stable and Nightly.

Activity

  1. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Sep 25, 2024
  2. added
    I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
    A-mir-optArea: MIR optimizations
    on Sep 25, 2024
  3. added
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Sep 25, 2024
  4. added
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    and removed
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Sep 25, 2024
  5. RalfJung commented on Sep 29, 2024

    @RalfJung
    Member

    Cc @rust-lang/wg-mir-opt

  6. RalfJung commented on Sep 29, 2024

    @RalfJung
    Member

    Nice find, thanks. :)

  7. dianqk commented on Sep 29, 2024

    @dianqk
    Member

    Hmm, I don't think it's a miscompile. Your unsafe code breaks the semantic of immutable borrowing.

  8. RalfJung commented on Sep 30, 2024

    @RalfJung
    Member

    The test passes Miri. So no, it does not break the semantics of immutable borrowing as we understand them currently.

  9. added
    I-miscompileIssue: Correct Rust code lowers to incorrect machine code
    and removed
    C-bugCategory: This is a bug.
    on Oct 2, 2024
  10. 9 remaining items

  11. workingjubilee commented on Oct 11, 2024

    @workingjubilee
    Member

    Yeah, we have to drop this optimization for now, not because it is unjustifiable per se but because it is unjustified at the moment.

  12. RalfJung commented on Nov 1, 2024

    @RalfJung
    Member

    @cjgillot you are our main expert for the GVN transform... any chance you could take a look at this? Or leave some notes as starting points for someone else on what would be the best way to fix this?

  13. jieyouxu commented on Nov 13, 2024

    @jieyouxu
    Member

    P-high review pre-triage: assigning this issue to @cjgillot since they have a PR open (#132461) to address this already.

  14. added 2 commits that reference this issue on Nov 25, 2024
  15. added a commit that references this issue on Feb 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

A-codegenArea: Code generationA-mir-optArea: MIR optimizationsA-mir-opt-GVNArea: MIR opt Global Value Numbering (GVN)I-miscompileIssue: Correct Rust code lowers to incorrect machine codeI-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessP-highHigh priorityT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.WG-mir-optWorking group: MIR optimizations

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions