From e366793204e66425b3544c467f79a1dc9f763c41 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 12 Jan 2026 02:52:58 -0800 Subject: [PATCH 1/9] don't compare function pointers -- they're non-deterministic --- crates/fayalite/src/module.rs | 7 ++- crates/fayalite/src/sim.rs | 86 ++++++++++++++++++++++++++++------- 2 files changed, 75 insertions(+), 18 deletions(-) diff --git a/crates/fayalite/src/module.rs b/crates/fayalite/src/module.rs index 9d1a0e76..d9591829 100644 --- a/crates/fayalite/src/module.rs +++ b/crates/fayalite/src/module.rs @@ -2396,13 +2396,16 @@ impl ModuleBuilder { #[track_caller] pub fn extern_module_simulation_fn< Args: fmt::Debug + Clone + Hash + Eq + Send + Sync + 'static, + F: Copy + Fn(Args, crate::sim::ExternModuleSimulationState) -> Fut + Send + Sync + 'static, Fut: IntoFuture + 'static, >( &self, args: Args, - f: fn(Args, crate::sim::ExternModuleSimulationState) -> Fut, + f: F, ) { - self.extern_module_simulation(crate::sim::SimGeneratorFn { args, f }); + // use for compile-time side-effect + let _ = crate::sim::SimGeneratorFn::::ASSERT_F_IS_ZERO_SIZED; + self.extern_module_simulation(crate::sim::SimGeneratorFn::new(args, f)); } } diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 7068e446..a59a4c71 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -54,7 +54,6 @@ use std::{ hash::Hash, mem, pin::{Pin, pin}, - ptr, rc::Rc, sync::{Arc, Mutex, MutexGuard}, task::Poll, @@ -4065,13 +4064,48 @@ pub trait ExternModuleSimGenerator: Clone + Eq + Hash + Any + Send + Sync + fmt: fn run<'a>(&'a self, sim: ExternModuleSimulationState) -> impl IntoFuture + 'a; } -pub struct SimGeneratorFn { - pub args: Args, - pub f: fn(Args, ExternModuleSimulationState) -> Fut, +/// Type requirements: `F` must be zero-sized, this guarantees that the function called +/// by `F` is entirely determined by the type `F` and not by its value, allowing us to +/// correctly leave `F` out of the stuff used for `Hash` and `Eq`. +/// +/// Note we can't just use function pointers instead -- comparing them is non-deterministic +/// since Rust will duplicate and/or merge functions, even if those functions happen to have +/// different UB but just happen to compile to the same assembly language: +/// +pub struct SimGeneratorFn Fut, Fut> { + args: Args, + f: F, } -impl fmt::Debug for SimGeneratorFn { +impl Fut, Fut> + SimGeneratorFn +{ + pub const ASSERT_F_IS_ZERO_SIZED: () = { + if size_of::() != 0 { + panic!( + "F must be zero-sized -- so it must be a closure with no captures or a function item, it can't be a function pointer" + ); + } + }; + pub const fn new(args: Args, f: F) -> Self { + // use for compile-time side-effect + let _ = Self::ASSERT_F_IS_ZERO_SIZED; + Self { args, f } + } + pub const fn args(&self) -> &Args { + &self.args + } + pub const fn f(&self) -> F { + self.f + } +} + +impl Fut, Fut> fmt::Debug + for SimGeneratorFn +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // use for compile-time side-effect + let _ = Self::ASSERT_F_IS_ZERO_SIZED; let Self { args, f: _ } = self; f.debug_struct("SimGeneratorFn") .field("args", args) @@ -4080,25 +4114,39 @@ impl fmt::Debug for SimGeneratorFn { } } -impl Hash for SimGeneratorFn { +impl Fut, Fut> Hash + for SimGeneratorFn +{ fn hash(&self, state: &mut H) { - let Self { args, f } = self; + // use for compile-time side-effect + let _ = Self::ASSERT_F_IS_ZERO_SIZED; + let Self { args, f: _ } = self; args.hash(state); - f.hash(state); } } -impl Eq for SimGeneratorFn {} +impl Fut, Fut> Eq + for SimGeneratorFn +{ +} -impl PartialEq for SimGeneratorFn { +impl Fut, Fut> PartialEq + for SimGeneratorFn +{ fn eq(&self, other: &Self) -> bool { - let Self { args, f } = self; - *args == other.args && ptr::fn_addr_eq(*f, other.f) + // use for compile-time side-effect + let _ = Self::ASSERT_F_IS_ZERO_SIZED; + let Self { args, f: _ } = self; + *args == other.args } } -impl Clone for SimGeneratorFn { +impl Fut, Fut> Clone + for SimGeneratorFn +{ fn clone(&self) -> Self { + // use for compile-time side-effect + let _ = Self::ASSERT_F_IS_ZERO_SIZED; Self { args: self.args.clone(), f: self.f, @@ -4106,14 +4154,20 @@ impl Clone for SimGeneratorFn { } } -impl Copy for SimGeneratorFn {} +impl Fut, Fut> Copy + for SimGeneratorFn +{ +} impl< - T: fmt::Debug + Clone + Eq + Hash + Send + Sync + 'static, + Args: fmt::Debug + Clone + Eq + Hash + Send + Sync + 'static, + F: Copy + Fn(Args, ExternModuleSimulationState) -> Fut + Send + Sync + 'static, Fut: IntoFuture + 'static, -> ExternModuleSimGenerator for SimGeneratorFn +> ExternModuleSimGenerator for SimGeneratorFn { fn run<'a>(&'a self, sim: ExternModuleSimulationState) -> impl IntoFuture + 'a { + // use for compile-time side-effect + let _ = Self::ASSERT_F_IS_ZERO_SIZED; (self.f)(self.args.clone(), sim) } } From 11281a9842d8777ffd4dfe6b660eecbbb5891c2d Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 1 Feb 2026 21:19:32 -0800 Subject: [PATCH 2/9] add a thread-local cache when using TypeIdMap --- crates/fayalite/src/intern.rs | 18 ++++++++++++++++-- crates/fayalite/src/intern/type_map.rs | 11 +++++++---- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/crates/fayalite/src/intern.rs b/crates/fayalite/src/intern.rs index b68140b5..4969bcb6 100644 --- a/crates/fayalite/src/intern.rs +++ b/crates/fayalite/src/intern.rs @@ -605,7 +605,14 @@ pub struct Interner { impl Interner { fn get() -> &'static Interner { static TYPE_ID_MAP: TypeIdMap = TypeIdMap::new(); - TYPE_ID_MAP.get_or_insert_default() + thread_local! { + static TYPE_ID_MAP_CACHE: TypeIdMap = const { TypeIdMap::new() }; + } + TYPE_ID_MAP_CACHE.with(|cache| { + cache.get_or_insert_with(|| { + TYPE_ID_MAP.get_or_insert_with(|| Box::leak(Default::default())) + }) + }) } } @@ -1035,10 +1042,17 @@ pub trait MemoizeGeneric: 'static + Send + Sync + Hash + Eq + Copy { fn inner(self, input: Self::InputRef<'_>) -> Self::Output; fn get_cow(self, input: Self::InputCow<'_>) -> Self::Output { static TYPE_ID_MAP: TypeIdMap = TypeIdMap::new(); + thread_local! { + static TYPE_ID_MAP_CACHE: TypeIdMap = const { TypeIdMap::new() }; + } let map: &RwLock<( DefaultBuildHasher, HashTable<(Self, Self::InputOwned, Self::Output)>, - )> = TYPE_ID_MAP.get_or_insert_default(); + )> = TYPE_ID_MAP_CACHE.with(|cache| { + cache.get_or_insert_with(|| { + TYPE_ID_MAP.get_or_insert_with(|| Box::leak(Default::default())) + }) + }); fn hash_eq_key<'a, 'b, T: MemoizeGeneric>( this: &'a T, input: T::InputRef<'b>, diff --git a/crates/fayalite/src/intern/type_map.rs b/crates/fayalite/src/intern/type_map.rs index 945116bb..03b68e34 100644 --- a/crates/fayalite/src/intern/type_map.rs +++ b/crates/fayalite/src/intern/type_map.rs @@ -87,20 +87,23 @@ impl TypeIdMap { fn insert_slow( &self, type_id: TypeId, - make: fn() -> Box, + make: impl FnOnce() -> &'static (dyn Any + Sync + Send), ) -> &'static (dyn Any + Sync + Send) { - let value = Box::leak(make()); + let value = make(); let mut write_guard = self.0.write().unwrap(); *write_guard.entry(type_id).or_insert(value) } - pub(crate) fn get_or_insert_default(&self) -> &T { + pub(crate) fn get_or_insert_with( + &self, + make: impl FnOnce() -> &'static T, + ) -> &'static T { let type_id = TypeId::of::(); let read_guard = self.0.read().unwrap(); let retval = read_guard.get(&type_id).map(|v| *v); drop(read_guard); let retval = match retval { Some(retval) => retval, - None => self.insert_slow(type_id, move || Box::new(T::default())), + None => self.insert_slow(type_id, move || make()), }; retval.downcast_ref().expect("known to have correct TypeId") } From 26b0dc3fd864cd88cb2cd47da567c64150fc7220 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 2 Feb 2026 15:42:12 -0800 Subject: [PATCH 3/9] change Interner to pub(crate) --- crates/fayalite/src/intern.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/fayalite/src/intern.rs b/crates/fayalite/src/intern.rs index 4969bcb6..97dafe1b 100644 --- a/crates/fayalite/src/intern.rs +++ b/crates/fayalite/src/intern.rs @@ -598,7 +598,7 @@ struct InternerState { hasher: DefaultBuildHasher, } -pub struct Interner { +pub(crate) struct Interner { state: Mutex>, } From 39810043ea95b97b4def914d2b650db98e89376a Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 2 Feb 2026 15:44:37 -0800 Subject: [PATCH 4/9] move Interner into new mod interner --- crates/fayalite/src/intern.rs | 81 ++---------------------- crates/fayalite/src/intern/interner.rs | 86 ++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 76 deletions(-) create mode 100644 crates/fayalite/src/intern/interner.rs diff --git a/crates/fayalite/src/intern.rs b/crates/fayalite/src/intern.rs index 97dafe1b..29337d7b 100644 --- a/crates/fayalite/src/intern.rs +++ b/crates/fayalite/src/intern.rs @@ -16,9 +16,10 @@ use std::{ marker::PhantomData, ops::Deref, path::{Path, PathBuf}, - sync::{Mutex, RwLock}, + sync::RwLock, }; +mod interner; mod type_map; pub trait LazyInternedTrait: Send + Sync + Any { @@ -593,78 +594,6 @@ impl From> for Cow<'_, } } -struct InternerState { - table: HashTable<&'static T>, - hasher: DefaultBuildHasher, -} - -pub(crate) struct Interner { - state: Mutex>, -} - -impl Interner { - fn get() -> &'static Interner { - static TYPE_ID_MAP: TypeIdMap = TypeIdMap::new(); - thread_local! { - static TYPE_ID_MAP_CACHE: TypeIdMap = const { TypeIdMap::new() }; - } - TYPE_ID_MAP_CACHE.with(|cache| { - cache.get_or_insert_with(|| { - TYPE_ID_MAP.get_or_insert_with(|| Box::leak(Default::default())) - }) - }) - } -} - -impl Default for Interner { - fn default() -> Self { - Self { - state: Mutex::new(InternerState { - table: HashTable::new(), - hasher: Default::default(), - }), - } - } -} - -impl Interner { - fn intern) -> &'static T>( - &self, - alloc: F, - value: Cow<'_, T>, - ) -> Interned { - let mut state = self.state.lock().unwrap(); - let InternerState { table, hasher } = &mut *state; - let inner = *table - .entry( - hasher.hash_one(&*value), - |k| **k == *value, - |k| hasher.hash_one(&**k), - ) - .or_insert_with(|| alloc(value)) - .get(); - Interned { inner } - } -} - -impl Interner { - fn intern_sized(&self, value: Cow<'_, T>) -> Interned { - self.intern(|value| Box::leak(Box::new(value.into_owned())), value) - } -} - -impl Interner<[T]> { - fn intern_slice(&self, value: Cow<'_, [T]>) -> Interned<[T]> { - self.intern(|value| value.into_owned().leak(), value) - } -} - -impl Interner { - fn intern_bit_slice(&self, value: Cow<'_, BitSlice>) -> Interned { - self.intern(|value| value.into_owned().leak(), value) - } -} - pub struct Interned { inner: &'static T, } @@ -984,7 +913,7 @@ impl Intern for T { where Self: ToOwned, { - Interner::get().intern_sized(this) + interner::Interner::get().intern_sized(this) } } @@ -1004,7 +933,7 @@ impl Intern for [T] { where Self: ToOwned, { - Interner::get().intern_slice(this) + interner::Interner::get().intern_slice(this) } } @@ -1024,7 +953,7 @@ impl Intern for BitSlice { where Self: ToOwned, { - Interner::get().intern_bit_slice(this) + interner::Interner::get().intern_bit_slice(this) } } diff --git a/crates/fayalite/src/intern/interner.rs b/crates/fayalite/src/intern/interner.rs new file mode 100644 index 00000000..45899afd --- /dev/null +++ b/crates/fayalite/src/intern/interner.rs @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + intern::{Interned, type_map::TypeIdMap}, + util::DefaultBuildHasher, +}; +use bitvec::slice::BitSlice; +use hashbrown::HashTable; +use std::{ + borrow::Cow, + hash::{BuildHasher, Hash}, + sync::Mutex, +}; + +struct InternerState { + table: HashTable<&'static T>, + hasher: DefaultBuildHasher, +} + +pub(crate) struct Interner { + state: Mutex>, +} + +impl Interner { + pub(crate) fn get() -> &'static Interner { + static TYPE_ID_MAP: TypeIdMap = TypeIdMap::new(); + thread_local! { + static TYPE_ID_MAP_CACHE: TypeIdMap = const { TypeIdMap::new() }; + } + TYPE_ID_MAP_CACHE.with(|cache| { + cache.get_or_insert_with(|| { + TYPE_ID_MAP.get_or_insert_with(|| Box::leak(Default::default())) + }) + }) + } +} + +impl Default for Interner { + fn default() -> Self { + Self { + state: Mutex::new(InternerState { + table: HashTable::new(), + hasher: Default::default(), + }), + } + } +} + +impl Interner { + fn intern) -> &'static T>( + &self, + alloc: F, + value: Cow<'_, T>, + ) -> Interned { + let mut state = self.state.lock().unwrap(); + let InternerState { table, hasher } = &mut *state; + let inner = *table + .entry( + hasher.hash_one(&*value), + |k| **k == *value, + |k| hasher.hash_one(&**k), + ) + .or_insert_with(|| alloc(value)) + .get(); + Interned { inner } + } +} + +impl Interner { + pub(crate) fn intern_sized(&self, value: Cow<'_, T>) -> Interned { + self.intern(|value| Box::leak(Box::new(value.into_owned())), value) + } +} + +impl Interner<[T]> { + pub(crate) fn intern_slice(&self, value: Cow<'_, [T]>) -> Interned<[T]> { + self.intern(|value| value.into_owned().leak(), value) + } +} + +impl Interner { + pub(crate) fn intern_bit_slice(&self, value: Cow<'_, BitSlice>) -> Interned { + self.intern(|value| value.into_owned().leak(), value) + } +} From 4ac1bcbc0a9e4d3225608f4319c269f8ffaf1dd6 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 2 Feb 2026 15:49:26 -0800 Subject: [PATCH 5/9] change Interner to use a sharded hash table --- crates/fayalite/src/intern/interner.rs | 63 +++++++++++++++++++------- 1 file changed, 47 insertions(+), 16 deletions(-) diff --git a/crates/fayalite/src/intern/interner.rs b/crates/fayalite/src/intern/interner.rs index 45899afd..4e35636e 100644 --- a/crates/fayalite/src/intern/interner.rs +++ b/crates/fayalite/src/intern/interner.rs @@ -10,16 +10,31 @@ use hashbrown::HashTable; use std::{ borrow::Cow, hash::{BuildHasher, Hash}, - sync::Mutex, + sync::RwLock, }; -struct InternerState { +struct InternerShard { table: HashTable<&'static T>, - hasher: DefaultBuildHasher, +} + +const LOG2_SHARD_COUNT: u32 = 6; + +fn shard_index_from_hash(hash: u64) -> usize { + // number of bits used for hashbrown's Tag + const HASH_BROWN_TAG_BITS: u32 = 7; + // try to extract bits of the hash that hashbrown isn't using, + // while accounting for some hash functions only returning `usize` bits. + const SHARD_INDEX_START: u32 = usize::BITS + .saturating_sub(HASH_BROWN_TAG_BITS) + .saturating_sub(LOG2_SHARD_COUNT); + let mut shard_index = hash >> SHARD_INDEX_START; + shard_index %= 1 << LOG2_SHARD_COUNT; + shard_index as usize } pub(crate) struct Interner { - state: Mutex>, + shards: [RwLock>; 1 << LOG2_SHARD_COUNT], + hasher: DefaultBuildHasher, } impl Interner { @@ -39,10 +54,12 @@ impl Interner { impl Default for Interner { fn default() -> Self { Self { - state: Mutex::new(InternerState { - table: HashTable::new(), - hasher: Default::default(), - }), + shards: [const { + RwLock::new(InternerShard { + table: HashTable::new(), + }) + }; _], + hasher: Default::default(), } } } @@ -53,14 +70,28 @@ impl Interner { alloc: F, value: Cow<'_, T>, ) -> Interned { - let mut state = self.state.lock().unwrap(); - let InternerState { table, hasher } = &mut *state; - let inner = *table - .entry( - hasher.hash_one(&*value), - |k| **k == *value, - |k| hasher.hash_one(&**k), - ) + let hash = self.hasher.hash_one(&*value); + let shard_index = shard_index_from_hash(hash); + let shard = &self.shards[shard_index]; + let shard_read = shard.read().unwrap(); + let Some(&inner) = shard_read.table.find(hash, |k| **k == *value) else { + drop(shard_read); + return self.intern_cold(alloc, value, hash, shard); + }; + Interned { inner } + } + #[cold] + fn intern_cold) -> &'static T>( + &self, + alloc: F, + value: Cow<'_, T>, + hash: u64, + shard: &RwLock>, + ) -> Interned { + let mut shard = shard.write().unwrap(); + let inner = *shard + .table + .entry(hash, |k| **k == *value, |k| self.hasher.hash_one(&**k)) .or_insert_with(|| alloc(value)) .get(); Interned { inner } From a96efa9696ee66168d318153872767672656d028 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 2 Feb 2026 17:26:33 -0800 Subject: [PATCH 6/9] cache interned UInt/SInt types --- Cargo.lock | 5 +- Cargo.toml | 1 + crates/fayalite/Cargo.toml | 1 + crates/fayalite/src/int.rs | 111 +++++++++++++++++++++++++++++++--- crates/fayalite/src/intern.rs | 33 ++++++++++ 5 files changed, 140 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be5f3bcb..c7e22049 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -319,6 +319,7 @@ dependencies = [ "jobslot", "num-bigint", "num-traits", + "once_cell", "ordered-float", "petgraph", "serde", @@ -521,9 +522,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "ordered-float" diff --git a/Cargo.toml b/Cargo.toml index 2380ea7c..6559e2ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ indexmap = { version = "2.5.0", features = ["serde"] } jobslot = "0.2.23" num-bigint = "0.4.6" num-traits = "0.2.16" +once_cell = "1.21.3" ordered-float = { version = "5.1.0", features = ["serde"] } petgraph = "0.8.1" prettyplease = "0.2.20" diff --git a/crates/fayalite/Cargo.toml b/crates/fayalite/Cargo.toml index fdf1c871..61ee5c78 100644 --- a/crates/fayalite/Cargo.toml +++ b/crates/fayalite/Cargo.toml @@ -26,6 +26,7 @@ hashbrown.workspace = true jobslot.workspace = true num-bigint.workspace = true num-traits.workspace = true +once_cell.workspace = true ordered-float.workspace = true petgraph.workspace = true serde_json.workspace = true diff --git a/crates/fayalite/src/int.rs b/crates/fayalite/src/int.rs index 2d1f6d2c..c4613066 100644 --- a/crates/fayalite/src/int.rs +++ b/crates/fayalite/src/int.rs @@ -10,7 +10,7 @@ use crate::{ value_category::ValueCategoryValue, }, hdl, - intern::{Intern, Interned, Memoize}, + intern::{Intern, Interned, Memoize, OnceInterned}, sim::value::{SimValue, ToSimValueWithType}, source_location::SourceLocation, ty::{ @@ -65,14 +65,21 @@ pub type DynSize = ConstUsize; trait KnownSizeBaseSealed {} -impl KnownSizeBaseSealed for [(); N] {} +impl KnownSizeBaseSealed for ConstUsize {} #[expect(private_bounds)] -pub trait KnownSizeBase: KnownSizeBaseSealed {} +pub trait KnownSizeBase: KnownSizeBaseSealed + GetInternedIntCaches {} macro_rules! impl_known_size_base { ($($size:literal),* $(,)?) => { - $(impl KnownSizeBase for [(); $size] {})* + $(impl KnownSizeBase for ConstUsize<$size> {})* + $(impl GetInternedIntCaches for ConstUsize<$size> { + #[inline(always)] + fn get_interned_int_caches() -> &'static InternedIntCaches { + static CACHES: InternedIntCaches> = InternedIntCaches::new(); + &CACHES + } + })* }; } @@ -113,12 +120,34 @@ impl_known_size_base! { 0x200, } +trait GetInternedIntCaches { + fn get_interned_int_caches() -> &'static InternedIntCaches + where + Self: KnownSize; +} + +struct InternedIntCaches { + uint: OnceInterned>, + sint: OnceInterned>, +} + +impl InternedIntCaches { + const fn new() -> Self { + Self { + uint: OnceInterned::new(), + sint: OnceInterned::new(), + } + } +} + +#[expect(private_bounds)] pub trait KnownSize: GenericConstUsize + sealed::SizeTypeSealed + sealed::SizeSealed + Default + FillInDefaultedGenerics + + GetInternedIntCaches { const SIZE: Self; type ArrayMatch: AsRef<[Expr]> @@ -148,7 +177,7 @@ pub trait KnownSize: impl KnownSize for ConstUsize where - [(); N]: KnownSizeBase, + ConstUsize: KnownSizeBase, { const SIZE: Self = Self; type ArrayMatch = [Expr; N]; @@ -221,6 +250,10 @@ pub trait Size: fn from_usize(v: usize) -> Self::SizeType { Self::try_from_usize(v).expect("wrong size") } + #[doc(hidden)] + fn interned_uint(size_type: Self::SizeType) -> Interned>; + #[doc(hidden)] + fn interned_sint(size_type: Self::SizeType) -> Interned>; } impl sealed::SizeTypeSealed for usize {} @@ -229,6 +262,8 @@ impl SizeType for usize { type Size = DynSize; } +const MAX_CACHED_INT_WIDTH: usize = 1 << 10; + impl Size for DynSize { type ArrayMatch = Box<[Expr]>; type ArraySimValue = Box<[SimValue]>; @@ -242,6 +277,36 @@ impl Size for DynSize { fn try_from_usize(v: usize) -> Option { Some(v) } + + #[doc(hidden)] + fn interned_uint(size_type: Self::SizeType) -> Interned> { + static CACHED: [OnceInterned; MAX_CACHED_INT_WIDTH] = + [const { OnceInterned::new() }; _]; + #[cold] + fn intern_cold(width: usize) -> Interned { + Intern::intern_sized(UInt::new(width)) + } + if let Some(cached) = CACHED.get(size_type) { + cached.get_or_init(|| intern_cold(size_type)) + } else { + intern_cold(size_type) + } + } + + #[doc(hidden)] + fn interned_sint(size_type: Self::SizeType) -> Interned> { + static CACHED: [OnceInterned; MAX_CACHED_INT_WIDTH] = + [const { OnceInterned::new() }; _]; + #[cold] + fn intern_cold(width: usize) -> Interned { + Intern::intern_sized(SInt::new(width)) + } + if let Some(cached) = CACHED.get(size_type) { + cached.get_or_init(|| intern_cold(size_type)) + } else { + intern_cold(size_type) + } + } } impl sealed::SizeSealed for ConstUsize {} @@ -267,6 +332,20 @@ impl Size for T { fn try_from_usize(v: usize) -> Option { if v == T::VALUE { Some(T::SIZE) } else { None } } + + #[doc(hidden)] + fn interned_uint(_size_type: Self::SizeType) -> Interned> { + T::get_interned_int_caches() + .uint + .get_or_init(|| UIntType::new_static().intern_sized()) + } + + #[doc(hidden)] + fn interned_sint(_size_type: Self::SizeType) -> Interned> { + T::get_interned_int_caches() + .sint + .get_or_init(|| SIntType::new_static().intern_sized()) + } } #[derive(Clone, PartialEq, Eq, Debug)] @@ -586,7 +665,7 @@ macro_rules! impl_valueless_op_forward { } macro_rules! impl_int { - ($pretty_name:ident, $name:ident, $generic_name:ident, $value:ident, $SIGNED:literal) => { + ($pretty_name:ident, $name:ident, $generic_name:ident, $value:ident, $SIGNED:literal, $interned_int:ident) => { #[derive(Copy, Clone, PartialEq, Eq, Hash)] #[repr(transparent)] pub struct $name { @@ -1003,7 +1082,7 @@ macro_rules! impl_int { type Output = $name; fn index(&self, width: Width) -> &Self::Output { - Interned::into_inner(Intern::intern_sized($name::new(width))) + Interned::into_inner(Width::Size::$interned_int(width)) } } @@ -1184,8 +1263,22 @@ macro_rules! impl_int { }; } -impl_int!(UInt, UIntType, UIntWithoutGenerics, UIntValue, false); -impl_int!(SInt, SIntType, SIntWithoutGenerics, SIntValue, true); +impl_int!( + UInt, + UIntType, + UIntWithoutGenerics, + UIntValue, + false, + interned_uint +); +impl_int!( + SInt, + SIntType, + SIntWithoutGenerics, + SIntValue, + true, + interned_sint +); impl UInt { /// gets the smallest `UInt` that fits `v` losslessly diff --git a/crates/fayalite/src/intern.rs b/crates/fayalite/src/intern.rs index 29337d7b..9ffd2517 100644 --- a/crates/fayalite/src/intern.rs +++ b/crates/fayalite/src/intern.rs @@ -4,6 +4,7 @@ use crate::{intern::type_map::TypeIdMap, util::DefaultBuildHasher}; use bitvec::{ptr::BitPtr, slice::BitSlice, vec::BitVec}; use hashbrown::HashTable; +use once_cell::race::OnceRef; use serde::{Deserialize, Serialize}; use std::{ any::{Any, TypeId}, @@ -1083,3 +1084,35 @@ pub trait Memoize: 'static + Send + Sync + Hash + Eq + Copy { self.get_cow(Cow::Borrowed(input)) } } + +/// like `once_cell::race::OnceBox` but for `Interned` instead of `Box` +pub struct OnceInterned(OnceRef<'static, T>); + +impl fmt::Debug for OnceInterned { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("OnceInterned").field(&self.get()).finish() + } +} + +impl Default for OnceInterned { + fn default() -> Self { + Self::new() + } +} + +impl OnceInterned { + pub const fn new() -> Self { + Self(OnceRef::new()) + } + pub fn set(&self, v: Interned) -> Result<(), ()> { + self.0.set(v.inner) + } + pub fn get(&self) -> Option> { + self.0.get().map(|inner| Interned { inner }) + } + pub fn get_or_init Interned>(&self, f: F) -> Interned { + Interned { + inner: self.0.get_or_init(|| f().inner), + } + } +} From caa097db0bb407d209702c0c7d9b40a40853ac43 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 3 Feb 2026 17:29:59 -0800 Subject: [PATCH 7/9] change rust version to 1.93.0 --- .forgejo/workflows/test.yml | 1 + Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index 001168f4..7a69a7e5 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -16,6 +16,7 @@ jobs: - uses: https://git.libre-chip.org/mirrors/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/master' }} + - run: rustup override set 1.93.0 - run: cargo test - run: cargo build --tests --features=unstable-doc - run: cargo test --doc --features=unstable-doc diff --git a/Cargo.toml b/Cargo.toml index 6559e2ae..504d90f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ edition = "2024" repository = "https://git.libre-chip.org/libre-chip/fayalite" keywords = ["hdl", "hardware", "semiconductors", "firrtl", "fpga"] categories = ["simulation", "development-tools", "compilers"] -rust-version = "1.89.0" +rust-version = "1.93.0" [workspace.dependencies] fayalite-proc-macros = { version = "=0.3.0", path = "crates/fayalite-proc-macros" } From 9db32406440a705125334c2d6efe17467b46322f Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 3 Feb 2026 17:41:49 -0800 Subject: [PATCH 8/9] fix UI test's expected output --- .../ui/simvalue_is_not_internable.stderr | 504 ++++++++---------- 1 file changed, 234 insertions(+), 270 deletions(-) diff --git a/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr b/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr index 7191e5e9..987cfd05 100644 --- a/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr +++ b/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr @@ -1,107 +1,95 @@ error[E0277]: `Cell` cannot be shared between threads safely - --> tests/ui/simvalue_is_not_internable.rs:11:26 - | -11 | fn f(v: SimValue<()>) -> Interned> { - | ^^^^^^^^^^^^^^^^^^^^^^ `Cell` cannot be shared between threads safely - | - = help: within `fayalite::prelude::SimValue<()>`, the trait `Sync` is not implemented for `Cell` - = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` + --> tests/ui/simvalue_is_not_internable.rs:11:26 + | + 11 | fn f(v: SimValue<()>) -> Interned> { + | ^^^^^^^^^^^^^^^^^^^^^^ `Cell` cannot be shared between threads safely + | + = help: within `fayalite::prelude::SimValue<()>`, the trait `Sync` is not implemented for `Cell` + = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` note: required because it appears within the type `util::alternating_cell::AlternatingCell>` - --> src/util/alternating_cell.rs - | - | pub(crate) struct AlternatingCell { - | ^^^^^^^^^^^^^^^ + --> src/util/alternating_cell.rs + | + 22 | pub(crate) struct AlternatingCell { + | ^^^^^^^^^^^^^^^ note: required because it appears within the type `fayalite::prelude::SimValue<()>` - --> src/sim/value.rs - | - | pub struct SimValue { - | ^^^^^^^^ + --> src/sim/value.rs + | + | pub struct SimValue { + | ^^^^^^^^ note: required by a bound in `fayalite::intern::Interned` - --> src/intern.rs - | - | pub struct Interned { - | ^^^^ required by this bound in `Interned` + --> src/intern.rs + | + | pub struct Interned { + | ^^^^ required by this bound in `Interned` error[E0277]: `UnsafeCell>` cannot be shared between threads safely - --> tests/ui/simvalue_is_not_internable.rs:11:26 - | -11 | fn f(v: SimValue<()>) -> Interned> { - | ^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell>` cannot be shared between threads safely - | - = help: within `fayalite::prelude::SimValue<()>`, the trait `Sync` is not implemented for `UnsafeCell>` + --> tests/ui/simvalue_is_not_internable.rs:11:26 + | + 11 | fn f(v: SimValue<()>) -> Interned> { + | ^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell>` cannot be shared between threads safely + | + = help: within `fayalite::prelude::SimValue<()>`, the trait `Sync` is not implemented for `UnsafeCell>` note: required because it appears within the type `util::alternating_cell::AlternatingCell>` - --> src/util/alternating_cell.rs - | - | pub(crate) struct AlternatingCell { - | ^^^^^^^^^^^^^^^ + --> src/util/alternating_cell.rs + | + 22 | pub(crate) struct AlternatingCell { + | ^^^^^^^^^^^^^^^ note: required because it appears within the type `fayalite::prelude::SimValue<()>` - --> src/sim/value.rs - | - | pub struct SimValue { - | ^^^^^^^^ + --> src/sim/value.rs + | + | pub struct SimValue { + | ^^^^^^^^ note: required by a bound in `fayalite::intern::Interned` - --> src/intern.rs - | - | pub struct Interned { - | ^^^^ required by this bound in `Interned` + --> src/intern.rs + | + | pub struct Interned { + | ^^^^ required by this bound in `Interned` error[E0277]: `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` cannot be sent between threads safely - --> tests/ui/simvalue_is_not_internable.rs:11:26 - | -11 | fn f(v: SimValue<()>) -> Interned> { - | ^^^^^^^^^^^^^^^^^^^^^^ `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` cannot be sent between threads safely - | - = help: within `fayalite::prelude::SimValue<()>`, the trait `Send` is not implemented for `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` + --> tests/ui/simvalue_is_not_internable.rs:11:26 + | + 11 | fn f(v: SimValue<()>) -> Interned> { + | ^^^^^^^^^^^^^^^^^^^^^^ `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` cannot be sent between threads safely + | + = help: within `fayalite::prelude::SimValue<()>`, the trait `Send` is not implemented for `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` note: required because it appears within the type `DynSimOnlyValue` - --> src/sim/value/sim_only_value_unsafe.rs - | - | pub struct DynSimOnlyValue(Rc); - | ^^^^^^^^^^^^^^^ + --> src/sim/value/sim_only_value_unsafe.rs + | + | pub struct DynSimOnlyValue(Rc); + | ^^^^^^^^^^^^^^^ note: required because it appears within the type `PhantomData` - --> $RUST/core/src/marker.rs - | - | pub struct PhantomData; - | ^^^^^^^^^^^ + --> $RUST/core/src/marker.rs note: required because it appears within the type `alloc::raw_vec::RawVec` - --> $RUST/alloc/src/raw_vec/mod.rs - | - | pub(crate) struct RawVec { - | ^^^^^^ + --> $RUST/alloc/src/raw_vec/mod.rs note: required because it appears within the type `Vec` - --> $RUST/alloc/src/vec/mod.rs - | - | pub struct Vec { - | ^^^ + --> $RUST/alloc/src/vec/mod.rs note: required because it appears within the type `OpaqueSimValue` - --> src/ty.rs - | - | pub struct OpaqueSimValue { - | ^^^^^^^^^^^^^^ + --> src/ty.rs + | + | pub struct OpaqueSimValue { + | ^^^^^^^^^^^^^^ note: required because it appears within the type `value::SimValueInner<()>` - --> src/sim/value.rs - | - | struct SimValueInner { - | ^^^^^^^^^^^^^ + --> src/sim/value.rs + | + 51 | struct SimValueInner { + | ^^^^^^^^^^^^^ note: required because it appears within the type `UnsafeCell>` - --> $RUST/core/src/cell.rs - | - | pub struct UnsafeCell { - | ^^^^^^^^^^ + --> $RUST/core/src/cell.rs note: required because it appears within the type `util::alternating_cell::AlternatingCell>` - --> src/util/alternating_cell.rs - | - | pub(crate) struct AlternatingCell { - | ^^^^^^^^^^^^^^^ + --> src/util/alternating_cell.rs + | + 22 | pub(crate) struct AlternatingCell { + | ^^^^^^^^^^^^^^^ note: required because it appears within the type `fayalite::prelude::SimValue<()>` - --> src/sim/value.rs - | - | pub struct SimValue { - | ^^^^^^^^ + --> src/sim/value.rs + | + | pub struct SimValue { + | ^^^^^^^^ note: required by a bound in `fayalite::intern::Interned` - --> src/intern.rs - | - | pub struct Interned { - | ^^^^ required by this bound in `Interned` + --> src/intern.rs + | + | pub struct Interned { + | ^^^^ required by this bound in `Interned` error[E0277]: the trait bound `fayalite::prelude::SimValue<()>: Intern` is not satisfied --> tests/ui/simvalue_is_not_internable.rs:12:26 @@ -118,238 +106,214 @@ help: consider dereferencing here | + error[E0277]: `Cell` cannot be shared between threads safely - --> tests/ui/simvalue_is_not_internable.rs:12:26 - | -12 | Intern::intern_sized(v) - | -------------------- ^ `Cell` cannot be shared between threads safely - | | - | required by a bound introduced by this call - | - = help: within `fayalite::prelude::SimValue<()>`, the trait `Sync` is not implemented for `Cell` - = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` + --> tests/ui/simvalue_is_not_internable.rs:12:26 + | + 12 | Intern::intern_sized(v) + | -------------------- ^ `Cell` cannot be shared between threads safely + | | + | required by a bound introduced by this call + | + = help: within `fayalite::prelude::SimValue<()>`, the trait `Sync` is not implemented for `Cell` + = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` note: required because it appears within the type `util::alternating_cell::AlternatingCell>` - --> src/util/alternating_cell.rs - | - | pub(crate) struct AlternatingCell { - | ^^^^^^^^^^^^^^^ + --> src/util/alternating_cell.rs + | + 22 | pub(crate) struct AlternatingCell { + | ^^^^^^^^^^^^^^^ note: required because it appears within the type `fayalite::prelude::SimValue<()>` - --> src/sim/value.rs - | - | pub struct SimValue { - | ^^^^^^^^ + --> src/sim/value.rs + | + | pub struct SimValue { + | ^^^^^^^^ note: required by a bound in `intern_sized` - --> src/intern.rs - | - | pub trait Intern: Any + Send + Sync { - | ^^^^ required by this bound in `Intern::intern_sized` + --> src/intern.rs + | + | pub trait Intern: Any + Send + Sync { + | ^^^^ required by this bound in `Intern::intern_sized` ... - | fn intern_sized(self) -> Interned - | ------------ required by a bound in this associated function + | fn intern_sized(self) -> Interned + | ------------ required by a bound in this associated function help: consider dereferencing here - | -12 | Intern::intern_sized(*v) - | + + | + 12 | Intern::intern_sized(*v) + | + error[E0277]: `UnsafeCell>` cannot be shared between threads safely - --> tests/ui/simvalue_is_not_internable.rs:12:26 - | -12 | Intern::intern_sized(v) - | -------------------- ^ `UnsafeCell>` cannot be shared between threads safely - | | - | required by a bound introduced by this call - | - = help: within `fayalite::prelude::SimValue<()>`, the trait `Sync` is not implemented for `UnsafeCell>` + --> tests/ui/simvalue_is_not_internable.rs:12:26 + | + 12 | Intern::intern_sized(v) + | -------------------- ^ `UnsafeCell>` cannot be shared between threads safely + | | + | required by a bound introduced by this call + | + = help: within `fayalite::prelude::SimValue<()>`, the trait `Sync` is not implemented for `UnsafeCell>` note: required because it appears within the type `util::alternating_cell::AlternatingCell>` - --> src/util/alternating_cell.rs - | - | pub(crate) struct AlternatingCell { - | ^^^^^^^^^^^^^^^ + --> src/util/alternating_cell.rs + | + 22 | pub(crate) struct AlternatingCell { + | ^^^^^^^^^^^^^^^ note: required because it appears within the type `fayalite::prelude::SimValue<()>` - --> src/sim/value.rs - | - | pub struct SimValue { - | ^^^^^^^^ + --> src/sim/value.rs + | + | pub struct SimValue { + | ^^^^^^^^ note: required by a bound in `intern_sized` - --> src/intern.rs - | - | pub trait Intern: Any + Send + Sync { - | ^^^^ required by this bound in `Intern::intern_sized` + --> src/intern.rs + | + | pub trait Intern: Any + Send + Sync { + | ^^^^ required by this bound in `Intern::intern_sized` ... - | fn intern_sized(self) -> Interned - | ------------ required by a bound in this associated function + | fn intern_sized(self) -> Interned + | ------------ required by a bound in this associated function help: consider dereferencing here - | -12 | Intern::intern_sized(*v) - | + + | + 12 | Intern::intern_sized(*v) + | + error[E0277]: `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` cannot be sent between threads safely - --> tests/ui/simvalue_is_not_internable.rs:12:26 - | -12 | Intern::intern_sized(v) - | -------------------- ^ `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` cannot be sent between threads safely - | | - | required by a bound introduced by this call - | - = help: within `fayalite::prelude::SimValue<()>`, the trait `Send` is not implemented for `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` + --> tests/ui/simvalue_is_not_internable.rs:12:26 + | + 12 | Intern::intern_sized(v) + | -------------------- ^ `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` cannot be sent between threads safely + | | + | required by a bound introduced by this call + | + = help: within `fayalite::prelude::SimValue<()>`, the trait `Send` is not implemented for `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` note: required because it appears within the type `DynSimOnlyValue` - --> src/sim/value/sim_only_value_unsafe.rs - | - | pub struct DynSimOnlyValue(Rc); - | ^^^^^^^^^^^^^^^ + --> src/sim/value/sim_only_value_unsafe.rs + | + | pub struct DynSimOnlyValue(Rc); + | ^^^^^^^^^^^^^^^ note: required because it appears within the type `PhantomData` - --> $RUST/core/src/marker.rs - | - | pub struct PhantomData; - | ^^^^^^^^^^^ + --> $RUST/core/src/marker.rs note: required because it appears within the type `alloc::raw_vec::RawVec` - --> $RUST/alloc/src/raw_vec/mod.rs - | - | pub(crate) struct RawVec { - | ^^^^^^ + --> $RUST/alloc/src/raw_vec/mod.rs note: required because it appears within the type `Vec` - --> $RUST/alloc/src/vec/mod.rs - | - | pub struct Vec { - | ^^^ + --> $RUST/alloc/src/vec/mod.rs note: required because it appears within the type `OpaqueSimValue` - --> src/ty.rs - | - | pub struct OpaqueSimValue { - | ^^^^^^^^^^^^^^ + --> src/ty.rs + | + | pub struct OpaqueSimValue { + | ^^^^^^^^^^^^^^ note: required because it appears within the type `value::SimValueInner<()>` - --> src/sim/value.rs - | - | struct SimValueInner { - | ^^^^^^^^^^^^^ + --> src/sim/value.rs + | + 51 | struct SimValueInner { + | ^^^^^^^^^^^^^ note: required because it appears within the type `UnsafeCell>` - --> $RUST/core/src/cell.rs - | - | pub struct UnsafeCell { - | ^^^^^^^^^^ + --> $RUST/core/src/cell.rs note: required because it appears within the type `util::alternating_cell::AlternatingCell>` - --> src/util/alternating_cell.rs - | - | pub(crate) struct AlternatingCell { - | ^^^^^^^^^^^^^^^ + --> src/util/alternating_cell.rs + | + 22 | pub(crate) struct AlternatingCell { + | ^^^^^^^^^^^^^^^ note: required because it appears within the type `fayalite::prelude::SimValue<()>` - --> src/sim/value.rs - | - | pub struct SimValue { - | ^^^^^^^^ + --> src/sim/value.rs + | + | pub struct SimValue { + | ^^^^^^^^ note: required by a bound in `intern_sized` - --> src/intern.rs - | - | pub trait Intern: Any + Send + Sync { - | ^^^^ required by this bound in `Intern::intern_sized` + --> src/intern.rs + | + | pub trait Intern: Any + Send + Sync { + | ^^^^ required by this bound in `Intern::intern_sized` ... - | fn intern_sized(self) -> Interned - | ------------ required by a bound in this associated function + | fn intern_sized(self) -> Interned + | ------------ required by a bound in this associated function help: consider dereferencing here - | -12 | Intern::intern_sized(*v) - | + + | + 12 | Intern::intern_sized(*v) + | + error[E0277]: `Cell` cannot be shared between threads safely - --> tests/ui/simvalue_is_not_internable.rs:12:5 - | -12 | Intern::intern_sized(v) - | ^^^^^^^^^^^^^^^^^^^^^^^ `Cell` cannot be shared between threads safely - | - = help: within `fayalite::prelude::SimValue<()>`, the trait `Sync` is not implemented for `Cell` - = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` + --> tests/ui/simvalue_is_not_internable.rs:12:5 + | + 12 | Intern::intern_sized(v) + | ^^^^^^^^^^^^^^^^^^^^^^^ `Cell` cannot be shared between threads safely + | + = help: within `fayalite::prelude::SimValue<()>`, the trait `Sync` is not implemented for `Cell` + = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` note: required because it appears within the type `util::alternating_cell::AlternatingCell>` - --> src/util/alternating_cell.rs - | - | pub(crate) struct AlternatingCell { - | ^^^^^^^^^^^^^^^ + --> src/util/alternating_cell.rs + | + 22 | pub(crate) struct AlternatingCell { + | ^^^^^^^^^^^^^^^ note: required because it appears within the type `fayalite::prelude::SimValue<()>` - --> src/sim/value.rs - | - | pub struct SimValue { - | ^^^^^^^^ + --> src/sim/value.rs + | + | pub struct SimValue { + | ^^^^^^^^ note: required by a bound in `fayalite::intern::Interned` - --> src/intern.rs - | - | pub struct Interned { - | ^^^^ required by this bound in `Interned` + --> src/intern.rs + | + | pub struct Interned { + | ^^^^ required by this bound in `Interned` error[E0277]: `UnsafeCell>` cannot be shared between threads safely - --> tests/ui/simvalue_is_not_internable.rs:12:5 - | -12 | Intern::intern_sized(v) - | ^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell>` cannot be shared between threads safely - | - = help: within `fayalite::prelude::SimValue<()>`, the trait `Sync` is not implemented for `UnsafeCell>` + --> tests/ui/simvalue_is_not_internable.rs:12:5 + | + 12 | Intern::intern_sized(v) + | ^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell>` cannot be shared between threads safely + | + = help: within `fayalite::prelude::SimValue<()>`, the trait `Sync` is not implemented for `UnsafeCell>` note: required because it appears within the type `util::alternating_cell::AlternatingCell>` - --> src/util/alternating_cell.rs - | - | pub(crate) struct AlternatingCell { - | ^^^^^^^^^^^^^^^ + --> src/util/alternating_cell.rs + | + 22 | pub(crate) struct AlternatingCell { + | ^^^^^^^^^^^^^^^ note: required because it appears within the type `fayalite::prelude::SimValue<()>` - --> src/sim/value.rs - | - | pub struct SimValue { - | ^^^^^^^^ + --> src/sim/value.rs + | + | pub struct SimValue { + | ^^^^^^^^ note: required by a bound in `fayalite::intern::Interned` - --> src/intern.rs - | - | pub struct Interned { - | ^^^^ required by this bound in `Interned` + --> src/intern.rs + | + | pub struct Interned { + | ^^^^ required by this bound in `Interned` error[E0277]: `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` cannot be sent between threads safely - --> tests/ui/simvalue_is_not_internable.rs:12:5 - | -12 | Intern::intern_sized(v) - | ^^^^^^^^^^^^^^^^^^^^^^^ `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` cannot be sent between threads safely - | - = help: within `fayalite::prelude::SimValue<()>`, the trait `Send` is not implemented for `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` + --> tests/ui/simvalue_is_not_internable.rs:12:5 + | + 12 | Intern::intern_sized(v) + | ^^^^^^^^^^^^^^^^^^^^^^^ `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` cannot be sent between threads safely + | + = help: within `fayalite::prelude::SimValue<()>`, the trait `Send` is not implemented for `Rc<(dyn value::sim_only_value_unsafe::DynSimOnlyValueTrait + 'static)>` note: required because it appears within the type `DynSimOnlyValue` - --> src/sim/value/sim_only_value_unsafe.rs - | - | pub struct DynSimOnlyValue(Rc); - | ^^^^^^^^^^^^^^^ + --> src/sim/value/sim_only_value_unsafe.rs + | + | pub struct DynSimOnlyValue(Rc); + | ^^^^^^^^^^^^^^^ note: required because it appears within the type `PhantomData` - --> $RUST/core/src/marker.rs - | - | pub struct PhantomData; - | ^^^^^^^^^^^ + --> $RUST/core/src/marker.rs note: required because it appears within the type `alloc::raw_vec::RawVec` - --> $RUST/alloc/src/raw_vec/mod.rs - | - | pub(crate) struct RawVec { - | ^^^^^^ + --> $RUST/alloc/src/raw_vec/mod.rs note: required because it appears within the type `Vec` - --> $RUST/alloc/src/vec/mod.rs - | - | pub struct Vec { - | ^^^ + --> $RUST/alloc/src/vec/mod.rs note: required because it appears within the type `OpaqueSimValue` - --> src/ty.rs - | - | pub struct OpaqueSimValue { - | ^^^^^^^^^^^^^^ + --> src/ty.rs + | + | pub struct OpaqueSimValue { + | ^^^^^^^^^^^^^^ note: required because it appears within the type `value::SimValueInner<()>` - --> src/sim/value.rs - | - | struct SimValueInner { - | ^^^^^^^^^^^^^ + --> src/sim/value.rs + | + 51 | struct SimValueInner { + | ^^^^^^^^^^^^^ note: required because it appears within the type `UnsafeCell>` - --> $RUST/core/src/cell.rs - | - | pub struct UnsafeCell { - | ^^^^^^^^^^ + --> $RUST/core/src/cell.rs note: required because it appears within the type `util::alternating_cell::AlternatingCell>` - --> src/util/alternating_cell.rs - | - | pub(crate) struct AlternatingCell { - | ^^^^^^^^^^^^^^^ + --> src/util/alternating_cell.rs + | + 22 | pub(crate) struct AlternatingCell { + | ^^^^^^^^^^^^^^^ note: required because it appears within the type `fayalite::prelude::SimValue<()>` - --> src/sim/value.rs - | - | pub struct SimValue { - | ^^^^^^^^ + --> src/sim/value.rs + | + | pub struct SimValue { + | ^^^^^^^^ note: required by a bound in `fayalite::intern::Interned` - --> src/intern.rs - | - | pub struct Interned { - | ^^^^ required by this bound in `Interned` + --> src/intern.rs + | + | pub struct Interned { + | ^^^^ required by this bound in `Interned` From 1bc835803bf87643c6464eaca0932e30e5febb8c Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 3 Feb 2026 17:30:27 -0800 Subject: [PATCH 9/9] speed up LazyInterned by redoing caching using RwLock and add a thread-local cache --- crates/fayalite/src/array.rs | 32 +++- crates/fayalite/src/intern.rs | 244 +++++++++++++++---------- crates/fayalite/src/intern/type_map.rs | 4 +- crates/fayalite/src/phantom_const.rs | 16 +- 4 files changed, 189 insertions(+), 107 deletions(-) diff --git a/crates/fayalite/src/array.rs b/crates/fayalite/src/array.rs index 4e2b2235..6ca68093 100644 --- a/crates/fayalite/src/array.rs +++ b/crates/fayalite/src/array.rs @@ -109,14 +109,42 @@ impl Default for ArrayType { } } +struct MakeType(Interned); + +impl From> for Interned { + fn from(value: MakeType) -> Self { + value.0 + } +} + +impl Default for MakeType { + fn default() -> Self { + Self(T::TYPE.intern_sized()) + } +} + +struct MakeMaskType(Interned); + +impl From> for Interned { + fn from(value: MakeMaskType) -> Self { + value.0 + } +} + +impl Default for MakeMaskType { + fn default() -> Self { + Self(T::MASK_TYPE.intern_sized()) + } +} + impl StaticType for ArrayType { const TYPE: Self = Self { - element: LazyInterned::new_lazy(&|| T::TYPE.intern_sized()), + element: LazyInterned::new_const::>(), len: Len::SIZE, type_properties: Self::TYPE_PROPERTIES, }; const MASK_TYPE: Self::MaskType = ArrayType:: { - element: LazyInterned::new_lazy(&|| T::MASK_TYPE.intern_sized()), + element: LazyInterned::new_const::>(), len: Len::SIZE, type_properties: Self::MASK_TYPE_PROPERTIES, }; diff --git a/crates/fayalite/src/intern.rs b/crates/fayalite/src/intern.rs index 9ffd2517..b78aa593 100644 --- a/crates/fayalite/src/intern.rs +++ b/crates/fayalite/src/intern.rs @@ -9,12 +9,12 @@ use serde::{Deserialize, Serialize}; use std::{ any::{Any, TypeId}, borrow::{Borrow, Cow}, + cell::RefCell, cmp::Ordering, ffi::{OsStr, OsString}, fmt, hash::{BuildHasher, Hash, Hasher}, iter::FusedIterator, - marker::PhantomData, ops::Deref, path::{Path, PathBuf}, sync::RwLock, @@ -23,51 +23,172 @@ use std::{ mod interner; mod type_map; -pub trait LazyInternedTrait: Send + Sync + Any { - fn get(&self) -> Interned; +/// invariant: T must be zero-sized, `type_id` is unique for every possible T value. +struct LazyInternedLazyInner { + type_id: TypeId, + value: T, } -impl Interned + Send + Sync + Any> - LazyInternedTrait for F -{ - fn get(&self) -> Interned { - self() +impl Hash for LazyInternedLazyInner { + fn hash(&self, state: &mut H) { + let Self { type_id, value: _ } = self; + type_id.hash(state); } } -#[repr(transparent)] -pub struct LazyInternedFn(pub &'static dyn LazyInternedTrait); +impl PartialEq for LazyInternedLazyInner { + fn eq(&self, other: &Self) -> bool { + let Self { type_id, value: _ } = self; + *type_id == other.type_id + } +} -impl Copy for LazyInternedFn {} +impl Eq for LazyInternedLazyInner {} -impl Clone for LazyInternedFn { +impl fmt::Debug for LazyInternedLazyInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("LazyInternedLazyInner") + .finish_non_exhaustive() + } +} + +impl LazyInternedLazyInner { + const fn new(value: T) -> Self + where + T: Sized, + { + const { assert!(size_of::() == 0) }; + Self { + type_id: TypeId::of::(), + value, + } + } +} + +pub struct LazyInternedLazy( + &'static LazyInternedLazyInner Interned + Send + Sync>, +); + +impl LazyInternedLazy { + pub const fn new_const>>() -> Self { + Self(&const { LazyInternedLazyInner::new(|| V::default().into()) }) + } + pub const fn new_const_default() -> Self + where + Interned: Default, + { + Self::new_const::>() + } + pub fn interned(self) -> Interned { + struct Map(hashbrown::HashTable<(TypeId, &'static (dyn Any + Send + Sync))>); + impl Map { + const EMPTY: Self = Self(hashbrown::HashTable::new()); + fn get( + &self, + lazy_interned_lazy: LazyInternedLazy, + hash: u64, + ) -> Option<&'static Interned> { + let &(_, v) = self.0.find(hash, |v| v.0 == lazy_interned_lazy.0.type_id)?; + let Some(retval) = v.downcast_ref::>() else { + unreachable!(); + }; + Some(retval) + } + fn get_or_insert( + &mut self, + lazy_interned_lazy: LazyInternedLazy, + hash: u64, + v: &'static Interned, + ) -> &'static Interned { + let entry = self + .0 + .entry( + hash, + |&(k, _)| k == lazy_interned_lazy.0.type_id, + |&(k, _)| type_map::TypeIdBuildHasher.hash_one(k), + ) + .or_insert_with(|| (lazy_interned_lazy.0.type_id, v)); + let &(_, v) = entry.get(); + let Some(retval) = v.downcast_ref::>() else { + unreachable!(); + }; + retval + } + } + static GLOBAL_CACHE: RwLock = RwLock::new(Map::EMPTY); + #[cold] + fn insert_in_thread_local_cache( + cache: &RefCell, + this: LazyInternedLazy, + hash: u64, + ) -> Interned { + let read_lock = GLOBAL_CACHE.read().unwrap(); + let v = read_lock.get(this, hash); + drop(read_lock); + let v = v.unwrap_or_else(|| { + let v = Box::leak(Box::new((this.0.value)())); + GLOBAL_CACHE.write().unwrap().get_or_insert(this, hash, v) + }); + *cache.borrow_mut().get_or_insert(this, hash, v) + } + thread_local! { + static THREAD_LOCAL_CACHE: RefCell = const { RefCell::new(Map::EMPTY) }; + } + let hash = type_map::TypeIdBuildHasher.hash_one(self.0.type_id); + THREAD_LOCAL_CACHE.with(|cache| { + let borrow = cache.borrow(); + if let Some(v) = borrow.get(self, hash) { + *v + } else { + drop(borrow); + insert_in_thread_local_cache(cache, self, hash) + } + }) + } +} + +impl Copy for LazyInternedLazy {} + +impl Clone for LazyInternedLazy { fn clone(&self) -> Self { *self } } -impl Hash for LazyInternedFn { +impl Hash for LazyInternedLazy { fn hash(&self, state: &mut H) { - self.0.get_ptr_eq_with_type_id().hash(state); + self.0.hash(state); } } -impl Eq for LazyInternedFn {} +impl Eq for LazyInternedLazy {} -impl PartialEq for LazyInternedFn { +impl PartialEq for LazyInternedLazy { fn eq(&self, other: &Self) -> bool { - self.0.get_ptr_eq_with_type_id() == other.0.get_ptr_eq_with_type_id() + self.0 == other.0 } } pub enum LazyInterned { Interned(Interned), - Lazy(LazyInternedFn), + Lazy(LazyInternedLazy), } impl LazyInterned { - pub const fn new_lazy(v: &'static dyn LazyInternedTrait) -> Self { - Self::Lazy(LazyInternedFn(v)) + pub const fn new_const>>() -> Self { + Self::Lazy(LazyInternedLazy::new_const::()) + } + pub const fn new_const_default() -> Self + where + Interned: Default, + { + Self::new_const::>() + } + pub fn interned(self) -> Interned { + match self { + Self::Interned(retval) => retval, + Self::Lazy(retval) => retval.interned(), + } } } @@ -79,7 +200,7 @@ impl Clone for LazyInterned { impl Copy for LazyInterned {} -impl Deref for LazyInterned { +impl Deref for LazyInterned { type Target = T; fn deref(&self) -> &Self::Target { @@ -87,9 +208,9 @@ impl Deref for LazyInterned { } } -impl Eq for LazyInterned where Interned: Eq {} +impl Eq for LazyInterned where Interned: Eq {} -impl PartialEq for LazyInterned +impl PartialEq for LazyInterned where Interned: PartialEq, { @@ -98,7 +219,7 @@ where } } -impl Ord for LazyInterned +impl Ord for LazyInterned where Interned: Ord, { @@ -107,7 +228,7 @@ where } } -impl PartialOrd for LazyInterned +impl PartialOrd for LazyInterned where Interned: PartialOrd, { @@ -116,7 +237,7 @@ where } } -impl Hash for LazyInterned +impl Hash for LazyInterned where Interned: Hash, { @@ -125,77 +246,6 @@ where } } -impl LazyInterned { - pub fn interned(self) -> Interned - where - T: Intern, - { - struct MemoizeInterned(PhantomData); - - impl Hash for MemoizeInterned { - fn hash(&self, _state: &mut H) {} - } - - impl PartialEq for MemoizeInterned { - fn eq(&self, _other: &Self) -> bool { - true - } - } - - impl Eq for MemoizeInterned {} - - impl Clone for MemoizeInterned { - fn clone(&self) -> Self { - *self - } - } - - impl Copy for MemoizeInterned {} - - impl MemoizeGeneric for MemoizeInterned { - type InputRef<'a> = LazyInternedFn; - - type InputOwned = LazyInternedFn; - - type InputCow<'a> = LazyInternedFn; - - type Output = Interned; - - fn input_eq(a: Self::InputRef<'_>, b: Self::InputRef<'_>) -> bool { - a == b - } - - fn input_borrow(input: &Self::InputOwned) -> Self::InputRef<'_> { - *input - } - - fn input_cow_into_owned(input: Self::InputCow<'_>) -> Self::InputOwned { - input - } - - fn input_cow_borrow<'a>(input: &'a Self::InputCow<'_>) -> Self::InputRef<'a> { - *input - } - - fn input_cow_from_owned<'a>(input: Self::InputOwned) -> Self::InputCow<'a> { - input - } - - fn input_cow_from_ref(input: Self::InputRef<'_>) -> Self::InputCow<'_> { - input - } - - fn inner(self, input: Self::InputRef<'_>) -> Self::Output { - input.0.get() - } - } - match self { - Self::Interned(retval) => retval, - Self::Lazy(retval) => MemoizeInterned(PhantomData).get(retval), - } - } -} - pub trait InternedCompare { type InternedCompareKey: Ord + Hash; fn interned_compare_key_ref(this: &Self) -> Self::InternedCompareKey; diff --git a/crates/fayalite/src/intern/type_map.rs b/crates/fayalite/src/intern/type_map.rs index 03b68e34..e31a5bf1 100644 --- a/crates/fayalite/src/intern/type_map.rs +++ b/crates/fayalite/src/intern/type_map.rs @@ -6,7 +6,7 @@ use std::{ sync::RwLock, }; -struct TypeIdHasher(u64); +pub(crate) struct TypeIdHasher(u64); // assumes TypeId has at least 64 bits that is a good hash impl Hasher for TypeIdHasher { @@ -63,7 +63,7 @@ impl Hasher for TypeIdHasher { } } -struct TypeIdBuildHasher; +pub(crate) struct TypeIdBuildHasher; impl BuildHasher for TypeIdBuildHasher { type Hasher = TypeIdHasher; diff --git a/crates/fayalite/src/phantom_const.rs b/crates/fayalite/src/phantom_const.rs index 32e9d6b9..fb7be6f5 100644 --- a/crates/fayalite/src/phantom_const.rs +++ b/crates/fayalite/src/phantom_const.rs @@ -4,7 +4,7 @@ use crate::{ expr::{Expr, HdlPartialEqImpl, HdlPartialOrdImpl, ToExpr, ValueType}, int::Bool, - intern::{Intern, Interned, InternedCompare, LazyInterned, LazyInternedTrait, Memoize}, + intern::{Intern, Interned, InternedCompare, LazyInterned, Memoize}, sim::value::{SimValue, ToSimValue, ToSimValueWithType}, source_location::SourceLocation, ty::{ @@ -240,11 +240,17 @@ impl PhantomConst { { Self::new_interned(value.intern_deref()) } - pub const fn new_lazy(v: &'static dyn LazyInternedTrait) -> Self { + pub const fn new_const>>() -> Self { Self { - value: LazyInterned::new_lazy(v), + value: LazyInterned::new_const::(), } } + pub const fn new_const_default() -> Self + where + Interned: Default, + { + Self::new_const::>() + } pub fn get(self) -> Interned { self.value.interned() } @@ -334,9 +340,7 @@ impl StaticType for PhantomConst where Interned: Default, { - const TYPE: Self = PhantomConst { - value: LazyInterned::new_lazy(&Interned::::default), - }; + const TYPE: Self = Self::new_const_default(); const MASK_TYPE: Self::MaskType = (); const TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES; const MASK_TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES;