Skip to content

Make retags an implicit part of typed copies#154341

Open
RalfJung wants to merge 1 commit intorust-lang:mainfrom
RalfJung:retag-on-typed-copy
Open

Make retags an implicit part of typed copies#154341
RalfJung wants to merge 1 commit intorust-lang:mainfrom
RalfJung:retag-on-typed-copy

Conversation

@RalfJung
Copy link
Member

@RalfJung RalfJung commented Mar 24, 2026

View all comments

Ever since Stacked Borrows was first implemented in Miri, that was done with Retag statements: given a place (usually a local variable), those statements find all references stored inside the place and refresh their tags to ensure the aliasing requirements are upheld. However, this is a somewhat unsatisfying approach for multiple reasons:

  • It leaves open the question of where to even put Retag statements. Over time, the AddRetag pass settled on one possible answer to this, but it wasn't very canonical.
  • For assignments of the form *ptr = expr, if the assignment involves copying a reference, we probably want to do a retag -- but if we do a Retag(*ptr) as the next instruction, it can be non-trivial to argue that this even retags the right value, so we refrained from doing retags in that case. This has come up as a potential issue for Rust making better use of LLVM "captures" annotations.
  • Normal compilation avoids generating retags, but we still generate LLVM IR with noalias. What does that even mean? How do MIR optimization passes interact with retags? These are questions we have to figure out to make better use of aliasing information, but currently we can't even really ask such questions.

I think we should resolve all that by making retags part of what happens during a typed copy (a concept and interpreter infrastructure that did not exist yet when retags were initially introduced). Under this proposal, when executing a MIR assignment statement, what conceptually happens is as follows:

  • We evaluate the LHS to a place.
  • We evaluate the RHS to a value. This does a typed load from memory if needed, raising UB if memory does not contain a valid representation of the assignment's type.
  • We walk that value, identify all references inside of it, and retag them. If this happens as part of passing a function argument, this is a protecting retag.
  • We store (a representation of) the value into the place.

However, this semantics doesn't fully work: there's a mandatory MIR pass that turns expressions like &mut ***ptr into intermediate deref's. Those must not do any retags. So far this happened because the AddRetag pass did not add retags for assignments to deref temporaries, but that information is not recorded in cross-crate MIR. Therefore I instead added a field to Rvalue::Use to indicate whether this value should be retagged or not. A non-retagging copy seems like a sufficiently canonical primitive that we should be able to express it. Dealing with the fallout from that is a large chunk of the overall diff. (I also considered adding this field to StatementKind::Assign instead, but decided against that as we only actually need it for Rvalue::Use. I am not sure if this was the right call...)

This neatly answers the question of when retags should occur, and handles cases like *ptr = expr. It avoids traversing values twice in Miri. It makes codegen's use of noalias sound wrt the actual MIR that it is working on. It also gives us a target semantics to evaluate MIR opts against. However, I did not carefully check all MIR opts -- in particular, GVN needs a thorough look under the new semantics. (But this PR doesn't make things any worse for normal compilation where the retag indicator is anyway ignored.)

Another side-effect of this PR is that -Zmiri-disable-validation now also disables alias checking. It'd be nicer to keep them orthogonal but I find this an acceptable price to pay.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Mar 24, 2026
@rust-log-analyzer

This comment has been minimized.

@RalfJung RalfJung force-pushed the retag-on-typed-copy branch from dbabc07 to c5a3e40 Compare March 24, 2026 22:18
@rust-log-analyzer

This comment has been minimized.

@RalfJung RalfJung force-pushed the retag-on-typed-copy branch from c5a3e40 to df515dd Compare March 24, 2026 22:44
@rustbot rustbot added the T-clippy Relevant to the Clippy team. label Mar 24, 2026
@RalfJung
Copy link
Member Author

@bors try
@rust-timer queue

@rust-timer

This comment has been minimized.

@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Mar 24, 2026
@rust-bors

This comment has been minimized.

rust-bors bot pushed a commit that referenced this pull request Mar 24, 2026
Make retags an implicit part of typed copies
@rust-log-analyzer

This comment has been minimized.

@RalfJung RalfJung force-pushed the retag-on-typed-copy branch from df515dd to 76c8c9d Compare March 24, 2026 22:52
@rust-log-analyzer

This comment has been minimized.

@rust-bors
Copy link
Contributor

rust-bors bot commented Mar 25, 2026

☀️ Try build successful (CI)
Build commit: 82d9903 (82d99031f4626ac962af0c7f6d78d1f7173d7145, parent: 362211dc29abc4e8f8cfc384740237f144929b03)

@rust-timer

This comment has been minimized.

@rust-timer

This comment was marked as outdated.

