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

Copy propagation on "move" assignments introduces UB (using Miri/MiniRust semantics) #556

Open
RalfJung opened this issue Feb 13, 2025 · 0 comments

Comments

@RalfJung
Copy link
Member

So far, we have a theory for what move could mean on function calls, but we don't have a clear idea what it could mean in assignments (#416). Turns out the two are tightly linked: this example compiles to the following MIR without optimizations:

fn src(_1: Foo) -> () {
    debug x => _1;
    let mut _0: ();
    let _2: ();
    let _3: &Foo;
    let _4: ();

    bb0: {
        _3 = &_1;
        _2 = escape(copy _3) -> [return: bb1, unwind continue];
    }

    bb1: {
        _4 = move_arg(move _1) -> [return: bb2, unwind continue];
    }

    bb2: {
        return;
    }
}

And the following with optimizations:

fn src(_1: Foo) -> () {
    debug x => _1;
    let mut _0: ();
    let _2: ();
    let mut _3: Foo;
    scope 1 (inlined escape) {
        let mut _4: *const Foo;
        let mut _5: *mut *const Foo;
    }

    bb0: {
        _4 = &raw const _1;
        StorageLive(_5);
        _5 = const {alloc1: *mut *const Foo};
        (*_5) = copy _4;
        StorageDead(_5);
        StorageLive(_3);
        _3 = move _1;
        _2 = move_arg(move _3) -> [return: bb1, unwind continue];
    }

    bb1: {
        StorageDead(_3);
        return;
    }
}

Miri semantics says the former program has UB (though I have not managed to actually executed that MIR in Miri, passing -O is not enough somehow), while the latter is fine. The latter is fine since _3 = move _1; ignores the move, and so the argument actually moved to move_arg is _3 and that argument gets protected for the duration of the call.

I assume we want to allow the transformation that removes the extra move (though I don't know if we currently perform such an optimization); that is very tricky: _1 might be accessed in various ways after the move (even if we consider a move to de-init memory, one can still write to it); if the argument moved to move_arg becomes an alias of _1 that can easily introduce conflicts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant