atomic_maybe_uninit/arch/x86.rs
1// SPDX-License-Identifier: Apache-2.0 OR MIT
2
3/*
4x86 and x86_64
5
6Refs:
7- IntelĀ® 64 and IA-32 Architectures Software Developer Manuals
8 https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html
9- x86 and amd64 instruction reference
10 https://www.felixcloutier.com/x86
11- portable-atomic
12 https://github.com/taiki-e/portable-atomic
13
14See tests/asm-test/asm/atomic-maybe-uninit for generated assembly.
15*/
16
17#[cfg(target_arch = "x86_64")]
18#[cfg(target_feature = "cmpxchg16b")]
19#[cfg(not(atomic_maybe_uninit_no_outline_atomics))]
20#[cfg(not(target_env = "sgx"))]
21#[cfg_attr(
22 not(test),
23 cfg(not(any(
24 target_feature = "avx",
25 all(
26 not(target_feature = "avx"),
27 any(
28 atomic_maybe_uninit_no_outline_atomics,
29 target_env = "sgx",
30 not(target_feature = "sse"),
31 ),
32 ),
33 )))
34)]
35#[path = "../detect/x86_64.rs"]
36mod detect;
37
38delegate_size!(delegate_load_store);
39delegate_size!(delegate_swap);
40#[cfg(not(all(target_arch = "x86", atomic_maybe_uninit_no_cmpxchg)))]
41delegate_size!(delegate_cas);
42
43#[cfg(target_arch = "x86")]
44#[cfg(not(atomic_maybe_uninit_no_cmpxchg8b))]
45#[cfg(all(target_feature = "sse", not(atomic_maybe_uninit_test_prefer_x87_over_sse)))]
46use core::arch::x86::__m128;
47#[cfg(target_arch = "x86")]
48#[cfg(not(atomic_maybe_uninit_no_cmpxchg8b))]
49#[cfg(all(target_feature = "sse2", not(atomic_maybe_uninit_test_prefer_x87_over_sse)))]
50use core::arch::x86::__m128i;
51#[cfg(target_arch = "x86_64")]
52#[cfg(target_feature = "cmpxchg16b")]
53#[cfg(not(all(
54 not(target_feature = "avx"),
55 any(atomic_maybe_uninit_no_outline_atomics, target_env = "sgx", not(target_feature = "sse")),
56)))]
57use core::arch::x86_64::__m128i;
58use core::{
59 arch::asm,
60 mem::{self, MaybeUninit},
61 sync::atomic::Ordering,
62};
63
64#[cfg(not(all(target_arch = "x86", atomic_maybe_uninit_no_cmpxchg)))]
65use crate::raw::AtomicCompareExchange;
66use crate::raw::{AtomicLoad, AtomicStore, AtomicSwap};
67#[cfg(target_arch = "x86")]
68#[cfg(not(atomic_maybe_uninit_no_cmpxchg8b))]
69use crate::utils::{MaybeUninit64, Pair};
70#[cfg(target_arch = "x86_64")]
71#[cfg(target_feature = "cmpxchg16b")]
72use crate::utils::{MaybeUninit128, Pair};
73
74#[cfg(target_pointer_width = "32")]
75macro_rules! ptr_modifier {
76 () => {
77 ":e"
78 };
79}
80#[cfg(target_pointer_width = "64")]
81macro_rules! ptr_modifier {
82 () => {
83 ""
84 };
85}
86
87// -----------------------------------------------------------------------------
88// Register-width or smaller atomics
89
90macro_rules! atomic {
91 (
92 $ty:ident, $val_reg:ident, $ux_reg:ident, $ux:ident,
93 $zx:literal, $val_modifier:literal, $reg_val_modifier:tt, $zx_val_modifier:tt, $ptr_size:tt,
94 $cmpxchg_cmp_reg:tt
95 ) => {
96 #[cfg(target_arch = "x86")]
97 atomic!($ty, $val_reg, $ux_reg, reg_abcd, $ux, $zx, $val_modifier,
98 $reg_val_modifier, $zx_val_modifier, $ptr_size, $cmpxchg_cmp_reg);
99 #[cfg(target_arch = "x86_64")]
100 atomic!($ty, $val_reg, $ux_reg, reg, $ux, $zx, $val_modifier,
101 $reg_val_modifier, $zx_val_modifier, $ptr_size, $cmpxchg_cmp_reg);
102 };
103 (
104 $ty:ident, $val_reg:ident, $ux_reg:ident, $r_reg:ident, $ux:ident,
105 $zx:literal, $val_modifier:literal, $reg_val_modifier:tt, $zx_val_modifier:tt, $ptr_size:tt,
106 $cmpxchg_cmp_reg:tt
107 ) => {
108 delegate_signed!(delegate_load_store, $ty);
109 delegate_signed!(delegate_swap, $ty);
110 #[cfg(not(all(target_arch = "x86", atomic_maybe_uninit_no_cmpxchg)))]
111 delegate_signed!(delegate_cas, $ty);
112 impl AtomicLoad for $ty {
113 #[inline]
114 unsafe fn atomic_load(
115 src: *const MaybeUninit<Self>,
116 _order: Ordering,
117 ) -> MaybeUninit<Self> {
118 debug_assert_atomic_unsafe_precondition!(src, $ty);
119 let out;
120
121 // SAFETY: the caller must uphold the safety contract.
122 // load by MOV has SeqCst semantics.
123 unsafe {
124 asm!(
125 concat!("mov", $zx, " {out", $zx_val_modifier, "}, ", $ptr_size, " ptr [{src", ptr_modifier!(), "}]"), // atomic { out = zero_extend(*src) }
126 src = in(reg) src,
127 out = lateout(reg) out,
128 options(nostack, preserves_flags),
129 );
130 }
131 crate::utils::extend32::$ty::extract(out)
132 }
133 }
134 impl AtomicStore for $ty {
135 #[inline]
136 unsafe fn __atomic_store_impl(
137 dst: *mut MaybeUninit<Self>,
138 mut val: MaybeUninit<Self>,
139 order: Ordering,
140 ) {
141 debug_assert_atomic_unsafe_precondition!(dst, $ty);
142
143 // SAFETY: the caller must uphold the safety contract.
144 unsafe {
145 match order {
146 // Relaxed and Release stores are equivalent.
147 Ordering::Relaxed | Ordering::Release => {
148 asm!(
149 concat!("mov ", $ptr_size, " ptr [{dst", ptr_modifier!(), "}], {val", $val_modifier, "}"), // atomic { *dst = val }
150 dst = in(reg) dst,
151 val = in($val_reg) val,
152 options(nostack, preserves_flags),
153 );
154 }
155 #[allow(unused_assignments)] // TODO(gcc): Workaround for rustc_codegen_gcc bug
156 Ordering::SeqCst => {
157 asm!(
158 // SeqCst store is xchg, not mov
159 concat!("xchg ", $ptr_size, " ptr [{dst", ptr_modifier!(), "}], {val", $val_modifier, "}"), // atomic { _x = *dst; *dst = val; val = _x }
160 dst = in(reg) dst,
161 val = inout($val_reg) val,
162 options(nostack, preserves_flags),
163 );
164 }
165 _ => crate::utils::unreachable_unchecked(),
166 }
167 }
168 }
169 }
170 impl AtomicSwap for $ty {
171 #[inline]
172 unsafe fn __atomic_swap_impl(
173 dst: *mut MaybeUninit<Self>,
174 val: MaybeUninit<Self>,
175 _order: Ordering,
176 ) -> MaybeUninit<Self> {
177 debug_assert_atomic_unsafe_precondition!(dst, $ty);
178 let out: MaybeUninit<Self>;
179
180 // SAFETY: the caller must uphold the safety contract.
181 // XCHG has SeqCst semantics.
182 unsafe {
183 asm!(
184 concat!("xchg ", $ptr_size, " ptr [{dst", ptr_modifier!(), "}], {val", $val_modifier, "}"), // atomic { _x = *dst; *dst = val; val = _x }
185 dst = in(reg) dst,
186 val = inout($val_reg) val => out,
187 options(nostack, preserves_flags),
188 );
189 }
190 out
191 }
192 }
193 #[cfg(not(all(target_arch = "x86", atomic_maybe_uninit_no_cmpxchg)))]
194 impl AtomicCompareExchange for $ty {
195 #[inline]
196 unsafe fn __atomic_compare_exchange_impl(
197 dst: *mut MaybeUninit<Self>,
198 old: MaybeUninit<Self>,
199 new: MaybeUninit<Self>,
200 _success: Ordering,
201 _failure: Ordering,
202 ) -> (MaybeUninit<Self>, bool) {
203 debug_assert_atomic_unsafe_precondition!(dst, $ty);
204 let out: MaybeUninit<Self>;
205
206 // SAFETY: the caller must uphold the safety contract.
207 // CMPXCHG has SeqCst semantics.
208 //
209 // Refs: https://www.felixcloutier.com/x86/cmpxchg
210 unsafe {
211 let r: MaybeUninit<u32>;
212 asm!(
213 concat!("lock cmpxchg ", $ptr_size, " ptr [{dst", ptr_modifier!(), "}], {new", $reg_val_modifier, "}"), // atomic { if *dst == $cmpxchg_cmp_reg { ZF = 1; *dst = new } else { ZF = 0; $cmpxchg_cmp_reg = *dst } }
214 "sete {r:l}", // r = ZF
215 dst = in(reg) dst,
216 // Avoid reg_byte ($val_reg) in new and r to work around cranelift bug with multiple or lateout reg_byte.
217 new = in($ux_reg) crate::utils::extend32::$ty::$ux(new),
218 r = lateout($r_reg) r,
219 inout($cmpxchg_cmp_reg) old => out,
220 // Do not use `preserves_flags` because CMPXCHG modifies the ZF, CF, PF, AF, SF, and OF flags.
221 options(nostack),
222 );
223 let r = crate::utils::extend32::u8::extract(r).assume_init();
224 crate::utils::assert_unchecked(r == 0 || r == 1); // may help remove extra test
225 (out, r != 0)
226 }
227 }
228 }
229 };
230}
231
232#[cfg(target_arch = "x86")]
233atomic!(u8, reg_byte, reg_abcd, uninit, "zx", "", ":l", ":e", "byte", "al");
234#[cfg(target_arch = "x86_64")]
235atomic!(u8, reg_byte, reg, uninit, "zx", "", ":l", ":e", "byte", "al");
236atomic!(u16, reg, reg, identity, "zx", ":x", ":x", ":e", "word", "ax");
237atomic!(u32, reg, reg, identity, "", ":e", ":e", ":e", "dword", "eax");
238#[cfg(target_arch = "x86_64")]
239atomic!(u64, reg, reg, identity, "", "", "", "", "qword", "rax");
240
241// -----------------------------------------------------------------------------
242// 64-bit atomics on x86_32
243//
244// For load/store, we can use MOVQ(SSE2)/MOVLPS(SSE)/FILD&FISTP(x87) instead of CMPXCHG8B.
245// Refs: https://github.com/llvm/llvm-project/blob/llvmorg-22.1.0-rc1/llvm/test/CodeGen/X86/atomic-load-store-wide.ll
246
247#[cfg(all(target_arch = "x86", not(atomic_maybe_uninit_no_cmpxchg8b)))]
248delegate_signed!(delegate_all, u64);
249#[cfg(all(target_arch = "x86", not(atomic_maybe_uninit_no_cmpxchg8b)))]
250impl AtomicLoad for u64 {
251 #[inline]
252 unsafe fn atomic_load(src: *const MaybeUninit<Self>, _order: Ordering) -> MaybeUninit<Self> {
253 debug_assert_atomic_unsafe_precondition!(src, u64);
254
255 #[cfg(all(target_feature = "sse2", not(atomic_maybe_uninit_test_prefer_x87_over_sse)))]
256 // SAFETY: the caller must uphold the safety contract.
257 // cfg guarantees that the CPU supports SSE.
258 // load by MOVQ has SeqCst semantics.
259 //
260 // Refs:
261 // - https://www.felixcloutier.com/x86/movq (SSE2)
262 // - https://www.felixcloutier.com/x86/movd:movq (SSE2)
263 unsafe {
264 let out;
265 asm!(
266 "movq {out}, qword ptr [{src}]", // atomic { out[:] = *src }
267 src = in(reg) src,
268 out = out(xmm_reg) out,
269 options(nostack, preserves_flags),
270 );
271 mem::transmute::<MaybeUninit<__m128i>, [MaybeUninit<Self>; 2]>(out)[0]
272 }
273 #[cfg(all(
274 not(target_feature = "sse2"),
275 target_feature = "sse",
276 not(atomic_maybe_uninit_test_prefer_x87_over_sse),
277 ))]
278 // SAFETY: the caller must uphold the safety contract.
279 // cfg guarantees that the CPU supports SSE.
280 // load by MOVLPS has SeqCst semantics.
281 //
282 // Refs:
283 // - https://www.felixcloutier.com/x86/movlps (SSE)
284 unsafe {
285 let out;
286 asm!(
287 "movlps {out}, qword ptr [{src}]", // atomic { out[:] = *src }
288 src = in(reg) src,
289 out = out(xmm_reg) out,
290 options(nostack, preserves_flags),
291 );
292 mem::transmute::<MaybeUninit<__m128>, [MaybeUninit<Self>; 2]>(out)[0]
293 }
294 #[cfg(all(
295 any(not(target_feature = "sse"), atomic_maybe_uninit_test_prefer_x87_over_sse),
296 all(
297 any(target_feature = "x87", atomic_maybe_uninit_target_feature = "x87"),
298 not(atomic_maybe_uninit_test_prefer_cmpxchg8b_over_x87),
299 ),
300 ))]
301 // SAFETY: the caller must uphold the safety contract.
302 // load by FILD has SeqCst semantics.
303 //
304 // Refs:
305 // - https://www.felixcloutier.com/x86/fild
306 // - https://www.felixcloutier.com/x86/fist:fistp
307 unsafe {
308 let mut out = MaybeUninit::<Self>::uninit();
309 asm!(
310 "fild qword ptr [{src}]", // atomic { st.push(*src) }
311 "fistp qword ptr [{out}]", // *out = st.pop()
312 src = in(reg) src,
313 out = in(reg) out.as_mut_ptr(),
314 out("st(0)") _,
315 out("st(1)") _,
316 out("st(2)") _,
317 out("st(3)") _,
318 out("st(4)") _,
319 out("st(5)") _,
320 out("st(6)") _,
321 out("st(7)") _,
322 // Do not use `preserves_flags` because FILD and FISTP modify C1 in x87 FPU status word.
323 options(nostack),
324 );
325 out
326 }
327 #[cfg(all(
328 any(not(target_feature = "sse"), atomic_maybe_uninit_test_prefer_x87_over_sse),
329 not(all(
330 any(target_feature = "x87", atomic_maybe_uninit_target_feature = "x87"),
331 not(atomic_maybe_uninit_test_prefer_cmpxchg8b_over_x87),
332 )),
333 ))]
334 // SAFETY: the caller must uphold the safety contract.
335 // CMPXCHG8B has SeqCst semantics.
336 //
337 // Refs: https://www.felixcloutier.com/x86/cmpxchg8b:cmpxchg16b
338 unsafe {
339 let (out_lo, out_hi);
340 asm!(
341 "lock cmpxchg8b qword ptr [edi]", // atomic { if *edi == edx:eax { ZF = 1; *edi = ecx:ebx } else { ZF = 0; edx:eax = *edi } }
342 // set old/new args of CMPXCHG8B to 0
343 in("ebx") 0_u32,
344 in("ecx") 0_u32,
345 inout("eax") 0_u32 => out_lo,
346 inout("edx") 0_u32 => out_hi,
347 in("edi") src,
348 // Do not use `preserves_flags` because CMPXCHG8B modifies the ZF flag.
349 options(nostack),
350 );
351 MaybeUninit64 { pair: Pair { lo: out_lo, hi: out_hi } }.whole
352 }
353 }
354}
355#[cfg(all(target_arch = "x86", not(atomic_maybe_uninit_no_cmpxchg8b)))]
356impl AtomicStore for u64 {
357 #[inline]
358 unsafe fn __atomic_store_impl(
359 dst: *mut MaybeUninit<Self>,
360 val: MaybeUninit<Self>,
361 order: Ordering,
362 ) {
363 debug_assert_atomic_unsafe_precondition!(dst, u64);
364
365 #[cfg(all(target_feature = "sse", not(atomic_maybe_uninit_test_prefer_x87_over_sse)))]
366 // SAFETY: the caller must uphold the safety contract.
367 // cfg guarantees that the CPU supports SSE.
368 //
369 // Refs:
370 // - https://www.felixcloutier.com/x86/movlps (SSE)
371 // - https://www.felixcloutier.com/x86/lock
372 // - https://www.felixcloutier.com/x86/or
373 unsafe {
374 let val: MaybeUninit<__m128> = mem::transmute([val, MaybeUninit::uninit()]);
375 match order {
376 // Relaxed and Release stores are equivalent.
377 Ordering::Relaxed | Ordering::Release => {
378 asm!(
379 "movlps qword ptr [{dst}], {val}", // atomic { *dst = val[:] }
380 dst = in(reg) dst,
381 val = in(xmm_reg) val,
382 options(nostack, preserves_flags),
383 );
384 }
385 Ordering::SeqCst => {
386 let p = core::cell::UnsafeCell::new(MaybeUninit::<u32>::uninit());
387 asm!(
388 "movlps qword ptr [{dst}], {val}", // atomic { *dst = val[:] }
389 // Equivalent to `mfence`, but is up to 3.1x faster on Coffee Lake and up to 2.4x faster on Raptor Lake-H at least in simple cases.
390 // - https://github.com/taiki-e/portable-atomic/pull/156
391 // - LLVM uses `lock or` https://godbolt.org/z/vv6rjzfYd
392 // - Windows uses `xchg` for x86_32 for MemoryBarrier https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-memorybarrier
393 // - MSVC STL uses `lock inc` https://github.com/microsoft/STL/pull/740
394 // - boost uses `lock or` https://github.com/boostorg/atomic/commit/559eba81af71386cedd99f170dc6101c6ad7bf22
395 "xchg dword ptr [{p}], {tmp}", // fence
396 dst = in(reg) dst,
397 val = in(xmm_reg) val,
398 p = inout(reg) p.get() => _,
399 tmp = lateout(reg) _,
400 options(nostack, preserves_flags),
401 );
402 }
403 _ => crate::utils::unreachable_unchecked(),
404 }
405 }
406 #[cfg(all(
407 any(not(target_feature = "sse"), atomic_maybe_uninit_test_prefer_x87_over_sse),
408 all(
409 any(target_feature = "x87", atomic_maybe_uninit_target_feature = "x87"),
410 not(atomic_maybe_uninit_test_prefer_cmpxchg8b_over_x87),
411 ),
412 ))]
413 // SAFETY: the caller must uphold the safety contract.
414 //
415 // Refs:
416 // - https://www.felixcloutier.com/x86/fild
417 // - https://www.felixcloutier.com/x86/fist:fistp
418 unsafe {
419 match order {
420 // Relaxed and Release stores are equivalent.
421 Ordering::Relaxed | Ordering::Release => {
422 asm!(
423 "fild qword ptr [{val}]", // st.push(*val)
424 "fistp qword ptr [{dst}]", // atomic { *dst = st.pop() }
425 dst = in(reg) dst,
426 val = in(reg) val.as_ptr(),
427 out("st(0)") _,
428 out("st(1)") _,
429 out("st(2)") _,
430 out("st(3)") _,
431 out("st(4)") _,
432 out("st(5)") _,
433 out("st(6)") _,
434 out("st(7)") _,
435 // Do not use `preserves_flags` because FILD and FISTP modify condition code flags in x87 FPU status word.
436 options(nostack),
437 );
438 }
439 Ordering::SeqCst => {
440 let p = core::cell::UnsafeCell::new(MaybeUninit::<u32>::uninit());
441 asm!(
442 "fild qword ptr [{val}]", // st.push(*val)
443 "fistp qword ptr [{dst}]", // atomic { *dst = st.pop() }
444 // Equivalent to `mfence`, but is up to 3.1x faster on Coffee Lake and up to 2.4x faster on Raptor Lake-H at least in simple cases.
445 // - https://github.com/taiki-e/portable-atomic/pull/156
446 // - LLVM uses `lock or` https://godbolt.org/z/vv6rjzfYd
447 // - Windows uses `xchg` for x86_32 for MemoryBarrier https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-memorybarrier
448 // - MSVC STL uses `lock inc` https://github.com/microsoft/STL/pull/740
449 // - boost uses `lock or` https://github.com/boostorg/atomic/commit/559eba81af71386cedd99f170dc6101c6ad7bf22
450 "xchg dword ptr [{p}], {tmp}", // fence
451 dst = in(reg) dst,
452 val = in(reg) val.as_ptr(),
453 p = inout(reg) p.get() => _,
454 tmp = lateout(reg) _,
455 out("st(0)") _,
456 out("st(1)") _,
457 out("st(2)") _,
458 out("st(3)") _,
459 out("st(4)") _,
460 out("st(5)") _,
461 out("st(6)") _,
462 out("st(7)") _,
463 // Do not use `preserves_flags` because FILD and FISTP modify condition code flags in x87 FPU status word.
464 options(nostack),
465 );
466 }
467 _ => crate::utils::unreachable_unchecked(),
468 }
469 }
470 #[cfg(all(
471 any(not(target_feature = "sse"), atomic_maybe_uninit_test_prefer_x87_over_sse),
472 not(all(
473 any(target_feature = "x87", atomic_maybe_uninit_target_feature = "x87"),
474 not(atomic_maybe_uninit_test_prefer_cmpxchg8b_over_x87),
475 )),
476 ))]
477 // SAFETY: the caller must uphold the safety contract.
478 unsafe {
479 // CMPXCHG8B has SeqCst semantics.
480 let _ = order;
481 <Self as AtomicSwap>::__atomic_swap_impl(dst, val, Ordering::SeqCst);
482 }
483 }
484}
485#[cfg(all(target_arch = "x86", not(atomic_maybe_uninit_no_cmpxchg8b)))]
486impl AtomicSwap for u64 {
487 #[inline]
488 unsafe fn __atomic_swap_impl(
489 dst: *mut MaybeUninit<Self>,
490 val: MaybeUninit<Self>,
491 _order: Ordering,
492 ) -> MaybeUninit<Self> {
493 debug_assert_atomic_unsafe_precondition!(dst, u64);
494 let val = MaybeUninit64 { whole: val };
495 let (mut prev_lo, mut prev_hi);
496
497 // SAFETY: the caller must uphold the safety contract.
498 // CMPXCHG8B has SeqCst semantics.
499 //
500 // Refs: https://www.felixcloutier.com/x86/cmpxchg8b:cmpxchg16b
501 unsafe {
502 asm!(
503 // This is based on the code generated for the first load in DW RMWs by LLVM,
504 // but it is interesting that they generate code that does mixed-sized atomic access.
505 //
506 // This is not single-copy atomic reads, but this is ok because subsequent
507 // CAS will check for consistency.
508 "mov eax, dword ptr [edi]", // atomic { eax = *edi }
509 "mov edx, dword ptr [edi + 4]", // atomic { edx = *edi.byte_add(4) }
510 "2:", // 'retry:
511 "lock cmpxchg8b qword ptr [edi]", // atomic { if *edi == edx:eax { ZF = 1; *edi = ecx:ebx } else { ZF = 0; edx:eax = *edi } }
512 "jne 2b", // if ZF == 0 { jump 'retry }
513 in("ebx") val.pair.lo,
514 in("ecx") val.pair.hi,
515 out("eax") prev_lo,
516 out("edx") prev_hi,
517 in("edi") dst,
518 // Do not use `preserves_flags` because CMPXCHG8B modifies the ZF flag.
519 options(nostack),
520 );
521 MaybeUninit64 { pair: Pair { lo: prev_lo, hi: prev_hi } }.whole
522 }
523 }
524}
525#[cfg(all(target_arch = "x86", not(atomic_maybe_uninit_no_cmpxchg8b)))]
526impl AtomicCompareExchange for u64 {
527 #[inline]
528 unsafe fn __atomic_compare_exchange_impl(
529 dst: *mut MaybeUninit<Self>,
530 old: MaybeUninit<Self>,
531 new: MaybeUninit<Self>,
532 _success: Ordering,
533 _failure: Ordering,
534 ) -> (MaybeUninit<Self>, bool) {
535 debug_assert_atomic_unsafe_precondition!(dst, u64);
536 let old = MaybeUninit64 { whole: old };
537 let new = MaybeUninit64 { whole: new };
538 let (prev_lo, prev_hi);
539 let r: u8;
540
541 // SAFETY: the caller must uphold the safety contract.
542 // CMPXCHG8B has SeqCst semantics.
543 //
544 // Refs: https://www.felixcloutier.com/x86/cmpxchg8b:cmpxchg16b
545 unsafe {
546 asm!(
547 "lock cmpxchg8b qword ptr [edi]", // atomic { if *edi == edx:eax { ZF = 1; *edi = ecx:ebx } else { ZF = 0; edx:eax = *edi } }
548 "sete cl", // cl = ZF
549 in("ebx") new.pair.lo,
550 in("ecx") new.pair.hi,
551 inout("eax") old.pair.lo => prev_lo,
552 inout("edx") old.pair.hi => prev_hi,
553 in("edi") dst,
554 lateout("cl") r,
555 // Do not use `preserves_flags` because CMPXCHG8B modifies the ZF flag.
556 options(nostack),
557 );
558 crate::utils::assert_unchecked(r == 0 || r == 1); // may help remove extra test
559 (MaybeUninit64 { pair: Pair { lo: prev_lo, hi: prev_hi } }.whole, r != 0)
560 }
561 }
562}
563
564// -----------------------------------------------------------------------------
565// 128-bit atomics on x86_64
566
567#[cfg(target_arch = "x86_64")]
568#[cfg(target_feature = "cmpxchg16b")]
569macro_rules! atomic128 {
570 () => {
571 // rdi and rsi are call-preserved on Windows.
572 #[cfg(not(windows))]
573 #[cfg(target_pointer_width = "32")]
574 atomic128!("edi", "esi", "rsi");
575 #[cfg(not(windows))]
576 #[cfg(target_pointer_width = "64")]
577 atomic128!("rdi", "rsi", "rsi");
578 #[cfg(windows)]
579 #[cfg(target_pointer_width = "32")]
580 atomic128!("r9d", "r11d", "r8");
581 #[cfg(windows)]
582 #[cfg(target_pointer_width = "64")]
583 atomic128!("r9", "r11", "r8");
584 };
585 ($dst:tt, $cas_dst:tt, $save:tt) => {
586 delegate_signed!(delegate_all, u128);
587 impl AtomicLoad for u128 {
588 #[inline]
589 unsafe fn atomic_load(
590 src: *const MaybeUninit<Self>,
591 _order: Ordering,
592 ) -> MaybeUninit<Self> {
593 // VMOVDQA is atomic when AVX is available.
594 // See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104688 for details.
595 //
596 // Refs: https://www.felixcloutier.com/x86/movdqa:vmovdqa32:vmovdqa64
597 #[cfg(not(all(
598 not(target_feature = "avx"),
599 any(atomic_maybe_uninit_no_outline_atomics, target_env = "sgx", not(target_feature = "sse")),
600 )))]
601 #[target_feature(enable = "avx")]
602 #[inline]
603 unsafe fn atomic_load_avx(
604 src: *const MaybeUninit<u128>,
605 ) -> MaybeUninit<u128> {
606 // SAFETY: the caller must guarantee that `src` is valid for reads,
607 // 16-byte aligned, and that there are no concurrent non-atomic operations.
608 // load by VMOVDQA has SeqCst semantics.
609 unsafe {
610 let out;
611 asm!(
612 concat!("vmovdqa {out}, xmmword ptr [{src", ptr_modifier!(), "}]"), // atomic { out = *src }
613 src = in(reg) src,
614 out = lateout(xmm_reg) out,
615 options(nostack, preserves_flags),
616 );
617 mem::transmute::<MaybeUninit<__m128i>, MaybeUninit<u128>>(out)
618 }
619 }
620 #[cfg(not(target_feature = "avx"))]
621 #[inline]
622 unsafe fn atomic_load_cmpxchg16b(
623 src: *const MaybeUninit<u128>,
624 ) -> MaybeUninit<u128> {
625 // SAFETY: the caller must guarantee that `src` is valid for both writes and
626 // reads, 16-byte aligned, and that there are no concurrent non-atomic operations.
627 // CMPXCHG16B has SeqCst semantics.
628 //
629 // Refs: https://www.felixcloutier.com/x86/cmpxchg8b:cmpxchg16b
630 unsafe {
631 let (out_lo, out_hi);
632 asm!(
633 concat!("mov ", $save, ", rbx"), // save rbx which is reserved by LLVM
634 "xor rbx, rbx", // zeroed rbx
635 concat!("lock cmpxchg16b xmmword ptr [", $dst, "]"), // atomic { if *$rdi == rdx:rax { ZF = 1; *$rdi = rcx:rbx } else { ZF = 0; rdx:rax = *$rdi } }
636 concat!("mov rbx, ", $save), // restore rbx
637 // set old/new args of CMPXCHG16B to 0 (rbx is zeroed after saved to rbx_tmp, to avoid xchg)
638 out($save) _,
639 in("rcx") 0_u64,
640 inout("rax") 0_u64 => out_lo,
641 inout("rdx") 0_u64 => out_hi,
642 in($dst) src,
643 // Do not use `preserves_flags` because CMPXCHG16B modifies the ZF flag.
644 options(nostack),
645 );
646 MaybeUninit128 { pair: Pair { lo: out_lo, hi: out_hi } }.whole
647 }
648 }
649 debug_assert_atomic_unsafe_precondition!(src, u128);
650
651 #[cfg(target_feature = "avx")]
652 // SAFETY: the caller must uphold the safety contract.
653 // cfg guarantees that the CPU supports AVX.
654 unsafe {
655 atomic_load_avx(src)
656 }
657 #[cfg(not(target_feature = "avx"))]
658 #[cfg(not(all(
659 not(target_feature = "avx"),
660 any(atomic_maybe_uninit_no_outline_atomics, target_env = "sgx", not(target_feature = "sse")),
661 )))]
662 // SAFETY: the caller must uphold the safety contract.
663 // cfg guarantees that the CPU supports CMPXCHG16B.
664 unsafe {
665 ifunc!(unsafe fn(src: *const MaybeUninit<u128>) -> MaybeUninit<u128> {
666 if detect::detect().avx() {
667 atomic_load_avx
668 } else {
669 atomic_load_cmpxchg16b
670 }
671 })
672 }
673 #[cfg(not(target_feature = "avx"))]
674 #[cfg(all(
675 not(target_feature = "avx"),
676 any(atomic_maybe_uninit_no_outline_atomics, target_env = "sgx", not(target_feature = "sse")),
677 ))]
678 // SAFETY: the caller must uphold the safety contract.
679 // cfg guarantees that the CPU supports CMPXCHG16B.
680 unsafe {
681 atomic_load_cmpxchg16b(src)
682 }
683 }
684 }
685 impl AtomicStore for u128 {
686 #[inline]
687 unsafe fn __atomic_store_impl(
688 dst: *mut MaybeUninit<Self>,
689 val: MaybeUninit<Self>,
690 order: Ordering,
691 ) {
692 // VMOVDQA is atomic when AVX is available.
693 // See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104688 for details.
694 //
695 // Refs: https://www.felixcloutier.com/x86/movdqa:vmovdqa32:vmovdqa64
696 #[cfg(not(all(
697 not(target_feature = "avx"),
698 any(atomic_maybe_uninit_no_outline_atomics, target_env = "sgx", not(target_feature = "sse")),
699 )))]
700 #[target_feature(enable = "avx")]
701 #[inline]
702 unsafe fn atomic_store_avx(
703 dst: *mut MaybeUninit<u128>,
704 val: MaybeUninit<u128>,
705 order: Ordering,
706 ) {
707 // SAFETY: the caller must guarantee that `dst` is valid for writes,
708 // 16-byte aligned, and that there are no concurrent non-atomic operations.
709 // cfg guarantees that the CPU supports AVX.
710 unsafe {
711 let val: MaybeUninit<__m128i> = mem::transmute(val);
712 match order {
713 // Relaxed and Release stores are equivalent.
714 Ordering::Relaxed | Ordering::Release => {
715 asm!(
716 concat!("vmovdqa xmmword ptr [{dst", ptr_modifier!(), "}], {val}"), // atomic { *dst = val }
717 dst = in(reg) dst,
718 val = in(xmm_reg) val,
719 options(nostack, preserves_flags),
720 );
721 }
722 Ordering::SeqCst => {
723 let p = core::cell::UnsafeCell::new(MaybeUninit::<u64>::uninit());
724 asm!(
725 concat!("vmovdqa xmmword ptr [{dst", ptr_modifier!(), "}], {val}"), // atomic { *dst = val }
726 // Equivalent to `mfence`, but is up to 3.1x faster on Coffee Lake and up to 2.4x faster on Raptor Lake-H at least in simple cases.
727 // - https://github.com/taiki-e/portable-atomic/pull/156
728 // - LLVM uses `lock or` https://godbolt.org/z/vv6rjzfYd
729 // - Windows uses `xchg` for x86_32 for MemoryBarrier https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-memorybarrier
730 // - MSVC STL uses `lock inc` https://github.com/microsoft/STL/pull/740
731 // - boost uses `lock or` https://github.com/boostorg/atomic/commit/559eba81af71386cedd99f170dc6101c6ad7bf22
732 concat!("xchg qword ptr [{p", ptr_modifier!(), "}], {tmp}"), // fence
733 dst = in(reg) dst,
734 val = in(xmm_reg) val,
735 p = in(reg) p.get(),
736 tmp = out(reg) _,
737 options(nostack, preserves_flags),
738 );
739 }
740 _ => crate::utils::unreachable_unchecked(),
741 }
742 }
743 }
744 #[cfg(not(target_feature = "avx"))]
745 #[inline]
746 unsafe fn atomic_store_cmpxchg16b(
747 dst: *mut MaybeUninit<u128>,
748 val: MaybeUninit<u128>,
749 ) {
750 // SAFETY: the caller must uphold the safety contract.
751 unsafe {
752 // CMPXCHG16B has SeqCst semantics.
753 <u128 as AtomicSwap>::__atomic_swap_impl(dst, val, Ordering::SeqCst);
754 }
755 }
756 debug_assert_atomic_unsafe_precondition!(dst, u128);
757
758 #[cfg(target_feature = "avx")]
759 // SAFETY: the caller must uphold the safety contract.
760 // cfg guarantees that the CPU supports AVX.
761 unsafe {
762 atomic_store_avx(dst, val, order);
763 }
764 #[cfg(not(target_feature = "avx"))]
765 #[cfg(not(all(
766 not(target_feature = "avx"),
767 any(atomic_maybe_uninit_no_outline_atomics, target_env = "sgx", not(target_feature = "sse")),
768 )))]
769 // SAFETY: the caller must uphold the safety contract.
770 // cfg guarantees that the CPU supports CMPXCHG16B.
771 unsafe {
772 fn_alias! {
773 #[target_feature(enable = "avx")]
774 unsafe fn(dst: *mut MaybeUninit<u128>, val: MaybeUninit<u128>);
775 // atomic store by vmovdqa has at least release semantics.
776 atomic_store_avx_non_seqcst = atomic_store_avx(Ordering::Release);
777 atomic_store_avx_seqcst = atomic_store_avx(Ordering::SeqCst);
778 }
779 match order {
780 // Relaxed and Release stores are equivalent in all implementations
781 // that may be called here.
782 Ordering::Relaxed | Ordering::Release => {
783 ifunc!(unsafe fn(dst: *mut MaybeUninit<u128>, val: MaybeUninit<u128>) {
784 if detect::detect().avx() {
785 atomic_store_avx_non_seqcst
786 } else {
787 atomic_store_cmpxchg16b
788 }
789 });
790 }
791 Ordering::SeqCst => {
792 ifunc!(unsafe fn(dst: *mut MaybeUninit<u128>, val: MaybeUninit<u128>) {
793 if detect::detect().avx() {
794 atomic_store_avx_seqcst
795 } else {
796 atomic_store_cmpxchg16b
797 }
798 });
799 }
800 _ => crate::utils::unreachable_unchecked(),
801 }
802 }
803 #[cfg(not(target_feature = "avx"))]
804 #[cfg(all(
805 not(target_feature = "avx"),
806 any(atomic_maybe_uninit_no_outline_atomics, target_env = "sgx", not(target_feature = "sse")),
807 ))]
808 // SAFETY: the caller must uphold the safety contract.
809 // cfg guarantees that the CPU supports CMPXCHG16B.
810 unsafe {
811 // CMPXCHG16B has SeqCst semantics.
812 let _ = order;
813 atomic_store_cmpxchg16b(dst, val);
814 }
815 }
816 }
817 impl AtomicSwap for u128 {
818 #[inline]
819 unsafe fn __atomic_swap_impl(
820 dst: *mut MaybeUninit<Self>,
821 val: MaybeUninit<Self>,
822 _order: Ordering,
823 ) -> MaybeUninit<Self> {
824 debug_assert_atomic_unsafe_precondition!(dst, u128);
825 let val = MaybeUninit128 { whole: val };
826 let (mut prev_lo, mut prev_hi);
827
828 // SAFETY: the caller must guarantee that `dst` is valid for both writes and
829 // reads, 16-byte aligned, and that there are no concurrent non-atomic operations.
830 // cfg guarantees that the CPU supports CMPXCHG16B.
831 // CMPXCHG16B has SeqCst semantics.
832 //
833 // Refs: https://www.felixcloutier.com/x86/cmpxchg8b:cmpxchg16b
834 unsafe {
835 asm!(
836 concat!("xchg ", $save, ", rbx"), // save rbx which is reserved by LLVM
837 // This is based on the code generated for the first load in DW RMWs by LLVM,
838 // but it is interesting that they generate code that does mixed-sized atomic access.
839 //
840 // This is not single-copy atomic reads, but this is ok because subsequent
841 // CAS will check for consistency.
842 concat!("mov rax, qword ptr [", $dst, "]"), // atomic { rax = *$rdi }
843 concat!("mov rdx, qword ptr [", $dst, " + 8]"), // atomic { rdx = *$rdi.byte_add(8) }
844 "2:", // 'retry:
845 concat!("lock cmpxchg16b xmmword ptr [", $dst, "]"), // atomic { if *$rdi == rdx:rax { ZF = 1; *$rdi = rcx:rbx } else { ZF = 0; rdx:rax = *$rdi } }
846 "jne 2b", // if ZF == 0 { jump 'retry }
847 concat!("mov rbx, ", $save), // restore rbx
848 inout($save) val.pair.lo => _,
849 in("rcx") val.pair.hi,
850 out("rax") prev_lo,
851 out("rdx") prev_hi,
852 in($dst) dst,
853 // Do not use `preserves_flags` because CMPXCHG16B modifies the ZF flag.
854 options(nostack),
855 );
856 MaybeUninit128 { pair: Pair { lo: prev_lo, hi: prev_hi } }.whole
857 }
858 }
859 }
860 impl AtomicCompareExchange for u128 {
861 #[inline]
862 unsafe fn __atomic_compare_exchange_impl(
863 dst: *mut MaybeUninit<Self>,
864 old: MaybeUninit<Self>,
865 new: MaybeUninit<Self>,
866 _success: Ordering,
867 _failure: Ordering,
868 ) -> (MaybeUninit<Self>, bool) {
869 debug_assert_atomic_unsafe_precondition!(dst, u128);
870 let old = MaybeUninit128 { whole: old };
871 let new = MaybeUninit128 { whole: new };
872 let (prev_lo, prev_hi);
873 let r: u8;
874
875 // SAFETY: the caller must guarantee that `dst` is valid for both writes and
876 // reads, 16-byte aligned, and that there are no concurrent non-atomic operations.
877 // cfg guarantees that the CPU supports CMPXCHG16B.
878 // CMPXCHG16B has SeqCst semantics.
879 //
880 // Refs: https://www.felixcloutier.com/x86/cmpxchg8b:cmpxchg16b
881 unsafe {
882 asm!(
883 "xchg r8, rbx", // save rbx which is reserved by LLVM
884 concat!("lock cmpxchg16b xmmword ptr [", $cas_dst, "]"), // atomic { if *$rdi == rdx:rax { ZF = 1; *$rdi = rcx:rbx } else { ZF = 0; rdx:rax = *$rdi } }
885 "sete cl", // cl = ZF
886 "mov rbx, r8", // restore rbx
887 inout("r8") new.pair.lo => _,
888 in("rcx") new.pair.hi,
889 inout("rax") old.pair.lo => prev_lo,
890 inout("rdx") old.pair.hi => prev_hi,
891 in($cas_dst) dst,
892 lateout("cl") r,
893 // Do not use `preserves_flags` because CMPXCHG16B modifies the ZF flag.
894 options(nostack),
895 );
896 crate::utils::assert_unchecked(r == 0 || r == 1); // may help remove extra test
897 (
898 MaybeUninit128 { pair: Pair { lo: prev_lo, hi: prev_hi } }.whole,
899 r != 0
900 )
901 }
902 }
903 }
904 };
905}
906
907#[cfg(target_arch = "x86_64")]
908#[cfg(target_feature = "cmpxchg16b")]
909atomic128!();
910
911// -----------------------------------------------------------------------------
912// cfg macros
913
914#[macro_export]
915macro_rules! cfg_has_atomic_8 {
916 ($($tt:tt)*) => { $($tt)* };
917}
918#[macro_export]
919macro_rules! cfg_no_atomic_8 {
920 ($($tt:tt)*) => {};
921}
922#[macro_export]
923macro_rules! cfg_has_atomic_16 {
924 ($($tt:tt)*) => { $($tt)* };
925}
926#[macro_export]
927macro_rules! cfg_no_atomic_16 {
928 ($($tt:tt)*) => {};
929}
930#[macro_export]
931macro_rules! cfg_has_atomic_32 {
932 ($($tt:tt)*) => { $($tt)* };
933}
934#[macro_export]
935macro_rules! cfg_no_atomic_32 {
936 ($($tt:tt)*) => {};
937}
938#[cfg(not(all(target_arch = "x86", atomic_maybe_uninit_no_cmpxchg8b)))]
939#[macro_export]
940macro_rules! cfg_has_atomic_64 {
941 ($($tt:tt)*) => { $($tt)* };
942}
943#[cfg(not(all(target_arch = "x86", atomic_maybe_uninit_no_cmpxchg8b)))]
944#[macro_export]
945macro_rules! cfg_no_atomic_64 {
946 ($($tt:tt)*) => {};
947}
948#[cfg(all(target_arch = "x86", atomic_maybe_uninit_no_cmpxchg8b))]
949#[macro_export]
950macro_rules! cfg_has_atomic_64 {
951 ($($tt:tt)*) => {};
952}
953#[cfg(all(target_arch = "x86", atomic_maybe_uninit_no_cmpxchg8b))]
954#[macro_export]
955macro_rules! cfg_no_atomic_64 {
956 ($($tt:tt)*) => { $($tt)* };
957}
958#[cfg(not(all(target_arch = "x86_64", target_feature = "cmpxchg16b")))]
959#[macro_export]
960macro_rules! cfg_has_atomic_128 {
961 ($($tt:tt)*) => {};
962}
963#[cfg(not(all(target_arch = "x86_64", target_feature = "cmpxchg16b")))]
964#[macro_export]
965macro_rules! cfg_no_atomic_128 {
966 ($($tt:tt)*) => { $($tt)* };
967}
968#[cfg(all(target_arch = "x86_64", target_feature = "cmpxchg16b"))]
969#[macro_export]
970macro_rules! cfg_has_atomic_128 {
971 ($($tt:tt)*) => { $($tt)* };
972}
973#[cfg(all(target_arch = "x86_64", target_feature = "cmpxchg16b"))]
974#[macro_export]
975macro_rules! cfg_no_atomic_128 {
976 ($($tt:tt)*) => {};
977}
978#[cfg(not(all(target_arch = "x86", atomic_maybe_uninit_no_cmpxchg)))]
979#[macro_export]
980macro_rules! cfg_has_atomic_cas {
981 ($($tt:tt)*) => { $($tt)* };
982}
983#[cfg(not(all(target_arch = "x86", atomic_maybe_uninit_no_cmpxchg)))]
984#[macro_export]
985macro_rules! cfg_no_atomic_cas {
986 ($($tt:tt)*) => {};
987}
988#[cfg(all(target_arch = "x86", atomic_maybe_uninit_no_cmpxchg))]
989#[macro_export]
990macro_rules! cfg_has_atomic_cas {
991 ($($tt:tt)*) => {};
992}
993#[cfg(all(target_arch = "x86", atomic_maybe_uninit_no_cmpxchg))]
994#[macro_export]
995macro_rules! cfg_no_atomic_cas {
996 ($($tt:tt)*) => { $($tt)* };
997}