@rustbot rustbot added perf-regression Performance regression. and removed S-waiting-on-perf Status: Waiting on a perf run to be completed. labels Mar 25, 2026
@RalfJung
Copy link
Member Author

Looks like enabling validation of references just to keep retags working in const-eval was not a good idea...

@RalfJung RalfJung force-pushed the retag-on-typed-copy branch from 76c8c9d to d79e607 Compare March 25, 2026 07:23
@RalfJung
Copy link
Member Author

@bors try
@rust-timer queue

@rust-timer

This comment has been minimized.

@rust-bors

This comment has been minimized.

rust-bors bot pushed a commit that referenced this pull request Mar 25, 2026
Make retags an implicit part of typed copies
@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Mar 25, 2026
@rust-log-analyzer

This comment has been minimized.

@rust-bors
Copy link
Contributor

rust-bors bot commented Mar 25, 2026

☀️ Try build successful (CI)
Build commit: 5bbea76 (5bbea7620d94ef1e4dd2e6617ed840cde1cf87f3, parent: 8a703520e80d87d4423c01f9d4fbc9e5f6533a02)

@RalfJung RalfJung force-pushed the retag-on-typed-copy branch from 9b5b622 to 2a1bbaa Compare March 25, 2026 11:15
@rust-log-analyzer

This comment has been minimized.

@RalfJung RalfJung force-pushed the retag-on-typed-copy branch from 2a1bbaa to b0c8a17 Compare March 25, 2026 12:10
@RalfJung
Copy link
Member Author

@bors try jobs=dist-x86_64-linux

@rust-bors

This comment has been minimized.

rust-bors bot pushed a commit that referenced this pull request Mar 25, 2026
Make retags an implicit part of typed copies


try-job: dist-x86_64-linux
@rust-log-analyzer

This comment has been minimized.

@RalfJung RalfJung force-pushed the retag-on-typed-copy branch 2 times, most recently from 1a60ecb to c15e5b2 Compare March 25, 2026 13:57
@RalfJung RalfJung marked this pull request as ready for review March 25, 2026 14:02
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Mar 25, 2026
@rustbot
Copy link
Collaborator

rustbot commented Mar 25, 2026

Some changes occurred in src/tools/clippy

cc @rust-lang/clippy

The Miri subtree was changed

cc @rust-lang/miri

Some changes occurred to the CTFE / Miri interpreter

cc @rust-lang/miri, @oli-obk, @lcnr

Some changes occurred to constck

cc @fee1-dead

Some changes occurred in coverage instrumentation.

cc @Zalathar

Some changes occurred in match lowering

cc @Nadrieril

This PR changes MIR

cc @oli-obk, @JakobDegen, @vakaras

Some changes occurred in compiler/rustc_codegen_cranelift

cc @bjorn3

Some changes occurred to MIR optimizations

cc @rust-lang/wg-mir-opt

Some changes occurred to the CTFE machinery

cc @oli-obk, @lcnr

This PR changes rustc_public

cc @oli-obk, @celinval, @ouz-a, @makai410

Some changes occurred to the CTFE / Miri interpreter

cc @rust-lang/miri

Some changes occurred to the intrinsics. Make sure the CTFE / Miri interpreter
gets adapted for the changes, if necessary.

cc @rust-lang/miri, @oli-obk, @lcnr

@rustbot rustbot removed the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Mar 25, 2026
@rustbot
Copy link
Collaborator

rustbot commented Mar 25, 2026

r? @fee1-dead

rustbot has assigned @fee1-dead.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: compiler, mir, mir-opt
  • compiler, mir, mir-opt expanded to 69 candidates
  • Random selection from 13 candidates

// knows what is supposed to happen here -- retag or not? `CopyForDeref`
// later turns into a no-retag assignment so probably maybe that's also
// what we need here.
Rvalue::Use(Operand::Copy(deref_place), WithRetag::No)
Copy link
Member Author

Choose a reason for hiding this comment

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

Cc @beepster4096 maybe you can help us with this.

Copy link
Member Author

Choose a reason for hiding this comment

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

GVN (and also other MIR passes) has a somewhat non-obvious choice to make for these retags... either we ensure that the values that get replaced are all being retagged already anyway, or the new assignments they insert must be no-retag. We have to avoid a situation where such a pass adds a retag to a value that originally wasn't going to be retagged. I don't understand how GVN works so I decided to ignore this pass for now (and leave it as "always retag" to avoid a huge diff in mir-opt). This should be cleaned up in a follow-up PR. For the other passes I think I made sound choices but I am not fully confident about this.

Copy link
Member Author

Choose a reason for hiding this comment

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

