Skip to content

Commit 7577b35

Browse files
committed
Auto merge of #3149 - RalfJung:atomic-readonly-loads, r=RalfJung
accept some atomic loads from read-only memory matches rust-lang/rust#115577
2 parents 98ce6d1 + fabf9e8 commit 7577b35

10 files changed

+135
-62
lines changed

src/concurrency/data_race.rs

+49-19
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet};
5252
use rustc_index::{Idx, IndexVec};
5353
use rustc_middle::mir;
5454
use rustc_span::Span;
55-
use rustc_target::abi::{Align, Size};
55+
use rustc_target::abi::{Align, HasDataLayout, Size};
5656

5757
use crate::diagnostics::RacingOp;
5858
use crate::*;
@@ -194,6 +194,13 @@ struct AtomicMemoryCellClocks {
194194
size: Size,
195195
}
196196

197+
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
198+
enum AtomicAccessType {
199+
Load(AtomicReadOrd),
200+
Store,
201+
Rmw,
202+
}
203+
197204
/// Type of write operation: allocating memory
198205
/// non-atomic writes and deallocating memory
199206
/// are all treated as writes for the purpose
@@ -526,7 +533,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
526533
atomic: AtomicReadOrd,
527534
) -> InterpResult<'tcx, Scalar<Provenance>> {
528535
let this = self.eval_context_ref();
529-
this.atomic_access_check(place)?;
536+
this.atomic_access_check(place, AtomicAccessType::Load(atomic))?;
530537
// This will read from the last store in the modification order of this location. In case
531538
// weak memory emulation is enabled, this may not be the store we will pick to actually read from and return.
532539
// This is fine with StackedBorrow and race checks because they don't concern metadata on
@@ -546,7 +553,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
546553
atomic: AtomicWriteOrd,
547554
) -> InterpResult<'tcx> {
548555
let this = self.eval_context_mut();
549-
this.atomic_access_check(dest)?;
556+
this.atomic_access_check(dest, AtomicAccessType::Store)?;
550557

551558
this.allow_data_races_mut(move |this| this.write_scalar(val, dest))?;
552559
this.validate_atomic_store(dest, atomic)?;
@@ -558,8 +565,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
558565
this.buffered_atomic_write(val, dest, atomic, val)
559566
}
560567

561-
/// Perform an atomic operation on a memory location.
562-
fn atomic_op_immediate(
568+
/// Perform an atomic RMW operation on a memory location.
569+
fn atomic_rmw_op_immediate(
563570
&mut self,
564571
place: &MPlaceTy<'tcx, Provenance>,
565572
rhs: &ImmTy<'tcx, Provenance>,
@@ -568,7 +575,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
568575
atomic: AtomicRwOrd,
569576
) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
570577
let this = self.eval_context_mut();
571-
this.atomic_access_check(place)?;
578+
this.atomic_access_check(place, AtomicAccessType::Rmw)?;
572579

573580
let old = this.allow_data_races_mut(|this| this.read_immediate(place))?;
574581

@@ -592,7 +599,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
592599
atomic: AtomicRwOrd,
593600
) -> InterpResult<'tcx, Scalar<Provenance>> {
594601
let this = self.eval_context_mut();
595-
this.atomic_access_check(place)?;
602+
this.atomic_access_check(place, AtomicAccessType::Rmw)?;
596603

597604
let old = this.allow_data_races_mut(|this| this.read_scalar(place))?;
598605
this.allow_data_races_mut(|this| this.write_scalar(new, place))?;
@@ -613,7 +620,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
613620
atomic: AtomicRwOrd,
614621
) -> InterpResult<'tcx, ImmTy<'tcx, Provenance>> {
615622
let this = self.eval_context_mut();
616-
this.atomic_access_check(place)?;
623+
this.atomic_access_check(place, AtomicAccessType::Rmw)?;
617624

618625
let old = this.allow_data_races_mut(|this| this.read_immediate(place))?;
619626
let lt = this.wrapping_binary_op(mir::BinOp::Lt, &old, &rhs)?.to_scalar().to_bool()?;
@@ -652,7 +659,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
652659
) -> InterpResult<'tcx, Immediate<Provenance>> {
653660
use rand::Rng as _;
654661
let this = self.eval_context_mut();
655-
this.atomic_access_check(place)?;
662+
this.atomic_access_check(place, AtomicAccessType::Rmw)?;
656663

657664
// Failure ordering cannot be stronger than success ordering, therefore first attempt
658665
// to read with the failure ordering and if successful then try again with the success
@@ -1062,7 +1069,11 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
10621069
}
10631070

10641071
/// Checks that an atomic access is legal at the given place.
1065-
fn atomic_access_check(&self, place: &MPlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
1072+
fn atomic_access_check(
1073+
&self,
1074+
place: &MPlaceTy<'tcx, Provenance>,
1075+
access_type: AtomicAccessType,
1076+
) -> InterpResult<'tcx> {
10661077
let this = self.eval_context_ref();
10671078
// Check alignment requirements. Atomics must always be aligned to their size,
10681079
// even if the type they wrap would be less aligned (e.g. AtomicU64 on 32bit must
@@ -1080,15 +1091,34 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
10801091
.ptr_try_get_alloc_id(place.ptr())
10811092
.expect("there are no zero-sized atomic accesses");
10821093
if this.get_alloc_mutability(alloc_id)? == Mutability::Not {
1083-
// FIXME: make this prettier, once these messages have separate title/span/help messages.
1084-
throw_ub_format!(
1085-
"atomic operations cannot be performed on read-only memory\n\
1086-
many platforms require atomic read-modify-write instructions to be performed on writeable memory, even if the operation fails \
1087-
(and is hence nominally read-only)\n\
1088-
some platforms implement (some) atomic loads via compare-exchange, which means they do not work on read-only memory; \
1089-
it is possible that we could have an exception permitting this for specific kinds of loads\n\
1090-
please report an issue at <https://github.com/rust-lang/miri/issues> if this is a problem for you"
1091-
);
1094+
// See if this is fine.
1095+
match access_type {
1096+
AtomicAccessType::Rmw | AtomicAccessType::Store => {
1097+
throw_ub_format!(
1098+
"atomic store and read-modify-write operations cannot be performed on read-only memory\n\
1099+
see <https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#atomic-accesses-to-read-only-memory> for more information"
1100+
);
1101+
}
1102+
AtomicAccessType::Load(_)
1103+
if place.layout.size > this.tcx.data_layout().pointer_size() =>
1104+
{
1105+
throw_ub_format!(
1106+
"large atomic load operations cannot be performed on read-only memory\n\
1107+
these operations often have to be implemented using read-modify-write operations, which require writeable memory\n\
1108+
see <https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#atomic-accesses-to-read-only-memory> for more information"
1109+
);
1110+
}
1111+
AtomicAccessType::Load(o) if o != AtomicReadOrd::Relaxed => {
1112+
throw_ub_format!(
1113+
"non-relaxed atomic load operations cannot be performed on read-only memory\n\
1114+
these operations sometimes have to be implemented using read-modify-write operations, which require writeable memory\n\
1115+
see <https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#atomic-accesses-to-read-only-memory> for more information"
1116+
);
1117+
}
1118+
_ => {
1119+
// Large relaxed loads are fine!
1120+
}
1121+
}
10921122
}
10931123
Ok(())
10941124
}

src/shims/intrinsics/atomic.rs

+12-12
Original file line numberDiff line numberDiff line change
@@ -77,40 +77,40 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
7777
this.atomic_compare_exchange_weak(args, dest, rw_ord(ord1)?, read_ord(ord2)?)?,
7878

7979
["or", ord] =>
80-
this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::BitOr, false), rw_ord(ord)?)?,
80+
this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::BitOr, false), rw_ord(ord)?)?,
8181
["xor", ord] =>
82-
this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::BitXor, false), rw_ord(ord)?)?,
82+
this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::BitXor, false), rw_ord(ord)?)?,
8383
["and", ord] =>
84-
this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::BitAnd, false), rw_ord(ord)?)?,
84+
this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::BitAnd, false), rw_ord(ord)?)?,
8585
["nand", ord] =>
86-
this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::BitAnd, true), rw_ord(ord)?)?,
86+
this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::BitAnd, true), rw_ord(ord)?)?,
8787
["xadd", ord] =>
88-
this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::Add, false), rw_ord(ord)?)?,
88+
this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::Add, false), rw_ord(ord)?)?,
8989
["xsub", ord] =>
90-
this.atomic_op(args, dest, AtomicOp::MirOp(BinOp::Sub, false), rw_ord(ord)?)?,
90+
this.atomic_rmw_op(args, dest, AtomicOp::MirOp(BinOp::Sub, false), rw_ord(ord)?)?,
9191
["min", ord] => {
9292
// Later we will use the type to indicate signed vs unsigned,
9393
// so make sure it matches the intrinsic name.
9494
assert!(matches!(args[1].layout.ty.kind(), ty::Int(_)));
95-
this.atomic_op(args, dest, AtomicOp::Min, rw_ord(ord)?)?;
95+
this.atomic_rmw_op(args, dest, AtomicOp::Min, rw_ord(ord)?)?;
9696
}
9797
["umin", ord] => {
9898
// Later we will use the type to indicate signed vs unsigned,
9999
// so make sure it matches the intrinsic name.
100100
assert!(matches!(args[1].layout.ty.kind(), ty::Uint(_)));
101-
this.atomic_op(args, dest, AtomicOp::Min, rw_ord(ord)?)?;
101+
this.atomic_rmw_op(args, dest, AtomicOp::Min, rw_ord(ord)?)?;
102102
}
103103
["max", ord] => {
104104
// Later we will use the type to indicate signed vs unsigned,
105105
// so make sure it matches the intrinsic name.
106106
assert!(matches!(args[1].layout.ty.kind(), ty::Int(_)));
107-
this.atomic_op(args, dest, AtomicOp::Max, rw_ord(ord)?)?;
107+
this.atomic_rmw_op(args, dest, AtomicOp::Max, rw_ord(ord)?)?;
108108
}
109109
["umax", ord] => {
110110
// Later we will use the type to indicate signed vs unsigned,
111111
// so make sure it matches the intrinsic name.
112112
assert!(matches!(args[1].layout.ty.kind(), ty::Uint(_)));
113-
this.atomic_op(args, dest, AtomicOp::Max, rw_ord(ord)?)?;
113+
this.atomic_rmw_op(args, dest, AtomicOp::Max, rw_ord(ord)?)?;
114114
}
115115

116116
_ => throw_unsup_format!("unimplemented intrinsic: `atomic_{intrinsic_name}`"),
@@ -178,7 +178,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
178178
Ok(())
179179
}
180180

181-
fn atomic_op(
181+
fn atomic_rmw_op(
182182
&mut self,
183183
args: &[OpTy<'tcx, Provenance>],
184184
dest: &PlaceTy<'tcx, Provenance>,
@@ -213,7 +213,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
213213
Ok(())
214214
}
215215
AtomicOp::MirOp(op, neg) => {
216-
let old = this.atomic_op_immediate(&place, &rhs, op, neg, atomic)?;
216+
let old = this.atomic_rmw_op_immediate(&place, &rhs, op, neg, atomic)?;
217217
this.write_immediate(*old, dest)?; // old value is returned
218218
Ok(())
219219
}

tests/fail/concurrency/read_only_atomic_cmpxchg.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ fn main() {
77
static X: i32 = 0;
88
let x = &X as *const i32 as *const AtomicI32;
99
let x = unsafe { &*x };
10-
x.compare_exchange(1, 2, Ordering::Relaxed, Ordering::Relaxed).unwrap_err(); //~ERROR: atomic operations cannot be performed on read-only memory
10+
x.compare_exchange(1, 2, Ordering::Relaxed, Ordering::Relaxed).unwrap_err(); //~ERROR: cannot be performed on read-only memory
1111
}

tests/fail/concurrency/read_only_atomic_cmpxchg.stderr

+4-8
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
1-
error: Undefined Behavior: atomic operations cannot be performed on read-only memory
2-
many platforms require atomic read-modify-write instructions to be performed on writeable memory, even if the operation fails (and is hence nominally read-only)
3-
some platforms implement (some) atomic loads via compare-exchange, which means they do not work on read-only memory; it is possible that we could have an exception permitting this for specific kinds of loads
4-
please report an issue at <https://github.com/rust-lang/miri/issues> if this is a problem for you
1+
error: Undefined Behavior: atomic store and read-modify-write operations cannot be performed on read-only memory
2+
see <https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#atomic-accesses-to-read-only-memory> for more information
53
--> $DIR/read_only_atomic_cmpxchg.rs:LL:CC
64
|
75
LL | x.compare_exchange(1, 2, Ordering::Relaxed, Ordering::Relaxed).unwrap_err();
8-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ atomic operations cannot be performed on read-only memory
9-
many platforms require atomic read-modify-write instructions to be performed on writeable memory, even if the operation fails (and is hence nominally read-only)
10-
some platforms implement (some) atomic loads via compare-exchange, which means they do not work on read-only memory; it is possible that we could have an exception permitting this for specific kinds of loads
11-
please report an issue at <https://github.com/rust-lang/miri/issues> if this is a problem for you
6+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ atomic store and read-modify-write operations cannot be performed on read-only memory
7+
see <https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#atomic-accesses-to-read-only-memory> for more information
128
|
139
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
1410
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

tests/fail/concurrency/read_only_atomic_load.stderr

-21
This file was deleted.

tests/fail/concurrency/read_only_atomic_load.rs renamed to tests/fail/concurrency/read_only_atomic_load_acquire.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ fn main() {
99
let x = unsafe { &*x };
1010
// Some targets can implement atomic loads via compare_exchange, so we cannot allow them on
1111
// read-only memory.
12-
x.load(Ordering::Relaxed); //~ERROR: atomic operations cannot be performed on read-only memory
12+
x.load(Ordering::Acquire); //~ERROR: cannot be performed on read-only memory
1313
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
error: Undefined Behavior: non-relaxed atomic load operations cannot be performed on read-only memory
2+
these operations sometimes have to be implemented using read-modify-write operations, which require writeable memory
3+
see <https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#atomic-accesses-to-read-only-memory> for more information
4+
--> $DIR/read_only_atomic_load_acquire.rs:LL:CC
5+
|
6+
LL | x.load(Ordering::Acquire);
7+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ non-relaxed atomic load operations cannot be performed on read-only memory
8+
these operations sometimes have to be implemented using read-modify-write operations, which require writeable memory
9+
see <https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#atomic-accesses-to-read-only-memory> for more information
10+
|
11+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
12+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
13+
= note: BACKTRACE:
14+
= note: inside `main` at $DIR/read_only_atomic_load_acquire.rs:LL:CC
15+
16+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
17+
18+
error: aborting due to previous error
19+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Should not rely on the aliasing model for its failure.
2+
//@compile-flags: -Zmiri-disable-stacked-borrows
3+
// Needs atomic accesses larger than the pointer size
4+
//@ignore-64bit
5+
6+
use std::sync::atomic::{AtomicI64, Ordering};
7+
8+
#[repr(align(8))]
9+
struct AlignedI64(i64);
10+
11+
fn main() {
12+
static X: AlignedI64 = AlignedI64(0);
13+
let x = &X as *const AlignedI64 as *const AtomicI64;
14+
let x = unsafe { &*x };
15+
// Some targets can implement atomic loads via compare_exchange, so we cannot allow them on
16+
// read-only memory.
17+
x.load(Ordering::Relaxed); //~ERROR: cannot be performed on read-only memory
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
error: Undefined Behavior: large atomic load operations cannot be performed on read-only memory
2+
these operations often have to be implemented using read-modify-write operations, which require writeable memory
3+
see <https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#atomic-accesses-to-read-only-memory> for more information
4+
--> $DIR/read_only_atomic_load_large.rs:LL:CC
5+
|
6+
LL | x.load(Ordering::Relaxed);
7+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ large atomic load operations cannot be performed on read-only memory
8+
these operations often have to be implemented using read-modify-write operations, which require writeable memory
9+
see <https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#atomic-accesses-to-read-only-memory> for more information
10+
|
11+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
12+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
13+
= note: BACKTRACE:
14+
= note: inside `main` at $DIR/read_only_atomic_load_large.rs:LL:CC
15+
16+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
17+
18+
error: aborting due to previous error
19+

tests/pass/atomic-readonly-load.rs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Stacked Borrows doesn't like this.
2+
//@compile-flags: -Zmiri-tree-borrows
3+
4+
use std::sync::atomic::*;
5+
6+
fn main() {
7+
// Atomic loads from read-only memory are fine if they are relaxed and small.
8+
static X: i32 = 0;
9+
let x = &X as *const i32 as *const AtomicI32;
10+
let x = unsafe { &*x };
11+
x.load(Ordering::Relaxed);
12+
}

0 commit comments

Comments
 (0)