]> Repositorios git - scryer-prolog.git/commitdiff
make F64Table concurency safe
authorBennet Bleßmann <[email protected]>
Sat, 2 Sep 2023 20:27:43 +0000 (22:27 +0200)
committerBennet Bleßmann <[email protected]>
Tue, 5 Sep 2023 17:39:59 +0000 (19:39 +0200)
src/arena.rs
src/macros.rs
src/rcu.rs

index 758285dad301c6340369e382c824ef36aa18c9c8..0631decc857660f7b9a39f2c22a4ff2069e8eb96 100644 (file)
@@ -4,12 +4,16 @@ use crate::machine::loader::LiveLoadState;
 use crate::machine::machine_indices::*;
 use crate::machine::streams::*;
 use crate::raw_block::*;
+use crate::rcu::Rcu;
+use crate::rcu::RcuRef;
 use crate::read::*;
 
 use crate::parser::dashu::{Integer, Rational};
 use ordered_float::OrderedFloat;
+use tokio::sync::RwLock;
 
 use std::alloc;
+use std::cell::UnsafeCell;
 use std::fmt;
 use std::hash::{Hash, Hasher};
 use std::mem;
@@ -34,25 +38,35 @@ macro_rules! float_alloc {
         let result = $e;
         #[allow(unused_unsafe)]
         unsafe {
-            $arena.f64_tbl.build_with(result)
+            $arena.f64_tbl.build_with(result).as_ptr()
         }
     }};
 }
 
-#[cfg(test)]
-use std::cell::RefCell;
+use std::sync::Arc;
+use std::sync::Mutex;
+use std::sync::Weak;
 
 const F64_TABLE_INIT_SIZE: usize = 1 << 16;
 const F64_TABLE_ALIGN: usize = 8;
 
-#[cfg(test)]
-thread_local! {
-    static F64_TABLE_BUF_BASE: RefCell<*const u8> = RefCell::new(ptr::null_mut());
+#[inline(always)]
+fn global_f64table() -> &'static RwLock<Weak<F64Table>> {
+    #[cfg(feature = "rust_beta_channel")]
+    {
+        // const Weak::new will be stabilized in 1.73 which is currently in beta,
+        // till then we need a OnceLock for initialization
+        static GLOBAL_ATOM_TABLE: RwLock<Weak<F64Table>> = RwLock::const_new(Weak::new());
+        &GLOBAL_ATOM_TABLE
+    }
+    #[cfg(not(feature = "rust_beta_channel"))]
+    {
+        use std::sync::OnceLock;
+        static GLOBAL_ATOM_TABLE: OnceLock<RwLock<Weak<F64Table>>> = OnceLock::new();
+        GLOBAL_ATOM_TABLE.get_or_init(|| RwLock::new(Weak::new()))
+    }
 }
 
