Skip to content

Can we allow opportunistic in-place argument passing? #614

Open

Description

@RalfJung

@nikic suggested to me at the all-hands that sometimes it would be nice to allow function arguments to "opportunistically" be passed in-place: even if the argument is copy, which usually guarantees the caller will see the value at a different address than the callee, it could sometimes be useful to avoid the copy and just pass the value in-place.

@nikic do you have a concrete example of that?
@Amanieu do you think your MIR move semantics proposal could help with this?

Activity

  1. Amanieu commented on Jun 6, 2026

    @Amanieu
    Member

    We already have this which turns copy into move in MIR call terminators: rust-lang/rust#113758

  2. RalfJung commented on Jun 6, 2026

    @RalfJung
    MemberAuthor

    And how is that justified opsem-wise? Does it check that the address is not observed, or something like that?

  3. Amanieu commented on Jun 6, 2026

    @Amanieu
    Member

    It only applies to locals whose address is never taken.

  4. tmiasko commented on Jun 6, 2026

    @tmiasko

    We already have this which turns copy into move in MIR call terminators: rust-lang/rust#113758

    This transformation is not quite correct as far as current semantics is concerned. It ignores the fact that address of a local might be captured when it is passed in place (as an argument or a destination, although I am not sure if the latter is an issue). For example:

    pub fn f(a: [u8; 1024]) {
        g(a);
    }
    
    unsafe extern "Rust" {
        safe fn g(a: [u8; 1024]);
    }
    --- a.f.3-2-029.DeadStoreElimination-final.before.mir	2026-06-07 00:19:24.265982702 +0200
    +++ a.f.3-2-029.DeadStoreElimination-final.after.mir	2026-06-07 00:19:24.266036594 +0200
    @@ -1,15 +1,15 @@
    -// MIR for `f` before DeadStoreElimination-final
    +// MIR for `f` after DeadStoreElimination-final
     
     fn f(_1: [u8; 1024]) -> () {
         debug a => _1;
         let mut _0: ();
         let _2: ();
     
         bb0: {
    -        _2 = g(copy _1) -> [return: bb1, unwind continue];
    +        _2 = g(move _1) -> [return: bb1, unwind continue];
         }
     
         bb1: {
             return;
         }
     }

    The change from copy to move potentially introduces behavior which wasn't possible before, since address _1 might be observed before a call to f and inside g.

    It probably makes more sense under semantics from "MIR move elimination".

  5. Amanieu commented on Jun 7, 2026

    @Amanieu
    Member

    This is actually completely independent from the "MIR move elimination" semantics. See minirust/minirust#65 (comment) for the semantics that I have in mind for move operands to calls. Effectively, passing a place by move "donates" that place to an allocation pool for the duration of the call, and locals (including argument/return place locals) in callees may be allocated from this pool. This gives us the needed flexibility to do all of the optimizations we want.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions