swym_htm/
lib.rs

1//! Hardware transactional memory primitives
2
3#![feature(core_intrinsics)]
4#![feature(link_llvm_intrinsics)]
5#![feature(test)]
6#![warn(missing_docs)]
7
8extern crate test;
9
10#[cfg_attr(
11    all(target_arch = "powerpc64", feature = "htm"),
12    path = "./powerpc64.rs"
13)]
14#[cfg_attr(all(target_arch = "x86_64", feature = "rtm"), path = "./x86_64.rs")]
15#[cfg_attr(
16    not(any(
17        all(target_arch = "x86_64", feature = "rtm"),
18        all(target_arch = "powerpc64", feature = "htm")
19    )),
20    path = "./unsupported.rs"
21)]
22pub mod back;
23
24use std::{
25    cell::UnsafeCell,
26    marker::PhantomData,
27    ops::{Deref, DerefMut},
28    sync::atomic::AtomicUsize,
29};
30
31/// Returns true if the platform may support hardware transactional memory.
32#[inline]
33pub const fn htm_supported() -> bool {
34    back::htm_supported()
35}
36
37/// Returns true if the platform definitely supports hardware transactional memory.
38#[inline]
39pub fn htm_supported_runtime() -> bool {
40    back::htm_supported_runtime()
41}
42
43/// Attempts to begin a hardware transaction.
44///
45/// Control is returned to the point where begin was called on a failed transaction, only the
46/// [`BeginCode`] now contains the reason for the failure.
47///
48/// # Safety
49///
50/// It is unsafe to always retry the transaction after a failure. It is also unsafe to
51/// never subsequently call `end`.
52#[inline]
53pub unsafe fn begin() -> BeginCode {
54    BeginCode(back::begin())
55}
56
57/// Aborts an in progress hardware transaction.
58///
59/// # Safety
60///
61/// There must be an in progress hardware transaction.
62#[inline]
63pub unsafe fn abort() -> ! {
64    back::abort()
65}
66
67/// Tests the current transactional state of the thread.
68#[inline]
69pub unsafe fn test() -> TestCode {
70    TestCode(back::test())
71}
72
73/// Ends and commits an in progress hardware transaction.
74///
75/// # Safety
76///
77/// There must be an in progress hardware transaction.
78#[inline]
79pub unsafe fn end() {
80    back::end()
81}
82
83/// The result of calling `begin`.
84#[repr(transparent)]
85#[derive(PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Debug, Hash)]
86pub struct BeginCode(back::BeginCode);
87
88impl BeginCode {
89    /// Returns true if the `BeginCode` represents a successfully started transaction.
90    #[inline]
91    pub fn is_started(&self) -> bool {
92        self.0.is_started()
93    }
94
95    /// Returns true if the `BeginCode` represents a transaction that was explicitly [`abort`]ed.
96    #[inline]
97    pub fn is_explicit_abort(&self) -> bool {
98        self.0.is_explicit_abort()
99    }
100
101    /// Returns true if retrying the hardware transaction is suggested.
102    #[inline]
103    pub fn is_retry(&self) -> bool {
104        self.0.is_retry()
105    }
106
107    /// Returns true if the transaction aborted due to a memory conflict.
108    #[inline]
109    pub fn is_conflict(&self) -> bool {
110        self.0.is_conflict()
111    }
112
113    /// Returns true if the transaction aborted due to running out of capacity.
114    ///
115    /// Hardware transactions are typically bounded by L1 cache sizes.
116    #[inline]
117    pub fn is_capacity(&self) -> bool {
118        self.0.is_capacity()
119    }
120}
121
122/// The result of calling `test`.
123#[repr(transparent)]
124#[derive(PartialEq, Eq, Ord, PartialOrd, Copy, Clone, Debug, Hash)]
125pub struct TestCode(back::TestCode);
126
127impl TestCode {
128    /// Returns true if the current thread is in a hardware transaction.
129    #[inline]
130    pub fn in_transaction(&self) -> bool {
131        self.0.in_transaction()
132    }
133
134    /// Returns true if the current thread is in a suspended hardware transaction.
135    #[inline]
136    pub fn is_suspended(&self) -> bool {
137        self.0.is_suspended()
138    }
139}
140
141/// A hardware memory transaction.
142///
143/// On drop, the transaction is committed.
144#[derive(Debug)]
145pub struct HardwareTx {
146    _private: PhantomData<*mut ()>,
147}
148
149impl Drop for HardwareTx {
150    #[inline]
151    fn drop(&mut self) {
152        unsafe { end() }
153    }
154}
155
156impl HardwareTx {
157    /// Starts a new hardware transaction.
158    ///
159    /// Takes a retry handler which is called on transaction abort. If the retry handler returns
160    /// `Ok(())`, the transaction is retried. Any `Err` is passed back to the location where `new`
161    /// was called.
162    ///
163    /// The retry handler is never called with [`BeginCode`]s where `code.is_started() == true`.
164    ///
165    /// # Safety
166    ///
167    /// It is unsafe to pass in a retry handler that never returns `Err`. It is also unsafe to leak
168    /// the transaction, unless [`end`] is manually called sometime after.
169    #[inline]
170    pub unsafe fn new<F, E>(mut retry_handler: F) -> Result<Self, E>
171    where
172        F: FnMut(BeginCode) -> Result<(), E>,
173    {
174        loop {
175            let b = begin();
176            if std::intrinsics::likely(b.is_started()) {
177                return Ok(HardwareTx {
178                    _private: PhantomData,
179                });
180            } else {
181                retry_handler(b)?
182            }
183        }
184    }
185
186    /// Aborts the current transaction.
187    ///
188    /// Aborting a hardware transaction will effectively reset the thread/call stack to the location
189    /// where new was called, passing control to the retry handler.
190    ///
191    /// Even though this never returns, it does **not** panic.
192    #[inline(always)]
193    pub fn abort(&self) -> ! {
194        unsafe { abort() }
195    }
196}
197
198/// An atomic and hardware transactional usize.
199#[derive(Debug)]
200#[repr(transparent)]
201pub struct HtmUsize {
202    inner: UnsafeCell<AtomicUsize>,
203}
204
205unsafe impl Send for HtmUsize {}
206unsafe impl Sync for HtmUsize {}
207
208impl HtmUsize {
209    /// Creates a new hardware transactional cell.
210    #[inline]
211    pub const fn new(value: usize) -> Self {
212        HtmUsize {
213            inner: UnsafeCell::new(AtomicUsize::new(value)),
214        }
215    }
216
217    /// # Safety
218    ///
219    /// This is unsafe because AtomicUsize already allows mutation through immutable reference.
220    /// Therefore, the returned mutable reference cannot escape this module.
221    #[inline(always)]
222    unsafe fn as_raw(&self, _: &HardwareTx) -> &mut AtomicUsize {
223        &mut *self.inner.get()
224    }
225
226    /// Get the contained value transactionally.
227    #[inline(always)]
228    pub fn get(&self, htx: &HardwareTx) -> usize {
229        unsafe { *self.as_raw(htx).get_mut() }
230    }
231
232    /// Set the contained value transactionally.
233    #[inline(always)]
234    pub fn set(&self, htx: &HardwareTx, value: usize) {
235        unsafe { *self.as_raw(htx).get_mut() = value }
236    }
237}
238
239impl Deref for HtmUsize {
240    type Target = AtomicUsize;
241
242    #[inline(always)]
243    fn deref(&self) -> &Self::Target {
244        unsafe { &*self.inner.get() }
245    }
246}
247
248impl DerefMut for HtmUsize {
249    #[inline(always)]
250    fn deref_mut(&mut self) -> &mut Self::Target {
251        unsafe { &mut *self.inner.get() }
252    }
253}
254
255macro_rules! bench_tx {
256    ($name:ident, $count:expr) => {
257        #[bench]
258        fn $name(bench: &mut test::Bencher) {
259            const ITER_COUNT: usize = 1_000_000;
260            const WORDS_WRITTEN: usize = $count;
261
262            #[repr(align(4096))]
263            struct AlignedArr([usize; WORDS_WRITTEN]);
264
265            let mut arr = AlignedArr([0usize; WORDS_WRITTEN]);
266
267            for (i, elem) in arr.0.iter_mut().enumerate() {
268                unsafe { std::ptr::write_volatile(elem, test::black_box(elem.wrapping_add(i))) };
269                test::black_box(elem);
270            }
271
272            bench.iter(move || {
273                for _ in 0..ITER_COUNT {
274                    unsafe {
275                        let tx = HardwareTx::new(|_| -> Result<(), ()> { Err(()) });
276                        for i in 0..arr.0.len() {
277                            *arr.0.get_unchecked_mut(i) =
278                                arr.0.get_unchecked_mut(i).wrapping_add(1);
279                        }
280                        drop(tx);
281                    }
282                }
283            });
284        }
285    };
286}
287
288bench_tx! {bench_tx0000, 0}
289bench_tx! {bench_tx0001, 1}
290bench_tx! {bench_tx0002, 2}
291bench_tx! {bench_tx0004, 4}
292bench_tx! {bench_tx0008, 8}
293bench_tx! {bench_tx0016, 16}
294bench_tx! {bench_tx0024, 24}
295bench_tx! {bench_tx0032, 32}
296bench_tx! {bench_tx0040, 40}
297bench_tx! {bench_tx0048, 48}
298bench_tx! {bench_tx0056, 56}
299bench_tx! {bench_tx0064, 64}
300bench_tx! {bench_tx0072, 72}
301bench_tx! {bench_tx0080, 80}
302bench_tx! {bench_tx0112, 112}
303bench_tx! {bench_tx0120, 120}
304bench_tx! {bench_tx0128, 128}
305bench_tx! {bench_tx0256, 256}
306
307#[bench]
308fn bench_abort(bench: &mut test::Bencher) {
309    const ITER_COUNT: usize = 1_000_000;
310
311    bench.iter(|| {
312        for _ in 0..ITER_COUNT {
313            unsafe {
314                let tx = HardwareTx::new(|code| -> Result<(), ()> {
315                    if code.is_explicit_abort() {
316                        Err(())
317                    } else {
318                        Ok(())
319                    }
320                });
321                drop(tx.map(|tx| tx.abort()));
322            }
323        }
324    });
325}
326
327#[test]
328fn begin_end() {
329    const ITER_COUNT: usize = 1_000_000;
330
331    let mut fails = 0;
332    for _ in 0..ITER_COUNT {
333        unsafe {
334            let _tx = HardwareTx::new(|_| -> Result<(), ()> {
335                fails += 1;
336                Ok(())
337            })
338            .unwrap();
339        }
340    }
341    println!(
342        "fail rate {:.4}%",
343        fails as f64 * 100.0 / (ITER_COUNT + fails) as f64
344    );
345}
346
347#[test]
348fn test_in_transaction() {
349    for _ in 0..1000000 {
350        unsafe {
351            assert!(!test().in_transaction());
352            let _tx = HardwareTx::new(|_| -> Result<(), ()> { Ok(()) }).unwrap();
353            assert!(test().in_transaction());
354        }
355    }
356}
357
358#[test]
359fn begin_abort() {
360    let mut i = 0i32;
361    let mut abort_count = 0;
362    loop {
363        let i = &mut i;
364        *i += 1;
365        unsafe {
366            let tx = HardwareTx::new(|code| -> Result<(), ()> {
367                if code.is_explicit_abort() {
368                    abort_count += 1;
369                    *i += 1;
370                }
371                Ok(())
372            })
373            .unwrap();
374
375            if *i % 128 != 0 && *i != 1_000_000 {
376                tx.abort();
377            }
378        }
379        if *i == 1_000_000 {
380            break;
381        }
382    }
383    assert_eq!(abort_count, 992187);
384}
385
386#[test]
387fn capacity_check() {
388    use std::mem;
389
390    const CACHE_LINE_SIZE: usize = 64 / mem::size_of::<usize>();
391
392    let mut data = vec![0usize; 1000000];
393    let mut capacity = 0;
394    let end = data.len() / CACHE_LINE_SIZE;
395    for i in (0..end).rev() {
396        data[i * CACHE_LINE_SIZE] = data[i * CACHE_LINE_SIZE].wrapping_add(1);
397        test::black_box(&mut data[i * CACHE_LINE_SIZE]);
398    }
399    for max in 0..end {
400        let mut fail_count = 0;
401        unsafe {
402            let tx = HardwareTx::new(|code| {
403                let cap = code.is_capacity();
404                if cap {
405                    fail_count += 1;
406                }
407                if !cap || fail_count < 1000 {
408                    Ok(())
409                } else {
410                    Err(())
411                }
412            });
413            let tx = match tx {
414                Ok(tx) => tx,
415                Err(()) => break,
416            };
417            for i in 0..max {
418                let elem = data.get_unchecked_mut(i * CACHE_LINE_SIZE);
419                *elem = elem.wrapping_add(1);
420            }
421            drop(tx);
422        }
423        capacity = max;
424    }
425    test::black_box(&mut data);
426    println!("sum: {}", data.iter().sum::<usize>());
427    // println!("Data: {:?}", data);
428    println!(
429        "Capacity: {}",
430        capacity * mem::size_of::<usize>() * CACHE_LINE_SIZE
431    );
432}
433
434#[test]
435fn supported() {
436    let compile = htm_supported();
437    let runtime = htm_supported_runtime();
438    if compile {
439        assert!(runtime);
440    }
441
442    println!("");
443    println!("compile time support check: {}", compile);
444    println!("     runtime support check: {}", runtime);
445}