From 27f3733573929daa4cc12991fb328574e961ba08 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 11 May 2026 22:44:54 -0700 Subject: [PATCH] WIP adding TraceAsString --- crates/fayalite/src/expr.rs | 2 + crates/fayalite/src/expr/ops.rs | 171 ++++++++++- crates/fayalite/src/module.rs | 5 +- crates/fayalite/src/sim.rs | 81 ++++- crates/fayalite/src/sim/compiler.rs | 36 ++- crates/fayalite/src/sim/value.rs | 3 +- crates/fayalite/src/ty.rs | 388 +++++++++++++++++++++++- crates/fayalite/src/ty/serde_impls.rs | 24 +- crates/fayalite/src/util.rs | 1 + crates/fayalite/src/util/serde_by_id.rs | 234 ++++++++++++++ crates/fayalite/visit_types.json | 29 +- 11 files changed, 962 insertions(+), 12 deletions(-) create mode 100644 crates/fayalite/src/util/serde_by_id.rs diff --git a/crates/fayalite/src/expr.rs b/crates/fayalite/src/expr.rs index 00a0cee..2b8544c 100644 --- a/crates/fayalite/src/expr.rs +++ b/crates/fayalite/src/expr.rs @@ -225,6 +225,8 @@ expr_enum! { RegSync(Reg), RegAsync(Reg), MemPort(MemPort), + AsTraceAsString(ops::AsTraceAsString), + TraceAsStringAsInner(ops::TraceAsStringAsInner), } } diff --git a/crates/fayalite/src/expr/ops.rs b/crates/fayalite/src/expr/ops.rs index f4cfebd..742e6d0 100644 --- a/crates/fayalite/src/expr/ops.rs +++ b/crates/fayalite/src/expr/ops.rs @@ -27,7 +27,7 @@ use crate::{ ToSyncReset, }, sim::value::{SimValue, ToSimValue}, - ty::{CanonicalType, StaticType, Type}, + ty::{CanonicalType, StaticType, TraceAsString, Type}, util::ConstUsize, }; use bitvec::{order::Lsb0, slice::BitSlice, vec::BitVec, view::BitView}; @@ -4694,3 +4694,172 @@ impl, A> FromIterator for Expr { This::expr_from_iter(iter) } } + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct AsTraceAsString { + inner: Expr, + ty: TraceAsString, + literal_bits: Result, NotALiteralExpr>, + target: Option>, +} + +impl fmt::Debug for AsTraceAsString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + inner, + ty: _, + literal_bits: _, + target: _, + } = self; + f.debug_struct("AsTraceAsString") + .field("inner", inner) + .finish_non_exhaustive() + } +} + +impl AsTraceAsString { + pub fn new(inner: Expr, ty: TraceAsString) -> Self { + assert_eq!(inner.ty(), ty.inner_ty().canonical()); + let literal_bits = inner.to_literal_bits(); + let target = inner.target(); + Self { + inner, + ty, + literal_bits, + target, + } + } + pub fn inner(self) -> Expr { + self.inner + } +} + +impl GetTarget for AsTraceAsString { + fn target(&self) -> Option> { + self.target + } +} + +impl ToLiteralBits for AsTraceAsString { + fn to_literal_bits(&self) -> Result, NotALiteralExpr> { + self.literal_bits + } +} + +impl ValueType for AsTraceAsString { + type Type = TraceAsString; + type ValueCategory = ValueCategoryExpr; + + fn ty(&self) -> Self::Type { + self.ty + } +} + +impl ToExpr for AsTraceAsString { + fn to_expr(&self) -> Expr { + Expr { + __enum: ExprEnum::AsTraceAsString(AsTraceAsString { + inner: self.inner, + ty: self.ty.canonical_trace_as_string(), + literal_bits: self.literal_bits, + target: self.target, + }) + .intern(), + __ty: self.ty, + __flow: Expr::flow(self.inner), + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct TraceAsStringAsInner { + arg: Expr>, + ty: T, + literal_bits: Result, NotALiteralExpr>, + target: Option>, +} + +impl fmt::Debug for TraceAsStringAsInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + arg, + ty: _, + literal_bits: _, + target: _, + } = self; + f.debug_struct("TraceAsStringAsInner") + .field("arg", arg) + .finish_non_exhaustive() + } +} + +impl TraceAsStringAsInner { + pub fn from_arg_and_ty(arg: Expr>, ty: T) -> Self { + assert_eq!(arg.ty().inner_ty(), ty.canonical()); + let literal_bits = arg.to_literal_bits(); + let target = arg.target(); + Self { + arg, + ty, + literal_bits, + target, + } + } + pub fn new(arg: Expr>) -> Self { + Self::from_arg_and_ty( + Expr { + __enum: arg.__enum, + __ty: arg.__ty.canonical_trace_as_string(), + __flow: arg.__flow, + }, + arg.ty().inner_ty(), + ) + } + pub fn arg(self) -> Expr> { + self.arg + } + pub fn arg_typed(self) -> Expr> { + Expr { + __enum: self.arg.__enum, + __ty: TraceAsString::from_canonical_trace_as_string(self.arg.__ty), + __flow: self.arg.__flow, + } + } +} + +impl GetTarget for TraceAsStringAsInner { + fn target(&self) -> Option> { + self.target + } +} + +impl ToLiteralBits for TraceAsStringAsInner { + fn to_literal_bits(&self) -> Result, NotALiteralExpr> { + self.literal_bits + } +} + +impl ValueType for TraceAsStringAsInner { + type Type = T; + type ValueCategory = ValueCategoryExpr; + + fn ty(&self) -> Self::Type { + self.ty + } +} + +impl ToExpr for TraceAsStringAsInner { + fn to_expr(&self) -> Expr { + Expr { + __enum: ExprEnum::TraceAsStringAsInner(TraceAsStringAsInner { + arg: self.arg, + ty: self.ty.canonical(), + literal_bits: self.literal_bits, + target: self.target, + }) + .intern(), + __ty: self.ty, + __flow: Expr::flow(self.arg), + } + } +} diff --git a/crates/fayalite/src/module.rs b/crates/fayalite/src/module.rs index d959182..f2059fd 100644 --- a/crates/fayalite/src/module.rs +++ b/crates/fayalite/src/module.rs @@ -1537,7 +1537,7 @@ impl TargetState { fn new(target: Interned, declared_in_block: usize) -> Self { Self { target, - inner: match target.canonical_ty() { + inner: match target.canonical_ty().unwrap_transparent_types() { CanonicalType::Bundle(ty) => TargetStateInner::Decomposed { subtargets: ty .fields() @@ -1586,6 +1586,9 @@ impl TargetState { declared_in_block, written_in_blocks: RefCell::default(), }, + CanonicalType::TraceAsString(_) => { + unreachable!("handled by unwrap_transparent_types") + } }, } } diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 4a7721b..1fda165 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -38,7 +38,7 @@ use crate::{ }, ty::{ OpaqueSimValue, OpaqueSimValueSize, OpaqueSimValueSizeRange, OpaqueSimValueSlice, - OpaqueSimValueWriter, + OpaqueSimValueWriter, TraceAsString, }, util::{BitSliceWriteWithBase, DebugAsDisplay, HashMap, HashSet, copy_le_bytes_to_bitslice}, }; @@ -432,6 +432,15 @@ impl_trace_decl! { ty: DynSimOnly, flow: Flow, }), + TraceAsString(TraceTraceAsString { + fn location(self) -> _ { + self.location + } + location: TraceLocation, + name: Interned, + ty: TraceAsString, + flow: Flow, + }), }), } @@ -543,6 +552,7 @@ pub trait TraceWriter: fmt::Debug + 'static { id: TraceScalarId, value: &DynSimOnlyValue, ) -> Result<(), Self::Error>; + fn set_signal_string(&mut self, id: TraceScalarId, value: &str) -> Result<(), Self::Error>; } pub struct DynTraceWriterDecls(Box); @@ -607,6 +617,7 @@ trait TraceWriterDynTrait: fmt::Debug + 'static { id: TraceScalarId, value: &DynSimOnlyValue, ) -> std::io::Result<()>; + fn set_signal_string_dyn(&mut self, id: TraceScalarId, value: &str) -> std::io::Result<()>; } impl TraceWriterDynTrait for T { @@ -680,6 +691,9 @@ impl TraceWriterDynTrait for T { ) -> std::io::Result<()> { Ok(TraceWriter::set_signal_sim_only_value(self, id, value).map_err(err_into_io)?) } + fn set_signal_string_dyn(&mut self, id: TraceScalarId, value: &str) -> std::io::Result<()> { + Ok(TraceWriter::set_signal_string(self, id, value).map_err(err_into_io)?) + } } pub struct DynTraceWriter(Box); @@ -758,6 +772,9 @@ impl TraceWriter for DynTraceWriter { ) -> Result<(), Self::Error> { self.0.set_signal_sim_only_value_dyn(id, value) } + fn set_signal_string(&mut self, id: TraceScalarId, value: &str) -> Result<(), Self::Error> { + self.0.set_signal_string_dyn(id, value) + } } #[derive(Debug)] @@ -934,6 +951,41 @@ pub(crate) enum SimTraceKind { PhantomConst { ty: PhantomConst, }, + TraceAsString { + layout: compiler::CompiledTypeLayout, + range: TypeIndexRange, + ty: TraceAsString, + }, +} + +#[derive(PartialEq, Eq)] +pub(crate) struct SimTraceStateOpaqueSimValue { + bits: BitVec, + sim_only: Vec, +} + +impl fmt::Debug for SimTraceStateOpaqueSimValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { bits, sim_only } = self; + f.debug_struct("SimTraceStateOpaqueSimValue") + .field("bits", &BitSliceWriteWithBase(bits)) + .field("sim_only", sim_only) + .finish() + } +} + +impl Clone for SimTraceStateOpaqueSimValue { + fn clone(&self) -> Self { + Self { + bits: self.bits.clone(), + sim_only: self.sim_only.clone(), + } + } + fn clone_from(&mut self, source: &Self) { + let Self { bits, sim_only } = self; + bits.clone_from(&source.bits); + sim_only.clone_from(&source.sim_only); + } } #[derive(PartialEq, Eq)] @@ -941,6 +993,7 @@ pub(crate) enum SimTraceState { Bits(BitVec), SimOnly(DynSimOnlyValue), PhantomConst, + OpaqueSimValue(Option), } impl Clone for SimTraceState { @@ -949,6 +1002,7 @@ impl Clone for SimTraceState { Self::Bits(v) => Self::Bits(v.clone()), Self::SimOnly(v) => Self::SimOnly(v.clone()), Self::PhantomConst => Self::PhantomConst, + Self::OpaqueSimValue(v) => Self::OpaqueSimValue(v.clone()), } } fn clone_from(&mut self, source: &Self) { @@ -956,6 +1010,9 @@ impl Clone for SimTraceState { (SimTraceState::Bits(dest), SimTraceState::Bits(source)) => { dest.clone_from_bitslice(source); } + (SimTraceState::OpaqueSimValue(dest), SimTraceState::OpaqueSimValue(source)) => { + dest.clone_from(source); + } _ => *self = source.clone(), } } @@ -990,6 +1047,20 @@ impl SimTraceState { unreachable!() } } + fn unwrap_opaque_sim_value_ref(&self) -> &Option { + if let SimTraceState::OpaqueSimValue(v) = self { + v + } else { + unreachable!() + } + } + fn unwrap_opaque_sim_value_mut(&mut self) -> &mut Option { + if let SimTraceState::OpaqueSimValue(v) = self { + v + } else { + unreachable!() + } + } } impl fmt::Debug for SimTraceState { @@ -998,6 +1069,7 @@ impl fmt::Debug for SimTraceState { SimTraceState::Bits(v) => BitSliceWriteWithBase(v).fmt(f), SimTraceState::SimOnly(v) => v.fmt(f), SimTraceState::PhantomConst => f.debug_tuple("PhantomConst").finish(), + SimTraceState::OpaqueSimValue(v) => v.fmt(f), } } } @@ -1026,6 +1098,7 @@ impl SimTraceKind { } SimTraceKind::PhantomConst { .. } => SimTraceState::PhantomConst, SimTraceKind::SimOnly { index: _, ty } => SimTraceState::SimOnly(ty.default_value()), + SimTraceKind::TraceAsString { .. } => SimTraceState::OpaqueSimValue(None), } } } @@ -1273,7 +1346,8 @@ impl SimulationModuleState { | CanonicalType::Reset(_) | CanonicalType::Clock(_) | CanonicalType::PhantomConst(_) - | CanonicalType::DynSimOnly(_) => unreachable!(), + | CanonicalType::DynSimOnly(_) + | CanonicalType::TraceAsString(_) => unreachable!(), CanonicalType::AsyncReset(_) => true, CanonicalType::SyncReset(_) => false, } @@ -2181,6 +2255,9 @@ impl SimulationImpl { SimTraceKind::SimOnly { .. } => { trace_writer.set_signal_sim_only_value(id, state.unwrap_sim_only_ref())? } + SimTraceKind::TraceAsString { layout, range, ty } => { + trace_writer.set_signal_sim_only_value(id, state.unwrap_sim_only_ref())? + } } } Ok(trace_writer) diff --git a/crates/fayalite/src/sim/compiler.rs b/crates/fayalite/src/sim/compiler.rs index dbdbffb..c695fe0 100644 --- a/crates/fayalite/src/sim/compiler.rs +++ b/crates/fayalite/src/sim/compiler.rs @@ -29,7 +29,8 @@ use crate::{ TraceBool, TraceBundle, TraceClock, TraceDecl, TraceEnumDiscriminant, TraceEnumWithFields, TraceFieldlessEnum, TraceInstance, TraceLocation, TraceMem, TraceMemPort, TraceMemoryId, TraceMemoryLocation, TraceModule, TraceModuleIO, TracePhantomConst, TraceReg, TraceSInt, - TraceScalarId, TraceScope, TraceSimOnly, TraceSyncReset, TraceUInt, TraceWire, + TraceScalarId, TraceScope, TraceSimOnly, TraceSyncReset, TraceTraceAsString, TraceUInt, + TraceWire, interpreter::{ self, Insn, InsnField, InsnFieldKind, InsnFieldType, InsnOrLabel, Insns, InsnsBuilding, InsnsBuildingDone, InsnsBuildingKind, Label, PrefixLinesWrapper, SmallUInt, @@ -265,6 +266,7 @@ impl CompiledTypeLayout { body: CompiledTypeLayoutBody::Scalar, } } + CanonicalType::TraceAsString(ty) => CompiledTypeLayout::get(ty.inner_ty()), } } } @@ -1977,6 +1979,38 @@ macro_rules! impl_compiler { flow, } .into(), + CanonicalType::TraceAsString(ty) => { + let location = match target { + MakeTraceDeclTarget::Expr(target) => { + let compiled_value = self.compile_expr(instantiated_module, target); + let compiled_value = + self.compiled_expr_to_value(compiled_value, source_location); + TraceLocation::Scalar(self.new_sim_trace(SimTraceKind::TraceAsString { + ty, + })) + } + MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start, + ty: _, + } => TraceLocation::Memory(TraceMemoryLocation { + id, + depth, + stride, + start, + len: ty.type_properties().bit_width, + }), + }; + TraceTraceAsString { + location, + name, + ty, + flow, + } + .into() + } } } fn compiled_expr_to_value( diff --git a/crates/fayalite/src/sim/value.rs b/crates/fayalite/src/sim/value.rs index eaa3f8d..79515ac 100644 --- a/crates/fayalite/src/sim/value.rs +++ b/crates/fayalite/src/sim/value.rs @@ -1098,7 +1098,8 @@ impl ToSimValueWithType for bool { | CanonicalType::Enum(_) | CanonicalType::Bundle(_) | CanonicalType::PhantomConst(_) - | CanonicalType::DynSimOnly(_) => { + | CanonicalType::DynSimOnly(_) + | CanonicalType::TraceAsString(_) => { panic!("can't create SimValue from bool: expected value of type: {ty:?}"); } CanonicalType::Bool(_) diff --git a/crates/fayalite/src/ty.rs b/crates/fayalite/src/ty.rs index ed0ba03..54099e3 100644 --- a/crates/fayalite/src/ty.rs +++ b/crates/fayalite/src/ty.rs @@ -6,14 +6,18 @@ use crate::{ bundle::Bundle, clock::Clock, enum_::Enum, - expr::Expr, + expr::{Expr, ToExpr, ValueType, ops}, int::{Bool, SInt, UInt, UIntValue}, - intern::{Intern, Interned}, + intern::{Intern, Interned, LazyInterned, SupportsPtrEqWithTypeId}, phantom_const::PhantomConst, reset::{AsyncReset, Reset, SyncReset}, - sim::value::{DynSimOnly, DynSimOnlyValue, SimValue, ToSimValueWithType}, + sim::value::{DynSimOnly, DynSimOnlyValue, SimValue, ToSimValue, ToSimValueWithType}, source_location::SourceLocation, - util::{ConstUsize, slice_range, try_slice_range}, + util::{ + ConstUsize, + serde_by_id::{SerdeByIdProperties, SerdeByIdTable, SerdeByIdTrait}, + slice_range, try_slice_range, + }, }; use bitvec::{slice::BitSlice, vec::BitVec}; use serde::{Deserialize, Deserializer, Serialize, Serializer, de::DeserializeOwned}; @@ -69,6 +73,7 @@ pub enum CanonicalType { Clock(Clock), PhantomConst(PhantomConst), DynSimOnly(DynSimOnly), + TraceAsString(TraceAsString), } impl fmt::Debug for CanonicalType { @@ -86,6 +91,7 @@ impl fmt::Debug for CanonicalType { Self::Clock(v) => v.fmt(f), Self::PhantomConst(v) => v.fmt(f), Self::DynSimOnly(v) => v.fmt(f), + Self::TraceAsString(v) => v.fmt(f), } } } @@ -123,6 +129,7 @@ impl CanonicalType { CanonicalType::Clock(v) => v.type_properties(), CanonicalType::PhantomConst(v) => v.type_properties(), CanonicalType::DynSimOnly(v) => v.type_properties(), + CanonicalType::TraceAsString(v) => v.type_properties(), } } pub fn is_passive(self) -> bool { @@ -217,11 +224,37 @@ impl CanonicalType { }; lhs.can_connect(rhs) } + CanonicalType::TraceAsString(lhs) => { + let CanonicalType::TraceAsString(rhs) = rhs else { + return false; + }; + lhs.can_connect(rhs) + } } } pub(crate) fn as_serde_unexpected_str(self) -> &'static str { serde_impls::SerdeCanonicalType::from(self).as_serde_unexpected_str() } + /// Unwrap transparent types until reaching a non-transparent type. Currently [`TraceAsString`] is the only transparent type. + pub fn unwrap_transparent_types(mut self) -> Self { + loop { + self = match self { + Self::UInt(_) + | Self::SInt(_) + | Self::Bool(_) + | Self::Array(_) + | Self::Enum(_) + | Self::Bundle(_) + | Self::AsyncReset(_) + | Self::SyncReset(_) + | Self::Reset(_) + | Self::Clock(_) + | Self::PhantomConst(_) + | Self::DynSimOnly(_) => return self, + Self::TraceAsString(ty) => ty.inner_ty(), + }; + } + } } pub trait MatchVariantAndInactiveScope: Sized { @@ -328,6 +361,7 @@ impl_base_type!(Reset); impl_base_type!(Clock); impl_base_type!(PhantomConst); impl_base_type!(DynSimOnly); +impl_base_type!(TraceAsString); impl_base_type_serde!(Bool, "a Bool"); impl_base_type_serde!(Enum, "an Enum"); @@ -336,6 +370,7 @@ impl_base_type_serde!(AsyncReset, "an AsyncReset"); impl_base_type_serde!(SyncReset, "a SyncReset"); impl_base_type_serde!(Reset, "a Reset"); impl_base_type_serde!(Clock, "a Clock"); +impl_base_type_serde!(TraceAsString, "a TraceAsString"); impl sealed::BaseTypeSealed for CanonicalType {} @@ -473,6 +508,7 @@ impl Type for CanonicalType { CanonicalType::Clock(v) => v.mask_type().canonical(), CanonicalType::PhantomConst(v) => v.mask_type().canonical(), CanonicalType::DynSimOnly(v) => v.mask_type().canonical(), + CanonicalType::TraceAsString(v) => v.mask_type().canonical(), } } fn canonical(&self) -> CanonicalType { @@ -1143,3 +1179,347 @@ impl Index for AsMaskWithoutGenerics { Interned::into_inner(Intern::intern_sized(ty.mask_type())) } } + +trait TraceAsStringTrait: fmt::Debug + 'static + Send + Sync + SupportsPtrEqWithTypeId { + fn trace_fmt(&self, opaque: OpaqueSimValueSlice<'_>, f: &mut fmt::Formatter<'_>) + -> fmt::Result; + fn serde_by_id_properties(&self) -> SerdeByIdProperties> { + SerdeByIdProperties::of::() + } +} + +impl TraceAsStringTrait for T { + fn trace_fmt( + &self, + opaque: OpaqueSimValueSlice<'_>, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + fmt::Debug::fmt(&Type::sim_value_from_opaque(self, opaque), f) + } +} + +impl crate::intern::InternedCompare for dyn TraceAsStringTrait { + type InternedCompareKey = crate::intern::PtrEqWithTypeId; + + fn interned_compare_key_ref(this: &Self) -> Self::InternedCompareKey { + this.get_ptr_eq_with_type_id() + } +} + +impl SerdeByIdTrait for Interned { + fn serde_by_id_properties(&self) -> SerdeByIdProperties { + TraceAsStringTrait::serde_by_id_properties(&**self) + } + + fn static_table() -> &'static SerdeByIdTable { + static TABLE: SerdeByIdTable> = SerdeByIdTable::new(); + &TABLE + } + + const NAME: &'static str = "dyn TraceAsStringTrait"; +} + +/// When running the fayalite simulator, outputs a single string signal containing a formatted version of the inner value (uses [`fmt::Debug`] by default). +/// This is a transparent type, meaning [`CanonicalType::unwrap_transparent_types`] will unwrap this type. +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct TraceAsString { + inner_ty: LazyInterned, + trace_as_string: LazyInterned, +} + +#[expect(non_upper_case_globals)] +pub const TraceAsString: TraceAsStringWithoutGenerics = TraceAsStringWithoutGenerics; + +impl fmt::Debug for TraceAsString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + inner_ty, + trace_as_string: _, + } = self; + f.debug_struct("TraceAsString") + .field("inner_ty", &inner_ty.interned()) + .finish_non_exhaustive() + } +} + +impl TraceAsString { + pub fn new(inner_ty: T) -> Self { + Self::new_interned(inner_ty.intern_sized()) + } + pub fn new_interned(inner_ty: Interned) -> Self { + Self { + inner_ty: LazyInterned::Interned(inner_ty), + trace_as_string: LazyInterned::Interned(Interned::cast_unchecked( + inner_ty, + |v| -> &dyn TraceAsStringTrait { v }, + )), + } + } + pub fn interned_inner_ty(self) -> Interned { + self.inner_ty.interned() + } + pub fn inner_ty(self) -> T { + *self.interned_inner_ty() + } + pub fn canonical_trace_as_string(self) -> TraceAsString { + let Self { + inner_ty, + trace_as_string, + } = self; + TraceAsString { + inner_ty: LazyInterned::Interned(inner_ty.interned().canonical().intern_sized()), + trace_as_string, + } + } + pub fn from_canonical_trace_as_string(canonical: TraceAsString) -> Self { + let TraceAsString { + inner_ty, + trace_as_string, + } = canonical; + Self { + inner_ty: LazyInterned::Interned( + T::from_canonical(*inner_ty.interned()).intern_sized(), + ), + trace_as_string, + } + } + pub fn type_properties(self) -> TypeProperties { + self.interned_inner_ty().canonical().type_properties() + } + pub fn can_connect(self, rhs: TraceAsString) -> bool { + self.interned_inner_ty() + .canonical() + .can_connect(rhs.interned_inner_ty().canonical()) + } + pub fn trace_fmt( + self, + opaque: OpaqueSimValueSlice<'_>, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + self.trace_as_string.interned().trace_fmt(opaque, f) + } +} + +impl SimValueDebug for TraceAsString { + fn sim_value_debug( + value: &::SimValue, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + T::sim_value_debug(value, f) + } +} + +impl Type for TraceAsString { + type BaseType = TraceAsString; + type MaskType = T::MaskType; + type SimValue = TraceAsStringSimValue; + type MatchVariant = T::MatchVariant; + type MatchActiveScope = T::MatchActiveScope; + type MatchVariantAndInactiveScope = T::MatchVariantAndInactiveScope; + type MatchVariantsIter = T::MatchVariantsIter; + + fn match_variants( + this: Expr, + source_location: SourceLocation, + ) -> Self::MatchVariantsIter { + T::match_variants( + ops::TraceAsStringAsInner::new(this).to_expr(), + source_location, + ) + } + + fn mask_type(&self) -> Self::MaskType { + self.inner_ty.mask_type() + } + + fn canonical(&self) -> CanonicalType { + CanonicalType::TraceAsString(self.canonical_trace_as_string()) + } + + fn from_canonical(canonical_type: CanonicalType) -> Self { + let CanonicalType::TraceAsString(canonical) = canonical_type else { + panic!("expected TraceAsString"); + }; + Self::from_canonical_trace_as_string(canonical) + } + + fn source_location() -> SourceLocation { + SourceLocation::builtin() + } + + fn sim_value_from_opaque(&self, opaque: OpaqueSimValueSlice<'_>) -> Self::SimValue { + TraceAsStringSimValue { + inner: self.inner_ty.sim_value_from_opaque(opaque), + } + } + + fn sim_value_clone_from_opaque( + &self, + value: &mut Self::SimValue, + opaque: OpaqueSimValueSlice<'_>, + ) { + self.inner_ty + .sim_value_clone_from_opaque(&mut value.inner, opaque); + } + + fn sim_value_to_opaque<'w>( + &self, + value: &Self::SimValue, + writer: OpaqueSimValueWriter<'w>, + ) -> OpaqueSimValueWritten<'w> { + self.inner_ty.sim_value_to_opaque(&value.inner, writer) + } +} + +struct TraceAsStringStaticTypeHelper(PhantomData); + +impl Default for TraceAsStringStaticTypeHelper { + fn default() -> Self { + Self(PhantomData) + } +} + +impl From> for Interned { + fn from(_value: TraceAsStringStaticTypeHelper) -> Self { + Interned::cast_unchecked(T::TYPE.intern_sized(), |v| -> &dyn TraceAsStringTrait { v }) + } +} + +impl Default for TraceAsString { + fn default() -> Self { + Self::TYPE + } +} + +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()) + } +} + +impl StaticType for TraceAsString { + const TYPE: Self = Self { + inner_ty: LazyInterned::new_const::>(), + trace_as_string: LazyInterned::new_const::>(), + }; + const MASK_TYPE: Self::MaskType = T::MASK_TYPE; + const TYPE_PROPERTIES: TypeProperties = T::TYPE_PROPERTIES; + const MASK_TYPE_PROPERTIES: TypeProperties = T::MASK_TYPE_PROPERTIES; +} + +#[doc(hidden)] +pub struct TraceAsStringWithoutGenerics; + +impl Index for TraceAsStringWithoutGenerics { + type Output = TraceAsString; + + fn index(&self, inner_ty: T) -> &Self::Output { + Interned::into_inner(TraceAsString::new(inner_ty).intern_sized()) + } +} + +#[derive(Copy, Clone, Eq, Ord, Hash, Default, Serialize, Deserialize)] +pub struct TraceAsStringSimValue { + inner: T, +} + +impl TraceAsStringSimValue { + pub const fn new(inner: T) -> Self { + Self { inner } + } + pub fn into_inner(this: Self) -> T { + let Self { inner } = this; + inner + } +} + +impl ValueType for TraceAsStringSimValue { + type Type = TraceAsString; + type ValueCategory = T::ValueCategory; + + fn ty(&self) -> Self::Type { + TraceAsString::new(self.inner.ty()) + } +} + +impl ToExpr for TraceAsStringSimValue { + fn to_expr(&self) -> Expr { + let inner = self.inner.to_expr(); + ops::AsTraceAsString::new(Expr::canonical(inner), TraceAsString::new(inner.ty())).to_expr() + } +} + +impl, Ty: Type> ToSimValueWithType> + for TraceAsStringSimValue +{ + fn to_sim_value_with_type(&self, ty: TraceAsString) -> SimValue> { + let inner = self.inner.to_sim_value_with_type(ty.inner_ty()); + let inner = SimValue::into_value(inner); + SimValue::from_value(ty, TraceAsStringSimValue { inner }) + } + fn into_sim_value_with_type(self, ty: TraceAsString) -> SimValue> { + let inner = self.inner.into_sim_value_with_type(ty.inner_ty()); + let inner = SimValue::into_value(inner); + SimValue::from_value(ty, TraceAsStringSimValue { inner }) + } +} + +impl ToSimValue for TraceAsStringSimValue { + fn to_sim_value(&self) -> SimValue { + self.to_sim_value_with_type(self.ty()) + } + fn into_sim_value(self) -> SimValue { + let ty = self.ty(); + self.into_sim_value_with_type(ty) + } +} + +impl std::ops::Deref for TraceAsStringSimValue { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl std::ops::DerefMut for TraceAsStringSimValue { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl fmt::Debug for TraceAsStringSimValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { inner } = self; + fmt::Debug::fmt(inner, f) + } +} + +impl fmt::Display for TraceAsStringSimValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { inner } = self; + fmt::Display::fmt(inner, f) + } +} + +impl, U> PartialOrd> for TraceAsStringSimValue { + fn partial_cmp(&self, other: &TraceAsStringSimValue) -> Option { + let Self { inner } = self; + inner.partial_cmp(&other.inner) + } +} + +impl, U> PartialEq> for TraceAsStringSimValue { + fn eq(&self, other: &TraceAsStringSimValue) -> bool { + let Self { inner } = self; + *inner == other.inner + } +} diff --git a/crates/fayalite/src/ty/serde_impls.rs b/crates/fayalite/src/ty/serde_impls.rs index af324f9..126c4b0 100644 --- a/crates/fayalite/src/ty/serde_impls.rs +++ b/crates/fayalite/src/ty/serde_impls.rs @@ -12,7 +12,8 @@ use crate::{ prelude::PhantomConst, reset::{AsyncReset, Reset, SyncReset}, sim::value::DynSimOnly, - ty::{BaseType, CanonicalType}, + ty::{BaseType, CanonicalType, TraceAsString, TraceAsStringTrait}, + util::serde_by_id::SerdeById, }; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -65,6 +66,10 @@ pub(crate) enum SerdeCanonicalType< Clock, PhantomConst(ThePhantomConst), DynSimOnly(DynSimOnly), + TraceAsString { + inner_ty: Interned, + trace_as_string: SerdeById>, + }, } impl SerdeCanonicalType { @@ -82,6 +87,7 @@ impl SerdeCanonicalType "a Clock", Self::PhantomConst(_) => "a PhantomConst", Self::DynSimOnly(_) => "a SimOnlyValue", + Self::TraceAsString { .. } => "a TraceAsString", } } } @@ -109,6 +115,15 @@ impl From for SerdeCanonicalType { CanonicalType::Clock(Clock {}) => Self::Clock, CanonicalType::PhantomConst(ty) => Self::PhantomConst(SerdePhantomConst(ty.get())), CanonicalType::DynSimOnly(ty) => Self::DynSimOnly(ty), + CanonicalType::TraceAsString(TraceAsString { + inner_ty, + trace_as_string, + }) => Self::TraceAsString { + inner_ty: inner_ty.interned(), + trace_as_string: SerdeById { + inner: trace_as_string.interned(), + }, + }, } } } @@ -130,6 +145,13 @@ impl From for CanonicalType { Self::PhantomConst(PhantomConst::new_interned(value.0)) } SerdeCanonicalType::DynSimOnly(value) => Self::DynSimOnly(value), + SerdeCanonicalType::TraceAsString { + inner_ty, + trace_as_string, + } => Self::TraceAsString(TraceAsString { + inner_ty: crate::intern::LazyInterned::Interned(inner_ty), + trace_as_string: crate::intern::LazyInterned::Interned(trace_as_string.inner), + }), } } } diff --git a/crates/fayalite/src/util.rs b/crates/fayalite/src/util.rs index f1457de..6845d3c 100644 --- a/crates/fayalite/src/util.rs +++ b/crates/fayalite/src/util.rs @@ -46,3 +46,4 @@ pub(crate) use misc::{InternedStrCompareAsStr, chain, copy_le_bytes_to_bitslice} pub mod job_server; pub mod prefix_sum; pub mod ready_valid; +pub(crate) mod serde_by_id; diff --git a/crates/fayalite/src/util/serde_by_id.rs b/crates/fayalite/src/util/serde_by_id.rs new file mode 100644 index 0000000..3db4ab6 --- /dev/null +++ b/crates/fayalite/src/util/serde_by_id.rs @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::util::HashMap; +use hashbrown::hash_map::Entry; +use serde::{Deserialize, Serialize, de::Error}; +use std::{ + any::TypeId, + borrow::Cow, + fmt::Write, + hash::{BuildHasher, Hash, Hasher}, + marker::PhantomData, + sync::Mutex, +}; + +pub(crate) struct SerdeByIdProperties { + type_id: TypeId, + type_name: &'static str, + _phantom: PhantomData T>, +} + +impl Clone for SerdeByIdProperties { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for SerdeByIdProperties {} + +impl SerdeByIdProperties { + pub fn of() -> Self { + Self { + type_id: TypeId::of::(), + type_name: std::any::type_name::(), + _phantom: PhantomData, + } + } +} + +pub(crate) trait SerdeByIdTrait: Hash + Eq + Clone + 'static + Send { + fn serde_by_id_properties(&self) -> SerdeByIdProperties; + fn static_table() -> &'static SerdeByIdTable; + const NAME: &'static str; +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)] +#[serde(transparent)] +struct SerdeRandomId([u32; 4]); + +#[derive(Serialize, Deserialize)] +pub(crate) struct SerdeId<'a, T: SerdeByIdTrait> { + random_id: SerdeRandomId, + #[serde(borrow)] + type_name: Cow<'a, str>, + #[serde(skip)] + _phantom: PhantomData T>, +} + +impl<'a, T: SerdeByIdTrait> Clone for SerdeId<'a, T> { + fn clone(&self) -> Self { + Self { + random_id: self.random_id, + type_name: self.type_name.clone(), + _phantom: PhantomData, + } + } +} + +impl<'a, T: SerdeByIdTrait> Eq for SerdeId<'a, T> {} + +impl<'a, 'b, T: SerdeByIdTrait> PartialEq> for SerdeId<'a, T> { + fn eq(&self, other: &SerdeId<'b, T>) -> bool { + let Self { + random_id, + type_name, + _phantom: _, + } = self; + *random_id == other.random_id && *type_name == other.type_name + } +} + +impl<'a, T: SerdeByIdTrait> Hash for SerdeId<'a, T> { + fn hash(&self, state: &mut H) { + let Self { + random_id, + type_name: _, + _phantom: _, + } = self; + random_id.hash(state); + } +} + +struct SerdeByIdTableRest { + from_serde: HashMap, T>, + serde_id_random_state: std::hash::RandomState, + buffer: String, +} + +impl Default for SerdeByIdTableRest { + fn default() -> Self { + Self { + from_serde: Default::default(), + serde_id_random_state: Default::default(), + buffer: Default::default(), + } + } +} + +impl SerdeByIdTableRest { + fn add_new(&mut self, value: T) -> SerdeId<'static, T> { + let properties = value.serde_by_id_properties(); + let mut try_number = 0u64; + let mut hasher = self.serde_id_random_state.build_hasher(); + // extract more bits of randomness from TypeId -- its Hash impl only hashes 64-bits + write!(self.buffer, "{:?}", properties.type_id).expect("shouldn't ever fail"); + self.buffer.hash(&mut hasher); + loop { + let mut hasher = hasher.clone(); + try_number.hash(&mut hasher); + try_number += 1; + let key = SerdeId { + random_id: SerdeRandomId(std::array::from_fn(|i| { + let mut hasher = hasher.clone(); + i.hash(&mut hasher); + hasher.finish() as u32 + })), + type_name: Cow::Borrowed(properties.type_name), + _phantom: PhantomData, + }; + match self.from_serde.entry(key) { + Entry::Occupied(_) => continue, + Entry::Vacant(e) => { + let key = e.key().clone(); + e.insert(value); + return key; + } + } + } + } +} + +pub(crate) struct SerdeByIdTableMut { + to_serde: HashMap>, + rest: SerdeByIdTableRest, +} + +impl Default for SerdeByIdTableMut { + fn default() -> Self { + Self { + to_serde: Default::default(), + rest: Default::default(), + } + } +} + +impl SerdeByIdTableMut { + pub(crate) fn to_serde(&mut self, value: &T) -> SerdeId<'static, T> { + if let Some(retval) = self.to_serde.get(value) { + return retval.clone(); + } + self.to_serde_insert(value) + } + #[cold] + fn to_serde_insert(&mut self, value: &T) -> SerdeId<'static, T> { + let value = value.clone(); + let retval = self.rest.add_new(value.clone()); + self.to_serde.insert(value, retval.clone()); + retval + } + pub(crate) fn from_serde(&self, id: &SerdeId<'_, T>) -> Option { + self.rest.from_serde.get(id).cloned() + } +} + +pub(crate) struct SerdeByIdTable(Mutex>>); + +impl SerdeByIdTable { + pub(crate) const fn new() -> Self { + Self(Mutex::new(None)) + } + pub(crate) fn to_serde(&self, value: &T) -> SerdeId<'static, T> { + self.0 + .lock() + .expect("shouldn't be poison") + .get_or_insert_with( + #[cold] + || Default::default(), + ) + .to_serde(value) + } + pub(crate) fn from_serde(&self, id: &SerdeId<'_, T>) -> Option { + self.0 + .lock() + .expect("shouldn't be poison") + .get_or_insert_with( + #[cold] + || Default::default(), + ) + .from_serde(id) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default, Ord, PartialOrd)] +pub(crate) struct SerdeById { + pub(crate) inner: T, +} + +impl<'de, T: SerdeByIdTrait> Deserialize<'de> for SerdeById { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let id = SerdeId::deserialize(deserializer)?; + let inner = T::static_table().from_serde(&id).ok_or_else(|| { + D::Error::custom(format_args!( + "doesn't match any {} that was serialized this time this program was run: type_name={:?}", + T::NAME, + id.type_name, + )) + })?; + Ok(Self { inner }) + } +} + +impl Serialize for SerdeById { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + T::static_table() + .to_serde(&self.inner) + .serialize(serializer) + } +} diff --git a/crates/fayalite/visit_types.json b/crates/fayalite/visit_types.json index a74cef9..2c96dce 100644 --- a/crates/fayalite/visit_types.json +++ b/crates/fayalite/visit_types.json @@ -51,7 +51,8 @@ "Reset": "Visible", "Clock": "Visible", "PhantomConst": "Visible", - "DynSimOnly": "Visible" + "DynSimOnly": "Visible", + "TraceAsString": "Visible" } }, "Bundle": { @@ -1021,6 +1022,27 @@ "fold_where": "T: Fold", "visit_where": "T: Visit" }, + "ops::AsTraceAsString": { + "data": { + "$kind": "Struct", + "$constructor": "ops::AsTraceAsString::new", + "inner()": "Visible", + "ty()": "Visible" + }, + "generics": "", + "fold_where": "T: Fold", + "visit_where": "T: Visit" + }, + "ops::TraceAsStringAsInner": { + "data": { + "$kind": "Struct", + "$constructor": "ops::TraceAsStringAsInner::new", + "arg_typed()": "Visible" + }, + "generics": "", + "fold_where": "T: Fold", + "visit_where": "T: Visit" + }, "BlockId": { "data": { "$kind": "Opaque" @@ -1306,6 +1328,11 @@ "data": { "$kind": "ManualImpl" } + }, + "TraceAsString": { + "data": { + "$kind": "ManualImpl" + } } } } \ No newline at end of file