-#[cfg(not(test))]
-static mut F64_TABLE_BUF_BASE: *const u8 = ptr::null_mut();
-
 impl RawBlockTraits for F64Table {
     #[inline]
     fn init_size() -> usize {
@@ -67,65 +81,86 @@ impl RawBlockTraits for F64Table {
 
 #[derive(Debug)]
 pub struct F64Table {
-    block: RawBlock<F64Table>,
-}
-
-#[cfg(test)]
-fn set_f64_tbl_buf_base(ptr: *const u8) {
-    F64_TABLE_BUF_BASE.with(|f64_table_buf_base| {
-        *f64_table_buf_base.borrow_mut() = ptr;
-    });
-}
-
-#[cfg(test)]
-pub(crate) fn get_f64_tbl_buf_base() -> *const u8 {
-    F64_TABLE_BUF_BASE.with(|f64_table_buf_base| *f64_table_buf_base.borrow())
-}
-
-#[cfg(not(test))]
-fn set_f64_tbl_buf_base(ptr: *const u8) {
-    unsafe {
-        F64_TABLE_BUF_BASE = ptr;
-    }
-}
-
-#[cfg(not(test))]
-pub(crate) fn get_f64_tbl_buf_base() -> *const u8 {
-    unsafe { F64_TABLE_BUF_BASE }
+    block: Rcu<RawBlock<F64Table>>,
+    update: Mutex<()>,
 }
 
 #[inline(always)]
-pub fn lookup_float(offset: usize) -> *mut OrderedFloat<f64> {
-    let base = get_f64_tbl_buf_base() as usize;
-    (base + offset) as *mut _
+pub fn lookup_float(
+    offset: F64Offset,
+) -> RcuRef<RawBlock<F64Table>, UnsafeCell<OrderedFloat<f64>>> {
+    let f64table = global_f64table()
+        .blocking_read()
+        .upgrade()
+        .expect("We should only be looking up floats while there is a float table");
+
+    RcuRef::try_map(f64table.block.active_epoch(), |raw_block| unsafe {
+        raw_block
+            .base
+            .offset(offset.0 as isize)
+            .cast_mut()
+            .cast::<UnsafeCell<OrderedFloat<f64>>>()
+            .as_ref()
+    })
+    .expect("The offset should result in a non-null pointer")
 }
 
 impl F64Table {
     #[inline]
-    pub fn new() -> Self {
-        let table = Self {
-            block: RawBlock::new(),
-        };
-        set_f64_tbl_buf_base(table.block.base);
-        table
+    pub fn new() -> Arc<Self> {
+        let upgraded = global_f64table().blocking_read().upgrade();
+        // don't inline upgraded, otherwise temporary will be dropped too late in case of None
+        if let Some(atom_table) = upgraded {
+            atom_table
+        } else {
+            let mut guard = global_f64table().blocking_write();
+            // try to upgrade again in case we lost the race on the write lock
+            if let Some(atom_table) = guard.upgrade() {
+                atom_table
+            } else {
+                let atom_table = Arc::new(Self {
+                    block: Rcu::new(RawBlock::new()),
+                    update: Mutex::new(()),
+                });
+                *guard = Arc::downgrade(&atom_table);
+                atom_table
+            }
+        }
     }
 
-    pub unsafe fn build_with(&mut self, value: f64) -> F64Ptr {
+    pub unsafe fn build_with(&self, value: f64) -> F64Offset {
+        let update_guard = self.update.lock();
+
+        // we don't have an index table for lookups as AtomTable does so
+        // just get the epoch after we take the upgrade lock
+        let mut block_epoch = self.block.active_epoch();
+
         let mut ptr;
 
         loop {
-            ptr = self.block.alloc(mem::size_of::<f64>());
+            ptr = block_epoch.alloc(mem::size_of::<f64>());
 
             if ptr.is_null() {
-                self.block.grow();
-                set_f64_tbl_buf_base(self.block.base);
+                let new_block = block_epoch.grow_new().unwrap();
+                self.block.replace(new_block);
+                block_epoch = self.block.active_epoch();
             } else {
                 break;
             }
         }
 
         ptr::write(ptr as *mut OrderedFloat<f64>, OrderedFloat(value));
-        F64Ptr(ptr::NonNull::new_unchecked(ptr as *mut _))
+
+        let float = F64Offset {
+            0: ptr as usize - block_epoch.base as usize,
+        };
+
+        // atometable would have to update the index table at this point
+
+        // expicit drop to ensure we don't accidentally drop it early
+        drop(update_guard);
+
+        float
     }
 }
 
@@ -336,12 +371,18 @@ pub trait ArenaAllocated: Sized {
     }
 }
 
-#[derive(Copy, Clone, Debug)]
-pub struct F64Ptr(pub ptr::NonNull<OrderedFloat<f64>>);
+#[derive(Debug)]
+pub struct F64Ptr(RcuRef<RawBlock<F64Table>, UnsafeCell<OrderedFloat<f64>>>);
+
+impl Clone for F64Ptr {
+    fn clone(&self) -> Self {
+        Self(RcuRef::clone(&self.0))
+    }
+}
 
 impl PartialEq for F64Ptr {
     fn eq(&self, other: &F64Ptr) -> bool {
-        self.0 == other.0 || &**self == &**other
+        RcuRef::ptr_eq(&self.0, &other.0) || self.deref() == other.deref()
     }
 }
 
@@ -377,26 +418,26 @@ impl Deref for F64Ptr {
 
     #[inline]
     fn deref(&self) -> &Self::Target {
-        unsafe { &*self.0.as_ptr() }
+        unsafe { &*self.0.get().as_ref().unwrap() }
     }
 }
 
 impl DerefMut for F64Ptr {
     #[inline]
     fn deref_mut(&mut self) -> &mut Self::Target {
-        unsafe { &mut *self.0.as_ptr() }
+        unsafe { &mut *self.0.get().as_mut().unwrap() }
     }
 }
 
 impl F64Ptr {
     #[inline(always)]
-    pub fn from_offset(offset: usize) -> Self {
-        unsafe { F64Ptr(ptr::NonNull::new_unchecked(lookup_float(offset))) }
+    pub fn from_offset(offset: F64Offset) -> Self {
+        Self(lookup_float(offset))
     }
 
     #[inline(always)]
     pub fn as_offset(&self) -> F64Offset {
-        F64Offset(self.0.as_ptr() as usize - get_f64_tbl_buf_base() as usize)
+        F64Offset(self.0.get() as usize - RcuRef::get_root(&self.0).base as usize)
     }
 }
 
@@ -416,7 +457,7 @@ impl F64Offset {
 
     #[inline(always)]
     pub fn as_ptr(self) -> F64Ptr {
-        F64Ptr::from_offset(self.0)
+        F64Ptr::from_offset(self)
     }
 
     #[inline(always)]
@@ -672,7 +713,7 @@ struct AllocSlab {
 #[derive(Debug)]
 pub struct Arena {
     base: *mut AllocSlab,
-    pub f64_tbl: F64Table,
+    pub f64_tbl: Arc<F64Table>,
 }
 
 unsafe impl Send for Arena {}
@@ -804,6 +845,8 @@ const_assert!(mem::size_of::<OrderedFloat<f64>>() == 8);
 
 #[cfg(test)]
 mod tests {
+    use std::ops::Deref;
+
     use crate::machine::mock_wam::*;
     use crate::machine::partial_string::*;
 
@@ -812,15 +855,15 @@ mod tests {
 
     #[test]
     fn float_ptr_cast() {
-        let mut wam = MockWAM::new();
+        let wam = MockWAM::new();
 
         let f = 0f64;
         let fp = float_alloc!(f, wam.machine_st.arena);
-        let mut cell = HeapCellValue::from(fp);
+        let mut cell = HeapCellValue::from(fp.clone());
 
         assert_eq!(cell.get_tag(), HeapCellValueTag::F64);
         assert_eq!(cell.get_mark_bit(), false);
-        assert_eq!(*fp, OrderedFloat(f));
+        assert_eq!(fp.deref(), &OrderedFloat(f));
 
         cell.set_mark_bit(true);
 
index bb51b781a1d82cdb3a237a8caf93c0ed3f2370d8..168dcb3a14783da201a5fffee7aa3da6651a8242 100644 (file)
@@ -88,7 +88,7 @@ macro_rules! cell_as_atom_cell {
 macro_rules! cell_as_f64_ptr {
     ($cell:expr) => {{
         let offset = $cell.get_value() as usize;
-        F64Ptr::from_offset(offset)
+        F64Ptr::from_offset(F64Offset::new(offset))
     }};
 }
 
index 2ff1de1ef177953dca4250baaf16755bd6a6d85a..a554208e31c098df9a1ab38ef6c148c722b65e19 100644 (file)
@@ -189,16 +189,24 @@ impl<T: ?Sized, M: ?Sized> RcuRef<T, M> {
         })
     }
 
-    pub fn same_epoch(this: &Self, other: &Self) -> bool {
+    pub fn same_epoch<M2>(this: &Self, other: &RcuRef<T, M2>) -> bool {
         Arc::ptr_eq(&this.arc, &other.arc)
     }
 
+    pub fn ptr_eq(this: &Self, other: &Self) -> bool {
+        this.data == other.data
+    }
+
     pub fn clone(this: &Self) -> Self {
         Self {
             arc: Arc::clone(&this.arc),
             data: this.data,
         }
     }
+
+    pub fn get_root(this: &Self) -> &T {
+        &this.arc
+    }
 }
 
 impl<T: ?Sized, M: ?Sized> Deref for RcuRef<T, M> {