// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 //! module for the SharedVector and related things #![allow(unsafe_code)] #![warn(missing_docs)] use core::fmt::Debug; use core::mem::MaybeUninit; use core::ops::Deref; use core::ptr::NonNull; use portable_atomic as atomic; #[repr(C)] struct SharedVectorHeader { refcount: atomic::AtomicIsize, size: usize, capacity: usize, } #[repr(C)] struct SharedVectorInner { header: SharedVectorHeader, data: MaybeUninit, } fn compute_inner_layout(capacity: usize) -> core::alloc::Layout { core::alloc::Layout::new::() .extend(core::alloc::Layout::array::(capacity).unwrap()) .unwrap() .0 } unsafe fn drop_inner(mut inner: NonNull>) { debug_assert_eq!(inner.as_ref().header.refcount.load(atomic::Ordering::Relaxed), 0); let data_ptr = inner.as_mut().data.as_mut_ptr(); for x in 0..inner.as_ref().header.size { core::ptr::drop_in_place(data_ptr.add(x)); } alloc::alloc::dealloc( inner.as_ptr() as *mut u8, compute_inner_layout::(inner.as_ref().header.capacity), ) } /// Allocate the memory for the SharedVector with the given capacity. Return the inner with size and refcount set to 1 fn alloc_with_capacity(capacity: usize) -> NonNull> { let ptr = unsafe { ::alloc::alloc::alloc(compute_inner_layout::(capacity)) }; assert!(!ptr.is_null(), "allocation of {:?} bytes failed", capacity); unsafe { core::ptr::write( ptr as *mut SharedVectorHeader, SharedVectorHeader { refcount: 1.into(), size: 0, capacity }, ); } NonNull::new(ptr).unwrap().cast() } /// Return a new capacity suitable for this vector /// Loosely based on alloc::raw_vec::RawVec::grow_amortized. fn capacity_for_grow(current_cap: usize, required_cap: usize, elem_size: usize) -> usize { if current_cap >= required_cap { return current_cap; } let cap = core::cmp::max(current_cap * 2, required_cap); let min_non_zero_cap = if elem_size == 1 { 8 } else if elem_size <= 1024 { 4 } else { 1 }; core::cmp::max(min_non_zero_cap, cap) } #[repr(C)] /// SharedVector holds a reference-counted read-only copy of `[T]`. pub struct SharedVector { inner: NonNull>, } // Safety: We use atomic reference counting, and if T is Send, we can send the vector to another thread unsafe impl Send for SharedVector {} impl Drop for SharedVector { fn drop(&mut self) { unsafe { if self .inner .cast::() .as_ref() .refcount .load(atomic::Ordering::Relaxed) < 0 { return; } if self.inner.as_ref().header.refcount.fetch_sub(1, atomic::Ordering::SeqCst) == 1 { drop_inner(self.inner) } } } } impl Clone for SharedVector { fn clone(&self) -> Self { unsafe { if self .inner .cast::() .as_ref() .refcount .load(atomic::Ordering::Relaxed) > 0 { self.inner.as_ref().header.refcount.fetch_add(1, atomic::Ordering::SeqCst); } SharedVector { inner: self.inner } } } } impl SharedVector { /// Create a new empty array with a pre-allocated capacity in number of items pub fn with_capacity(capacity: usize) -> Self { Self { inner: alloc_with_capacity(capacity) } } fn as_ptr(&self) -> *const T { unsafe { self.inner.as_ref().data.as_ptr() } } /// Number of elements in the array pub fn len(&self) -> usize { unsafe { self.inner.cast::().as_ref().size } } /// Return true if the SharedVector is empty pub fn is_empty(&self) -> bool { self.len() == 0 } /// Return a slice to the array pub fn as_slice(&self) -> &[T] { if self.is_empty() { &[] } else { // Safety: When len > 0, we know that the pointer holds an array of the size of len unsafe { core::slice::from_raw_parts(self.as_ptr(), self.len()) } } } /// Returns the number of elements the vector can hold without reallocating, when not shared fn capacity(&self) -> usize { unsafe { self.inner.cast::().as_ref().capacity } } } impl SharedVector { /// Create a SharedVector from a slice pub fn from_slice(slice: &[T]) -> SharedVector { Self::from(slice) } /// Ensure that the reference count is 1 so the array can be changed. /// If that's not the case, the array will be cloned fn detach(&mut self, new_capacity: usize) { let is_shared = unsafe { self.inner.as_ref().header.refcount.load(atomic::Ordering::Relaxed) } != 1; if !is_shared && new_capacity <= self.capacity() { return; } let mut new_array = SharedVector::with_capacity(new_capacity); core::mem::swap(&mut self.inner, &mut new_array.inner); let mut size = 0; for x in new_array.into_iter() { assert_ne!(size, new_capacity); unsafe { core::ptr::write(self.inner.as_mut().data.as_mut_ptr().add(size), x); size += 1; self.inner.as_mut().header.size = size; } if size == new_capacity { break; } } } /// Return a mutable slice to the array. If the array was shared, this will make a copy of the array. pub fn make_mut_slice(&mut self) -> &mut [T] { self.detach(self.len()); unsafe { core::slice::from_raw_parts_mut(self.as_ptr() as *mut T, self.len()) } } /// Add an element to the array. If the array was shared, this will make a copy of the array. pub fn push(&mut self, value: T) { self.detach(capacity_for_grow(self.capacity(), self.len() + 1, core::mem::size_of::())); unsafe { core::ptr::write( self.inner.as_mut().data.as_mut_ptr().add(self.inner.as_mut().header.size), value, ); self.inner.as_mut().header.size += 1; } } /// Removes last element from the array and returns it. /// If the array was shared, this will make a copy of the array. pub fn pop(&mut self) -> Option { if self.is_empty() { None } else { self.detach(self.len()); unsafe { self.inner.as_mut().header.size -= 1; Some(core::ptr::read(self.inner.as_mut().data.as_mut_ptr().add(self.len()))) } } } /// Resize the array to the given size. /// If the array was smaller new elements will be initialized with the value. /// If the array was bigger, extra elements will be discarded /// /// ``` /// use i_slint_core::SharedVector; /// let mut shared_vector = SharedVector::::from_slice(&[1, 2, 3]); /// shared_vector.resize(5, 8); /// assert_eq!(shared_vector.as_slice(), &[1, 2, 3, 8, 8]); /// shared_vector.resize(2, 0); /// assert_eq!(shared_vector.as_slice(), &[1, 2]); /// ``` pub fn resize(&mut self, new_len: usize, value: T) { if self.len() == new_len { return; } self.detach(new_len); // Safety: detach ensured that the array is not shared. let inner = unsafe { self.inner.as_mut() }; if inner.header.size >= new_len { self.shrink(new_len); } else { while inner.header.size < new_len { // Safety: The array must have a capacity of at least new_len because of the detach call earlier unsafe { core::ptr::write(inner.data.as_mut_ptr().add(inner.header.size), value.clone()); } inner.header.size += 1; } } } fn shrink(&mut self, new_len: usize) { if self.len() == new_len { return; } assert!( unsafe { self.inner.as_ref().header.refcount.load(atomic::Ordering::Relaxed) } == 1 ); // Safety: caller (and above debug_assert) must ensure that the array is not shared. let inner = unsafe { self.inner.as_mut() }; while inner.header.size > new_len { inner.header.size -= 1; // Safety: The array was of size inner.header.size, so there should be an element there unsafe { core::ptr::drop_in_place(inner.data.as_mut_ptr().add(inner.header.size)); } } } /// Clears the vector and removes all elements. pub fn clear(&mut self) { let is_shared = unsafe { self.inner.as_ref().header.refcount.load(atomic::Ordering::Relaxed) } != 1; if is_shared { *self = SharedVector::default(); } else { self.shrink(0) } } } impl Deref for SharedVector { type Target = [T]; fn deref(&self) -> &Self::Target { self.as_slice() } } /* FIXME: is this a good idea to implement DerefMut knowing what it might detach? impl DerefMut for SharedVector { fn deref_mut(&mut self) -> &mut Self::Target { self.as_mut_slice() } }*/ impl From<&[T]> for SharedVector { fn from(slice: &[T]) -> Self { let capacity = slice.len(); let mut result = Self::with_capacity(capacity); for x in slice { unsafe { core::ptr::write( result.inner.as_mut().data.as_mut_ptr().add(result.inner.as_mut().header.size), x.clone(), ); result.inner.as_mut().header.size += 1; } } result } } impl From<[T; N]> for SharedVector { fn from(array: [T; N]) -> Self { array.into_iter().collect() } } impl FromIterator for SharedVector { fn from_iter>(iter: I) -> Self { let mut iter = iter.into_iter(); let mut capacity = iter.size_hint().0; let mut result = Self::with_capacity(capacity); let mut size = 0; while let Some(x) = iter.next() { if size >= capacity { capacity = capacity_for_grow( capacity, size + 1 + iter.size_hint().0, core::mem::size_of::(), ); unsafe { result.inner.as_ref().header.refcount.store(0, atomic::Ordering::Relaxed) }; let mut iter = IntoIter(IntoIterInner::UnShared(result.inner, 0)); result.inner = alloc_with_capacity::(capacity); match &mut iter.0 { IntoIterInner::UnShared(old_inner, begin) => { while *begin < size { unsafe { core::ptr::write( result.inner.as_mut().data.as_mut_ptr().add(*begin), core::ptr::read(old_inner.as_ref().data.as_ptr().add(*begin)), ); *begin += 1; result.inner.as_mut().header.size = *begin; } } } _ => unreachable!(), } } debug_assert_eq!(result.len(), size); debug_assert!(result.capacity() > size); unsafe { core::ptr::write(result.inner.as_mut().data.as_mut_ptr().add(size), x); size += 1; result.inner.as_mut().header.size = size; } } result } } impl Extend for SharedVector { fn extend>(&mut self, iter: X) { let iter = iter.into_iter(); let hint = iter.size_hint().0; if hint > 0 { self.detach(capacity_for_grow( self.capacity(), self.len() + hint, core::mem::size_of::(), )); } for item in iter { self.push(item); } } } static SHARED_NULL: SharedVectorHeader = SharedVectorHeader { refcount: atomic::AtomicIsize::new(-1), size: 0, capacity: 0 }; impl Default for SharedVector { fn default() -> Self { SharedVector { inner: NonNull::from(&SHARED_NULL).cast() } } } impl Debug for SharedVector { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.as_slice().fmt(f) } } impl AsRef<[T]> for SharedVector { #[inline] fn as_ref(&self) -> &[T] { self.as_slice() } } impl PartialEq for SharedVector where U: ?Sized + AsRef<[T]>, T: PartialEq, { fn eq(&self, other: &U) -> bool { self.as_slice() == other.as_ref() } } impl Eq for SharedVector {} impl IntoIterator for SharedVector { type Item = T; type IntoIter = IntoIter; fn into_iter(self) -> Self::IntoIter { IntoIter(unsafe { if self.inner.as_ref().header.refcount.load(atomic::Ordering::Relaxed) == 1 { let inner = self.inner; core::mem::forget(self); inner.as_ref().header.refcount.store(0, atomic::Ordering::Relaxed); IntoIterInner::UnShared(inner, 0) } else { IntoIterInner::Shared(self, 0) } }) } } #[cfg(feature = "serde")] use serde::ser::SerializeSeq; #[cfg(feature = "serde")] impl serde::Serialize for SharedVector where T: serde::Serialize, { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut seq = serializer.serialize_seq(Some(self.len()))?; for item in self.iter() { seq.serialize_element(item)?; } seq.end() } } #[cfg(feature = "serde")] impl<'de, T> serde::Deserialize<'de> for SharedVector where T: Clone + serde::Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let mut elements: alloc::vec::Vec = serde::Deserialize::deserialize(deserializer)?; let mut shared_vec = SharedVector::with_capacity(elements.len()); for elem in elements.drain(..) { shared_vec.push(elem); } Ok(shared_vec) } } enum IntoIterInner { Shared(SharedVector, usize), // Elements up to the usize member are already moved out UnShared(NonNull>, usize), } impl Drop for IntoIterInner { fn drop(&mut self) { match self { IntoIterInner::Shared(..) => { /* drop of SharedVector takes care of it */ } IntoIterInner::UnShared(mut inner, begin) => unsafe { debug_assert_eq!(inner.as_ref().header.refcount.load(atomic::Ordering::Relaxed), 0); let data_ptr = inner.as_mut().data.as_mut_ptr(); for x in (*begin)..inner.as_ref().header.size { core::ptr::drop_in_place(data_ptr.add(x)); } ::alloc::alloc::dealloc( inner.as_ptr() as *mut u8, compute_inner_layout::(inner.as_ref().header.capacity), ) }, } } } /// An iterator that moves out of a SharedVector. /// /// This `struct` is created by the `into_iter` method on [`SharedVector`] (provided /// by the [`IntoIterator`] trait). pub struct IntoIter(IntoIterInner); impl Iterator for IntoIter { type Item = T; fn next(&mut self) -> Option { match &mut self.0 { IntoIterInner::Shared(array, moved) => { let result = array.as_slice().get(*moved).cloned(); *moved += 1; result } IntoIterInner::UnShared(inner, begin) => unsafe { if *begin < inner.as_ref().header.size { let r = core::ptr::read(inner.as_ref().data.as_ptr().add(*begin)); *begin += 1; Some(r) } else { None } }, } } } #[test] fn simple_test() { let x: SharedVector = SharedVector::from([1, 2, 3]); let y: SharedVector = SharedVector::from([3, 2, 1]); assert_eq!(x, x.clone()); assert_ne!(x, y); let z: [i32; 3] = [1, 2, 3]; assert_eq!(z, x.as_slice()); let vec: Vec = vec![1, 2, 3]; assert_eq!(x, vec); let def: SharedVector = Default::default(); assert_eq!(def, SharedVector::::default()); assert_ne!(def, x); } #[test] fn push_test() { let mut x: SharedVector = SharedVector::from([1, 2, 3]); let y = x.clone(); x.push(4); x.push(5); x.push(6); assert_eq!(x.as_slice(), &[1, 2, 3, 4, 5, 6]); assert_eq!(y.as_slice(), &[1, 2, 3]); } #[test] #[should_panic] fn invalid_capacity_test() { let _: SharedVector = SharedVector::with_capacity(usize::MAX / 2 - 1000); } #[test] fn collect_from_iter_with_no_size_hint() { struct NoSizeHintIter<'a> { data: &'a [&'a str], i: usize, } impl<'a> Iterator for NoSizeHintIter<'a> { type Item = String; fn next(&mut self) -> Option { if self.i >= self.data.len() { return None; } let item = self.data[self.i]; self.i += 1; Some(item.to_string()) } fn size_hint(&self) -> (usize, Option) { (0, None) } } // 5 elements to be above the initial "grow"-capacity of 4 and thus require one realloc. let input = NoSizeHintIter { data: &["Hello", "sweet", "world", "of", "iterators"], i: 0 }; let shared_vec: SharedVector = input.collect(); assert_eq!(shared_vec.as_slice(), &["Hello", "sweet", "world", "of", "iterators"]); } #[test] fn test_capacity_grows_only_when_needed() { let mut vec: SharedVector = SharedVector::with_capacity(2); vec.push(0); assert_eq!(vec.capacity(), 2); vec.push(0); assert_eq!(vec.capacity(), 2); vec.push(0); assert_eq!(vec.len(), 3); assert!(vec.capacity() > 2); } #[test] fn test_vector_clear() { let mut vec: SharedVector = Default::default(); vec.clear(); vec.push("Hello".into()); vec.push("World".into()); vec.push("of".into()); vec.push("Vectors".into()); let mut copy = vec.clone(); assert_eq!(vec.len(), 4); let orig_cap = vec.capacity(); assert!(orig_cap >= vec.len()); vec.clear(); assert_eq!(vec.len(), 0); assert_eq!(vec.capacity(), 0); // vec was shared, so start with new empty vector. vec.push("Welcome back".into()); assert_eq!(vec.len(), 1); assert!(vec.capacity() >= vec.len()); assert_eq!(copy.len(), 4); assert_eq!(copy.capacity(), orig_cap); copy.clear(); // copy is not shared (anymore), retain capacity. assert_eq!(copy.capacity(), orig_cap); } #[test] fn pop_test() { let mut x: SharedVector = SharedVector::from([1, 2, 3]); let y = x.clone(); assert_eq!(x.pop(), Some(3)); assert_eq!(x.pop(), Some(2)); assert_eq!(x.pop(), Some(1)); assert_eq!(x.pop(), None); assert!(x.is_empty()); assert_eq!(y.as_slice(), &[1, 2, 3]); } #[cfg(feature = "ffi")] pub(crate) mod ffi { use super::*; #[no_mangle] /// This function is used for the low-level C++ interface to allocate the backing vector of a SharedVector. pub unsafe extern "C" fn slint_shared_vector_allocate(size: usize, align: usize) -> *mut u8 { alloc::alloc::alloc(alloc::alloc::Layout::from_size_align(size, align).unwrap()) } #[no_mangle] /// This function is used for the low-level C++ interface to deallocate the backing vector of a SharedVector pub unsafe extern "C" fn slint_shared_vector_free(ptr: *mut u8, size: usize, align: usize) { alloc::alloc::dealloc(ptr, alloc::alloc::Layout::from_size_align(size, align).unwrap()) } #[no_mangle] /// This function is used for the low-level C++ interface to initialize the empty SharedVector. pub unsafe extern "C" fn slint_shared_vector_empty() -> *const u8 { &SHARED_NULL as *const _ as *const u8 } } #[cfg(feature = "serde")] #[test] fn test_serialize_deserialize_sharedvector() { let v = SharedVector::from([1, 2, 3]); let serialized = serde_json::to_string(&v).unwrap(); let deserialized: SharedVector = serde_json::from_str(&serialized).unwrap(); assert_eq!(v, deserialized); }