Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 6 additions & 17 deletions compiler/rustc_mir_transform/src/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -886,24 +886,13 @@ fn inline_call<'tcx, I: Inliner<'tcx>>(
// Place could result in two different locations if `f`
// writes to `i`. To prevent this we need to create a temporary
// borrow of the place and pass the destination as `*temp` instead.
fn dest_needs_borrow(place: Place<'_>) -> bool {

@Amanieu Amanieu Sep 21, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing dest_needs_borrow is just a cleanup, there is no functional change. It is equivalent to !is_stable_offset().

View changes since the review

for elem in place.projection.iter() {
match elem {
ProjectionElem::Deref | ProjectionElem::Index(_) => return true,
_ => {}
}
}

false
}

let dest = if dest_needs_borrow(destination) {
//
// This must be a raw pointer: a mutable reference could be invalidated by
// reading the arguments later, and would be invalid if the destination type
// is uninhabited.
let dest = if !destination.is_stable_offset() {
trace!("creating temp for return destination");
let dest = Rvalue::Ref(
tcx.lifetimes.re_erased,
BorrowKind::Mut { kind: MutBorrowKind::Default },
destination,
);
let dest = Rvalue::RawPtr(RawPtrKind::Mut, destination);
let dest_ty = dest.ty(caller_body, tcx);
let temp = Place::from(new_call_temp(caller_body, callsite, dest_ty, return_block));
caller_body[callsite.block].statements.push(Statement::new(
Expand Down
53 changes: 52 additions & 1 deletion tests/mir-opt/inline/indirect_destination.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use core::intrinsics::mir::*;
// CHECK-LABEL: fn f(
// CHECK: bb1: {
// CHECK-NEXT: StorageLive([[A:.*]]);
// CHECK-NEXT: [[A]] = &mut (*_1);
// CHECK-NEXT: [[A]] = &raw mut (*_1);
// CHECK-NEXT: StorageLive([[B:.*]]);
// CHECK-NEXT: [[B]] = const 42_u8;
// CHECK-NEXT: (*[[A]]) = move [[B]];
Expand Down Expand Up @@ -40,3 +40,54 @@ fn g() -> u8 {
}
}
}

// Reading the argument must not invalidate the saved destination pointer.
#[custom_mir(dialect = "runtime", phase = "initial")]
// CHECK-LABEL: fn indexed(
// CHECK: [[DEST:_[0-9]+]] = &raw mut _2[_1];
// CHECK: [[ARG:_[0-9]+]] = copy _2[_1];
// CHECK-NEXT: [[RET:_[0-9]+]] = copy [[ARG]];
// CHECK-NEXT: (*[[DEST]]) = move [[RET]];
pub fn indexed(index: usize) -> u32 {
mir! {
let values: [u32; 2];
{
values = [41, 99];
Call(values[index] = identity(values[index]), ReturnTo(done), UnwindContinue())
}
done = {
RET = values[index];
Return()
}
}
}

#[inline(always)]
fn identity(value: u32) -> u32 {
value
}

// Saving an uninhabited destination must not construct an invalid reference.
#[custom_mir(dialect = "runtime", phase = "initial")]
// CHECK-LABEL: fn uninhabited(
// CHECK: [[PTR:_[0-9]+]] = &raw mut _1;
// CHECK: [[DEST:_[0-9]+]] = &raw mut (*[[PTR]]);
// CHECK: panic_fmt
pub fn uninhabited() {
mir! {
let slot: !;
let ptr: *mut !;
{
ptr = &raw mut slot;
Call(*ptr = fail(), ReturnTo(done), UnwindContinue())
}
done = {
Return()
}
}
}

#[inline(always)]
fn fail() -> ! {
panic!("expected panic")
}