Change:
As there is a write through a derived pointer, this code has UB under Stacked Borrows, but is fine under Tree Borrows, results in (which Miri agrees and is the right result), but miscompiles it to print Interestingly, if you remove in function , the miscompilation goes away. is required to prevent mir-opt from doing this. Directly getting a through in the first line of also makes the bug go away. cc
If you mark as , does that fix the behavior? (This should be required if you use instead of .)
No, it doesn't change the behaviour (and there's an warning)
That is very strange, I was under the impression that we generate the same code for addrof and addrof_mut. Making and equivalent for UB everwhere is an oft-requested feature, which is why TB accepts this code.
parameter, which is passed indirectly, is marked as readonly by pass (the pass assumes that is non-mutating).
AFAIK it was never intended to be considered non-mutating. In fact we still track this Miri behavior as a bug:
Sure, I am just describing why generated LLVM IR has undefined behavior.
do you know any other places where we assume one cannot mutate through an -created pointer? It was always (since ) my understanding that this should work and Stacked Borrows was unnecessarily pedantic here.
While I want to be able to say "yes this is DB", doesn't this hit issues depending on how the pointer is obtained? If you do and then , I don't think you should be allowed to mutate that pointer. For certain kinds of places (ones derived from a path for which would be valid) it might be okay to derive a mutable pointer to the place, but even then you hit mutable borrow invalidation issues if you use twice. Why not just use the mutability the user asked for? This is literally what is for. Note that in the issue was with two ways to obtain a pointer from a , so you definitely have mutable access in that case. When it's just a path you may or may not have mutable access to begin with, and the compiler knows which.
IMO it would be strange if allowed mutation but did not. I don't consider vs on local variables to be meaningful, and in fact we don't even preserve that information to MIR I think.
I agree with this, but perhaps draw the opposite conclusion from it. :) is the boundary between things the compiler can track and things it can't. When the compiler thinks something is immutable, we should really try to have the opsem respect that. Once it's a pointer I think it is okay to do mutability crimes because it's not helping anyone to be pedantic about it, but if would not compile, then it seems really wrong for to work (for mutation without UB). Put another way, if you want to make it legal to mutate bindings using raw pointers, that needs a larger quorum beyond just t-opsem, and I think t-lang will have some things to say about it.
I'm quite sympathetic to Mario's point here, but this is still the wrong order to consider this in. We should turn off the sketchy optimization while we figure out if it's ok
I find it funny that first there was a wave of complaints when Miri started making a difference between and (and this confusion still regularly comes up every few months), and now there is a wave of complaints when Miri does not make a difference between and . :joy: Some of this honestly feels like post-hoc rationalization to me: we got used to how Miri works, now we don't want it to change any more. But anyway, that's a topic for

Discussion:
use rustc_hir::def_id::LocalDefId; use rustc_index::bit_set::BitSet; use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor}; <del> use rustc_middle::mir::{Body, Local, Location, Operand, Terminator, TerminatorKind, RETURN_PLACE}; </del> <ins> use rustc_middle::mir::{Body, Location, Operand, Place, Terminator, TerminatorKind, RETURN_PLACE}; </ins> use rustc_middle::ty::{self, DeducedParamAttrs, Ty, TyCtxt}; use rustc_session::config::OptLevel;

} impl<'tcx> Visitor<'tcx> for DeduceReadOnly { <del> fn visit_local(&mut self, local: Local, mut context: PlaceContext, _: Location) { </del> <ins> fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) { </ins> // We're only interested in arguments. <del> if local == RETURN_PLACE || local.index() > self.mutable_args.domain_size() { </del> <ins> if place.local == RETURN_PLACE || place.local.index() > self.mutable_args.domain_size() { </ins> return; } <del> // Replace place contexts that are moves with copies. This is safe in all cases except // function argument position, which we already handled in `visit_terminator()` by using the // ArgumentChecker. See the comment in that method for more details. // // In the future, we might want to move this out into a separate pass, but for now let's // just do it on the fly because that's faster. if matches!(context, PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)) { context = PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy); } match context { PlaceContext::MutatingUse(..) | PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) => { </del> <ins> let mark_as_mutable = match context { PlaceContext::MutatingUse(..) => { </ins> // This is a mutation, so mark it as such. <del> self.mutable_args.insert(local.index() - 1); </del> <ins> true } PlaceContext::NonMutatingUse(NonMutatingUseContext::AddressOf) => { // Whether mutating though a `&raw const` is allowed is still undecided, so we // disable any sketchy `readonly` optimizations for now. // But we only need to do this if the pointer would point into the argument. !place.is_indirect() </ins> } PlaceContext::NonMutatingUse(..) | PlaceContext::NonUse(..) => { // Not mutating, so it's fine. <ins> false </ins> } <ins> }; if mark_as_mutable { self.mutable_args.insert(place.local.index() - 1); </ins> } }

<ins> // compile-flags: -C opt-level=3 -C no-prepopulate-passes // min-llvm-version: 15.0 (for opaque pointers) #![crate_type = "lib"] // Test for the absence of `readonly` on the argument when it is mutated via `&raw const`. // See <https://github.com/rust-lang/rust/issues/111502>. // CHECK: i8 @foo(ptr noalias nocapture noundef dereferenceable(128) %x) #[no_mangle] pub fn foo(x: [u8; 128]) -> u8 { let ptr = core::ptr::addr_of!(x).cast_mut(); unsafe { (*ptr)[0] = 1; } x[0] } // CHECK: i1 @second(ptr noalias nocapture noundef dereferenceable({{[0-9]+}}) %a_ptr_and_b) #[no_mangle] pub unsafe fn second(a_ptr_and_b: (*mut (i32, bool), (i64, bool))) -> bool { let b_bool_ptr = core::ptr::addr_of!(a_ptr_and_b.1.1).cast_mut(); (*b_bool_ptr) = true; a_ptr_and_b.1.1 } // If going through a deref (and there are no other mutating accesses), then `readonly` is fine. // CHECK: i1 @third(ptr noalias nocapture noundef readonly dereferenceable({{[0-9]+}}) %a_ptr_and_b) #[no_mangle] pub unsafe fn third(a_ptr_and_b: (*mut (i32, bool), (i64, bool))) -> bool { let b_bool_ptr = core::ptr::addr_of!((*a_ptr_and_b.0).1).cast_mut(); (*b_bool_ptr) = true; a_ptr_and_b.1.1 } </ins>