Skip to content

Commit b084ee1

Browse files
committed
Add unsafe-assume-privileged
cc #222
1 parent 0c89356 commit b084ee1

17 files changed

Lines changed: 287 additions & 73 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ float = []
3838
std = []
3939
require-cas = []
4040
unsafe-assume-single-core = []
41+
unsafe-assume-privileged = []
4142
s-mode = []
4243
force-amo = []
4344
disable-fiq = []

README.md

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,11 @@ RUSTFLAGS="--cfg portable_atomic_unsafe_assume_single_core" cargo ...
9393

9494
This enables atomic types with larger than the width supported by atomic instructions available on the current target. If the current target supports 128-bit atomics, this is no-op.
9595

96-
This uses lock-based fallback implementations by default. The following features/cfgs change this behavior:
97-
- `unsafe-assume-single-core` feature / `portable_atomic_unsafe_assume_single_core` cfg: Use fallback implementations that disabling interrupts instead of using locks.
96+
This uses fallback implementation that using global locks by default. The following features/cfgs change this behavior:
97+
- [`unsafe-assume-single-core` feature / `portable_atomic_unsafe_assume_single_core` cfg](#optional-features-unsafe-assume-single-core): Use fallback implementations that disabling interrupts instead of using global locks.
98+
- If your target is single-core and calling interrupt disable instructions is safe, this is a safer and more efficient option.
99+
- [`unsafe-assume-privileged` feature / `portable_atomic_unsafe_assume_privileged` cfg](#optional-features-unsafe-assume-privileged): Use fallback implementations that using global locks with disabling interrupts.
100+
- If your target is multi-core and calling interrupt disable instructions is safe, this is a safer option.
98101

99102
- <a name="optional-features-float"></a>**`float` feature**<br>
100103
Provide `AtomicF{32,64}`.
@@ -145,6 +148,7 @@ RUSTFLAGS="--cfg portable_atomic_unsafe_assume_single_core" cargo ...
145148
> ```
146149
>
147150
> - Enabling both this feature and `unsafe-assume-single-core` feature (or `portable_atomic_unsafe_assume_single_core` cfg) will result in a compile error.
151+
> - Enabling both this feature and `unsafe-assume-privileged` feature (or `portable_atomic_unsafe_assume_privileged` cfg) will result in a compile error.
148152
> - The MSRV when this feature is enabled depends on the MSRV of [critical-section].
149153
150154
- <a name="optional-features-unsafe-assume-single-core"></a><a name="optional-cfg-unsafe-assume-single-core"></a>**`unsafe-assume-single-core` feature / `portable_atomic_unsafe_assume_single_core` cfg**<br>
@@ -156,28 +160,53 @@ RUSTFLAGS="--cfg portable_atomic_unsafe_assume_single_core" cargo ...
156160
> [!WARNING]
157161
> This feature/cfg is `unsafe`, and note the following safety requirements:
158162
> - Enabling this feature/cfg for multi-core systems is always **unsound**.
163+
>
159164
> - This uses privileged instructions to disable interrupts, so it usually doesn't work on unprivileged mode.
160165
>
161166
> Enabling this feature/cfg in an environment where privileged instructions are not available, or if the instructions used are not sufficient to disable interrupts in the system, it is also usually considered **unsound**, although the details are system-dependent.
162167
>
163168
> The following are known cases:
164169
> - On pre-v6 Arm, this disables only IRQs by default. For many systems (e.g., GBA) this is enough. If the system need to disable both IRQs and FIQs, you need to enable the `disable-fiq` feature (or `portable_atomic_disable_fiq` cfg) together.
165170
> - On RISC-V without A-extension, this generates code for machine-mode (M-mode) by default. If you enable the `s-mode` feature (or `portable_atomic_s_mode` cfg) together, this generates code for supervisor-mode (S-mode). In particular, `qemu-system-riscv*` uses [OpenSBI](https://github.com/riscv-software-src/opensbi) as the default firmware.
166-
>
167-
> Consider using the [`critical-section` feature](#optional-features-critical-section) for systems that cannot use this feature/cfg.
168-
>
169-
> See also the [`interrupt` module's readme](https://github.com/taiki-e/portable-atomic/blob/HEAD/src/imp/interrupt/README.md).
171+
172+
Consider using the [`unsafe-assume-privileged` feature (or `portable_atomic_unsafe_assume_privileged` cfg)](#optional-features-unsafe-assume-privileged) for multi-core systems with atomic CAS.
173+
174+
Consider using the [`critical-section` feature](#optional-features-critical-section) for systems that cannot use this feature/cfg.
175+
176+
See also the [`interrupt` module's readme](https://github.com/taiki-e/portable-atomic/blob/HEAD/src/imp/interrupt/README.md).
170177
171178
> [!NOTE]
172179
> - It is **very strongly discouraged** to enable this feature/cfg in libraries that depend on `portable-atomic`.
173180
>
174181
> The recommended approach for libraries is to leave it up to the end user whether or not to enable this feature/cfg. (However, it may make sense to enable this feature/cfg by default for libraries specific to a platform where it is guaranteed to always be sound, for example in a hardware abstraction layer targeting a single-core chip.)
175182
> - Enabling this feature/cfg for unsupported architectures will result in a compile error.
176-
> - Armv6-M (thumbv6m), pre-v6 Arm (e.g., thumbv4t, thumbv5te), RISC-V without A-extension, and Xtensa are currently supported. (Since all MSP430 and AVR are single-core, we always provide atomic CAS for them without this feature/cfg.)
183+
> - Arm M-Profile architectures (e.g., thumbv6m), pre-v6 Arm (e.g., thumbv4t, thumbv5te), RISC-V, and Xtensa are currently supported. (Since all MSP430 and AVR are single-core, we always provide atomic CAS for them without this feature/cfg.)
184+
> - Feel free to [submit an issue](https://github.com/taiki-e/portable-atomic/issues/new) if your target is not supported yet.
185+
> - Enabling this feature/cfg for targets where privileged instructions are obviously unavailable (e.g., Linux) will result in a compile error.
186+
> - Feel free to [submit an issue](https://github.com/taiki-e/portable-atomic/issues/new) if your target supports privileged instructions but the build rejected.
187+
> - Enabling both this feature/cfg and `critical-section` feature will result in a compile error.
188+
> - When both this feature/cfg and `unsafe-assume-privileged` feature (or `portable_atomic_unsafe_assume_privileged` cfg) are enabled, this feature/cfg is preferred.
189+
190+
- <a name="optional-features-unsafe-assume-privileged"></a><a name="optional-cfg-unsafe-assume-privileged"></a>**`unsafe-assume-privileged` feature / `portable_atomic_unsafe_assume_privileged` cfg**<br>
191+
Similar to `unsafe-assume-single-core` feature / `portable_atomic_unsafe_assume_single_core` cfg, but only assumes about availability of privileged instructions required to disable interrupts.
192+
193+
- When both this feature/cfg and enabled-by-default `fallback` feature is enabled, this crate provides atomic types with larger than the width supported by native instructions by using global locks with disabling interrupts.
194+
195+
> [!WARNING]
196+
> This feature/cfg is `unsafe`, and except for being sound in multi-core systems, this has the same safety requirements as [`unsafe-assume-single-core` feature / `portable_atomic_unsafe_assume_single_core` cfg](#optional-features-unsafe-assume-single-core).
197+
198+
> [!NOTE]
199+
> - It is **very strongly discouraged** to enable this feature/cfg in libraries that depend on `portable-atomic`.
200+
>
201+
> The recommended approach for libraries is to leave it up to the end user whether or not to enable this feature/cfg. (However, it may make sense to enable this feature/cfg by default for libraries specific to a platform where it is guaranteed to always be sound, for example in a hardware abstraction layer.)
202+
> - Enabling this feature/cfg for unsupported targets will result in a compile error.
203+
> - This requires atomic CAS (`cfg(target_has_atomic = "ptr")` or `cfg_no_atomic_cas!`).
204+
> - Arm M-Profile architectures (e.g., thumbv6m), pre-v6 Arm (e.g., thumbv4t, thumbv5te), RISC-V, and Xtensa are currently supported.
177205
> - Feel free to [submit an issue](https://github.com/taiki-e/portable-atomic/issues/new) if your target is not supported yet.
178206
> - Enabling this feature/cfg for targets where privileged instructions are obviously unavailable (e.g., Linux) will result in a compile error.
179207
> - Feel free to [submit an issue](https://github.com/taiki-e/portable-atomic/issues/new) if your target supports privileged instructions but the build rejected.
180208
> - Enabling both this feature/cfg and `critical-section` feature will result in a compile error.
209+
> - When both this feature/cfg and `unsafe-assume-single-core` feature (or `portable_atomic_unsafe_assume_single_core` cfg) are enabled, `unsafe-assume-single-core` is preferred.
181210
182211
- <a name="optional-cfg-no-outline-atomics"></a>**`portable_atomic_no_outline_atomics` cfg**<br>
183212
Disable dynamic dispatching by run-time CPU feature detection.

build.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ fn main() {
2020

2121
#[cfg(feature = "unsafe-assume-single-core")]
2222
println!("cargo:rustc-cfg=portable_atomic_unsafe_assume_single_core");
23+
#[cfg(feature = "unsafe-assume-privileged")]
24+
println!("cargo:rustc-cfg=portable_atomic_unsafe_assume_privileged");
2325
#[cfg(feature = "s-mode")]
2426
println!("cargo:rustc-cfg=portable_atomic_s_mode");
2527
#[cfg(feature = "force-amo")]
@@ -54,7 +56,7 @@ fn main() {
5456
// Custom cfgs set by build script. Not public API.
5557
// grep -F 'cargo:rustc-cfg=' build.rs | grep -Ev '^ *//' | sed -E 's/^.*cargo:rustc-cfg=//; s/(=\\)?".*$//' | LC_ALL=C sort -u | tr '\n' ',' | sed -E 's/,$/\n/'
5658
println!(
57-
"cargo:rustc-check-cfg=cfg(portable_atomic_disable_fiq,portable_atomic_force_amo,portable_atomic_ll_sc_rmw,portable_atomic_atomic_intrinsics,portable_atomic_no_asm,portable_atomic_no_asm_maybe_uninit,portable_atomic_no_atomic_64,portable_atomic_no_atomic_cas,portable_atomic_no_atomic_load_store,portable_atomic_no_atomic_min_max,portable_atomic_no_cfg_target_has_atomic,portable_atomic_no_cmpxchg16b_intrinsic,portable_atomic_no_cmpxchg16b_target_feature,portable_atomic_no_const_mut_refs,portable_atomic_no_const_raw_ptr_deref,portable_atomic_no_const_transmute,portable_atomic_no_core_unwind_safe,portable_atomic_no_diagnostic_namespace,portable_atomic_no_strict_provenance,portable_atomic_no_stronger_failure_ordering,portable_atomic_no_track_caller,portable_atomic_no_unsafe_op_in_unsafe_fn,portable_atomic_pre_llvm_15,portable_atomic_pre_llvm_16,portable_atomic_pre_llvm_18,portable_atomic_pre_llvm_20,portable_atomic_s_mode,portable_atomic_sanitize_thread,portable_atomic_target_feature,portable_atomic_unsafe_assume_single_core,portable_atomic_unstable_asm,portable_atomic_unstable_asm_experimental_arch,portable_atomic_unstable_cfg_target_has_atomic,portable_atomic_unstable_isa_attribute)"
59+
"cargo:rustc-check-cfg=cfg(portable_atomic_atomic_intrinsics,portable_atomic_disable_fiq,portable_atomic_force_amo,portable_atomic_ll_sc_rmw,portable_atomic_no_asm,portable_atomic_no_asm_maybe_uninit,portable_atomic_no_atomic_64,portable_atomic_no_atomic_cas,portable_atomic_no_atomic_load_store,portable_atomic_no_atomic_min_max,portable_atomic_no_cfg_target_has_atomic,portable_atomic_no_cmpxchg16b_intrinsic,portable_atomic_no_cmpxchg16b_target_feature,portable_atomic_no_const_mut_refs,portable_atomic_no_const_raw_ptr_deref,portable_atomic_no_const_transmute,portable_atomic_no_core_unwind_safe,portable_atomic_no_diagnostic_namespace,portable_atomic_no_strict_provenance,portable_atomic_no_stronger_failure_ordering,portable_atomic_no_track_caller,portable_atomic_no_unsafe_op_in_unsafe_fn,portable_atomic_pre_llvm_15,portable_atomic_pre_llvm_16,portable_atomic_pre_llvm_18,portable_atomic_pre_llvm_20,portable_atomic_s_mode,portable_atomic_sanitize_thread,portable_atomic_target_feature,portable_atomic_unsafe_assume_privileged,portable_atomic_unsafe_assume_single_core,portable_atomic_unstable_asm,portable_atomic_unstable_asm_experimental_arch,portable_atomic_unstable_cfg_target_has_atomic,portable_atomic_unstable_isa_attribute)"
5860
);
5961
// TODO: handle multi-line target_feature_fallback
6062
// grep -F 'target_feature_fallback("' build.rs | grep -Ev '^ *//' | sed -E 's/^.*target_feature_fallback\(//; s/",.*$/"/' | LC_ALL=C sort -u | tr '\n' ',' | sed -E 's/,$/\n/'

src/imp/fallback/seq_lock.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ use core::{
88
};
99

1010
use super::utils::Backoff;
11+
#[cfg(portable_atomic_unsafe_assume_privileged)]
12+
use crate::imp::interrupt::arch as interrupt;
1113

1214
// See mod.rs for details.
1315
pub(super) type AtomicChunk = AtomicU64;
@@ -54,14 +56,24 @@ impl SeqLock {
5456
/// Grabs the lock for writing.
5557
#[inline]
5658
pub(super) fn write(&self) -> SeqLockWriteGuard<'_> {
59+
// Get current interrupt state and disable interrupts when the user
60+
// explicitly declares that privileged instructions are available.
61+
#[cfg(portable_atomic_unsafe_assume_privileged)]
62+
let interrupt_state = interrupt::disable();
63+
5764
let mut backoff = Backoff::new();
5865
loop {
5966
let previous = self.state.swap(LOCKED, Ordering::Acquire);
6067

6168
if previous != LOCKED {
6269
atomic::fence(Ordering::Release);
6370

64-
return SeqLockWriteGuard { lock: self, state: previous };
71+
return SeqLockWriteGuard {
72+
lock: self,
73+
state: previous,
74+
#[cfg(portable_atomic_unsafe_assume_privileged)]
75+
interrupt_state,
76+
};
6577
}
6678

6779
while self.state.load(Ordering::Relaxed) == LOCKED {
@@ -79,6 +91,10 @@ pub(super) struct SeqLockWriteGuard<'a> {
7991

8092
/// The stamp before locking.
8193
state: State,
94+
95+
/// The interrupt state before disabling.
96+
#[cfg(portable_atomic_unsafe_assume_privileged)]
97+
interrupt_state: interrupt::State,
8298
}
8399

84100
impl SeqLockWriteGuard<'_> {
@@ -93,6 +109,13 @@ impl SeqLockWriteGuard<'_> {
93109
//
94110
// Release ordering for synchronizing with `optimistic_read`.
95111
this.lock.state.store(this.state, Ordering::Release);
112+
113+
// Restore interrupt state.
114+
// SAFETY: the state was retrieved by the previous `disable`.
115+
#[cfg(portable_atomic_unsafe_assume_privileged)]
116+
unsafe {
117+
interrupt::restore(this.interrupt_state);
118+
}
96119
}
97120
}
98121

@@ -103,6 +126,13 @@ impl Drop for SeqLockWriteGuard<'_> {
103126
//
104127
// Release ordering for synchronizing with `optimistic_read`.
105128
self.lock.state.store(self.state.wrapping_add(2), Ordering::Release);
129+
130+
// Restore interrupt state.
131+
// SAFETY: the state was retrieved by the previous `disable`.
132+
#[cfg(portable_atomic_unsafe_assume_privileged)]
133+
unsafe {
134+
interrupt::restore(self.interrupt_state);
135+
}
106136
}
107137
}
108138

src/imp/fallback/seq_lock_wide.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ use core::{
88
};
99

1010
use super::utils::Backoff;
11+
#[cfg(portable_atomic_unsafe_assume_privileged)]
12+
use crate::imp::interrupt::arch as interrupt;
1113

1214
// See mod.rs for details.
1315
pub(super) type AtomicChunk = AtomicU32;
@@ -85,6 +87,11 @@ impl SeqLock {
8587
/// Grabs the lock for writing.
8688
#[inline]
8789
pub(super) fn write(&self) -> SeqLockWriteGuard<'_> {
90+
// Get current interrupt state and disable interrupts when the user
91+
// explicitly declares that privileged instructions are available.
92+
#[cfg(portable_atomic_unsafe_assume_privileged)]
93+
let interrupt_state = interrupt::disable();
94+
8895
let mut backoff = Backoff::new();
8996
loop {
9097
let previous = self.state_lo.swap(LOCKED, Ordering::Acquire);
@@ -94,7 +101,12 @@ impl SeqLock {
94101
// the data at the critical section of `(state_hi, previous)`.
95102
atomic::fence(Ordering::Release);
96103

97-
return SeqLockWriteGuard { lock: self, state_lo: previous };
104+
return SeqLockWriteGuard {
105+
lock: self,
106+
state_lo: previous,
107+
#[cfg(portable_atomic_unsafe_assume_privileged)]
108+
interrupt_state,
109+
};
98110
}
99111

100112
while self.state_lo.load(Ordering::Relaxed) == LOCKED {
@@ -112,6 +124,10 @@ pub(super) struct SeqLockWriteGuard<'a> {
112124

113125
/// The stamp before locking.
114126
state_lo: State,
127+
128+
/// The interrupt state before disabling.
129+
#[cfg(portable_atomic_unsafe_assume_privileged)]
130+
interrupt_state: interrupt::State,
115131
}
116132

117133
impl SeqLockWriteGuard<'_> {
@@ -126,6 +142,13 @@ impl SeqLockWriteGuard<'_> {
126142
//
127143
// Release ordering for synchronizing with `optimistic_read`.
128144
this.lock.state_lo.store(this.state_lo, Ordering::Release);
145+
146+
// Restore interrupt state.
147+
// SAFETY: the state was retrieved by the previous `disable`.
148+
#[cfg(portable_atomic_unsafe_assume_privileged)]
149+
unsafe {
150+
interrupt::restore(this.interrupt_state);
151+
}
129152
}
130153
}
131154

@@ -146,6 +169,13 @@ impl Drop for SeqLockWriteGuard<'_> {
146169
//
147170
// Release ordering for synchronizing with `optimistic_read`.
148171
self.lock.state_lo.store(state_lo, Ordering::Release);
172+
173+
// Restore interrupt state.
174+
// SAFETY: the state was retrieved by the previous `disable`.
175+
#[cfg(portable_atomic_unsafe_assume_privileged)]
176+
unsafe {
177+
interrupt::restore(self.interrupt_state);
178+
}
149179
}
150180
}
151181

0 commit comments

Comments
 (0)