This pass doesn't track whether the constant it encounters are retagged or not, and I couldn't figure out if there could be non-integers involved here, so I just made the assignments it introduces no-retag to ensure we don't add incorrect retags. (Not retagging is the safe option in the sense that it's always allowed for remove retags, but not to add them.)

);
let mut helps = vec![operation_summary(&op.info.summary(), self.history.id, op.range)];
if op.info.in_field {
helps.push(format!("errors for retagging in fields are fairly new; please reach out to us (e.g. at <https://rust-lang.zulipchat.com/#narrow/stream/269128-miri>) if you find this error troubling"));
Copy link
Member Author

Choose a reason for hiding this comment

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

We have done field retagging long enough now, I think we can remove this message. Instead, the with_validation_path machinery prints a path showing where in the value the relevant reference was encountered.

|
LL | let xraw: *mut i32 = unsafe { mem::transmute(&mut x) };
| ^^^^^^
| ^^^^^^^^^^^^^^^^^^^^^^
Copy link
Member Author

Choose a reason for hiding this comment

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

This span changes because we're now transmuting the input of the transmute again as part of the transmute operation.

Copy link
Member Author

Choose a reason for hiding this comment

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

These tests don't fail any more since we can no longer disable validation without disabling alias checks. I moved them to "pass". They don't really test the original issue any more but they seem worth keeping around as nasty corner cases.

Copy link
Member Author

Choose a reason for hiding this comment

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

I wasn't sure if there could ever be non-integers in the values we are copying here, so it seemed safer to not add any retags.

@rust-log-analyzer

This comment has been minimized.

@rust-bors
Copy link
Contributor

rust-bors bot commented Mar 25, 2026

☀️ Try build successful (CI)
Build commit: 2b1714e (2b1714e869694712562be686f5f043b35954ce82, parent: 8a703520e80d87d4423c01f9d4fbc9e5f6533a02)

@RalfJung
Copy link
Member Author

Miri performance numbers are positive throughout. :)

Comparison with baseline (relative speed, lower is better for the new results):
  backtraces: 0.97 ± 0.01
  big-allocs: 0.99 ± 0.10
  mse: 0.97 ± 0.02
  range-iteration: 0.94 ± 0.01
  serde1: 0.94 ± 0.01
  serde2: 0.93 ± 0.00
  slice-chunked: 0.96 ± 0.03
  slice-get-unchecked: 0.99 ± 0.02
  string-replace: 0.95 ± 0.03
  unicode: 0.97 ± 0.01
  zip-equal: 0.86 ± 0.01

@RalfJung RalfJung force-pushed the retag-on-typed-copy branch 2 times, most recently from 8f6343a to 443e1d2 Compare March 25, 2026 21:57
@rust-log-analyzer

This comment has been minimized.

@rust-bors

This comment has been minimized.

@RalfJung RalfJung force-pushed the retag-on-typed-copy branch from 443e1d2 to d298feb Compare March 26, 2026 07:07
@rustbot
Copy link
Collaborator

rustbot commented Mar 26, 2026

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@RalfJung
Copy link
Member Author

Yay, CI is green. :)

@fee1-dead as randomly selected reviewer for a big PR like this -- please let me know if you're comfortable reviewing this (in due time of course, no rush), or if I should go look for someone else.

) -> InterpResult<'tcx, Option<ImmTy<'tcx, CtfeProvenance>>> {
if matches!(ecx.machine.retag_mode, RetagMode::None | RetagMode::Raw) {
return interp_ok(None);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

out of curiosity -- what's the difference between Raw and None for RetagMode?

Copy link
Member Author

@RalfJung RalfJung Mar 26, 2026

Choose a reason for hiding this comment

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

A Raw retag does more than a default retag: in Stacked Borrows, when it finds a raw pointer, it will retag that. (In Tree Borrows, default retags and raw retags are equivalent.)

A None retag does less than a default retag. It doesn't retag anything.

Comment on lines +428 to +430
_ if ty.is_box_global(*this.tcx) => {
// The `None` marks this as a Box.
NewPermission::new(ty.builtin_deref(true).unwrap(), None, mode, this)
Copy link
Contributor

Choose a reason for hiding this comment

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

(apologies if this is not the right place for asking; happy to open a zulip thread) i had never noticed this; is it documented somewhere that only Box<T, Global> gets retags and effectively has aliasing validity? always assumed it was any Box!

Copy link
Member Author

@RalfJung RalfJung Mar 26, 2026

Choose a reason for hiding this comment

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

There has been some prior discussion and there are Miri testcases that show it would be UB to also require uniqueness for non-global Boxes, for examples of code that is meant to be valid with local allocators (at least that's what I am assuming). So indeed probably best to take this to Zulip.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-clippy Relevant to the Clippy team. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants