diff --git a/.forgejo/workflows/deps.yml b/.forgejo/workflows/deps.yml index ffaca53..b29723c 100644 --- a/.forgejo/workflows/deps.yml +++ b/.forgejo/workflows/deps.yml @@ -12,10 +12,10 @@ jobs: outputs: cache-primary-key: ${{ steps.restore-deps.outputs.cache-primary-key }} steps: - - uses: https://code.forgejo.org/actions/checkout@v3 + - uses: https://git.libre-chip.org/mirrors/checkout@v3 with: fetch-depth: 0 - - uses: https://code.forgejo.org/actions/cache/restore@v3 + - uses: https://git.libre-chip.org/mirrors/cache/restore@v3 id: restore-deps with: path: deps @@ -58,19 +58,19 @@ jobs: - name: Get SymbiYosys if: steps.restore-deps.outputs.cache-hit != 'true' run: | - git clone --depth=1 --branch=yosys-0.45 https://github.com/YosysHQ/sby.git deps/sby + git clone --depth=1 --branch=yosys-0.45 https://git.libre-chip.org/mirrors/sby deps/sby - name: Build Z3 if: steps.restore-deps.outputs.cache-hit != 'true' run: | - git clone --depth=1 --recursive --branch=z3-4.13.3 https://github.com/Z3Prover/z3.git deps/z3 + git clone --depth=1 --recursive --branch=z3-4.13.3 https://git.libre-chip.org/mirrors/z3 deps/z3 (cd deps/z3; PYTHON=python3 ./configure --prefix=/usr/local) make -C deps/z3/build -j"$(nproc)" - name: Build Yosys if: steps.restore-deps.outputs.cache-hit != 'true' run: | - git clone --depth=1 --recursive --branch=0.45 https://github.com/YosysHQ/yosys.git deps/yosys + git clone --depth=1 --recursive --branch=0.45 https://git.libre-chip.org/mirrors/yosys deps/yosys make -C deps/yosys -j"$(nproc)" - - uses: https://code.forgejo.org/actions/cache/save@v3 + - uses: https://git.libre-chip.org/mirrors/cache/save@v3 if: steps.restore-deps.outputs.cache-hit != 'true' with: path: deps diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index 969d691..b2d03ba 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -9,7 +9,7 @@ jobs: runs-on: debian-12 needs: deps steps: - - uses: https://code.forgejo.org/actions/checkout@v3 + - uses: https://git.libre-chip.org/mirrors/checkout@v3 with: fetch-depth: 0 - run: | @@ -38,10 +38,10 @@ jobs: z3 \ zlib1g-dev - run: | - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.80.1 + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.82.0 source "$HOME/.cargo/env" echo "$PATH" >> "$GITHUB_PATH" - - uses: https://code.forgejo.org/actions/cache/restore@v3 + - uses: https://git.libre-chip.org/mirrors/cache/restore@v3 with: path: deps key: ${{ needs.deps.outputs.cache-primary-key }} @@ -52,9 +52,11 @@ jobs: make -C deps/yosys install export PATH="$(realpath deps/firtool/bin):$PATH" echo "$PATH" >> "$GITHUB_PATH" - - uses: https://github.com/Swatinem/rust-cache@v2 + - uses: https://git.libre-chip.org/mirrors/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/master' }} - run: cargo test - run: cargo build --tests --features=unstable-doc + - run: cargo test --doc --features=unstable-doc - run: cargo doc --features=unstable-doc + - run: FAYALITE_TEST_HASHER=always_zero cargo test --test=module --features=unstable-doc,unstable-test-hasher diff --git a/Cargo.lock b/Cargo.lock index 500bd34..e0c32e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,18 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "ahash" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "allocator-api2" version = "0.2.16" @@ -301,7 +289,7 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fayalite" -version = "0.2.1" +version = "0.3.0" dependencies = [ "bitvec", "blake3", @@ -315,23 +303,25 @@ dependencies = [ "num-bigint", "num-traits", "os_pipe", + "petgraph", "serde", "serde_json", "tempfile", "trybuild", + "vec_map", "which", ] [[package]] name = "fayalite-proc-macros" -version = "0.2.1" +version = "0.3.0" dependencies = [ "fayalite-proc-macros-impl", ] [[package]] name = "fayalite-proc-macros-impl" -version = "0.2.1" +version = "0.3.0" dependencies = [ "base16ct", "num-bigint", @@ -345,7 +335,7 @@ dependencies = [ [[package]] name = "fayalite-visit-gen" -version = "0.2.1" +version = "0.3.0" dependencies = [ "indexmap", "prettyplease", @@ -357,6 +347,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "funty" version = "2.0.0" @@ -392,12 +394,13 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ - "ahash", "allocator-api2", + "equivalent", + "foldhash", ] [[package]] @@ -423,9 +426,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.2.6" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown", @@ -472,11 +475,10 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -515,6 +517,18 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "petgraph" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a98c6720655620a521dcc722d0ad66cd8afd5d86e34a89ef691c50b7b24de06" +dependencies = [ + "fixedbitset", + "hashbrown", + "indexmap", + "serde", +] + [[package]] name = "prettyplease" version = "0.2.20" @@ -527,9 +541,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.83" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -631,9 +645,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.66" +version = "2.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" dependencies = [ "proc-macro2", "quote", @@ -720,6 +734,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.4" @@ -871,23 +891,3 @@ checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] - -[[package]] -name = "zerocopy" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/Cargo.toml b/Cargo.toml index b6b8616..d681425 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,38 +5,40 @@ resolver = "2" members = ["crates/*"] [workspace.package] -version = "0.2.1" +version = "0.3.0" license = "LGPL-3.0-or-later" edition = "2021" repository = "https://git.libre-chip.org/libre-chip/fayalite" keywords = ["hdl", "hardware", "semiconductors", "firrtl", "fpga"] categories = ["simulation", "development-tools", "compilers"] -rust-version = "1.80.1" +rust-version = "1.82.0" [workspace.dependencies] -fayalite-proc-macros = { version = "=0.2.1", path = "crates/fayalite-proc-macros" } -fayalite-proc-macros-impl = { version = "=0.2.1", path = "crates/fayalite-proc-macros-impl" } -fayalite-visit-gen = { version = "=0.2.1", path = "crates/fayalite-visit-gen" } +fayalite-proc-macros = { version = "=0.3.0", path = "crates/fayalite-proc-macros" } +fayalite-proc-macros-impl = { version = "=0.3.0", path = "crates/fayalite-proc-macros-impl" } +fayalite-visit-gen = { version = "=0.3.0", path = "crates/fayalite-visit-gen" } base16ct = "0.2.0" bitvec = { version = "1.0.1", features = ["serde"] } blake3 = { version = "1.5.4", features = ["serde"] } clap = { version = "4.5.9", features = ["derive", "env", "string"] } ctor = "0.2.8" eyre = "0.6.12" -hashbrown = "0.14.3" -indexmap = { version = "2.2.6", features = ["serde"] } +hashbrown = "0.15.2" +indexmap = { version = "2.5.0", features = ["serde"] } jobslot = "0.2.19" -num-bigint = "0.4.4" +num-bigint = "0.4.6" num-traits = "0.2.16" os_pipe = "1.2.1" +petgraph = "0.8.1" prettyplease = "0.2.20" proc-macro2 = "1.0.83" quote = "1.0.36" serde = { version = "1.0.202", features = ["derive"] } serde_json = { version = "1.0.117", features = ["preserve_order"] } sha2 = "0.10.8" -syn = { version = "2.0.66", features = ["full", "fold", "visit", "extra-traits"] } +syn = { version = "2.0.93", features = ["full", "fold", "visit", "extra-traits"] } tempfile = "3.10.1" thiserror = "1.0.61" trybuild = "1.0" +vec_map = "0.8.2" which = "6.0.1" diff --git a/crates/fayalite-proc-macros-impl/src/fold.rs b/crates/fayalite-proc-macros-impl/src/fold.rs index 49cc8c1..22e7b82 100644 --- a/crates/fayalite-proc-macros-impl/src/fold.rs +++ b/crates/fayalite-proc-macros-impl/src/fold.rs @@ -220,6 +220,7 @@ forward_fold!(syn::ExprArray => fold_expr_array); forward_fold!(syn::ExprCall => fold_expr_call); forward_fold!(syn::ExprIf => fold_expr_if); forward_fold!(syn::ExprMatch => fold_expr_match); +forward_fold!(syn::ExprMethodCall => fold_expr_method_call); forward_fold!(syn::ExprPath => fold_expr_path); forward_fold!(syn::ExprRepeat => fold_expr_repeat); forward_fold!(syn::ExprStruct => fold_expr_struct); diff --git a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs index 79326e2..7441cb3 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs @@ -30,7 +30,9 @@ pub(crate) struct ParsedBundle { pub(crate) field_flips: Vec>>, pub(crate) mask_type_ident: Ident, pub(crate) mask_type_match_variant_ident: Ident, + pub(crate) mask_type_sim_value_ident: Ident, pub(crate) match_variant_ident: Ident, + pub(crate) sim_value_ident: Ident, pub(crate) builder_ident: Ident, pub(crate) mask_type_builder_ident: Ident, } @@ -83,6 +85,7 @@ impl ParsedBundle { custom_bounds, no_static: _, no_runtime_generics: _, + cmp_eq: _, } = options.body; let mut fields = match fields { syn::Fields::Named(fields) => fields, @@ -124,7 +127,9 @@ impl ParsedBundle { field_flips, mask_type_ident: format_ident!("__{}__MaskType", ident), mask_type_match_variant_ident: format_ident!("__{}__MaskType__MatchVariant", ident), + mask_type_sim_value_ident: format_ident!("__{}__MaskType__SimValue", ident), match_variant_ident: format_ident!("__{}__MatchVariant", ident), + sim_value_ident: format_ident!("__{}__SimValue", ident), mask_type_builder_ident: format_ident!("__{}__MaskType__Builder", ident), builder_ident: format_ident!("__{}__Builder", ident), ident, @@ -426,7 +431,9 @@ impl ToTokens for ParsedBundle { field_flips, mask_type_ident, mask_type_match_variant_ident, + mask_type_sim_value_ident, match_variant_ident, + sim_value_ident, builder_ident, mask_type_builder_ident, } = self; @@ -437,6 +444,7 @@ impl ToTokens for ParsedBundle { custom_bounds: _, no_static, no_runtime_generics, + cmp_eq, } = &options.body; let target = get_target(target, ident); let mut item_attrs = attrs.clone(); @@ -521,7 +529,7 @@ impl ToTokens for ParsedBundle { semi_token: None, } .to_tokens(tokens); - let mut mask_type_match_variant_fields = mask_type_fields; + let mut mask_type_match_variant_fields = mask_type_fields.clone(); for Field { ty, .. } in &mut mask_type_match_variant_fields.named { *ty = parse_quote_spanned! {span=> ::fayalite::expr::Expr<#ty> @@ -563,6 +571,58 @@ impl ToTokens for ParsedBundle { semi_token: None, } .to_tokens(tokens); + let mut mask_type_sim_value_fields = mask_type_fields; + for Field { ty, .. } in &mut mask_type_sim_value_fields.named { + *ty = parse_quote_spanned! {span=> + ::fayalite::sim::value::SimValue<#ty> + }; + } + ItemStruct { + attrs: vec![ + parse_quote_spanned! {span=> + #[::fayalite::__std::prelude::v1::derive( + ::fayalite::__std::fmt::Debug, + ::fayalite::__std::clone::Clone, + )] + }, + parse_quote_spanned! {span=> + #[allow(non_camel_case_types, dead_code)] + }, + ], + vis: vis.clone(), + struct_token: *struct_token, + ident: mask_type_sim_value_ident.clone(), + generics: generics.into(), + fields: Fields::Named(mask_type_sim_value_fields), + semi_token: None, + } + .to_tokens(tokens); + let mut sim_value_fields = FieldsNamed::from(fields.clone()); + for Field { ty, .. } in &mut sim_value_fields.named { + *ty = parse_quote_spanned! {span=> + ::fayalite::sim::value::SimValue<#ty> + }; + } + ItemStruct { + attrs: vec![ + parse_quote_spanned! {span=> + #[::fayalite::__std::prelude::v1::derive( + ::fayalite::__std::fmt::Debug, + ::fayalite::__std::clone::Clone, + )] + }, + parse_quote_spanned! {span=> + #[allow(non_camel_case_types, dead_code)] + }, + ], + vis: vis.clone(), + struct_token: *struct_token, + ident: sim_value_ident.clone(), + generics: generics.into(), + fields: Fields::Named(sim_value_fields), + semi_token: None, + } + .to_tokens(tokens); let this_token = Ident::new("__this", span); let fields_token = Ident::new("__fields", span); let self_token = Token![self](span); @@ -613,6 +673,31 @@ impl ToTokens for ParsedBundle { } }, )); + let sim_value_from_bits_fields = Vec::from_iter(fields.named().into_iter().map(|field| { + let ident: &Ident = field.ident().as_ref().unwrap(); + quote_spanned! {span=> + #ident: v.field_from_bits(), + } + })); + let sim_value_clone_from_bits_fields = + Vec::from_iter(fields.named().into_iter().map(|field| { + let ident: &Ident = field.ident().as_ref().unwrap(); + quote_spanned! {span=> + v.field_clone_from_bits(&mut value.#ident); + } + })); + let sim_value_to_bits_fields = Vec::from_iter(fields.named().into_iter().map(|field| { + let ident: &Ident = field.ident().as_ref().unwrap(); + quote_spanned! {span=> + v.field_to_bits(&value.#ident); + } + })); + let to_sim_value_fields = Vec::from_iter(fields.named().into_iter().map(|field| { + let ident: &Ident = field.ident().as_ref().unwrap(); + quote_spanned! {span=> + #ident: ::fayalite::sim::value::SimValue::ty(&self.#ident), + } + })); let fields_len = fields.named().into_iter().len(); quote_spanned! {span=> #[automatically_derived] @@ -621,6 +706,7 @@ impl ToTokens for ParsedBundle { { type BaseType = ::fayalite::bundle::Bundle; type MaskType = #mask_type_ident #type_generics; + type SimValue = #mask_type_sim_value_ident #type_generics; type MatchVariant = #mask_type_match_variant_ident #type_generics; type MatchActiveScope = (); type MatchVariantAndInactiveScope = ::fayalite::ty::MatchVariantWithoutScope< @@ -658,6 +744,34 @@ impl ToTokens for ParsedBundle { fn source_location() -> ::fayalite::source_location::SourceLocation { ::fayalite::source_location::SourceLocation::caller() } + fn sim_value_from_bits( + &self, + bits: &::fayalite::bitvec::slice::BitSlice, + ) -> ::SimValue { + #![allow(unused_mut, unused_variables)] + let mut v = ::fayalite::bundle::BundleSimValueFromBits::new(*self, bits); + #mask_type_sim_value_ident { + #(#sim_value_from_bits_fields)* + } + } + fn sim_value_clone_from_bits( + &self, + value: &mut ::SimValue, + bits: &::fayalite::bitvec::slice::BitSlice, + ) { + #![allow(unused_mut, unused_variables)] + let mut v = ::fayalite::bundle::BundleSimValueFromBits::new(*self, bits); + #(#sim_value_clone_from_bits_fields)* + } + fn sim_value_to_bits( + &self, + value: &::SimValue, + bits: &mut ::fayalite::bitvec::slice::BitSlice, + ) { + #![allow(unused_mut, unused_variables)] + let mut v = ::fayalite::bundle::BundleSimValueToBits::new(*self, bits); + #(#sim_value_to_bits_fields)* + } } #[automatically_derived] impl #impl_generics ::fayalite::bundle::BundleType for #mask_type_ident #type_generics @@ -689,11 +803,57 @@ impl ToTokens for ParsedBundle { } } #[automatically_derived] + impl #impl_generics ::fayalite::sim::value::ToSimValue for #mask_type_sim_value_ident #type_generics + #where_clause + { + type Type = #mask_type_ident #type_generics; + + fn to_sim_value( + &self, + ) -> ::fayalite::sim::value::SimValue< + ::Type, + > { + let ty = #mask_type_ident { + #(#to_sim_value_fields)* + }; + ::fayalite::sim::value::SimValue::from_value(ty, ::fayalite::__std::clone::Clone::clone(self)) + } + fn into_sim_value( + self, + ) -> ::fayalite::sim::value::SimValue< + ::Type, + > { + let ty = #mask_type_ident { + #(#to_sim_value_fields)* + }; + ::fayalite::sim::value::SimValue::from_value(ty, self) + } + } + #[automatically_derived] + impl #impl_generics ::fayalite::sim::value::ToSimValueWithType<#mask_type_ident #type_generics> + for #mask_type_sim_value_ident #type_generics + #where_clause + { + fn to_sim_value_with_type( + &self, + ty: #mask_type_ident #type_generics, + ) -> ::fayalite::sim::value::SimValue<#mask_type_ident #type_generics> { + ::fayalite::sim::value::SimValue::from_value(ty, ::fayalite::__std::clone::Clone::clone(self)) + } + fn into_sim_value_with_type( + self, + ty: #mask_type_ident #type_generics, + ) -> ::fayalite::sim::value::SimValue<#mask_type_ident #type_generics> { + ::fayalite::sim::value::SimValue::from_value(ty, self) + } + } + #[automatically_derived] impl #impl_generics ::fayalite::ty::Type for #target #type_generics #where_clause { type BaseType = ::fayalite::bundle::Bundle; type MaskType = #mask_type_ident #type_generics; + type SimValue = #sim_value_ident #type_generics; type MatchVariant = #match_variant_ident #type_generics; type MatchActiveScope = (); type MatchVariantAndInactiveScope = ::fayalite::ty::MatchVariantWithoutScope< @@ -733,6 +893,34 @@ impl ToTokens for ParsedBundle { fn source_location() -> ::fayalite::source_location::SourceLocation { ::fayalite::source_location::SourceLocation::caller() } + fn sim_value_from_bits( + &self, + bits: &::fayalite::bitvec::slice::BitSlice, + ) -> ::SimValue { + #![allow(unused_mut, unused_variables)] + let mut v = ::fayalite::bundle::BundleSimValueFromBits::new(*self, bits); + #sim_value_ident { + #(#sim_value_from_bits_fields)* + } + } + fn sim_value_clone_from_bits( + &self, + value: &mut ::SimValue, + bits: &::fayalite::bitvec::slice::BitSlice, + ) { + #![allow(unused_mut, unused_variables)] + let mut v = ::fayalite::bundle::BundleSimValueFromBits::new(*self, bits); + #(#sim_value_clone_from_bits_fields)* + } + fn sim_value_to_bits( + &self, + value: &::SimValue, + bits: &mut ::fayalite::bitvec::slice::BitSlice, + ) { + #![allow(unused_mut, unused_variables)] + let mut v = ::fayalite::bundle::BundleSimValueToBits::new(*self, bits); + #(#sim_value_to_bits_fields)* + } } #[automatically_derived] impl #impl_generics ::fayalite::bundle::BundleType for #target #type_generics @@ -763,8 +951,144 @@ impl ToTokens for ParsedBundle { ::fayalite::intern::Interned::into_inner(::fayalite::intern::Intern::intern_sized(__retval)) } } + #[automatically_derived] + impl #impl_generics ::fayalite::sim::value::ToSimValue for #sim_value_ident #type_generics + #where_clause + { + type Type = #target #type_generics; + + fn to_sim_value( + &self, + ) -> ::fayalite::sim::value::SimValue< + ::Type, + > { + let ty = #target { + #(#to_sim_value_fields)* + }; + ::fayalite::sim::value::SimValue::from_value(ty, ::fayalite::__std::clone::Clone::clone(self)) + } + fn into_sim_value( + self, + ) -> ::fayalite::sim::value::SimValue< + ::Type, + > { + let ty = #target { + #(#to_sim_value_fields)* + }; + ::fayalite::sim::value::SimValue::from_value(ty, self) + } + } + #[automatically_derived] + impl #impl_generics ::fayalite::sim::value::ToSimValueWithType<#target #type_generics> + for #sim_value_ident #type_generics + #where_clause + { + fn to_sim_value_with_type( + &self, + ty: #target #type_generics, + ) -> ::fayalite::sim::value::SimValue<#target #type_generics> { + ::fayalite::sim::value::SimValue::from_value(ty, ::fayalite::__std::clone::Clone::clone(self)) + } + fn into_sim_value_with_type( + self, + ty: #target #type_generics, + ) -> ::fayalite::sim::value::SimValue<#target #type_generics> { + ::fayalite::sim::value::SimValue::from_value(ty, self) + } + } } .to_tokens(tokens); + if let Some((cmp_eq,)) = cmp_eq { + let mut expr_where_clause = + Generics::from(generics) + .where_clause + .unwrap_or_else(|| syn::WhereClause { + where_token: Token![where](span), + predicates: Punctuated::new(), + }); + let mut sim_value_where_clause = expr_where_clause.clone(); + let mut fields_sim_value_eq = vec![]; + let mut fields_cmp_eq = vec![]; + let mut fields_cmp_ne = vec![]; + for field in fields.named() { + let field_ident = field.ident(); + let field_ty = field.ty(); + expr_where_clause + .predicates + .push(parse_quote_spanned! {cmp_eq.span=> + #field_ty: ::fayalite::expr::ops::ExprPartialEq<#field_ty> + }); + sim_value_where_clause + .predicates + .push(parse_quote_spanned! {cmp_eq.span=> + #field_ty: ::fayalite::sim::value::SimValuePartialEq<#field_ty> + }); + fields_sim_value_eq.push(quote_spanned! {span=> + ::fayalite::sim::value::SimValuePartialEq::sim_value_eq(&__lhs.#field_ident, &__rhs.#field_ident) + }); + fields_cmp_eq.push(quote_spanned! {span=> + ::fayalite::expr::ops::ExprPartialEq::cmp_eq(__lhs.#field_ident, __rhs.#field_ident) + }); + fields_cmp_ne.push(quote_spanned! {span=> + ::fayalite::expr::ops::ExprPartialEq::cmp_ne(__lhs.#field_ident, __rhs.#field_ident) + }); + } + let sim_value_eq_body; + let cmp_eq_body; + let cmp_ne_body; + if fields_len == 0 { + sim_value_eq_body = quote_spanned! {span=> + true + }; + cmp_eq_body = quote_spanned! {span=> + ::fayalite::expr::ToExpr::to_expr(&true) + }; + cmp_ne_body = quote_spanned! {span=> + ::fayalite::expr::ToExpr::to_expr(&false) + }; + } else { + sim_value_eq_body = quote_spanned! {span=> + #(#fields_sim_value_eq)&&* + }; + cmp_eq_body = quote_spanned! {span=> + #(#fields_cmp_eq)&* + }; + cmp_ne_body = quote_spanned! {span=> + #(#fields_cmp_ne)|* + }; + }; + quote_spanned! {span=> + #[automatically_derived] + impl #impl_generics ::fayalite::expr::ops::ExprPartialEq for #target #type_generics + #expr_where_clause + { + fn cmp_eq( + __lhs: ::fayalite::expr::Expr, + __rhs: ::fayalite::expr::Expr, + ) -> ::fayalite::expr::Expr<::fayalite::int::Bool> { + #cmp_eq_body + } + fn cmp_ne( + __lhs: ::fayalite::expr::Expr, + __rhs: ::fayalite::expr::Expr, + ) -> ::fayalite::expr::Expr<::fayalite::int::Bool> { + #cmp_ne_body + } + } + #[automatically_derived] + impl #impl_generics ::fayalite::sim::value::SimValuePartialEq for #target #type_generics + #sim_value_where_clause + { + fn sim_value_eq( + __lhs: &::fayalite::sim::value::SimValue, + __rhs: &::fayalite::sim::value::SimValue, + ) -> bool { + #sim_value_eq_body + } + } + } + .to_tokens(tokens); + } if let (None, MaybeParsed::Parsed(generics)) = (no_static, &self.generics) { let static_generics = generics.clone().for_static_type(); let (static_impl_generics, static_type_generics, static_where_clause) = @@ -800,6 +1124,14 @@ impl ToTokens for ParsedBundle { } })); quote_spanned! {span=> + #[automatically_derived] + impl #static_impl_generics ::fayalite::__std::default::Default for #mask_type_ident #static_type_generics + #static_where_clause + { + fn default() -> Self { + ::TYPE + } + } #[automatically_derived] impl #static_impl_generics ::fayalite::ty::StaticType for #mask_type_ident #static_type_generics #static_where_clause @@ -822,6 +1154,15 @@ impl ToTokens for ParsedBundle { }; } #[automatically_derived] + impl #static_impl_generics ::fayalite::__std::default::Default + for #target #static_type_generics + #static_where_clause + { + fn default() -> Self { + ::TYPE + } + } + #[automatically_derived] impl #static_impl_generics ::fayalite::ty::StaticType for #target #static_type_generics #static_where_clause { diff --git a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs index 1d16177..e072135 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs @@ -129,6 +129,9 @@ pub(crate) struct ParsedEnum { pub(crate) brace_token: Brace, pub(crate) variants: Punctuated, pub(crate) match_variant_ident: Ident, + pub(crate) sim_value_ident: Ident, + pub(crate) sim_builder_ident: Ident, + pub(crate) sim_builder_ty_field_ident: Ident, } impl ParsedEnum { @@ -155,7 +158,11 @@ impl ParsedEnum { custom_bounds, no_static: _, no_runtime_generics: _, + cmp_eq, } = options.body; + if let Some((cmp_eq,)) = cmp_eq { + errors.error(cmp_eq, "#[hdl(cmp_eq)] is not yet implemented for enums"); + } attrs.retain(|attr| { if attr.path().is_ident("repr") { errors.error(attr, "#[repr] is not supported on #[hdl] enums"); @@ -186,6 +193,9 @@ impl ParsedEnum { brace_token, variants, match_variant_ident: format_ident!("__{}__MatchVariant", ident), + sim_value_ident: format_ident!("__{}__SimValue", ident), + sim_builder_ident: format_ident!("__{}__SimBuilder", ident), + sim_builder_ty_field_ident: format_ident!("__ty", span = ident.span()), ident, }) } @@ -203,6 +213,9 @@ impl ToTokens for ParsedEnum { brace_token, variants, match_variant_ident, + sim_value_ident, + sim_builder_ident, + sim_builder_ty_field_ident, } = self; let span = ident.span(); let ItemOptions { @@ -211,6 +224,7 @@ impl ToTokens for ParsedEnum { custom_bounds: _, no_static, no_runtime_generics, + cmp_eq: _, // TODO: implement cmp_eq for enums } = &options.body; let target = get_target(target, ident); let mut struct_attrs = attrs.clone(); @@ -404,6 +418,133 @@ impl ToTokens for ParsedEnum { )), } .to_tokens(tokens); + let mut struct_attrs = attrs.clone(); + struct_attrs.push(parse_quote_spanned! {span=> + #[allow(dead_code, non_camel_case_types)] + }); + ItemStruct { + attrs: struct_attrs, + vis: vis.clone(), + struct_token: Token![struct](enum_token.span), + ident: sim_builder_ident.clone(), + generics: generics.into(), + fields: FieldsNamed { + brace_token: *brace_token, + named: Punctuated::from_iter([Field { + attrs: vec![], + vis: Visibility::Inherited, + mutability: FieldMutability::None, + ident: Some(sim_builder_ty_field_ident.clone()), + colon_token: Some(Token![:](span)), + ty: parse_quote_spanned! {span=> + #target #type_generics + }, + }]), + } + .into(), + semi_token: None, + } + .to_tokens(tokens); + let mut enum_attrs = attrs.clone(); + enum_attrs.push(parse_quote_spanned! {span=> + #[::fayalite::__std::prelude::v1::derive( + ::fayalite::__std::fmt::Debug, + ::fayalite::__std::clone::Clone, + )] + }); + enum_attrs.push(parse_quote_spanned! {span=> + #[allow(dead_code, non_camel_case_types)] + }); + let sim_value_has_unknown_variant = !variants.len().is_power_of_two(); + let sim_value_unknown_variant_name = sim_value_has_unknown_variant.then(|| { + let mut name = String::new(); + let unknown = "Unknown"; + loop { + let orig_len = name.len(); + name.push_str(unknown); + if variants.iter().all(|v| v.ident != name) { + break Ident::new(&name, span); + } + name.truncate(orig_len); + name.push('_'); + } + }); + let sim_value_unknown_variant = + sim_value_unknown_variant_name + .as_ref() + .map(|unknown_variant_name| { + Pair::End(parse_quote_spanned! {span=> + #unknown_variant_name(::fayalite::enum_::UnknownVariantSimValue) + }) + }); + ItemEnum { + attrs: enum_attrs, + vis: vis.clone(), + enum_token: *enum_token, + ident: sim_value_ident.clone(), + generics: generics.into(), + brace_token: *brace_token, + variants: Punctuated::from_iter( + variants + .pairs() + .map_pair_value_ref( + |ParsedVariant { + attrs, + options: _, + ident, + field, + }| Variant { + attrs: attrs.clone(), + ident: ident.clone(), + fields: match field { + Some(ParsedVariantField { + paren_token, + attrs, + options: _, + ty, + comma_token, + }) => Fields::Unnamed(FieldsUnnamed { + paren_token: *paren_token, + unnamed: Punctuated::from_iter([ + Pair::new( + Field { + attrs: attrs.clone(), + vis: Visibility::Inherited, + mutability: FieldMutability::None, + ident: None, + colon_token: None, + ty: parse_quote_spanned! {span=> + ::fayalite::sim::value::SimValue<#ty> + }, + }, + Some(comma_token.unwrap_or(Token![,](ident.span()))), + ), + Pair::new( + Field { + attrs: vec![], + vis: Visibility::Inherited, + mutability: FieldMutability::None, + ident: None, + colon_token: None, + ty: parse_quote_spanned! {span=> + ::fayalite::enum_::EnumPaddingSimValue + }, + }, + None, + ), + ]), + }), + None => Fields::Unnamed(parse_quote_spanned! {span=> + (::fayalite::enum_::EnumPaddingSimValue) + }), + }, + discriminant: None, + }, + ) + .chain(sim_value_unknown_variant), + ), + } + .to_tokens(tokens); let self_token = Token![self](span); for (index, ParsedVariant { ident, field, .. }) in variants.iter().enumerate() { if let Some(ParsedVariantField { ty, .. }) = field { @@ -430,6 +571,25 @@ impl ToTokens for ParsedEnum { ) } } + #[automatically_derived] + impl #impl_generics #sim_builder_ident #type_generics + #where_clause + { + #[allow(non_snake_case, dead_code)] + #vis fn #ident<__V: ::fayalite::sim::value::ToSimValueWithType<#ty>>( + #self_token, + v: __V, + ) -> ::fayalite::sim::value::SimValue<#target #type_generics> { + let v = ::fayalite::sim::value::ToSimValueWithType::into_sim_value_with_type( + v, + #self_token.#sim_builder_ty_field_ident.#ident, + ); + ::fayalite::sim::value::SimValue::from_value( + #self_token.#sim_builder_ty_field_ident, + #sim_value_ident::#ident(v, ::fayalite::enum_::EnumPaddingSimValue::new()), + ) + } + } } } else { quote_spanned! {span=> @@ -448,6 +608,18 @@ impl ToTokens for ParsedEnum { ) } } + #[automatically_derived] + impl #impl_generics #sim_builder_ident #type_generics + #where_clause + { + #[allow(non_snake_case, dead_code)] + #vis fn #ident(#self_token) -> ::fayalite::sim::value::SimValue<#target #type_generics> { + ::fayalite::sim::value::SimValue::from_value( + #self_token.#sim_builder_ty_field_ident, + #sim_value_ident::#ident(::fayalite::enum_::EnumPaddingSimValue::new()), + ) + } + } } } .to_tokens(tokens); @@ -529,6 +701,142 @@ impl ToTokens for ParsedEnum { } }, )); + let sim_value_from_bits_unknown_match_arm = if let Some(sim_value_unknown_variant_name) = + &sim_value_unknown_variant_name + { + quote_spanned! {span=> + _ => #sim_value_ident::#sim_value_unknown_variant_name(v.unknown_variant_from_bits()), + } + } else { + quote_spanned! {span=> + _ => ::fayalite::__std::unreachable!(), + } + }; + let sim_value_from_bits_match_arms = Vec::from_iter( + variants + .iter() + .enumerate() + .map( + |( + index, + ParsedVariant { + attrs: _, + options: _, + ident, + field, + }, + )| { + if let Some(_) = field { + quote_spanned! {span=> + #index => { + let (field, padding) = v.variant_with_field_from_bits(); + #sim_value_ident::#ident(field, padding) + } + } + } else { + quote_spanned! {span=> + #index => #sim_value_ident::#ident( + v.variant_no_field_from_bits(), + ), + } + } + }, + ) + .chain([sim_value_from_bits_unknown_match_arm]), + ); + let sim_value_clone_from_bits_unknown_match_arm = + if let Some(sim_value_unknown_variant_name) = &sim_value_unknown_variant_name { + quote_spanned! {span=> + _ => if let #sim_value_ident::#sim_value_unknown_variant_name(value) = value { + v.unknown_variant_clone_from_bits(value); + } else { + *value = #sim_value_ident::#sim_value_unknown_variant_name( + v.unknown_variant_from_bits(), + ); + }, + } + } else { + quote_spanned! {span=> + _ => ::fayalite::__std::unreachable!(), + } + }; + let sim_value_clone_from_bits_match_arms = Vec::from_iter( + variants + .iter() + .enumerate() + .map( + |( + index, + ParsedVariant { + attrs: _, + options: _, + ident, + field, + }, + )| { + if let Some(_) = field { + quote_spanned! {span=> + #index => if let #sim_value_ident::#ident(field, padding) = value { + v.variant_with_field_clone_from_bits(field, padding); + } else { + let (field, padding) = v.variant_with_field_from_bits(); + *value = #sim_value_ident::#ident(field, padding); + }, + } + } else { + quote_spanned! {span=> + #index => if let #sim_value_ident::#ident(padding) = value { + v.variant_no_field_clone_from_bits(padding); + } else { + *value = #sim_value_ident::#ident( + v.variant_no_field_from_bits(), + ); + }, + } + } + }, + ) + .chain([sim_value_clone_from_bits_unknown_match_arm]), + ); + let sim_value_to_bits_match_arms = Vec::from_iter( + variants + .iter() + .enumerate() + .map( + |( + index, + ParsedVariant { + attrs: _, + options: _, + ident, + field, + }, + )| { + if let Some(_) = field { + quote_spanned! {span=> + #sim_value_ident::#ident(field, padding) => { + v.variant_with_field_to_bits(#index, field, padding); + } + } + } else { + quote_spanned! {span=> + #sim_value_ident::#ident(padding) => { + v.variant_no_field_to_bits(#index, padding); + } + } + } + }, + ) + .chain(sim_value_unknown_variant_name.as_ref().map( + |sim_value_unknown_variant_name| { + quote_spanned! {span=> + #sim_value_ident::#sim_value_unknown_variant_name(value) => { + v.unknown_variant_to_bits(value); + } + } + }, + )), + ); let variants_len = variants.len(); quote_spanned! {span=> #[automatically_derived] @@ -537,6 +845,7 @@ impl ToTokens for ParsedEnum { { type BaseType = ::fayalite::enum_::Enum; type MaskType = ::fayalite::int::Bool; + type SimValue = #sim_value_ident #type_generics; type MatchVariant = #match_variant_ident #type_generics; type MatchActiveScope = ::fayalite::module::Scope; type MatchVariantAndInactiveScope = ::fayalite::enum_::EnumMatchVariantAndInactiveScope; @@ -569,11 +878,41 @@ impl ToTokens for ParsedEnum { fn source_location() -> ::fayalite::source_location::SourceLocation { ::fayalite::source_location::SourceLocation::caller() } + fn sim_value_from_bits( + &self, + bits: &::fayalite::bitvec::slice::BitSlice, + ) -> ::SimValue { + let v = ::fayalite::enum_::EnumSimValueFromBits::new(*self, bits); + match v.discriminant() { + #(#sim_value_from_bits_match_arms)* + } + } + fn sim_value_clone_from_bits( + &self, + value: &mut ::SimValue, + bits: &::fayalite::bitvec::slice::BitSlice, + ) { + let v = ::fayalite::enum_::EnumSimValueFromBits::new(*self, bits); + match v.discriminant() { + #(#sim_value_clone_from_bits_match_arms)* + } + } + fn sim_value_to_bits( + &self, + value: &::SimValue, + bits: &mut ::fayalite::bitvec::slice::BitSlice, + ) { + let v = ::fayalite::enum_::EnumSimValueToBits::new(*self, bits); + match value { + #(#sim_value_to_bits_match_arms)* + } + } } #[automatically_derived] impl #impl_generics ::fayalite::enum_::EnumType for #target #type_generics #where_clause { + type SimBuilder = #sim_builder_ident #type_generics; fn match_activate_scope( v: ::MatchVariantAndInactiveScope, ) -> (::MatchVariant, ::MatchActiveScope) { @@ -592,6 +931,33 @@ impl ToTokens for ParsedEnum { ][..]) } } + #[automatically_derived] + impl #impl_generics ::fayalite::sim::value::ToSimValueWithType<#target #type_generics> + for #sim_value_ident #type_generics + #where_clause + { + fn to_sim_value_with_type( + &self, + ty: #target #type_generics, + ) -> ::fayalite::sim::value::SimValue<#target #type_generics> { + ::fayalite::sim::value::SimValue::from_value(ty, ::fayalite::__std::clone::Clone::clone(self)) + } + fn into_sim_value_with_type( + self, + ty: #target #type_generics, + ) -> ::fayalite::sim::value::SimValue<#target #type_generics> { + ::fayalite::sim::value::SimValue::from_value(ty, self) + } + } + #[automatically_derived] + impl #impl_generics ::fayalite::__std::convert::From<#target #type_generics> + for #sim_builder_ident #type_generics + #where_clause + { + fn from(#sim_builder_ty_field_ident: #target #type_generics) -> Self { + Self { #sim_builder_ty_field_ident } + } + } } .to_tokens(tokens); if let (None, MaybeParsed::Parsed(generics)) = (no_static, &self.generics) { @@ -629,6 +995,15 @@ impl ToTokens for ParsedEnum { } })); quote_spanned! {span=> + #[automatically_derived] + impl #static_impl_generics ::fayalite::__std::default::Default + for #target #static_type_generics + #static_where_clause + { + fn default() -> Self { + ::TYPE + } + } #[automatically_derived] impl #static_impl_generics ::fayalite::ty::StaticType for #target #static_type_generics @@ -647,6 +1022,34 @@ impl ToTokens for ParsedEnum { const MASK_TYPE_PROPERTIES: ::fayalite::ty::TypeProperties = <::fayalite::int::Bool as ::fayalite::ty::StaticType>::TYPE_PROPERTIES; } + #[automatically_derived] + impl #static_impl_generics ::fayalite::sim::value::ToSimValue + for #sim_value_ident #static_type_generics + #static_where_clause + { + type Type = #target #static_type_generics; + + fn to_sim_value( + &self, + ) -> ::fayalite::sim::value::SimValue< + ::Type, + > { + ::fayalite::sim::value::SimValue::from_value( + ::fayalite::ty::StaticType::TYPE, + ::fayalite::__std::clone::Clone::clone(self), + ) + } + fn into_sim_value( + self, + ) -> ::fayalite::sim::value::SimValue< + ::Type, + > { + ::fayalite::sim::value::SimValue::from_value( + ::fayalite::ty::StaticType::TYPE, + self, + ) + } + } } .to_tokens(tokens); } diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs index e5d5f7b..97501e7 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_alias.rs @@ -49,10 +49,14 @@ impl ParsedTypeAlias { custom_bounds, no_static, no_runtime_generics: _, + cmp_eq, } = options.body; if let Some((no_static,)) = no_static { errors.error(no_static, "no_static is not valid on type aliases"); } + if let Some((cmp_eq,)) = cmp_eq { + errors.error(cmp_eq, "cmp_eq is not valid on type aliases"); + } let generics = if custom_bounds.is_some() { MaybeParsed::Unrecognized(generics) } else if let Some(generics) = errors.ok(ParsedGenerics::parse(&mut generics)) { @@ -95,6 +99,7 @@ impl ToTokens for ParsedTypeAlias { custom_bounds: _, no_static: _, no_runtime_generics, + cmp_eq: _, } = &options.body; let target = get_target(target, ident); let mut type_attrs = attrs.clone(); diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs index 3f3f817..2da0915 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs @@ -26,6 +26,7 @@ crate::options! { CustomBounds(custom_bounds), NoStatic(no_static), NoRuntimeGenerics(no_runtime_generics), + CmpEq(cmp_eq), } } @@ -2044,6 +2045,7 @@ pub(crate) mod known_items { impl_known_item!(::fayalite::int::Size); impl_known_item!(::fayalite::int::UInt); impl_known_item!(::fayalite::int::UIntType); + impl_known_item!(::fayalite::reset::ResetType); impl_known_item!(::fayalite::ty::CanonicalType); impl_known_item!(::fayalite::ty::StaticType); impl_known_item!(::fayalite::ty::Type); @@ -2068,11 +2070,16 @@ macro_rules! impl_bounds { $( $Variant:ident, )* + $( + #[unknown] + $Unknown:ident, + )? } ) => { #[derive(Clone, Debug)] $vis enum $enum_type { $($Variant(known_items::$Variant),)* + $($Unknown(syn::TypeParamBound),)? } $(impl From for $enum_type { @@ -2085,28 +2092,54 @@ macro_rules! impl_bounds { fn to_tokens(&self, tokens: &mut TokenStream) { match self { $(Self::$Variant(v) => v.to_tokens(tokens),)* + $(Self::$Unknown(v) => v.to_tokens(tokens),)? } } } impl $enum_type { $vis fn parse_path(path: Path) -> Result { + #![allow(unreachable_code)] $(let path = match known_items::$Variant::parse_path(path) { Ok(v) => return Ok(Self::$Variant(v)), Err(path) => path, };)* + $(return Ok(Self::$Unknown(syn::TraitBound { + paren_token: None, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path, + }.into()));)? Err(path) } + $vis fn parse_type_param_bound(mut type_param_bound: syn::TypeParamBound) -> Result { + #![allow(unreachable_code)] + if let syn::TypeParamBound::Trait(mut trait_bound) = type_param_bound { + if let syn::TraitBound { + paren_token: _, + modifier: syn::TraitBoundModifier::None, + lifetimes: None, + path: _, + } = trait_bound { + match Self::parse_path(trait_bound.path) { + Ok(retval) => return Ok(retval), + Err(path) => trait_bound.path = path, + } + } + type_param_bound = trait_bound.into(); + } + $(return Ok(Self::$Unknown(type_param_bound));)? + Err(type_param_bound) + } } impl Parse for $enum_type { fn parse(input: ParseStream) -> syn::Result { - Self::parse_path(Path::parse_mod_style(input)?).map_err(|path| { - syn::Error::new_spanned( - path, + Self::parse_type_param_bound(input.parse()?) + .map_err(|type_param_bound| syn::Error::new_spanned( + type_param_bound, format_args!("expected one of: {}", [$(stringify!($Variant)),*].join(", ")), - ) - }) + )) } } @@ -2114,6 +2147,7 @@ macro_rules! impl_bounds { #[allow(non_snake_case)] $vis struct $struct_type { $($vis $Variant: Option,)* + $($vis $Unknown: Vec,)? } impl ToTokens for $struct_type { @@ -2125,42 +2159,63 @@ macro_rules! impl_bounds { separator = Some(::default()); v.to_tokens(tokens); })* + $(for v in &self.$Unknown { + separator.to_tokens(tokens); + separator = Some(::default()); + v.to_tokens(tokens); + })* } } const _: () = { #[derive(Clone, Debug)] - $vis struct Iter($vis $struct_type); + #[allow(non_snake_case)] + $vis struct Iter { + $($Variant: Option,)* + $($Unknown: std::vec::IntoIter,)? + } impl IntoIterator for $struct_type { type Item = $enum_type; type IntoIter = Iter; fn into_iter(self) -> Self::IntoIter { - Iter(self) + Iter { + $($Variant: self.$Variant,)* + $($Unknown: self.$Unknown.into_iter(),)? + } } } impl Iterator for Iter { type Item = $enum_type; - fn next(&mut self) -> Option { $( - if let Some(value) = self.0.$Variant.take() { + if let Some(value) = self.$Variant.take() { return Some($enum_type::$Variant(value)); } )* + $( + if let Some(value) = self.$Unknown.next() { + return Some($enum_type::$Unknown(value)); + } + )? None } #[allow(unused_mut, unused_variables)] fn fold B>(mut self, mut init: B, mut f: F) -> B { $( - if let Some(value) = self.0.$Variant.take() { + if let Some(value) = self.$Variant.take() { init = f(init, $enum_type::$Variant(value)); } )* + $( + if let Some(value) = self.$Unknown.next() { + init = f(init, $enum_type::$Unknown(value)); + } + )? init } } @@ -2172,6 +2227,9 @@ macro_rules! impl_bounds { $($enum_type::$Variant(v) => { self.$Variant = Some(v); })* + $($enum_type::$Unknown(v) => { + self.$Unknown.push(v); + })? }); } } @@ -2190,6 +2248,7 @@ macro_rules! impl_bounds { $(if let Some(v) = v.$Variant { self.$Variant = Some(v); })* + $(self.$Unknown.extend(v.$Unknown);)* }); } } @@ -2239,9 +2298,12 @@ impl_bounds! { EnumType, IntType, KnownSize, + ResetType, Size, StaticType, Type, + #[unknown] + Unknown, } } @@ -2252,8 +2314,11 @@ impl_bounds! { BundleType, EnumType, IntType, + ResetType, StaticType, Type, + #[unknown] + Unknown, } } @@ -2264,8 +2329,10 @@ impl From for ParsedBound { ParsedTypeBound::BundleType(v) => ParsedBound::BundleType(v), ParsedTypeBound::EnumType(v) => ParsedBound::EnumType(v), ParsedTypeBound::IntType(v) => ParsedBound::IntType(v), + ParsedTypeBound::ResetType(v) => ParsedBound::ResetType(v), ParsedTypeBound::StaticType(v) => ParsedBound::StaticType(v), ParsedTypeBound::Type(v) => ParsedBound::Type(v), + ParsedTypeBound::Unknown(v) => ParsedBound::Unknown(v), } } } @@ -2277,8 +2344,10 @@ impl From for ParsedBounds { BundleType, EnumType, IntType, + ResetType, StaticType, Type, + Unknown, } = value; Self { BoolOrIntType, @@ -2286,9 +2355,11 @@ impl From for ParsedBounds { EnumType, IntType, KnownSize: None, + ResetType, Size: None, StaticType, Type, + Unknown, } } } @@ -2314,11 +2385,17 @@ impl ParsedTypeBound { ParsedTypeBound::BoolOrIntType(known_items::BoolOrIntType(span)), ParsedTypeBound::Type(known_items::Type(span)), ]), + Self::ResetType(v) => ParsedTypeBounds::from_iter([ + ParsedTypeBound::from(v), + ParsedTypeBound::StaticType(known_items::StaticType(span)), + ParsedTypeBound::Type(known_items::Type(span)), + ]), Self::StaticType(v) => ParsedTypeBounds::from_iter([ ParsedTypeBound::from(v), ParsedTypeBound::Type(known_items::Type(span)), ]), Self::Type(v) => ParsedTypeBounds::from_iter([ParsedTypeBound::from(v)]), + Self::Unknown(v) => ParsedTypeBounds::from_iter([ParsedTypeBound::Unknown(v)]), } } } @@ -2349,9 +2426,11 @@ impl From for ParsedBounds { EnumType: None, IntType: None, KnownSize, + ResetType: None, Size, StaticType: None, Type: None, + Unknown: vec![], } } } @@ -2379,6 +2458,7 @@ impl ParsedBounds { fn categorize(self, errors: &mut Errors, span: Span) -> ParsedBoundsCategory { let mut type_bounds = None; let mut size_type_bounds = None; + let mut unknown_bounds = vec![]; self.into_iter().for_each(|bound| match bound.categorize() { ParsedBoundCategory::Type(bound) => { type_bounds @@ -2390,15 +2470,37 @@ impl ParsedBounds { .get_or_insert_with(ParsedSizeTypeBounds::default) .extend([bound]); } + ParsedBoundCategory::Unknown(bound) => unknown_bounds.push(bound), }); - match (type_bounds, size_type_bounds) { - (None, None) => ParsedBoundsCategory::Type(ParsedTypeBounds { + match (type_bounds, size_type_bounds, unknown_bounds.is_empty()) { + (None, None, true) => ParsedBoundsCategory::Type(ParsedTypeBounds { Type: Some(known_items::Type(span)), ..Default::default() }), - (None, Some(bounds)) => ParsedBoundsCategory::SizeType(bounds), - (Some(bounds), None) => ParsedBoundsCategory::Type(bounds), - (Some(type_bounds), Some(size_type_bounds)) => { + (None, None, false) => { + errors.error( + unknown_bounds.remove(0), + "unknown bounds: must use at least one known bound (such as `Type`) with any unknown bounds", + ); + ParsedBoundsCategory::Type(ParsedTypeBounds { + Unknown: unknown_bounds, + ..Default::default() + }) + } + (None, Some(bounds), true) => ParsedBoundsCategory::SizeType(bounds), + (None, Some(bounds), false) => { + // TODO: implement + errors.error( + unknown_bounds.remove(0), + "unknown bounds with `Size` bounds are not implemented", + ); + ParsedBoundsCategory::SizeType(bounds) + } + (Some(bounds), None, _) => ParsedBoundsCategory::Type(ParsedTypeBounds { + Unknown: unknown_bounds, + ..bounds + }), + (Some(type_bounds), Some(size_type_bounds), _) => { errors.error( size_type_bounds .Size @@ -2415,6 +2517,7 @@ impl ParsedBounds { pub(crate) enum ParsedBoundCategory { Type(ParsedTypeBound), SizeType(ParsedSizeTypeBound), + Unknown(syn::TypeParamBound), } impl ParsedBound { @@ -2425,15 +2528,18 @@ impl ParsedBound { Self::EnumType(v) => ParsedBoundCategory::Type(ParsedTypeBound::EnumType(v)), Self::IntType(v) => ParsedBoundCategory::Type(ParsedTypeBound::IntType(v)), Self::KnownSize(v) => ParsedBoundCategory::SizeType(ParsedSizeTypeBound::KnownSize(v)), + Self::ResetType(v) => ParsedBoundCategory::Type(ParsedTypeBound::ResetType(v)), Self::Size(v) => ParsedBoundCategory::SizeType(ParsedSizeTypeBound::Size(v)), Self::StaticType(v) => ParsedBoundCategory::Type(ParsedTypeBound::StaticType(v)), Self::Type(v) => ParsedBoundCategory::Type(ParsedTypeBound::Type(v)), + Self::Unknown(v) => ParsedBoundCategory::Unknown(v), } } fn implied_bounds(self) -> ParsedBounds { match self.categorize() { ParsedBoundCategory::Type(v) => v.implied_bounds().into(), ParsedBoundCategory::SizeType(v) => v.implied_bounds().into(), + ParsedBoundCategory::Unknown(v) => ParsedBounds::from_iter([ParsedBound::Unknown(v)]), } } } @@ -3310,8 +3416,9 @@ impl ParsedGenerics { ParsedTypeBound::BoolOrIntType(_) | ParsedTypeBound::BundleType(_) | ParsedTypeBound::EnumType(_) - | ParsedTypeBound::IntType(_) => { - errors.error(bound, "bound on mask type not implemented"); + | ParsedTypeBound::IntType(_) + | ParsedTypeBound::ResetType(_) => { + errors.error(bound, "bounds on mask types are not implemented"); } ParsedTypeBound::StaticType(bound) => { if bounds.StaticType.is_none() { @@ -3323,6 +3430,12 @@ impl ParsedGenerics { } } ParsedTypeBound::Type(_) => {} + ParsedTypeBound::Unknown(_) => { + errors.error( + bound, + "unknown bounds on mask types are not implemented", + ); + } } } bounds.add_implied_bounds(); diff --git a/crates/fayalite-proc-macros-impl/src/lib.rs b/crates/fayalite-proc-macros-impl/src/lib.rs index 0ffd4d4..4f7c4f0 100644 --- a/crates/fayalite-proc-macros-impl/src/lib.rs +++ b/crates/fayalite-proc-macros-impl/src/lib.rs @@ -3,14 +3,20 @@ #![cfg_attr(test, recursion_limit = "512")] use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; -use std::io::{ErrorKind, Write}; +use std::{ + collections::{hash_map::Entry, HashMap}, + io::{ErrorKind, Write}, +}; use syn::{ - bracketed, parenthesized, + bracketed, + ext::IdentExt, + parenthesized, parse::{Parse, ParseStream, Parser}, parse_quote, - punctuated::Pair, + punctuated::{Pair, Punctuated}, spanned::Spanned, - AttrStyle, Attribute, Error, Item, ItemFn, Token, + token::{Bracket, Paren}, + AttrStyle, Attribute, Error, Ident, Item, ItemFn, LitBool, LitStr, Meta, Token, }; mod fold; @@ -19,6 +25,7 @@ mod hdl_enum; mod hdl_type_alias; mod hdl_type_common; mod module; +mod process_cfg; pub(crate) trait CustomToken: Copy @@ -59,14 +66,20 @@ mod kw { }; } + custom_keyword!(__evaluated_cfgs); + custom_keyword!(all); + custom_keyword!(any); + custom_keyword!(cfg); + custom_keyword!(cfg_attr); custom_keyword!(clock_domain); + custom_keyword!(cmp_eq); custom_keyword!(connect_inexact); custom_keyword!(custom_bounds); custom_keyword!(flip); custom_keyword!(hdl); custom_keyword!(hdl_module); - custom_keyword!(input); custom_keyword!(incomplete_wire); + custom_keyword!(input); custom_keyword!(instance); custom_keyword!(m); custom_keyword!(memory); @@ -75,10 +88,12 @@ mod kw { custom_keyword!(no_reset); custom_keyword!(no_runtime_generics); custom_keyword!(no_static); + custom_keyword!(not); custom_keyword!(outline_generated); custom_keyword!(output); custom_keyword!(reg_builder); custom_keyword!(reset); + custom_keyword!(sim); custom_keyword!(skip); custom_keyword!(target); custom_keyword!(wire); @@ -901,15 +916,346 @@ fn hdl_module_impl(item: ItemFn) -> syn::Result { Ok(contents) } -pub fn hdl_module(attr: TokenStream, item: TokenStream) -> syn::Result { - let kw = kw::hdl_module::default(); - hdl_module_impl(syn::parse2(quote! { #[#kw(#attr)] #item })?) +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub(crate) enum CfgExpr { + Option { + ident: Ident, + value: Option<(Token![=], LitStr)>, + }, + All { + all: kw::all, + paren: Paren, + exprs: Punctuated, + }, + Any { + any: kw::any, + paren: Paren, + exprs: Punctuated, + }, + Not { + not: kw::not, + paren: Paren, + expr: Box, + trailing_comma: Option, + }, } -pub fn hdl_attr(attr: TokenStream, item: TokenStream) -> syn::Result { - let kw = kw::hdl::default(); - let item = quote! { #[#kw(#attr)] #item }; - let item = syn::parse2::(item)?; +impl Parse for CfgExpr { + fn parse(input: ParseStream) -> syn::Result { + match input.cursor().ident() { + Some((_, cursor)) if cursor.eof() => { + return Ok(CfgExpr::Option { + ident: input.call(Ident::parse_any)?, + value: None, + }); + } + _ => {} + } + if input.peek(Ident::peek_any) && input.peek2(Token![=]) { + return Ok(CfgExpr::Option { + ident: input.call(Ident::parse_any)?, + value: Some((input.parse()?, input.parse()?)), + }); + } + let contents; + if input.peek(kw::all) { + Ok(CfgExpr::All { + all: input.parse()?, + paren: parenthesized!(contents in input), + exprs: contents.call(Punctuated::parse_terminated)?, + }) + } else if input.peek(kw::any) { + Ok(CfgExpr::Any { + any: input.parse()?, + paren: parenthesized!(contents in input), + exprs: contents.call(Punctuated::parse_terminated)?, + }) + } else if input.peek(kw::not) { + Ok(CfgExpr::Not { + not: input.parse()?, + paren: parenthesized!(contents in input), + expr: contents.parse()?, + trailing_comma: contents.parse()?, + }) + } else { + Err(input.error("expected cfg-pattern")) + } + } +} + +impl ToTokens for CfgExpr { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + CfgExpr::Option { ident, value } => { + ident.to_tokens(tokens); + if let Some((eq, value)) = value { + eq.to_tokens(tokens); + value.to_tokens(tokens); + } + } + CfgExpr::All { all, paren, exprs } => { + all.to_tokens(tokens); + paren.surround(tokens, |tokens| exprs.to_tokens(tokens)); + } + CfgExpr::Any { any, paren, exprs } => { + any.to_tokens(tokens); + paren.surround(tokens, |tokens| exprs.to_tokens(tokens)); + } + CfgExpr::Not { + not, + paren, + expr, + trailing_comma, + } => { + not.to_tokens(tokens); + paren.surround(tokens, |tokens| { + expr.to_tokens(tokens); + trailing_comma.to_tokens(tokens); + }); + } + } + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub(crate) struct Cfg { + cfg: kw::cfg, + paren: Paren, + expr: CfgExpr, + trailing_comma: Option, +} + +impl Cfg { + fn parse_meta(meta: &Meta) -> syn::Result { + syn::parse2(meta.to_token_stream()) + } +} + +impl ToTokens for Cfg { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + cfg, + paren, + expr, + trailing_comma, + } = self; + cfg.to_tokens(tokens); + paren.surround(tokens, |tokens| { + expr.to_tokens(tokens); + trailing_comma.to_tokens(tokens); + }); + } +} + +impl Parse for Cfg { + fn parse(input: ParseStream) -> syn::Result { + let contents; + Ok(Self { + cfg: input.parse()?, + paren: parenthesized!(contents in input), + expr: contents.parse()?, + trailing_comma: contents.parse()?, + }) + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub(crate) struct CfgAttr { + cfg_attr: kw::cfg_attr, + paren: Paren, + expr: CfgExpr, + comma: Token![,], + attrs: Punctuated, +} + +impl CfgAttr { + pub(crate) fn to_cfg(&self) -> Cfg { + Cfg { + cfg: kw::cfg(self.cfg_attr.span), + paren: self.paren, + expr: self.expr.clone(), + trailing_comma: None, + } + } + fn parse_meta(meta: &Meta) -> syn::Result { + syn::parse2(meta.to_token_stream()) + } +} + +impl Parse for CfgAttr { + fn parse(input: ParseStream) -> syn::Result { + let contents; + Ok(Self { + cfg_attr: input.parse()?, + paren: parenthesized!(contents in input), + expr: contents.parse()?, + comma: contents.parse()?, + attrs: contents.call(Punctuated::parse_terminated)?, + }) + } +} + +pub(crate) struct CfgAndValue { + cfg: Cfg, + eq_token: Token![=], + value: LitBool, +} + +impl Parse for CfgAndValue { + fn parse(input: ParseStream) -> syn::Result { + Ok(Self { + cfg: input.parse()?, + eq_token: input.parse()?, + value: input.parse()?, + }) + } +} + +pub(crate) struct Cfgs { + pub(crate) bracket: Bracket, + pub(crate) cfgs_map: HashMap, + pub(crate) cfgs_list: Vec, +} + +impl Default for Cfgs { + fn default() -> Self { + Self { + bracket: Default::default(), + cfgs_map: Default::default(), + cfgs_list: Default::default(), + } + } +} + +impl Cfgs { + fn insert_cfg(&mut self, cfg: Cfg, value: T) { + match self.cfgs_map.entry(cfg) { + Entry::Occupied(_) => {} + Entry::Vacant(entry) => { + self.cfgs_list.push(entry.key().clone()); + entry.insert(value); + } + } + } +} + +impl Parse for Cfgs { + fn parse(input: ParseStream) -> syn::Result { + let contents; + let bracket = bracketed!(contents in input); + let mut cfgs_map = HashMap::new(); + let mut cfgs_list = Vec::new(); + for CfgAndValue { + cfg, + eq_token, + value, + } in contents.call(Punctuated::::parse_terminated)? + { + let _ = eq_token; + match cfgs_map.entry(cfg) { + Entry::Occupied(_) => {} + Entry::Vacant(entry) => { + cfgs_list.push(entry.key().clone()); + entry.insert(value.value); + } + } + } + Ok(Self { + bracket, + cfgs_map, + cfgs_list, + }) + } +} + +impl Parse for Cfgs<()> { + fn parse(input: ParseStream) -> syn::Result { + let contents; + let bracket = bracketed!(contents in input); + let mut cfgs_map = HashMap::new(); + let mut cfgs_list = Vec::new(); + for cfg in contents.call(Punctuated::::parse_terminated)? { + match cfgs_map.entry(cfg) { + Entry::Occupied(_) => {} + Entry::Vacant(entry) => { + cfgs_list.push(entry.key().clone()); + entry.insert(()); + } + } + } + Ok(Self { + bracket, + cfgs_map, + cfgs_list, + }) + } +} + +impl ToTokens for Cfgs<()> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + bracket, + cfgs_map: _, + cfgs_list, + } = self; + bracket.surround(tokens, |tokens| { + for cfg in cfgs_list { + cfg.to_tokens(tokens); + ::default().to_tokens(tokens); + } + }); + } +} + +fn hdl_main( + kw: impl CustomToken, + attr: TokenStream, + item: TokenStream, +) -> syn::Result { + fn parse_evaluated_cfgs_attr( + input: ParseStream, + parse_inner: impl FnOnce(ParseStream) -> syn::Result, + ) -> syn::Result { + let _: Token![#] = input.parse()?; + let bracket_content; + bracketed!(bracket_content in input); + let _: kw::__evaluated_cfgs = bracket_content.parse()?; + let paren_content; + parenthesized!(paren_content in bracket_content); + parse_inner(&paren_content) + } + let (evaluated_cfgs, item): (_, TokenStream) = Parser::parse2( + |input: ParseStream| { + let peek = input.fork(); + if parse_evaluated_cfgs_attr(&peek, |_| Ok(())).is_ok() { + let evaluated_cfgs = parse_evaluated_cfgs_attr(input, Cfgs::::parse)?; + Ok((Some(evaluated_cfgs), input.parse()?)) + } else { + Ok((None, input.parse()?)) + } + }, + item, + )?; + let cfgs = if let Some(cfgs) = evaluated_cfgs { + cfgs + } else { + let cfgs = process_cfg::collect_cfgs(syn::parse2(item.clone())?)?; + if cfgs.cfgs_list.is_empty() { + Cfgs::default() + } else { + return Ok(quote! { + ::fayalite::__cfg_expansion_helper! { + [] + #cfgs + {#[::fayalite::#kw(#attr)]} { #item } + } + }); + } + }; + let item = syn::parse2(quote! { #[#kw(#attr)] #item })?; + let Some(item) = process_cfg::process_cfgs(item, cfgs)? else { + return Ok(TokenStream::new()); + }; match item { Item::Enum(item) => hdl_enum::hdl_enum(item), Item::Struct(item) => hdl_bundle::hdl_bundle(item), @@ -921,3 +1267,11 @@ pub fn hdl_attr(attr: TokenStream, item: TokenStream) -> syn::Result syn::Result { + hdl_main(kw::hdl_module::default(), attr, item) +} + +pub fn hdl_attr(attr: TokenStream, item: TokenStream) -> syn::Result { + hdl_main(kw::hdl::default(), attr, item) +} diff --git a/crates/fayalite-proc-macros-impl/src/module.rs b/crates/fayalite-proc-macros-impl/src/module.rs index 0852f58..62b7837 100644 --- a/crates/fayalite-proc-macros-impl/src/module.rs +++ b/crates/fayalite-proc-macros-impl/src/module.rs @@ -377,7 +377,7 @@ impl ModuleFn { module_kind, vis, sig, - block, + mut block, struct_generics, the_struct, } = match self.0 { @@ -439,6 +439,12 @@ impl ModuleFn { body_sig .inputs .insert(0, parse_quote! { m: &::fayalite::module::ModuleBuilder }); + block.stmts.insert( + 0, + parse_quote! { + let _ = m; + }, + ); let body_fn = ItemFn { attrs: vec![], vis: Visibility::Inherited, diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs index 6e99e87..a0f8eb0 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs @@ -16,7 +16,7 @@ use std::{borrow::Borrow, convert::Infallible}; use syn::{ fold::{fold_expr, fold_expr_lit, fold_expr_unary, fold_local, fold_stmt, Fold}, parenthesized, - parse::{Nothing, Parse, ParseStream}, + parse::{Parse, ParseStream}, parse_quote, parse_quote_spanned, spanned::Spanned, token::Paren, @@ -27,6 +27,13 @@ use syn::{ mod expand_aggregate_literals; mod expand_match; +options! { + #[options = ExprOptions] + pub(crate) enum ExprOption { + Sim(sim), + } +} + options! { pub(crate) enum LetFnKind { Input(input), @@ -952,7 +959,7 @@ with_debug_clone_and_fold! { #[allow(dead_code)] pub(crate) struct HdlLet { pub(crate) attrs: Vec, - pub(crate) hdl_attr: HdlAttr, + pub(crate) hdl_attr: HdlAttr, pub(crate) let_token: Token![let], pub(crate) mut_token: Option, pub(crate) name: Ident, @@ -1109,7 +1116,7 @@ fn parse_quote_let_pat>( } } -fn wrap_ty_with_expr(ty: impl ToTokens) -> Type { +pub(crate) fn wrap_ty_with_expr(ty: impl ToTokens) -> Type { parse_quote_spanned! {ty.span()=> ::fayalite::expr::Expr<#ty> } @@ -1173,7 +1180,7 @@ impl Visitor<'_> { Some(_) => {} } } - fn process_hdl_if(&mut self, hdl_attr: HdlAttr, expr_if: ExprIf) -> Expr { + fn process_hdl_if(&mut self, hdl_attr: HdlAttr, expr_if: ExprIf) -> Expr { let ExprIf { attrs, if_token, @@ -1181,10 +1188,10 @@ impl Visitor<'_> { then_branch, else_branch, } = expr_if; - self.require_normal_module_or_fn(if_token); - let else_expr = else_branch.unzip().1.map(|else_expr| match *else_expr { - Expr::If(expr_if) => self.process_hdl_if(hdl_attr.clone(), expr_if), - expr => expr, + let (else_token, else_expr) = else_branch.unzip(); + let else_expr = else_expr.map(|else_expr| match *else_expr { + Expr::If(expr_if) => Box::new(self.process_hdl_if(hdl_attr.clone(), expr_if)), + _ => else_expr, }); if let Expr::Let(ExprLet { attrs: let_attrs, @@ -1206,7 +1213,19 @@ impl Visitor<'_> { }, ); } - if let Some(else_expr) = else_expr { + let ExprOptions { sim } = hdl_attr.body; + if sim.is_some() { + ExprIf { + attrs, + if_token, + cond: parse_quote_spanned! {if_token.span=> + *::fayalite::sim::value::SimValue::<::fayalite::int::Bool>::value(&::fayalite::sim::value::ToSimValue::into_sim_value(#cond)) + }, + then_branch, + else_branch: else_token.zip(else_expr), + } + .into() + } else if let Some(else_expr) = else_expr { parse_quote_spanned! {if_token.span=> #(#attrs)* { @@ -1586,7 +1605,7 @@ impl Visitor<'_> { } } -fn empty_let() -> Local { +pub(crate) fn empty_let() -> Local { Local { attrs: vec![], let_token: Default::default(), @@ -1668,20 +1687,42 @@ impl Fold for Visitor<'_> { Repeat => process_hdl_repeat, Struct => process_hdl_struct, Tuple => process_hdl_tuple, + MethodCall => process_hdl_method_call, + Call => process_hdl_call, } } } - fn fold_local(&mut self, let_stmt: Local) -> Local { + fn fold_local(&mut self, mut let_stmt: Local) -> Local { match self .errors - .ok(HdlAttr::::parse_and_leave_attr( + .ok(HdlAttr::::parse_and_leave_attr( &let_stmt.attrs, )) { None => return empty_let(), Some(None) => return fold_local(self, let_stmt), Some(Some(HdlAttr { .. })) => {} }; + let mut pat = &let_stmt.pat; + if let Pat::Type(pat_type) = pat { + pat = &pat_type.pat; + } + let Pat::Ident(syn::PatIdent { + attrs: _, + by_ref: None, + mutability: _, + ident: _, + subpat: None, + }) = pat + else { + let hdl_attr = + HdlAttr::::parse_and_take_attr(&mut let_stmt.attrs) + .ok() + .flatten() + .expect("already checked above"); + let let_stmt = fold_local(self, let_stmt); + return self.process_hdl_let_pat(hdl_attr, let_stmt); + }; let hdl_let = syn::parse2::>>(let_stmt.into_token_stream()); let Some(hdl_let) = self.errors.ok(hdl_let) else { return empty_let(); diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs index b5a0ad3..61f6c75 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs @@ -1,45 +1,103 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use crate::{kw, module::transform_body::Visitor, HdlAttr}; + +use crate::{ + kw, + module::transform_body::{ + expand_match::{parse_enum_path, EnumPath}, + ExprOptions, Visitor, + }, + HdlAttr, +}; use quote::{format_ident, quote_spanned}; +use std::mem; use syn::{ - parse::Nothing, parse_quote, parse_quote_spanned, spanned::Spanned, Expr, ExprArray, ExprPath, - ExprRepeat, ExprStruct, ExprTuple, FieldValue, TypePath, + parse_quote_spanned, punctuated::Punctuated, spanned::Spanned, token::Paren, Expr, ExprArray, + ExprCall, ExprGroup, ExprMethodCall, ExprParen, ExprPath, ExprRepeat, ExprStruct, ExprTuple, + FieldValue, Token, TypePath, }; impl Visitor<'_> { pub(crate) fn process_hdl_array( &mut self, - hdl_attr: HdlAttr, + hdl_attr: HdlAttr, mut expr_array: ExprArray, ) -> Expr { - self.require_normal_module_or_fn(hdl_attr); - for elem in &mut expr_array.elems { - *elem = parse_quote_spanned! {elem.span()=> - ::fayalite::expr::ToExpr::to_expr(&(#elem)) - }; + let ExprOptions { sim } = hdl_attr.body; + let span = hdl_attr.kw.span; + if sim.is_some() { + for elem in &mut expr_array.elems { + *elem = parse_quote_spanned! {elem.span()=> + ::fayalite::sim::value::ToSimValue::to_sim_value(&(#elem)) + }; + } + parse_quote_spanned! {span=> + ::fayalite::sim::value::ToSimValue::into_sim_value(#expr_array) + } + } else { + for elem in &mut expr_array.elems { + *elem = parse_quote_spanned! {elem.span()=> + ::fayalite::expr::ToExpr::to_expr(&(#elem)) + }; + } + parse_quote_spanned! {span=> + ::fayalite::expr::ToExpr::to_expr(&#expr_array) + } } - parse_quote! {::fayalite::expr::ToExpr::to_expr(&#expr_array)} } pub(crate) fn process_hdl_repeat( &mut self, - hdl_attr: HdlAttr, + hdl_attr: HdlAttr, mut expr_repeat: ExprRepeat, ) -> Expr { - self.require_normal_module_or_fn(hdl_attr); let repeated_value = &expr_repeat.expr; - *expr_repeat.expr = parse_quote_spanned! {repeated_value.span()=> - ::fayalite::expr::ToExpr::to_expr(&(#repeated_value)) - }; - parse_quote! {::fayalite::expr::ToExpr::to_expr(&#expr_repeat)} + let ExprOptions { sim } = hdl_attr.body; + let span = hdl_attr.kw.span; + if sim.is_some() { + *expr_repeat.expr = parse_quote_spanned! {repeated_value.span()=> + ::fayalite::sim::value::ToSimValue::to_sim_value(&(#repeated_value)) + }; + parse_quote_spanned! {span=> + ::fayalite::sim::value::ToSimValue::into_sim_value(#expr_repeat) + } + } else { + *expr_repeat.expr = parse_quote_spanned! {repeated_value.span()=> + ::fayalite::expr::ToExpr::to_expr(&(#repeated_value)) + }; + parse_quote_spanned! {span=> + ::fayalite::expr::ToExpr::to_expr(&#expr_repeat) + } + } } pub(crate) fn process_hdl_struct( &mut self, - hdl_attr: HdlAttr, - expr_struct: ExprStruct, + hdl_attr: HdlAttr, + mut expr_struct: ExprStruct, ) -> Expr { - self.require_normal_module_or_fn(&hdl_attr); let name_span = expr_struct.path.segments.last().unwrap().ident.span(); + let ExprOptions { sim } = hdl_attr.body; + if sim.is_some() { + let ty_path = TypePath { + qself: expr_struct.qself.take(), + path: expr_struct.path, + }; + expr_struct.path = parse_quote_spanned! {name_span=> + __SimValue::<#ty_path> + }; + for field in &mut expr_struct.fields { + let expr = &field.expr; + field.expr = parse_quote_spanned! {field.member.span()=> + ::fayalite::sim::value::ToSimValue::to_sim_value(&(#expr)) + }; + } + return parse_quote_spanned! {name_span=> + { + type __SimValue = ::SimValue; + let value: ::fayalite::sim::value::SimValue<#ty_path> = ::fayalite::sim::value::ToSimValue::into_sim_value(#expr_struct); + value + } + }; + } let builder_ident = format_ident!("__builder", span = name_span); let empty_builder = if expr_struct.qself.is_some() || expr_struct @@ -91,12 +149,126 @@ impl Visitor<'_> { } pub(crate) fn process_hdl_tuple( &mut self, - hdl_attr: HdlAttr, - expr_tuple: ExprTuple, + hdl_attr: HdlAttr, + mut expr_tuple: ExprTuple, ) -> Expr { - self.require_normal_module_or_fn(hdl_attr); - parse_quote_spanned! {expr_tuple.span()=> - ::fayalite::expr::ToExpr::to_expr(&#expr_tuple) + let ExprOptions { sim } = hdl_attr.body; + if sim.is_some() { + for element in &mut expr_tuple.elems { + *element = parse_quote_spanned! {element.span()=> + &(#element) + }; + } + parse_quote_spanned! {expr_tuple.span()=> + ::fayalite::sim::value::ToSimValue::into_sim_value(#expr_tuple) + } + } else { + parse_quote_spanned! {expr_tuple.span()=> + ::fayalite::expr::ToExpr::to_expr(&#expr_tuple) + } } } + pub(crate) fn process_hdl_call( + &mut self, + hdl_attr: HdlAttr, + mut expr_call: ExprCall, + ) -> Expr { + let span = hdl_attr.kw.span; + let mut func = &mut *expr_call.func; + let EnumPath { + variant_path: _, + enum_path, + variant_name, + } = loop { + match func { + Expr::Group(ExprGroup { expr, .. }) | Expr::Paren(ExprParen { expr, .. }) => { + func = &mut **expr; + } + Expr::Path(_) => { + let Expr::Path(ExprPath { attrs, qself, path }) = + mem::replace(func, Expr::PLACEHOLDER) + else { + unreachable!(); + }; + match parse_enum_path(TypePath { qself, path }) { + Ok(path) => break path, + Err(path) => { + self.errors.error(&path, "unsupported enum variant path"); + let TypePath { qself, path } = path; + *func = ExprPath { attrs, qself, path }.into(); + return expr_call.into(); + } + } + } + _ => { + self.errors.error( + &expr_call.func, + "#[hdl] function call -- function must be a possibly-parenthesized path", + ); + return expr_call.into(); + } + } + }; + self.process_hdl_method_call( + hdl_attr, + ExprMethodCall { + attrs: expr_call.attrs, + receiver: parse_quote_spanned! {span=> + <#enum_path as ::fayalite::ty::StaticType>::TYPE + }, + dot_token: Token![.](span), + method: variant_name, + turbofish: None, + paren_token: expr_call.paren_token, + args: expr_call.args, + }, + ) + } + pub(crate) fn process_hdl_method_call( + &mut self, + hdl_attr: HdlAttr, + mut expr_method_call: ExprMethodCall, + ) -> Expr { + let ExprOptions { sim } = hdl_attr.body; + let span = hdl_attr.kw.span; + // remove any number of groups and up to one paren + let mut receiver = &mut *expr_method_call.receiver; + let mut has_group = false; + let receiver = loop { + match receiver { + Expr::Group(ExprGroup { expr, .. }) => { + has_group = true; + receiver = expr; + } + Expr::Paren(ExprParen { expr, .. }) => break &mut **expr, + receiver @ Expr::Path(_) => break receiver, + _ => { + if !has_group { + self.errors.error( + &expr_method_call.receiver, + "#[hdl] on a method call needs parenthesized receiver", + ); + } + break &mut *expr_method_call.receiver; + } + } + }; + let func = if sim.is_some() { + parse_quote_spanned! {span=> + ::fayalite::enum_::enum_type_to_sim_builder + } + } else { + parse_quote_spanned! {span=> + ::fayalite::enum_::assert_is_enum_type + } + }; + *expr_method_call.receiver = ExprCall { + attrs: vec![], + func, + paren_token: Paren(span), + args: Punctuated::from_iter([mem::replace(receiver, Expr::PLACEHOLDER)]), + } + .into(); + expr_method_call.into() + } } diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs index 1d53104..68218c1 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs @@ -3,22 +3,119 @@ use crate::{ fold::{impl_fold, DoFold}, kw, - module::transform_body::{with_debug_clone_and_fold, Visitor}, + module::transform_body::{ + empty_let, with_debug_clone_and_fold, wrap_ty_with_expr, ExprOptions, Visitor, + }, Errors, HdlAttr, PairsIterExt, }; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote_spanned, ToTokens, TokenStreamExt}; +use std::collections::BTreeSet; use syn::{ - fold::{fold_arm, fold_expr_match, fold_pat, Fold}, - parse::Nothing, + fold::{fold_arm, fold_expr_match, fold_local, fold_pat, Fold}, parse_quote_spanned, punctuated::Punctuated, spanned::Spanned, token::{Brace, Paren}, - Arm, Attribute, Expr, ExprMatch, FieldPat, Ident, Member, Pat, PatIdent, PatOr, PatParen, - PatPath, PatRest, PatStruct, PatTupleStruct, PatWild, Path, PathSegment, Token, TypePath, + Arm, Attribute, Expr, ExprMatch, FieldPat, Ident, Local, Member, Pat, PatIdent, PatOr, + PatParen, PatPath, PatRest, PatStruct, PatTuple, PatTupleStruct, PatWild, Path, PathSegment, + Token, TypePath, }; +macro_rules! visit_trait { + ( + $($vis:vis fn $fn:ident($state:ident: _, $value:ident: &$Value:ty) $block:block)* + ) => { + trait VisitMatchPat<'a> { + $(fn $fn(&mut self, $value: &'a $Value) { + $fn(self, $value); + })* + } + + $($vis fn $fn<'a>($state: &mut (impl ?Sized + VisitMatchPat<'a>), $value: &'a $Value) $block)* + }; +} + +visit_trait! { + fn visit_match_pat_binding(_state: _, v: &MatchPatBinding) { + let MatchPatBinding { ident: _ } = v; + } + fn visit_match_pat_wild(_state: _, v: &MatchPatWild) { + let MatchPatWild { underscore_token: _ } = v; + } + fn visit_match_pat_rest(_state: _, v: &MatchPatRest) { + let MatchPatRest { dot2_token: _ } = v; + } + fn visit_match_pat_paren(state: _, v: &MatchPatParen) { + let MatchPatParen { paren_token: _, pat } = v; + state.visit_match_pat(pat); + } + fn visit_match_pat_paren_simple(state: _, v: &MatchPatParen) { + let MatchPatParen { paren_token: _, pat } = v; + state.visit_match_pat_simple(pat); + } + fn visit_match_pat_or(state: _, v: &MatchPatOr) { + let MatchPatOr { leading_vert: _, cases } = v; + for v in cases { + state.visit_match_pat(v); + } + } + fn visit_match_pat_or_simple(state: _, v: &MatchPatOr) { + let MatchPatOr { leading_vert: _, cases } = v; + for v in cases { + state.visit_match_pat_simple(v); + } + } + fn visit_match_pat_struct_field(state: _, v: &MatchPatStructField) { + let MatchPatStructField { field_name: _, colon_token: _, pat } = v; + state.visit_match_pat_simple(pat); + } + fn visit_match_pat_struct(state: _, v: &MatchPatStruct) { + let MatchPatStruct { match_span: _, path: _, brace_token: _, fields, rest: _ } = v; + for v in fields { + state.visit_match_pat_struct_field(v); + } + } + fn visit_match_pat_tuple(state: _, v: &MatchPatTuple) { + let MatchPatTuple { paren_token: _, fields } = v; + for v in fields { + state.visit_match_pat_simple(v); + } + } + fn visit_match_pat_enum_variant(state: _, v: &MatchPatEnumVariant) { + let MatchPatEnumVariant { + match_span:_, + sim:_, + variant_path: _, + enum_path: _, + variant_name: _, + field, + } = v; + if let Some((_, v)) = field { + state.visit_match_pat_simple(v); + } + } + fn visit_match_pat_simple(state: _, v: &MatchPatSimple) { + match v { + MatchPatSimple::Paren(v) => state.visit_match_pat_paren_simple(v), + MatchPatSimple::Or(v) => state.visit_match_pat_or_simple(v), + MatchPatSimple::Binding(v) => state.visit_match_pat_binding(v), + MatchPatSimple::Wild(v) => state.visit_match_pat_wild(v), + MatchPatSimple::Rest(v) => state.visit_match_pat_rest(v), + } + } + fn visit_match_pat(state: _, v: &MatchPat) { + match v { + MatchPat::Simple(v) => state.visit_match_pat_simple(v), + MatchPat::Or(v) => state.visit_match_pat_or(v), + MatchPat::Paren(v) => state.visit_match_pat_paren(v), + MatchPat::Struct(v) => state.visit_match_pat_struct(v), + MatchPat::Tuple(v) => state.visit_match_pat_tuple(v), + MatchPat::EnumVariant(v) => state.visit_match_pat_enum_variant(v), + } + } +} + with_debug_clone_and_fold! { struct MatchPatBinding<> { ident: Ident, @@ -53,6 +150,15 @@ with_debug_clone_and_fold! { } } +impl

MatchPatOr

{ + /// returns the first `|` between two patterns + fn first_inner_vert(&self) -> Option { + let mut pairs = self.cases.pairs(); + pairs.next_back(); + pairs.next().and_then(|v| v.into_tuple().1.copied()) + } +} + impl ToTokens for MatchPatOr

{ fn to_tokens(&self, tokens: &mut TokenStream) { let Self { @@ -77,6 +183,19 @@ impl ToTokens for MatchPatWild { } } +with_debug_clone_and_fold! { + struct MatchPatRest<> { + dot2_token: Token![..], + } +} + +impl ToTokens for MatchPatRest { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { dot2_token } = self; + dot2_token.to_tokens(tokens); + } +} + with_debug_clone_and_fold! { struct MatchPatStructField<> { field_name: Ident, @@ -159,9 +278,29 @@ impl ToTokens for MatchPatStruct { } } +with_debug_clone_and_fold! { + struct MatchPatTuple<> { + paren_token: Paren, + fields: Punctuated, + } +} + +impl ToTokens for MatchPatTuple { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + paren_token, + fields, + } = self; + paren_token.surround(tokens, |tokens| { + fields.to_tokens(tokens); + }) + } +} + with_debug_clone_and_fold! { struct MatchPatEnumVariant<> { match_span: Span, + sim: Option<(kw::sim,)>, variant_path: Path, enum_path: Path, variant_name: Ident, @@ -173,6 +312,7 @@ impl ToTokens for MatchPatEnumVariant { fn to_tokens(&self, tokens: &mut TokenStream) { let Self { match_span, + sim, variant_path: _, enum_path, variant_name, @@ -182,7 +322,28 @@ impl ToTokens for MatchPatEnumVariant { __MatchTy::<#enum_path>::#variant_name } .to_tokens(tokens); - if let Some((paren_token, field)) = field { + if sim.is_some() { + if let Some((paren_token, field)) = field { + paren_token.surround(tokens, |tokens| { + field.to_tokens(tokens); + match field { + MatchPatSimple::Paren(_) + | MatchPatSimple::Or(_) + | MatchPatSimple::Binding(_) + | MatchPatSimple::Wild(_) => quote_spanned! {*match_span=> + , _ + } + .to_tokens(tokens), + MatchPatSimple::Rest(_) => {} + } + }); + } else { + quote_spanned! {*match_span=> + (_) + } + .to_tokens(tokens); + } + } else if let Some((paren_token, field)) = field { paren_token.surround(tokens, |tokens| field.to_tokens(tokens)); } } @@ -194,6 +355,7 @@ enum MatchPatSimple { Or(MatchPatOr), Binding(MatchPatBinding), Wild(MatchPatWild), + Rest(MatchPatRest), } impl_fold! { @@ -202,6 +364,7 @@ impl_fold! { Or(MatchPatOr), Binding(MatchPatBinding), Wild(MatchPatWild), + Rest(MatchPatRest), } } @@ -212,17 +375,18 @@ impl ToTokens for MatchPatSimple { Self::Paren(v) => v.to_tokens(tokens), Self::Binding(v) => v.to_tokens(tokens), Self::Wild(v) => v.to_tokens(tokens), + Self::Rest(v) => v.to_tokens(tokens), } } } -struct EnumPath { - variant_path: Path, - enum_path: Path, - variant_name: Ident, +pub(crate) struct EnumPath { + pub(crate) variant_path: Path, + pub(crate) enum_path: Path, + pub(crate) variant_name: Ident, } -fn parse_enum_path(variant_path: TypePath) -> Result { +pub(crate) fn parse_enum_path(variant_path: TypePath) -> Result { let TypePath { qself: None, path: variant_path, @@ -278,6 +442,7 @@ trait ParseMatchPat: Sized { fn or(v: MatchPatOr) -> Self; fn paren(v: MatchPatParen) -> Self; fn struct_(state: &mut HdlMatchParseState<'_>, v: MatchPatStruct) -> Result; + fn tuple(state: &mut HdlMatchParseState<'_>, v: MatchPatTuple) -> Result; fn enum_variant(state: &mut HdlMatchParseState<'_>, v: MatchPatEnumVariant) -> Result; fn parse(state: &mut HdlMatchParseState<'_>, pat: Pat) -> Result { @@ -313,6 +478,7 @@ trait ParseMatchPat: Sized { state, MatchPatEnumVariant { match_span: state.match_span, + sim: state.sim, variant_path, enum_path, variant_name, @@ -359,6 +525,7 @@ trait ParseMatchPat: Sized { state, MatchPatEnumVariant { match_span: state.match_span, + sim: state.sim, variant_path, enum_path, variant_name, @@ -443,6 +610,7 @@ trait ParseMatchPat: Sized { state, MatchPatEnumVariant { match_span: state.match_span, + sim: state.sim, variant_path, enum_path, variant_name, @@ -462,7 +630,34 @@ trait ParseMatchPat: Sized { }) => Ok(Self::simple(MatchPatSimple::Wild(MatchPatWild { underscore_token, }))), - Pat::Tuple(_) | Pat::Slice(_) | Pat::Const(_) | Pat::Lit(_) | Pat::Range(_) => { + Pat::Tuple(PatTuple { + attrs: _, + paren_token, + elems, + }) => { + let fields = elems + .into_pairs() + .filter_map_pair_value(|field_pat| { + if let Pat::Rest(PatRest { + attrs: _, + dot2_token, + }) = field_pat + { + Some(MatchPatSimple::Rest(MatchPatRest { dot2_token })) + } else { + MatchPatSimple::parse(state, field_pat).ok() + } + }) + .collect(); + Self::tuple( + state, + MatchPatTuple { + paren_token, + fields, + }, + ) + } + Pat::Slice(_) | Pat::Const(_) | Pat::Lit(_) | Pat::Range(_) => { state .errors .error(pat, "not yet implemented in #[hdl] patterns"); @@ -497,6 +692,14 @@ impl ParseMatchPat for MatchPatSimple { Err(()) } + fn tuple(state: &mut HdlMatchParseState<'_>, v: MatchPatTuple) -> Result { + state.errors.push(syn::Error::new( + v.paren_token.span.open(), + "matching tuples is not yet implemented inside structs/enums in #[hdl] patterns", + )); + Err(()) + } + fn enum_variant( state: &mut HdlMatchParseState<'_>, v: MatchPatEnumVariant, @@ -515,6 +718,7 @@ enum MatchPat { Or(MatchPatOr), Paren(MatchPatParen), Struct(MatchPatStruct), + Tuple(MatchPatTuple), EnumVariant(MatchPatEnumVariant), } @@ -524,6 +728,7 @@ impl_fold! { Or(MatchPatOr), Paren(MatchPatParen), Struct(MatchPatStruct), + Tuple(MatchPatTuple), EnumVariant(MatchPatEnumVariant), } } @@ -545,6 +750,10 @@ impl ParseMatchPat for MatchPat { Ok(Self::Struct(v)) } + fn tuple(_state: &mut HdlMatchParseState<'_>, v: MatchPatTuple) -> Result { + Ok(Self::Tuple(v)) + } + fn enum_variant( _state: &mut HdlMatchParseState<'_>, v: MatchPatEnumVariant, @@ -560,6 +769,7 @@ impl ToTokens for MatchPat { Self::Or(v) => v.to_tokens(tokens), Self::Paren(v) => v.to_tokens(tokens), Self::Struct(v) => v.to_tokens(tokens), + Self::Tuple(v) => v.to_tokens(tokens), Self::EnumVariant(v) => v.to_tokens(tokens), } } @@ -622,10 +832,6 @@ struct RewriteAsCheckMatch { } impl Fold for RewriteAsCheckMatch { - fn fold_field_pat(&mut self, mut i: FieldPat) -> FieldPat { - i.colon_token = Some(Token![:](i.member.span())); - i - } fn fold_pat(&mut self, pat: Pat) -> Pat { match pat { Pat::Ident(mut pat_ident) => match parse_enum_ident(pat_ident.ident) { @@ -740,17 +946,177 @@ impl Fold for RewriteAsCheckMatch { // don't recurse into expressions i } + fn fold_local(&mut self, mut let_stmt: Local) -> Local { + if let Some(syn::LocalInit { + eq_token, + expr: _, + diverge, + }) = let_stmt.init.take() + { + let_stmt.init = Some(syn::LocalInit { + eq_token, + expr: parse_quote_spanned! {self.span=> + __match_value + }, + diverge: diverge.map(|(else_, _expr)| { + ( + else_, + parse_quote_spanned! {self.span=> + match __infallible {} + }, + ) + }), + }); + } + fold_local(self, let_stmt) + } } struct HdlMatchParseState<'a> { + sim: Option<(kw::sim,)>, match_span: Span, errors: &'a mut Errors, } +struct HdlLetPatVisitState<'a> { + errors: &'a mut Errors, + bindings: BTreeSet<&'a Ident>, +} + +impl<'a> VisitMatchPat<'a> for HdlLetPatVisitState<'a> { + fn visit_match_pat_binding(&mut self, v: &'a MatchPatBinding) { + self.bindings.insert(&v.ident); + } + + fn visit_match_pat_or(&mut self, v: &'a MatchPatOr) { + if let Some(first_inner_vert) = v.first_inner_vert() { + self.errors.error( + first_inner_vert, + "or-patterns are not supported in let statements", + ); + } + visit_match_pat_or(self, v); + } + + fn visit_match_pat_or_simple(&mut self, v: &'a MatchPatOr) { + if let Some(first_inner_vert) = v.first_inner_vert() { + self.errors.error( + first_inner_vert, + "or-patterns are not supported in let statements", + ); + } + visit_match_pat_or_simple(self, v); + } + + fn visit_match_pat_enum_variant(&mut self, v: &'a MatchPatEnumVariant) { + self.errors.error(v, "refutable pattern in let statement"); + } +} + impl Visitor<'_> { + pub(crate) fn process_hdl_let_pat( + &mut self, + hdl_attr: HdlAttr, + mut let_stmt: Local, + ) -> Local { + let span = let_stmt.let_token.span(); + let ExprOptions { sim } = hdl_attr.body; + if let Pat::Type(pat) = &mut let_stmt.pat { + *pat.ty = wrap_ty_with_expr((*pat.ty).clone()); + } + let check_let_stmt = RewriteAsCheckMatch { span }.fold_local(let_stmt.clone()); + let Local { + attrs: _, + let_token, + pat, + init, + semi_token, + } = let_stmt; + let Some(syn::LocalInit { + eq_token, + expr, + diverge, + }) = init + else { + self.errors + .error(let_token, "#[hdl] let must be assigned a value"); + return empty_let(); + }; + if let Some((else_, _)) = diverge { + // TODO: implement let-else + self.errors + .error(else_, "#[hdl] let ... else { ... } is not implemented"); + return empty_let(); + } + let Ok(pat) = MatchPat::parse( + &mut HdlMatchParseState { + sim, + match_span: span, + errors: &mut self.errors, + }, + pat, + ) else { + return empty_let(); + }; + let mut state = HdlLetPatVisitState { + errors: &mut self.errors, + bindings: BTreeSet::new(), + }; + state.visit_match_pat(&pat); + let HdlLetPatVisitState { + errors: _, + bindings, + } = state; + let retval = if sim.is_some() { + parse_quote_spanned! {span=> + let (#(#bindings,)*) = { + type __MatchTy = ::SimValue; + let __match_value = ::fayalite::sim::value::ToSimValue::to_sim_value(&(#expr)); + #let_token #pat #eq_token ::fayalite::sim::value::SimValue::into_value(__match_value) #semi_token + (#(#bindings,)*) + }; + } + } else { + parse_quote_spanned! {span=> + let (#(#bindings,)* __scope,) = { + type __MatchTy = ::MatchVariant; + let __match_expr = ::fayalite::expr::ToExpr::to_expr(&(#expr)); + ::fayalite::expr::check_match_expr( + __match_expr, + |__match_value, __infallible| { + #[allow(unused_variables)] + #check_let_stmt + match __infallible {} + }, + ); + let mut __match_iter = ::fayalite::module::match_(__match_expr); + let ::fayalite::__std::option::Option::Some(__match_variant) = + ::fayalite::__std::iter::Iterator::next(&mut __match_iter) + else { + ::fayalite::__std::unreachable!("#[hdl] let with uninhabited type"); + }; + let ::fayalite::__std::option::Option::None = + ::fayalite::__std::iter::Iterator::next(&mut __match_iter) + else { + ::fayalite::__std::unreachable!("#[hdl] let with refutable pattern"); + }; + let (__match_variant, __scope) = + ::fayalite::ty::MatchVariantAndInactiveScope::match_activate_scope( + __match_variant, + ); + #let_token #pat #eq_token __match_variant #semi_token + (#(#bindings,)* __scope,) + }; + } + }; + match retval { + syn::Stmt::Local(retval) => retval, + _ => unreachable!(), + } + } pub(crate) fn process_hdl_match( &mut self, - _hdl_attr: HdlAttr, + hdl_attr: HdlAttr, expr_match: ExprMatch, ) -> Expr { let span = expr_match.match_token.span(); @@ -762,8 +1128,9 @@ impl Visitor<'_> { brace_token: _, arms, } = expr_match; - self.require_normal_module_or_fn(match_token); + let ExprOptions { sim } = hdl_attr.body; let mut state = HdlMatchParseState { + sim, match_span: span, errors: &mut self.errors, }; @@ -771,24 +1138,36 @@ impl Visitor<'_> { arms.into_iter() .filter_map(|arm| MatchArm::parse(&mut state, arm).ok()), ); - let expr = quote_spanned! {span=> - { - type __MatchTy = ::MatchVariant; - let __match_expr = ::fayalite::expr::ToExpr::to_expr(&(#expr)); - ::fayalite::expr::check_match_expr(__match_expr, |__match_value, __infallible| { - #[allow(unused_variables)] - #check_match - }); - for __match_variant in ::fayalite::module::match_(__match_expr) { - let (__match_variant, __scope) = - ::fayalite::ty::MatchVariantAndInactiveScope::match_activate_scope( - __match_variant, - ); - #match_token __match_variant { + let expr = if sim.is_some() { + quote_spanned! {span=> + { + type __MatchTy = ::SimValue; + let __match_expr = ::fayalite::sim::value::ToSimValue::to_sim_value(&(#expr)); + #match_token ::fayalite::sim::value::SimValue::into_value(__match_expr) { #(#arms)* } } } + } else { + quote_spanned! {span=> + { + type __MatchTy = ::MatchVariant; + let __match_expr = ::fayalite::expr::ToExpr::to_expr(&(#expr)); + ::fayalite::expr::check_match_expr(__match_expr, |__match_value, __infallible| { + #[allow(unused_variables)] + #check_match + }); + for __match_variant in ::fayalite::module::match_(__match_expr) { + let (__match_variant, __scope) = + ::fayalite::ty::MatchVariantAndInactiveScope::match_activate_scope( + __match_variant, + ); + #match_token __match_variant { + #(#arms)* + } + } + } + } }; syn::parse2(expr).unwrap() } diff --git a/crates/fayalite-proc-macros-impl/src/process_cfg.rs b/crates/fayalite-proc-macros-impl/src/process_cfg.rs new file mode 100644 index 0000000..5cff08f --- /dev/null +++ b/crates/fayalite-proc-macros-impl/src/process_cfg.rs @@ -0,0 +1,2527 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{Cfg, CfgAttr, Cfgs, Errors}; +use proc_macro2::Ident; +use std::{collections::VecDeque, marker::PhantomData}; +use syn::{ + punctuated::{Pair, Punctuated}, + Token, +}; + +struct State { + cfgs: Cfgs, + errors: Errors, + _phantom: PhantomData

, +} + +impl State

{ + #[must_use] + fn eval_cfg(&mut self, cfg: Cfg) -> bool { + struct MyDispatch<'a> { + cfg: Cfg, + _phantom: PhantomData<&'a ()>, + } + impl<'a> PhaseDispatch for MyDispatch<'a> { + type Args = &'a mut State

; + type Output = bool; + + fn dispatch_collect( + self, + args: Self::Args, + ) -> Self::Output { + args.cfgs.insert_cfg(self.cfg, ()); + true + } + + fn dispatch_process( + self, + args: Self::Args, + ) -> Self::Output { + if let Some(&retval) = args.cfgs.cfgs_map.get(&self.cfg) { + retval + } else { + args.errors.error(self.cfg, "unrecognized cfg -- cfg wasn't evaluated when running `__cfg_expansion_helper!`"); + true + } + } + } + P::dispatch( + MyDispatch { + cfg, + _phantom: PhantomData, + }, + self, + ) + } + #[must_use] + fn eval_cfgs( + &mut self, + mut attrs: Vec, + ) -> Option, P>> { + let mut queue = VecDeque::from(attrs); + attrs = Vec::with_capacity(queue.len()); // cfg_attr is rare, and cfg can't increase length + while let Some(attr) = queue.pop_front() { + if attr.path().is_ident("cfg") { + if let Some(cfg) = self.errors.ok(Cfg::parse_meta(&attr.meta)) { + if !self.eval_cfg(cfg) { + return None; + } + continue; + } + } else if attr.path().is_ident("cfg_attr") { + if let Some(cfg_attr) = self.errors.ok(CfgAttr::parse_meta(&attr.meta)) { + if self.eval_cfg(cfg_attr.to_cfg()) { + // push onto queue since cfg_attr(, cfg_attr(, )) is valid + for meta in cfg_attr.attrs { + queue.push_front(syn::Attribute { + pound_token: attr.pound_token, + style: attr.style, + bracket_token: attr.bracket_token, + meta, + }); + } + } + continue; + } + } + attrs.push(attr); + } + Some(Output::new(attrs)) + } + fn process_qself_and_path( + &mut self, + qself: Option, + path: syn::Path, + ) -> Option<(Output, P>, Output)> { + let qself = if let Some(syn::QSelf { + lt_token, + ty, + position, + as_token, + gt_token, + }) = qself + { + ty.process(self)?.map(|ty| { + Some(syn::QSelf { + lt_token, + ty, + position, + as_token, + gt_token, + }) + }) + } else { + Output::new(None) + }; + let syn::Path { + leading_colon, + segments, + } = path; + // path segments don't get removed + let path = segments.process(self)?.map(|segments| syn::Path { + leading_colon, + segments, + }); + Some((qself, path)) + } +} + +trait PhaseDispatch { + type Args; + type Output; + fn dispatch_collect(self, args: Self::Args) + -> Self::Output; + fn dispatch_process(self, args: Self::Args) + -> Self::Output; +} + +trait Phase: Sized + 'static { + type Output; + type CfgsValue; + fn output_new(v: T) -> Output; + fn output_map U>(v: Output, f: F) -> Output; + fn output_zip(t: Output, u: Output) -> Output<(T, U), Self>; + fn dispatch(d: D, args: D::Args) -> D::Output; +} + +struct CollectCfgsPhase; + +impl Phase for CollectCfgsPhase { + type Output = (); + type CfgsValue = (); + + fn output_new(_v: T) -> Output { + Output(()) + } + + fn output_map U>(_v: Output, _f: F) -> Output { + Output(()) + } + + fn output_zip(_t: Output, _u: Output) -> Output<(T, U), Self> { + Output(()) + } + + fn dispatch(d: D, args: D::Args) -> D::Output { + d.dispatch_collect(args) + } +} + +struct ProcessCfgsPhase; + +impl Phase for ProcessCfgsPhase { + type Output = T; + type CfgsValue = bool; + + fn output_new(v: T) -> Output { + Output(v) + } + + fn output_map U>(v: Output, f: F) -> Output { + Output(f(v.0)) + } + + fn output_zip(t: Output, u: Output) -> Output<(T, U), Self> { + Output((t.0, u.0)) + } + + fn dispatch(d: D, args: D::Args) -> D::Output { + d.dispatch_process(args) + } +} + +struct Output(P::Output); + +trait OutputZip: Sized { + type Output; + fn zip(self) -> Output; + fn call R>(self, f: F) -> Output { + self.zip().map(f) + } +} + +impl OutputZip

for () { + type Output = (); + + fn zip(self) -> Output { + Output::new(()) + } +} + +impl OutputZip

for (Output,) { + type Output = (T,); + + fn zip(self) -> Output { + self.0.map(|v| (v,)) + } +} + +macro_rules! impl_zip { + ($first_arg:ident: $first_T:ident, $($arg:ident: $T:ident),* $(,)?) => { + impl_zip!(@step [], [($first_arg: $first_T) $(($arg: $T))*], (),); + }; + ( + @impl($first_arg:tt,), + $tuple_pat:tt, + ) => {}; + ( + @impl(($first_arg:ident: $first_T:ident), + $(($arg:ident: $T:ident),)*), + $tuple_pat:tt, + ) => { + impl<$first_T, $($T,)* P: Phase> OutputZip

for (Output<$first_T, P>, $(Output<$T, P>),*) { + type Output = ($first_T, $($T),*); + fn zip(self) -> Output<($first_T, $($T),*), P> { + let (tuples, $($arg),*) = self; + $(let tuples = P::output_zip(tuples, $arg);)* + tuples.map(|$tuple_pat| ($first_arg, $($arg),*)) + } + } + }; + ( + @step [$($cur:tt)*], + [], + $tuple_pat:tt, + ) => {}; + ( + @step [$($cur:tt)*], + [($next_arg:ident: $next_T:ident) $($rest:tt)*], + (), + ) => { + impl_zip!(@impl($($cur,)* ($next_arg: $next_T),), $next_arg,); + impl_zip!(@step [$($cur)* ($next_arg: $next_T)], [$($rest)*], $next_arg,); + }; + ( + @step [$($cur:tt)*], + [($next_arg:ident: $next_T:ident) $($rest:tt)*], + $tuple_pat:tt, + ) => { + impl_zip!(@impl($($cur,)* ($next_arg: $next_T),), ($tuple_pat, $next_arg),); + impl_zip!(@step [$($cur)* ($next_arg: $next_T)], [$($rest)*], ($tuple_pat, $next_arg),); + }; +} + +impl_zip!(t0: T0, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7, t8: T8, t9: T9, t10: T10, t11: T11); + +impl Copy for Output where P::Output: Copy {} + +impl Clone for Output +where + P::Output: Clone, +{ + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Output { + fn new(v: T) -> Self { + P::output_new(v) + } + fn map U>(self, f: F) -> Output { + P::output_map(self, f) + } +} + +trait Process: Sized { + #[must_use] + fn process(self, state: &mut State

) -> Option>; +} + +impl Process

for syn::Item { + fn process(self, _state: &mut State

) -> Option> { + // don't recurse into items + Some(Output::new(self)) + } +} + +impl Process

for Vec { + fn process(self, state: &mut State

) -> Option> { + state.eval_cfgs(self) + } +} + +impl, P: Phase> Process

for Box { + fn process(self, state: &mut State

) -> Option> { + Some(T::process(*self, state)?.map(Box::new)) + } +} + +trait ProcessVecElement { + const REMOVE_ELEMENTS: bool; +} + +impl ProcessVecElement for syn::Arm { + const REMOVE_ELEMENTS: bool = true; +} + +impl ProcessVecElement for syn::Stmt { + const REMOVE_ELEMENTS: bool = true; +} + +impl ProcessVecElement for syn::ForeignItem { + const REMOVE_ELEMENTS: bool = true; +} + +impl ProcessVecElement for syn::ImplItem { + const REMOVE_ELEMENTS: bool = true; +} + +impl ProcessVecElement for syn::Item { + const REMOVE_ELEMENTS: bool = true; +} + +impl ProcessVecElement for syn::TraitItem { + const REMOVE_ELEMENTS: bool = true; +} + +impl + ProcessVecElement, P: Phase> Process

for Vec { + fn process(self, state: &mut State

) -> Option> { + let mut output = Output::new(Vec::new()); + for value in self { + if let Some(value) = value.process(state) { + output = (output, value).call(|(mut output, value)| { + output.push(value); + output + }); + } else if !T::REMOVE_ELEMENTS { + return None; + } + } + Some(output) + } +} + +trait ProcessOption { + /// if a configured-off value causes this value to be `None` instead of propagating the configuring-off + const REMOVE_VALUE: bool; +} + +impl ProcessOption for syn::Abi { + const REMOVE_VALUE: bool = true; +} + +impl ProcessOption for syn::Block { + const REMOVE_VALUE: bool = true; +} + +impl ProcessOption for syn::WhereClause { + const REMOVE_VALUE: bool = true; +} + +impl ProcessOption for syn::Expr { + const REMOVE_VALUE: bool = true; +} + +impl ProcessOption for syn::Type { + const REMOVE_VALUE: bool = true; +} + +impl ProcessOption for Box { + const REMOVE_VALUE: bool = true; +} + +impl ProcessOption for syn::AngleBracketedGenericArguments { + const REMOVE_VALUE: bool = true; +} + +impl ProcessOption for syn::ImplRestriction { + const REMOVE_VALUE: bool = false; +} + +impl ProcessOption for syn::BoundLifetimes { + const REMOVE_VALUE: bool = false; +} + +impl ProcessOption for (Token![=], syn::Expr) { + const REMOVE_VALUE: bool = true; +} + +impl ProcessOption for (Token![=], syn::Type) { + const REMOVE_VALUE: bool = true; +} + +impl ProcessOption for (Token![if], Box) { + const REMOVE_VALUE: bool = true; +} + +impl ProcessOption for (Token![else], Box) { + const REMOVE_VALUE: bool = true; +} + +impl ProcessOption for (Token![&], Option) { + const REMOVE_VALUE: bool = true; +} + +impl ProcessOption for (Token![as], Ident) { + const REMOVE_VALUE: bool = true; +} + +impl ProcessOption for (Ident, Token![:]) { + const REMOVE_VALUE: bool = true; +} + +impl ProcessOption for (Option, syn::Path, Token![for]) { + const REMOVE_VALUE: bool = false; +} + +impl ProcessOption for syn::BareVariadic { + const REMOVE_VALUE: bool = true; +} + +impl ProcessOption for syn::Variadic { + const REMOVE_VALUE: bool = true; +} + +impl ProcessOption for syn::LocalInit { + const REMOVE_VALUE: bool = false; +} + +impl ProcessOption for syn::Label { + const REMOVE_VALUE: bool = true; +} + +impl ProcessOption for syn::PatRest { + const REMOVE_VALUE: bool = true; +} + +impl ProcessOption for (Box, Token![:]) { + const REMOVE_VALUE: bool = false; +} + +impl ProcessOption for (Token![@], Box) { + const REMOVE_VALUE: bool = false; +} + +impl ProcessOption for (syn::token::Brace, Vec) { + const REMOVE_VALUE: bool = false; +} + +impl + ProcessOption, P: Phase> Process

for Option { + fn process(self, state: &mut State

) -> Option> { + if let Some(this) = self { + match this.process(state) { + Some(v) => Some(v.map(Some)), + None => { + if T::REMOVE_VALUE { + Some(Output::new(None)) + } else { + None + } + } + } + } else { + Some(Output::new(None)) + } + } +} + +trait ProcessPunctuatedElement { + const REMOVE_ELEMENTS: bool; +} + +impl + ProcessPunctuatedElement, P: Phase, Punct: Default> Process

+ for Punctuated +{ + fn process(self, state: &mut State

) -> Option> { + let mut output = Output::new(Punctuated::::new()); + for pair in self.into_pairs() { + let (value, punct) = pair.into_tuple(); + if let Some(value) = value.process(state) { + output = (output, value).call(|(mut output, value)| { + output.extend([Pair::new(value, punct)]); + output + }); + } else if !T::REMOVE_ELEMENTS { + return None; + } + } + Some(output) + } +} + +impl ProcessPunctuatedElement for syn::PathSegment { + const REMOVE_ELEMENTS: bool = false; +} + +impl ProcessPunctuatedElement for syn::Type { + const REMOVE_ELEMENTS: bool = true; +} + +impl ProcessPunctuatedElement for syn::Expr { + const REMOVE_ELEMENTS: bool = true; +} + +impl ProcessPunctuatedElement for syn::Pat { + const REMOVE_ELEMENTS: bool = true; +} + +impl ProcessPunctuatedElement for syn::CapturedParam { + const REMOVE_ELEMENTS: bool = true; +} + +impl ProcessPunctuatedElement for syn::GenericArgument { + const REMOVE_ELEMENTS: bool = true; +} + +impl ProcessPunctuatedElement for syn::GenericParam { + const REMOVE_ELEMENTS: bool = true; +} + +impl ProcessPunctuatedElement for syn::Lifetime { + const REMOVE_ELEMENTS: bool = true; +} + +impl ProcessPunctuatedElement for syn::WherePredicate { + const REMOVE_ELEMENTS: bool = true; +} + +impl ProcessPunctuatedElement for syn::Variant { + const REMOVE_ELEMENTS: bool = true; +} + +impl ProcessPunctuatedElement for syn::FnArg { + const REMOVE_ELEMENTS: bool = true; +} + +impl ProcessPunctuatedElement for syn::BareFnArg { + const REMOVE_ELEMENTS: bool = true; +} + +impl ProcessPunctuatedElement for syn::TypeParamBound { + const REMOVE_ELEMENTS: bool = true; +} + +impl ProcessPunctuatedElement for syn::FieldValue { + const REMOVE_ELEMENTS: bool = true; +} + +impl ProcessPunctuatedElement for syn::Field { + const REMOVE_ELEMENTS: bool = true; +} + +impl ProcessPunctuatedElement for syn::FieldPat { + const REMOVE_ELEMENTS: bool = true; +} + +impl ProcessPunctuatedElement for syn::UseTree { + const REMOVE_ELEMENTS: bool = true; +} + +impl, U: Process

, P: Phase> Process

for (T, U) { + fn process(self, state: &mut State

) -> Option> { + let (t, u) = self; + let t = t.process(state)?; + let u = u.process(state)?; + Some((t, u).zip()) + } +} + +impl, U: Process

, V: Process

, P: Phase> Process

for (T, U, V) { + fn process(self, state: &mut State

) -> Option> { + let (t, u, v) = self; + let t = t.process(state)?; + let u = u.process(state)?; + let v = v.process(state)?; + Some((t, u, v).zip()) + } +} + +macro_rules! process_no_op { + ($ty:ty) => { + impl Process

for $ty { + fn process(self, _state: &mut State

) -> Option> { + Some(Output::new(self)) + } + } + + impl ProcessOption for $ty { + const REMOVE_VALUE: bool = false; + } + }; +} + +process_no_op!(Token![Self]); +process_no_op!(Token![abstract]); +process_no_op!(Token![as]); +process_no_op!(Token![async]); +process_no_op!(Token![auto]); +process_no_op!(Token![await]); +process_no_op!(Token![become]); +process_no_op!(Token![box]); +process_no_op!(Token![break]); +process_no_op!(Token![const]); +process_no_op!(Token![continue]); +process_no_op!(Token![crate]); +process_no_op!(Token![default]); +process_no_op!(Token![do]); +process_no_op!(Token![dyn]); +process_no_op!(Token![else]); +process_no_op!(Token![enum]); +process_no_op!(Token![extern]); +process_no_op!(Token![final]); +process_no_op!(Token![fn]); +process_no_op!(Token![for]); +process_no_op!(Token![if]); +process_no_op!(Token![impl]); +process_no_op!(Token![in]); +process_no_op!(Token![let]); +process_no_op!(Token![loop]); +process_no_op!(Token![macro]); +process_no_op!(Token![match]); +process_no_op!(Token![mod]); +process_no_op!(Token![move]); +process_no_op!(Token![mut]); +process_no_op!(Token![override]); +process_no_op!(Token![priv]); +process_no_op!(Token![pub]); +process_no_op!(Token![raw]); +process_no_op!(Token![ref]); +process_no_op!(Token![return]); +process_no_op!(Token![self]); +process_no_op!(Token![static]); +process_no_op!(Token![struct]); +process_no_op!(Token![super]); +process_no_op!(Token![trait]); +process_no_op!(Token![try]); +process_no_op!(Token![type]); +process_no_op!(Token![typeof]); +process_no_op!(Token![union]); +process_no_op!(Token![unsafe]); +process_no_op!(Token![unsized]); +process_no_op!(Token![use]); +process_no_op!(Token![virtual]); +process_no_op!(Token![where]); +process_no_op!(Token![while]); +process_no_op!(Token![yield]); + +process_no_op!(Token![!]); +process_no_op!(Token![!=]); +process_no_op!(Token![#]); +process_no_op!(Token![$]); +process_no_op!(Token![%]); +process_no_op!(Token![%=]); +process_no_op!(Token![&]); +process_no_op!(Token![&&]); +process_no_op!(Token![&=]); +process_no_op!(Token![*]); +process_no_op!(Token![*=]); +process_no_op!(Token![+]); +process_no_op!(Token![+=]); +process_no_op!(Token![,]); +process_no_op!(Token![-]); +process_no_op!(Token![-=]); +process_no_op!(Token![->]); +process_no_op!(Token![.]); +process_no_op!(Token![..]); +process_no_op!(Token![...]); +process_no_op!(Token![..=]); +process_no_op!(Token![/]); +process_no_op!(Token![/=]); +process_no_op!(Token![:]); +process_no_op!(Token![::]); +process_no_op!(Token![;]); +process_no_op!(Token![<]); +process_no_op!(Token![<-]); +process_no_op!(Token![<<]); +process_no_op!(Token![<<=]); +process_no_op!(Token![<=]); +process_no_op!(Token![=]); +process_no_op!(Token![==]); +process_no_op!(Token![=>]); +process_no_op!(Token![>]); +process_no_op!(Token![>=]); +process_no_op!(Token![>>]); +process_no_op!(Token![>>=]); +process_no_op!(Token![?]); +process_no_op!(Token![@]); +process_no_op!(Token![^]); +process_no_op!(Token![^=]); +process_no_op!(Token![_]); +process_no_op!(Token![|]); +process_no_op!(Token![|=]); +process_no_op!(Token![||]); +process_no_op!(Token![~]); + +process_no_op!(syn::token::Brace); +process_no_op!(syn::token::Bracket); +process_no_op!(syn::token::Paren); +process_no_op!(syn::token::Group); + +process_no_op!(Ident); +process_no_op!(syn::Index); +process_no_op!(syn::Lifetime); +process_no_op!(syn::LitBool); +process_no_op!(syn::LitByte); +process_no_op!(syn::LitByteStr); +process_no_op!(syn::LitChar); +process_no_op!(syn::LitCStr); +process_no_op!(syn::LitFloat); +process_no_op!(syn::LitInt); +process_no_op!(syn::LitStr); +process_no_op!(proc_macro2::TokenStream); +process_no_op!(proc_macro2::Literal); + +macro_rules! process_struct { + ($ty:path { + $($field:ident,)* + }) => { + impl Process

for $ty { + fn process(self, state: &mut State

) -> Option> { + let Self { + $($field,)* + } = self; + $(let $field = $field.process(state)?;)* + Some(($($field,)*).call(|($($field,)*)| Self { + $($field,)* + })) + } + } + }; + ($ty:path { + $($fields_before:ident,)* + #[qself] + $qself:ident, + $path:ident, + $($fields_after:ident,)* + }) => { + impl Process

for $ty { + fn process(self, state: &mut State

) -> Option> { + let Self { + $($fields_before,)* + $qself, + $path, + $($fields_after,)* + } = self; + $(let $fields_before = $fields_before.process(state)?;)* + let ($qself, $path) = state.process_qself_and_path($qself, $path)?; + $(let $fields_after = $fields_after.process(state)?;)* + Some(( + $($fields_before,)* + $qself, + $path, + $($fields_after,)* + ).call(|( + $($fields_before,)* + $qself, + $path, + $($fields_after,)* + )| Self { + $($fields_before,)* + $qself, + $path, + $($fields_after,)* + })) + } + } + }; +} + +process_struct! { + syn::Abi { + extern_token, + name, + } +} + +process_struct! { + syn::AngleBracketedGenericArguments { + colon2_token, + lt_token, + args, + gt_token, + } +} + +process_struct! { + syn::Arm { + attrs, + pat, + guard, + fat_arrow_token, + body, + comma, + } +} + +process_struct! { + syn::AssocConst { + ident, + generics, + eq_token, + value, + } +} + +process_struct! { + syn::AssocType { + ident, + generics, + eq_token, + ty, + } +} + +process_struct! { + syn::BareFnArg { + attrs, + name, + ty, + } +} + +process_struct! { + syn::BareVariadic { + attrs, + name, + dots, + comma, + } +} + +process_struct! { + syn::Block { + brace_token, + stmts, + } +} + +process_struct! { + syn::BoundLifetimes { + for_token, + lt_token, + lifetimes, + gt_token, + } +} + +process_struct! { + syn::ConstParam { + attrs, + const_token, + ident, + colon_token, + ty, + eq_token, + default, + } +} + +process_struct! { + syn::Constraint { + ident, + generics, + colon_token, + bounds, + } +} + +process_struct! { + syn::DataEnum { + enum_token, + brace_token, + variants, + } +} + +process_struct! { + syn::DataStruct { + struct_token, + fields, + semi_token, + } +} + +process_struct! { + syn::DataUnion { + union_token, + fields, + } +} + +process_struct! { + syn::DeriveInput { + attrs, + vis, + ident, + generics, + data, + } +} + +process_struct! { + syn::ExprArray { + attrs, + bracket_token, + elems, + } +} + +process_struct! { + syn::ExprAssign { + attrs, + left, + eq_token, + right, + } +} + +process_struct! { + syn::ExprAsync { + attrs, + async_token, + capture, + block, + } +} + +process_struct! { + syn::ExprAwait { + attrs, + base, + dot_token, + await_token, + } +} + +process_struct! { + syn::ExprBinary { + attrs, + left, + op, + right, + } +} + +process_struct! { + syn::ExprBlock { + attrs, + label, + block, + } +} + +process_struct! { + syn::ExprBreak { + attrs, + break_token, + label, + expr, + } +} + +process_struct! { + syn::ExprCall { + attrs, + func, + paren_token, + args, + } +} + +process_struct! { + syn::ExprCast { + attrs, + expr, + as_token, + ty, + } +} + +process_struct! { + syn::ExprClosure { + attrs, + lifetimes, + constness, + movability, + asyncness, + capture, + or1_token, + inputs, + or2_token, + output, + body, + } +} + +process_struct! { + syn::ExprConst { + attrs, + const_token, + block, + } +} + +process_struct! { + syn::ExprContinue { + attrs, + continue_token, + label, + } +} + +process_struct! { + syn::ExprField { + attrs, + base, + dot_token, + member, + } +} + +process_struct! { + syn::ExprForLoop { + attrs, + label, + for_token, + pat, + in_token, + expr, + body, + } +} + +process_struct! { + syn::ExprGroup { + attrs, + group_token, + expr, + } +} + +process_struct! { + syn::ExprIf { + attrs, + if_token, + cond, + then_branch, + else_branch, + } +} + +process_struct! { + syn::ExprIndex { + attrs, + expr, + bracket_token, + index, + } +} + +process_struct! { + syn::ExprInfer { + attrs, + underscore_token, + } +} + +process_struct! { + syn::ExprLet { + attrs, + let_token, + pat, + eq_token, + expr, + } +} + +process_struct! { + syn::ExprLit { + attrs, + lit, + } +} + +process_struct! { + syn::ExprLoop { + attrs, + label, + loop_token, + body, + } +} + +process_struct! { + syn::ExprMacro { + attrs, + mac, + } +} + +process_struct! { + syn::ExprMatch { + attrs, + match_token, + expr, + brace_token, + arms, + } +} + +process_struct! { + syn::ExprMethodCall { + attrs, + receiver, + dot_token, + method, + turbofish, + paren_token, + args, + } +} + +process_struct! { + syn::ExprParen { + attrs, + paren_token, + expr, + } +} + +process_struct! { + syn::ExprPath { + attrs, + #[qself] + qself, + path, + } +} + +process_struct! { + syn::ExprRange { + attrs, + start, + limits, + end, + } +} + +process_struct! { + syn::ExprRawAddr { + attrs, + and_token, + raw, + mutability, + expr, + } +} + +process_struct! { + syn::ExprReference { + attrs, + and_token, + mutability, + expr, + } +} + +process_struct! { + syn::ExprRepeat { + attrs, + bracket_token, + expr, + semi_token, + len, + } +} + +process_struct! { + syn::ExprReturn { + attrs, + return_token, + expr, + } +} + +process_struct! { + syn::ExprStruct { + attrs, + #[qself] + qself, + path, + brace_token, + fields, + dot2_token, + rest, + } +} + +process_struct! { + syn::ExprTry { + attrs, + expr, + question_token, + } +} + +process_struct! { + syn::ExprTryBlock { + attrs, + try_token, + block, + } +} + +process_struct! { + syn::ExprTuple { + attrs, + paren_token, + elems, + } +} + +process_struct! { + syn::ExprUnary { + attrs, + op, + expr, + } +} + +process_struct! { + syn::ExprUnsafe { + attrs, + unsafe_token, + block, + } +} + +process_struct! { + syn::ExprWhile { + attrs, + label, + while_token, + cond, + body, + } +} + +process_struct! { + syn::ExprYield { + attrs, + yield_token, + expr, + } +} + +process_struct! { + syn::Field { + attrs, + vis, + mutability, + ident, + colon_token, + ty, + } +} + +process_struct! { + syn::FieldPat { + attrs, + member, + colon_token, + pat, + } +} + +process_struct! { + syn::FieldValue { + attrs, + member, + colon_token, + expr, + } +} + +process_struct! { + syn::FieldsNamed { + brace_token, + named, + } +} + +process_struct! { + syn::FieldsUnnamed { + paren_token, + unnamed, + } +} + +process_struct! { + syn::ForeignItemFn { + attrs, + vis, + sig, + semi_token, + } +} + +process_struct! { + syn::ForeignItemMacro { + attrs, + mac, + semi_token, + } +} + +process_struct! { + syn::ForeignItemStatic { + attrs, + vis, + static_token, + mutability, + ident, + colon_token, + ty, + semi_token, + } +} + +process_struct! { + syn::ForeignItemType { + attrs, + vis, + type_token, + ident, + generics, + semi_token, + } +} + +process_struct! { + syn::Generics { + lt_token, + params, + gt_token, + where_clause, + } +} + +process_struct! { + syn::ImplItemConst { + attrs, + vis, + defaultness, + const_token, + ident, + generics, + colon_token, + ty, + eq_token, + expr, + semi_token, + } +} + +process_struct! { + syn::ImplItemFn { + attrs, + vis, + defaultness, + sig, + block, + } +} + +process_struct! { + syn::ImplItemMacro { + attrs, + mac, + semi_token, + } +} + +process_struct! { + syn::ImplItemType { + attrs, + vis, + defaultness, + type_token, + ident, + generics, + eq_token, + ty, + semi_token, + } +} + +process_struct! { + syn::ItemConst { + attrs, + vis, + const_token, + ident, + generics, + colon_token, + ty, + eq_token, + expr, + semi_token, + } +} + +process_struct! { + syn::ItemEnum { + attrs, + vis, + enum_token, + ident, + generics, + brace_token, + variants, + } +} + +process_struct! { + syn::ItemExternCrate { + attrs, + vis, + extern_token, + crate_token, + ident, + rename, + semi_token, + } +} + +process_struct! { + syn::ItemFn { + attrs, + vis, + sig, + block, + } +} + +process_struct! { + syn::ItemForeignMod { + attrs, + unsafety, + abi, + brace_token, + items, + } +} + +process_struct! { + syn::ItemImpl { + attrs, + defaultness, + unsafety, + impl_token, + generics, + trait_, + self_ty, + brace_token, + items, + } +} + +process_struct! { + syn::ItemMacro { + attrs, + ident, + mac, + semi_token, + } +} + +process_struct! { + syn::ItemMod { + attrs, + vis, + unsafety, + mod_token, + ident, + content, + semi, + } +} + +process_struct! { + syn::ItemStatic { + attrs, + vis, + static_token, + mutability, + ident, + colon_token, + ty, + eq_token, + expr, + semi_token, + } +} + +process_struct! { + syn::ItemStruct { + attrs, + vis, + struct_token, + ident, + generics, + fields, + semi_token, + } +} + +process_struct! { + syn::ItemTrait { + attrs, + vis, + unsafety, + auto_token, + restriction, + trait_token, + ident, + generics, + colon_token, + supertraits, + brace_token, + items, + } +} + +process_struct! { + syn::ItemTraitAlias { + attrs, + vis, + trait_token, + ident, + generics, + eq_token, + bounds, + semi_token, + } +} + +process_struct! { + syn::ItemType { + attrs, + vis, + type_token, + ident, + generics, + eq_token, + ty, + semi_token, + } +} + +process_struct! { + syn::ItemUnion { + attrs, + vis, + union_token, + ident, + generics, + fields, + } +} + +process_struct! { + syn::ItemUse { + attrs, + vis, + use_token, + leading_colon, + tree, + semi_token, + } +} + +process_struct! { + syn::Label { + name, + colon_token, + } +} + +process_struct! { + syn::LifetimeParam { + attrs, + lifetime, + colon_token, + bounds, + } +} + +process_struct! { + syn::Local { + attrs, + let_token, + pat, + init, + semi_token, + } +} + +process_struct! { + syn::LocalInit { + eq_token, + expr, + diverge, + } +} + +process_struct! { + syn::Macro { + path, + bang_token, + delimiter, + tokens, + } +} + +process_struct! { + syn::MetaList { + path, + delimiter, + tokens, + } +} + +process_struct! { + syn::MetaNameValue { + path, + eq_token, + value, + } +} + +process_struct! { + syn::ParenthesizedGenericArguments { + paren_token, + inputs, + output, + } +} + +process_struct! { + syn::PatIdent { + attrs, + by_ref, + mutability, + ident, + subpat, + } +} + +process_struct! { + syn::PatOr { + attrs, + leading_vert, + cases, + } +} + +process_struct! { + syn::PatParen { + attrs, + paren_token, + pat, + } +} + +process_struct! { + syn::PatReference { + attrs, + and_token, + mutability, + pat, + } +} + +process_struct! { + syn::PatRest { + attrs, + dot2_token, + } +} + +process_struct! { + syn::PatSlice { + attrs, + bracket_token, + elems, + } +} + +process_struct! { + syn::PatStruct { + attrs, + #[qself] + qself, + path, + brace_token, + fields, + rest, + } +} + +process_struct! { + syn::PatTuple { + attrs, + paren_token, + elems, + } +} + +process_struct! { + syn::PatTupleStruct { + attrs, + #[qself] + qself, + path, + paren_token, + elems, + } +} + +process_struct! { + syn::PatType { + attrs, + pat, + colon_token, + ty, + } +} + +process_struct! { + syn::PatWild { + attrs, + underscore_token, + } +} + +process_struct! { + syn::Path { + leading_colon, + segments, + } +} + +process_struct! { + syn::PathSegment { + ident, + arguments, + } +} + +process_struct! { + syn::PreciseCapture { + use_token, + lt_token, + params, + gt_token, + } +} + +process_struct! { + syn::PredicateLifetime { + lifetime, + colon_token, + bounds, + } +} + +process_struct! { + syn::PredicateType { + lifetimes, + bounded_ty, + colon_token, + bounds, + } +} + +process_struct! { + syn::Receiver { + attrs, + reference, + mutability, + self_token, + colon_token, + ty, + } +} + +process_struct! { + syn::Signature { + constness, + asyncness, + unsafety, + abi, + fn_token, + ident, + generics, + paren_token, + inputs, + variadic, + output, + } +} + +process_struct! { + syn::StmtMacro { + attrs, + mac, + semi_token, + } +} + +process_struct! { + syn::TraitBound { + paren_token, + modifier, + lifetimes, + path, + } +} + +process_struct! { + syn::TraitItemConst { + attrs, + const_token, + ident, + generics, + colon_token, + ty, + default, + semi_token, + } +} + +process_struct! { + syn::TraitItemFn { + attrs, + sig, + default, + semi_token, + } +} + +process_struct! { + syn::TraitItemMacro { + attrs, + mac, + semi_token, + } +} + +process_struct! { + syn::TraitItemType { + attrs, + type_token, + ident, + generics, + colon_token, + bounds, + default, + semi_token, + } +} + +process_struct! { + syn::TypeArray { + bracket_token, + elem, + semi_token, + len, + } +} + +process_struct! { + syn::TypeBareFn { + lifetimes, + unsafety, + abi, + fn_token, + paren_token, + inputs, + variadic, + output, + } +} + +process_struct! { + syn::TypeGroup { + group_token, + elem, + } +} + +process_struct! { + syn::TypeImplTrait { + impl_token, + bounds, + } +} + +process_struct! { + syn::TypeInfer { + underscore_token, + } +} + +process_struct! { + syn::TypeMacro { + mac, + } +} + +process_struct! { + syn::TypeNever { + bang_token, + } +} + +process_struct! { + syn::TypeParam { + attrs, + ident, + colon_token, + bounds, + eq_token, + default, + } +} + +process_struct! { + syn::TypeParen { + paren_token, + elem, + } +} + +process_struct! { + syn::TypePath { + #[qself] + qself, + path, + } +} + +process_struct! { + syn::TypePtr { + star_token, + const_token, + mutability, + elem, + } +} + +process_struct! { + syn::TypeReference { + and_token, + lifetime, + mutability, + elem, + } +} + +process_struct! { + syn::TypeSlice { + bracket_token, + elem, + } +} + +process_struct! { + syn::TypeTraitObject { + dyn_token, + bounds, + } +} + +process_struct! { + syn::TypeTuple { + paren_token, + elems, + } +} + +process_struct! { + syn::UseGlob { + star_token, + } +} + +process_struct! { + syn::UseGroup { + brace_token, + items, + } +} + +process_struct! { + syn::UseName { + ident, + } +} + +process_struct! { + syn::UsePath { + ident, + colon2_token, + tree, + } +} + +process_struct! { + syn::UseRename { + ident, + as_token, + rename, + } +} + +process_struct! { + syn::Variadic { + attrs, + pat, + dots, + comma, + } +} + +process_struct! { + syn::Variant { + attrs, + ident, + fields, + discriminant, + } +} + +process_struct! { + syn::VisRestricted { + pub_token, + paren_token, + in_token, + path, + } +} + +process_struct! { + syn::WhereClause { + where_token, + predicates, + } +} + +macro_rules! process_enum { + ($path:path { + $($variant:ident$(($($field:ident),* $(,)?))?,)* + }) => { + impl Process

for $path { + fn process(self, state: &mut State

) -> Option> { + match self { + $(Self::$variant$(($($field),*))? => Some(($($($field.process(state)?,)*)?).call(|($($($field,)*)?)| Self::$variant$(($($field),*))?)),)* + } + } + } + }; + ($path:path { + $($variant:ident$(($($field:ident),* $(,)?))?,)* + #[no_op] + _, + }) => { + impl Process

for $path { + fn process(self, state: &mut State

) -> Option> { + #![allow(unused_variables)] + match self { + $(Self::$variant$(($($field),*))? => Some(($($($field.process(state)?,)*)?).call(|($($($field,)*)?)| Self::$variant$(($($field),*))?)),)* + _ => Some(Output::new(self)), + } + } + } + }; +} + +process_enum! { + syn::AttrStyle { + Outer, + Inner(f0), + } +} + +process_enum! { + syn::BinOp { + Add(f0), + Sub(f0), + Mul(f0), + Div(f0), + Rem(f0), + And(f0), + Or(f0), + BitXor(f0), + BitAnd(f0), + BitOr(f0), + Shl(f0), + Shr(f0), + Eq(f0), + Lt(f0), + Le(f0), + Ne(f0), + Ge(f0), + Gt(f0), + AddAssign(f0), + SubAssign(f0), + MulAssign(f0), + DivAssign(f0), + RemAssign(f0), + BitXorAssign(f0), + BitAndAssign(f0), + BitOrAssign(f0), + ShlAssign(f0), + ShrAssign(f0), + #[no_op] + _, + } +} + +process_enum! { + syn::CapturedParam { + Lifetime(f0), + Ident(f0), + #[no_op] + _, + } +} + +process_enum! { + syn::Data { + Struct(f0), + Enum(f0), + Union(f0), + } +} + +process_enum! { + syn::Expr { + Array(f0), + Assign(f0), + Async(f0), + Await(f0), + Binary(f0), + Block(f0), + Break(f0), + Call(f0), + Cast(f0), + Closure(f0), + Const(f0), + Continue(f0), + Field(f0), + ForLoop(f0), + Group(f0), + If(f0), + Index(f0), + Infer(f0), + Let(f0), + Lit(f0), + Loop(f0), + Macro(f0), + Match(f0), + MethodCall(f0), + Paren(f0), + Path(f0), + Range(f0), + RawAddr(f0), + Reference(f0), + Repeat(f0), + Return(f0), + Struct(f0), + Try(f0), + TryBlock(f0), + Tuple(f0), + Unary(f0), + Unsafe(f0), + Verbatim(f0), + While(f0), + Yield(f0), + #[no_op] + _, + } +} + +process_enum! { + syn::FieldMutability { + None, + #[no_op] + _, + } +} + +process_enum! { + syn::Fields { + Named(f0), + Unnamed(f0), + Unit, + } +} + +process_enum! { + syn::FnArg { + Receiver(f0), + Typed(f0), + } +} + +process_enum! { + syn::ForeignItem { + Fn(f0), + Static(f0), + Type(f0), + Macro(f0), + Verbatim(f0), + #[no_op] + _, + } +} + +process_enum! { + syn::GenericArgument { + Lifetime(f0), + Type(f0), + Const(f0), + AssocType(f0), + AssocConst(f0), + Constraint(f0), + #[no_op] + _, + } +} + +process_enum! { + syn::GenericParam { + Lifetime(f0), + Type(f0), + Const(f0), + } +} + +process_enum! { + syn::ImplItem { + Const(f0), + Fn(f0), + Type(f0), + Macro(f0), + Verbatim(f0), + #[no_op] + _, + } +} + +process_enum! { + syn::ImplRestriction { + #[no_op] + _, + } +} + +process_enum! { + syn::Lit { + Str(f0), + ByteStr(f0), + CStr(f0), + Byte(f0), + Char(f0), + Int(f0), + Float(f0), + Bool(f0), + Verbatim(f0), + #[no_op] + _, + } +} + +process_enum! { + syn::MacroDelimiter { + Paren(f0), + Brace(f0), + Bracket(f0), + } +} + +process_enum! { + syn::Member { + Named(f0), + Unnamed(f0), + } +} + +process_enum! { + syn::Meta { + Path(f0), + List(f0), + NameValue(f0), + } +} + +process_enum! { + syn::Pat { + Const(f0), + Ident(f0), + Lit(f0), + Macro(f0), + Or(f0), + Paren(f0), + Path(f0), + Range(f0), + Reference(f0), + Rest(f0), + Slice(f0), + Struct(f0), + Tuple(f0), + TupleStruct(f0), + Type(f0), + Verbatim(f0), + Wild(f0), + #[no_op] + _, + } +} + +process_enum! { + syn::PathArguments { + None, + AngleBracketed(f0), + Parenthesized(f0), + } +} + +process_enum! { + syn::PointerMutability { + Const(f0), + Mut(f0), + } +} + +process_enum! { + syn::RangeLimits { + HalfOpen(f0), + Closed(f0), + } +} + +process_enum! { + syn::ReturnType { + Default, + Type(f0, f1), + } +} + +process_enum! { + syn::StaticMutability { + Mut(f0), + None, + #[no_op] + _, + } +} + +process_enum! { + syn::Stmt { + Local(f0), + Item(f0), + Expr(f0, f1), + Macro(f0), + } +} + +process_enum! { + syn::TraitBoundModifier { + None, + Maybe(f0), + } +} + +process_enum! { + syn::TraitItem { + Const(f0), + Fn(f0), + Type(f0), + Macro(f0), + Verbatim(f0), + #[no_op] + _, + } +} + +process_enum! { + syn::Type { + Array(f0), + BareFn(f0), + Group(f0), + ImplTrait(f0), + Infer(f0), + Macro(f0), + Never(f0), + Paren(f0), + Path(f0), + Ptr(f0), + Reference(f0), + Slice(f0), + TraitObject(f0), + Tuple(f0), + Verbatim(f0), + #[no_op] + _, + } +} + +process_enum! { + syn::TypeParamBound { + Trait(f0), + Lifetime(f0), + PreciseCapture(f0), + Verbatim(f0), + #[no_op] + _, + } +} + +process_enum! { + syn::UnOp { + Deref(f0), + Not(f0), + Neg(f0), + #[no_op] + _, + } +} + +process_enum! { + syn::UseTree { + Path(f0), + Name(f0), + Rename(f0), + Glob(f0), + Group(f0), + } +} + +process_enum! { + syn::Visibility { + Public(f0), + Restricted(f0), + Inherited, + } +} + +process_enum! { + syn::WherePredicate { + Lifetime(f0), + Type(f0), + #[no_op] + _, + } +} + +struct TopItem(syn::Item); + +impl Process

for TopItem { + fn process(self, state: &mut State

) -> Option> { + match self.0 { + syn::Item::Const(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Enum(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::ExternCrate(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Fn(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::ForeignMod(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Impl(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Macro(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Mod(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Static(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Struct(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Trait(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::TraitAlias(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Type(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Union(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Use(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + _ => Some(Output::new(self)), + } + } +} + +pub(crate) fn process_cfgs(item: syn::Item, cfgs: Cfgs) -> syn::Result> { + let mut state = State:: { + cfgs, + errors: Errors::new(), + _phantom: PhantomData, + }; + let retval = TopItem(item).process(&mut state).map(|v| v.0 .0); + state.errors.finish()?; + Ok(retval) +} + +pub(crate) fn collect_cfgs(item: syn::Item) -> syn::Result> { + let mut state = State:: { + cfgs: Cfgs::default(), + errors: Errors::new(), + _phantom: PhantomData, + }; + let (None | Some(Output(()))) = TopItem(item).process(&mut state); + state.errors.finish()?; + Ok(state.cfgs) +} diff --git a/crates/fayalite/Cargo.toml b/crates/fayalite/Cargo.toml index 5724a80..f176698 100644 --- a/crates/fayalite/Cargo.toml +++ b/crates/fayalite/Cargo.toml @@ -25,9 +25,11 @@ jobslot.workspace = true num-bigint.workspace = true num-traits.workspace = true os_pipe.workspace = true +petgraph.workspace = true serde_json.workspace = true serde.workspace = true tempfile.workspace = true +vec_map.workspace = true which.workspace = true [dev-dependencies] @@ -38,6 +40,7 @@ fayalite-visit-gen.workspace = true [features] unstable-doc = [] +unstable-test-hasher = [] [package.metadata.docs.rs] features = ["unstable-doc"] diff --git a/crates/fayalite/build.rs b/crates/fayalite/build.rs index 24d8f31..c6680d5 100644 --- a/crates/fayalite/build.rs +++ b/crates/fayalite/build.rs @@ -5,6 +5,9 @@ use std::{env, fs, path::Path}; fn main() { println!("cargo::rustc-check-cfg=cfg(todo)"); + println!("cargo::rustc-check-cfg=cfg(cfg_false_for_tests)"); + println!("cargo::rustc-check-cfg=cfg(cfg_true_for_tests)"); + println!("cargo::rustc-cfg=cfg_true_for_tests"); let path = "visit_types.json"; println!("cargo::rerun-if-changed={path}"); println!("cargo::rerun-if-changed=build.rs"); diff --git a/crates/fayalite/src/_docs/modules/module_bodies/hdl_let_statements.rs b/crates/fayalite/src/_docs/modules/module_bodies/hdl_let_statements.rs index 61d29b5..229871b 100644 --- a/crates/fayalite/src/_docs/modules/module_bodies/hdl_let_statements.rs +++ b/crates/fayalite/src/_docs/modules/module_bodies/hdl_let_statements.rs @@ -2,6 +2,7 @@ // See Notices.txt for copyright information //! ## `#[hdl] let` statements +pub mod destructuring; pub mod inputs_outputs; pub mod instances; pub mod memories; diff --git a/crates/fayalite/src/_docs/modules/module_bodies/hdl_let_statements/destructuring.rs b/crates/fayalite/src/_docs/modules/module_bodies/hdl_let_statements/destructuring.rs new file mode 100644 index 0000000..1fc4705 --- /dev/null +++ b/crates/fayalite/src/_docs/modules/module_bodies/hdl_let_statements/destructuring.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +//! ### Destructuring Let +//! +//! You can use `#[hdl] let` to destructure types, similarly to Rust `let` statements with non-trivial patterns. +//! +//! `#[hdl] let` statements can only match one level of struct/tuple pattern for now, +//! e.g. you can match with the pattern `MyStruct { a, b }`, but not `MyStruct { a, b: Struct2 { v } }`. +//! +//! ``` +//! # use fayalite::prelude::*; +//! #[hdl] +//! struct MyStruct { +//! a: UInt<8>, +//! b: Bool, +//! } +//! +//! #[hdl_module] +//! fn my_module() { +//! #[hdl] +//! let my_input: MyStruct = m.input(); +//! #[hdl] +//! let my_output: UInt<8> = m.input(); +//! #[hdl] +//! let MyStruct { a, b } = my_input; +//! #[hdl] +//! if b { +//! connect(my_output, a); +//! } else { +//! connect(my_output, 0_hdl_u8); +//! } +//! } +//! ``` diff --git a/crates/fayalite/src/_docs/modules/module_bodies/hdl_match_statements.rs b/crates/fayalite/src/_docs/modules/module_bodies/hdl_match_statements.rs index 9e6c511..6df70f1 100644 --- a/crates/fayalite/src/_docs/modules/module_bodies/hdl_match_statements.rs +++ b/crates/fayalite/src/_docs/modules/module_bodies/hdl_match_statements.rs @@ -7,5 +7,5 @@ //! //! `#[hdl] match` statements' bodies must evaluate to type `()` for now. //! -//! `#[hdl] match` statements can only match one level of struct/enum pattern for now, +//! `#[hdl] match` statements can only match one level of struct/tuple/enum pattern for now, //! e.g. you can match with the pattern `HdlSome(v)`, but not `HdlSome(HdlSome(_))`. diff --git a/crates/fayalite/src/annotations.rs b/crates/fayalite/src/annotations.rs index 8eff4a0..1c517ae 100644 --- a/crates/fayalite/src/annotations.rs +++ b/crates/fayalite/src/annotations.rs @@ -12,7 +12,7 @@ use std::{ ops::Deref, }; -#[derive(Clone)] +#[derive(Clone, Debug)] struct CustomFirrtlAnnotationFieldsImpl { value: serde_json::Map, serialized: Interned, diff --git a/crates/fayalite/src/array.rs b/crates/fayalite/src/array.rs index f617f91..6d9b043 100644 --- a/crates/fayalite/src/array.rs +++ b/crates/fayalite/src/array.rs @@ -2,17 +2,24 @@ // See Notices.txt for copyright information use crate::{ - expr::{ops::ArrayIndex, Expr, ToExpr}, - int::{DynSize, KnownSize, Size, SizeType, DYN_SIZE}, + expr::{ + ops::{ArrayLiteral, ExprFromIterator, ExprIntoIterator, ExprPartialEq}, + CastToBits, Expr, HdlPartialEq, ReduceBits, ToExpr, + }, + int::{Bool, DynSize, KnownSize, Size, SizeType, DYN_SIZE}, intern::{Intern, Interned, LazyInterned}, module::transform::visit::{Fold, Folder, Visit, Visitor}, + sim::value::{SimValue, SimValuePartialEq}, source_location::SourceLocation, ty::{ - CanonicalType, MatchVariantWithoutScope, StaticType, Type, TypeProperties, TypeWithDeref, + serde_impls::SerdeCanonicalType, CanonicalType, MatchVariantWithoutScope, StaticType, Type, + TypeProperties, TypeWithDeref, }, util::ConstUsize, }; -use std::ops::Index; +use bitvec::slice::BitSlice; +use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; +use std::{iter::FusedIterator, ops::Index}; #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct ArrayType { @@ -91,6 +98,12 @@ impl> ArrayType { } } +impl Default for ArrayType { + fn default() -> Self { + Self::TYPE + } +} + impl StaticType for ArrayType { const TYPE: Self = Self { element: LazyInterned::new_lazy(&|| T::TYPE.intern_sized()), @@ -139,6 +152,7 @@ impl, Len: Size, State: Visitor + ?Sized> Visit impl Type for ArrayType { type BaseType = Array; type MaskType = ArrayType; + type SimValue = Len::ArraySimValue; type MatchVariant = Len::ArrayMatch; type MatchActiveScope = (); type MatchVariantAndInactiveScope = MatchVariantWithoutScope>; @@ -148,10 +162,8 @@ impl Type for ArrayType { this: Expr, source_location: SourceLocation, ) -> Self::MatchVariantsIter { - let base = Expr::as_dyn_array(this); - let base_ty = Expr::ty(base); let _ = source_location; - let retval = Vec::from_iter((0..base_ty.len()).map(|i| ArrayIndex::new(base, i).to_expr())); + let retval = Vec::from_iter(this); std::iter::once(MatchVariantWithoutScope( Len::ArrayMatch::::try_from(retval) .ok() @@ -177,16 +189,97 @@ impl Type for ArrayType { Len::from_usize(array.len()), ) } + fn source_location() -> SourceLocation { SourceLocation::builtin() } + + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + assert_eq!(bits.len(), self.type_properties.bit_width); + let element = self.element(); + let element_bit_width = element.canonical().bit_width(); + TryFrom::try_from(Vec::from_iter((0..self.len()).map(|i| { + SimValue::from_bitslice(element, &bits[i * element_bit_width..][..element_bit_width]) + }))) + .ok() + .expect("used correct length") + } + + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + assert_eq!(bits.len(), self.type_properties.bit_width); + let element_ty = self.element(); + let element_bit_width = element_ty.canonical().bit_width(); + let value: &mut [SimValue] = value.as_mut(); + assert_eq!(self.len(), value.len()); + for (i, element_value) in value.iter_mut().enumerate() { + assert_eq!(SimValue::ty(element_value), element_ty); + SimValue::bits_mut(element_value) + .bits_mut() + .copy_from_bitslice(&bits[i * element_bit_width..][..element_bit_width]); + } + } + + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + assert_eq!(bits.len(), self.type_properties.bit_width); + let element_ty = self.element(); + let element_bit_width = element_ty.canonical().bit_width(); + let value: &[SimValue] = value.as_ref(); + assert_eq!(self.len(), value.len()); + for (i, element_value) in value.iter().enumerate() { + assert_eq!(SimValue::ty(element_value), element_ty); + bits[i * element_bit_width..][..element_bit_width] + .copy_from_bitslice(SimValue::bits(element_value).bits()); + } + } +} + +impl Serialize for ArrayType { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + SerdeCanonicalType::::Array { + element: self.element(), + len: self.len(), + } + .serialize(serializer) + } +} + +impl<'de, T: Type + Deserialize<'de>, Len: Size> Deserialize<'de> for ArrayType { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let name = |len| -> String { + if let Some(len) = len { + format!("an Array<_, {len}>") + } else { + "an Array<_>".to_string() + } + }; + match SerdeCanonicalType::::deserialize(deserializer)? { + SerdeCanonicalType::Array { element, len } => { + if let Some(len) = Len::try_from_usize(len) { + Ok(Self::new(element, len)) + } else { + Err(Error::invalid_value( + serde::de::Unexpected::Other(&name(Some(len))), + &&*name(Len::KNOWN_VALUE), + )) + } + } + ty => Err(Error::invalid_value( + serde::de::Unexpected::Other(ty.as_serde_unexpected_str()), + &&*name(Len::KNOWN_VALUE), + )), + } + } } impl TypeWithDeref for ArrayType { fn expr_deref(this: &Expr) -> &Self::MatchVariant { - let base = Expr::as_dyn_array(*this); - let base_ty = Expr::ty(base); - let retval = Vec::from_iter((0..base_ty.len()).map(|i| ArrayIndex::new(base, i).to_expr())); + let retval = Vec::from_iter(*this); Interned::into_inner(Intern::intern_sized( Len::ArrayMatch::::try_from(retval) .ok() @@ -218,3 +311,143 @@ impl Index for ArrayWithoutLen { Interned::into_inner(Intern::intern_sized(ArrayType::new(self.element, len))) } } + +impl ExprPartialEq> for ArrayType +where + Lhs: ExprPartialEq, +{ + fn cmp_eq(lhs: Expr, rhs: Expr>) -> Expr { + let lhs_ty = Expr::ty(lhs); + let rhs_ty = Expr::ty(rhs); + assert_eq!(lhs_ty.len(), rhs_ty.len()); + lhs.into_iter() + .zip(rhs) + .map(|(l, r)| l.cmp_eq(r)) + .collect::>>() + .cast_to_bits() + .all_one_bits() + } + + fn cmp_ne(lhs: Expr, rhs: Expr>) -> Expr { + let lhs_ty = Expr::ty(lhs); + let rhs_ty = Expr::ty(rhs); + assert_eq!(lhs_ty.len(), rhs_ty.len()); + lhs.into_iter() + .zip(rhs) + .map(|(l, r)| l.cmp_ne(r)) + .collect::>>() + .cast_to_bits() + .any_one_bits() + } +} + +impl SimValuePartialEq> for ArrayType +where + Lhs: SimValuePartialEq, +{ + fn sim_value_eq(this: &SimValue, other: &SimValue>) -> bool { + AsRef::<[_]>::as_ref(&**this) + .iter() + .zip(AsRef::<[_]>::as_ref(&**other)) + .all(|(l, r)| SimValuePartialEq::sim_value_eq(l, r)) + } +} + +impl ExprIntoIterator for ArrayType { + type Item = T; + type ExprIntoIter = ExprArrayIter; + + fn expr_into_iter(e: Expr) -> Self::ExprIntoIter { + ExprArrayIter { + base: e, + indexes: 0..Expr::ty(e).len(), + } + } +} + +#[derive(Clone, Debug)] +pub struct ExprArrayIter { + base: Expr>, + indexes: std::ops::Range, +} + +impl ExprArrayIter { + pub fn base(&self) -> Expr> { + self.base + } + pub fn indexes(&self) -> std::ops::Range { + self.indexes.clone() + } +} + +impl Iterator for ExprArrayIter { + type Item = Expr; + + fn next(&mut self) -> Option { + self.indexes.next().map(|i| self.base[i]) + } + + fn size_hint(&self) -> (usize, Option) { + self.indexes.size_hint() + } + + fn count(self) -> usize { + self.indexes.count() + } + + fn last(mut self) -> Option { + self.next_back() + } + + fn nth(&mut self, n: usize) -> Option { + self.indexes.nth(n).map(|i| self.base[i]) + } + + fn fold(self, init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.indexes.fold(init, |b, i| f(b, self.base[i])) + } +} + +impl DoubleEndedIterator for ExprArrayIter { + fn next_back(&mut self) -> Option { + self.indexes.next_back().map(|i| self.base[i]) + } + + fn nth_back(&mut self, n: usize) -> Option { + self.indexes.nth_back(n).map(|i| self.base[i]) + } + + fn rfold(self, init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.indexes.rfold(init, |b, i| f(b, self.base[i])) + } +} + +impl ExactSizeIterator for ExprArrayIter { + fn len(&self) -> usize { + self.indexes.len() + } +} + +impl FusedIterator for ExprArrayIter {} + +impl ExprFromIterator> for Array { + fn expr_from_iter>>(iter: T) -> Expr { + ArrayLiteral::new( + A::TYPE, + iter.into_iter().map(|v| Expr::canonical(v)).collect(), + ) + .to_expr() + } +} + +impl<'a, A: StaticType> ExprFromIterator<&'a Expr> for Array { + fn expr_from_iter>>(iter: T) -> Expr { + iter.into_iter().copied().collect() + } +} diff --git a/crates/fayalite/src/bundle.rs b/crates/fayalite/src/bundle.rs index 843eb6c..240c0c6 100644 --- a/crates/fayalite/src/bundle.rs +++ b/crates/fayalite/src/bundle.rs @@ -2,18 +2,25 @@ // See Notices.txt for copyright information use crate::{ - expr::{ops::BundleLiteral, Expr, ToExpr}, + expr::{ + ops::{ArrayLiteral, BundleLiteral, ExprPartialEq}, + CastToBits, Expr, ReduceBits, ToExpr, + }, + int::{Bool, DynSize}, intern::{Intern, Interned}, + sim::value::{SimValue, SimValuePartialEq, ToSimValue, ToSimValueWithType}, source_location::SourceLocation, ty::{ - impl_match_variant_as_self, CanonicalType, MatchVariantWithoutScope, StaticType, Type, - TypeProperties, TypeWithDeref, + impl_match_variant_as_self, CanonicalType, MatchVariantWithoutScope, OpaqueSimValue, + StaticType, Type, TypeProperties, TypeWithDeref, }, + util::HashMap, }; -use hashbrown::HashMap; +use bitvec::{slice::BitSlice, vec::BitVec}; +use serde::{Deserialize, Serialize}; use std::{fmt, marker::PhantomData}; -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub struct BundleField { pub name: Interned, pub flipped: bool, @@ -153,7 +160,7 @@ impl Default for BundleTypePropertiesBuilder { impl Bundle { #[track_caller] pub fn new(fields: Interned<[BundleField]>) -> Self { - let mut name_indexes = HashMap::with_capacity(fields.len()); + let mut name_indexes = HashMap::with_capacity_and_hasher(fields.len(), Default::default()); let mut field_offsets = Vec::with_capacity(fields.len()); let mut type_props_builder = BundleTypePropertiesBuilder::new(); for (index, &BundleField { name, flipped, ty }) in fields.iter().enumerate() { @@ -210,6 +217,7 @@ impl Bundle { impl Type for Bundle { type BaseType = Bundle; type MaskType = Bundle; + type SimValue = OpaqueSimValue; impl_match_variant_as_self!(); fn mask_type(&self) -> Self::MaskType { Self::new(Interned::from_iter(self.0.fields.into_iter().map( @@ -233,6 +241,20 @@ impl Type for Bundle { fn source_location() -> SourceLocation { SourceLocation::builtin() } + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + assert_eq!(bits.len(), self.type_properties().bit_width); + OpaqueSimValue::from_bitslice(bits) + } + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + assert_eq!(bits.len(), self.type_properties().bit_width); + assert_eq!(value.bit_width(), self.type_properties().bit_width); + value.bits_mut().bits_mut().copy_from_bitslice(bits); + } + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + assert_eq!(bits.len(), self.type_properties().bit_width); + assert_eq!(value.bit_width(), self.type_properties().bit_width); + bits.copy_from_bitslice(value.bits().bits()); + } } pub trait BundleType: Type { @@ -241,6 +263,93 @@ pub trait BundleType: Type { fn fields(&self) -> Interned<[BundleField]>; } +pub struct BundleSimValueFromBits<'a> { + fields: std::slice::Iter<'static, BundleField>, + bits: &'a BitSlice, +} + +impl<'a> BundleSimValueFromBits<'a> { + #[track_caller] + pub fn new(bundle_ty: T, bits: &'a BitSlice) -> Self { + let fields = bundle_ty.fields(); + assert_eq!( + bits.len(), + fields + .iter() + .map(|BundleField { ty, .. }| ty.bit_width()) + .sum::() + ); + Self { + fields: Interned::into_inner(fields).iter(), + bits, + } + } + #[track_caller] + fn field_ty_and_bits(&mut self) -> (T, &'a BitSlice) { + let Some(&BundleField { + name: _, + flipped: _, + ty, + }) = self.fields.next() + else { + panic!("tried to read too many fields from BundleSimValueFromBits"); + }; + let (field_bits, rest) = self.bits.split_at(ty.bit_width()); + self.bits = rest; + (T::from_canonical(ty), field_bits) + } + #[track_caller] + pub fn field_from_bits(&mut self) -> SimValue { + let (field_ty, field_bits) = self.field_ty_and_bits::(); + SimValue::from_bitslice(field_ty, field_bits) + } + #[track_caller] + pub fn field_clone_from_bits(&mut self, field_value: &mut SimValue) { + let (field_ty, field_bits) = self.field_ty_and_bits::(); + assert_eq!(field_ty, SimValue::ty(field_value)); + SimValue::bits_mut(field_value) + .bits_mut() + .copy_from_bitslice(field_bits); + } +} + +pub struct BundleSimValueToBits<'a> { + fields: std::slice::Iter<'static, BundleField>, + bits: &'a mut BitSlice, +} + +impl<'a> BundleSimValueToBits<'a> { + #[track_caller] + pub fn new(bundle_ty: T, bits: &'a mut BitSlice) -> Self { + let fields = bundle_ty.fields(); + assert_eq!( + bits.len(), + fields + .iter() + .map(|BundleField { ty, .. }| ty.bit_width()) + .sum::() + ); + Self { + fields: Interned::into_inner(fields).iter(), + bits, + } + } + #[track_caller] + pub fn field_to_bits(&mut self, field_value: &SimValue) { + let Some(&BundleField { + name: _, + flipped: _, + ty, + }) = self.fields.next() + else { + panic!("tried to read too many fields from BundleSimValueFromBits"); + }; + assert_eq!(T::from_canonical(ty), SimValue::ty(field_value)); + self.bits[..ty.bit_width()].copy_from_bitslice(SimValue::bits(field_value).bits()); + self.bits = &mut std::mem::take(&mut self.bits)[ty.bit_width()..]; + } +} + #[derive(Default)] pub struct NoBuilder; @@ -323,7 +432,19 @@ macro_rules! impl_tuple_builder_fields { } macro_rules! impl_tuples { - ([$({#[num = $num:literal, field = $field:ident] $var:ident: $T:ident})*] []) => { + ( + [$({ + #[ + num = $num:tt, + field = $field:ident, + ty = $ty_var:ident: $Ty:ident, + lhs = $lhs_var:ident: $Lhs:ident, + rhs = $rhs_var:ident: $Rhs:ident + ] + $var:ident: $T:ident + })*] + [] + ) => { impl_tuple_builder_fields! { {} [$({ @@ -335,6 +456,7 @@ macro_rules! impl_tuples { impl<$($T: Type,)*> Type for ($($T,)*) { type BaseType = Bundle; type MaskType = ($($T::MaskType,)*); + type SimValue = ($(SimValue<$T>,)*); type MatchVariant = ($(Expr<$T>,)*); type MatchActiveScope = (); type MatchVariantAndInactiveScope = MatchVariantWithoutScope; @@ -373,6 +495,24 @@ macro_rules! impl_tuples { fn source_location() -> SourceLocation { SourceLocation::builtin() } + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + #![allow(unused_mut, unused_variables)] + let mut v = BundleSimValueFromBits::new(*self, bits); + $(let $var = v.field_from_bits();)* + ($($var,)*) + } + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + #![allow(unused_mut, unused_variables)] + let mut v = BundleSimValueFromBits::new(*self, bits); + let ($($var,)*) = value; + $(v.field_clone_from_bits($var);)* + } + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + #![allow(unused_mut, unused_variables)] + let mut v = BundleSimValueToBits::new(*self, bits); + let ($($var,)*) = value; + $(v.field_to_bits($var);)* + } } impl<$($T: Type,)*> BundleType for ($($T,)*) { type Builder = TupleBuilder<($(Unfilled<$T>,)*)>; @@ -423,6 +563,106 @@ macro_rules! impl_tuples { BundleLiteral::new(ty, field_values[..].intern()).to_expr() } } + impl<$($T: ToSimValueWithType,)*> ToSimValueWithType for ($($T,)*) { + #[track_caller] + fn to_sim_value_with_type(&self, ty: CanonicalType) -> SimValue { + SimValue::into_canonical(ToSimValueWithType::::to_sim_value_with_type(self, Bundle::from_canonical(ty))) + } + #[track_caller] + fn into_sim_value_with_type(self, ty: CanonicalType) -> SimValue + { + SimValue::into_canonical(ToSimValueWithType::::into_sim_value_with_type(self, Bundle::from_canonical(ty))) + } + } + impl<$($T: ToSimValueWithType,)*> ToSimValueWithType for ($($T,)*) { + #[track_caller] + fn to_sim_value_with_type(&self, ty: Bundle) -> SimValue { + let ($($var,)*) = self; + let [$($ty_var,)*] = *ty.fields() else { + panic!("bundle has wrong number of fields"); + }; + $(let $var = $var.to_sim_value_with_type($ty_var.ty);)* + ToSimValueWithType::into_sim_value_with_type(($($var,)*), ty) + } + #[track_caller] + fn into_sim_value_with_type(self, ty: Bundle) -> SimValue { + #![allow(unused_mut)] + #![allow(clippy::unused_unit)] + let ($($var,)*) = self; + let [$($ty_var,)*] = *ty.fields() else { + panic!("bundle has wrong number of fields"); + }; + let mut bits = BitVec::new(); + $(let $var = $var.into_sim_value_with_type($ty_var.ty); + assert_eq!(SimValue::ty(&$var), $ty_var.ty); + bits.extend_from_bitslice(SimValue::bits(&$var).bits()); + )* + bits.into_sim_value_with_type(ty) + } + } + impl<$($T: ToSimValueWithType<$Ty>, $Ty: Type,)*> ToSimValueWithType<($($Ty,)*)> for ($($T,)*) { + #[track_caller] + fn to_sim_value_with_type(&self, ty: ($($Ty,)*)) -> SimValue<($($Ty,)*)> { + let ($($var,)*) = self; + let ($($ty_var,)*) = ty; + $(let $var = $var.to_sim_value_with_type($ty_var);)* + SimValue::from_value(ty, ($($var,)*)) + } + #[track_caller] + fn into_sim_value_with_type(self, ty: ($($Ty,)*)) -> SimValue<($($Ty,)*)> { + let ($($var,)*) = self; + let ($($ty_var,)*) = ty; + $(let $var = $var.into_sim_value_with_type($ty_var);)* + SimValue::from_value(ty, ($($var,)*)) + } + } + impl<$($T: ToSimValue,)*> ToSimValue for ($($T,)*) { + type Type = ($($T::Type,)*); + #[track_caller] + fn to_sim_value(&self) -> SimValue { + let ($($var,)*) = self; + $(let $var = $var.to_sim_value();)* + SimValue::from_value(($(SimValue::ty(&$var),)*), ($($var,)*)) + } + #[track_caller] + fn into_sim_value(self) -> SimValue { + let ($($var,)*) = self; + $(let $var = $var.to_sim_value();)* + SimValue::from_value(($(SimValue::ty(&$var),)*), ($($var,)*)) + } + } + impl<$($Lhs: Type + ExprPartialEq<$Rhs>, $Rhs: Type,)*> ExprPartialEq<($($Rhs,)*)> for ($($Lhs,)*) { + fn cmp_eq(lhs: Expr, rhs: Expr<($($Rhs,)*)>) -> Expr { + let ($($lhs_var,)*) = *lhs; + let ($($rhs_var,)*) = *rhs; + ArrayLiteral::::new( + Bool, + FromIterator::from_iter([$(Expr::canonical(ExprPartialEq::cmp_eq($lhs_var, $rhs_var)),)*]), + ) + .cast_to_bits() + .all_one_bits() + } + + fn cmp_ne(lhs: Expr, rhs: Expr<($($Rhs,)*)>) -> Expr { + let ($($lhs_var,)*) = *lhs; + let ($($rhs_var,)*) = *rhs; + ArrayLiteral::::new( + Bool, + FromIterator::from_iter([$(Expr::canonical(ExprPartialEq::cmp_ne($lhs_var, $rhs_var)),)*]), + ) + .cast_to_bits() + .any_one_bits() + } + } + impl<$($Lhs: SimValuePartialEq<$Rhs>, $Rhs: Type,)*> SimValuePartialEq<($($Rhs,)*)> for ($($Lhs,)*) { + fn sim_value_eq(lhs: &SimValue, rhs: &SimValue<($($Rhs,)*)>) -> bool { + let ($($lhs_var,)*) = &**lhs; + let ($($rhs_var,)*) = &**rhs; + let retval = true; + $(let retval = retval && $lhs_var == $rhs_var;)* + retval + } + } }; ([$($lhs:tt)*] [$rhs_first:tt $($rhs:tt)*]) => { impl_tuples!([$($lhs)*] []); @@ -432,24 +672,25 @@ macro_rules! impl_tuples { impl_tuples! { [] [ - {#[num = 0, field = field_0] v0: T0} - {#[num = 1, field = field_1] v1: T1} - {#[num = 2, field = field_2] v2: T2} - {#[num = 3, field = field_3] v3: T3} - {#[num = 4, field = field_4] v4: T4} - {#[num = 5, field = field_5] v5: T5} - {#[num = 6, field = field_6] v6: T6} - {#[num = 7, field = field_7] v7: T7} - {#[num = 8, field = field_8] v8: T8} - {#[num = 9, field = field_9] v9: T9} - {#[num = 10, field = field_10] v10: T10} - {#[num = 11, field = field_11] v11: T11} + {#[num = 0, field = field_0, ty = ty0: Ty0, lhs = lhs0: Lhs0, rhs = rhs0: Rhs0] v0: T0} + {#[num = 1, field = field_1, ty = ty1: Ty1, lhs = lhs1: Lhs1, rhs = rhs1: Rhs1] v1: T1} + {#[num = 2, field = field_2, ty = ty2: Ty2, lhs = lhs2: Lhs2, rhs = rhs2: Rhs2] v2: T2} + {#[num = 3, field = field_3, ty = ty3: Ty3, lhs = lhs3: Lhs3, rhs = rhs3: Rhs3] v3: T3} + {#[num = 4, field = field_4, ty = ty4: Ty4, lhs = lhs4: Lhs4, rhs = rhs4: Rhs4] v4: T4} + {#[num = 5, field = field_5, ty = ty5: Ty5, lhs = lhs5: Lhs5, rhs = rhs5: Rhs5] v5: T5} + {#[num = 6, field = field_6, ty = ty6: Ty6, lhs = lhs6: Lhs6, rhs = rhs6: Rhs6] v6: T6} + {#[num = 7, field = field_7, ty = ty7: Ty7, lhs = lhs7: Lhs7, rhs = rhs7: Rhs7] v7: T7} + {#[num = 8, field = field_8, ty = ty8: Ty8, lhs = lhs8: Lhs8, rhs = rhs8: Rhs8] v8: T8} + {#[num = 9, field = field_9, ty = ty9: Ty9, lhs = lhs9: Lhs9, rhs = rhs9: Rhs9] v9: T9} + {#[num = 10, field = field_10, ty = ty10: Ty10, lhs = lhs10: Lhs10, rhs = rhs10: Rhs10] v10: T10} + {#[num = 11, field = field_11, ty = ty11: Ty11, lhs = lhs11: Lhs11, rhs = rhs11: Rhs11] v11: T11} ] } impl Type for PhantomData { type BaseType = Bundle; type MaskType = (); + type SimValue = PhantomData; type MatchVariant = PhantomData; type MatchActiveScope = (); type MatchVariantAndInactiveScope = MatchVariantWithoutScope; @@ -482,6 +723,16 @@ impl Type for PhantomData { fn source_location() -> SourceLocation { SourceLocation::builtin() } + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + assert!(bits.is_empty()); + *self + } + fn sim_value_clone_from_bits(&self, _value: &mut Self::SimValue, bits: &BitSlice) { + assert!(bits.is_empty()); + } + fn sim_value_to_bits(&self, _value: &Self::SimValue, bits: &mut BitSlice) { + assert!(bits.is_empty()); + } } pub struct PhantomDataBuilder(PhantomData); @@ -528,3 +779,39 @@ impl ToExpr for PhantomData { BundleLiteral::new(PhantomData, Interned::default()).to_expr() } } + +impl ToSimValue for PhantomData { + type Type = PhantomData; + + #[track_caller] + fn to_sim_value(&self) -> SimValue { + SimValue::from_value(*self, *self) + } +} + +impl ToSimValueWithType for PhantomData { + #[track_caller] + fn to_sim_value_with_type(&self, ty: Self) -> SimValue { + SimValue::from_value(ty, *self) + } +} + +impl ToSimValueWithType for PhantomData { + #[track_caller] + fn to_sim_value_with_type(&self, ty: Bundle) -> SimValue { + assert!(ty.fields().is_empty()); + ToSimValueWithType::into_sim_value_with_type(BitVec::new(), ty) + } +} + +impl ToSimValueWithType for PhantomData { + #[track_caller] + fn to_sim_value_with_type(&self, ty: CanonicalType) -> SimValue { + let ty = Bundle::from_canonical(ty); + assert!(ty.fields().is_empty()); + SimValue::into_canonical(ToSimValueWithType::into_sim_value_with_type( + BitVec::new(), + ty, + )) + } +} diff --git a/crates/fayalite/src/cli.rs b/crates/fayalite/src/cli.rs index 1030474..1f208a8 100644 --- a/crates/fayalite/src/cli.rs +++ b/crates/fayalite/src/cli.rs @@ -258,7 +258,7 @@ pub struct VerilogArgs { default_value = "firtool", env = "FIRTOOL", value_hint = ValueHint::CommandName, - value_parser = OsStringValueParser::new().try_map(which::which) + value_parser = OsStringValueParser::new().try_map(which) )] pub firtool: PathBuf, #[arg(long)] @@ -428,6 +428,13 @@ impl clap::Args for FormalAdjustArgs { } } +fn which(v: std::ffi::OsString) -> which::Result { + #[cfg(not(miri))] + return which::which(v); + #[cfg(miri)] + return Ok(Path::new("/").join(v)); +} + #[derive(Parser, Clone)] #[non_exhaustive] pub struct FormalArgs { @@ -438,7 +445,7 @@ pub struct FormalArgs { default_value = "sby", env = "SBY", value_hint = ValueHint::CommandName, - value_parser = OsStringValueParser::new().try_map(which::which) + value_parser = OsStringValueParser::new().try_map(which) )] pub sby: PathBuf, #[arg(long)] @@ -653,21 +660,19 @@ impl FormalArgs { self.sby.display() ))) }; - if do_cache { - fs::write( - cache_file, - serde_json::to_string_pretty(&FormalCache { - version: FormalCacheVersion::CURRENT, - contents_hash: contents_hash.unwrap(), - stdout_stderr: captured_output, - result: match &result { - Ok(FormalOutput { verilog: _ }) => Ok(FormalCacheOutput {}), - Err(error) => Err(error.to_string()), - }, - }) - .expect("serialization shouldn't ever fail"), - )?; - } + fs::write( + cache_file, + serde_json::to_string_pretty(&FormalCache { + version: FormalCacheVersion::CURRENT, + contents_hash: contents_hash.unwrap(), + stdout_stderr: captured_output, + result: match &result { + Ok(FormalOutput { verilog: _ }) => Ok(FormalCacheOutput {}), + Err(error) => Err(error.to_string()), + }, + }) + .expect("serialization shouldn't ever fail"), + )?; result } } diff --git a/crates/fayalite/src/clock.rs b/crates/fayalite/src/clock.rs index fe99653..f0623d4 100644 --- a/crates/fayalite/src/clock.rs +++ b/crates/fayalite/src/clock.rs @@ -4,10 +4,11 @@ use crate::{ expr::{Expr, ToExpr}, hdl, int::Bool, - reset::Reset, + reset::{Reset, ResetType}, source_location::SourceLocation, ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, }; +use bitvec::slice::BitSlice; #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct Clock; @@ -15,6 +16,7 @@ pub struct Clock; impl Type for Clock { type BaseType = Clock; type MaskType = Bool; + type SimValue = bool; impl_match_variant_as_self!(); @@ -36,6 +38,21 @@ impl Type for Clock { }; retval } + + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + assert_eq!(bits.len(), 1); + bits[0] + } + + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + assert_eq!(bits.len(), 1); + *value = bits[0]; + } + + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + assert_eq!(bits.len(), 1); + bits.set(0, *value); + } } impl Clock { @@ -88,9 +105,9 @@ impl ToClock for Expr { } #[hdl] -pub struct ClockDomain { +pub struct ClockDomain { pub clk: Clock, - pub rst: Reset, + pub rst: R, } impl ToClock for bool { diff --git a/crates/fayalite/src/enum_.rs b/crates/fayalite/src/enum_.rs index 2ed0b8e..36b5aa7 100644 --- a/crates/fayalite/src/enum_.rs +++ b/crates/fayalite/src/enum_.rs @@ -2,21 +2,30 @@ // See Notices.txt for copyright information use crate::{ - expr::{ops::VariantAccess, Expr, ToExpr}, + expr::{ + ops::{ExprPartialEq, VariantAccess}, + Expr, ToExpr, + }, hdl, - int::Bool, + int::{Bool, UIntValue}, intern::{Intern, Interned}, module::{ connect, enum_match_variants_helper, incomplete_wire, wire, EnumMatchVariantAndInactiveScopeImpl, EnumMatchVariantsIterImpl, Scope, }, + sim::value::{SimValue, SimValuePartialEq}, source_location::SourceLocation, - ty::{CanonicalType, MatchVariantAndInactiveScope, StaticType, Type, TypeProperties}, + ty::{ + CanonicalType, MatchVariantAndInactiveScope, OpaqueSimValue, StaticType, Type, + TypeProperties, + }, + util::HashMap, }; -use hashbrown::HashMap; -use std::{convert::Infallible, fmt, iter::FusedIterator}; +use bitvec::{order::Lsb0, slice::BitSlice, view::BitView}; +use serde::{Deserialize, Serialize}; +use std::{convert::Infallible, fmt, iter::FusedIterator, sync::Arc}; -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] pub struct EnumVariant { pub name: Interned, pub ty: Option, @@ -149,6 +158,12 @@ impl EnumTypePropertiesBuilder { variant_count: variant_count + 1, } } + #[must_use] + pub fn variants(self, variants: impl IntoIterator) -> Self { + variants.into_iter().fold(self, |this, variant| { + this.variant(variant.ty.map(CanonicalType::type_properties)) + }) + } pub const fn finish(self) -> TypeProperties { assert!( self.variant_count != 0, @@ -178,7 +193,8 @@ impl Default for EnumTypePropertiesBuilder { impl Enum { #[track_caller] pub fn new(variants: Interned<[EnumVariant]>) -> Self { - let mut name_indexes = HashMap::with_capacity(variants.len()); + let mut name_indexes = + HashMap::with_capacity_and_hasher(variants.len(), Default::default()); let mut type_props_builder = EnumTypePropertiesBuilder::new(); for (index, EnumVariant { name, ty }) in variants.iter().enumerate() { if let Some(old_index) = name_indexes.insert(*name, index) { @@ -245,6 +261,7 @@ pub trait EnumType: MatchVariantsIter = EnumMatchVariantsIter, > { + type SimBuilder: From; fn variants(&self) -> Interned<[EnumVariant]>; fn match_activate_scope( v: Self::MatchVariantAndInactiveScope, @@ -307,7 +324,18 @@ impl DoubleEndedIterator for EnumMatchVariantsIter { } } +pub struct NoBuilder { + _ty: Enum, +} + +impl From for NoBuilder { + fn from(_ty: Enum) -> Self { + Self { _ty } + } +} + impl EnumType for Enum { + type SimBuilder = NoBuilder; fn match_activate_scope( v: Self::MatchVariantAndInactiveScope, ) -> (Self::MatchVariant, Self::MatchActiveScope) { @@ -322,6 +350,7 @@ impl EnumType for Enum { impl Type for Enum { type BaseType = Enum; type MaskType = Bool; + type SimValue = OpaqueSimValue; type MatchVariant = Option>; type MatchActiveScope = Scope; type MatchVariantAndInactiveScope = EnumMatchVariantAndInactiveScope; @@ -352,6 +381,309 @@ impl Type for Enum { fn source_location() -> SourceLocation { SourceLocation::builtin() } + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + assert_eq!(bits.len(), self.type_properties().bit_width); + OpaqueSimValue::from_bitslice(bits) + } + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + assert_eq!(bits.len(), self.type_properties().bit_width); + assert_eq!(value.bit_width(), self.type_properties().bit_width); + value.bits_mut().bits_mut().copy_from_bitslice(bits); + } + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + assert_eq!(bits.len(), self.type_properties().bit_width); + assert_eq!(value.bit_width(), self.type_properties().bit_width); + bits.copy_from_bitslice(value.bits().bits()); + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, Default)] +pub struct EnumPaddingSimValue { + bits: Option, +} + +impl EnumPaddingSimValue { + pub const fn new() -> Self { + Self { bits: None } + } + pub fn bit_width(&self) -> Option { + self.bits.as_ref().map(UIntValue::width) + } + pub fn bits(&self) -> &Option { + &self.bits + } + pub fn bits_mut(&mut self) -> &mut Option { + &mut self.bits + } + pub fn into_bits(self) -> Option { + self.bits + } + pub fn from_bits(bits: Option) -> Self { + Self { bits } + } + pub fn from_bitslice(v: &BitSlice) -> Self { + Self { + bits: Some(UIntValue::new(Arc::new(v.to_bitvec()))), + } + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub struct UnknownVariantSimValue { + discriminant: usize, + body_bits: UIntValue, +} + +impl UnknownVariantSimValue { + pub fn discriminant(&self) -> usize { + self.discriminant + } + pub fn body_bits(&self) -> &UIntValue { + &self.body_bits + } + pub fn body_bits_mut(&mut self) -> &mut UIntValue { + &mut self.body_bits + } + pub fn into_body_bits(self) -> UIntValue { + self.body_bits + } + pub fn into_parts(self) -> (usize, UIntValue) { + (self.discriminant, self.body_bits) + } + pub fn new(discriminant: usize, body_bits: UIntValue) -> Self { + Self { + discriminant, + body_bits, + } + } +} + +pub struct EnumSimValueFromBits<'a> { + variants: Interned<[EnumVariant]>, + discriminant: usize, + body_bits: &'a BitSlice, +} + +impl<'a> EnumSimValueFromBits<'a> { + #[track_caller] + pub fn new(ty: T, bits: &'a BitSlice) -> Self { + let variants = ty.variants(); + let bit_width = EnumTypePropertiesBuilder::new() + .variants(variants) + .finish() + .bit_width; + assert_eq!(bit_width, bits.len()); + let (discriminant_bits, body_bits) = + bits.split_at(discriminant_bit_width_impl(variants.len())); + let mut discriminant = 0usize; + discriminant.view_bits_mut::()[..discriminant_bits.len()] + .copy_from_bitslice(discriminant_bits); + Self { + variants, + discriminant, + body_bits, + } + } + pub fn discriminant(&self) -> usize { + self.discriminant + } + #[track_caller] + #[cold] + fn usage_error(&self, clone: bool) -> ! { + let clone = if clone { "clone_" } else { "" }; + match self.variants.get(self.discriminant) { + None => { + panic!("should have called EnumSimValueFromBits::unknown_variant_{clone}from_bits"); + } + Some(EnumVariant { ty: None, .. }) => { + panic!( + "should have called EnumSimValueFromBits::variant_no_field_{clone}from_bits" + ); + } + Some(EnumVariant { ty: Some(_), .. }) => { + panic!( + "should have called EnumSimValueFromBits::variant_with_field_{clone}from_bits" + ); + } + } + } + #[track_caller] + fn known_variant(&self, clone: bool) -> (Option, &'a BitSlice, &'a BitSlice) { + let Some(EnumVariant { ty, .. }) = self.variants.get(self.discriminant) else { + self.usage_error(clone); + }; + let variant_bit_width = ty.map_or(0, CanonicalType::bit_width); + let (variant_bits, padding_bits) = self.body_bits.split_at(variant_bit_width); + (*ty, variant_bits, padding_bits) + } + #[track_caller] + pub fn unknown_variant_from_bits(self) -> UnknownVariantSimValue { + let None = self.variants.get(self.discriminant) else { + self.usage_error(false); + }; + UnknownVariantSimValue::new( + self.discriminant, + UIntValue::new(Arc::new(self.body_bits.to_bitvec())), + ) + } + #[track_caller] + pub fn unknown_variant_clone_from_bits(self, value: &mut UnknownVariantSimValue) { + let None = self.variants.get(self.discriminant) else { + self.usage_error(true); + }; + value.discriminant = self.discriminant; + assert_eq!(value.body_bits.width(), self.body_bits.len()); + value + .body_bits + .bits_mut() + .copy_from_bitslice(self.body_bits); + } + #[track_caller] + pub fn variant_no_field_from_bits(self) -> EnumPaddingSimValue { + let (None, _variant_bits, padding_bits) = self.known_variant(false) else { + self.usage_error(false); + }; + EnumPaddingSimValue::from_bitslice(padding_bits) + } + #[track_caller] + pub fn variant_with_field_from_bits(self) -> (SimValue, EnumPaddingSimValue) { + let (Some(variant_ty), variant_bits, padding_bits) = self.known_variant(false) else { + self.usage_error(false); + }; + ( + SimValue::from_bitslice(T::from_canonical(variant_ty), variant_bits), + EnumPaddingSimValue::from_bitslice(padding_bits), + ) + } + #[track_caller] + fn clone_padding_from_bits(padding: &mut EnumPaddingSimValue, padding_bits: &BitSlice) { + match padding.bits_mut() { + None => *padding = EnumPaddingSimValue::from_bitslice(padding_bits), + Some(padding) => { + assert_eq!(padding.width(), padding_bits.len()); + padding.bits_mut().copy_from_bitslice(padding_bits); + } + } + } + #[track_caller] + pub fn variant_no_field_clone_from_bits(self, padding: &mut EnumPaddingSimValue) { + let (None, _variant_bits, padding_bits) = self.known_variant(true) else { + self.usage_error(true); + }; + Self::clone_padding_from_bits(padding, padding_bits); + } + #[track_caller] + pub fn variant_with_field_clone_from_bits( + self, + value: &mut SimValue, + padding: &mut EnumPaddingSimValue, + ) { + let (Some(variant_ty), variant_bits, padding_bits) = self.known_variant(true) else { + self.usage_error(true); + }; + assert_eq!(SimValue::ty(value), T::from_canonical(variant_ty)); + SimValue::bits_mut(value) + .bits_mut() + .copy_from_bitslice(variant_bits); + Self::clone_padding_from_bits(padding, padding_bits); + } +} + +pub struct EnumSimValueToBits<'a> { + variants: Interned<[EnumVariant]>, + bit_width: usize, + discriminant_bit_width: usize, + bits: &'a mut BitSlice, +} + +impl<'a> EnumSimValueToBits<'a> { + #[track_caller] + pub fn new(ty: T, bits: &'a mut BitSlice) -> Self { + let variants = ty.variants(); + let bit_width = EnumTypePropertiesBuilder::new() + .variants(variants) + .finish() + .bit_width; + assert_eq!(bit_width, bits.len()); + Self { + variants, + bit_width, + discriminant_bit_width: discriminant_bit_width_impl(variants.len()), + bits, + } + } + #[track_caller] + fn discriminant_to_bits(&mut self, mut discriminant: usize) { + let orig_discriminant = discriminant; + let discriminant_bits = + &mut discriminant.view_bits_mut::()[..self.discriminant_bit_width]; + self.bits[..self.discriminant_bit_width].copy_from_bitslice(discriminant_bits); + discriminant_bits.fill(false); + assert!( + discriminant == 0, + "{orig_discriminant:#x} is too big to fit in enum discriminant bits", + ); + } + #[track_caller] + pub fn unknown_variant_to_bits(mut self, value: &UnknownVariantSimValue) { + self.discriminant_to_bits(value.discriminant); + let None = self.variants.get(value.discriminant) else { + panic!("can't use UnknownVariantSimValue to set known discriminant"); + }; + assert_eq!( + self.bit_width - self.discriminant_bit_width, + value.body_bits.width() + ); + self.bits[self.discriminant_bit_width..].copy_from_bitslice(value.body_bits.bits()); + } + #[track_caller] + fn known_variant( + mut self, + discriminant: usize, + padding: &EnumPaddingSimValue, + ) -> (Option, &'a mut BitSlice) { + self.discriminant_to_bits(discriminant); + let variant_ty = self.variants[discriminant].ty; + let variant_bit_width = variant_ty.map_or(0, CanonicalType::bit_width); + let padding_bits = &mut self.bits[self.discriminant_bit_width..][variant_bit_width..]; + if let Some(padding) = padding.bits() { + assert_eq!(padding.width(), padding_bits.len()); + padding_bits.copy_from_bitslice(padding.bits()); + } else { + padding_bits.fill(false); + } + let variant_bits = &mut self.bits[self.discriminant_bit_width..][..variant_bit_width]; + (variant_ty, variant_bits) + } + #[track_caller] + pub fn variant_no_field_to_bits(self, discriminant: usize, padding: &EnumPaddingSimValue) { + let (None, _variant_bits) = self.known_variant(discriminant, padding) else { + panic!("expected variant to have no field"); + }; + } + #[track_caller] + pub fn variant_with_field_to_bits( + self, + discriminant: usize, + value: &SimValue, + padding: &EnumPaddingSimValue, + ) { + let (Some(variant_ty), variant_bits) = self.known_variant(discriminant, padding) else { + panic!("expected variant to have a field"); + }; + assert_eq!(SimValue::ty(value), T::from_canonical(variant_ty)); + variant_bits.copy_from_bitslice(SimValue::bits(value).bits()); + } +} + +#[doc(hidden)] +pub fn assert_is_enum_type(v: T) -> T { + v +} + +#[doc(hidden)] +pub fn enum_type_to_sim_builder(v: T) -> T::SimBuilder { + v.into() } #[hdl] @@ -360,6 +692,79 @@ pub enum HdlOption { HdlSome(T), } +impl, Rhs: Type> ExprPartialEq> for HdlOption { + #[hdl] + fn cmp_eq(lhs: Expr, rhs: Expr>) -> Expr { + #[hdl] + let cmp_eq = wire(); + #[hdl] + match lhs { + HdlSome(lhs) => + { + #[hdl] + match rhs { + HdlSome(rhs) => connect(cmp_eq, ExprPartialEq::cmp_eq(lhs, rhs)), + HdlNone => connect(cmp_eq, false), + } + } + HdlNone => + { + #[hdl] + match rhs { + HdlSome(_) => connect(cmp_eq, false), + HdlNone => connect(cmp_eq, true), + } + } + } + cmp_eq + } + + #[hdl] + fn cmp_ne(lhs: Expr, rhs: Expr>) -> Expr { + #[hdl] + let cmp_ne = wire(); + #[hdl] + match lhs { + HdlSome(lhs) => + { + #[hdl] + match rhs { + HdlSome(rhs) => connect(cmp_ne, ExprPartialEq::cmp_ne(lhs, rhs)), + HdlNone => connect(cmp_ne, true), + } + } + HdlNone => + { + #[hdl] + match rhs { + HdlSome(_) => connect(cmp_ne, true), + HdlNone => connect(cmp_ne, false), + } + } + } + cmp_ne + } +} + +impl, Rhs: Type> SimValuePartialEq> for HdlOption { + fn sim_value_eq(this: &SimValue, other: &SimValue>) -> bool { + type SimValueMatch = ::SimValue; + match (&**this, &**other) { + (SimValueMatch::::HdlNone(_), SimValueMatch::>::HdlNone(_)) => { + true + } + (SimValueMatch::::HdlSome(..), SimValueMatch::>::HdlNone(_)) + | (SimValueMatch::::HdlNone(_), SimValueMatch::>::HdlSome(..)) => { + false + } + ( + SimValueMatch::::HdlSome(l, _), + SimValueMatch::>::HdlSome(r, _), + ) => l == r, + } + } +} + #[allow(non_snake_case)] pub fn HdlNone() -> Expr> { HdlOption[T::TYPE].HdlNone() diff --git a/crates/fayalite/src/expr.rs b/crates/fayalite/src/expr.rs index fa50852..e070674 100644 --- a/crates/fayalite/src/expr.rs +++ b/crates/fayalite/src/expr.rs @@ -16,7 +16,9 @@ use crate::{ transform::visit::{Fold, Folder, Visit, Visitor}, Instance, ModuleIO, }, + phantom_const::PhantomConst, reg::Reg, + reset::{AsyncReset, Reset, ResetType, ResetTypeDispatch, SyncReset}, ty::{CanonicalType, StaticType, Type, TypeWithDeref}, wire::Wire, }; @@ -108,6 +110,7 @@ expr_enum! { UIntLiteral(Interned), SIntLiteral(Interned), BoolLiteral(bool), + PhantomConst(PhantomConst), BundleLiteral(ops::BundleLiteral), ArrayLiteral(ops::ArrayLiteral), EnumLiteral(ops::EnumLiteral), @@ -209,7 +212,9 @@ expr_enum! { ModuleIO(ModuleIO), Instance(Instance), Wire(Wire), - Reg(Reg), + Reg(Reg), + RegSync(Reg), + RegAsync(Reg), MemPort(MemPort), } } @@ -269,6 +274,17 @@ pub struct Expr { impl fmt::Debug for Expr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[cfg(debug_assertions)] + { + let Self { + __enum, + __ty, + __flow, + } = self; + let expr_ty = __ty.canonical(); + let enum_ty = __enum.to_expr().__ty; + assert_eq!(expr_ty, enum_ty, "expr ty mismatch:\nExpr {{\n__enum: {__enum:?},\n__ty: {__ty:?},\n__flow: {__flow:?}\n}}"); + } self.__enum.fmt(f) } } @@ -593,25 +609,42 @@ impl GetTarget for Wire { } } -impl ToExpr for Reg { +impl ToExpr for Reg { type Type = T; fn to_expr(&self) -> Expr { + struct Dispatch; + impl ResetTypeDispatch for Dispatch { + type Input = Reg; + type Output = ExprEnum; + + fn reset(self, input: Self::Input) -> Self::Output { + ExprEnum::Reg(input) + } + + fn sync_reset(self, input: Self::Input) -> Self::Output { + ExprEnum::RegSync(input) + } + + fn async_reset(self, input: Self::Input) -> Self::Output { + ExprEnum::RegAsync(input) + } + } Expr { - __enum: ExprEnum::Reg(self.canonical()).intern_sized(), + __enum: R::dispatch(self.canonical(), Dispatch).intern_sized(), __ty: self.ty(), __flow: self.flow(), } } } -impl ToLiteralBits for Reg { +impl ToLiteralBits for Reg { fn to_literal_bits(&self) -> Result, NotALiteralExpr> { Err(NotALiteralExpr) } } -impl GetTarget for Reg { +impl GetTarget for Reg { fn target(&self) -> Option> { Some(Intern::intern_sized(self.canonical().into())) } @@ -678,6 +711,7 @@ impl CastToBits for T { } pub trait CastBitsTo { + #[track_caller] fn cast_bits_to(&self, ty: T) -> Expr; } @@ -735,3 +769,27 @@ pub fn repeat( ) .to_expr() } + +impl ToExpr for PhantomConst { + type Type = Self; + + fn to_expr(&self) -> Expr { + Expr { + __enum: ExprEnum::PhantomConst(self.canonical_phantom_const()).intern_sized(), + __ty: *self, + __flow: Flow::Source, + } + } +} + +impl GetTarget for PhantomConst { + fn target(&self) -> Option> { + None + } +} + +impl ToLiteralBits for PhantomConst { + fn to_literal_bits(&self) -> Result, NotALiteralExpr> { + Ok(Interned::default()) + } +} diff --git a/crates/fayalite/src/expr/ops.rs b/crates/fayalite/src/expr/ops.rs index 3579641..e794a68 100644 --- a/crates/fayalite/src/expr/ops.rs +++ b/crates/fayalite/src/expr/ops.rs @@ -11,15 +11,19 @@ use crate::{ GetTarget, Target, TargetPathArrayElement, TargetPathBundleField, TargetPathDynArrayElement, TargetPathElement, }, - CastTo, Expr, ExprEnum, Flow, HdlPartialEq, HdlPartialOrd, NotALiteralExpr, ReduceBits, - ToExpr, ToLiteralBits, + CastBitsTo as _, CastTo, CastToBits as _, Expr, ExprEnum, Flow, HdlPartialEq, + HdlPartialOrd, NotALiteralExpr, ReduceBits, ToExpr, ToLiteralBits, }, int::{ Bool, BoolOrIntType, DynSize, IntType, KnownSize, SInt, SIntType, SIntValue, Size, UInt, UIntType, UIntValue, }, intern::{Intern, Interned}, - reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset}, + phantom_const::{PhantomConst, PhantomConstValue}, + reset::{ + AsyncReset, Reset, ResetType, ResetTypeDispatch, SyncReset, ToAsyncReset, ToReset, + ToSyncReset, + }, ty::{CanonicalType, StaticType, Type}, util::ConstUsize, }; @@ -262,7 +266,7 @@ impl Neg { }; let result_ty = retval.ty(); retval.literal_bits = arg.to_literal_bits().map(|bits| { - Intern::intern_owned(result_ty.bits_from_bigint_wrapping(-SInt::bits_to_bigint(&bits))) + Intern::intern_owned(result_ty.bits_from_bigint_wrapping(&-SInt::bits_to_bigint(&bits))) }); retval } @@ -369,7 +373,7 @@ fn binary_op_literal_bits, #[dyn] SInt, AsyncReset, #[trai impl_cast_bit_op!(CastSyncResetToBool, SyncReset, Bool); impl_cast_bit_op!(CastSyncResetToUInt, SyncReset, UInt<1>, #[dyn] UInt); impl_cast_bit_op!(CastSyncResetToSInt, SyncReset, SInt<1>, #[dyn] SInt); -impl_cast_bit_op!(CastSyncResetToReset, SyncReset, Reset, #[trait] ToReset::to_reset); +impl_cast_bit_op!(CastSyncResetToReset, SyncReset, Reset); impl_cast_bit_op!(CastAsyncResetToBool, AsyncReset, Bool); impl_cast_bit_op!(CastAsyncResetToUInt, AsyncReset, UInt<1>, #[dyn] UInt); impl_cast_bit_op!(CastAsyncResetToSInt, AsyncReset, SInt<1>, #[dyn] SInt); -impl_cast_bit_op!(CastAsyncResetToReset, AsyncReset, Reset, #[trait] ToReset::to_reset); +impl_cast_bit_op!(CastAsyncResetToReset, AsyncReset, Reset); impl_cast_bit_op!(CastResetToBool, Reset, Bool); impl_cast_bit_op!(CastResetToUInt, Reset, UInt<1>, #[dyn] UInt); impl_cast_bit_op!(CastResetToSInt, Reset, SInt<1>, #[dyn] SInt); @@ -1788,6 +1792,127 @@ impl_cast_bit_op!(CastClockToBool, Clock, Bool); impl_cast_bit_op!(CastClockToUInt, Clock, UInt<1>, #[dyn] UInt); impl_cast_bit_op!(CastClockToSInt, Clock, SInt<1>, #[dyn] SInt); +impl ToReset for Expr { + fn to_reset(&self) -> Expr { + struct Dispatch; + impl ResetTypeDispatch for Dispatch { + type Input = Expr; + type Output = Expr; + + fn reset(self, input: Self::Input) -> Self::Output { + input + } + + fn sync_reset(self, input: Self::Input) -> Self::Output { + input.cast_to_static() + } + + fn async_reset(self, input: Self::Input) -> Self::Output { + input.cast_to_static() + } + } + T::dispatch(*self, Dispatch) + } +} + +impl ExprCastTo for AsyncReset { + fn cast_to(src: Expr, _to_type: AsyncReset) -> Expr { + src + } +} + +impl ExprCastTo for AsyncReset { + fn cast_to(src: Expr, to_type: SyncReset) -> Expr { + src.cast_to(Bool).cast_to(to_type) + } +} + +impl ExprCastTo for AsyncReset { + fn cast_to(src: Expr, to_type: Clock) -> Expr { + src.cast_to(Bool).cast_to(to_type) + } +} + +impl ExprCastTo for SyncReset { + fn cast_to(src: Expr, to_type: AsyncReset) -> Expr { + src.cast_to(Bool).cast_to(to_type) + } +} + +impl ExprCastTo for SyncReset { + fn cast_to(src: Expr, _to_type: SyncReset) -> Expr { + src + } +} + +impl ExprCastTo for SyncReset { + fn cast_to(src: Expr, to_type: Clock) -> Expr { + src.cast_to(Bool).cast_to(to_type) + } +} + +impl ExprCastTo for Reset { + fn cast_to(src: Expr, to_type: AsyncReset) -> Expr { + src.cast_to(Bool).cast_to(to_type) + } +} + +impl ExprCastTo for Reset { + fn cast_to(src: Expr, to_type: SyncReset) -> Expr { + src.cast_to(Bool).cast_to(to_type) + } +} + +impl ExprCastTo for Reset { + fn cast_to(src: Expr, _to_type: Reset) -> Expr { + src + } +} + +impl ExprCastTo for Reset { + fn cast_to(src: Expr, to_type: Clock) -> Expr { + src.cast_to(Bool).cast_to(to_type) + } +} + +impl ExprCastTo for Clock { + fn cast_to(src: Expr, to_type: AsyncReset) -> Expr { + src.cast_to(Bool).cast_to(to_type) + } +} + +impl ExprCastTo for Clock { + fn cast_to(src: Expr, to_type: SyncReset) -> Expr { + src.cast_to(Bool).cast_to(to_type) + } +} + +impl ExprCastTo for Clock { + fn cast_to(src: Expr, _to_type: Clock) -> Expr { + src + } +} + +impl ExprCastTo<()> for PhantomConst { + fn cast_to(src: Expr, to_type: ()) -> Expr<()> { + src.cast_to_bits().cast_bits_to(to_type) + } +} + +impl ExprCastTo> for () { + fn cast_to(src: Expr, to_type: PhantomConst) -> Expr> { + src.cast_to_bits().cast_bits_to(to_type) + } +} + +impl ExprCastTo> + for PhantomConst +{ + fn cast_to(src: Expr, to_type: PhantomConst) -> Expr> { + src.cast_to_bits().cast_bits_to(to_type) + } +} + #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct FieldAccess { base: Expr, @@ -2604,3 +2729,47 @@ impl ToExpr for Uninit { } } } + +pub trait ExprIntoIterator: Type { + type Item: Type; + type ExprIntoIter: Iterator>; + + fn expr_into_iter(e: Expr) -> Self::ExprIntoIter; +} + +impl IntoIterator for Expr { + type Item = Expr; + type IntoIter = T::ExprIntoIter; + + fn into_iter(self) -> Self::IntoIter { + T::expr_into_iter(self) + } +} + +impl IntoIterator for &'_ Expr { + type Item = Expr; + type IntoIter = T::ExprIntoIter; + + fn into_iter(self) -> Self::IntoIter { + T::expr_into_iter(*self) + } +} + +impl IntoIterator for &'_ mut Expr { + type Item = Expr; + type IntoIter = T::ExprIntoIter; + + fn into_iter(self) -> Self::IntoIter { + T::expr_into_iter(*self) + } +} + +pub trait ExprFromIterator: Type { + fn expr_from_iter>(iter: T) -> Expr; +} + +impl, A> FromIterator for Expr { + fn from_iter>(iter: T) -> Self { + This::expr_from_iter(iter) + } +} diff --git a/crates/fayalite/src/expr/target.rs b/crates/fayalite/src/expr/target.rs index 0f85f62..c8c55e9 100644 --- a/crates/fayalite/src/expr/target.rs +++ b/crates/fayalite/src/expr/target.rs @@ -3,18 +3,19 @@ use crate::{ array::Array, bundle::{Bundle, BundleField}, - expr::Flow, + expr::{Expr, Flow, ToExpr}, intern::{Intern, Interned}, memory::{DynPortType, MemPort}, module::{Instance, ModuleIO, TargetName}, reg::Reg, + reset::{AsyncReset, Reset, ResetType, ResetTypeDispatch, SyncReset}, source_location::SourceLocation, ty::{CanonicalType, Type}, wire::Wire, }; use std::fmt; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct TargetPathBundleField { pub name: Interned, } @@ -25,7 +26,7 @@ impl fmt::Display for TargetPathBundleField { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct TargetPathArrayElement { pub index: usize, } @@ -36,7 +37,7 @@ impl fmt::Display for TargetPathArrayElement { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct TargetPathDynArrayElement {} impl fmt::Display for TargetPathDynArrayElement { @@ -45,7 +46,7 @@ impl fmt::Display for TargetPathDynArrayElement { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum TargetPathElement { BundleField(TargetPathBundleField), ArrayElement(TargetPathArrayElement), @@ -127,6 +128,7 @@ macro_rules! impl_target_base { $(#[$enum_meta:meta])* $enum_vis:vis enum $TargetBase:ident { $( + $(#[from = $from:ident])? #[is = $is_fn:ident] #[to = $to_fn:ident] $(#[$variant_meta:meta])* @@ -150,19 +152,19 @@ macro_rules! impl_target_base { } } - $( + $($( impl From<$VariantTy> for $TargetBase { - fn from(value: $VariantTy) -> Self { + fn $from(value: $VariantTy) -> Self { Self::$Variant(value) } } impl From<$VariantTy> for Target { - fn from(value: $VariantTy) -> Self { + fn $from(value: $VariantTy) -> Self { $TargetBase::$Variant(value).into() } } - )* + )*)? impl $TargetBase { $( @@ -193,30 +195,79 @@ macro_rules! impl_target_base { } } } + + impl ToExpr for $TargetBase { + type Type = CanonicalType; + + fn to_expr(&self) -> Expr { + match self { + $(Self::$Variant(v) => Expr::canonical(v.to_expr()),)* + } + } + } }; } impl_target_base! { - #[derive(Clone, PartialEq, Eq, Hash)] + #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub enum TargetBase { + #[from = from] #[is = is_module_io] #[to = module_io] ModuleIO(ModuleIO), + #[from = from] #[is = is_mem_port] #[to = mem_port] MemPort(MemPort), #[is = is_reg] #[to = reg] - Reg(Reg), + Reg(Reg), + #[is = is_reg_sync] + #[to = reg_sync] + RegSync(Reg), + #[is = is_reg_async] + #[to = reg_async] + RegAsync(Reg), + #[from = from] #[is = is_wire] #[to = wire] Wire(Wire), + #[from = from] #[is = is_instance] #[to = instance] Instance(Instance), } } +impl From> for TargetBase { + fn from(value: Reg) -> Self { + struct Dispatch; + impl ResetTypeDispatch for Dispatch { + type Input = Reg; + type Output = TargetBase; + + fn reset(self, input: Self::Input) -> Self::Output { + TargetBase::Reg(input) + } + + fn sync_reset(self, input: Self::Input) -> Self::Output { + TargetBase::RegSync(input) + } + + fn async_reset(self, input: Self::Input) -> Self::Output { + TargetBase::RegAsync(input) + } + } + R::dispatch(value, Dispatch) + } +} + +impl From> for Target { + fn from(value: Reg) -> Self { + TargetBase::from(value).into() + } +} + impl fmt::Display for TargetBase { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.target_name()) @@ -229,6 +280,8 @@ impl TargetBase { TargetBase::ModuleIO(v) => TargetName(v.scoped_name(), None), TargetBase::MemPort(v) => TargetName(v.mem_name(), Some(v.port_name())), TargetBase::Reg(v) => TargetName(v.scoped_name(), None), + TargetBase::RegSync(v) => TargetName(v.scoped_name(), None), + TargetBase::RegAsync(v) => TargetName(v.scoped_name(), None), TargetBase::Wire(v) => TargetName(v.scoped_name(), None), TargetBase::Instance(v) => TargetName(v.scoped_name(), None), } @@ -238,6 +291,8 @@ impl TargetBase { TargetBase::ModuleIO(v) => v.ty(), TargetBase::MemPort(v) => v.ty().canonical(), TargetBase::Reg(v) => v.ty(), + TargetBase::RegSync(v) => v.ty(), + TargetBase::RegAsync(v) => v.ty(), TargetBase::Wire(v) => v.ty(), TargetBase::Instance(v) => v.ty().canonical(), } @@ -313,7 +368,7 @@ impl TargetChild { } } -#[derive(Clone, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, Hash)] pub enum Target { Base(Interned), Child(TargetChild), diff --git a/crates/fayalite/src/firrtl.rs b/crates/fayalite/src/firrtl.rs index bc75ccc..d33c7a9 100644 --- a/crates/fayalite/src/firrtl.rs +++ b/crates/fayalite/src/firrtl.rs @@ -15,7 +15,7 @@ use crate::{ target::{ Target, TargetBase, TargetPathArrayElement, TargetPathBundleField, TargetPathElement, }, - Expr, ExprEnum, + CastBitsTo, Expr, ExprEnum, }, formal::FormalKind, int::{Bool, DynSize, IntType, SIntValue, UInt, UIntValue}, @@ -31,17 +31,16 @@ use crate::{ StmtConnect, StmtDeclaration, StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg, StmtWire, }, - reset::{AsyncReset, Reset, SyncReset}, + reset::{AsyncReset, Reset, ResetType, SyncReset}, source_location::SourceLocation, ty::{CanonicalType, Type}, util::{ const_str_array_is_strictly_ascending, BitSliceWriteWithBase, DebugAsRawString, - GenericConstBool, + GenericConstBool, HashMap, HashSet, }, }; use bitvec::slice::BitSlice; use clap::value_parser; -use hashbrown::{HashMap, HashSet}; use num_traits::Signed; use serde::Serialize; use std::{ @@ -447,6 +446,7 @@ impl TypeState { CanonicalType::AsyncReset(AsyncReset {}) => "AsyncReset".into(), CanonicalType::SyncReset(SyncReset {}) => "UInt<1>".into(), CanonicalType::Reset(Reset {}) => "Reset".into(), + CanonicalType::PhantomConst(_) => "{}".into(), } } } @@ -1152,6 +1152,7 @@ impl<'a> Exporter<'a> { | CanonicalType::Clock(_) | CanonicalType::AsyncReset(_) | CanonicalType::Reset(_) => format!("asUInt({value_str})"), + CanonicalType::PhantomConst(_) => "UInt<0>(0)".into(), } } fn expr_cast_bits_to_bundle( @@ -1357,6 +1358,12 @@ impl<'a> Exporter<'a> { CanonicalType::AsyncReset(_) => format!("asAsyncReset({value_str})"), CanonicalType::SyncReset(_) => value_str, CanonicalType::Reset(_) => unreachable!("Reset is not bit castable to"), + CanonicalType::PhantomConst(_) => { + let retval = self.module.ns.make_new("_cast_bits_to_phantom_const_expr"); + definitions.add_definition_line(format_args!("{extra_indent}wire {retval}: {{}}")); + definitions.add_definition_line(format_args!("{extra_indent}invalidate {retval}")); + return retval.to_string(); + } } } fn expr_unary( @@ -1395,6 +1402,11 @@ impl<'a> Exporter<'a> { ExprEnum::UIntLiteral(literal) => self.uint_literal(&literal), ExprEnum::SIntLiteral(literal) => self.sint_literal(&literal), ExprEnum::BoolLiteral(literal) => self.bool_literal(literal), + ExprEnum::PhantomConst(ty) => self.expr( + UInt[0].zero().cast_bits_to(ty.canonical()), + definitions, + const_ty, + ), ExprEnum::ArrayLiteral(array_literal) => { self.array_literal_expr(array_literal, definitions, const_ty) } @@ -1739,6 +1751,14 @@ impl<'a> Exporter<'a> { assert!(!const_ty, "not a constant"); self.module.ns.get(expr.scoped_name().1).to_string() } + ExprEnum::RegSync(expr) => { + assert!(!const_ty, "not a constant"); + self.module.ns.get(expr.scoped_name().1).to_string() + } + ExprEnum::RegAsync(expr) => { + assert!(!const_ty, "not a constant"); + self.module.ns.get(expr.scoped_name().1).to_string() + } ExprEnum::MemPort(expr) => { assert!(!const_ty, "not a constant"); let mem_name = self.module.ns.get(expr.mem_name().1); @@ -1848,6 +1868,8 @@ impl<'a> Exporter<'a> { self.module.ns.get(v.mem_name().1) } TargetBase::Reg(v) => self.module.ns.get(v.name_id()), + TargetBase::RegSync(v) => self.module.ns.get(v.name_id()), + TargetBase::RegAsync(v) => self.module.ns.get(v.name_id()), TargetBase::Wire(v) => self.module.ns.get(v.name_id()), TargetBase::Instance(v) => self.module.ns.get(v.name_id()), }; @@ -1956,6 +1978,37 @@ impl<'a> Exporter<'a> { drop(memory_indent); Ok(body) } + fn stmt_reg( + &mut self, + stmt_reg: StmtReg, + module_name: Ident, + definitions: &RcDefinitions, + body: &mut String, + ) { + let StmtReg { annotations, reg } = stmt_reg; + let indent = self.indent; + self.targeted_annotations(module_name, vec![], &annotations); + let name = self.module.ns.get(reg.name_id()); + let ty = self.type_state.ty(reg.ty()); + let clk = self.expr(Expr::canonical(reg.clock_domain().clk), definitions, false); + if let Some(init) = reg.init() { + let rst = self.expr(Expr::canonical(reg.clock_domain().rst), definitions, false); + let init = self.expr(init, definitions, false); + writeln!( + body, + "{indent}regreset {name}: {ty}, {clk}, {rst}, {init}{}", + FileInfo::new(reg.source_location()), + ) + .unwrap(); + } else { + writeln!( + body, + "{indent}reg {name}: {ty}, {clk}{}", + FileInfo::new(reg.source_location()), + ) + .unwrap(); + } + } fn block( &mut self, module: Interned>, @@ -2126,30 +2179,14 @@ impl<'a> Exporter<'a> { ) .unwrap(); } - Stmt::Declaration(StmtDeclaration::Reg(StmtReg { annotations, reg })) => { - self.targeted_annotations(module_name, vec![], &annotations); - let name = self.module.ns.get(reg.name_id()); - let ty = self.type_state.ty(reg.ty()); - let clk = - self.expr(Expr::canonical(reg.clock_domain().clk), &definitions, false); - if let Some(init) = reg.init() { - let rst = - self.expr(Expr::canonical(reg.clock_domain().rst), &definitions, false); - let init = self.expr(init, &definitions, false); - writeln!( - body, - "{indent}regreset {name}: {ty}, {clk}, {rst}, {init}{}", - FileInfo::new(reg.source_location()), - ) - .unwrap(); - } else { - writeln!( - body, - "{indent}reg {name}: {ty}, {clk}{}", - FileInfo::new(reg.source_location()), - ) - .unwrap(); - } + Stmt::Declaration(StmtDeclaration::Reg(stmt_reg)) => { + self.stmt_reg(stmt_reg, module_name, &definitions, &mut body); + } + Stmt::Declaration(StmtDeclaration::RegSync(stmt_reg)) => { + self.stmt_reg(stmt_reg, module_name, &definitions, &mut body); + } + Stmt::Declaration(StmtDeclaration::RegAsync(stmt_reg)) => { + self.stmt_reg(stmt_reg, module_name, &definitions, &mut body); } Stmt::Declaration(StmtDeclaration::Instance(StmtInstance { annotations, @@ -2220,6 +2257,7 @@ impl<'a> Exporter<'a> { ModuleBody::Extern(ExternModuleBody { verilog_name, parameters, + simulation: _, }) => { let verilog_name = Ident(verilog_name); writeln!(body, "{indent}defname = {verilog_name}").unwrap(); @@ -2583,7 +2621,7 @@ fn export_impl( indent_depth: &indent_depth, indent: " ", }, - seen_modules: HashSet::new(), + seen_modules: HashSet::default(), unwritten_modules: VecDeque::new(), global_ns, module: ModuleState::default(), diff --git a/crates/fayalite/src/int.rs b/crates/fayalite/src/int.rs index 03b2c88..d8364b1 100644 --- a/crates/fayalite/src/int.rs +++ b/crates/fayalite/src/int.rs @@ -2,27 +2,52 @@ // See Notices.txt for copyright information use crate::{ + array::ArrayType, expr::{ target::{GetTarget, Target}, Expr, NotALiteralExpr, ToExpr, ToLiteralBits, }, + hdl, intern::{Intern, Interned, Memoize}, + sim::value::{SimValue, ToSimValueWithType}, source_location::SourceLocation, ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, util::{interned_bit, ConstBool, ConstUsize, GenericConstBool, GenericConstUsize}, }; -use bitvec::{order::Lsb0, slice::BitSlice, vec::BitVec}; +use bitvec::{order::Lsb0, slice::BitSlice, vec::BitVec, view::BitView}; use num_bigint::{BigInt, BigUint, Sign}; -use num_traits::{Signed, Zero}; +use num_traits::{One, Signed, Zero}; +use serde::{ + de::{DeserializeOwned, Error, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, +}; use std::{ borrow::{BorrowMut, Cow}, fmt, marker::PhantomData, num::NonZero, ops::{Bound, Index, Not, Range, RangeBounds, RangeInclusive}, + str::FromStr, sync::Arc, }; +mod uint_in_range; + +#[hdl] +pub type UIntInRangeType = uint_in_range::UIntInRangeType; + +#[hdl] +pub type UIntInRange = + UIntInRangeType, ConstUsize>; + +#[hdl] +pub type UIntInRangeInclusiveType = + uint_in_range::UIntInRangeInclusiveType; + +#[hdl] +pub type UIntInRangeInclusive = + UIntInRangeInclusiveType, ConstUsize>; + mod sealed { pub trait BoolOrIntTypeSealed {} pub trait SizeSealed {} @@ -49,6 +74,16 @@ pub trait KnownSize: + IntoIterator> + TryFrom>> + Into>>; + type ArraySimValue: AsRef<[SimValue]> + + AsMut<[SimValue]> + + BorrowMut<[SimValue]> + + 'static + + Clone + + std::fmt::Debug + + IntoIterator> + + TryFrom>> + + Into>> + + ToSimValueWithType>; } macro_rules! known_widths { @@ -60,6 +95,7 @@ macro_rules! known_widths { }> { const SIZE: Self = Self; type ArrayMatch = [Expr; Self::VALUE]; + type ArraySimValue = [SimValue; Self::VALUE]; } }; ([2 $($rest:tt)*] $($bits:literal)+) => { @@ -72,6 +108,7 @@ macro_rules! known_widths { impl KnownSize for ConstUsize<{2 $(* $rest)*}> { const SIZE: Self = Self; type ArrayMatch = [Expr; Self::VALUE]; + type ArraySimValue = [SimValue; Self::VALUE]; } }; } @@ -79,13 +116,31 @@ macro_rules! known_widths { known_widths!([2 2 2 2 2 2 2 2 2]); pub trait SizeType: - sealed::SizeTypeSealed + Copy + Ord + std::hash::Hash + std::fmt::Debug + Send + Sync + 'static + sealed::SizeTypeSealed + + Copy + + Ord + + std::hash::Hash + + std::fmt::Debug + + Send + + Sync + + 'static + + Serialize + + DeserializeOwned { type Size: Size; } pub trait Size: - sealed::SizeSealed + Copy + Ord + std::hash::Hash + std::fmt::Debug + Send + Sync + 'static + sealed::SizeSealed + + Copy + + Ord + + std::hash::Hash + + std::fmt::Debug + + Send + + Sync + + 'static + + Serialize + + DeserializeOwned { type ArrayMatch: AsRef<[Expr]> + AsMut<[Expr]> @@ -100,6 +155,16 @@ pub trait Size: + IntoIterator> + TryFrom>> + Into>>; + type ArraySimValue: AsRef<[SimValue]> + + AsMut<[SimValue]> + + BorrowMut<[SimValue]> + + 'static + + Clone + + std::fmt::Debug + + IntoIterator> + + TryFrom>> + + Into>> + + ToSimValueWithType>; const KNOWN_VALUE: Option; type SizeType: SizeType + Copy @@ -125,6 +190,7 @@ impl SizeType for usize { impl Size for DynSize { type ArrayMatch = Box<[Expr]>; + type ArraySimValue = Box<[SimValue]>; const KNOWN_VALUE: Option = None; type SizeType = usize; @@ -147,6 +213,7 @@ impl SizeType for T { impl Size for T { type ArrayMatch = ::ArrayMatch; + type ArraySimValue = ::ArraySimValue; const KNOWN_VALUE: Option = Some(T::VALUE); @@ -165,6 +232,305 @@ impl Size for T { } } +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum ParseIntValueError { + Empty, + InvalidDigit, + MissingDigits, + InvalidRadix, + MissingType, + InvalidType, + TypeMismatch { + parsed_signed: bool, + parsed_width: usize, + expected_signed: bool, + expected_width: usize, + }, + PosOverflow, + NegOverflow, + WidthOverflow, + MissingWidth, +} + +impl std::error::Error for ParseIntValueError {} + +impl fmt::Display for ParseIntValueError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::Empty => "can't parse integer from empty string", + Self::InvalidDigit => "invalid digit", + Self::MissingDigits => "missing digits", + Self::InvalidRadix => "invalid radix", + Self::MissingType => "missing type", + Self::InvalidType => "invalid type", + Self::TypeMismatch { + parsed_signed, + parsed_width, + expected_signed, + expected_width, + } => { + return write!( + f, + "type mismatch: parsed type {parsed_signed_str}{parsed_width}, \ + expected type {expected_signed_str}{expected_width}", + parsed_signed_str = if *parsed_signed { "i" } else { "u" }, + expected_signed_str = if *expected_signed { "i" } else { "u" }, + ); + } + Self::PosOverflow => "value too large to fit in type", + Self::NegOverflow => "value too small to fit in type", + Self::WidthOverflow => "width is too large", + Self::MissingWidth => "missing width", + }) + } +} + +fn parse_int_value( + s: &str, + type_is_signed: bool, + type_width: Option, + parse_type: bool, +) -> Result, ParseIntValueError> { + if !parse_type && type_width.is_none() { + return Err(ParseIntValueError::MissingWidth); + } + let mut s = s.trim(); + if s.is_empty() { + return Err(ParseIntValueError::Empty); + } + let negative = match s.bytes().next() { + Some(ch @ (b'+' | b'-')) => { + s = s[1..].trim_start(); + ch == b'-' + } + _ => false, + }; + let radix = match s.bytes().next() { + Some(b'0') => match s.bytes().nth(1) { + Some(b'x' | b'X') => { + s = &s[2..]; + 16 + } + Some(b'b' | b'B') => { + s = &s[2..]; + 2 + } + Some(b'o' | b'O') => { + s = &s[2..]; + 8 + } + _ => 10, + }, + Some(b'1'..=b'9') => 10, + _ => return Err(ParseIntValueError::InvalidDigit), + }; + let mut any_digits = false; + let digits_end = s + .as_bytes() + .iter() + .position(|&ch| { + if ch == b'_' { + false + } else if (ch as char).to_digit(radix).is_some() { + any_digits = true; + false + } else { + true + } + }) + .unwrap_or(s.len()); + let digits = &s[..digits_end]; + s = &s[digits_end..]; + if !any_digits { + return Err(ParseIntValueError::MissingDigits); + } + let width = if parse_type { + const HDL_PREFIX: &[u8] = b"hdl_"; + let mut missing_type = ParseIntValueError::MissingType; + if s.as_bytes() + .get(..HDL_PREFIX.len()) + .is_some_and(|bytes| bytes.eq_ignore_ascii_case(HDL_PREFIX)) + { + s = &s[HDL_PREFIX.len()..]; + missing_type = ParseIntValueError::InvalidType; + } + let signed = match s.bytes().next() { + Some(b'u' | b'U') => false, + Some(b'i' | b'I') => true, + Some(_) => return Err(ParseIntValueError::InvalidType), + None => return Err(missing_type), + }; + s = &s[1..]; + let mut width = 0usize; + let mut any_digits = false; + for ch in s.bytes() { + let digit = (ch as char) + .to_digit(10) + .ok_or(ParseIntValueError::InvalidDigit)?; + any_digits = true; + width = width + .checked_mul(10) + .and_then(|v| v.checked_add(digit as usize)) + .ok_or(ParseIntValueError::WidthOverflow)?; + } + if !any_digits { + return Err(ParseIntValueError::MissingDigits); + } + if width > ::MAX_BITS { + return Err(ParseIntValueError::WidthOverflow); + } + let expected_width = type_width.unwrap_or(width); + if type_is_signed != signed || expected_width != width { + let expected_width = type_width.unwrap_or(width); + return Err(ParseIntValueError::TypeMismatch { + parsed_signed: signed, + parsed_width: width, + expected_signed: type_is_signed, + expected_width, + }); + } + width + } else { + if !s.is_empty() { + return Err(ParseIntValueError::InvalidDigit); + } + type_width.expect("checked earlier") + }; + if !type_is_signed && negative { + return Err(ParseIntValueError::InvalidDigit); + } + if radix == 10 { + let mut value: BigInt = digits + .replace("_", "") + .parse() + .expect("checked that the digits are valid already"); + if negative { + value = -value; + } + let uint_value: UIntValue = UInt::new(width).from_bigint_wrapping(&value); + if value.is_zero() { + Ok(uint_value.into_bits()) + } else { + for i in 0..width { + value.set_bit(i as u64, type_is_signed && negative); + } + if value.is_zero() { + Ok(uint_value.into_bits()) + } else if type_is_signed && negative { + if value.sign() == Sign::Minus && value.magnitude().is_one() { + Ok(uint_value.into_bits()) + } else { + Err(ParseIntValueError::NegOverflow) + } + } else { + Err(ParseIntValueError::PosOverflow) + } + } + } else { + let mut value = BitVec::repeat(false, width); + let bits_per_digit = match radix { + 2 => 1, + 8 => 3, + 16 => 4, + _ => unreachable!(), + }; + let mut digits = digits + .bytes() + .rev() + .filter_map(|ch| (ch as char).to_digit(radix)); + let overflow_error = if negative { + ParseIntValueError::NegOverflow + } else { + ParseIntValueError::PosOverflow + }; + for chunk in value.chunks_mut(bits_per_digit) { + if let Some(mut digit) = digits.next() { + let digit_bits = &mut digit.view_bits_mut::()[..chunk.len()]; + chunk.clone_from_bitslice(digit_bits); + digit_bits.fill(false); + if digit != 0 { + return Err(overflow_error); + } + } else { + break; + } + } + for digit in digits { + if digit != 0 { + return Err(overflow_error); + } + } + let negative_zero = if negative { + // negating a value happens in three regions: + // * the least-significant zeros, which are left as zeros + // * the least-significant one bit, which is left as a one bit + // * all the most-significant bits, which are inverted + // e.g.: + const { + let inp = 0b1010_1_000_u8; + let out = 0b0101_1_000_u8; + assert!(inp.wrapping_neg() == out); + }; + if let Some(first_one) = value.first_one() { + let most_significant_bits = &mut value[first_one + 1..]; + // modifies in-place despite using `Not::not` + let _ = Not::not(most_significant_bits); + false + } else { + true + } + } else { + false + }; + if !negative_zero && type_is_signed && negative != value[value.len() - 1] { + Err(overflow_error) + } else { + Ok(Arc::new(value)) + } + } +} + +fn deserialize_int_value<'de, D: Deserializer<'de>>( + deserializer: D, + type_is_signed: bool, + type_width: Option, +) -> Result, D::Error> { + struct IntValueVisitor { + type_is_signed: bool, + type_width: Option, + } + impl<'de> Visitor<'de> for IntValueVisitor { + type Value = Arc; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(if self.type_is_signed { + "SIntValue" + } else { + "UIntValue" + })?; + if let Some(type_width) = self.type_width { + write!(f, "<{type_width}>")?; + } + Ok(()) + } + + fn visit_str(self, v: &str) -> Result { + parse_int_value(v, self.type_is_signed, self.type_width, true).map_err(E::custom) + } + + fn visit_bytes(self, v: &[u8]) -> Result { + match std::str::from_utf8(v) { + Ok(v) => self.visit_str(v), + Err(_) => Err(Error::invalid_value(serde::de::Unexpected::Bytes(v), &self)), + } + } + } + deserializer.deserialize_str(IntValueVisitor { + type_is_signed, + type_width, + }) +} + macro_rules! impl_int { ($pretty_name:ident, $name:ident, $generic_name:ident, $value:ident, $SIGNED:literal) => { #[derive(Copy, Clone, PartialEq, Eq, Hash)] @@ -188,31 +554,26 @@ macro_rules! impl_int { pub const $name: $generic_name = $generic_name; impl $name { - pub fn new(width: Width::SizeType) -> Self { + pub const fn new(width: Width::SizeType) -> Self { Self { width } } pub fn width(self) -> usize { Width::as_usize(self.width) } pub fn type_properties(self) -> TypeProperties { - TypeProperties { - is_passive: true, - is_storable: true, - is_castable_from_bits: true, - bit_width: self.width(), - } + self.as_dyn_int().type_properties_dyn() } - pub fn bits_from_bigint_wrapping(self, v: BigInt) -> BitVec { + pub fn bits_from_bigint_wrapping(self, v: &BigInt) -> BitVec { BoolOrIntType::bits_from_bigint_wrapping(self, v) } - pub fn from_bigint_wrapping(self, v: BigInt) -> $value { + pub fn from_bigint_wrapping(self, v: &BigInt) -> $value { $value { bits: Arc::new(self.bits_from_bigint_wrapping(v)), _phantom: PhantomData, } } pub fn from_int_wrapping(self, v: impl Into) -> $value { - self.from_bigint_wrapping(v.into()) + self.from_bigint_wrapping(&v.into()) } pub fn zero(self) -> $value { self.from_int_wrapping(0u8) @@ -227,12 +588,29 @@ macro_rules! impl_int { impl BoolOrIntType for $name { type Width = Width; type Signed = ConstBool<$SIGNED>; + type Value = $value; fn width(self) -> usize { $name::width(self) } fn new(width: Width::SizeType) -> Self { $name { width } } + fn value_from_bigint_wrapping(self, v: &BigInt) -> Self::Value { + $value::::from_bigint_wrapping(self, v) + } + fn bits_to_value(bits: Cow<'_, BitSlice>) -> Self::Value { + #[derive(Copy, Clone, Eq, PartialEq, Hash)] + struct MemoizeBitsToValue; + impl Memoize for MemoizeBitsToValue { + type Input = BitSlice; + type InputOwned = BitVec; + type Output = Arc; + fn inner(self, input: &Self::Input) -> Self::Output { + Arc::new(input.to_bitvec()) + } + } + $value::new(MemoizeBitsToValue.get_cow(bits)) + } fn bits_to_expr(bits: Cow<'_, BitSlice>) -> Expr { #[derive(Copy, Clone, Eq, PartialEq, Hash)] struct MemoizeBitsToExpr; @@ -246,6 +624,12 @@ macro_rules! impl_int { } Expr::from_dyn_int(MemoizeBitsToExpr.get_cow(bits)) } + fn from_str_without_ty( + self, + s: &str, + ) -> Result::Err> { + parse_int_value(s, $SIGNED, Some(self.width()), false).map(Self::Value::new) + } } impl IntType for $name { @@ -253,12 +637,20 @@ macro_rules! impl_int { } impl $name { - pub fn new_dyn(width: usize) -> Self { + pub const fn new_dyn(width: usize) -> Self { Self { width } } pub fn bits_to_bigint(bits: &BitSlice) -> BigInt { ::bits_to_bigint(bits) } + pub const fn type_properties_dyn(self) -> TypeProperties { + TypeProperties { + is_passive: true, + is_storable: true, + is_castable_from_bits: true, + bit_width: self.width, + } + } } impl $name { @@ -270,6 +662,7 @@ macro_rules! impl_int { impl Type for $name { type BaseType = $pretty_name; type MaskType = Bool; + type SimValue = $value; impl_match_variant_as_self!(); fn mask_type(&self) -> Self::MaskType { Bool @@ -280,7 +673,7 @@ macro_rules! impl_int { #[track_caller] fn from_canonical(canonical_type: CanonicalType) -> Self { let CanonicalType::$pretty_name(retval) = canonical_type else { - panic!("expected {}", stringify!($name)); + panic!("expected {}", stringify!($pretty_name)); }; $name { width: Width::from_usize(retval.width), @@ -289,20 +682,78 @@ macro_rules! impl_int { fn source_location() -> SourceLocation { SourceLocation::builtin() } + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + assert_eq!(bits.len(), self.width()); + $value::new(Arc::new(bits.to_bitvec())) + } + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + assert_eq!(bits.len(), self.width()); + assert_eq!(value.width(), self.width()); + value.bits_mut().copy_from_bitslice(bits); + } + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + assert_eq!(bits.len(), self.width()); + assert_eq!(value.width(), self.width()); + bits.copy_from_bitslice(value.bits()); + } + } + + impl Default for $name { + fn default() -> Self { + Self::TYPE + } } impl StaticType for $name { const TYPE: Self = Self { width: Width::SIZE }; const MASK_TYPE: Self::MaskType = Bool; - const TYPE_PROPERTIES: TypeProperties = TypeProperties { - is_passive: true, - is_storable: true, - is_castable_from_bits: true, - bit_width: Width::VALUE, - }; + const TYPE_PROPERTIES: TypeProperties = $name { + width: Width::VALUE, + } + .type_properties_dyn(); const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES; } + impl Serialize for $name { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.canonical().serialize(serializer) + } + } + + impl<'de, Width: Size> Deserialize<'de> for $name { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let name = |width| -> String { + if let Some(width) = width { + format!("a {}<{width}>", stringify!($pretty_name)) + } else { + format!("a {}", stringify!($pretty_name)) + } + }; + match CanonicalType::deserialize(deserializer)? { + CanonicalType::$pretty_name(retval) => { + if let Some(width) = Width::try_from_usize(retval.width()) { + Ok($name { width }) + } else { + Err(Error::invalid_value( + serde::de::Unexpected::Other(&name(Some(retval.width()))), + &&*name(Width::KNOWN_VALUE), + )) + } + } + ty => Err(Error::invalid_value( + serde::de::Unexpected::Other(ty.as_serde_unexpected_str()), + &&*name(Width::KNOWN_VALUE), + )), + } + } + } + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] pub struct $generic_name; @@ -320,7 +771,7 @@ macro_rules! impl_int { _phantom: PhantomData, } - impl fmt::Debug for $value { + impl fmt::Display for $value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let value = self.to_bigint(); let (sign, magnitude) = value.into_parts(); @@ -334,6 +785,47 @@ macro_rules! impl_int { } } + impl fmt::Debug for $value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } + } + + impl std::str::FromStr for $value { + type Err = ParseIntValueError; + + fn from_str(s: &str) -> Result { + parse_int_value(s, $SIGNED, Width::KNOWN_VALUE, true).map(Self::new) + } + } + + impl Serialize for $value { + fn serialize(&self, serializer: S) -> Result { + self.to_string().serialize(serializer) + } + } + + impl<'de, Width: Size> Deserialize<'de> for $value { + fn deserialize>(deserializer: D) -> Result { + deserialize_int_value(deserializer, $SIGNED, Width::KNOWN_VALUE).map(Self::new) + } + } + + impl PartialOrd for $value { + fn partial_cmp(&self, other: &Self) -> Option { + if self.width() != other.width() { + return None; + } + Some(self.to_bigint().cmp(&other.to_bigint())) + } + } + + impl From<$value> for BigInt { + fn from(v: $value) -> BigInt { + v.to_bigint() + } + } + impl $value { pub fn width(&self) -> usize { if let Some(retval) = Width::KNOWN_VALUE { @@ -343,7 +835,7 @@ macro_rules! impl_int { self.bits.len() } } - pub fn from_bigint_wrapping(ty: $name, v: BigInt) -> $value { + pub fn from_bigint_wrapping(ty: $name, v: &BigInt) -> $value { ty.from_bigint_wrapping(v) } pub fn to_bigint(&self) -> BigInt { @@ -366,6 +858,9 @@ macro_rules! impl_int { pub fn bits(&self) -> &Arc { &self.bits } + pub fn bits_mut(&mut self) -> &mut BitSlice { + Arc::::make_mut(&mut self.bits) + } } impl ToLiteralBits for $value { @@ -420,6 +915,10 @@ impl UInt { let v: BigUint = v.into(); Self::new(v.bits().try_into().expect("too big")) } + /// gets the smallest `UInt` that fits `v` losslessly + pub const fn for_value_usize(v: usize) -> Self { + Self::new((usize::BITS - v.leading_zeros()) as usize) + } /// gets the smallest `UInt` that fits `r` losslessly, panics if `r` is empty #[track_caller] pub fn range(r: Range>) -> Self { @@ -430,6 +929,12 @@ impl UInt { } /// gets the smallest `UInt` that fits `r` losslessly, panics if `r` is empty #[track_caller] + pub const fn range_usize(r: Range) -> Self { + assert!(r.end != 0, "empty range"); + Self::range_inclusive_usize(r.start..=(r.end - 1)) + } + /// gets the smallest `UInt` that fits `r` losslessly, panics if `r` is empty + #[track_caller] pub fn range_inclusive(r: RangeInclusive>) -> Self { let (start, end) = r.into_inner(); let start: BigUint = start.into(); @@ -439,6 +944,16 @@ impl UInt { // so must not take more bits than `end` Self::for_value(end) } + /// gets the smallest `UInt` that fits `r` losslessly, panics if `r` is empty + #[track_caller] + pub const fn range_inclusive_usize(r: RangeInclusive) -> Self { + let start = *r.start(); + let end = *r.end(); + assert!(start <= end, "empty range"); + // no need to check `start`` since it's no larger than `end` + // so must not take more bits than `end` + Self::for_value_usize(end) + } } impl SInt { @@ -453,7 +968,10 @@ impl SInt { v.not().bits().checked_add(1).expect("too big") } Sign::NoSign => 0, - Sign::Plus => v.bits(), + Sign::Plus => { + // account for sign bit + v.bits().checked_add(1).expect("too big") + } } .try_into() .expect("too big"), @@ -482,6 +1000,19 @@ macro_rules! impl_prim_int { $(#[$meta:meta])* $prim_int:ident, $ty:ty ) => { + impl From<$prim_int> for <$ty as BoolOrIntType>::Value { + fn from(v: $prim_int) -> Self { + <$ty>::le_bytes_to_value_wrapping( + &v.to_le_bytes(), + <$ty as BoolOrIntType>::Width::VALUE, + ) + } + } + impl From> for <$ty as BoolOrIntType>::Value { + fn from(v: NonZero<$prim_int>) -> Self { + v.get().into() + } + } $(#[$meta])* impl ToExpr for $prim_int { type Type = $ty; @@ -498,10 +1029,7 @@ macro_rules! impl_prim_int { type Type = $ty; fn to_expr(&self) -> Expr { - <$ty>::le_bytes_to_expr_wrapping( - &self.get().to_le_bytes(), - <$ty as BoolOrIntType>::Width::VALUE, - ) + self.get().to_expr() } } }; @@ -519,18 +1047,30 @@ impl_prim_int!(i64, SInt<64>); impl_prim_int!(i128, SInt<128>); impl_prim_int!( - /// for portability reasons, [`usize`] always translates to [`UInt<64>`] + /// for portability reasons, [`usize`] always translates to [`UInt<64>`][type@UInt] usize, UInt<64> ); impl_prim_int!( - /// for portability reasons, [`isize`] always translates to [`SInt<64>`] + /// for portability reasons, [`isize`] always translates to [`SInt<64>`][type@SInt] isize, SInt<64> ); pub trait BoolOrIntType: Type + sealed::BoolOrIntTypeSealed { type Width: Size; type Signed: GenericConstBool; + type Value: Clone + + PartialOrd + + Eq + + std::hash::Hash + + fmt::Debug + + fmt::Display + + Send + + Sync + + 'static + + ToExpr + + Into + + std::str::FromStr; fn width(self) -> usize; fn new(width: ::SizeType) -> Self; fn new_static() -> Self @@ -545,17 +1085,30 @@ pub trait BoolOrIntType: Type + sealed::BoolOrIntTypeSealed { fn as_same_width_uint(self) -> UIntType { UIntType::new(Self::Width::from_usize(self.width())) } - fn bits_from_bigint_wrapping(self, v: BigInt) -> BitVec { - let width = self.width(); + fn value_from_int_wrapping(self, v: impl Into) -> Self::Value { + self.value_from_bigint_wrapping(&v.into()) + } + fn value_from_bigint_wrapping(self, v: &BigInt) -> Self::Value; + fn bits_from_bigint_wrapping(self, v: &BigInt) -> BitVec { + let mut bits = BitVec::repeat(false, self.width()); + Self::copy_bits_from_bigint_wrapping(v, &mut bits); + bits + } + fn copy_bits_from_bigint_wrapping(v: &BigInt, bits: &mut BitSlice) { + let width = bits.len(); let mut bytes = v.to_signed_bytes_le(); bytes.resize( width.div_ceil(u8::BITS as usize), if v.is_negative() { 0xFF } else { 0 }, ); let bitslice = &BitSlice::::from_slice(&bytes)[..width]; - let mut bits = BitVec::new(); - bits.extend_from_bitslice(bitslice); - bits + bits.clone_from_bitslice(bitslice); + } + fn bits_equal_bigint_wrapping(v: &BigInt, bits: &BitSlice) -> bool { + bits.iter() + .by_vals() + .enumerate() + .all(|(bit_index, bit): (usize, bool)| v.bit(bit_index as u64) == bit) } fn bits_to_bigint(bits: &BitSlice) -> BigInt { let sign_byte = if Self::Signed::VALUE && bits.last().as_deref().copied().unwrap_or(false) { @@ -567,8 +1120,9 @@ pub trait BoolOrIntType: Type + sealed::BoolOrIntTypeSealed { BitSlice::::from_slice_mut(&mut bytes)[..bits.len()].clone_from_bitslice(bits); BigInt::from_signed_bytes_le(&bytes) } + fn bits_to_value(bits: Cow<'_, BitSlice>) -> Self::Value; fn bits_to_expr(bits: Cow<'_, BitSlice>) -> Expr; - fn le_bytes_to_expr_wrapping(bytes: &[u8], bit_width: usize) -> Expr { + fn le_bytes_to_bits_wrapping(bytes: &[u8], bit_width: usize) -> BitVec { let bitslice = BitSlice::::from_slice(bytes); let bitslice = &bitslice[..bit_width.min(bitslice.len())]; let mut bits = BitVec::new(); @@ -577,11 +1131,24 @@ pub trait BoolOrIntType: Type + sealed::BoolOrIntTypeSealed { bit_width, Self::Signed::VALUE && bits.last().as_deref().copied().unwrap_or(false), ); - Self::bits_to_expr(Cow::Owned(bits)) + bits } + fn le_bytes_to_expr_wrapping(bytes: &[u8], bit_width: usize) -> Expr { + Self::bits_to_expr(Cow::Owned(Self::le_bytes_to_bits_wrapping( + bytes, bit_width, + ))) + } + fn le_bytes_to_value_wrapping(bytes: &[u8], bit_width: usize) -> Self::Value { + Self::bits_to_value(Cow::Owned(Self::le_bytes_to_bits_wrapping( + bytes, bit_width, + ))) + } + fn from_str_without_ty(self, s: &str) -> Result::Err>; } -pub trait IntType: BoolOrIntType::Dyn> { +pub trait IntType: + BoolOrIntType::Dyn, Value: FromStr> +{ type Dyn: IntType; fn as_dyn_int(self) -> Self::Dyn { Self::new_dyn(self.width()) @@ -621,7 +1188,7 @@ pub trait IntType: BoolOrIntType::Dyn> { } } -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] pub struct Bool; impl sealed::BoolOrIntTypeSealed for Bool {} @@ -629,6 +1196,7 @@ impl sealed::BoolOrIntTypeSealed for Bool {} impl BoolOrIntType for Bool { type Width = ConstUsize<1>; type Signed = ConstBool; + type Value = bool; fn width(self) -> usize { 1 @@ -639,10 +1207,23 @@ impl BoolOrIntType for Bool { Bool } + fn value_from_bigint_wrapping(self, v: &BigInt) -> Self::Value { + v.bit(0) + } + fn bits_to_expr(bits: Cow<'_, BitSlice>) -> Expr { assert_eq!(bits.len(), 1); bits[0].to_expr() } + + fn bits_to_value(bits: Cow<'_, BitSlice>) -> Self::Value { + assert_eq!(bits.len(), 1); + bits[0] + } + + fn from_str_without_ty(self, s: &str) -> Result::Err> { + FromStr::from_str(s) + } } impl Bool { @@ -657,6 +1238,7 @@ impl Bool { impl Type for Bool { type BaseType = Bool; type MaskType = Bool; + type SimValue = bool; impl_match_variant_as_self!(); fn mask_type(&self) -> Self::MaskType { Bool @@ -674,6 +1256,18 @@ impl Type for Bool { fn source_location() -> SourceLocation { SourceLocation::builtin() } + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + assert_eq!(bits.len(), 1); + bits[0] + } + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + assert_eq!(bits.len(), 1); + *value = bits[0]; + } + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + assert_eq!(bits.len(), 1); + bits.set(0, *value); + } } impl StaticType for Bool { @@ -693,3 +1287,138 @@ impl ToLiteralBits for bool { Ok(interned_bit(*self)) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_different_value_widths_compare_ne() { + // interning relies on [SU]IntValue with different `width` comparing not equal + assert_ne!(UInt[3].from_int_wrapping(0), UInt[4].from_int_wrapping(0)); + assert_ne!(SInt[3].from_int_wrapping(0), SInt[4].from_int_wrapping(0)); + } + + #[test] + fn test_uint_for_value() { + assert_eq!(UInt::for_value(0u8).width, 0); + assert_eq!(UInt::for_value(1u8).width, 1); + assert_eq!(UInt::for_value(2u8).width, 2); + assert_eq!(UInt::for_value(3u8).width, 2); + assert_eq!(UInt::for_value(4u8).width, 3); + } + + #[test] + fn test_sint_for_value() { + assert_eq!(SInt::for_value(-5).width, 4); + assert_eq!(SInt::for_value(-4).width, 3); + assert_eq!(SInt::for_value(-3).width, 3); + assert_eq!(SInt::for_value(-2).width, 2); + assert_eq!(SInt::for_value(-1).width, 1); + assert_eq!(SInt::for_value(0).width, 0); + assert_eq!(SInt::for_value(1).width, 2); + assert_eq!(SInt::for_value(2).width, 3); + assert_eq!(SInt::for_value(3).width, 3); + assert_eq!(SInt::for_value(4).width, 4); + } + + #[test] + fn test_serde_round_trip() { + use serde_json::json; + #[track_caller] + fn check( + value: T, + expected: serde_json::Value, + ) { + assert_eq!(serde_json::to_value(&value).unwrap(), expected); + assert_eq!(value, T::deserialize(expected).unwrap()); + } + check(UInt[0], json! { { "UInt": { "width": 0 } } }); + check(UInt::<0>::TYPE, json! { { "UInt": { "width": 0 } } }); + check(UInt::<35>::TYPE, json! { { "UInt": { "width": 35 } } }); + check(SInt[0], json! { { "SInt": { "width": 0 } } }); + check(SInt::<0>::TYPE, json! { { "SInt": { "width": 0 } } }); + check(SInt::<35>::TYPE, json! { { "SInt": { "width": 35 } } }); + check(Bool, json! { "Bool" }); + check(UIntValue::from(0u8), json! { "0x0_u8" }); + check(SIntValue::from(-128i8), json! { "-0x80_i8" }); + check(UInt[3].from_int_wrapping(5), json! { "0x5_u3" }); + check(UInt[12].from_int_wrapping(0x1123), json! { "0x123_u12" }); + check(SInt[12].from_int_wrapping(0xFEE), json! { "-0x12_i12" }); + check(SInt[12].from_int_wrapping(0x7EE), json! { "0x7EE_i12" }); + } + + #[test] + fn test_deserialize() { + use serde_json::json; + #[track_caller] + fn check( + expected: Result, + input: serde_json::Value, + ) { + let mut error = String::new(); + let value = T::deserialize(input).map_err(|e| -> &str { + error = e.to_string(); + &error + }); + assert_eq!(value, expected); + } + check::>( + Err("invalid value: a UInt<2>, expected a UInt<0>"), + json! { { "UInt": { "width": 2 } } }, + ); + check::>( + Err("invalid value: a Bool, expected a UInt<0>"), + json! { "Bool" }, + ); + check::>( + Err("invalid value: a Bool, expected a SInt<0>"), + json! { "Bool" }, + ); + check::( + Err("invalid value: a Bool, expected a UInt"), + json! { "Bool" }, + ); + check::( + Err("invalid value: a Bool, expected a SInt"), + json! { "Bool" }, + ); + check::(Err("value too large to fit in type"), json! { "2_u1" }); + check::(Err("value too large to fit in type"), json! { "10_u1" }); + check::(Err("value too large to fit in type"), json! { "0x2_u1" }); + check::(Err("value too large to fit in type"), json! { "0b10_u1" }); + check::(Err("value too large to fit in type"), json! { "0o2_u1" }); + check::(Err("value too large to fit in type"), json! { "0o377_i8" }); + check::(Err("value too large to fit in type"), json! { "0o200_i8" }); + check(Ok(SInt[8].from_int_wrapping(i8::MAX)), json! { "0o177_i8" }); + check::(Err("value too small to fit in type"), json! { "-0o201_i8" }); + check::(Err("value too small to fit in type"), json! { "-0o377_i8" }); + check::(Err("value too small to fit in type"), json! { "-0o400_i8" }); + check::( + Err("value too small to fit in type"), + json! { "-0o4000_i8" }, + ); + check(Ok(UIntValue::from(0u8)), json! { "0_u8" }); + check(Ok(UIntValue::from(0u8)), json! { "0b0_u8" }); + check(Ok(UIntValue::from(0u8)), json! { "00_u8" }); + check(Ok(UIntValue::from(0u8)), json! { "0x0_u8" }); + check(Ok(UIntValue::from(0u8)), json! { "0o0_u8" }); + check(Ok(SIntValue::from(-128i8)), json! { "-0x000_80_i8" }); + check(Ok(SIntValue::from(-128i8)), json! { "-0o002_00_hdl_i8" }); + check(Ok(SIntValue::from(-128i8)), json! { "-0b1__000_0000_i8" }); + check(Ok(UInt[3].from_int_wrapping(5)), json! { " + 0x5_u3 " }); + check( + Ok(UInt[12].from_int_wrapping(0x1123)), + json! { "0x1_2_3_hdl_u12" }, + ); + check(Ok(SInt[12].from_int_wrapping(0xFEE)), json! { "-0x12_i12" }); + check( + Ok(SInt[12].from_int_wrapping(0x7EE)), + json! { " + \t0x7__E_e_i012\n" }, + ); + check(Ok(SInt[0].from_int_wrapping(0)), json! { "-0i0" }); + check(Ok(SInt[1].from_int_wrapping(0)), json! { "-0i1" }); + check(Ok(SInt[0].from_int_wrapping(0)), json! { "-0x0i0" }); + check(Ok(SInt[1].from_int_wrapping(0)), json! { "-0x0i1" }); + } +} diff --git a/crates/fayalite/src/int/uint_in_range.rs b/crates/fayalite/src/int/uint_in_range.rs new file mode 100644 index 0000000..0e2d07e --- /dev/null +++ b/crates/fayalite/src/int/uint_in_range.rs @@ -0,0 +1,614 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + bundle::{Bundle, BundleField, BundleType, BundleTypePropertiesBuilder, NoBuilder}, + expr::{ + ops::{ExprCastTo, ExprPartialEq, ExprPartialOrd}, + CastBitsTo, CastTo, CastToBits, Expr, HdlPartialEq, HdlPartialOrd, + }, + int::{Bool, DynSize, KnownSize, Size, SizeType, UInt, UIntType}, + intern::{Intern, Interned}, + phantom_const::PhantomConst, + sim::value::{SimValue, SimValuePartialEq, ToSimValueWithType}, + source_location::SourceLocation, + ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, +}; +use bitvec::{order::Lsb0, slice::BitSlice, view::BitView}; +use serde::{ + de::{value::UsizeDeserializer, Error, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, +}; +use std::{fmt, marker::PhantomData, ops::Index}; + +const UINT_IN_RANGE_TYPE_FIELD_NAMES: [&'static str; 2] = ["value", "range"]; + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] +pub struct UIntInRangeMaskType { + value: Bool, + range: PhantomConstRangeMaskType, +} + +impl Type for UIntInRangeMaskType { + type BaseType = Bundle; + type MaskType = Self; + type SimValue = bool; + impl_match_variant_as_self!(); + + fn mask_type(&self) -> Self::MaskType { + *self + } + + fn canonical(&self) -> CanonicalType { + CanonicalType::Bundle(Bundle::new(self.fields())) + } + + fn from_canonical(canonical_type: CanonicalType) -> Self { + let fields = Bundle::from_canonical(canonical_type).fields(); + let [BundleField { + name: value_name, + flipped: false, + ty: value, + }, BundleField { + name: range_name, + flipped: false, + ty: range, + }] = *fields + else { + panic!("expected UIntInRangeMaskType"); + }; + assert_eq!([&*value_name, &*range_name], UINT_IN_RANGE_TYPE_FIELD_NAMES); + let value = Bool::from_canonical(value); + let range = PhantomConstRangeMaskType::from_canonical(range); + Self { value, range } + } + + fn source_location() -> SourceLocation { + SourceLocation::builtin() + } + + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + Bool.sim_value_from_bits(bits) + } + + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + Bool.sim_value_clone_from_bits(value, bits); + } + + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + Bool.sim_value_to_bits(value, bits); + } +} + +impl BundleType for UIntInRangeMaskType { + type Builder = NoBuilder; + type FilledBuilder = Expr; + + fn fields(&self) -> Interned<[BundleField]> { + let [value_name, range_name] = UINT_IN_RANGE_TYPE_FIELD_NAMES; + let Self { value, range } = self; + [ + BundleField { + name: value_name.intern(), + flipped: false, + ty: value.canonical(), + }, + BundleField { + name: range_name.intern(), + flipped: false, + ty: range.canonical(), + }, + ][..] + .intern() + } +} + +impl StaticType for UIntInRangeMaskType { + const TYPE: Self = Self { + value: Bool, + range: PhantomConstRangeMaskType::TYPE, + }; + const MASK_TYPE: Self::MaskType = Self::TYPE; + const TYPE_PROPERTIES: TypeProperties = BundleTypePropertiesBuilder::new() + .field(false, Bool::TYPE_PROPERTIES) + .field(false, PhantomConstRangeMaskType::TYPE_PROPERTIES) + .finish(); + const MASK_TYPE_PROPERTIES: TypeProperties = Self::TYPE_PROPERTIES; +} + +impl ToSimValueWithType for bool { + fn to_sim_value_with_type(&self, ty: UIntInRangeMaskType) -> SimValue { + SimValue::from_value(ty, *self) + } +} + +impl ExprCastTo for UIntInRangeMaskType { + fn cast_to(src: Expr, to_type: Bool) -> Expr { + src.cast_to_bits().cast_to(to_type) + } +} + +impl ExprCastTo for Bool { + fn cast_to(src: Expr, to_type: UIntInRangeMaskType) -> Expr { + src.cast_to_static::>().cast_bits_to(to_type) + } +} + +impl ExprPartialEq for UIntInRangeMaskType { + fn cmp_eq(lhs: Expr, rhs: Expr) -> Expr { + lhs.cast_to_bits().cmp_eq(rhs.cast_to_bits()) + } + fn cmp_ne(lhs: Expr, rhs: Expr) -> Expr { + lhs.cast_to_bits().cmp_ne(rhs.cast_to_bits()) + } +} + +impl SimValuePartialEq for UIntInRangeMaskType { + fn sim_value_eq(this: &SimValue, other: &SimValue) -> bool { + **this == **other + } +} + +type PhantomConstRangeMaskType = > as Type>::MaskType; + +#[derive(Default, Copy, Clone, Debug)] +struct RangeParseError; + +macro_rules! define_uint_in_range_type { + ( + $UIntInRange:ident, + $UIntInRangeType:ident, + $UIntInRangeTypeWithoutGenerics:ident, + $UIntInRangeTypeWithStart:ident, + $SerdeRange:ident, + $range_operator_str:literal, + |$uint_range_usize_start:ident, $uint_range_usize_end:ident| $uint_range_usize:expr, + ) => { + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + struct $SerdeRange { + start: Start::SizeType, + end: End::SizeType, + } + + impl Default for $SerdeRange { + fn default() -> Self { + Self { + start: Start::SIZE, + end: End::SIZE, + } + } + } + + impl std::str::FromStr for $SerdeRange { + type Err = RangeParseError; + + fn from_str(s: &str) -> Result { + let Some((start, end)) = s.split_once($range_operator_str) else { + return Err(RangeParseError); + }; + if start.is_empty() + || start.bytes().any(|b| !b.is_ascii_digit()) + || end.is_empty() + || end.bytes().any(|b| !b.is_ascii_digit()) + { + return Err(RangeParseError); + } + let start = start.parse().map_err(|_| RangeParseError)?; + let end = end.parse().map_err(|_| RangeParseError)?; + let retval = Self { start, end }; + if retval.is_empty() { + Err(RangeParseError) + } else { + Ok(retval) + } + } + } + + impl fmt::Display for $SerdeRange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { start, end } = *self; + write!( + f, + "{}{}{}", + Start::as_usize(start), + $range_operator_str, + End::as_usize(end), + ) + } + } + + impl Serialize for $SerdeRange { + fn serialize(&self, serializer: S) -> Result { + serializer.collect_str(self) + } + } + + impl<'de, Start: Size, End: Size> Deserialize<'de> for $SerdeRange { + fn deserialize>(deserializer: D) -> Result { + struct SerdeRangeVisitor(PhantomData<(Start, End)>); + impl<'de, Start: Size, End: Size> Visitor<'de> for SerdeRangeVisitor { + type Value = $SerdeRange; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("a string with format \"")?; + if let Some(start) = Start::KNOWN_VALUE { + write!(f, "{start}")?; + } else { + f.write_str("")?; + }; + f.write_str($range_operator_str)?; + if let Some(end) = End::KNOWN_VALUE { + write!(f, "{end}")?; + } else { + f.write_str("")?; + }; + f.write_str("\" that is a non-empty range") + } + + fn visit_str(self, v: &str) -> Result { + let $SerdeRange:: { start, end } = + v.parse().map_err(|_| { + Error::invalid_value(serde::de::Unexpected::Str(v), &self) + })?; + let start = + Start::SizeType::deserialize(UsizeDeserializer::::new(start))?; + let end = End::SizeType::deserialize(UsizeDeserializer::::new(end))?; + Ok($SerdeRange { start, end }) + } + + fn visit_bytes(self, v: &[u8]) -> Result { + match std::str::from_utf8(v) { + Ok(v) => self.visit_str(v), + Err(_) => { + Err(Error::invalid_value(serde::de::Unexpected::Bytes(v), &self)) + } + } + } + } + deserializer.deserialize_str(SerdeRangeVisitor(PhantomData)) + } + } + + #[derive(Copy, Clone, PartialEq, Eq, Hash)] + pub struct $UIntInRangeType { + value: UInt, + range: PhantomConst<$SerdeRange>, + } + + impl $UIntInRangeType { + fn from_phantom_const_range(range: PhantomConst<$SerdeRange>) -> Self { + let $SerdeRange { start, end } = *range.get(); + let $uint_range_usize_start = Start::as_usize(start); + let $uint_range_usize_end = End::as_usize(end); + Self { + value: $uint_range_usize, + range, + } + } + pub fn new(start: Start::SizeType, end: End::SizeType) -> Self { + Self::from_phantom_const_range(PhantomConst::new( + $SerdeRange { start, end }.intern_sized(), + )) + } + } + + impl fmt::Debug for $UIntInRangeType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { value, range } = self; + let $SerdeRange { start, end } = *range.get(); + f.debug_struct(&format!( + "{}<{}, {}>", + stringify!($UIntInRange), + Start::as_usize(start), + End::as_usize(end), + )) + .field("value", value) + .finish_non_exhaustive() + } + } + + impl Type for $UIntInRangeType { + type BaseType = Bundle; + type MaskType = UIntInRangeMaskType; + type SimValue = usize; + impl_match_variant_as_self!(); + + fn mask_type(&self) -> Self::MaskType { + UIntInRangeMaskType::TYPE + } + + fn canonical(&self) -> CanonicalType { + CanonicalType::Bundle(Bundle::new(self.fields())) + } + + fn from_canonical(canonical_type: CanonicalType) -> Self { + let fields = Bundle::from_canonical(canonical_type).fields(); + let [BundleField { + name: value_name, + flipped: false, + ty: value, + }, BundleField { + name: range_name, + flipped: false, + ty: range, + }] = *fields + else { + panic!("expected {}", stringify!($UIntInRange)); + }; + assert_eq!([&*value_name, &*range_name], UINT_IN_RANGE_TYPE_FIELD_NAMES); + let value = UInt::from_canonical(value); + let range = PhantomConst::<$SerdeRange>::from_canonical(range); + let retval = Self::from_phantom_const_range(range); + assert_eq!(retval, Self { value, range }); + retval + } + + fn source_location() -> SourceLocation { + SourceLocation::builtin() + } + + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + let mut retval = 0usize; + retval.view_bits_mut::()[..bits.len()].clone_from_bitslice(bits); + retval + } + + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + *value = self.sim_value_from_bits(bits); + } + + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + bits.clone_from_bitslice(&value.view_bits::()[..bits.len()]); + } + } + + impl BundleType for $UIntInRangeType { + type Builder = NoBuilder; + type FilledBuilder = Expr; + + fn fields(&self) -> Interned<[BundleField]> { + let [value_name, range_name] = UINT_IN_RANGE_TYPE_FIELD_NAMES; + let Self { value, range } = self; + [ + BundleField { + name: value_name.intern(), + flipped: false, + ty: value.canonical(), + }, + BundleField { + name: range_name.intern(), + flipped: false, + ty: range.canonical(), + }, + ][..] + .intern() + } + } + + impl Default for $UIntInRangeType { + fn default() -> Self { + Self::TYPE + } + } + + impl StaticType for $UIntInRangeType { + const TYPE: Self = { + let $uint_range_usize_start = Start::VALUE; + let $uint_range_usize_end = End::VALUE; + Self { + value: $uint_range_usize, + range: PhantomConst::<$SerdeRange>::TYPE, + } + }; + const MASK_TYPE: Self::MaskType = UIntInRangeMaskType::TYPE; + const TYPE_PROPERTIES: TypeProperties = BundleTypePropertiesBuilder::new() + .field(false, Self::TYPE.value.type_properties_dyn()) + .field( + false, + PhantomConst::<$SerdeRange>::TYPE_PROPERTIES, + ) + .finish(); + const MASK_TYPE_PROPERTIES: TypeProperties = UIntInRangeMaskType::TYPE_PROPERTIES; + } + + impl ToSimValueWithType<$UIntInRangeType> for usize { + fn to_sim_value_with_type( + &self, + ty: $UIntInRangeType, + ) -> SimValue<$UIntInRangeType> { + SimValue::from_value(ty, *self) + } + } + + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] + pub struct $UIntInRangeTypeWithoutGenerics; + + #[allow(non_upper_case_globals)] + pub const $UIntInRangeType: $UIntInRangeTypeWithoutGenerics = + $UIntInRangeTypeWithoutGenerics; + + impl Index for $UIntInRangeTypeWithoutGenerics { + type Output = $UIntInRangeTypeWithStart; + + fn index(&self, start: StartSize) -> &Self::Output { + Interned::into_inner($UIntInRangeTypeWithStart(start).intern_sized()) + } + } + + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + pub struct $UIntInRangeTypeWithStart(Start::SizeType); + + impl, End: Size> + Index for $UIntInRangeTypeWithStart + { + type Output = $UIntInRangeType; + + fn index(&self, end: EndSize) -> &Self::Output { + Interned::into_inner($UIntInRangeType::new(self.0, end).intern_sized()) + } + } + + impl ExprCastTo for $UIntInRangeType { + fn cast_to(src: Expr, to_type: UInt) -> Expr { + src.cast_to_bits().cast_to(to_type) + } + } + + impl ExprCastTo<$UIntInRangeType> for UInt { + fn cast_to( + src: Expr, + to_type: $UIntInRangeType, + ) -> Expr<$UIntInRangeType> { + src.cast_bits_to(to_type) + } + } + + impl + ExprPartialEq<$UIntInRangeType> + for $UIntInRangeType + { + fn cmp_eq( + lhs: Expr, + rhs: Expr<$UIntInRangeType>, + ) -> Expr { + lhs.cast_to_bits().cmp_eq(rhs.cast_to_bits()) + } + fn cmp_ne( + lhs: Expr, + rhs: Expr<$UIntInRangeType>, + ) -> Expr { + lhs.cast_to_bits().cmp_ne(rhs.cast_to_bits()) + } + } + + impl + ExprPartialOrd<$UIntInRangeType> + for $UIntInRangeType + { + fn cmp_lt( + lhs: Expr, + rhs: Expr<$UIntInRangeType>, + ) -> Expr { + lhs.cast_to_bits().cmp_lt(rhs.cast_to_bits()) + } + fn cmp_le( + lhs: Expr, + rhs: Expr<$UIntInRangeType>, + ) -> Expr { + lhs.cast_to_bits().cmp_le(rhs.cast_to_bits()) + } + fn cmp_gt( + lhs: Expr, + rhs: Expr<$UIntInRangeType>, + ) -> Expr { + lhs.cast_to_bits().cmp_gt(rhs.cast_to_bits()) + } + fn cmp_ge( + lhs: Expr, + rhs: Expr<$UIntInRangeType>, + ) -> Expr { + lhs.cast_to_bits().cmp_ge(rhs.cast_to_bits()) + } + } + + impl + SimValuePartialEq<$UIntInRangeType> + for $UIntInRangeType + { + fn sim_value_eq( + this: &SimValue, + other: &SimValue<$UIntInRangeType>, + ) -> bool { + **this == **other + } + } + + impl ExprPartialEq> + for $UIntInRangeType + { + fn cmp_eq(lhs: Expr, rhs: Expr>) -> Expr { + lhs.cast_to_bits().cmp_eq(rhs) + } + fn cmp_ne(lhs: Expr, rhs: Expr>) -> Expr { + lhs.cast_to_bits().cmp_ne(rhs) + } + } + + impl ExprPartialEq<$UIntInRangeType> + for UIntType + { + fn cmp_eq(lhs: Expr, rhs: Expr<$UIntInRangeType>) -> Expr { + lhs.cmp_eq(rhs.cast_to_bits()) + } + fn cmp_ne(lhs: Expr, rhs: Expr<$UIntInRangeType>) -> Expr { + lhs.cmp_ne(rhs.cast_to_bits()) + } + } + + impl ExprPartialOrd> + for $UIntInRangeType + { + fn cmp_lt(lhs: Expr, rhs: Expr>) -> Expr { + lhs.cast_to_bits().cmp_lt(rhs) + } + fn cmp_le(lhs: Expr, rhs: Expr>) -> Expr { + lhs.cast_to_bits().cmp_le(rhs) + } + fn cmp_gt(lhs: Expr, rhs: Expr>) -> Expr { + lhs.cast_to_bits().cmp_gt(rhs) + } + fn cmp_ge(lhs: Expr, rhs: Expr>) -> Expr { + lhs.cast_to_bits().cmp_ge(rhs) + } + } + + impl ExprPartialOrd<$UIntInRangeType> + for UIntType + { + fn cmp_lt(lhs: Expr, rhs: Expr<$UIntInRangeType>) -> Expr { + lhs.cmp_lt(rhs.cast_to_bits()) + } + fn cmp_le(lhs: Expr, rhs: Expr<$UIntInRangeType>) -> Expr { + lhs.cmp_le(rhs.cast_to_bits()) + } + fn cmp_gt(lhs: Expr, rhs: Expr<$UIntInRangeType>) -> Expr { + lhs.cmp_gt(rhs.cast_to_bits()) + } + fn cmp_ge(lhs: Expr, rhs: Expr<$UIntInRangeType>) -> Expr { + lhs.cmp_ge(rhs.cast_to_bits()) + } + } + }; +} + +define_uint_in_range_type! { + UIntInRange, + UIntInRangeType, + UIntInRangeTypeWithoutGenerics, + UIntInRangeTypeWithStart, + SerdeRange, + "..", + |start, end| UInt::range_usize(start..end), +} + +define_uint_in_range_type! { + UIntInRangeInclusive, + UIntInRangeInclusiveType, + UIntInRangeInclusiveTypeWithoutGenerics, + UIntInRangeInclusiveTypeWithStart, + SerdeRangeInclusive, + "..=", + |start, end| UInt::range_inclusive_usize(start..=end), +} + +impl SerdeRange { + fn is_empty(self) -> bool { + self.start >= self.end + } +} + +impl SerdeRangeInclusive { + fn is_empty(self) -> bool { + self.start > self.end + } +} diff --git a/crates/fayalite/src/intern.rs b/crates/fayalite/src/intern.rs index 3780ad3..af91f0a 100644 --- a/crates/fayalite/src/intern.rs +++ b/crates/fayalite/src/intern.rs @@ -1,9 +1,9 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information #![allow(clippy::type_complexity)] -use crate::intern::type_map::TypeIdMap; +use crate::{intern::type_map::TypeIdMap, util::DefaultBuildHasher}; use bitvec::{ptr::BitPtr, slice::BitSlice, vec::BitVec}; -use hashbrown::{hash_map::RawEntryMut, HashMap, HashTable}; +use hashbrown::HashTable; use serde::{Deserialize, Serialize}; use std::{ any::{Any, TypeId}, @@ -17,7 +17,7 @@ use std::{ sync::{Mutex, RwLock}, }; -pub mod type_map; +mod type_map; pub trait LazyInternedTrait: Send + Sync + Any { fn get(&self) -> Interned; @@ -316,8 +316,13 @@ pub trait Intern: Any + Send + Sync { } } +struct InternerState { + table: HashTable<&'static T>, + hasher: DefaultBuildHasher, +} + pub struct Interner { - map: Mutex>, + state: Mutex>, } impl Interner { @@ -330,7 +335,10 @@ impl Interner { impl Default for Interner { fn default() -> Self { Self { - map: Default::default(), + state: Mutex::new(InternerState { + table: HashTable::new(), + hasher: Default::default(), + }), } } } @@ -341,17 +349,16 @@ impl Interner { alloc: F, value: Cow<'_, T>, ) -> Interned { - let mut map = self.map.lock().unwrap(); - let hasher = map.hasher().clone(); - let hash = hasher.hash_one(&*value); - let inner = match map.raw_entry_mut().from_hash(hash, |k| **k == *value) { - RawEntryMut::Occupied(entry) => *entry.key(), - RawEntryMut::Vacant(entry) => { - *entry - .insert_with_hasher(hash, alloc(value), (), |k| hasher.hash_one(&**k)) - .0 - } - }; + 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 } } } @@ -742,7 +749,7 @@ pub trait MemoizeGeneric: 'static + Send + Sync + Hash + Eq + Copy { fn get_cow(self, input: Self::InputCow<'_>) -> Self::Output { static TYPE_ID_MAP: TypeIdMap = TypeIdMap::new(); let map: &RwLock<( - hashbrown::hash_map::DefaultHashBuilder, + DefaultBuildHasher, HashTable<(Self, Self::InputOwned, Self::Output)>, )> = TYPE_ID_MAP.get_or_insert_default(); fn hash_eq_key<'a, 'b, T: MemoizeGeneric>( diff --git a/crates/fayalite/src/intern/type_map.rs b/crates/fayalite/src/intern/type_map.rs index 48433af..945116b 100644 --- a/crates/fayalite/src/intern/type_map.rs +++ b/crates/fayalite/src/intern/type_map.rs @@ -1,10 +1,8 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use hashbrown::HashMap; use std::{ any::{Any, TypeId}, hash::{BuildHasher, Hasher}, - ptr::NonNull, sync::RwLock, }; @@ -75,59 +73,36 @@ impl BuildHasher for TypeIdBuildHasher { } } -struct Value(NonNull); - -impl Value { - unsafe fn get_transmute_lifetime<'b>(&self) -> &'b (dyn Any + Send + Sync) { - unsafe { &*self.0.as_ptr() } - } - fn new(v: Box) -> Self { - unsafe { Self(NonNull::new_unchecked(Box::into_raw(v))) } - } -} - -unsafe impl Send for Value {} -unsafe impl Sync for Value {} - -impl Drop for Value { - fn drop(&mut self) { - unsafe { std::ptr::drop_in_place(self.0.as_ptr()) } - } -} - -pub struct TypeIdMap(RwLock>); +pub(crate) struct TypeIdMap( + RwLock>, +); impl TypeIdMap { - pub const fn new() -> Self { - Self(RwLock::new(HashMap::with_hasher(TypeIdBuildHasher))) + pub(crate) const fn new() -> Self { + Self(RwLock::new(hashbrown::HashMap::with_hasher( + TypeIdBuildHasher, + ))) } #[cold] - unsafe fn insert_slow( + fn insert_slow( &self, type_id: TypeId, make: fn() -> Box, - ) -> &(dyn Any + Sync + Send) { - let value = Value::new(make()); + ) -> &'static (dyn Any + Sync + Send) { + let value = Box::leak(make()); let mut write_guard = self.0.write().unwrap(); - unsafe { - write_guard - .entry(type_id) - .or_insert(value) - .get_transmute_lifetime() - } + *write_guard.entry(type_id).or_insert(value) } - pub fn get_or_insert_default(&self) -> &T { + pub(crate) fn get_or_insert_default(&self) -> &T { let type_id = TypeId::of::(); let read_guard = self.0.read().unwrap(); - let retval = read_guard - .get(&type_id) - .map(|v| unsafe { Value::get_transmute_lifetime(v) }); + let retval = read_guard.get(&type_id).map(|v| *v); drop(read_guard); let retval = match retval { Some(retval) => retval, - None => unsafe { self.insert_slow(type_id, move || Box::new(T::default())) }, + None => self.insert_slow(type_id, move || Box::new(T::default())), }; - unsafe { &*(retval as *const dyn Any as *const T) } + retval.downcast_ref().expect("known to have correct TypeId") } } diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index eedb1bb..932464b 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -11,6 +11,59 @@ extern crate self as fayalite; #[doc(hidden)] pub use std as __std; +#[doc(hidden)] +#[macro_export] +macro_rules! __cfg_expansion_helper { + ( + [ + $($evaluated_cfgs:ident($($evaluated_exprs:tt)*) = $evaluated_results:ident,)* + ] + [ + $cfg:ident($($expr:tt)*), + $($unevaluated_cfgs:ident($($unevaluated_exprs:tt)*),)* + ] + // pass as tt so we get right span for attribute + $after_evaluation_attr:tt $after_evaluation_body:tt + ) => { + #[$cfg($($expr)*)] + $crate::__cfg_expansion_helper! { + [ + $($evaluated_cfgs($($evaluated_exprs)*) = $evaluated_results,)* + $cfg($($expr)*) = true, + ] + [ + $($unevaluated_cfgs($($unevaluated_exprs)*),)* + ] + $after_evaluation_attr $after_evaluation_body + } + #[$cfg(not($($expr)*))] + $crate::__cfg_expansion_helper! { + [ + $($evaluated_cfgs($($evaluated_exprs)*) = $evaluated_results,)* + $cfg($($expr)*) = false, + ] + [ + $($unevaluated_cfgs($($unevaluated_exprs)*),)* + ] + $after_evaluation_attr $after_evaluation_body + } + }; + ( + [ + $($evaluated_cfgs:ident($($evaluated_exprs:tt)*) = $evaluated_results:ident,)* + ] + [] + // don't use #[...] so we get right span for `#` and `[]` of attribute + {$($after_evaluation_attr:tt)*} {$($after_evaluation_body:tt)*} + ) => { + $($after_evaluation_attr)* + #[__evaluated_cfgs([ + $($evaluated_cfgs($($evaluated_exprs)*) = $evaluated_results,)* + ])] + $($after_evaluation_body)* + }; +} + #[doc(inline)] /// The `#[hdl_module]` attribute is applied to a Rust function so that that function creates /// a [`Module`][`::fayalite::module::Module`] when called. @@ -23,6 +76,8 @@ pub use fayalite_proc_macros::hdl_module; #[doc(inline)] pub use fayalite_proc_macros::hdl; +pub use bitvec; + /// struct used as a placeholder when applying defaults #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct __; @@ -43,9 +98,11 @@ pub mod int; pub mod intern; pub mod memory; pub mod module; +pub mod phantom_const; pub mod prelude; pub mod reg; pub mod reset; +pub mod sim; pub mod source_location; pub mod testing; pub mod ty; diff --git a/crates/fayalite/src/memory.rs b/crates/fayalite/src/memory.rs index f583a8c..622ffc6 100644 --- a/crates/fayalite/src/memory.rs +++ b/crates/fayalite/src/memory.rs @@ -22,7 +22,7 @@ use std::{ fmt, hash::{Hash, Hasher}, marker::PhantomData, - num::NonZeroU32, + num::NonZeroUsize, rc::Rc, }; @@ -470,7 +470,7 @@ pub enum ReadUnderWrite { Undefined, } -#[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] struct MemImpl { scoped_name: ScopedNameId, source_location: SourceLocation, @@ -478,7 +478,7 @@ struct MemImpl { initial_value: Option>, ports: P, read_latency: usize, - write_latency: NonZeroU32, + write_latency: NonZeroUsize, read_under_write: ReadUnderWrite, port_annotations: Interned<[TargetedAnnotation]>, mem_annotations: Interned<[Annotation]>, @@ -519,7 +519,12 @@ impl fmt::Debug for Mem { f.debug_struct("Mem") .field("name", scoped_name) .field("array_type", array_type) - .field("initial_value", initial_value) + .field( + "initial_value", + &initial_value.as_ref().map(|initial_value| { + DebugMemoryData::from_bit_slice(*array_type, initial_value) + }), + ) .field("read_latency", read_latency) .field("write_latency", write_latency) .field("read_under_write", read_under_write) @@ -562,7 +567,7 @@ impl Mem { initial_value: Option>, ports: Interned<[MemPort]>, read_latency: usize, - write_latency: NonZeroU32, + write_latency: NonZeroUsize, read_under_write: ReadUnderWrite, port_annotations: Interned<[TargetedAnnotation]>, mem_annotations: Interned<[Annotation]>, @@ -645,7 +650,7 @@ impl Mem { pub fn read_latency(self) -> usize { self.0.read_latency } - pub fn write_latency(self) -> NonZeroU32 { + pub fn write_latency(self) -> NonZeroUsize { self.0.write_latency } pub fn read_under_write(self) -> ReadUnderWrite { @@ -707,7 +712,7 @@ pub(crate) struct MemBuilderTarget { pub(crate) initial_value: Option>, pub(crate) ports: Vec>, pub(crate) read_latency: usize, - pub(crate) write_latency: NonZeroU32, + pub(crate) write_latency: NonZeroUsize, pub(crate) read_under_write: ReadUnderWrite, pub(crate) port_annotations: Vec, pub(crate) mem_annotations: Vec, @@ -867,7 +872,7 @@ impl MemBuilder { initial_value: None, ports: vec![], read_latency: 0, - write_latency: NonZeroU32::new(1).unwrap(), + write_latency: NonZeroUsize::new(1).unwrap(), read_under_write: ReadUnderWrite::Old, port_annotations: vec![], mem_annotations: vec![], @@ -1030,10 +1035,10 @@ impl MemBuilder { pub fn read_latency(&mut self, read_latency: usize) { self.target.borrow_mut().read_latency = read_latency; } - pub fn get_write_latency(&self) -> NonZeroU32 { + pub fn get_write_latency(&self) -> NonZeroUsize { self.target.borrow().write_latency } - pub fn write_latency(&mut self, write_latency: NonZeroU32) { + pub fn write_latency(&mut self, write_latency: NonZeroUsize) { self.target.borrow_mut().write_latency = write_latency; } pub fn get_read_under_write(&self) -> ReadUnderWrite { @@ -1077,5 +1082,64 @@ pub fn splat_mask(ty: T, value: Expr) -> Expr> { ) .to_expr(), )), + CanonicalType::PhantomConst(_) => Expr::from_canonical(Expr::canonical(().to_expr())), + } +} + +pub trait DebugMemoryDataGetElement { + fn get_element(&self, element_index: usize, array_type: Array) -> &BitSlice; +} + +impl<'a, F: ?Sized + Fn(usize, Array) -> &'a BitSlice> DebugMemoryDataGetElement for &'a F { + fn get_element(&self, element_index: usize, array_type: Array) -> &BitSlice { + self(element_index, array_type) + } +} + +#[derive(Clone)] +pub struct DebugMemoryData { + pub array_type: Array, + pub get_element: GetElement, +} + +impl DebugMemoryDataGetElement for &'_ BitSlice { + fn get_element(&self, element_index: usize, array_type: Array) -> &BitSlice { + assert!(element_index < array_type.len()); + let stride = array_type.element().bit_width(); + let start = element_index + .checked_mul(stride) + .expect("memory is too big"); + let end = start.checked_add(stride).expect("memory is too big"); + &self[start..end] + } +} + +impl<'a> DebugMemoryData<&'a BitSlice> { + pub fn from_bit_slice( + array_type: ArrayType, + bit_slice: &'a BitSlice, + ) -> Self { + let array_type = array_type.as_dyn_array(); + assert_eq!(bit_slice.len(), array_type.type_properties().bit_width); + Self { + array_type, + get_element: bit_slice, + } + } +} + +impl fmt::Debug for DebugMemoryData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.array_type.len() == 0 { + return f.write_str("[]"); + } + writeln!(f, "[\n // len = {:#x}", self.array_type.len())?; + for element_index in 0..self.array_type.len() { + let element = crate::util::BitSliceWriteWithBase( + self.get_element.get_element(element_index, self.array_type), + ); + writeln!(f, " [{element_index:#x}]: {element:#x},")?; + } + f.write_str("]") } } diff --git a/crates/fayalite/src/module.rs b/crates/fayalite/src/module.rs index 7387832..920b0af 100644 --- a/crates/fayalite/src/module.rs +++ b/crates/fayalite/src/module.rs @@ -20,18 +20,21 @@ use crate::{ intern::{Intern, Interned}, memory::{Mem, MemBuilder, MemBuilderTarget, PortName}, reg::Reg, + reset::{AsyncReset, Reset, ResetType, ResetTypeDispatch, SyncReset}, + sim::{ExternModuleSimGenerator, ExternModuleSimulation}, source_location::SourceLocation, ty::{CanonicalType, Type}, - util::ScopedRef, + util::{HashMap, HashSet, ScopedRef}, wire::{IncompleteWire, Wire}, }; -use hashbrown::{hash_map::Entry, HashMap, HashSet}; +use hashbrown::hash_map::Entry; use num_bigint::BigInt; use std::{ cell::RefCell, collections::VecDeque, convert::Infallible, fmt, + future::IntoFuture, hash::{Hash, Hasher}, iter::FusedIterator, marker::PhantomData, @@ -180,7 +183,7 @@ impl Block { } } -#[derive(Clone, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct StmtConnect { pub lhs: Expr, pub rhs: Expr, @@ -235,7 +238,7 @@ impl fmt::Debug for StmtConnect { } } -#[derive(Clone, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct StmtFormal { pub kind: FormalKind, pub clk: Expr, @@ -284,6 +287,8 @@ pub struct StmtIf { pub blocks: [S::Block; 2], } +impl Copy for StmtIf {} + impl StmtIf { pub fn then_block(&self) -> S::Block { self.blocks[0] @@ -315,6 +320,8 @@ pub struct StmtMatch { pub blocks: Interned<[S::Block]>, } +impl Copy for StmtMatch {} + impl StmtMatch { #[track_caller] fn assert_validity(&self) { @@ -346,7 +353,7 @@ macro_rules! wrapper_enum { $(#[$enum_meta:meta])* $vis:vis enum $enum_name:ident<$T_enum:ident: $T_bound:ident = $T_enum_default:ident> { $( - #[is = $is_fn:ident, as_ref = $as_ref_fn:ident] + #[is = $is_fn:ident, as_ref = $as_ref_fn:ident $(, from = $from:ident)?] $(#[$variant_meta:meta])* $Variant:ident($VariantTy:ty), )* @@ -358,7 +365,7 @@ macro_rules! wrapper_enum { $(#[$enum_meta])* $vis enum $enum_name<$T_enum: $T_bound = $T_enum_default> { $( - #[is = $is_fn, as_ref = $as_ref_fn] + #[is = $is_fn, as_ref = $as_ref_fn $(, from = $from)?] $(#[$variant_meta])* $Variant($VariantTy), )* @@ -385,7 +392,7 @@ macro_rules! wrapper_enum { $(#[$enum_meta:meta])* $vis:vis enum $enum_name:ident<$T_enum:ident: $T_bound:ident = $T_enum_default:ident> { $( - #[is = $is_fn:ident, as_ref = $as_ref_fn:ident] + #[is = $is_fn:ident, as_ref = $as_ref_fn:ident $(, from = $from:ident)?] $(#[$variant_meta:meta])* $Variant:ident($VariantTy:ty), )* @@ -397,22 +404,22 @@ macro_rules! wrapper_enum { $(#[$enum_meta])* $vis enum $enum_name<$T_enum: $T_bound = $T_enum_default> { $( - #[is = $is_fn, as_ref = $as_ref_fn] + #[is = $is_fn, as_ref = $as_ref_fn $(, from = $from)?] $(#[$variant_meta])* $Variant($VariantTy), )* } } - $( + $($( wrapper_enum! { impl $T_to From<$VariantTy> for $to_type { - fn from(value: $VariantTy) -> Self { + fn $from(value: $VariantTy) -> Self { $enum_name::$Variant(value).into() } } } - )* + )?)* }; ( #[impl()] @@ -420,7 +427,7 @@ macro_rules! wrapper_enum { $(#[$enum_meta:meta])* $vis:vis enum $enum_name:ident<$T_enum:ident: $T_bound:ident = $T_enum_default:ident> { $( - #[is = $is_fn:ident, as_ref = $as_ref_fn:ident] + #[is = $is_fn:ident, as_ref = $as_ref_fn:ident $(, from = $from:ident)?] $(#[$variant_meta:meta])* $Variant:ident($VariantTy:ty), )* @@ -459,13 +466,15 @@ pub struct StmtWire { pub wire: Wire, } +impl Copy for StmtWire {} + #[derive(Hash, Clone, PartialEq, Eq, Debug)] -pub struct StmtReg { +pub struct StmtReg { pub annotations: S::StmtAnnotations, - pub reg: Reg, + pub reg: Reg, } -impl Copy for StmtReg {} +impl Copy for StmtReg {} #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub struct StmtInstance { @@ -473,6 +482,8 @@ pub struct StmtInstance { pub instance: Instance, } +impl Copy for StmtInstance {} + wrapper_enum! { #[impl( () self: StmtDeclaration = self, @@ -481,20 +492,57 @@ wrapper_enum! { #[to(() StmtDeclaration, () Stmt)] #[derive(Clone, PartialEq, Eq, Hash)] pub enum StmtDeclaration { - #[is = is_wire, as_ref = wire] + #[is = is_wire, as_ref = wire, from = from] Wire(StmtWire), #[is = is_reg, as_ref = reg] - Reg(StmtReg), - #[is = is_instance, as_ref = instance] + Reg(StmtReg), + #[is = is_reg_sync, as_ref = reg_sync] + RegSync(StmtReg), + #[is = is_reg_async, as_ref = reg_async] + RegAsync(StmtReg), + #[is = is_instance, as_ref = instance, from = from] Instance(StmtInstance), } } +impl Copy for StmtDeclaration {} + +impl From> for Stmt { + fn from(value: StmtReg) -> Self { + StmtDeclaration::from(value).into() + } +} + +impl From> for StmtDeclaration { + fn from(value: StmtReg) -> Self { + struct Dispatch(PhantomData); + impl ResetTypeDispatch for Dispatch { + type Input = StmtReg; + type Output = StmtDeclaration; + + fn reset(self, input: Self::Input) -> Self::Output { + StmtDeclaration::Reg(input) + } + + fn sync_reset(self, input: Self::Input) -> Self::Output { + StmtDeclaration::RegSync(input) + } + + fn async_reset(self, input: Self::Input) -> Self::Output { + StmtDeclaration::RegAsync(input) + } + } + R::dispatch(value, Dispatch(PhantomData)) + } +} + impl StmtDeclaration { pub fn annotations(&self) -> S::StmtAnnotations { match self { StmtDeclaration::Wire(v) => v.annotations, StmtDeclaration::Reg(v) => v.annotations, + StmtDeclaration::RegSync(v) => v.annotations, + StmtDeclaration::RegAsync(v) => v.annotations, StmtDeclaration::Instance(v) => v.annotations, } } @@ -502,6 +550,8 @@ impl StmtDeclaration { match self { StmtDeclaration::Wire(v) => v.wire.source_location(), StmtDeclaration::Reg(v) => v.reg.source_location(), + StmtDeclaration::RegSync(v) => v.reg.source_location(), + StmtDeclaration::RegAsync(v) => v.reg.source_location(), StmtDeclaration::Instance(v) => v.instance.source_location(), } } @@ -509,20 +559,26 @@ impl StmtDeclaration { match self { StmtDeclaration::Wire(v) => v.wire.scoped_name(), StmtDeclaration::Reg(v) => v.reg.scoped_name(), + StmtDeclaration::RegSync(v) => v.reg.scoped_name(), + StmtDeclaration::RegAsync(v) => v.reg.scoped_name(), StmtDeclaration::Instance(v) => v.instance.scoped_name(), } } pub fn sub_stmt_blocks(&self) -> &[S::Block] { match self { - StmtDeclaration::Wire(_) | StmtDeclaration::Reg(_) | StmtDeclaration::Instance(_) => { - &[] - } + StmtDeclaration::Wire(_) + | StmtDeclaration::Reg(_) + | StmtDeclaration::RegSync(_) + | StmtDeclaration::RegAsync(_) + | StmtDeclaration::Instance(_) => &[], } } pub fn canonical_ty(&self) -> CanonicalType { match self { StmtDeclaration::Wire(v) => v.wire.ty(), StmtDeclaration::Reg(v) => v.reg.ty(), + StmtDeclaration::RegSync(v) => v.reg.ty(), + StmtDeclaration::RegAsync(v) => v.reg.ty(), StmtDeclaration::Instance(v) => CanonicalType::Bundle(v.instance.ty()), } } @@ -533,19 +589,21 @@ wrapper_enum! { #[to(() Stmt)] #[derive(Clone, PartialEq, Eq, Hash)] pub enum Stmt { - #[is = is_connect, as_ref = connect] + #[is = is_connect, as_ref = connect, from = from] Connect(StmtConnect), - #[is = is_formal, as_ref = formal] + #[is = is_formal, as_ref = formal, from = from] Formal(StmtFormal), - #[is = is_if, as_ref = if_] + #[is = is_if, as_ref = if_, from = from] If(StmtIf), - #[is = is_match, as_ref = match_] + #[is = is_match, as_ref = match_, from = from] Match(StmtMatch), - #[is = is_declaration, as_ref = declaration] + #[is = is_declaration, as_ref = declaration, from = from] Declaration(StmtDeclaration), } } +impl Copy for Stmt {} + impl Stmt { pub fn sub_stmt_blocks(&self) -> &[S::Block] { match self { @@ -714,6 +772,18 @@ impl Instance { source_location, } } + pub fn from_canonical(v: Instance) -> Self { + let Instance { + scoped_name, + instantiated, + source_location, + } = v; + Self { + scoped_name, + instantiated: Module::from_canonical(*instantiated).intern_sized(), + source_location, + } + } pub fn containing_module_name(self) -> Interned { self.containing_module_name_id().0 } @@ -958,6 +1028,14 @@ impl From> for NormalModuleBody { annotations: (), reg, }) => StmtReg { annotations, reg }.into(), + StmtDeclaration::RegSync(StmtReg { + annotations: (), + reg, + }) => StmtReg { annotations, reg }.into(), + StmtDeclaration::RegAsync(StmtReg { + annotations: (), + reg, + }) => StmtReg { annotations, reg }.into(), StmtDeclaration::Instance(StmtInstance { annotations: (), instance, @@ -1005,6 +1083,7 @@ pub struct ExternModuleBody< > { pub verilog_name: Interned, pub parameters: P, + pub simulation: Option, } impl From>> for ExternModuleBody { @@ -1012,11 +1091,13 @@ impl From>> for ExternModuleBody { let ExternModuleBody { verilog_name, parameters, + simulation, } = value; let parameters = Intern::intern_owned(parameters); Self { verilog_name, parameters, + simulation, } } } @@ -1207,10 +1288,12 @@ impl fmt::Debug for DebugModuleBody { ModuleBody::Extern(ExternModuleBody { verilog_name, parameters, + simulation, }) => { debug_struct .field("verilog_name", verilog_name) - .field("parameters", parameters); + .field("parameters", parameters) + .field("simulation", simulation); } } debug_struct.finish_non_exhaustive() @@ -1414,6 +1497,9 @@ impl TargetState { }) .collect(), }, + CanonicalType::PhantomConst(_) => TargetStateInner::Decomposed { + subtargets: HashMap::default(), + }, CanonicalType::Array(ty) => TargetStateInner::Decomposed { subtargets: (0..ty.len()) .map(|index| { @@ -1661,6 +1747,14 @@ impl AssertValidityState { annotations: _, reg, })) => self.insert_new_base(TargetBase::intern_sized(reg.into()), block), + Stmt::Declaration(StmtDeclaration::RegSync(StmtReg { + annotations: _, + reg, + })) => self.insert_new_base(TargetBase::intern_sized(reg.into()), block), + Stmt::Declaration(StmtDeclaration::RegAsync(StmtReg { + annotations: _, + reg, + })) => self.insert_new_base(TargetBase::intern_sized(reg.into()), block), Stmt::Declaration(StmtDeclaration::Instance(StmtInstance { annotations: _, instance, @@ -1674,6 +1768,7 @@ impl AssertValidityState { ModuleBody::Extern(ExternModuleBody { verilog_name: _, parameters: _, + simulation: _, }) => {} ModuleBody::Normal(NormalModuleBody { body }) => { let body = self.make_block_index(body); @@ -1769,7 +1864,7 @@ impl Module { AssertValidityState { module: self.canonical(), blocks: vec![], - target_states: HashMap::with_capacity(64), + target_states: HashMap::with_capacity_and_hasher(64, Default::default()), } .assert_validity(); } @@ -1842,10 +1937,10 @@ impl RegBuilder { } impl RegBuilder<(), I, T> { - pub fn clock_domain( + pub fn clock_domain( self, - clock_domain: impl ToExpr, - ) -> RegBuilder, I, T> { + clock_domain: impl ToExpr>, + ) -> RegBuilder>, I, T> { let Self { name, source_location, @@ -1863,7 +1958,7 @@ impl RegBuilder<(), I, T> { } } -impl RegBuilder, Option>, T> { +impl RegBuilder>, Option>, T> { #[track_caller] pub fn build(self) -> Expr { let Self { @@ -2021,6 +2116,7 @@ impl ModuleBuilder { ModuleKind::Extern => ModuleBody::Extern(ExternModuleBody { verilog_name: name.0, parameters: vec![], + simulation: None, }), ModuleKind::Normal => ModuleBody::Normal(NormalModuleBody { body: BuilderModuleBody { @@ -2029,8 +2125,8 @@ impl ModuleBuilder { incomplete_declarations: vec![], stmts: vec![], }], - annotations_map: HashMap::new(), - memory_map: HashMap::new(), + annotations_map: HashMap::default(), + memory_map: HashMap::default(), }, }), }; @@ -2040,7 +2136,7 @@ impl ModuleBuilder { impl_: RefCell::new(ModuleBuilderImpl { body, io: vec![], - io_indexes: HashMap::new(), + io_indexes: HashMap::default(), module_annotations: vec![], }), }; @@ -2087,6 +2183,7 @@ impl ModuleBuilder { .builder_extern_body() .verilog_name = name.intern(); } + #[track_caller] pub fn parameter(&self, name: impl AsRef, value: ExternModuleParameterValue) { let name = name.as_ref(); self.impl_ @@ -2099,6 +2196,7 @@ impl ModuleBuilder { value, }); } + #[track_caller] pub fn parameter_int(&self, name: impl AsRef, value: impl Into) { let name = name.as_ref(); let value = value.into(); @@ -2112,6 +2210,7 @@ impl ModuleBuilder { value: ExternModuleParameterValue::Integer(value), }); } + #[track_caller] pub fn parameter_str(&self, name: impl AsRef, value: impl AsRef) { let name = name.as_ref(); let value = value.as_ref(); @@ -2125,6 +2224,7 @@ impl ModuleBuilder { value: ExternModuleParameterValue::String(value.intern()), }); } + #[track_caller] pub fn parameter_raw_verilog(&self, name: impl AsRef, raw_verilog: impl AsRef) { let name = name.as_ref(); let raw_verilog = raw_verilog.as_ref(); @@ -2138,6 +2238,26 @@ impl ModuleBuilder { value: ExternModuleParameterValue::RawVerilog(raw_verilog.intern()), }); } + #[track_caller] + pub fn extern_module_simulation(&self, generator: G) { + let mut impl_ = self.impl_.borrow_mut(); + let simulation = &mut impl_.body.builder_extern_body().simulation; + if simulation.is_some() { + panic!("already added an extern module simulation"); + } + *simulation = Some(ExternModuleSimulation::new(generator)); + } + #[track_caller] + pub fn extern_module_simulation_fn< + Args: fmt::Debug + Clone + Hash + Eq + Send + Sync + 'static, + Fut: IntoFuture + 'static, + >( + &self, + args: Args, + f: fn(Args, crate::sim::ExternModuleSimulationState) -> Fut, + ) { + self.extern_module_simulation(crate::sim::SimGeneratorFn { args, f }); + } } #[track_caller] @@ -2188,6 +2308,16 @@ pub fn annotate(target: Expr, annotations: impl IntoAnnotations) { reg, } .into(), + TargetBase::RegSync(reg) => StmtReg { + annotations: (), + reg, + } + .into(), + TargetBase::RegAsync(reg) => StmtReg { + annotations: (), + reg, + } + .into(), TargetBase::Wire(wire) => StmtWire { annotations: (), wire, @@ -2629,3 +2759,50 @@ impl ModuleIO { self.ty } } + +#[derive(PartialEq, Eq, Hash, Clone, Copy)] +pub enum InstantiatedModule { + Base(Interned>), + Child { + parent: Interned, + instance: Interned>, + }, +} + +impl InstantiatedModule { + pub fn leaf_module(self) -> Interned> { + match self { + InstantiatedModule::Base(base) => base, + InstantiatedModule::Child { instance, .. } => instance.instantiated(), + } + } + fn write_path(self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + InstantiatedModule::Base(base) => fmt::Debug::fmt(&base.name_id(), f), + InstantiatedModule::Child { parent, instance } => { + parent.write_path(f)?; + write!(f, ".{}", instance.name_id()) + } + } + } +} + +impl fmt::Debug for InstantiatedModule { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "InstantiatedModule(")?; + self.write_path(f)?; + write!(f, ": {})", self.leaf_module().name_id()) + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub struct TargetInInstantiatedModule { + pub instantiated_module: InstantiatedModule, + pub target: Target, +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub struct ExprInInstantiatedModule { + pub instantiated_module: InstantiatedModule, + pub expr: Expr, +} diff --git a/crates/fayalite/src/module/transform.rs b/crates/fayalite/src/module/transform.rs index 4117087..063a1a3 100644 --- a/crates/fayalite/src/module/transform.rs +++ b/crates/fayalite/src/module/transform.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information +pub mod deduce_resets; pub mod simplify_enums; pub mod simplify_memories; pub mod visit; diff --git a/crates/fayalite/src/module/transform/deduce_resets.rs b/crates/fayalite/src/module/transform/deduce_resets.rs new file mode 100644 index 0000000..5fb829e --- /dev/null +++ b/crates/fayalite/src/module/transform/deduce_resets.rs @@ -0,0 +1,2281 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + annotations::{Annotation, TargetedAnnotation}, + bundle::{BundleField, BundleType}, + enum_::{EnumType, EnumVariant}, + expr::{ + ops::{self, ArrayLiteral}, + target::{ + Target, TargetBase, TargetChild, TargetPathArrayElement, TargetPathBundleField, + TargetPathDynArrayElement, TargetPathElement, + }, + ExprEnum, + }, + formal::FormalKind, + int::{SIntValue, UIntValue}, + intern::{Intern, Interned, Memoize}, + memory::{DynPortType, MemPort}, + module::{ + AnnotatedModuleIO, Block, ExprInInstantiatedModule, ExternModuleBody, InstantiatedModule, + ModuleBody, ModuleIO, NameId, NormalModuleBody, Stmt, StmtConnect, StmtDeclaration, + StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg, StmtWire, + }, + prelude::*, + reset::{ResetType, ResetTypeDispatch}, + util::{HashMap, HashSet}, +}; +use hashbrown::hash_map::Entry; +use num_bigint::BigInt; +use petgraph::unionfind::UnionFind; +use std::{fmt, marker::PhantomData}; + +#[derive(Debug)] +pub enum DeduceResetsError { + ResetIsNotDrivenByAsyncOrSync { source_location: SourceLocation }, + ResetIsDrivenByBothAsyncAndSync { source_location: SourceLocation }, +} + +impl fmt::Display for DeduceResetsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DeduceResetsError::ResetIsNotDrivenByAsyncOrSync { source_location } => { + write!(f, "deduce_reset failed: Reset signal is not driven by any AsyncReset or SyncReset signals: {source_location}") + } + DeduceResetsError::ResetIsDrivenByBothAsyncAndSync { source_location } => { + write!(f, "deduce_reset failed: Reset signal is driven by both AsyncReset and SyncReset signals: {source_location}") + } + } + } +} + +impl std::error::Error for DeduceResetsError {} + +impl From for std::io::Error { + fn from(value: DeduceResetsError) -> Self { + std::io::Error::new(std::io::ErrorKind::Other, value) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +enum AnyReg { + Reg(Reg), + RegSync(Reg), + RegAsync(Reg), +} + +macro_rules! match_any_reg { + ( + $match_expr:expr, $fn:expr + ) => { + match $match_expr { + AnyReg::Reg(reg) => $fn(reg), + AnyReg::RegSync(reg) => $fn(reg), + AnyReg::RegAsync(reg) => $fn(reg), + } + }; +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +enum ResetsLayout { + NoResets, + Reset, + SyncReset, + AsyncReset, + Bundle { + fields: Interned<[ResetsLayout]>, + reset_count: usize, + }, + Enum { + variants: Interned<[ResetsLayout]>, + reset_count: usize, + }, + Array { + element: Interned, + reset_count: usize, + }, +} + +impl ResetsLayout { + fn reset_count(self) -> usize { + match self { + ResetsLayout::NoResets => 0, + ResetsLayout::Reset | ResetsLayout::SyncReset | ResetsLayout::AsyncReset => 1, + ResetsLayout::Bundle { reset_count, .. } + | ResetsLayout::Enum { reset_count, .. } + | ResetsLayout::Array { reset_count, .. } => reset_count, + } + } + fn new(ty: CanonicalType) -> Self { + #[derive(Clone, Copy, PartialEq, Eq, Hash)] + struct MyMemoize; + impl Memoize for MyMemoize { + type Input = CanonicalType; + type InputOwned = CanonicalType; + type Output = ResetsLayout; + + fn inner(self, ty: &Self::Input) -> Self::Output { + match *ty { + CanonicalType::UInt(_) => ResetsLayout::NoResets, + CanonicalType::SInt(_) => ResetsLayout::NoResets, + CanonicalType::Bool(_) => ResetsLayout::NoResets, + CanonicalType::Array(ty) => { + let element = ResetsLayout::new(ty.element()).intern_sized(); + ResetsLayout::Array { + element, + reset_count: element.reset_count(), + } + } + CanonicalType::Enum(ty) => { + let mut reset_count = 0; + let variants = Interned::from_iter(ty.variants().iter().map(|variant| { + let resets_layout = + variant.ty.map_or(ResetsLayout::NoResets, ResetsLayout::new); + reset_count += resets_layout.reset_count(); + resets_layout + })); + ResetsLayout::Enum { + variants, + reset_count, + } + } + CanonicalType::Bundle(ty) => { + let mut reset_count = 0; + let fields = Interned::from_iter(ty.fields().iter().map(|field| { + let resets_layout = ResetsLayout::new(field.ty); + reset_count += resets_layout.reset_count(); + resets_layout + })); + ResetsLayout::Bundle { + fields, + reset_count, + } + } + CanonicalType::AsyncReset(_) => ResetsLayout::AsyncReset, + CanonicalType::SyncReset(_) => ResetsLayout::SyncReset, + CanonicalType::Reset(_) => ResetsLayout::Reset, + CanonicalType::Clock(_) => ResetsLayout::NoResets, + CanonicalType::PhantomConst(_) => ResetsLayout::NoResets, + } + } + } + MyMemoize.get_owned(ty) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +struct ResetNodeIndex(usize); + +#[derive(Copy, Clone, Debug)] +struct ResetNode { + is_async: Option, + source_location: Option, +} + +impl ResetNode { + fn union( + self, + other: Self, + fallback_error_source_location: SourceLocation, + ) -> Result { + match (self.is_async, other.is_async) { + (None, None) => Ok(Self { + is_async: None, + source_location: self.source_location.or(other.source_location), + }), + (None, is_async @ Some(_)) => Ok(Self { + is_async, + // prioritize `other` + source_location: other.source_location.or(self.source_location), + }), + (is_async @ Some(_), None) => Ok(Self { + is_async, + // prioritize `self` + source_location: self.source_location.or(other.source_location), + }), + (Some(self_is_async), Some(other_is_async)) => { + if self_is_async == other_is_async { + Ok(Self { + is_async: Some(self_is_async), + source_location: self.source_location.or(other.source_location), + }) + } else { + Err(DeduceResetsError::ResetIsDrivenByBothAsyncAndSync { + source_location: self + .source_location + .or(other.source_location) + .unwrap_or(fallback_error_source_location), + }) + } + } + } + } +} + +#[derive(Debug, Default)] +struct ResetGraph { + union_find: UnionFind, + nodes: Vec, +} + +impl ResetGraph { + fn new_node( + &mut self, + is_async: Option, + source_location: Option, + ) -> ResetNodeIndex { + let index = self.union_find.new_set(); + assert_eq!(index, self.nodes.len()); + self.nodes.push(ResetNode { + is_async, + source_location, + }); + ResetNodeIndex(index) + } + fn union( + &mut self, + a: ResetNodeIndex, + b: ResetNodeIndex, + fallback_error_source_location: SourceLocation, + ) -> Result<(), DeduceResetsError> { + let a = self.union_find.find_mut(a.0); + let b = self.union_find.find_mut(b.0); + if a != b { + self.union_find.union(a, b); + let merged = self.union_find.find_mut(a); + self.nodes[merged] = + self.nodes[a].union(self.nodes[b], fallback_error_source_location)?; + } + Ok(()) + } + fn is_async( + &mut self, + node: ResetNodeIndex, + fallback_to_sync_reset: bool, + fallback_error_source_location: SourceLocation, + ) -> Result { + let ResetNode { + is_async, + source_location, + } = self.nodes[self.union_find.find_mut(node.0)]; + if let Some(is_async) = is_async { + Ok(is_async) + } else if fallback_to_sync_reset { + Ok(false) + } else { + Err(DeduceResetsError::ResetIsNotDrivenByAsyncOrSync { + source_location: source_location.unwrap_or(fallback_error_source_location), + }) + } + } + fn append_new_nodes_for_layout( + &mut self, + layout: ResetsLayout, + node_indexes: &mut Vec, + source_location: Option, + ) { + match layout { + ResetsLayout::NoResets => {} + ResetsLayout::Reset => node_indexes.push(self.new_node(None, source_location)), + ResetsLayout::SyncReset => { + node_indexes.push(self.new_node(Some(false), source_location)) + } + ResetsLayout::AsyncReset => { + node_indexes.push(self.new_node(Some(true), source_location)) + } + ResetsLayout::Bundle { + fields, + reset_count: _, + } => { + for field in fields { + self.append_new_nodes_for_layout(field, node_indexes, source_location); + } + } + ResetsLayout::Enum { + variants, + reset_count: _, + } => { + for variant in variants { + self.append_new_nodes_for_layout(variant, node_indexes, source_location); + } + } + ResetsLayout::Array { + element, + reset_count: _, + } => { + self.append_new_nodes_for_layout(*element, node_indexes, source_location); + } + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +struct Resets { + ty: CanonicalType, + layout: ResetsLayout, + node_indexes: Interned<[ResetNodeIndex]>, +} + +impl Resets { + fn with_new_nodes( + reset_graph: &mut ResetGraph, + ty: CanonicalType, + source_location: Option, + ) -> Self { + let layout = ResetsLayout::new(ty); + let mut node_indexes = Vec::with_capacity(layout.reset_count()); + reset_graph.append_new_nodes_for_layout(layout, &mut node_indexes, source_location); + let node_indexes = Intern::intern_owned(node_indexes); + Self { + ty, + layout, + node_indexes, + } + } + fn array_elements(self) -> Self { + let array = ::from_canonical(self.ty); + let ResetsLayout::Array { + element, + reset_count: _, + } = self.layout + else { + unreachable!(); + }; + Self { + ty: array.element(), + layout: *element, + node_indexes: self.node_indexes, + } + } + fn bundle_fields(self) -> impl Iterator { + let bundle = Bundle::from_canonical(self.ty); + let ResetsLayout::Bundle { + fields, + reset_count: _, + } = self.layout + else { + unreachable!(); + }; + bundle.fields().into_iter().zip(fields).scan( + 0, + move |start_index, (BundleField { ty, .. }, layout)| { + let end_index = *start_index + layout.reset_count(); + let node_indexes = self.node_indexes[*start_index..end_index].intern(); + *start_index = end_index; + Some(Self { + ty, + layout, + node_indexes, + }) + }, + ) + } + fn enum_variants(self) -> impl Iterator> { + let enum_ = Enum::from_canonical(self.ty); + let ResetsLayout::Enum { + variants, + reset_count: _, + } = self.layout + else { + unreachable!(); + }; + enum_.variants().into_iter().zip(variants).scan( + 0, + move |start_index, (EnumVariant { ty, .. }, layout)| { + let end_index = *start_index + layout.reset_count(); + let node_indexes = self.node_indexes[*start_index..end_index].intern(); + *start_index = end_index; + Some(ty.map(|ty| Self { + ty, + layout, + node_indexes, + })) + }, + ) + } + fn substituted_type( + self, + reset_graph: &mut ResetGraph, + fallback_to_sync_reset: bool, + fallback_error_source_location: SourceLocation, + ) -> Result { + if self.layout.reset_count() == 0 { + return Ok(self.ty); + } + match self.ty { + CanonicalType::UInt(_) + | CanonicalType::SInt(_) + | CanonicalType::Bool(_) + | CanonicalType::AsyncReset(_) + | CanonicalType::SyncReset(_) + | CanonicalType::Clock(_) + | CanonicalType::PhantomConst(_) => Ok(self.ty), + CanonicalType::Array(ty) => Ok(CanonicalType::Array(Array::new_dyn( + self.array_elements().substituted_type( + reset_graph, + fallback_to_sync_reset, + fallback_error_source_location, + )?, + ty.len(), + ))), + CanonicalType::Enum(ty) => Ok(CanonicalType::Enum(Enum::new(Result::from_iter( + self.enum_variants().zip(ty.variants()).map( + |(resets, EnumVariant { name, ty: _ })| { + Ok(EnumVariant { + name, + ty: resets + .map(|resets| { + resets.substituted_type( + reset_graph, + fallback_to_sync_reset, + fallback_error_source_location, + ) + }) + .transpose()?, + }) + }, + ), + )?))), + CanonicalType::Bundle(ty) => Ok(CanonicalType::Bundle(Bundle::new(Result::from_iter( + self.bundle_fields().zip(ty.fields()).map( + |( + resets, + BundleField { + name, + flipped, + ty: _, + }, + )| { + Ok(BundleField { + name, + flipped, + ty: resets.substituted_type( + reset_graph, + fallback_to_sync_reset, + fallback_error_source_location, + )?, + }) + }, + ), + )?))), + CanonicalType::Reset(_) => Ok( + if reset_graph.is_async( + self.node_indexes[0], + fallback_to_sync_reset, + fallback_error_source_location, + )? { + CanonicalType::AsyncReset(AsyncReset) + } else { + CanonicalType::SyncReset(SyncReset) + }, + ), + } + } +} + +#[derive(Debug)] +struct State { + modules_added_to_graph: HashSet, + substituted_modules: HashMap>, + expr_resets: HashMap, Resets>, + reset_graph: ResetGraph, + fallback_to_sync_reset: bool, +} + +impl State { + fn get_resets( + &self, + instantiated_module: InstantiatedModule, + expr: impl ToExpr, + ) -> Option { + self.expr_resets + .get(&ExprInInstantiatedModule { + instantiated_module, + expr: Expr::canonical(expr.to_expr()), + }) + .copied() + } + fn get_or_make_resets( + &mut self, + instantiated_module: InstantiatedModule, + expr: impl ToExpr, + source_location: Option, + ) -> (Resets, bool) { + let expr = Expr::canonical(expr.to_expr()); + match self.expr_resets.entry(ExprInInstantiatedModule { + instantiated_module, + expr, + }) { + Entry::Occupied(entry) => (*entry.get(), false), + Entry::Vacant(entry) => ( + *entry.insert(Resets::with_new_nodes( + &mut self.reset_graph, + Expr::ty(expr), + source_location, + )), + true, + ), + } + } +} + +struct PassOutput(P::Output); + +impl PassOutput { + fn new(v: T) -> Self { + P::output_new(v) + } + fn from_fn(f: impl FnOnce() -> T) -> Self { + PassOutput::new(()).map(|()| f()) + } + fn map(self, f: impl FnOnce(T) -> U) -> PassOutput { + P::map(self, f) + } +} + +trait PassOutputZip: Sized { + type Zipped; + fn zip(self) -> PassOutput; + fn call(self, f: impl FnOnce(Self::Zipped) -> U) -> PassOutput { + self.zip().map(f) + } +} + +impl PassOutputZip

for () { + type Zipped = (); + fn zip(self) -> PassOutput { + PassOutput::new(()) + } +} + +impl PassOutputZip

for (PassOutput,) { + type Zipped = (T,); + fn zip(self) -> PassOutput { + self.0.map(|v| (v,)) + } +} + +macro_rules! impl_zip { + ($first_arg:ident: $first_T:ident, $($arg:ident: $T:ident),* $(,)?) => { + impl_zip!(@step [], [($first_arg: $first_T) $(($arg: $T))*], (),); + }; + ( + @impl($first_arg:tt,), + $tuple_pat:tt, + ) => {}; + ( + @impl(($first_arg:ident: $first_T:ident), + $(($arg:ident: $T:ident),)*), + $tuple_pat:tt, + ) => { + impl<$first_T, $($T,)* P: Pass> PassOutputZip

for (PassOutput<$first_T, P>, $(PassOutput<$T, P>),*) { + type Zipped = ($first_T, $($T),*); + fn zip(self) -> PassOutput<($first_T, $($T),*), P> { + let (tuples, $($arg),*) = self; + $(let tuples = P::zip(tuples, $arg);)* + tuples.map(|$tuple_pat| ($first_arg, $($arg),*)) + } + } + }; + ( + @step [$($cur:tt)*], + [], + $tuple_pat:tt, + ) => {}; + ( + @step [$($cur:tt)*], + [($next_arg:ident: $next_T:ident) $($rest:tt)*], + (), + ) => { + impl_zip!(@impl($($cur,)* ($next_arg: $next_T),), $next_arg,); + impl_zip!(@step [$($cur)* ($next_arg: $next_T)], [$($rest)*], $next_arg,); + }; + ( + @step [$($cur:tt)*], + [($next_arg:ident: $next_T:ident) $($rest:tt)*], + $tuple_pat:tt, + ) => { + impl_zip!(@impl($($cur,)* ($next_arg: $next_T),), ($tuple_pat, $next_arg),); + impl_zip!(@step [$($cur)* ($next_arg: $next_T)], [$($rest)*], ($tuple_pat, $next_arg),); + }; +} + +impl_zip!(t0: T0, t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7, t8: T8, t9: T9, t10: T10, t11: T11); + +impl, P: Pass, A> FromIterator> for PassOutput { + fn from_iter>>(iter: I) -> Self { + P::output_from_iter(iter) + } +} + +trait PassDispatch: Sized { + type Input; + type Output; + fn build_reset_graph( + self, + input: Self::Input, + ) -> Self::Output; + fn substitute_resets( + self, + input: Self::Input, + ) -> Self::Output; +} + +trait Pass: Sized { + type Output; + fn output_new(v: T) -> PassOutput; + fn output_from_iter, A>( + iter: impl IntoIterator>, + ) -> PassOutput; + fn try_array_from_fn( + f: impl FnMut(usize) -> Result, E>, + ) -> Result, E>; + fn map(v: PassOutput, f: impl FnOnce(T) -> U) -> PassOutput; + fn zip(t: PassOutput, u: PassOutput) -> PassOutput<(T, U), Self>; + fn dispatch(dispatch: D, input: D::Input) -> D::Output; +} + +struct BuildResetGraph; + +impl Pass for BuildResetGraph { + type Output = (); + + fn output_new(_v: T) -> PassOutput { + PassOutput(()) + } + + fn output_from_iter, A>( + iter: impl IntoIterator>, + ) -> PassOutput { + iter.into_iter().for_each(|_| {}); + PassOutput(()) + } + + fn try_array_from_fn( + mut f: impl FnMut(usize) -> Result, E>, + ) -> Result, E> { + for i in 0..N { + f(i)?; + } + Ok(PassOutput(())) + } + + fn map(_v: PassOutput, _f: impl FnOnce(T) -> U) -> PassOutput { + PassOutput(()) + } + + fn zip(_t: PassOutput, _u: PassOutput) -> PassOutput<(T, U), Self> { + PassOutput(()) + } + + fn dispatch(dispatch: D, input: D::Input) -> D::Output { + dispatch.build_reset_graph(input) + } +} + +struct SubstituteResets; + +impl Pass for SubstituteResets { + type Output = T; + + fn output_new(v: T) -> PassOutput { + PassOutput(v) + } + + fn output_from_iter, A>( + iter: impl IntoIterator>, + ) -> PassOutput { + PassOutput(T::from_iter(iter.into_iter().map(|PassOutput(v)| v))) + } + + fn try_array_from_fn( + mut f: impl FnMut(usize) -> Result, E>, + ) -> Result, E> { + let mut retval = [const { None }; N]; + for i in 0..N { + retval[i] = Some(f(i)?.0); + } + Ok(PassOutput( + retval.map(|v| v.expect("just wrote Some to all elements")), + )) + } + + fn map(v: PassOutput, f: impl FnOnce(T) -> U) -> PassOutput { + PassOutput(f(v.0)) + } + + fn zip(t: PassOutput, u: PassOutput) -> PassOutput<(T, U), Self> { + PassOutput((t.0, u.0)) + } + + fn dispatch(dispatch: D, input: D::Input) -> D::Output { + dispatch.substitute_resets(input) + } +} + +struct PassArgs<'a, P: Pass> { + state: &'a mut State, + instantiated_module: InstantiatedModule, + fallback_error_source_location: SourceLocation, + _phantom: PhantomData

, +} + +impl<'a, P: Pass> fmt::Debug for PassArgs<'a, P> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + state, + instantiated_module, + fallback_error_source_location, + _phantom: _, + } = self; + f.debug_struct("PassArgs") + .field("state", state) + .field("instantiated_module", instantiated_module) + .field( + "fallback_error_source_location", + fallback_error_source_location, + ) + .finish() + } +} + +impl<'a, P: Pass> PassArgs<'a, P> { + fn as_mut(&mut self) -> PassArgs<'_, P> { + let PassArgs { + ref mut state, + instantiated_module, + fallback_error_source_location, + _phantom: _, + } = *self; + PassArgs { + state: &mut **state, + instantiated_module, + fallback_error_source_location, + _phantom: PhantomData, + } + } + fn get_resets(&self, expr: impl ToExpr) -> Option { + self.state.get_resets(self.instantiated_module, expr) + } + fn get_or_make_resets( + &mut self, + expr: impl ToExpr, + source_location: Option, + ) -> (Resets, bool) { + self.state + .get_or_make_resets(self.instantiated_module, expr, source_location) + } + fn union( + &mut self, + a: Resets, + b: Resets, + fallback_error_source_location: Option, + ) -> Result<(), DeduceResetsError> { + assert_eq!(a.layout, b.layout); + assert!( + a.ty.can_connect(b.ty), + "can't connect types! a:\n{a:?}\nb:\n{b:?}" + ); + for (a_node_index, b_node_index) in a.node_indexes.into_iter().zip(b.node_indexes) { + self.state.reset_graph.union( + a_node_index, + b_node_index, + fallback_error_source_location.unwrap_or(self.fallback_error_source_location), + )?; + } + Ok(()) + } +} + +trait RunPass: Sized { + fn run_pass( + &self, + pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError>; +} + +trait RunPassDispatch: Sized { + fn build_reset_graph( + &self, + pass_args: PassArgs<'_, BuildResetGraph>, + ) -> Result, DeduceResetsError>; + fn substitute_resets( + &self, + pass_args: PassArgs<'_, SubstituteResets>, + ) -> Result, DeduceResetsError>; + fn dispatch( + &self, + pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError> { + struct Dispatch<'a, T>(T, PhantomData<&'a mut ()>); + impl<'a, T: RunPassDispatch> PassDispatch for Dispatch<'a, &'_ T> { + type Input = PassArgs<'a, P>; + type Output = Result, DeduceResetsError>; + + fn build_reset_graph( + self, + input: Self::Input, + ) -> Self::Output { + self.0.build_reset_graph(input) + } + fn substitute_resets( + self, + input: Self::Input, + ) -> Self::Output { + self.0.substitute_resets(input) + } + } + P::dispatch(Dispatch(self, PhantomData), pass_args) + } +} + +impl RunPass

for T { + fn run_pass( + &self, + pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError> { + T::dispatch(self, pass_args) + } +} + +trait RunPassExpr: ToExpr + Sized { + type Args<'a>: IntoIterator> + 'a + where + Self: 'a; + fn args<'a>(&'a self) -> Self::Args<'a>; + fn source_location(&self) -> Option; + fn union_parts( + &self, + resets: Resets, + args_resets: Vec, + pass_args: PassArgs<'_, BuildResetGraph>, + ) -> Result<(), DeduceResetsError>; + fn new( + &self, + ty: CanonicalType, + new_args: Vec>, + ) -> Result; +} + +impl RunPassDispatch for T { + fn build_reset_graph( + &self, + mut pass_args: PassArgs<'_, BuildResetGraph>, + ) -> Result, DeduceResetsError> { + let source_location = self.source_location(); + let (resets, _) = pass_args.get_or_make_resets(self, source_location); + let args_resets = Result::from_iter(self.args().into_iter().map(|arg| { + arg.run_pass(pass_args.as_mut())?; + let (resets, _) = pass_args.get_or_make_resets(arg, source_location); + Ok(resets) + }))?; + self.union_parts(resets, args_resets, pass_args)?; + Ok(PassOutput(())) + } + + fn substitute_resets( + &self, + mut pass_args: PassArgs<'_, SubstituteResets>, + ) -> Result, DeduceResetsError> { + let source_location = self.source_location(); + let (resets, _) = pass_args.get_or_make_resets(self, source_location); + let ty = resets.substituted_type( + &mut pass_args.state.reset_graph, + pass_args.state.fallback_to_sync_reset, + pass_args.fallback_error_source_location, + )?; + let new_args = Result::from_iter( + self.args() + .into_iter() + .map(|arg| Ok(arg.run_pass(pass_args.as_mut())?.0)), + )?; + Ok(PassOutput(self.new(ty, new_args)?)) + } +} + +impl + Intern + Clone, P: Pass> RunPass

for Interned { + fn run_pass( + &self, + pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError> { + Ok(T::run_pass(self, pass_args)?.map(Intern::intern_sized)) + } +} + +impl + Clone, P: Pass> RunPass

for Interned<[T]> +where + [T]: Intern, +{ + fn run_pass( + &self, + mut pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError> { + Result::from_iter(self.iter().map(|v| v.run_pass(pass_args.as_mut()))) + } +} + +impl, P: Pass, const N: usize> RunPass

for [T; N] { + fn run_pass( + &self, + mut pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError> { + P::try_array_from_fn(|i| self[i].run_pass(pass_args.as_mut())) + } +} + +impl, P: Pass> RunPass

for Option { + fn run_pass( + &self, + pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError> { + match self { + Some(v) => Ok(v.run_pass(pass_args)?.map(Some)), + None => Ok(PassOutput::new(None)), + } + } +} + +fn reg_expr_run_pass( + reg: &Reg, + pass_args: PassArgs<'_, P>, +) -> Result, DeduceResetsError> { + Ok(AnyReg::from(*reg) + .run_pass(pass_args)? + .map(|reg| match_any_reg!(reg, ExprEnum::from))) +} + +fn cast_bit_op( + expr: impl ToExpr, + arg: Expr, + pass_args: PassArgs<'_, P>, +) -> Result, DeduceResetsError> { + struct Dispatch<'a, T: Type, A: Type> { + expr: Expr, + arg: Expr, + _phantom: PhantomData<&'a mut ()>, + } + impl<'a, T: Type, A: Type> PassDispatch for Dispatch<'a, T, A> { + type Input = PassArgs<'a, P>; + type Output = Result, DeduceResetsError>; + + fn build_reset_graph( + self, + mut pass_args: Self::Input, + ) -> Self::Output { + Expr::canonical(self.arg).run_pass(pass_args.as_mut())?; + let (expr_resets, _) = pass_args.get_or_make_resets(self.expr, None); + let (arg_resets, _) = pass_args.get_or_make_resets(self.arg, None); + // don't use PassArgs::union since types don't match and we want to just union resets if they exist + for (expr_node_index, arg_node_index) in expr_resets + .node_indexes + .into_iter() + .zip(arg_resets.node_indexes) + { + pass_args.state.reset_graph.union( + expr_node_index, + arg_node_index, + pass_args.fallback_error_source_location, + )?; + } + Ok(PassOutput(())) + } + + fn substitute_resets( + self, + mut pass_args: Self::Input, + ) -> Self::Output { + let resets = pass_args + .get_resets(self.expr) + .expect("added resets in build_reset_graph"); + let arg = Expr::canonical(self.arg).run_pass(pass_args.as_mut())?; + let ty = resets.substituted_type( + &mut pass_args.state.reset_graph, + pass_args.state.fallback_to_sync_reset, + pass_args.fallback_error_source_location, + )?; + Ok(arg.map(|arg| { + macro_rules! match_expr_ty { + ($arg:ident, $($Variant:ident),*) => { + match ty { + CanonicalType::Array(_) + | CanonicalType::Enum(_) + | CanonicalType::Bundle(_) + | CanonicalType::Reset(_) + | CanonicalType::PhantomConst(_) => unreachable!(), + $(CanonicalType::$Variant(ty) => Expr::expr_enum($arg.cast_to(ty)),)* + } + }; + } + macro_rules! match_arg_ty { + ($($Variant:ident),*) => { + *match Expr::ty(arg) { + CanonicalType::Array(_) + | CanonicalType::Enum(_) + | CanonicalType::Bundle(_) + | CanonicalType::Reset(_) => unreachable!(), + CanonicalType::PhantomConst(_) => Expr::expr_enum(arg), + $(CanonicalType::$Variant(_) => { + let arg = Expr::<$Variant>::from_canonical(arg); + match_expr_ty!(arg, UInt, SInt, Bool, AsyncReset, SyncReset, Clock) + })* + } + }; + } + match_arg_ty!(UInt, SInt, Bool, AsyncReset, SyncReset, Clock) + })) + } + } + P::dispatch( + Dispatch { + expr: expr.to_expr(), + arg, + _phantom: PhantomData, + }, + pass_args, + ) +} + +impl RunPass

for ExprEnum { + fn run_pass( + &self, + pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError> { + match self { + ExprEnum::UIntLiteral(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::SIntLiteral(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::BoolLiteral(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::PhantomConst(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::BundleLiteral(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::ArrayLiteral(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::EnumLiteral(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::Uninit(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::NotU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::NotS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::NotB(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::Neg(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::BitAndU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::BitAndS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::BitAndB(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::BitOrU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::BitOrS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::BitOrB(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::BitXorU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::BitXorS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::BitXorB(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::AddU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::AddS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::SubU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::SubS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::MulU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::MulS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::DivU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::DivS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::RemU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::RemS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::DynShlU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::DynShlS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::DynShrU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::DynShrS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::FixedShlU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::FixedShlS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::FixedShrU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::FixedShrS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CmpLtB(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CmpLeB(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CmpGtB(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CmpGeB(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CmpEqB(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CmpNeB(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CmpLtU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CmpLeU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CmpGtU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CmpGeU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CmpEqU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CmpNeU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CmpLtS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CmpLeS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CmpGtS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CmpGeS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CmpEqS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CmpNeS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CastUIntToUInt(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CastUIntToSInt(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CastSIntToUInt(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CastSIntToSInt(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CastBoolToUInt(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CastBoolToSInt(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CastUIntToBool(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CastSIntToBool(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CastBoolToSyncReset(expr) => cast_bit_op(expr, expr.arg(), pass_args), + ExprEnum::CastUIntToSyncReset(expr) => cast_bit_op(expr, expr.arg(), pass_args), + ExprEnum::CastSIntToSyncReset(expr) => cast_bit_op(expr, expr.arg(), pass_args), + ExprEnum::CastBoolToAsyncReset(expr) => cast_bit_op(expr, expr.arg(), pass_args), + ExprEnum::CastUIntToAsyncReset(expr) => cast_bit_op(expr, expr.arg(), pass_args), + ExprEnum::CastSIntToAsyncReset(expr) => cast_bit_op(expr, expr.arg(), pass_args), + ExprEnum::CastSyncResetToBool(expr) => cast_bit_op(expr, expr.arg(), pass_args), + ExprEnum::CastSyncResetToUInt(expr) => cast_bit_op(expr, expr.arg(), pass_args), + ExprEnum::CastSyncResetToSInt(expr) => cast_bit_op(expr, expr.arg(), pass_args), + ExprEnum::CastSyncResetToReset(expr) => cast_bit_op(expr, expr.arg(), pass_args), + ExprEnum::CastAsyncResetToBool(expr) => cast_bit_op(expr, expr.arg(), pass_args), + ExprEnum::CastAsyncResetToUInt(expr) => cast_bit_op(expr, expr.arg(), pass_args), + ExprEnum::CastAsyncResetToSInt(expr) => cast_bit_op(expr, expr.arg(), pass_args), + ExprEnum::CastAsyncResetToReset(expr) => cast_bit_op(expr, expr.arg(), pass_args), + ExprEnum::CastResetToBool(expr) => cast_bit_op(expr, expr.arg(), pass_args), + ExprEnum::CastResetToUInt(expr) => cast_bit_op(expr, expr.arg(), pass_args), + ExprEnum::CastResetToSInt(expr) => cast_bit_op(expr, expr.arg(), pass_args), + ExprEnum::CastBoolToClock(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CastUIntToClock(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CastSIntToClock(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CastClockToBool(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CastClockToUInt(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CastClockToSInt(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::FieldAccess(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::VariantAccess(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::ArrayIndex(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::DynArrayIndex(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::ReduceBitAndU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::ReduceBitAndS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::ReduceBitOrU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::ReduceBitOrS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::ReduceBitXorU(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::ReduceBitXorS(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::SliceUInt(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::SliceSInt(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CastToBits(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::CastBitsTo(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::ModuleIO(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::Instance(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::Wire(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + ExprEnum::Reg(expr) => reg_expr_run_pass(expr, pass_args), + ExprEnum::RegSync(expr) => reg_expr_run_pass(expr, pass_args), + ExprEnum::RegAsync(expr) => reg_expr_run_pass(expr, pass_args), + ExprEnum::MemPort(expr) => Ok(expr.run_pass(pass_args)?.map(ExprEnum::from)), + } + } +} + +impl RunPass

for Expr { + fn run_pass( + &self, + pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError> { + Ok(Expr::expr_enum(*self) + .run_pass(pass_args)? + .map(|expr_enum| expr_enum.to_expr())) + } +} + +impl RunPass

for Expr> { + fn run_pass( + &self, + pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError> { + Ok(Expr::canonical(*self) + .run_pass(pass_args)? + .map(Expr::from_canonical)) + } +} + +impl RunPass

for Expr> { + fn run_pass( + &self, + pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError> { + Ok(Expr::canonical(*self) + .run_pass(pass_args)? + .map(Expr::from_canonical)) + } +} + +impl RunPass

for Expr { + fn run_pass( + &self, + pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError> { + Ok(Expr::canonical(*self) + .run_pass(pass_args)? + .map(Expr::from_canonical)) + } +} + +impl RunPass

for Expr { + fn run_pass( + &self, + pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError> { + Ok(Expr::canonical(*self) + .run_pass(pass_args)? + .map(Expr::from_canonical)) + } +} + +impl RunPass

for Expr { + fn run_pass( + &self, + pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError> { + Ok(Expr::canonical(*self) + .run_pass(pass_args)? + .map(Expr::from_canonical)) + } +} + +impl RunPassExpr for ops::Uninit { + type Args<'a> = [Expr; 0]; + + fn args<'a>(&'a self) -> Self::Args<'a> { + [] + } + + fn source_location(&self) -> Option { + None + } + + fn union_parts( + &self, + _resets: Resets, + _args_resets: Vec, + _pass_args: PassArgs<'_, BuildResetGraph>, + ) -> Result<(), DeduceResetsError> { + Ok(()) + } + + fn new( + &self, + ty: CanonicalType, + _new_args: Vec>, + ) -> Result { + Ok(ops::Uninit::new(ty)) + } +} + +impl RunPassExpr for ops::BundleLiteral { + type Args<'a> = Interned<[Expr]>; + + fn args<'a>(&'a self) -> Self::Args<'a> { + self.field_values() + } + + fn source_location(&self) -> Option { + None + } + + fn union_parts( + &self, + resets: Resets, + args_resets: Vec, + mut pass_args: PassArgs<'_, BuildResetGraph>, + ) -> Result<(), DeduceResetsError> { + for (resets_field, field_expr_resets) in resets.bundle_fields().zip(args_resets) { + pass_args.union(resets_field, field_expr_resets, None)?; + } + Ok(()) + } + + fn new( + &self, + ty: CanonicalType, + new_args: Vec>, + ) -> Result { + Ok(ops::BundleLiteral::new( + Bundle::from_canonical(ty), + Intern::intern_owned(new_args), + )) + } +} + +impl RunPassExpr for ArrayLiteral { + type Args<'a> = Interned<[Expr]>; + + fn args<'a>(&'a self) -> Self::Args<'a> { + self.element_values() + } + + fn source_location(&self) -> Option { + None + } + + fn union_parts( + &self, + resets: Resets, + args_resets: Vec, + mut pass_args: PassArgs<'_, BuildResetGraph>, + ) -> Result<(), DeduceResetsError> { + let resets_elements = resets.array_elements(); + for arg_resets in args_resets { + pass_args.union(resets_elements, arg_resets, None)?; + } + Ok(()) + } + + fn new( + &self, + ty: CanonicalType, + new_args: Vec>, + ) -> Result { + Ok(Self::new( + ::from_canonical(ty).element(), + Intern::intern_owned(new_args), + )) + } +} + +impl RunPassExpr for ops::EnumLiteral { + type Args<'a> = Option>; + + fn args<'a>(&'a self) -> Self::Args<'a> { + self.variant_value() + } + + fn source_location(&self) -> Option { + None + } + + fn union_parts( + &self, + resets: Resets, + args_resets: Vec, + mut pass_args: PassArgs<'_, BuildResetGraph>, + ) -> Result<(), DeduceResetsError> { + if let Some(Some(variant_resets)) = resets.enum_variants().nth(self.variant_index()) { + pass_args.union(variant_resets, args_resets[0], None)?; + } + Ok(()) + } + + fn new( + &self, + ty: CanonicalType, + new_args: Vec>, + ) -> Result { + Ok(Self::new_by_index( + Enum::from_canonical(ty), + self.variant_index(), + new_args.get(0).copied(), + )) + } +} + +impl RunPassExpr for ops::FieldAccess { + type Args<'a> = [Expr; 1]; + + fn args<'a>(&'a self) -> Self::Args<'a> { + [Expr::canonical(self.base())] + } + + fn source_location(&self) -> Option { + None + } + + fn union_parts( + &self, + resets: Resets, + args_resets: Vec, + mut pass_args: PassArgs<'_, BuildResetGraph>, + ) -> Result<(), DeduceResetsError> { + let Some(field_resets) = args_resets[0].bundle_fields().nth(self.field_index()) else { + unreachable!(); + }; + pass_args.union(resets, field_resets, None) + } + + fn new( + &self, + _ty: CanonicalType, + new_args: Vec>, + ) -> Result { + Ok(Self::new_by_index( + Expr::from_canonical(new_args[0]), + self.field_index(), + )) + } +} + +impl RunPassExpr for ops::VariantAccess { + type Args<'a> = [Expr; 1]; + + fn args<'a>(&'a self) -> Self::Args<'a> { + [Expr::canonical(self.base())] + } + + fn source_location(&self) -> Option { + None + } + + fn union_parts( + &self, + resets: Resets, + args_resets: Vec, + mut pass_args: PassArgs<'_, BuildResetGraph>, + ) -> Result<(), DeduceResetsError> { + if let Some(Some(variant_resets)) = args_resets[0].enum_variants().nth(self.variant_index()) + { + pass_args.union(resets, variant_resets, None)?; + } + Ok(()) + } + + fn new( + &self, + _ty: CanonicalType, + new_args: Vec>, + ) -> Result { + Ok(Self::new_by_index( + Expr::from_canonical(new_args[0]), + self.variant_index(), + )) + } +} + +impl RunPassExpr for ops::ArrayIndex { + type Args<'a> = [Expr; 1]; + + fn args<'a>(&'a self) -> Self::Args<'a> { + [Expr::canonical(self.base())] + } + + fn source_location(&self) -> Option { + None + } + + fn union_parts( + &self, + resets: Resets, + args_resets: Vec, + mut pass_args: PassArgs<'_, BuildResetGraph>, + ) -> Result<(), DeduceResetsError> { + pass_args.union(resets, args_resets[0].array_elements(), None) + } + + fn new( + &self, + _ty: CanonicalType, + new_args: Vec>, + ) -> Result { + Ok(Self::new( + Expr::from_canonical(new_args[0]), + self.element_index(), + )) + } +} + +impl RunPassExpr for ops::DynArrayIndex { + type Args<'a> = [Expr; 2]; + + fn args<'a>(&'a self) -> Self::Args<'a> { + [ + Expr::canonical(self.base()), + Expr::canonical(self.element_index()), + ] + } + + fn source_location(&self) -> Option { + None + } + + fn union_parts( + &self, + resets: Resets, + args_resets: Vec, + mut pass_args: PassArgs<'_, BuildResetGraph>, + ) -> Result<(), DeduceResetsError> { + pass_args.union(resets, args_resets[0].array_elements(), None) + } + + fn new( + &self, + _ty: CanonicalType, + new_args: Vec>, + ) -> Result { + Ok(Self::new( + Expr::from_canonical(new_args[0]), + Expr::from_canonical(new_args[1]), + )) + } +} + +impl RunPassExpr for ops::CastBitsTo { + type Args<'a> = [Expr; 1]; + + fn args<'a>(&'a self) -> Self::Args<'a> { + [Expr::canonical(self.arg())] + } + + fn source_location(&self) -> Option { + None + } + + fn union_parts( + &self, + _resets: Resets, + _args_resets: Vec, + _pass_args: PassArgs<'_, BuildResetGraph>, + ) -> Result<(), DeduceResetsError> { + Ok(()) + } + + fn new( + &self, + ty: CanonicalType, + new_args: Vec>, + ) -> Result { + Ok(Self::new(Expr::from_canonical(new_args[0]), ty)) + } +} + +impl RunPassExpr for ModuleIO { + type Args<'a> = [Expr; 0]; + + fn args<'a>(&'a self) -> Self::Args<'a> { + [] + } + + fn source_location(&self) -> Option { + Some(self.source_location()) + } + + fn union_parts( + &self, + _resets: Resets, + _args_resets: Vec, + _pass_args: PassArgs<'_, BuildResetGraph>, + ) -> Result<(), DeduceResetsError> { + Ok(()) + } + + fn new( + &self, + ty: CanonicalType, + _new_args: Vec>, + ) -> Result { + Ok(Self::new_unchecked( + self.containing_module_name_id(), + self.name_id(), + self.source_location(), + self.is_input(), + ty, + )) + } +} + +impl RunPassExpr for Wire { + type Args<'a> = [Expr; 0]; + + fn args<'a>(&'a self) -> Self::Args<'a> { + [] + } + + fn source_location(&self) -> Option { + Some(self.source_location()) + } + + fn union_parts( + &self, + _resets: Resets, + _args_resets: Vec, + _pass_args: PassArgs<'_, BuildResetGraph>, + ) -> Result<(), DeduceResetsError> { + Ok(()) + } + + fn new( + &self, + ty: CanonicalType, + _new_args: Vec>, + ) -> Result { + Ok(Self::new_unchecked( + self.scoped_name(), + self.source_location(), + ty, + )) + } +} + +impl From> for AnyReg { + fn from(value: Reg) -> Self { + struct Dispatch; + impl ResetTypeDispatch for Dispatch { + type Input = Reg; + type Output = AnyReg; + + fn reset(self, input: Self::Input) -> Self::Output { + AnyReg::Reg(input) + } + + fn sync_reset(self, input: Self::Input) -> Self::Output { + AnyReg::RegSync(input) + } + + fn async_reset(self, input: Self::Input) -> Self::Output { + AnyReg::RegAsync(input) + } + } + T::dispatch(value, Dispatch) + } +} + +impl RunPassDispatch for AnyReg { + fn build_reset_graph( + &self, + mut pass_args: PassArgs<'_, BuildResetGraph>, + ) -> Result, DeduceResetsError> { + match_any_reg!(self, |reg: &Reg| { + pass_args + .get_or_make_resets(Expr::canonical(reg.to_expr()), Some(reg.source_location())); + reg.init().run_pass(pass_args.as_mut())?; + Expr::canonical(reg.clock_domain()).run_pass(pass_args)?; + Ok(PassOutput(())) + }) + } + + fn substitute_resets( + &self, + mut pass_args: PassArgs<'_, SubstituteResets>, + ) -> Result, DeduceResetsError> { + match_any_reg!(self, |reg: &Reg| { + let scoped_name = reg.scoped_name(); + let source_location = reg.source_location(); + let resets = pass_args + .get_resets(Expr::canonical(reg.to_expr())) + .expect("added resets in build_reset_graph"); + let ty = resets.substituted_type( + &mut pass_args.state.reset_graph, + pass_args.state.fallback_to_sync_reset, + source_location, + )?; + let init = reg.init().run_pass(pass_args.as_mut())?.0; + let clock_domain = Expr::::from_canonical( + Expr::canonical(reg.clock_domain()).run_pass(pass_args)?.0, + ); + match Expr::ty(clock_domain) + .field_by_name("rst".intern()) + .expect("ClockDomain has rst field") + .ty + { + CanonicalType::AsyncReset(_) => { + Ok(PassOutput(AnyReg::RegAsync(Reg::new_unchecked( + scoped_name, + source_location, + ty, + Expr::from_bundle(clock_domain), + init, + )))) + } + CanonicalType::SyncReset(_) => Ok(PassOutput(AnyReg::RegSync(Reg::new_unchecked( + scoped_name, + source_location, + ty, + Expr::from_bundle(clock_domain), + init, + )))), + CanonicalType::UInt(_) + | CanonicalType::SInt(_) + | CanonicalType::Bool(_) + | CanonicalType::Array(_) + | CanonicalType::Enum(_) + | CanonicalType::Bundle(_) + | CanonicalType::Reset(_) + | CanonicalType::Clock(_) + | CanonicalType::PhantomConst(_) => unreachable!(), + } + }) + } +} + +impl RunPassDispatch for Instance { + fn build_reset_graph( + &self, + mut pass_args: PassArgs<'_, BuildResetGraph>, + ) -> Result, DeduceResetsError> { + self.instantiated().run_pass(PassArgs:: { + state: pass_args.state, + instantiated_module: InstantiatedModule::Child { + parent: pass_args.instantiated_module.intern_sized(), + instance: self.intern(), + }, + fallback_error_source_location: self.instantiated().source_location(), + _phantom: PhantomData, + })?; + let (resets, _) = pass_args.get_or_make_resets(self, Some(self.source_location())); + for (resets_field, module_io) in resets.bundle_fields().zip(self.instantiated().module_io()) + { + let (module_io_resets, _) = pass_args.get_or_make_resets( + module_io.module_io, + Some(self.instantiated().source_location()), + ); + pass_args.union(resets_field, module_io_resets, Some(self.source_location()))?; + } + Ok(PassOutput(())) + } + + fn substitute_resets( + &self, + pass_args: PassArgs<'_, SubstituteResets>, + ) -> Result, DeduceResetsError> { + let PassOutput(instantiated) = + self.instantiated().run_pass(PassArgs:: { + state: pass_args.state, + instantiated_module: InstantiatedModule::Child { + parent: pass_args.instantiated_module.intern_sized(), + instance: self.intern(), + }, + fallback_error_source_location: self.instantiated().source_location(), + _phantom: PhantomData, + })?; + Ok(PassOutput(Self::new_unchecked( + self.scoped_name(), + instantiated, + self.source_location(), + ))) + } +} + +macro_rules! impl_run_pass_copy { + ([$($generics:tt)*] $ty:ty) => { + impl RunPass

for $ty { + fn run_pass( + &self, + _pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError> { + Ok(PassOutput::new(*self)) + } + } + }; +} + +macro_rules! impl_run_pass_clone { + ([$($generics:tt)*] $ty:ty) => { + impl RunPass

for $ty { + fn run_pass( + &self, + _pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError> { + Ok(PassOutput::from_fn(|| self.clone())) + } + } + }; +} + +impl_run_pass_clone!([] BigInt); +impl_run_pass_clone!([] SIntValue); +impl_run_pass_clone!([] std::ops::Range); +impl_run_pass_clone!([] UIntValue); +impl_run_pass_copy!([] BlackBoxInlineAnnotation); +impl_run_pass_copy!([] BlackBoxPathAnnotation); +impl_run_pass_copy!([] bool); +impl_run_pass_copy!([] CustomFirrtlAnnotation); +impl_run_pass_copy!([] DocStringAnnotation); +impl_run_pass_copy!([] DontTouchAnnotation); +impl_run_pass_copy!([] ExternModuleBody); +impl_run_pass_copy!([] Interned); +impl_run_pass_copy!([] NameId); +impl_run_pass_copy!([] SInt); +impl_run_pass_copy!([] SourceLocation); +impl_run_pass_copy!([] SVAttributeAnnotation); +impl_run_pass_copy!([] UInt); +impl_run_pass_copy!([] usize); +impl_run_pass_copy!([] FormalKind); +impl_run_pass_copy!([] PhantomConst); + +macro_rules! impl_run_pass_for_struct { + ( + $(#[adjust_pass_args = $adjust_pass_args:expr])? + #[constructor = $constructor:expr] + impl[$($generics:tt)*] $RunPass:ident for $ty:ty { + $($field:ident $(($($args:tt)*))?: _,)* + } + ) => { + impl $RunPass

for $ty { + #[allow(unused_mut, unused_variables)] + fn run_pass( + &self, + mut pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError> { + $($adjust_pass_args(self, &mut pass_args);)? + Ok(($(self.$field$(($($args)*))?.run_pass(pass_args.as_mut())?,)*).call(|($($field,)*)| $constructor)) + } + } + }; + ( + $(#[adjust_pass_args = $adjust_pass_args:expr])? + impl[$($generics:tt)*] $RunPass:ident for $ty:ty { + $($field:ident: _,)* + } + ) => { + impl_run_pass_for_struct! { + $(#[adjust_pass_args = $adjust_pass_args])? + #[constructor = { + type Ty = T; + Ty::<$ty> { $($field,)* } + }] + impl[$($generics)*] $RunPass for $ty { + $($field: _,)* + } + } + }; +} + +macro_rules! impl_run_pass_for_enum { + (impl[$($generics:tt)*] $RunPass:ident for $ty:ty { + $($variant:ident($arg:ident),)* + }) => { + impl $RunPass

for $ty { + fn run_pass( + &self, + pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError> { + type Ty = T; + match self { + $(Ty::<$ty>::$variant($arg) => Ok($arg.run_pass(pass_args)?.map(<$ty>::$variant)),)* + } + } + } + }; +} + +macro_rules! impl_run_pass_for_unary_op { + ($path:path) => { + impl_run_pass_for_struct! { + #[constructor = <$path>::new(arg)] + impl[] RunPass for $path { + arg(): _, + } + } + }; +} + +impl_run_pass_for_unary_op!(ops::NotU); +impl_run_pass_for_unary_op!(ops::NotS); +impl_run_pass_for_unary_op!(ops::NotB); +impl_run_pass_for_unary_op!(ops::Neg); +impl_run_pass_for_unary_op!(ops::CastBoolToUInt); +impl_run_pass_for_unary_op!(ops::CastBoolToSInt); +impl_run_pass_for_unary_op!(ops::CastUIntToBool); +impl_run_pass_for_unary_op!(ops::CastSIntToBool); +impl_run_pass_for_unary_op!(ops::CastBoolToClock); +impl_run_pass_for_unary_op!(ops::CastUIntToClock); +impl_run_pass_for_unary_op!(ops::CastSIntToClock); +impl_run_pass_for_unary_op!(ops::CastClockToBool); +impl_run_pass_for_unary_op!(ops::CastClockToUInt); +impl_run_pass_for_unary_op!(ops::CastClockToSInt); +impl_run_pass_for_unary_op!(ops::ReduceBitAndU); +impl_run_pass_for_unary_op!(ops::ReduceBitAndS); +impl_run_pass_for_unary_op!(ops::ReduceBitOrU); +impl_run_pass_for_unary_op!(ops::ReduceBitOrS); +impl_run_pass_for_unary_op!(ops::ReduceBitXorU); +impl_run_pass_for_unary_op!(ops::ReduceBitXorS); + +macro_rules! impl_run_pass_for_binary_op { + ($path:path) => { + impl_run_pass_for_struct! { + #[constructor = <$path>::new(lhs, rhs)] + impl[] RunPass for $path { + lhs(): _, + rhs(): _, + } + } + }; +} + +impl_run_pass_for_binary_op!(ops::BitAndU); +impl_run_pass_for_binary_op!(ops::BitAndS); +impl_run_pass_for_binary_op!(ops::BitAndB); +impl_run_pass_for_binary_op!(ops::BitOrU); +impl_run_pass_for_binary_op!(ops::BitOrS); +impl_run_pass_for_binary_op!(ops::BitOrB); +impl_run_pass_for_binary_op!(ops::BitXorU); +impl_run_pass_for_binary_op!(ops::BitXorS); +impl_run_pass_for_binary_op!(ops::BitXorB); +impl_run_pass_for_binary_op!(ops::AddU); +impl_run_pass_for_binary_op!(ops::AddS); +impl_run_pass_for_binary_op!(ops::SubU); +impl_run_pass_for_binary_op!(ops::SubS); +impl_run_pass_for_binary_op!(ops::MulU); +impl_run_pass_for_binary_op!(ops::MulS); +impl_run_pass_for_binary_op!(ops::DivU); +impl_run_pass_for_binary_op!(ops::DivS); +impl_run_pass_for_binary_op!(ops::RemU); +impl_run_pass_for_binary_op!(ops::RemS); +impl_run_pass_for_binary_op!(ops::DynShlU); +impl_run_pass_for_binary_op!(ops::DynShlS); +impl_run_pass_for_binary_op!(ops::DynShrU); +impl_run_pass_for_binary_op!(ops::DynShrS); +impl_run_pass_for_binary_op!(ops::FixedShlU); +impl_run_pass_for_binary_op!(ops::FixedShlS); +impl_run_pass_for_binary_op!(ops::FixedShrU); +impl_run_pass_for_binary_op!(ops::FixedShrS); +impl_run_pass_for_binary_op!(ops::CmpLtB); +impl_run_pass_for_binary_op!(ops::CmpLeB); +impl_run_pass_for_binary_op!(ops::CmpGtB); +impl_run_pass_for_binary_op!(ops::CmpGeB); +impl_run_pass_for_binary_op!(ops::CmpEqB); +impl_run_pass_for_binary_op!(ops::CmpNeB); +impl_run_pass_for_binary_op!(ops::CmpLtU); +impl_run_pass_for_binary_op!(ops::CmpLeU); +impl_run_pass_for_binary_op!(ops::CmpGtU); +impl_run_pass_for_binary_op!(ops::CmpGeU); +impl_run_pass_for_binary_op!(ops::CmpEqU); +impl_run_pass_for_binary_op!(ops::CmpNeU); +impl_run_pass_for_binary_op!(ops::CmpLtS); +impl_run_pass_for_binary_op!(ops::CmpLeS); +impl_run_pass_for_binary_op!(ops::CmpGtS); +impl_run_pass_for_binary_op!(ops::CmpGeS); +impl_run_pass_for_binary_op!(ops::CmpEqS); +impl_run_pass_for_binary_op!(ops::CmpNeS); + +macro_rules! impl_run_pass_for_int_cast_op { + ($path:path) => { + impl_run_pass_for_struct! { + #[constructor = <$path>::new(arg, ty)] + impl[] RunPass for $path { + arg(): _, + ty(): _, + } + } + }; +} + +impl_run_pass_for_int_cast_op!(ops::CastUIntToUInt); +impl_run_pass_for_int_cast_op!(ops::CastUIntToSInt); +impl_run_pass_for_int_cast_op!(ops::CastSIntToUInt); +impl_run_pass_for_int_cast_op!(ops::CastSIntToSInt); + +impl_run_pass_for_struct! { + #[constructor = ops::SliceUInt::new(base, range)] + impl[] RunPass for ops::SliceUInt { + base(): _, + range(): _, + } +} + +impl_run_pass_for_struct! { + #[constructor = ops::SliceSInt::new(base, range)] + impl[] RunPass for ops::SliceSInt { + base(): _, + range(): _, + } +} + +impl_run_pass_for_struct! { + #[constructor = ops::CastToBits::new(arg)] + impl[] RunPass for ops::CastToBits { + arg(): _, + } +} + +impl_run_pass_for_struct! { + impl[] RunPass for StmtFormal { + kind: _, + clk: _, + pred: _, + en: _, + text: _, + source_location: _, + } +} + +impl_run_pass_for_struct! { + impl[] RunPass for StmtIf { + cond: _, + source_location: _, + blocks: _, + } +} + +impl_run_pass_for_struct! { + impl[] RunPass for StmtMatch { + expr: _, + source_location: _, + blocks: _, + } +} + +impl_run_pass_for_struct! { + impl[] RunPass for StmtWire { + annotations: _, + wire: _, + } +} + +impl_run_pass_for_struct! { + impl[] RunPass for StmtInstance { + annotations: _, + instance: _, + } +} + +impl_run_pass_for_enum! { + impl[] RunPass for Stmt { + Connect(v), + Formal(v), + If(v), + Match(v), + Declaration(v), + } +} + +impl_run_pass_for_struct! { + impl[] RunPass for Block { + memories: _, + stmts: _, + } +} + +impl_run_pass_for_struct! { + impl[] RunPass for NormalModuleBody { + body: _, + } +} + +impl_run_pass_copy!([] MemPort); // Mem can't contain any `Reset` types +impl_run_pass_copy!([] Mem); // Mem can't contain any `Reset` types + +impl RunPassDispatch for StmtConnect { + fn build_reset_graph( + &self, + mut pass_args: PassArgs<'_, BuildResetGraph>, + ) -> Result, DeduceResetsError> { + let Self { + lhs, + rhs, + source_location, + } = *self; + pass_args.fallback_error_source_location = source_location; + lhs.run_pass(pass_args.as_mut())?; + rhs.run_pass(pass_args.as_mut())?; + let (lhs_resets, _) = pass_args.get_or_make_resets(lhs, Some(source_location)); + let (rhs_resets, _) = pass_args.get_or_make_resets(rhs, Some(source_location)); + pass_args.union(lhs_resets, rhs_resets, Some(source_location))?; + Ok(PassOutput(())) + } + + fn substitute_resets( + &self, + mut pass_args: PassArgs<'_, SubstituteResets>, + ) -> Result, DeduceResetsError> { + let StmtConnect { + lhs, + rhs, + source_location, + } = *self; + pass_args.fallback_error_source_location = source_location; + let lhs = lhs.run_pass(pass_args.as_mut())?.0; + let rhs = rhs.run_pass(pass_args)?.0; + Ok(PassOutput(StmtConnect { + lhs, + rhs, + source_location, + })) + } +} + +impl RunPass

for TargetBase { + fn run_pass( + &self, + pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError> { + let reg: AnyReg = match self { + TargetBase::ModuleIO(v) => return Ok(v.run_pass(pass_args)?.map(TargetBase::ModuleIO)), + TargetBase::MemPort(v) => return Ok(v.run_pass(pass_args)?.map(TargetBase::MemPort)), + &TargetBase::Reg(v) => v.into(), + &TargetBase::RegSync(v) => v.into(), + &TargetBase::RegAsync(v) => v.into(), + TargetBase::Wire(v) => return Ok(v.run_pass(pass_args)?.map(TargetBase::Wire)), + TargetBase::Instance(v) => return Ok(v.run_pass(pass_args)?.map(TargetBase::Instance)), + }; + Ok(reg.run_pass(pass_args)?.map(|reg| match reg { + AnyReg::Reg(reg) => TargetBase::Reg(reg), + AnyReg::RegSync(reg) => TargetBase::RegSync(reg), + AnyReg::RegAsync(reg) => TargetBase::RegAsync(reg), + })) + } +} + +impl RunPass

for StmtDeclaration { + fn run_pass( + &self, + mut pass_args: PassArgs<'_, P>, + ) -> Result, DeduceResetsError> { + let (annotations, reg) = match self { + StmtDeclaration::Wire(v) => { + return Ok(v.run_pass(pass_args)?.map(StmtDeclaration::Wire)) + } + &StmtDeclaration::Reg(StmtReg { annotations, reg }) => (annotations, AnyReg::from(reg)), + &StmtDeclaration::RegSync(StmtReg { annotations, reg }) => { + (annotations, AnyReg::from(reg)) + } + &StmtDeclaration::RegAsync(StmtReg { annotations, reg }) => { + (annotations, AnyReg::from(reg)) + } + StmtDeclaration::Instance(v) => { + return Ok(v.run_pass(pass_args)?.map(StmtDeclaration::Instance)) + } + }; + let annotations = annotations.run_pass(pass_args.as_mut())?; + let reg = reg.run_pass(pass_args)?; + Ok((annotations, reg).call(|(annotations, reg)| match reg { + AnyReg::Reg(reg) => StmtReg { annotations, reg }.into(), + AnyReg::RegSync(reg) => StmtReg { annotations, reg }.into(), + AnyReg::RegAsync(reg) => StmtReg { annotations, reg }.into(), + })) + } +} + +impl_run_pass_for_struct! { + impl[] RunPass for TargetPathBundleField { + name: _, + } +} + +impl_run_pass_for_struct! { + impl[] RunPass for TargetPathArrayElement { + index: _, + } +} + +impl_run_pass_for_struct! { + impl[] RunPass for TargetPathDynArrayElement {} +} + +impl_run_pass_for_enum! { + impl[] RunPass for TargetPathElement { + BundleField(v), + ArrayElement(v), + DynArrayElement(v), + } +} + +impl_run_pass_for_enum! { + impl[] RunPass for Target { + Base(v), + Child(v), + } +} + +impl_run_pass_for_struct! { + #[constructor = TargetChild::new(parent, path_element)] + impl[] RunPass for TargetChild { + parent(): _, + path_element(): _, + } +} + +impl_run_pass_for_enum! { + impl[] RunPass for Annotation { + DontTouch(v), + SVAttribute(v), + BlackBoxInline(v), + BlackBoxPath(v), + DocString(v), + CustomFirrtl(v), + } +} + +impl_run_pass_for_enum! { + impl[] RunPass for ModuleBody { + Normal(v), + Extern(v), + } +} + +impl_run_pass_for_struct! { + #[constructor = TargetedAnnotation::new(target, annotation)] + impl[] RunPass for TargetedAnnotation { + target(): _, + annotation(): _, + } +} + +impl_run_pass_for_struct! { + impl[] RunPass for AnnotatedModuleIO { + annotations: _, + module_io: _, + } +} + +impl RunPassDispatch for Module { + fn build_reset_graph( + &self, + mut pass_args: PassArgs<'_, BuildResetGraph>, + ) -> Result, DeduceResetsError> { + pass_args.fallback_error_source_location = self.source_location(); + if pass_args + .state + .modules_added_to_graph + .insert(pass_args.instantiated_module) + { + self.name_id().run_pass(pass_args.as_mut())?; + self.source_location().run_pass(pass_args.as_mut())?; + self.module_io().run_pass(pass_args.as_mut())?; + self.body().run_pass(pass_args.as_mut())?; + self.module_annotations().run_pass(pass_args.as_mut())?; + } + Ok(PassOutput(())) + } + + fn substitute_resets( + &self, + mut pass_args: PassArgs<'_, SubstituteResets>, + ) -> Result, DeduceResetsError> { + pass_args.fallback_error_source_location = self.source_location(); + if let Some(&retval) = pass_args + .state + .substituted_modules + .get(&pass_args.instantiated_module) + { + return Ok(PassOutput(retval)); + } + let PassOutput(name_id) = self.name_id().run_pass(pass_args.as_mut())?; + let PassOutput(source_location) = self.source_location().run_pass(pass_args.as_mut())?; + let PassOutput(module_io) = self.module_io().run_pass(pass_args.as_mut())?; + let PassOutput(body) = self.body().run_pass(pass_args.as_mut())?; + let PassOutput(module_annotations) = + self.module_annotations().run_pass(pass_args.as_mut())?; + let retval = Module::new_unchecked( + name_id, + source_location, + body, + module_io, + module_annotations, + ); + pass_args + .state + .substituted_modules + .insert(pass_args.instantiated_module, retval); + Ok(PassOutput(retval)) + } +} + +pub fn deduce_resets( + module: Interned>, + fallback_to_sync_reset: bool, +) -> Result>, DeduceResetsError> { + let mut state = State { + modules_added_to_graph: HashSet::default(), + substituted_modules: HashMap::default(), + expr_resets: HashMap::default(), + reset_graph: ResetGraph::default(), + fallback_to_sync_reset, + }; + RunPass::::run_pass( + &*module, + PassArgs { + state: &mut state, + instantiated_module: InstantiatedModule::Base(module), + fallback_error_source_location: module.source_location(), + _phantom: PhantomData, + }, + )?; + Ok(RunPass::::run_pass( + &*module, + PassArgs { + state: &mut state, + instantiated_module: InstantiatedModule::Base(module), + fallback_error_source_location: module.source_location(), + _phantom: PhantomData, + }, + )? + .0 + .intern_sized()) +} diff --git a/crates/fayalite/src/module/transform/simplify_enums.rs b/crates/fayalite/src/module/transform/simplify_enums.rs index bb57cf0..333451d 100644 --- a/crates/fayalite/src/module/transform/simplify_enums.rs +++ b/crates/fayalite/src/module/transform/simplify_enums.rs @@ -18,10 +18,10 @@ use crate::{ }, source_location::SourceLocation, ty::{CanonicalType, Type}, + util::HashMap, wire::Wire, }; use core::fmt; -use hashbrown::HashMap; #[derive(Debug)] pub enum SimplifyEnumsError { @@ -69,7 +69,8 @@ fn contains_any_enum_types(ty: CanonicalType) -> bool { | CanonicalType::AsyncReset(_) | CanonicalType::SyncReset(_) | CanonicalType::Reset(_) - | CanonicalType::Clock(_) => false, + | CanonicalType::Clock(_) + | CanonicalType::PhantomConst(_) => false, } } } @@ -512,7 +513,8 @@ impl State { | CanonicalType::AsyncReset(_) | CanonicalType::SyncReset(_) | CanonicalType::Reset(_) - | CanonicalType::Clock(_) => unreachable!(), + | CanonicalType::Clock(_) + | CanonicalType::PhantomConst(_) => unreachable!(), } } } @@ -577,7 +579,8 @@ fn connect_port( | (CanonicalType::Clock(_), _) | (CanonicalType::AsyncReset(_), _) | (CanonicalType::SyncReset(_), _) - | (CanonicalType::Reset(_), _) => unreachable!( + | (CanonicalType::Reset(_), _) + | (CanonicalType::PhantomConst(_), _) => unreachable!( "trying to connect memory ports:\n{:?}\n{:?}", Expr::ty(lhs), Expr::ty(rhs), @@ -665,6 +668,7 @@ impl Folder for State { ExprEnum::UIntLiteral(_) | ExprEnum::SIntLiteral(_) | ExprEnum::BoolLiteral(_) + | ExprEnum::PhantomConst(_) | ExprEnum::BundleLiteral(_) | ExprEnum::ArrayLiteral(_) | ExprEnum::Uninit(_) @@ -764,7 +768,9 @@ impl Folder for State { | ExprEnum::ModuleIO(_) | ExprEnum::Instance(_) | ExprEnum::Wire(_) - | ExprEnum::Reg(_) => op.default_fold(self), + | ExprEnum::Reg(_) + | ExprEnum::RegSync(_) + | ExprEnum::RegAsync(_) => op.default_fold(self), } } @@ -921,7 +927,8 @@ impl Folder for State { | CanonicalType::Clock(_) | CanonicalType::AsyncReset(_) | CanonicalType::SyncReset(_) - | CanonicalType::Reset(_) => canonical_type.default_fold(self), + | CanonicalType::Reset(_) + | CanonicalType::PhantomConst(_) => canonical_type.default_fold(self), } } @@ -958,8 +965,8 @@ pub fn simplify_enums( kind: SimplifyEnumsKind, ) -> Result>, SimplifyEnumsError> { module.fold(&mut State { - enum_types: HashMap::new(), - replacement_mem_ports: HashMap::new(), + enum_types: HashMap::default(), + replacement_mem_ports: HashMap::default(), kind, module_state_stack: vec![], }) diff --git a/crates/fayalite/src/module/transform/simplify_memories.rs b/crates/fayalite/src/module/transform/simplify_memories.rs index e8f9cbf..6357843 100644 --- a/crates/fayalite/src/module/transform/simplify_memories.rs +++ b/crates/fayalite/src/module/transform/simplify_memories.rs @@ -14,11 +14,10 @@ use crate::{ }, source_location::SourceLocation, ty::{CanonicalType, Type}, - util::MakeMutSlice, + util::{HashMap, MakeMutSlice}, wire::Wire, }; use bitvec::{slice::BitSlice, vec::BitVec}; -use hashbrown::HashMap; use std::{ convert::Infallible, fmt::Write, @@ -62,6 +61,7 @@ enum MemSplit { Bundle { fields: Rc<[MemSplit]>, }, + PhantomConst, Single { output_mem: Option, element_type: SingleType, @@ -76,6 +76,7 @@ impl MemSplit { fn mark_changed_element_type(self) -> Self { match self { MemSplit::Bundle { fields: _ } => self, + MemSplit::PhantomConst => self, MemSplit::Single { output_mem, element_type, @@ -97,6 +98,7 @@ impl MemSplit { .map(|field| Self::new(field.ty).mark_changed_element_type()) .collect(), }, + CanonicalType::PhantomConst(_) => MemSplit::PhantomConst, CanonicalType::Array(ty) => { let element = MemSplit::new(ty.element()); if let Self::Single { @@ -339,6 +341,7 @@ impl SplitMemState<'_, '_> { self.split_state_stack.pop(); } } + MemSplit::PhantomConst => {} MemSplit::Single { output_mem, element_type: single_type, @@ -538,7 +541,12 @@ impl ModuleState { }; loop { match input_element_type { - CanonicalType::Bundle(_) => unreachable!("bundle types are always split"), + CanonicalType::Bundle(_) => { + unreachable!("bundle types are always split") + } + CanonicalType::PhantomConst(_) => { + unreachable!("PhantomConst are always removed") + } CanonicalType::Enum(_) if input_array_types .first() @@ -743,7 +751,8 @@ impl ModuleState { .. } | MemSplit::Bundle { .. } - | MemSplit::Array { .. } => { + | MemSplit::Array { .. } + | MemSplit::PhantomConst => { let mut replacement_ports = Vec::with_capacity(input_mem.ports().len()); let mut wire_port_rdata = Vec::with_capacity(input_mem.ports().len()); let mut wire_port_wdata = Vec::with_capacity(input_mem.ports().len()); @@ -887,7 +896,7 @@ impl Folder for State { module, ModuleState { output_module: None, - memories: HashMap::new(), + memories: HashMap::default(), }, ); let mut this = PushedState::push_module(self, module); diff --git a/crates/fayalite/src/module/transform/visit.rs b/crates/fayalite/src/module/transform/visit.rs index 2e1e48f..526a62c 100644 --- a/crates/fayalite/src/module/transform/visit.rs +++ b/crates/fayalite/src/module/transform/visit.rs @@ -28,8 +28,10 @@ use crate::{ NormalModuleBody, ScopedNameId, Stmt, StmtConnect, StmtDeclaration, StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg, StmtWire, }, + phantom_const::PhantomConst, reg::Reg, - reset::{AsyncReset, Reset, SyncReset}, + reset::{AsyncReset, Reset, ResetType, SyncReset}, + sim::ExternModuleSimulation, source_location::SourceLocation, ty::{CanonicalType, Type}, wire::Wire, diff --git a/crates/fayalite/src/phantom_const.rs b/crates/fayalite/src/phantom_const.rs new file mode 100644 index 0000000..c481692 --- /dev/null +++ b/crates/fayalite/src/phantom_const.rs @@ -0,0 +1,410 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + expr::{ + ops::{ExprPartialEq, ExprPartialOrd}, + Expr, ToExpr, + }, + int::Bool, + intern::{Intern, Interned, InternedCompare, LazyInterned, LazyInternedTrait, Memoize}, + sim::value::{SimValue, SimValuePartialEq, ToSimValue, ToSimValueWithType}, + source_location::SourceLocation, + ty::{ + impl_match_variant_as_self, + serde_impls::{SerdeCanonicalType, SerdePhantomConst}, + CanonicalType, StaticType, Type, TypeProperties, + }, +}; +use bitvec::slice::BitSlice; +use serde::{ + de::{DeserializeOwned, Error}, + Deserialize, Deserializer, Serialize, Serializer, +}; +use std::{ + any::Any, + fmt, + hash::{Hash, Hasher}, + marker::PhantomData, + ops::Index, +}; + +#[derive(Clone)] +pub struct PhantomConstCanonicalValue { + parsed: serde_json::Value, + serialized: Interned, +} + +impl PhantomConstCanonicalValue { + pub fn from_json_value(parsed: serde_json::Value) -> Self { + let serialized = Intern::intern_owned( + serde_json::to_string(&parsed) + .expect("conversion from json value to text shouldn't fail"), + ); + Self { parsed, serialized } + } + pub fn as_json_value(&self) -> &serde_json::Value { + &self.parsed + } + pub fn as_str(&self) -> Interned { + self.serialized + } +} + +impl fmt::Debug for PhantomConstCanonicalValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.serialized) + } +} + +impl fmt::Display for PhantomConstCanonicalValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.serialized) + } +} + +impl PartialEq for PhantomConstCanonicalValue { + fn eq(&self, other: &Self) -> bool { + self.serialized == other.serialized + } +} + +impl Eq for PhantomConstCanonicalValue {} + +impl Hash for PhantomConstCanonicalValue { + fn hash(&self, state: &mut H) { + self.serialized.hash(state); + } +} + +impl Serialize for PhantomConstCanonicalValue { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.parsed.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for PhantomConstCanonicalValue { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(Self::from_json_value(serde_json::Value::deserialize( + deserializer, + )?)) + } +} + +pub trait PhantomConstValue: Intern + InternedCompare + Serialize + fmt::Debug { + fn deserialize_value<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>; +} + +impl PhantomConstValue for T +where + T: ?Sized + Intern + InternedCompare + Serialize + fmt::Debug, + Interned: DeserializeOwned, +{ + fn deserialize_value<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + as Deserialize<'de>>::deserialize(deserializer) + } +} + +/// Wrapper type that allows any Rust value to be smuggled as a HDL [`Type`]. +/// This only works for values that can be [serialized][Serialize] to and [deserialized][Deserialize] from [JSON][serde_json]. +pub struct PhantomConst { + value: LazyInterned, +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] +pub struct PhantomConstWithoutGenerics; + +#[allow(non_upper_case_globals)] +pub const PhantomConst: PhantomConstWithoutGenerics = PhantomConstWithoutGenerics; + +impl Index for PhantomConstWithoutGenerics { + type Output = PhantomConst; + + fn index(&self, value: T) -> &Self::Output { + Interned::into_inner(PhantomConst::new(value.intern()).intern_sized()) + } +} + +impl fmt::Debug for PhantomConst { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("PhantomConst").field(&self.get()).finish() + } +} + +impl Clone for PhantomConst { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for PhantomConst {} + +impl PartialEq for PhantomConst { + fn eq(&self, other: &Self) -> bool { + self.get() == other.get() + } +} + +impl Eq for PhantomConst {} + +impl Hash for PhantomConst { + fn hash(&self, state: &mut H) { + self.get().hash(state); + } +} + +struct PhantomConstCanonicalMemoize(PhantomData); + +impl Copy + for PhantomConstCanonicalMemoize +{ +} + +impl Clone + for PhantomConstCanonicalMemoize +{ + fn clone(&self) -> Self { + *self + } +} + +impl Eq + for PhantomConstCanonicalMemoize +{ +} + +impl PartialEq + for PhantomConstCanonicalMemoize +{ + fn eq(&self, _other: &Self) -> bool { + true + } +} + +impl Hash + for PhantomConstCanonicalMemoize +{ + fn hash(&self, _state: &mut H) {} +} + +impl Memoize for PhantomConstCanonicalMemoize { + type Input = Interned; + type InputOwned = Interned; + type Output = Interned; + + fn inner(self, input: &Self::Input) -> Self::Output { + Intern::intern_sized(PhantomConstCanonicalValue::from_json_value( + serde_json::to_value(input) + .expect("serialization failed when constructing a canonical PhantomConst"), + )) + } +} + +impl Memoize for PhantomConstCanonicalMemoize { + type Input = Interned; + type InputOwned = Interned; + type Output = Interned; + + fn inner(self, input: &Self::Input) -> Self::Output { + PhantomConstValue::deserialize_value(input.as_json_value()) + .expect("deserialization failed ") + } +} + +impl PhantomConst { + pub fn new(value: Interned) -> Self { + Self { + value: LazyInterned::Interned(value), + } + } + pub const fn new_lazy(v: &'static dyn LazyInternedTrait) -> Self { + Self { + value: LazyInterned::new_lazy(v), + } + } + pub fn get(self) -> Interned { + self.value.interned() + } + pub fn type_properties(self) -> TypeProperties { + <()>::TYPE_PROPERTIES + } + pub fn can_connect(self, other: Self) -> bool { + self == other + } + pub fn canonical_phantom_const(self) -> PhantomConst { + if let Some(&retval) = ::downcast_ref::(&self) { + return retval; + } + ::new( + PhantomConstCanonicalMemoize::(PhantomData).get_owned(self.get()), + ) + } + pub fn from_canonical_phantom_const(canonical_type: PhantomConst) -> Self { + if let Some(&retval) = ::downcast_ref::(&canonical_type) { + return retval; + } + Self::new( + PhantomConstCanonicalMemoize::(PhantomData).get_owned(canonical_type.get()), + ) + } +} + +impl Type for PhantomConst { + type BaseType = PhantomConst; + type MaskType = (); + type SimValue = PhantomConst; + impl_match_variant_as_self!(); + + fn mask_type(&self) -> Self::MaskType { + () + } + + fn canonical(&self) -> CanonicalType { + CanonicalType::PhantomConst(self.canonical_phantom_const()) + } + + fn from_canonical(canonical_type: CanonicalType) -> Self { + let CanonicalType::PhantomConst(phantom_const) = canonical_type else { + panic!("expected PhantomConst"); + }; + Self::from_canonical_phantom_const(phantom_const) + } + + fn source_location() -> SourceLocation { + SourceLocation::builtin() + } + + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + assert!(bits.is_empty()); + *self + } + + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + assert!(bits.is_empty()); + assert_eq!(*value, *self); + } + + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + assert!(bits.is_empty()); + assert_eq!(*value, *self); + } +} + +impl Default for PhantomConst +where + Interned: Default, +{ + fn default() -> Self { + Self::TYPE + } +} + +impl StaticType for PhantomConst +where + Interned: Default, +{ + const TYPE: Self = PhantomConst { + value: LazyInterned::new_lazy(&Interned::::default), + }; + const MASK_TYPE: Self::MaskType = (); + const TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES; + const MASK_TYPE_PROPERTIES: TypeProperties = <()>::TYPE_PROPERTIES; +} + +type SerdeType = SerdeCanonicalType>>; + +impl Serialize for PhantomConst { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + SerdeType::::PhantomConst(SerdePhantomConst(self.get())).serialize(serializer) + } +} + +impl<'de, T: ?Sized + PhantomConstValue> Deserialize<'de> for PhantomConst { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + match SerdeType::::deserialize(deserializer)? { + SerdeCanonicalType::PhantomConst(SerdePhantomConst(value)) => Ok(Self::new(value)), + ty => Err(Error::invalid_value( + serde::de::Unexpected::Other(ty.as_serde_unexpected_str()), + &"a PhantomConst", + )), + } + } +} + +impl ExprPartialEq for PhantomConst { + fn cmp_eq(lhs: Expr, rhs: Expr) -> Expr { + assert_eq!(Expr::ty(lhs), Expr::ty(rhs)); + true.to_expr() + } + + fn cmp_ne(lhs: Expr, rhs: Expr) -> Expr { + assert_eq!(Expr::ty(lhs), Expr::ty(rhs)); + false.to_expr() + } +} + +impl ExprPartialOrd for PhantomConst { + fn cmp_lt(lhs: Expr, rhs: Expr) -> Expr { + assert_eq!(Expr::ty(lhs), Expr::ty(rhs)); + false.to_expr() + } + + fn cmp_le(lhs: Expr, rhs: Expr) -> Expr { + assert_eq!(Expr::ty(lhs), Expr::ty(rhs)); + true.to_expr() + } + + fn cmp_gt(lhs: Expr, rhs: Expr) -> Expr { + assert_eq!(Expr::ty(lhs), Expr::ty(rhs)); + false.to_expr() + } + + fn cmp_ge(lhs: Expr, rhs: Expr) -> Expr { + assert_eq!(Expr::ty(lhs), Expr::ty(rhs)); + true.to_expr() + } +} + +impl SimValuePartialEq for PhantomConst { + fn sim_value_eq(this: &SimValue, other: &SimValue) -> bool { + assert_eq!(SimValue::ty(this), SimValue::ty(other)); + true + } +} + +impl ToSimValue for PhantomConst { + type Type = PhantomConst; + + fn to_sim_value(&self) -> SimValue { + SimValue::from_value(*self, *self) + } +} + +impl ToSimValueWithType> for PhantomConst { + fn to_sim_value_with_type(&self, ty: PhantomConst) -> SimValue> { + SimValue::from_value(ty, *self) + } +} + +impl ToSimValueWithType for PhantomConst { + fn to_sim_value_with_type(&self, ty: CanonicalType) -> SimValue { + SimValue::into_canonical(SimValue::from_value(Self::from_canonical(ty), *self)) + } +} diff --git a/crates/fayalite/src/prelude.rs b/crates/fayalite/src/prelude.rs index 9e7a85e..519210f 100644 --- a/crates/fayalite/src/prelude.rs +++ b/crates/fayalite/src/prelude.rs @@ -20,17 +20,24 @@ pub use crate::{ hdl_cover_with_enable, MakeFormalExpr, }, hdl, hdl_module, - int::{Bool, DynSize, KnownSize, SInt, SIntType, Size, UInt, UIntType}, + int::{Bool, DynSize, KnownSize, SInt, SIntType, SIntValue, Size, UInt, UIntType, UIntValue}, memory::{Mem, MemBuilder, ReadUnderWrite}, module::{ annotate, connect, connect_any, incomplete_wire, instance, memory, memory_array, memory_with_init, reg_builder, wire, Instance, Module, ModuleBuilder, }, + phantom_const::PhantomConst, reg::Reg, reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset}, + sim::{ + time::{SimDuration, SimInstant}, + value::{SimValue, ToSimValue, ToSimValueWithType}, + ExternModuleSimulationState, Simulation, + }, source_location::SourceLocation, ty::{AsMask, CanonicalType, Type}, util::{ConstUsize, GenericConstUsize}, wire::Wire, __, }; +pub use bitvec::{slice::BitSlice, vec::BitVec}; diff --git a/crates/fayalite/src/reg.rs b/crates/fayalite/src/reg.rs index 8f757f2..20e0b94 100644 --- a/crates/fayalite/src/reg.rs +++ b/crates/fayalite/src/reg.rs @@ -5,21 +5,22 @@ use crate::{ expr::{Expr, Flow}, intern::Interned, module::{NameId, ScopedNameId}, + reset::{Reset, ResetType}, source_location::SourceLocation, ty::{CanonicalType, Type}, }; use std::fmt; #[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct Reg { +pub struct Reg { name: ScopedNameId, source_location: SourceLocation, ty: T, - clock_domain: Expr, + clock_domain: Expr>, init: Option>, } -impl fmt::Debug for Reg { +impl fmt::Debug for Reg { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { name, @@ -37,8 +38,8 @@ impl fmt::Debug for Reg { } } -impl Reg { - pub fn canonical(&self) -> Reg { +impl Reg { + pub fn canonical(&self) -> Reg { let Self { name, source_location, @@ -59,7 +60,7 @@ impl Reg { scoped_name: ScopedNameId, source_location: SourceLocation, ty: T, - clock_domain: Expr, + clock_domain: Expr>, init: Option>, ) -> Self { assert!( @@ -98,7 +99,7 @@ impl Reg { pub fn scoped_name(&self) -> ScopedNameId { self.name } - pub fn clock_domain(&self) -> Expr { + pub fn clock_domain(&self) -> Expr> { self.clock_domain } pub fn init(&self) -> Option> { diff --git a/crates/fayalite/src/reset.rs b/crates/fayalite/src/reset.rs index 70d5f02..312a8ea 100644 --- a/crates/fayalite/src/reset.rs +++ b/crates/fayalite/src/reset.rs @@ -1,26 +1,52 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information use crate::{ - expr::{Expr, ToExpr}, - int::Bool, + clock::Clock, + expr::{ops, Expr, ToExpr}, + int::{Bool, SInt, UInt}, source_location::SourceLocation, ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties}, }; +use bitvec::slice::BitSlice; mod sealed { pub trait ResetTypeSealed {} } -pub trait ResetType: StaticType + sealed::ResetTypeSealed {} +pub trait ResetType: + StaticType + + sealed::ResetTypeSealed + + ops::ExprCastTo + + ops::ExprCastTo + + ops::ExprCastTo + + ops::ExprCastTo + + ops::ExprCastTo + + ops::ExprCastTo> + + ops::ExprCastTo> + + ops::ExprCastTo + + ops::ExprCastTo +{ + fn dispatch(input: D::Input, dispatch: D) -> D::Output; +} + +pub trait ResetTypeDispatch: Sized { + type Input; + type Output; + + fn reset(self, input: Self::Input) -> Self::Output; + fn sync_reset(self, input: Self::Input) -> Self::Output; + fn async_reset(self, input: Self::Input) -> Self::Output; +} macro_rules! reset_type { - ($name:ident, $Trait:ident::$trait_fn:ident, $is_castable_from_bits:literal) => { + ($name:ident, $(#[$impl_trait:ident])? $Trait:ident::$trait_fn:ident, $is_castable_from_bits:literal, $dispatch_fn:ident) => { #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct $name; impl Type for $name { type BaseType = $name; type MaskType = Bool; + type SimValue = bool; impl_match_variant_as_self!(); @@ -42,6 +68,21 @@ macro_rules! reset_type { }; retval } + + fn sim_value_from_bits(&self, bits: &BitSlice) -> Self::SimValue { + assert_eq!(bits.len(), 1); + bits[0] + } + + fn sim_value_clone_from_bits(&self, value: &mut Self::SimValue, bits: &BitSlice) { + assert_eq!(bits.len(), 1); + *value = bits[0]; + } + + fn sim_value_to_bits(&self, value: &Self::SimValue, bits: &mut BitSlice) { + assert_eq!(bits.len(), 1); + bits.set(0, *value); + } } impl $name { @@ -67,7 +108,14 @@ macro_rules! reset_type { impl sealed::ResetTypeSealed for $name {} - impl ResetType for $name {} + impl ResetType for $name { + fn dispatch( + input: D::Input, + dispatch: D, + ) -> D::Output { + dispatch.$dispatch_fn(input) + } + } pub trait $Trait { fn $trait_fn(&self) -> Expr<$name>; @@ -91,20 +139,21 @@ macro_rules! reset_type { } } - impl $Trait for Expr<$name> { + $($impl_trait $Trait for Expr<$name> { fn $trait_fn(&self) -> Expr<$name> { *self } - } + })? }; } -reset_type!(AsyncReset, ToAsyncReset::to_async_reset, true); -reset_type!(SyncReset, ToSyncReset::to_sync_reset, true); +reset_type!(AsyncReset, #[impl] ToAsyncReset::to_async_reset, true, async_reset); +reset_type!(SyncReset, #[impl] ToSyncReset::to_sync_reset, true, sync_reset); reset_type!( Reset, ToReset::to_reset, - false // Reset is not castable from bits because we don't know if it's async or sync + false, // Reset is not castable from bits because we don't know if it's async or sync + reset ); impl ToSyncReset for bool { diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs new file mode 100644 index 0000000..6659391 --- /dev/null +++ b/crates/fayalite/src/sim.rs @@ -0,0 +1,7812 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +//! Fayalite Simulation + +use crate::{ + bundle::{BundleField, BundleType}, + enum_::{EnumType, EnumVariant}, + expr::{ + ops, + target::{ + GetTarget, Target, TargetBase, TargetPathArrayElement, TargetPathBundleField, + TargetPathElement, + }, + ExprEnum, Flow, ToLiteralBits, + }, + int::{BoolOrIntType, UIntValue}, + intern::{ + Intern, Interned, InternedCompare, Memoize, PtrEqWithTypeId, SupportsPtrEqWithTypeId, + }, + memory::PortKind, + module::{ + transform::deduce_resets::deduce_resets, AnnotatedModuleIO, Block, ExternModuleBody, Id, + InstantiatedModule, ModuleBody, NameId, NormalModuleBody, ScopedNameId, Stmt, StmtConnect, + StmtDeclaration, StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg, StmtWire, + TargetInInstantiatedModule, + }, + prelude::*, + reset::{ResetType, ResetTypeDispatch}, + sim::{ + interpreter::{ + BreakAction, BreakpointsSet, Insn, InsnField, InsnFieldKind, InsnFieldType, + InsnOrLabel, Insns, InsnsBuilding, InsnsBuildingDone, InsnsBuildingKind, Label, + MemoryData, RunResult, SlotDebugData, SmallUInt, State, StatePartArrayIndex, + StatePartArrayIndexed, StatePartIndex, StatePartIndexRange, StatePartKind, + StatePartKindBigSlots, StatePartKindMemories, StatePartKindSmallSlots, StatePartLayout, + StatePartLen, StatePartsValue, TypeArrayIndex, TypeArrayIndexes, TypeIndex, + TypeIndexRange, TypeLayout, TypeLen, TypeParts, + }, + time::{SimDuration, SimInstant}, + value::SimValue, + }, + ty::StaticType, + util::{BitSliceWriteWithBase, DebugAsDisplay, HashMap, HashSet}, +}; +use bitvec::{bits, order::Lsb0, slice::BitSlice, vec::BitVec, view::BitView}; +use num_bigint::BigInt; +use num_traits::{Signed, Zero}; +use petgraph::{ + data::FromElements, + visit::{ + EdgeRef, GraphBase, IntoEdgeReferences, IntoNeighbors, IntoNeighborsDirected, + IntoNodeIdentifiers, IntoNodeReferences, NodeRef, VisitMap, Visitable, + }, +}; +use std::{ + any::Any, + borrow::Cow, + cell::RefCell, + collections::BTreeSet, + fmt, + future::{Future, IntoFuture}, + hash::Hash, + marker::PhantomData, + mem, + ops::IndexMut, + pin::Pin, + rc::Rc, + sync::Arc, + task::Poll, +}; + +mod interpreter; +pub mod time; +pub mod value; +pub mod vcd; + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +enum CondBody { + IfTrue { + cond: CompiledValue, + }, + IfFalse { + cond: CompiledValue, + }, + MatchArm { + discriminant: StatePartIndex, + variant_index: usize, + }, +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +struct Cond { + body: CondBody, + source_location: SourceLocation, +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +struct CompiledBundleField { + offset: TypeIndex, + ty: CompiledTypeLayout, +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +enum CompiledTypeLayoutBody { + Scalar, + Array { + /// debug names are ignored, use parent's layout instead + element: Interned>, + }, + Bundle { + /// debug names are ignored, use parent's layout instead + fields: Interned<[CompiledBundleField]>, + }, +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +struct CompiledTypeLayout { + ty: T, + layout: TypeLayout, + body: CompiledTypeLayoutBody, +} + +impl CompiledTypeLayout { + fn with_prefixed_debug_names(self, prefix: &str) -> Self { + let Self { ty, layout, body } = self; + Self { + ty, + layout: layout.with_prefixed_debug_names(prefix), + body, + } + } + fn with_anonymized_debug_info(self) -> Self { + let Self { ty, layout, body } = self; + Self { + ty, + layout: layout.with_anonymized_debug_info(), + body, + } + } + fn get(ty: T) -> Self { + #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] + struct MyMemoize; + impl Memoize for MyMemoize { + type Input = CanonicalType; + type InputOwned = CanonicalType; + type Output = CompiledTypeLayout; + + fn inner(self, input: &Self::Input) -> Self::Output { + match input { + CanonicalType::UInt(_) + | CanonicalType::SInt(_) + | CanonicalType::Bool(_) + | CanonicalType::Enum(_) + | CanonicalType::AsyncReset(_) + | CanonicalType::SyncReset(_) + | CanonicalType::Reset(_) + | CanonicalType::Clock(_) => { + let mut layout = TypeLayout::empty(); + let debug_data = SlotDebugData { + name: Interned::default(), + ty: *input, + }; + layout.big_slots = StatePartLayout::scalar(debug_data, ()); + CompiledTypeLayout { + ty: *input, + layout: layout.into(), + body: CompiledTypeLayoutBody::Scalar, + } + } + CanonicalType::Array(array) => { + let mut layout = TypeLayout::empty(); + let element = CompiledTypeLayout::get(array.element()).intern_sized(); + for index in 0..array.len() { + layout.allocate( + &element + .layout + .with_prefixed_debug_names(&format!("[{index}]")), + ); + } + CompiledTypeLayout { + ty: *input, + layout: layout.into(), + body: CompiledTypeLayoutBody::Array { element }, + } + } + CanonicalType::PhantomConst(_) => { + let unit_layout = CompiledTypeLayout::get(()); + CompiledTypeLayout { + ty: *input, + layout: unit_layout.layout, + body: unit_layout.body, + } + } + CanonicalType::Bundle(bundle) => { + let mut layout = TypeLayout::empty(); + let fields = bundle + .fields() + .iter() + .map( + |BundleField { + name, + flipped: _, + ty, + }| { + let ty = CompiledTypeLayout::get(*ty); + let offset = layout + .allocate( + &ty.layout + .with_prefixed_debug_names(&format!(".{name}")), + ) + .start(); + CompiledBundleField { offset, ty } + }, + ) + .collect(); + CompiledTypeLayout { + ty: *input, + layout: layout.into(), + body: CompiledTypeLayoutBody::Bundle { fields }, + } + } + } + } + } + let CompiledTypeLayout { + ty: _, + layout, + body, + } = MyMemoize.get_owned(ty.canonical()); + Self { ty, layout, body } + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +struct CompiledValue { + layout: CompiledTypeLayout, + range: TypeIndexRange, + write: Option<(CompiledTypeLayout, TypeIndexRange)>, +} + +impl CompiledValue { + fn write(self) -> (CompiledTypeLayout, TypeIndexRange) { + self.write.unwrap_or((self.layout, self.range)) + } + fn write_value(self) -> Self { + let (layout, range) = self.write(); + Self { + layout, + range, + write: None, + } + } + fn map( + self, + mut f: impl FnMut( + CompiledTypeLayout, + TypeIndexRange, + ) -> (CompiledTypeLayout, TypeIndexRange), + ) -> CompiledValue { + let (layout, range) = f(self.layout, self.range); + CompiledValue { + layout, + range, + write: self.write.map(|(layout, range)| f(layout, range)), + } + } + fn map_ty(self, mut f: impl FnMut(T) -> U) -> CompiledValue { + self.map(|CompiledTypeLayout { ty, layout, body }, range| { + ( + CompiledTypeLayout { + ty: f(ty), + layout, + body, + }, + range, + ) + }) + } +} + +impl CompiledValue { + fn field_by_index(self, field_index: usize) -> CompiledValue { + self.map(|layout, range| { + let CompiledTypeLayout { + ty: _, + layout: _, + body: CompiledTypeLayoutBody::Bundle { fields }, + } = layout + else { + unreachable!(); + }; + ( + fields[field_index].ty, + range.slice(TypeIndexRange::new( + fields[field_index].offset, + fields[field_index].ty.layout.len(), + )), + ) + }) + } + fn field_by_name(self, name: Interned) -> CompiledValue { + self.field_by_index(self.layout.ty.name_indexes()[&name]) + } +} + +impl CompiledValue { + fn element(self, index: usize) -> CompiledValue { + self.map(|layout, range| { + let CompiledTypeLayoutBody::Array { element } = layout.body else { + unreachable!(); + }; + (*element, range.index_array(element.layout.len(), index)) + }) + } + fn element_dyn( + self, + index_slot: StatePartIndex, + ) -> CompiledExpr { + CompiledExpr::from(self).element_dyn(index_slot) + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] +struct CompiledExpr { + static_part: CompiledValue, + indexes: TypeArrayIndexes, +} + +impl From> for CompiledExpr { + fn from(static_part: CompiledValue) -> Self { + Self { + static_part, + indexes: TypeArrayIndexes::default(), + } + } +} + +impl CompiledExpr { + fn map_ty(self, f: impl FnMut(T) -> U) -> CompiledExpr { + let Self { + static_part, + indexes, + } = self; + CompiledExpr { + static_part: static_part.map_ty(f), + indexes, + } + } + fn add_target_without_indexes_to_set(self, inputs: &mut SlotSet) { + let Self { + static_part, + indexes, + } = self; + indexes.as_ref().for_each_offset(|offset| { + inputs.extend([static_part.range.offset(offset)]); + }); + } + fn add_target_and_indexes_to_set(self, inputs: &mut SlotSet) { + let Self { + static_part: _, + indexes, + } = self; + self.add_target_without_indexes_to_set(inputs); + inputs.extend(indexes.as_ref().iter()); + } +} + +impl CompiledExpr { + fn field_by_index(self, field_index: usize) -> CompiledExpr { + CompiledExpr { + static_part: self.static_part.field_by_index(field_index), + indexes: self.indexes, + } + } + fn field_by_name(self, name: Interned) -> CompiledExpr { + CompiledExpr { + static_part: self.static_part.field_by_name(name), + indexes: self.indexes, + } + } +} + +impl CompiledExpr { + fn element(self, index: usize) -> CompiledExpr { + CompiledExpr { + static_part: self.static_part.element(index), + indexes: self.indexes, + } + } + fn element_dyn( + self, + index_slot: StatePartIndex, + ) -> CompiledExpr { + let CompiledTypeLayoutBody::Array { element } = self.static_part.layout.body else { + unreachable!(); + }; + let stride = element.layout.len(); + let indexes = self.indexes.join(TypeArrayIndex::from_parts( + index_slot, + self.static_part.layout.ty.len(), + stride, + )); + CompiledExpr { + static_part: self.static_part.map(|layout, range| { + let CompiledTypeLayoutBody::Array { element } = layout.body else { + unreachable!(); + }; + (*element, range.index_array(stride, 0)) + }), + indexes, + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +enum AssignmentOrSlotIndex { + AssignmentIndex(usize), + SmallSlot(StatePartIndex), + BigSlot(StatePartIndex), +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +enum AssignmentIO { + BigInput { + assignment_index: usize, + slot: StatePartIndex, + }, + SmallInput { + assignment_index: usize, + slot: StatePartIndex, + }, + BigOutput { + assignment_index: usize, + slot: StatePartIndex, + }, + SmallOutput { + assignment_index: usize, + slot: StatePartIndex, + }, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +enum AssignmentsEdge { + IO(AssignmentIO), + AssignmentImmediatePredecessor { + predecessor_assignment_index: usize, + assignment_index: usize, + }, +} + +#[derive(Debug)] +enum Assignments { + Accumulating { + assignments: Vec, + }, + Finalized { + assignments: Box<[Assignment]>, + slots_layout: TypeLayout, + slot_readers: SlotToAssignmentIndexFullMap, + slot_writers: SlotToAssignmentIndexFullMap, + assignment_immediate_predecessors: Box<[Box<[usize]>]>, + assignment_immediate_successors: Box<[Box<[usize]>]>, + }, +} + +impl Default for Assignments { + fn default() -> Self { + Self::Accumulating { + assignments: Vec::new(), + } + } +} + +impl Assignments { + fn finalize(&mut self, slots_layout: TypeLayout) { + let Self::Accumulating { assignments } = self else { + unreachable!("already finalized"); + }; + let assignments = mem::take(assignments).into_boxed_slice(); + let mut slot_readers = SlotToAssignmentIndexFullMap::new(slots_layout.len()); + let mut slot_writers = SlotToAssignmentIndexFullMap::new(slots_layout.len()); + let mut assignment_immediate_predecessors = vec![BTreeSet::new(); assignments.len()]; + let mut assignment_immediate_successors = vec![BTreeSet::new(); assignments.len()]; + for (assignment_index, assignment) in assignments.iter().enumerate() { + slot_readers + .keys_for_assignment(assignment_index) + .extend([&assignment.inputs]); + slot_readers + .keys_for_assignment(assignment_index) + .extend(&assignment.conditions); + let SlotSet(TypeParts { + small_slots, + big_slots, + }) = &assignment.outputs; + for &slot in small_slots { + if let Some(&pred) = slot_writers[slot].last() { + assignment_immediate_predecessors[assignment_index].insert(pred); + assignment_immediate_successors[pred].insert(assignment_index); + } + slot_writers[slot].push(assignment_index); + } + for &slot in big_slots { + if let Some(&pred) = slot_writers[slot].last() { + assignment_immediate_predecessors[assignment_index].insert(pred); + assignment_immediate_successors[pred].insert(assignment_index); + } + slot_writers[slot].push(assignment_index); + } + } + *self = Self::Finalized { + assignments, + slots_layout, + slot_readers, + slot_writers, + assignment_immediate_predecessors: assignment_immediate_predecessors + .into_iter() + .map(Box::from_iter) + .collect(), + assignment_immediate_successors: assignment_immediate_successors + .into_iter() + .map(Box::from_iter) + .collect(), + }; + } + fn push(&mut self, v: Assignment) { + let Self::Accumulating { assignments } = self else { + unreachable!("already finalized"); + }; + assignments.push(v); + } + fn assignments(&self) -> &[Assignment] { + let Self::Finalized { assignments, .. } = self else { + unreachable!("Assignments::finalize should have been called"); + }; + assignments + } + fn slots_layout(&self) -> TypeLayout { + let Self::Finalized { slots_layout, .. } = self else { + unreachable!("Assignments::finalize should have been called"); + }; + *slots_layout + } + fn slot_readers(&self) -> &SlotToAssignmentIndexFullMap { + let Self::Finalized { slot_readers, .. } = self else { + unreachable!("Assignments::finalize should have been called"); + }; + slot_readers + } + fn slot_writers(&self) -> &SlotToAssignmentIndexFullMap { + let Self::Finalized { slot_writers, .. } = self else { + unreachable!("Assignments::finalize should have been called"); + }; + slot_writers + } + fn assignment_immediate_predecessors(&self) -> &[Box<[usize]>] { + let Self::Finalized { + assignment_immediate_predecessors, + .. + } = self + else { + unreachable!("Assignments::finalize should have been called"); + }; + assignment_immediate_predecessors + } + fn assignment_immediate_successors(&self) -> &[Box<[usize]>] { + let Self::Finalized { + assignment_immediate_successors, + .. + } = self + else { + unreachable!("Assignments::finalize should have been called"); + }; + assignment_immediate_successors + } + fn elements(&self) -> AssignmentsElements<'_> { + let SlotToAssignmentIndexFullMap(TypeParts { + small_slots, + big_slots, + }) = self.slot_readers(); + AssignmentsElements { + node_indexes: HashMap::with_capacity_and_hasher( + self.assignments().len() + small_slots.len() + big_slots.len(), + Default::default(), + ), + nodes: self.node_references(), + edges: self.edge_references(), + } + } +} + +impl GraphBase for Assignments { + type EdgeId = AssignmentsEdge; + type NodeId = AssignmentOrSlotIndex; +} + +#[derive(Debug, Clone, Copy)] +enum AssignmentsNodeRef<'a> { + Assignment { + index: usize, + assignment: &'a Assignment, + }, + SmallSlot(StatePartIndex, SlotDebugData), + BigSlot(StatePartIndex, SlotDebugData), +} + +impl<'a> NodeRef for AssignmentsNodeRef<'a> { + type NodeId = AssignmentOrSlotIndex; + type Weight = AssignmentsNodeRef<'a>; + + fn id(&self) -> Self::NodeId { + match *self { + AssignmentsNodeRef::Assignment { + index, + assignment: _, + } => AssignmentOrSlotIndex::AssignmentIndex(index), + AssignmentsNodeRef::SmallSlot(slot, _) => AssignmentOrSlotIndex::SmallSlot(slot), + AssignmentsNodeRef::BigSlot(slot, _) => AssignmentOrSlotIndex::BigSlot(slot), + } + } + + fn weight(&self) -> &Self::Weight { + self + } +} + +impl<'a> petgraph::visit::Data for &'a Assignments { + type NodeWeight = AssignmentsNodeRef<'a>; + type EdgeWeight = AssignmentsEdge; +} + +struct AssignmentsElements<'a> { + node_indexes: HashMap, + nodes: AssignmentsNodes<'a>, + edges: AssignmentsEdges<'a>, +} + +impl<'a> Iterator for AssignmentsElements<'a> { + type Item = petgraph::data::Element< + <&'a Assignments as petgraph::visit::Data>::NodeWeight, + <&'a Assignments as petgraph::visit::Data>::EdgeWeight, + >; + + fn next(&mut self) -> Option { + let Self { + node_indexes, + nodes, + edges, + } = self; + if let Some(node) = nodes.next() { + node_indexes.insert(node.id(), node_indexes.len()); + return Some(petgraph::data::Element::Node { weight: node }); + } + let edge = edges.next()?; + Some(petgraph::data::Element::Edge { + source: node_indexes[&edge.source()], + target: node_indexes[&edge.target()], + weight: *edge.weight(), + }) + } +} + +#[derive(Clone)] +struct AssignmentsNodeIdentifiers { + assignment_indexes: std::ops::Range, + small_slots: std::ops::Range, + big_slots: std::ops::Range, +} + +impl AssignmentsNodeIdentifiers { + fn internal_iter<'a>(&'a mut self) -> impl Iterator + 'a { + let Self { + assignment_indexes, + small_slots, + big_slots, + } = self; + assignment_indexes + .map(AssignmentOrSlotIndex::AssignmentIndex) + .chain(small_slots.map(|value| { + AssignmentOrSlotIndex::SmallSlot(StatePartIndex { + value, + _phantom: PhantomData, + }) + })) + .chain(big_slots.map(|value| { + AssignmentOrSlotIndex::BigSlot(StatePartIndex { + value, + _phantom: PhantomData, + }) + })) + } +} + +impl Iterator for AssignmentsNodeIdentifiers { + type Item = AssignmentOrSlotIndex; + fn next(&mut self) -> Option { + self.internal_iter().next() + } + + fn nth(&mut self, n: usize) -> Option { + self.internal_iter().nth(n) + } +} + +impl<'a> IntoNodeIdentifiers for &'a Assignments { + type NodeIdentifiers = AssignmentsNodeIdentifiers; + + fn node_identifiers(self) -> Self::NodeIdentifiers { + let TypeLen { + small_slots, + big_slots, + } = self.slot_readers().len(); + AssignmentsNodeIdentifiers { + assignment_indexes: 0..self.assignments().len(), + small_slots: 0..small_slots.value, + big_slots: 0..big_slots.value, + } + } +} + +struct AssignmentsNodes<'a> { + assignments: &'a Assignments, + nodes: AssignmentsNodeIdentifiers, +} + +impl<'a> Iterator for AssignmentsNodes<'a> { + type Item = AssignmentsNodeRef<'a>; + + fn next(&mut self) -> Option { + self.nodes.next().map(|node| match node { + AssignmentOrSlotIndex::AssignmentIndex(index) => AssignmentsNodeRef::Assignment { + index, + assignment: &self.assignments.assignments()[index], + }, + AssignmentOrSlotIndex::SmallSlot(slot) => AssignmentsNodeRef::SmallSlot( + slot, + *self.assignments.slots_layout().small_slots.debug_data(slot), + ), + AssignmentOrSlotIndex::BigSlot(slot) => AssignmentsNodeRef::BigSlot( + slot, + *self.assignments.slots_layout().big_slots.debug_data(slot), + ), + }) + } +} + +impl<'a> IntoNodeReferences for &'a Assignments { + type NodeRef = AssignmentsNodeRef<'a>; + type NodeReferences = AssignmentsNodes<'a>; + + fn node_references(self) -> Self::NodeReferences { + AssignmentsNodes { + assignments: self, + nodes: self.node_identifiers(), + } + } +} + +struct AssignmentsNeighborsDirected<'a> { + assignment_indexes: std::slice::Iter<'a, usize>, + small_slots: std::collections::btree_set::Iter<'a, StatePartIndex>, + big_slots: std::collections::btree_set::Iter<'a, StatePartIndex>, +} + +impl Iterator for AssignmentsNeighborsDirected<'_> { + type Item = AssignmentOrSlotIndex; + fn next(&mut self) -> Option { + let Self { + assignment_indexes, + small_slots, + big_slots, + } = self; + if let retval @ Some(_) = assignment_indexes + .next() + .copied() + .map(AssignmentOrSlotIndex::AssignmentIndex) + { + retval + } else if let retval @ Some(_) = small_slots + .next() + .copied() + .map(AssignmentOrSlotIndex::SmallSlot) + { + retval + } else if let retval @ Some(_) = big_slots + .next() + .copied() + .map(AssignmentOrSlotIndex::BigSlot) + { + retval + } else { + None + } + } +} + +impl<'a> IntoNeighbors for &'a Assignments { + type Neighbors = AssignmentsNeighborsDirected<'a>; + + fn neighbors(self, n: Self::NodeId) -> Self::Neighbors { + self.neighbors_directed(n, petgraph::Direction::Outgoing) + } +} + +impl<'a> IntoNeighborsDirected for &'a Assignments { + type NeighborsDirected = AssignmentsNeighborsDirected<'a>; + + fn neighbors_directed( + self, + n: Self::NodeId, + d: petgraph::Direction, + ) -> Self::NeighborsDirected { + use petgraph::Direction::*; + let slot_map = match d { + Outgoing => self.slot_readers(), + Incoming => self.slot_writers(), + }; + match n { + AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { + let assignment = &self.assignments()[assignment_index]; + let ( + assignment_indexes, + SlotSet(TypeParts { + small_slots, + big_slots, + }), + ) = match d { + Outgoing => ( + &self.assignment_immediate_successors()[assignment_index], + &assignment.outputs, + ), + Incoming => ( + &self.assignment_immediate_predecessors()[assignment_index], + &assignment.inputs, + ), + }; + AssignmentsNeighborsDirected { + assignment_indexes: assignment_indexes.iter(), + small_slots: small_slots.iter(), + big_slots: big_slots.iter(), + } + } + AssignmentOrSlotIndex::SmallSlot(slot) => AssignmentsNeighborsDirected { + assignment_indexes: slot_map[slot].iter(), + small_slots: Default::default(), + big_slots: Default::default(), + }, + AssignmentOrSlotIndex::BigSlot(slot) => AssignmentsNeighborsDirected { + assignment_indexes: slot_map[slot].iter(), + small_slots: Default::default(), + big_slots: Default::default(), + }, + } + } +} + +impl EdgeRef for AssignmentsEdge { + type NodeId = AssignmentOrSlotIndex; + type EdgeId = AssignmentsEdge; + type Weight = AssignmentsEdge; + + fn source(&self) -> Self::NodeId { + match *self { + AssignmentsEdge::IO(AssignmentIO::BigInput { + assignment_index: _, + slot, + }) => AssignmentOrSlotIndex::BigSlot(slot), + AssignmentsEdge::IO(AssignmentIO::SmallInput { + assignment_index: _, + slot, + }) => AssignmentOrSlotIndex::SmallSlot(slot), + AssignmentsEdge::IO(AssignmentIO::BigOutput { + assignment_index, + slot: _, + }) => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + AssignmentsEdge::IO(AssignmentIO::SmallOutput { + assignment_index, + slot: _, + }) => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + AssignmentsEdge::AssignmentImmediatePredecessor { + predecessor_assignment_index, + assignment_index: _, + } => AssignmentOrSlotIndex::AssignmentIndex(predecessor_assignment_index), + } + } + + fn target(&self) -> Self::NodeId { + match *self { + AssignmentsEdge::IO(AssignmentIO::BigInput { + assignment_index, + slot: _, + }) => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + AssignmentsEdge::IO(AssignmentIO::SmallInput { + assignment_index, + slot: _, + }) => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + AssignmentsEdge::IO(AssignmentIO::BigOutput { + assignment_index: _, + slot, + }) => AssignmentOrSlotIndex::BigSlot(slot), + AssignmentsEdge::IO(AssignmentIO::SmallOutput { + assignment_index: _, + slot, + }) => AssignmentOrSlotIndex::SmallSlot(slot), + AssignmentsEdge::AssignmentImmediatePredecessor { + predecessor_assignment_index: _, + assignment_index, + } => AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + } + } + + fn weight(&self) -> &Self::Weight { + self + } + + fn id(&self) -> Self::EdgeId { + *self + } +} + +struct AssignmentsEdges<'a> { + assignments: &'a Assignments, + nodes: AssignmentsNodeIdentifiers, + outgoing_neighbors: Option<(AssignmentOrSlotIndex, AssignmentsNeighborsDirected<'a>)>, +} + +impl Iterator for AssignmentsEdges<'_> { + type Item = AssignmentsEdge; + + fn next(&mut self) -> Option { + loop { + if let Some((node, outgoing_neighbors)) = &mut self.outgoing_neighbors { + if let Some(outgoing_neighbor) = outgoing_neighbors.next() { + return Some(match (*node, outgoing_neighbor) { + ( + AssignmentOrSlotIndex::SmallSlot(_) | AssignmentOrSlotIndex::BigSlot(_), + AssignmentOrSlotIndex::SmallSlot(_) | AssignmentOrSlotIndex::BigSlot(_), + ) => unreachable!(), + ( + AssignmentOrSlotIndex::AssignmentIndex(predecessor_assignment_index), + AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + ) => AssignmentsEdge::AssignmentImmediatePredecessor { + predecessor_assignment_index, + assignment_index, + }, + ( + AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + AssignmentOrSlotIndex::SmallSlot(slot), + ) => AssignmentsEdge::IO(AssignmentIO::SmallOutput { + assignment_index, + slot, + }), + ( + AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + AssignmentOrSlotIndex::BigSlot(slot), + ) => AssignmentsEdge::IO(AssignmentIO::BigOutput { + assignment_index, + slot, + }), + ( + AssignmentOrSlotIndex::SmallSlot(slot), + AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + ) => AssignmentsEdge::IO(AssignmentIO::SmallInput { + assignment_index, + slot, + }), + ( + AssignmentOrSlotIndex::BigSlot(slot), + AssignmentOrSlotIndex::AssignmentIndex(assignment_index), + ) => AssignmentsEdge::IO(AssignmentIO::BigInput { + assignment_index, + slot, + }), + }); + } + } + let node = self.nodes.next()?; + self.outgoing_neighbors = Some(( + node, + self.assignments + .neighbors_directed(node, petgraph::Direction::Outgoing), + )); + } + } +} + +impl<'a> IntoEdgeReferences for &'a Assignments { + type EdgeRef = AssignmentsEdge; + type EdgeReferences = AssignmentsEdges<'a>; + + fn edge_references(self) -> Self::EdgeReferences { + AssignmentsEdges { + assignments: self, + nodes: self.node_identifiers(), + outgoing_neighbors: None, + } + } +} + +struct AssignmentsVisitMap { + assignments: Vec, + slots: DenseSlotSet, +} + +impl VisitMap for AssignmentsVisitMap { + fn visit(&mut self, n: AssignmentOrSlotIndex) -> bool { + match n { + AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { + !mem::replace(&mut self.assignments[assignment_index], true) + } + AssignmentOrSlotIndex::SmallSlot(slot) => self.slots.insert(slot), + AssignmentOrSlotIndex::BigSlot(slot) => self.slots.insert(slot), + } + } + + fn is_visited(&self, n: &AssignmentOrSlotIndex) -> bool { + match *n { + AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { + self.assignments[assignment_index] + } + AssignmentOrSlotIndex::SmallSlot(slot) => self.slots.contains(slot), + AssignmentOrSlotIndex::BigSlot(slot) => self.slots.contains(slot), + } + } + + fn unvisit(&mut self, n: AssignmentOrSlotIndex) -> bool { + match n { + AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => { + mem::replace(&mut self.assignments[assignment_index], false) + } + AssignmentOrSlotIndex::SmallSlot(slot) => self.slots.remove(slot), + AssignmentOrSlotIndex::BigSlot(slot) => self.slots.remove(slot), + } + } +} + +impl Visitable for Assignments { + type Map = AssignmentsVisitMap; + + fn visit_map(self: &Self) -> Self::Map { + AssignmentsVisitMap { + assignments: vec![false; self.assignments().len()], + slots: DenseSlotSet::new(self.slot_readers().len()), + } + } + + fn reset_map(self: &Self, map: &mut Self::Map) { + let AssignmentsVisitMap { assignments, slots } = map; + assignments.clear(); + assignments.resize(self.assignments().len(), false); + if slots.len() != self.slot_readers().len() { + *slots = DenseSlotSet::new(self.slot_readers().len()); + } else { + slots.clear(); + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +struct DenseSlotSet(TypeParts); + +impl DenseSlotSet { + fn new(len: TypeLen) -> Self { + let TypeLen { + small_slots, + big_slots, + } = len; + Self(TypeParts { + small_slots: vec![false; small_slots.value.try_into().expect("length too big")] + .into_boxed_slice(), + big_slots: vec![false; big_slots.value.try_into().expect("length too big")] + .into_boxed_slice(), + }) + } + fn len(&self) -> TypeLen { + TypeLen { + small_slots: StatePartLen { + value: self.0.small_slots.len() as _, + _phantom: PhantomData, + }, + big_slots: StatePartLen { + value: self.0.big_slots.len() as _, + _phantom: PhantomData, + }, + } + } + fn clear(&mut self) { + let Self(TypeParts { + small_slots, + big_slots, + }) = self; + small_slots.fill(false); + big_slots.fill(false); + } +} + +impl StatePartsValue for DenseSlotSet { + type Value = Box<[bool]>; +} + +trait DenseSlotSetMethods: Extend> { + fn contains(&self, k: StatePartIndex) -> bool; + fn remove(&mut self, k: StatePartIndex) -> bool { + self.take(k).is_some() + } + fn take(&mut self, k: StatePartIndex) -> Option>; + fn replace(&mut self, k: StatePartIndex) -> Option>; + fn insert(&mut self, k: StatePartIndex) -> bool { + self.replace(k).is_none() + } +} + +impl Extend> for DenseSlotSet +where + Self: DenseSlotSetMethods, +{ + fn extend>>(&mut self, iter: T) { + iter.into_iter().for_each(|v| { + self.insert(v); + }); + } +} + +impl DenseSlotSetMethods for DenseSlotSet { + fn contains(&self, k: StatePartIndex) -> bool { + self.0.small_slots[k.as_usize()] + } + + fn take( + &mut self, + k: StatePartIndex, + ) -> Option> { + mem::replace(self.0.small_slots.get_mut(k.as_usize())?, false).then_some(k) + } + + fn replace( + &mut self, + k: StatePartIndex, + ) -> Option> { + mem::replace(&mut self.0.small_slots[k.as_usize()], true).then_some(k) + } +} + +impl DenseSlotSetMethods for DenseSlotSet { + fn contains(&self, k: StatePartIndex) -> bool { + self.0.big_slots[k.as_usize()] + } + + fn take( + &mut self, + k: StatePartIndex, + ) -> Option> { + mem::replace(self.0.big_slots.get_mut(k.as_usize())?, false).then_some(k) + } + + fn replace( + &mut self, + k: StatePartIndex, + ) -> Option> { + mem::replace(&mut self.0.big_slots[k.as_usize()], true).then_some(k) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +struct SlotVec(TypeParts); + +impl SlotVec { + fn is_empty(&self) -> bool { + let Self(TypeParts { + small_slots, + big_slots, + }) = self; + small_slots.is_empty() && big_slots.is_empty() + } +} + +impl StatePartsValue for SlotVec { + type Value = Vec>; +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +struct SlotSet(TypeParts); + +impl SlotSet { + fn is_empty(&self) -> bool { + let Self(TypeParts { + small_slots, + big_slots, + }) = self; + small_slots.is_empty() && big_slots.is_empty() + } + fn for_each( + &self, + small_slots_fn: impl FnMut(StatePartIndex), + big_slots_fn: impl FnMut(StatePartIndex), + ) { + let Self(TypeParts { + small_slots, + big_slots, + }) = self; + small_slots.iter().copied().for_each(small_slots_fn); + big_slots.iter().copied().for_each(big_slots_fn); + } + fn all( + &self, + small_slots_fn: impl FnMut(StatePartIndex) -> bool, + big_slots_fn: impl FnMut(StatePartIndex) -> bool, + ) -> bool { + let Self(TypeParts { + small_slots, + big_slots, + }) = self; + small_slots.iter().copied().all(small_slots_fn) + && big_slots.iter().copied().all(big_slots_fn) + } +} + +impl StatePartsValue for SlotSet { + type Value = BTreeSet>; +} + +impl Extend> for SlotSet { + fn extend>>(&mut self, iter: T) { + self.0.small_slots.extend(iter); + } +} + +impl Extend> for SlotSet { + fn extend>>(&mut self, iter: T) { + self.0.big_slots.extend(iter); + } +} + +impl Extend> for SlotSet +where + Self: Extend>, +{ + fn extend>>(&mut self, iter: T) { + self.extend(iter.into_iter().flat_map(|v| v.iter())); + } +} + +impl Extend for SlotSet { + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each( + |TypeIndexRange { + small_slots, + big_slots, + }| { + self.extend(small_slots.iter()); + self.extend(big_slots.iter()); + }, + ) + } +} + +impl Extend for SlotSet { + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each( + |TypeArrayIndex { + small_slots, + big_slots, + }| { + self.extend([small_slots]); + self.extend([big_slots]); + }, + ) + } +} + +impl Extend> for SlotSet { + fn extend>>(&mut self, iter: T) { + self.extend(iter.into_iter().map(|v| v.index)); + } +} + +impl Extend for SlotSet { + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each(|cond_body| match cond_body { + CondBody::IfTrue { cond } | CondBody::IfFalse { cond } => { + self.extend([cond.range]); + } + CondBody::MatchArm { + discriminant, + variant_index: _, + } => self.extend([discriminant]), + }) + } +} + +impl Extend for SlotSet { + fn extend>(&mut self, iter: T) { + self.extend(iter.into_iter().map(|v| v.body)) + } +} + +#[derive(Debug)] +struct Assignment { + inputs: SlotSet, + outputs: SlotSet, + conditions: Interned<[Cond]>, + insns: Vec, + source_location: SourceLocation, +} + +#[derive(Debug)] +struct SlotToAssignmentIndexFullMap(TypeParts); + +impl StatePartsValue for SlotToAssignmentIndexFullMap { + type Value = Box<[Vec]>; +} + +impl SlotToAssignmentIndexFullMap { + fn new(len: TypeLen) -> Self { + let TypeLen { + small_slots, + big_slots, + } = len; + Self(TypeParts { + small_slots: vec![Vec::new(); small_slots.value.try_into().expect("length too big")] + .into_boxed_slice(), + big_slots: vec![Vec::new(); big_slots.value.try_into().expect("length too big")] + .into_boxed_slice(), + }) + } + fn len(&self) -> TypeLen { + TypeLen { + small_slots: StatePartLen { + value: self.0.small_slots.len() as _, + _phantom: PhantomData, + }, + big_slots: StatePartLen { + value: self.0.big_slots.len() as _, + _phantom: PhantomData, + }, + } + } + fn keys_for_assignment( + &mut self, + assignment_index: usize, + ) -> SlotToAssignmentIndexFullMapKeysForAssignment<'_> { + SlotToAssignmentIndexFullMapKeysForAssignment { + map: self, + assignment_index, + } + } + fn for_each( + &self, + mut small_slots_fn: impl FnMut(StatePartIndex, &[usize]), + mut big_slots_fn: impl FnMut(StatePartIndex, &[usize]), + ) { + let Self(TypeParts { + small_slots, + big_slots, + }) = self; + small_slots.iter().enumerate().for_each(|(k, v)| { + small_slots_fn( + StatePartIndex { + value: k as _, + _phantom: PhantomData, + }, + v, + ) + }); + big_slots.iter().enumerate().for_each(|(k, v)| { + big_slots_fn( + StatePartIndex { + value: k as _, + _phantom: PhantomData, + }, + v, + ) + }); + } +} + +impl std::ops::Index> for SlotToAssignmentIndexFullMap { + type Output = Vec; + + fn index(&self, index: StatePartIndex) -> &Self::Output { + &self.0.small_slots[index.as_usize()] + } +} + +impl std::ops::IndexMut> for SlotToAssignmentIndexFullMap { + fn index_mut(&mut self, index: StatePartIndex) -> &mut Self::Output { + &mut self.0.small_slots[index.as_usize()] + } +} + +impl std::ops::Index> for SlotToAssignmentIndexFullMap { + type Output = Vec; + + fn index(&self, index: StatePartIndex) -> &Self::Output { + &self.0.big_slots[index.as_usize()] + } +} + +impl std::ops::IndexMut> for SlotToAssignmentIndexFullMap { + fn index_mut(&mut self, index: StatePartIndex) -> &mut Self::Output { + &mut self.0.big_slots[index.as_usize()] + } +} + +struct SlotToAssignmentIndexFullMapKeysForAssignment<'a> { + map: &'a mut SlotToAssignmentIndexFullMap, + assignment_index: usize, +} + +impl<'a, K: StatePartKind> Extend<&'a StatePartIndex> + for SlotToAssignmentIndexFullMapKeysForAssignment<'_> +where + Self: Extend>, +{ + fn extend>>(&mut self, iter: T) { + self.extend(iter.into_iter().copied()); + } +} + +impl Extend> + for SlotToAssignmentIndexFullMapKeysForAssignment<'_> +where + SlotToAssignmentIndexFullMap: IndexMut, Output = Vec>, +{ + fn extend>>(&mut self, iter: T) { + iter.into_iter() + .for_each(|slot| self.map[slot].push(self.assignment_index)); + } +} + +impl<'a> Extend<&'a SlotSet> for SlotToAssignmentIndexFullMapKeysForAssignment<'_> { + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each( + |SlotSet(TypeParts { + small_slots, + big_slots, + })| { + self.extend(small_slots); + self.extend(big_slots); + }, + ); + } +} + +impl<'a> Extend<&'a Cond> for SlotToAssignmentIndexFullMapKeysForAssignment<'_> { + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each(|cond| match cond.body { + CondBody::IfTrue { cond } | CondBody::IfFalse { cond } => { + let CompiledValue { + range: + TypeIndexRange { + small_slots, + big_slots, + }, + layout: _, + write: _, + } = cond; + self.extend(small_slots.iter()); + self.extend(big_slots.iter()); + } + CondBody::MatchArm { + discriminant, + variant_index: _, + } => self.extend([discriminant]), + }); + } +} + +impl Assignment { + fn new( + conditions: Interned<[Cond]>, + insns: Vec, + source_location: SourceLocation, + ) -> Self { + let mut inputs = SlotSet::default(); + let mut outputs = SlotSet::default(); + for insn in &insns { + let insn = match insn { + InsnOrLabel::Insn(insn) => insn, + InsnOrLabel::Label(_) => continue, + }; + for InsnField { ty, kind } in insn.fields() { + match (kind, ty) { + (InsnFieldKind::Input, InsnFieldType::SmallSlot(&slot)) => { + inputs.extend([slot]); + } + (InsnFieldKind::Input, InsnFieldType::BigSlot(&slot)) => { + inputs.extend([slot]); + } + ( + InsnFieldKind::Input, + InsnFieldType::SmallSlotArrayIndexed(&array_indexed), + ) => { + array_indexed.for_each_target(|slot| inputs.extend([slot])); + inputs.extend(array_indexed.indexes); + } + (InsnFieldKind::Input, InsnFieldType::BigSlotArrayIndexed(&array_indexed)) => { + array_indexed.for_each_target(|slot| inputs.extend([slot])); + inputs.extend(array_indexed.indexes); + } + (InsnFieldKind::Output, InsnFieldType::SmallSlot(&slot)) => { + outputs.extend([slot]); + } + (InsnFieldKind::Output, InsnFieldType::BigSlot(&slot)) => { + outputs.extend([slot]); + } + ( + InsnFieldKind::Output, + InsnFieldType::SmallSlotArrayIndexed(&array_indexed), + ) => { + array_indexed.for_each_target(|slot| { + outputs.extend([slot]); + }); + inputs.extend(array_indexed.indexes); + } + (InsnFieldKind::Output, InsnFieldType::BigSlotArrayIndexed(&array_indexed)) => { + array_indexed.for_each_target(|slot| { + outputs.extend([slot]); + }); + inputs.extend(array_indexed.indexes); + } + ( + _, + InsnFieldType::Memory(_) + | InsnFieldType::SmallUInt(_) + | InsnFieldType::SmallSInt(_) + | InsnFieldType::InternedBigInt(_) + | InsnFieldType::U8(_) + | InsnFieldType::USize(_) + | InsnFieldType::Empty(_), + ) + | ( + InsnFieldKind::Immediate + | InsnFieldKind::Memory + | InsnFieldKind::BranchTarget, + _, + ) => {} + } + } + } + Self { + inputs, + outputs, + conditions, + insns, + source_location, + } + } +} + +#[derive(Debug)] +struct RegisterReset { + is_async: bool, + init: CompiledValue, + rst: StatePartIndex, +} + +#[derive(Debug, Clone, Copy)] +struct ClockTrigger { + last_clk_was_low: StatePartIndex, + clk: StatePartIndex, + clk_triggered: StatePartIndex, + source_location: SourceLocation, +} + +#[derive(Debug)] +struct Register { + value: CompiledValue, + clk_triggered: StatePartIndex, + reset: Option, + source_location: SourceLocation, +} + +#[derive(Debug)] + +struct MemoryPort { + clk_triggered: StatePartIndex, + addr_delayed: Vec>, + en_delayed: Vec>, + data_layout: CompiledTypeLayout, + read_data_delayed: Vec, + write_data_delayed: Vec, + write_mask_delayed: Vec, + write_mode_delayed: Vec>, + write_insns: Vec, +} + +struct MemoryPortReadInsns<'a> { + addr: StatePartIndex, + en: StatePartIndex, + write_mode: Option>, + data: TypeIndexRange, + insns: &'a mut Vec, +} + +struct MemoryPortWriteInsns<'a> { + addr: StatePartIndex, + en: StatePartIndex, + write_mode: Option>, + data: TypeIndexRange, + mask: TypeIndexRange, + insns: &'a mut Vec, +} + +#[derive(Debug)] +struct Memory { + mem: Mem, + memory: StatePartIndex, + trace: TraceMem, + ports: Vec, +} + +#[derive(Copy, Clone)] +enum MakeTraceDeclTarget { + Expr(Expr), + Memory { + id: TraceMemoryId, + depth: usize, + stride: usize, + start: usize, + ty: CanonicalType, + }, +} + +impl MakeTraceDeclTarget { + fn flow(self) -> Flow { + match self { + MakeTraceDeclTarget::Expr(expr) => Expr::flow(expr), + MakeTraceDeclTarget::Memory { .. } => Flow::Duplex, + } + } + fn ty(self) -> CanonicalType { + match self { + MakeTraceDeclTarget::Expr(expr) => Expr::ty(expr), + MakeTraceDeclTarget::Memory { ty, .. } => ty, + } + } +} + +struct DebugOpaque(T); + +impl fmt::Debug for DebugOpaque { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("<...>") + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +struct CompiledExternModule { + module_io_targets: Interned<[Target]>, + module_io: Interned<[CompiledValue]>, + simulation: ExternModuleSimulation, +} + +#[derive(Debug)] +pub struct Compiler { + insns: Insns, + original_base_module: Interned>, + base_module: Interned>, + modules: HashMap, + extern_modules: Vec, + compiled_values: HashMap>, + compiled_exprs: HashMap, CompiledExpr>, + compiled_exprs_to_values: HashMap, CompiledValue>, + decl_conditions: HashMap>, + compiled_values_to_dyn_array_indexes: + HashMap, StatePartIndex>, + compiled_value_bool_dest_is_small_map: + HashMap, StatePartIndex>, + assignments: Assignments, + clock_triggers: Vec, + compiled_value_to_clock_trigger_map: HashMap, ClockTrigger>, + enum_discriminants: HashMap, StatePartIndex>, + registers: Vec, + traces: SimTraces>>, + memories: Vec, + dump_assignments_dot: Option>>, +} + +impl Compiler { + pub fn new(base_module: Interned>) -> Self { + let original_base_module = base_module; + let base_module = deduce_resets(base_module, true) + .unwrap_or_else(|e| panic!("failed to deduce reset types: {e}")); + Self { + insns: Insns::new(), + original_base_module, + base_module, + modules: HashMap::default(), + extern_modules: Vec::new(), + compiled_values: HashMap::default(), + compiled_exprs: HashMap::default(), + compiled_exprs_to_values: HashMap::default(), + decl_conditions: HashMap::default(), + compiled_values_to_dyn_array_indexes: HashMap::default(), + compiled_value_bool_dest_is_small_map: HashMap::default(), + assignments: Assignments::default(), + clock_triggers: Vec::new(), + compiled_value_to_clock_trigger_map: HashMap::default(), + enum_discriminants: HashMap::default(), + registers: Vec::new(), + traces: SimTraces(Vec::new()), + memories: Vec::new(), + dump_assignments_dot: None, + } + } + #[doc(hidden)] + /// This is explicitly unstable and may be changed/removed at any time + pub fn dump_assignments_dot(&mut self, callback: Box) { + self.dump_assignments_dot = Some(DebugOpaque(callback)); + } + fn new_sim_trace(&mut self, kind: SimTraceKind) -> TraceScalarId { + let id = TraceScalarId(self.traces.0.len()); + self.traces.0.push(SimTrace { + kind, + state: (), + last_state: (), + }); + id + } + fn make_trace_scalar_helper( + &mut self, + instantiated_module: InstantiatedModule, + target: MakeTraceDeclTarget, + source_location: SourceLocation, + small_kind: impl FnOnce(StatePartIndex) -> SimTraceKind, + big_kind: impl FnOnce(StatePartIndex) -> SimTraceKind, + ) -> TraceLocation { + 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(match compiled_value.range.len() { + TypeLen::A_SMALL_SLOT => small_kind(compiled_value.range.small_slots.start), + TypeLen::A_BIG_SLOT => big_kind(compiled_value.range.big_slots.start), + _ => unreachable!(), + })) + } + MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start, + ty, + } => TraceLocation::Memory(TraceMemoryLocation { + id, + depth, + stride, + start, + len: ty.bit_width(), + }), + } + } + fn make_trace_scalar( + &mut self, + instantiated_module: InstantiatedModule, + target: MakeTraceDeclTarget, + name: Interned, + source_location: SourceLocation, + ) -> TraceDecl { + let flow = target.flow(); + match target.ty() { + CanonicalType::UInt(ty) => TraceUInt { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + |index| SimTraceKind::SmallUInt { index, ty }, + |index| SimTraceKind::BigUInt { index, ty }, + ), + name, + ty, + flow, + } + .into(), + CanonicalType::SInt(ty) => TraceSInt { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + |index| SimTraceKind::SmallSInt { index, ty }, + |index| SimTraceKind::BigSInt { index, ty }, + ), + name, + ty, + flow, + } + .into(), + CanonicalType::Bool(_) => TraceBool { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + |index| SimTraceKind::SmallBool { index }, + |index| SimTraceKind::BigBool { index }, + ), + name, + flow, + } + .into(), + CanonicalType::Array(_) => unreachable!(), + CanonicalType::Enum(ty) => { + assert_eq!(ty.discriminant_bit_width(), ty.type_properties().bit_width); + 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); + let discriminant = self.compile_enum_discriminant( + compiled_value.map_ty(Enum::from_canonical), + source_location, + ); + TraceLocation::Scalar(self.new_sim_trace(SimTraceKind::EnumDiscriminant { + index: discriminant, + ty, + })) + } + MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start, + ty: _, + } => TraceLocation::Memory(TraceMemoryLocation { + id, + depth, + stride, + start, + len: ty.type_properties().bit_width, + }), + }; + TraceFieldlessEnum { + location, + name, + ty, + flow, + } + .into() + } + CanonicalType::Bundle(_) | CanonicalType::PhantomConst(_) => unreachable!(), + CanonicalType::AsyncReset(_) => TraceAsyncReset { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + |index| SimTraceKind::SmallAsyncReset { index }, + |index| SimTraceKind::BigAsyncReset { index }, + ), + name, + flow, + } + .into(), + CanonicalType::SyncReset(_) => TraceSyncReset { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + |index| SimTraceKind::SmallSyncReset { index }, + |index| SimTraceKind::BigSyncReset { index }, + ), + name, + flow, + } + .into(), + CanonicalType::Reset(_) => unreachable!(), + CanonicalType::Clock(_) => TraceClock { + location: self.make_trace_scalar_helper( + instantiated_module, + target, + source_location, + |index| SimTraceKind::SmallClock { index }, + |index| SimTraceKind::BigClock { index }, + ), + name, + flow, + } + .into(), + } + } + fn make_trace_decl_child( + &mut self, + instantiated_module: InstantiatedModule, + target: MakeTraceDeclTarget, + name: Interned, + source_location: SourceLocation, + ) -> TraceDecl { + match target.ty() { + CanonicalType::Array(ty) => { + let elements = Interned::from_iter((0..ty.len()).map(|index| { + self.make_trace_decl_child( + instantiated_module, + match target { + MakeTraceDeclTarget::Expr(target) => MakeTraceDeclTarget::Expr( + Expr::::from_canonical(target)[index], + ), + MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start, + ty: _, + } => MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start: start + ty.element().bit_width() * index, + ty: ty.element(), + }, + }, + Intern::intern_owned(format!("[{index}]")), + source_location, + ) + })); + TraceArray { + name, + elements, + ty, + flow: target.flow(), + } + .into() + } + CanonicalType::Enum(ty) => { + if ty.variants().iter().all(|v| v.ty.is_none()) { + self.make_trace_scalar(instantiated_module, target, name, source_location) + } else { + let flow = target.flow(); + 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); + let discriminant = self.compile_enum_discriminant( + compiled_value.map_ty(Enum::from_canonical), + source_location, + ); + TraceLocation::Scalar(self.new_sim_trace( + SimTraceKind::EnumDiscriminant { + index: discriminant, + ty, + }, + )) + } + MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start, + ty: _, + } => TraceLocation::Memory(TraceMemoryLocation { + id, + depth, + stride, + start, + len: ty.discriminant_bit_width(), + }), + }; + let discriminant = TraceEnumDiscriminant { + location, + name: "$tag".intern(), + ty, + flow, + }; + let non_empty_fields = + Interned::from_iter(ty.variants().into_iter().enumerate().flat_map( + |(variant_index, variant)| { + variant.ty.map(|variant_ty| { + self.make_trace_decl_child( + instantiated_module, + match target { + MakeTraceDeclTarget::Expr(target) => { + MakeTraceDeclTarget::Expr( + ops::VariantAccess::new_by_index( + Expr::::from_canonical(target), + variant_index, + ) + .to_expr(), + ) + } + MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start, + ty: _, + } => MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start: start + ty.discriminant_bit_width(), + ty: variant_ty, + }, + }, + variant.name, + source_location, + ) + }) + }, + )); + TraceEnumWithFields { + name, + discriminant, + non_empty_fields, + ty, + flow, + } + .into() + } + } + CanonicalType::Bundle(ty) => { + let fields = Interned::from_iter(ty.fields().iter().zip(ty.field_offsets()).map( + |(field, field_offset)| { + self.make_trace_decl_child( + instantiated_module, + match target { + MakeTraceDeclTarget::Expr(target) => { + MakeTraceDeclTarget::Expr(Expr::field( + Expr::::from_canonical(target), + &field.name, + )) + } + MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start, + ty: _, + } => MakeTraceDeclTarget::Memory { + id, + depth, + stride, + start: start + field_offset, + ty: field.ty, + }, + }, + field.name, + source_location, + ) + }, + )); + TraceBundle { + name, + fields, + ty, + flow: target.flow(), + } + .into() + } + CanonicalType::UInt(_) + | CanonicalType::SInt(_) + | CanonicalType::Bool(_) + | CanonicalType::AsyncReset(_) + | CanonicalType::SyncReset(_) + | CanonicalType::Reset(_) + | CanonicalType::Clock(_) => { + self.make_trace_scalar(instantiated_module, target, name, source_location) + } + CanonicalType::PhantomConst(_) => TraceBundle { + name, + fields: Interned::default(), + ty: Bundle::new(Interned::default()), + flow: target.flow(), + } + .into(), + } + } + fn make_trace_decl( + &mut self, + instantiated_module: InstantiatedModule, + target_base: TargetBase, + ) -> TraceDecl { + let target = MakeTraceDeclTarget::Expr(target_base.to_expr()); + match target_base { + TargetBase::ModuleIO(module_io) => TraceModuleIO { + name: module_io.name(), + child: self + .make_trace_decl_child( + instantiated_module, + target, + module_io.name(), + module_io.source_location(), + ) + .intern(), + ty: module_io.ty(), + flow: module_io.flow(), + } + .into(), + TargetBase::MemPort(mem_port) => { + let name = Intern::intern_owned(mem_port.port_name().to_string()); + let TraceDecl::Scope(TraceScope::Bundle(bundle)) = self.make_trace_decl_child( + instantiated_module, + target, + name, + mem_port.source_location(), + ) else { + unreachable!() + }; + TraceMemPort { + name, + bundle, + ty: mem_port.ty(), + } + .into() + } + TargetBase::Reg(reg) => TraceReg { + name: reg.name(), + child: self + .make_trace_decl_child( + instantiated_module, + target, + reg.name(), + reg.source_location(), + ) + .intern(), + ty: reg.ty(), + } + .into(), + TargetBase::RegSync(reg) => TraceReg { + name: reg.name(), + child: self + .make_trace_decl_child( + instantiated_module, + target, + reg.name(), + reg.source_location(), + ) + .intern(), + ty: reg.ty(), + } + .into(), + TargetBase::RegAsync(reg) => TraceReg { + name: reg.name(), + child: self + .make_trace_decl_child( + instantiated_module, + target, + reg.name(), + reg.source_location(), + ) + .intern(), + ty: reg.ty(), + } + .into(), + TargetBase::Wire(wire) => TraceWire { + name: wire.name(), + child: self + .make_trace_decl_child( + instantiated_module, + target, + wire.name(), + wire.source_location(), + ) + .intern(), + ty: wire.ty(), + } + .into(), + TargetBase::Instance(instance) => { + let TraceDecl::Scope(TraceScope::Bundle(instance_io)) = self.make_trace_decl_child( + instantiated_module, + target, + instance.name(), + instance.source_location(), + ) else { + unreachable!() + }; + let compiled_module = &self.modules[&InstantiatedModule::Child { + parent: instantiated_module.intern(), + instance: instance.intern(), + }]; + TraceInstance { + name: instance.name(), + instance_io, + module: compiled_module.trace_decls, + ty: instance.ty(), + } + .into() + } + } + } + fn compile_value( + &mut self, + target: TargetInInstantiatedModule, + ) -> CompiledValue { + if let Some(&retval) = self.compiled_values.get(&target) { + return retval; + } + let retval = match target.target { + Target::Base(base) => { + let unprefixed_layout = CompiledTypeLayout::get(base.canonical_ty()); + let layout = unprefixed_layout.with_prefixed_debug_names(&format!( + "{:?}.{:?}", + target.instantiated_module, + base.target_name() + )); + let range = self.insns.allocate_variable(&layout.layout); + let write = match *base { + TargetBase::ModuleIO(_) + | TargetBase::MemPort(_) + | TargetBase::Wire(_) + | TargetBase::Instance(_) => None, + TargetBase::Reg(_) | TargetBase::RegSync(_) | TargetBase::RegAsync(_) => { + let write_layout = unprefixed_layout.with_prefixed_debug_names(&format!( + "{:?}.{:?}$next", + target.instantiated_module, + base.target_name() + )); + Some(( + write_layout, + self.insns.allocate_variable(&write_layout.layout), + )) + } + }; + CompiledValue { + range, + layout, + write, + } + } + Target::Child(target_child) => { + let parent = self.compile_value(TargetInInstantiatedModule { + instantiated_module: target.instantiated_module, + target: *target_child.parent(), + }); + match *target_child.path_element() { + TargetPathElement::BundleField(TargetPathBundleField { name }) => { + parent.map_ty(Bundle::from_canonical).field_by_name(name) + } + TargetPathElement::ArrayElement(TargetPathArrayElement { index }) => { + parent.map_ty(Array::from_canonical).element(index) + } + TargetPathElement::DynArrayElement(_) => unreachable!(), + } + } + }; + self.compiled_values.insert(target, retval); + retval + } + fn compiled_expr_to_value( + &mut self, + expr: CompiledExpr, + source_location: SourceLocation, + ) -> CompiledValue { + if let Some(&retval) = self.compiled_exprs_to_values.get(&expr) { + return retval; + } + assert!( + expr.static_part.layout.ty.is_passive(), + "invalid expression passed to compiled_expr_to_value -- type must be passive", + ); + let CompiledExpr { + static_part, + indexes, + } = expr; + let retval = if indexes.as_ref().is_empty() { + CompiledValue { + layout: static_part.layout, + range: static_part.range, + write: None, + } + } else { + let layout = static_part.layout.with_anonymized_debug_info(); + let retval = CompiledValue { + layout, + range: self.insns.allocate_variable(&layout.layout), + write: None, + }; + let TypeIndexRange { + small_slots, + big_slots, + } = retval.range; + self.add_assignment( + Interned::default(), + small_slots + .iter() + .zip(static_part.range.small_slots.iter()) + .map(|(dest, base)| Insn::ReadSmallIndexed { + dest, + src: StatePartArrayIndexed { + base, + indexes: indexes.small_slots, + }, + }) + .chain( + big_slots + .iter() + .zip(static_part.range.big_slots.iter()) + .map(|(dest, base)| Insn::ReadIndexed { + dest, + src: StatePartArrayIndexed { + base, + indexes: indexes.big_slots, + }, + }), + ), + source_location, + ); + retval + }; + self.compiled_exprs_to_values.insert(expr, retval); + retval + } + fn add_assignment>( + &mut self, + conditions: Interned<[Cond]>, + insns: impl IntoIterator, + source_location: SourceLocation, + ) { + let insns = Vec::from_iter(insns.into_iter().map(Into::into)); + self.assignments + .push(Assignment::new(conditions, insns, source_location)); + } + fn simple_big_expr_input( + &mut self, + instantiated_module: InstantiatedModule, + input: Expr, + ) -> StatePartIndex { + let input = self.compile_expr(instantiated_module, input); + let input = + self.compiled_expr_to_value(input, instantiated_module.leaf_module().source_location()); + assert_eq!(input.range.len(), TypeLen::A_BIG_SLOT); + input.range.big_slots.start + } + fn compile_expr_helper( + &mut self, + instantiated_module: InstantiatedModule, + dest_ty: CanonicalType, + make_insns: impl FnOnce(&mut Self, TypeIndexRange) -> Vec, + ) -> CompiledValue { + let layout = CompiledTypeLayout::get(dest_ty); + let range = self.insns.allocate_variable(&layout.layout); + let retval = CompiledValue { + layout, + range, + write: None, + }; + let insns = make_insns(self, range); + self.add_assignment( + Interned::default(), + insns, + instantiated_module.leaf_module().source_location(), + ); + retval + } + fn simple_nary_big_expr_helper( + &mut self, + instantiated_module: InstantiatedModule, + dest_ty: CanonicalType, + make_insns: impl FnOnce(StatePartIndex) -> Vec, + ) -> CompiledValue { + self.compile_expr_helper(instantiated_module, dest_ty, |_, dest| { + assert_eq!(dest.len(), TypeLen::A_BIG_SLOT); + make_insns(dest.big_slots.start) + }) + } + fn simple_nary_big_expr( + &mut self, + instantiated_module: InstantiatedModule, + dest_ty: CanonicalType, + inputs: [Expr; N], + make_insns: impl FnOnce( + StatePartIndex, + [StatePartIndex; N], + ) -> Vec, + ) -> CompiledValue { + let inputs = inputs.map(|input| self.simple_big_expr_input(instantiated_module, input)); + self.simple_nary_big_expr_helper(instantiated_module, dest_ty, |dest| { + make_insns(dest, inputs) + }) + } + fn compiled_value_to_dyn_array_index( + &mut self, + compiled_value: CompiledValue, + source_location: SourceLocation, + ) -> StatePartIndex { + if let Some(&retval) = self + .compiled_values_to_dyn_array_indexes + .get(&compiled_value) + { + return retval; + } + let mut ty = compiled_value.layout.ty; + ty.width = ty.width.min(SmallUInt::BITS as usize); + let retval = match compiled_value.range.len() { + TypeLen::A_SMALL_SLOT => compiled_value.range.small_slots.start, + TypeLen::A_BIG_SLOT => { + let debug_data = SlotDebugData { + name: Interned::default(), + ty: ty.canonical(), + }; + let dest = self + .insns + .allocate_variable(&TypeLayout { + small_slots: StatePartLayout::scalar(debug_data, ()), + big_slots: StatePartLayout::empty(), + }) + .small_slots + .start; + self.add_assignment( + Interned::default(), + vec![Insn::CastBigToArrayIndex { + dest, + src: compiled_value.range.big_slots.start, + }], + source_location, + ); + dest + } + _ => unreachable!(), + }; + self.compiled_values_to_dyn_array_indexes + .insert(compiled_value, retval); + retval + } + fn compiled_value_bool_dest_is_small( + &mut self, + compiled_value: CompiledValue, + source_location: SourceLocation, + ) -> StatePartIndex { + if let Some(&retval) = self + .compiled_value_bool_dest_is_small_map + .get(&compiled_value) + { + return retval; + } + let retval = match compiled_value.range.len() { + TypeLen::A_SMALL_SLOT => compiled_value.range.small_slots.start, + TypeLen::A_BIG_SLOT => { + let debug_data = SlotDebugData { + name: Interned::default(), + ty: Bool.canonical(), + }; + let dest = self + .insns + .allocate_variable(&TypeLayout { + small_slots: StatePartLayout::scalar(debug_data, ()), + big_slots: StatePartLayout::empty(), + }) + .small_slots + .start; + self.add_assignment( + Interned::default(), + vec![Insn::IsNonZeroDestIsSmall { + dest, + src: compiled_value.range.big_slots.start, + }], + source_location, + ); + dest + } + _ => unreachable!(), + }; + self.compiled_value_bool_dest_is_small_map + .insert(compiled_value, retval); + retval + } + fn compile_cast_scalar_to_bits( + &mut self, + instantiated_module: InstantiatedModule, + arg: Expr, + cast_fn: impl FnOnce(Expr) -> Expr, + ) -> CompiledValue { + let arg = Expr::::from_canonical(arg); + let retval = cast_fn(arg); + let retval = self.compile_expr(instantiated_module, Expr::canonical(retval)); + let retval = self + .compiled_expr_to_value(retval, instantiated_module.leaf_module().source_location()); + retval.map_ty(UInt::from_canonical) + } + fn compile_cast_aggregate_to_bits( + &mut self, + instantiated_module: InstantiatedModule, + parts: impl IntoIterator>, + ) -> CompiledValue { + let retval = parts + .into_iter() + .map(|part| part.cast_to_bits()) + .reduce(|accumulator, part| accumulator | (part << Expr::ty(accumulator).width)) + .unwrap_or_else(|| UInt[0].zero().to_expr()); + let retval = self.compile_expr(instantiated_module, Expr::canonical(retval)); + let retval = self + .compiled_expr_to_value(retval, instantiated_module.leaf_module().source_location()); + retval.map_ty(UInt::from_canonical) + } + fn compile_cast_to_bits( + &mut self, + instantiated_module: InstantiatedModule, + expr: ops::CastToBits, + ) -> CompiledValue { + match Expr::ty(expr.arg()) { + CanonicalType::UInt(_) => { + self.compile_cast_scalar_to_bits(instantiated_module, expr.arg(), |arg| arg) + } + CanonicalType::SInt(ty) => self.compile_cast_scalar_to_bits( + instantiated_module, + expr.arg(), + |arg: Expr| arg.cast_to(ty.as_same_width_uint()), + ), + CanonicalType::Bool(_) + | CanonicalType::AsyncReset(_) + | CanonicalType::SyncReset(_) + | CanonicalType::Reset(_) + | CanonicalType::Clock(_) => self.compile_cast_scalar_to_bits( + instantiated_module, + expr.arg(), + |arg: Expr| arg.cast_to(UInt[1]), + ), + CanonicalType::Array(ty) => self.compile_cast_aggregate_to_bits( + instantiated_module, + (0..ty.len()).map(|index| Expr::::from_canonical(expr.arg())[index]), + ), + CanonicalType::Enum(ty) => self + .simple_nary_big_expr( + instantiated_module, + UInt[ty.type_properties().bit_width].canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| vec![Insn::Copy { dest, src }], + ) + .map_ty(UInt::from_canonical), + CanonicalType::Bundle(ty) => self.compile_cast_aggregate_to_bits( + instantiated_module, + ty.fields().iter().map(|field| { + Expr::field(Expr::::from_canonical(expr.arg()), &field.name) + }), + ), + CanonicalType::PhantomConst(_) => { + self.compile_cast_aggregate_to_bits(instantiated_module, []) + } + } + } + fn compile_cast_bits_to( + &mut self, + instantiated_module: InstantiatedModule, + expr: ops::CastBitsTo, + ) -> CompiledValue { + let retval = match expr.ty() { + CanonicalType::UInt(_) => Expr::canonical(expr.arg()), + CanonicalType::SInt(ty) => Expr::canonical(expr.arg().cast_to(ty)), + CanonicalType::Bool(ty) => Expr::canonical(expr.arg().cast_to(ty)), + CanonicalType::Array(ty) => { + let stride = ty.element().bit_width(); + Expr::::canonical( + ops::ArrayLiteral::new( + ty.element(), + Interned::from_iter((0..ty.len()).map(|index| { + let start = stride * index; + let end = start + stride; + expr.arg()[start..end].cast_bits_to(ty.element()) + })), + ) + .to_expr(), + ) + } + ty @ CanonicalType::Enum(_) => { + return self.simple_nary_big_expr( + instantiated_module, + ty, + [Expr::canonical(expr.arg())], + |dest, [src]| vec![Insn::Copy { dest, src }], + ); + } + CanonicalType::Bundle(ty) => Expr::canonical( + ops::BundleLiteral::new( + ty, + Interned::from_iter(ty.field_offsets().iter().zip(&ty.fields()).map( + |(&offset, &field)| { + let end = offset + field.ty.bit_width(); + expr.arg()[offset..end].cast_bits_to(field.ty) + }, + )), + ) + .to_expr(), + ), + CanonicalType::AsyncReset(ty) => Expr::canonical(expr.arg().cast_to(ty)), + CanonicalType::SyncReset(ty) => Expr::canonical(expr.arg().cast_to(ty)), + CanonicalType::Reset(_) => unreachable!(), + CanonicalType::Clock(ty) => Expr::canonical(expr.arg().cast_to(ty)), + CanonicalType::PhantomConst(ty) => { + let _ = self.compile_expr(instantiated_module, Expr::canonical(expr.arg())); + Expr::canonical(ty.to_expr()) + } + }; + let retval = self.compile_expr(instantiated_module, Expr::canonical(retval)); + self.compiled_expr_to_value(retval, instantiated_module.leaf_module().source_location()) + } + fn compile_aggregate_literal( + &mut self, + instantiated_module: InstantiatedModule, + dest_ty: CanonicalType, + inputs: Interned<[Expr]>, + ) -> CompiledValue { + self.compile_expr_helper(instantiated_module, dest_ty, |this, dest| { + let mut insns = Vec::new(); + let mut offset = TypeIndex::ZERO; + for input in inputs { + let input = this.compile_expr(instantiated_module, input); + let input = this + .compiled_expr_to_value( + input, + instantiated_module.leaf_module().source_location(), + ) + .range; + insns.extend( + input.insns_for_copy_to(dest.slice(TypeIndexRange::new(offset, input.len()))), + ); + offset = offset.offset(input.len().as_index()); + } + insns + }) + } + fn compile_expr( + &mut self, + instantiated_module: InstantiatedModule, + expr: Expr, + ) -> CompiledExpr { + if let Some(&retval) = self.compiled_exprs.get(&expr) { + return retval; + } + let mut cast_bit = |arg: Expr| { + let src_signed = match Expr::ty(arg) { + CanonicalType::UInt(_) => false, + CanonicalType::SInt(_) => true, + CanonicalType::Bool(_) => false, + CanonicalType::Array(_) => unreachable!(), + CanonicalType::Enum(_) => unreachable!(), + CanonicalType::Bundle(_) => unreachable!(), + CanonicalType::AsyncReset(_) => false, + CanonicalType::SyncReset(_) => false, + CanonicalType::Reset(_) => false, + CanonicalType::Clock(_) => false, + CanonicalType::PhantomConst(_) => unreachable!(), + }; + let dest_signed = match Expr::ty(expr) { + CanonicalType::UInt(_) => false, + CanonicalType::SInt(_) => true, + CanonicalType::Bool(_) => false, + CanonicalType::Array(_) => unreachable!(), + CanonicalType::Enum(_) => unreachable!(), + CanonicalType::Bundle(_) => unreachable!(), + CanonicalType::AsyncReset(_) => false, + CanonicalType::SyncReset(_) => false, + CanonicalType::Reset(_) => false, + CanonicalType::Clock(_) => false, + CanonicalType::PhantomConst(_) => unreachable!(), + }; + self.simple_nary_big_expr(instantiated_module, Expr::ty(expr), [arg], |dest, [src]| { + match (src_signed, dest_signed) { + (false, false) | (true, true) => { + vec![Insn::Copy { dest, src }] + } + (false, true) => vec![Insn::CastToSInt { + dest, + src, + dest_width: 1, + }], + (true, false) => vec![Insn::CastToUInt { + dest, + src, + dest_width: 1, + }], + } + }) + .into() + }; + let retval: CompiledExpr<_> = match *Expr::expr_enum(expr) { + ExprEnum::UIntLiteral(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [], + |dest, []| { + vec![Insn::Const { + dest, + value: expr.to_bigint().intern_sized(), + }] + }, + ) + .into(), + ExprEnum::SIntLiteral(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [], + |dest, []| { + vec![Insn::Const { + dest, + value: expr.to_bigint().intern_sized(), + }] + }, + ) + .into(), + ExprEnum::BoolLiteral(expr) => self + .simple_nary_big_expr(instantiated_module, Bool.canonical(), [], |dest, []| { + vec![Insn::Const { + dest, + value: BigInt::from(expr).intern_sized(), + }] + }) + .into(), + ExprEnum::PhantomConst(_) => self + .compile_aggregate_literal(instantiated_module, Expr::ty(expr), Interned::default()) + .into(), + ExprEnum::BundleLiteral(literal) => self + .compile_aggregate_literal( + instantiated_module, + Expr::ty(expr), + literal.field_values(), + ) + .into(), + ExprEnum::ArrayLiteral(literal) => self + .compile_aggregate_literal( + instantiated_module, + Expr::ty(expr), + literal.element_values(), + ) + .into(), + ExprEnum::EnumLiteral(expr) => { + let enum_bits_ty = UInt[expr.ty().type_properties().bit_width]; + let enum_bits = if let Some(variant_value) = expr.variant_value() { + ( + UInt[expr.ty().discriminant_bit_width()] + .from_int_wrapping(expr.variant_index()), + variant_value, + ) + .cast_to_bits() + .cast_to(enum_bits_ty) + } else { + enum_bits_ty + .from_int_wrapping(expr.variant_index()) + .to_expr() + }; + self.compile_expr( + instantiated_module, + enum_bits.cast_bits_to(expr.ty().canonical()), + ) + } + ExprEnum::Uninit(expr) => self.compile_expr( + instantiated_module, + UInt[expr.ty().bit_width()].zero().cast_bits_to(expr.ty()), + ), + ExprEnum::NotU(expr) => self + .simple_nary_big_expr( + instantiated_module, + Expr::ty(expr.arg()).canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| { + vec![Insn::NotU { + dest, + src, + width: Expr::ty(expr.arg()).width(), + }] + }, + ) + .into(), + ExprEnum::NotS(expr) => self + .simple_nary_big_expr( + instantiated_module, + Expr::ty(expr.arg()).canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| vec![Insn::NotS { dest, src }], + ) + .into(), + ExprEnum::NotB(expr) => self + .simple_nary_big_expr( + instantiated_module, + Expr::ty(expr.arg()).canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| { + vec![Insn::NotU { + dest, + src, + width: 1, + }] + }, + ) + .into(), + ExprEnum::Neg(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| vec![Insn::Neg { dest, src }], + ) + .into(), + ExprEnum::BitAndU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::And { dest, lhs, rhs }], + ) + .into(), + ExprEnum::BitAndS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::And { dest, lhs, rhs }], + ) + .into(), + ExprEnum::BitAndB(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::And { dest, lhs, rhs }], + ) + .into(), + ExprEnum::BitOrU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Or { dest, lhs, rhs }], + ) + .into(), + ExprEnum::BitOrS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Or { dest, lhs, rhs }], + ) + .into(), + ExprEnum::BitOrB(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Or { dest, lhs, rhs }], + ) + .into(), + ExprEnum::BitXorU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Xor { dest, lhs, rhs }], + ) + .into(), + ExprEnum::BitXorS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Xor { dest, lhs, rhs }], + ) + .into(), + ExprEnum::BitXorB(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Xor { dest, lhs, rhs }], + ) + .into(), + ExprEnum::AddU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Add { dest, lhs, rhs }], + ) + .into(), + ExprEnum::AddS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Add { dest, lhs, rhs }], + ) + .into(), + ExprEnum::SubU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| { + vec![Insn::SubU { + dest, + lhs, + rhs, + dest_width: expr.ty().width(), + }] + }, + ) + .into(), + ExprEnum::SubS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::SubS { dest, lhs, rhs }], + ) + .into(), + ExprEnum::MulU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Mul { dest, lhs, rhs }], + ) + .into(), + ExprEnum::MulS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Mul { dest, lhs, rhs }], + ) + .into(), + ExprEnum::DivU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Div { dest, lhs, rhs }], + ) + .into(), + ExprEnum::DivS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Div { dest, lhs, rhs }], + ) + .into(), + ExprEnum::RemU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Rem { dest, lhs, rhs }], + ) + .into(), + ExprEnum::RemS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::Rem { dest, lhs, rhs }], + ) + .into(), + ExprEnum::DynShlU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::DynShl { dest, lhs, rhs }], + ) + .into(), + ExprEnum::DynShlS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::DynShl { dest, lhs, rhs }], + ) + .into(), + ExprEnum::DynShrU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::DynShr { dest, lhs, rhs }], + ) + .into(), + ExprEnum::DynShrS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::DynShr { dest, lhs, rhs }], + ) + .into(), + ExprEnum::FixedShlU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs())], + |dest, [lhs]| { + vec![Insn::Shl { + dest, + lhs, + rhs: expr.rhs(), + }] + }, + ) + .into(), + ExprEnum::FixedShlS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs())], + |dest, [lhs]| { + vec![Insn::Shl { + dest, + lhs, + rhs: expr.rhs(), + }] + }, + ) + .into(), + ExprEnum::FixedShrU(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs())], + |dest, [lhs]| { + vec![Insn::Shr { + dest, + lhs, + rhs: expr.rhs(), + }] + }, + ) + .into(), + ExprEnum::FixedShrS(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.lhs())], + |dest, [lhs]| { + vec![Insn::Shr { + dest, + lhs, + rhs: expr.rhs(), + }] + }, + ) + .into(), + ExprEnum::CmpLtB(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLt { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpLeB(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLe { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpGtB(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + // swap both comparison direction and lhs/rhs + [Expr::canonical(expr.rhs()), Expr::canonical(expr.lhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLt { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpGeB(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + // swap both comparison direction and lhs/rhs + [Expr::canonical(expr.rhs()), Expr::canonical(expr.lhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLe { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpEqB(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpEq { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpNeB(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpNe { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpLtU(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLt { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpLeU(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLe { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpGtU(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + // swap both comparison direction and lhs/rhs + [Expr::canonical(expr.rhs()), Expr::canonical(expr.lhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLt { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpGeU(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + // swap both comparison direction and lhs/rhs + [Expr::canonical(expr.rhs()), Expr::canonical(expr.lhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLe { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpEqU(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpEq { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpNeU(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpNe { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpLtS(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLt { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpLeS(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLe { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpGtS(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + // swap both comparison direction and lhs/rhs + [Expr::canonical(expr.rhs()), Expr::canonical(expr.lhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLt { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpGeS(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + // swap both comparison direction and lhs/rhs + [Expr::canonical(expr.rhs()), Expr::canonical(expr.lhs())], + |dest, [lhs, rhs]| vec![Insn::CmpLe { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpEqS(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpEq { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CmpNeS(expr) => self + .simple_nary_big_expr( + instantiated_module, + Bool.canonical(), + [Expr::canonical(expr.lhs()), Expr::canonical(expr.rhs())], + |dest, [lhs, rhs]| vec![Insn::CmpNe { dest, lhs, rhs }], + ) + .into(), + ExprEnum::CastUIntToUInt(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| { + vec![Insn::CastToUInt { + dest, + src, + dest_width: expr.ty().width(), + }] + }, + ) + .into(), + ExprEnum::CastUIntToSInt(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| { + vec![Insn::CastToSInt { + dest, + src, + dest_width: expr.ty().width(), + }] + }, + ) + .into(), + ExprEnum::CastSIntToUInt(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| { + vec![Insn::CastToUInt { + dest, + src, + dest_width: expr.ty().width(), + }] + }, + ) + .into(), + ExprEnum::CastSIntToSInt(expr) => self + .simple_nary_big_expr( + instantiated_module, + expr.ty().canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| { + vec![Insn::CastToSInt { + dest, + src, + dest_width: expr.ty().width(), + }] + }, + ) + .into(), + ExprEnum::CastBoolToUInt(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastBoolToSInt(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastUIntToBool(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastSIntToBool(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastBoolToSyncReset(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastUIntToSyncReset(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastSIntToSyncReset(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastBoolToAsyncReset(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastUIntToAsyncReset(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastSIntToAsyncReset(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastSyncResetToBool(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastSyncResetToUInt(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastSyncResetToSInt(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastSyncResetToReset(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastAsyncResetToBool(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastAsyncResetToUInt(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastAsyncResetToSInt(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastAsyncResetToReset(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastResetToBool(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastResetToUInt(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastResetToSInt(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastBoolToClock(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastUIntToClock(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastSIntToClock(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastClockToBool(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastClockToUInt(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::CastClockToSInt(expr) => cast_bit(Expr::canonical(expr.arg())), + ExprEnum::FieldAccess(expr) => self + .compile_expr(instantiated_module, Expr::canonical(expr.base())) + .map_ty(Bundle::from_canonical) + .field_by_index(expr.field_index()), + ExprEnum::VariantAccess(variant_access) => { + let start = Expr::ty(variant_access.base()).discriminant_bit_width(); + let len = Expr::ty(expr).bit_width(); + self.compile_expr( + instantiated_module, + variant_access.base().cast_to_bits()[start..start + len] + .cast_bits_to(Expr::ty(expr)), + ) + } + ExprEnum::ArrayIndex(expr) => self + .compile_expr(instantiated_module, Expr::canonical(expr.base())) + .map_ty(Array::from_canonical) + .element(expr.element_index()), + ExprEnum::DynArrayIndex(expr) => { + let element_index = + self.compile_expr(instantiated_module, Expr::canonical(expr.element_index())); + let element_index = self.compiled_expr_to_value( + element_index, + instantiated_module.leaf_module().source_location(), + ); + let index_slot = self.compiled_value_to_dyn_array_index( + element_index.map_ty(UInt::from_canonical), + instantiated_module.leaf_module().source_location(), + ); + self.compile_expr(instantiated_module, Expr::canonical(expr.base())) + .map_ty(Array::from_canonical) + .element_dyn(index_slot) + } + ExprEnum::ReduceBitAndU(expr) => if Expr::ty(expr.arg()).width() == 0 { + self.compile_expr(instantiated_module, Expr::canonical(true.to_expr())) + } else { + self.compile_expr( + instantiated_module, + Expr::canonical( + expr.arg() + .cmp_eq(Expr::ty(expr.arg()).from_int_wrapping(-1)), + ), + ) + } + .into(), + ExprEnum::ReduceBitAndS(expr) => if Expr::ty(expr.arg()).width() == 0 { + self.compile_expr(instantiated_module, Expr::canonical(true.to_expr())) + } else { + self.compile_expr( + instantiated_module, + Expr::canonical( + expr.arg() + .cmp_eq(Expr::ty(expr.arg()).from_int_wrapping(-1)), + ), + ) + } + .into(), + ExprEnum::ReduceBitOrU(expr) => if Expr::ty(expr.arg()).width() == 0 { + self.compile_expr(instantiated_module, Expr::canonical(false.to_expr())) + } else { + self.compile_expr( + instantiated_module, + Expr::canonical(expr.arg().cmp_ne(Expr::ty(expr.arg()).from_int_wrapping(0))), + ) + } + .into(), + ExprEnum::ReduceBitOrS(expr) => if Expr::ty(expr.arg()).width() == 0 { + self.compile_expr(instantiated_module, Expr::canonical(false.to_expr())) + } else { + self.compile_expr( + instantiated_module, + Expr::canonical(expr.arg().cmp_ne(Expr::ty(expr.arg()).from_int_wrapping(0))), + ) + } + .into(), + ExprEnum::ReduceBitXorU(expr) => self + .simple_nary_big_expr( + instantiated_module, + UInt::<1>::TYPE.canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| { + vec![Insn::ReduceBitXor { + dest, + src, + input_width: Expr::ty(expr.arg()).width(), + }] + }, + ) + .into(), + ExprEnum::ReduceBitXorS(expr) => self + .simple_nary_big_expr( + instantiated_module, + UInt::<1>::TYPE.canonical(), + [Expr::canonical(expr.arg())], + |dest, [src]| { + vec![Insn::ReduceBitXor { + dest, + src, + input_width: Expr::ty(expr.arg()).width(), + }] + }, + ) + .into(), + ExprEnum::SliceUInt(expr) => self + .simple_nary_big_expr( + instantiated_module, + UInt::new_dyn(expr.range().len()).canonical(), + [Expr::canonical(expr.base())], + |dest, [src]| { + vec![Insn::SliceInt { + dest, + src, + start: expr.range().start, + len: expr.range().len(), + }] + }, + ) + .into(), + ExprEnum::SliceSInt(expr) => self + .simple_nary_big_expr( + instantiated_module, + UInt::new_dyn(expr.range().len()).canonical(), + [Expr::canonical(expr.base())], + |dest, [src]| { + vec![Insn::SliceInt { + dest, + src, + start: expr.range().start, + len: expr.range().len(), + }] + }, + ) + .into(), + ExprEnum::CastToBits(expr) => self + .compile_cast_to_bits(instantiated_module, expr) + .map_ty(CanonicalType::UInt) + .into(), + ExprEnum::CastBitsTo(expr) => { + self.compile_cast_bits_to(instantiated_module, expr).into() + } + ExprEnum::ModuleIO(expr) => self + .compile_value(TargetInInstantiatedModule { + instantiated_module, + target: expr.into(), + }) + .into(), + ExprEnum::Instance(expr) => self + .compile_value(TargetInInstantiatedModule { + instantiated_module, + target: expr.into(), + }) + .into(), + ExprEnum::Wire(expr) => self + .compile_value(TargetInInstantiatedModule { + instantiated_module, + target: expr.into(), + }) + .into(), + ExprEnum::Reg(expr) => self + .compile_value(TargetInInstantiatedModule { + instantiated_module, + target: expr.into(), + }) + .into(), + ExprEnum::RegSync(expr) => self + .compile_value(TargetInInstantiatedModule { + instantiated_module, + target: expr.into(), + }) + .into(), + ExprEnum::RegAsync(expr) => self + .compile_value(TargetInInstantiatedModule { + instantiated_module, + target: expr.into(), + }) + .into(), + ExprEnum::MemPort(expr) => self + .compile_value(TargetInInstantiatedModule { + instantiated_module, + target: expr.into(), + }) + .into(), + }; + self.compiled_exprs.insert(expr, retval); + retval + } + fn compile_simple_connect( + &mut self, + conditions: Interned<[Cond]>, + lhs: CompiledExpr, + rhs: CompiledValue, + source_location: SourceLocation, + ) { + let CompiledExpr { + static_part: lhs_static_part, + indexes, + } = lhs; + let (lhs_layout, lhs_range) = lhs_static_part.write(); + assert!( + lhs_layout.ty.is_passive(), + "invalid expression passed to compile_simple_connect -- type must be passive", + ); + let TypeIndexRange { + small_slots, + big_slots, + } = lhs_range; + self.add_assignment( + conditions, + small_slots + .iter() + .zip(rhs.range.small_slots.iter()) + .map(|(base, src)| { + if indexes.small_slots.is_empty() { + Insn::CopySmall { dest: base, src } + } else { + Insn::WriteSmallIndexed { + dest: StatePartArrayIndexed { + base, + indexes: indexes.small_slots, + }, + src, + } + } + }) + .chain( + big_slots + .iter() + .zip(rhs.range.big_slots.iter()) + .map(|(base, src)| { + if indexes.big_slots.is_empty() { + Insn::Copy { dest: base, src } + } else { + Insn::WriteIndexed { + dest: StatePartArrayIndexed { + base, + indexes: indexes.big_slots, + }, + src, + } + } + }), + ), + source_location, + ); + } + fn compile_connect( + &mut self, + lhs_instantiated_module: InstantiatedModule, + lhs_conditions: Interned<[Cond]>, + lhs: Expr, + rhs_instantiated_module: InstantiatedModule, + rhs_conditions: Interned<[Cond]>, + mut rhs: Expr, + source_location: SourceLocation, + ) { + if Expr::ty(lhs) != Expr::ty(rhs) || !Expr::ty(lhs).is_passive() { + match Expr::ty(lhs) { + CanonicalType::UInt(lhs_ty) => { + rhs = Expr::canonical(Expr::::from_canonical(rhs).cast_to(lhs_ty)); + } + CanonicalType::SInt(lhs_ty) => { + rhs = Expr::canonical(Expr::::from_canonical(rhs).cast_to(lhs_ty)); + } + CanonicalType::Bool(_) => unreachable!(), + CanonicalType::Array(lhs_ty) => { + let CanonicalType::Array(rhs_ty) = Expr::ty(rhs) else { + unreachable!(); + }; + assert_eq!(lhs_ty.len(), rhs_ty.len()); + let lhs = Expr::::from_canonical(lhs); + let rhs = Expr::::from_canonical(rhs); + for index in 0..lhs_ty.len() { + self.compile_connect( + lhs_instantiated_module, + lhs_conditions, + lhs[index], + rhs_instantiated_module, + rhs_conditions, + rhs[index], + source_location, + ); + } + return; + } + CanonicalType::Enum(lhs_ty) => { + let CanonicalType::Enum(rhs_ty) = Expr::ty(rhs) else { + unreachable!(); + }; + todo!("handle connect with different enum types"); + } + CanonicalType::Bundle(lhs_ty) => { + let CanonicalType::Bundle(rhs_ty) = Expr::ty(rhs) else { + unreachable!(); + }; + assert_eq!(lhs_ty.fields().len(), rhs_ty.fields().len()); + let lhs = Expr::::from_canonical(lhs); + let rhs = Expr::::from_canonical(rhs); + for ( + field_index, + ( + BundleField { + name, + flipped, + ty: _, + }, + rhs_field, + ), + ) in lhs_ty.fields().into_iter().zip(rhs_ty.fields()).enumerate() + { + assert_eq!(name, rhs_field.name); + assert_eq!(flipped, rhs_field.flipped); + let lhs_expr = ops::FieldAccess::new_by_index(lhs, field_index).to_expr(); + let rhs_expr = ops::FieldAccess::new_by_index(rhs, field_index).to_expr(); + if flipped { + // swap lhs/rhs + self.compile_connect( + rhs_instantiated_module, + rhs_conditions, + rhs_expr, + lhs_instantiated_module, + lhs_conditions, + lhs_expr, + source_location, + ); + } else { + self.compile_connect( + lhs_instantiated_module, + lhs_conditions, + lhs_expr, + rhs_instantiated_module, + rhs_conditions, + rhs_expr, + source_location, + ); + } + } + return; + } + CanonicalType::AsyncReset(_) => unreachable!(), + CanonicalType::SyncReset(_) => unreachable!(), + CanonicalType::Reset(_) => unreachable!(), + CanonicalType::Clock(_) => unreachable!(), + CanonicalType::PhantomConst(_) => unreachable!("PhantomConst mismatch"), + } + } + let Some(target) = lhs.target() else { + unreachable!("connect lhs must have target"); + }; + let lhs_decl_conditions = self.decl_conditions[&TargetInInstantiatedModule { + instantiated_module: lhs_instantiated_module, + target: target.base().into(), + }]; + let lhs = self.compile_expr(lhs_instantiated_module, lhs); + let rhs = self.compile_expr(rhs_instantiated_module, rhs); + let rhs = self.compiled_expr_to_value(rhs, source_location); + self.compile_simple_connect( + lhs_conditions[lhs_decl_conditions.len()..].intern(), + lhs, + rhs, + source_location, + ); + } + fn compile_clock( + &mut self, + clk: CompiledValue, + source_location: SourceLocation, + ) -> ClockTrigger { + if let Some(&retval) = self.compiled_value_to_clock_trigger_map.get(&clk) { + return retval; + } + let mut alloc_small_slot = |part_name: &str| { + self.insns + .state_layout + .ty + .small_slots + .allocate(&StatePartLayout::scalar( + SlotDebugData { + name: Interned::default(), + ty: Bool.canonical(), + }, + (), + )) + .start + }; + let last_clk_was_low = alloc_small_slot("last_clk_was_low"); + let clk_triggered = alloc_small_slot("clk_triggered"); + let retval = ClockTrigger { + last_clk_was_low, + clk: self.compiled_value_bool_dest_is_small( + clk.map_ty(CanonicalType::Clock), + source_location, + ), + clk_triggered, + source_location, + }; + self.add_assignment( + Interned::default(), + [Insn::AndSmall { + dest: clk_triggered, + lhs: retval.clk, + rhs: last_clk_was_low, + }], + source_location, + ); + self.clock_triggers.push(retval); + self.compiled_value_to_clock_trigger_map.insert(clk, retval); + retval + } + fn compile_enum_discriminant( + &mut self, + enum_value: CompiledValue, + source_location: SourceLocation, + ) -> StatePartIndex { + if let Some(&retval) = self.enum_discriminants.get(&enum_value) { + return retval; + } + let retval_ty = Enum::new( + enum_value + .layout + .ty + .variants() + .iter() + .map(|variant| EnumVariant { + name: variant.name, + ty: None, + }) + .collect(), + ); + let retval = if retval_ty == enum_value.layout.ty + && enum_value.range.len() == TypeLen::A_SMALL_SLOT + { + enum_value.range.small_slots.start + } else { + let retval = self + .insns + .state_layout + .ty + .small_slots + .allocate(&StatePartLayout::scalar( + SlotDebugData { + name: Interned::default(), + ty: retval_ty.canonical(), + }, + (), + )) + .start; + let discriminant_bit_width = enum_value.layout.ty.discriminant_bit_width(); + let discriminant_mask = !(!0u64 << discriminant_bit_width); + let insn = match enum_value.range.len() { + TypeLen::A_BIG_SLOT => Insn::AndBigWithSmallImmediate { + dest: retval, + lhs: enum_value.range.big_slots.start, + rhs: discriminant_mask, + }, + TypeLen::A_SMALL_SLOT => { + if discriminant_bit_width == enum_value.layout.ty.type_properties().bit_width { + Insn::CopySmall { + dest: retval, + src: enum_value.range.small_slots.start, + } + } else { + Insn::AndSmallImmediate { + dest: retval, + lhs: enum_value.range.small_slots.start, + rhs: discriminant_mask, + } + } + } + _ => unreachable!(), + }; + self.add_assignment(Interned::default(), [insn], source_location); + retval + }; + self.enum_discriminants.insert(enum_value, retval); + retval + } + fn compile_stmt_reg( + &mut self, + stmt_reg: StmtReg, + instantiated_module: InstantiatedModule, + value: CompiledValue, + ) { + let StmtReg { annotations, reg } = stmt_reg; + let clk = self.compile_expr(instantiated_module, Expr::canonical(reg.clock_domain().clk)); + let clk = self + .compiled_expr_to_value(clk, reg.source_location()) + .map_ty(Clock::from_canonical); + let clk = self.compile_clock(clk, reg.source_location()); + struct Dispatch; + impl ResetTypeDispatch for Dispatch { + type Input = (); + + type Output = bool; + + fn reset(self, _input: Self::Input) -> Self::Output { + unreachable!() + } + + fn sync_reset(self, _input: Self::Input) -> Self::Output { + false + } + + fn async_reset(self, _input: Self::Input) -> Self::Output { + true + } + } + let reset = if let Some(init) = reg.init() { + let init = self.compile_expr(instantiated_module, init); + let init = self.compiled_expr_to_value(init, reg.source_location()); + let rst = + self.compile_expr(instantiated_module, Expr::canonical(reg.clock_domain().rst)); + let rst = self.compiled_expr_to_value(rst, reg.source_location()); + let rst = self.compiled_value_bool_dest_is_small(rst, reg.source_location()); + let is_async = R::dispatch((), Dispatch); + if is_async { + let cond = Expr::canonical(reg.clock_domain().rst.cast_to(Bool)); + let cond = self.compile_expr(instantiated_module, cond); + let cond = self.compiled_expr_to_value(cond, reg.source_location()); + let cond = cond.map_ty(Bool::from_canonical); + // write to the register's current value since asynchronous reset is combinational + let lhs = CompiledValue { + layout: value.layout, + range: value.range, + write: None, + } + .into(); + self.compile_simple_connect( + [Cond { + body: CondBody::IfTrue { cond }, + source_location: reg.source_location(), + }][..] + .intern(), + lhs, + init, + reg.source_location(), + ); + } + Some(RegisterReset { + is_async, + init, + rst, + }) + } else { + None + }; + self.registers.push(Register { + value, + clk_triggered: clk.clk_triggered, + reset, + source_location: reg.source_location(), + }); + } + fn compile_declaration( + &mut self, + declaration: StmtDeclaration, + parent_module: Interned, + conditions: Interned<[Cond]>, + ) -> TraceDecl { + let target_base: TargetBase = match &declaration { + StmtDeclaration::Wire(v) => v.wire.into(), + StmtDeclaration::Reg(v) => v.reg.into(), + StmtDeclaration::RegSync(v) => v.reg.into(), + StmtDeclaration::RegAsync(v) => v.reg.into(), + StmtDeclaration::Instance(v) => v.instance.into(), + }; + let target = TargetInInstantiatedModule { + instantiated_module: *parent_module, + target: target_base.into(), + }; + self.decl_conditions.insert(target, conditions); + let compiled_value = self.compile_value(target); + match declaration { + StmtDeclaration::Wire(StmtWire { annotations, wire }) => {} + StmtDeclaration::Reg(_) => { + unreachable!("Reset types were already replaced by SyncReset or AsyncReset"); + } + StmtDeclaration::RegSync(stmt_reg) => { + self.compile_stmt_reg(stmt_reg, *parent_module, compiled_value) + } + StmtDeclaration::RegAsync(stmt_reg) => { + self.compile_stmt_reg(stmt_reg, *parent_module, compiled_value) + } + StmtDeclaration::Instance(StmtInstance { + annotations, + instance, + }) => { + let inner_instantiated_module = InstantiatedModule::Child { + parent: parent_module, + instance: instance.intern_sized(), + } + .intern_sized(); + let instance_expr = instance.to_expr(); + self.compile_module(inner_instantiated_module); + for (field_index, module_io) in + instance.instantiated().module_io().into_iter().enumerate() + { + let instance_field = + ops::FieldAccess::new_by_index(instance_expr, field_index).to_expr(); + match Expr::flow(instance_field) { + Flow::Source => { + // we need to supply the value to the instance since the + // parent module expects to read from the instance + self.compile_connect( + *parent_module, + conditions, + instance_field, + *inner_instantiated_module, + Interned::default(), + module_io.module_io.to_expr(), + instance.source_location(), + ); + } + Flow::Sink => { + // we need to take the value from the instance since the + // parent module expects to write to the instance + self.compile_connect( + *inner_instantiated_module, + Interned::default(), + module_io.module_io.to_expr(), + *parent_module, + conditions, + instance_field, + instance.source_location(), + ); + } + Flow::Duplex => unreachable!(), + } + } + } + } + self.make_trace_decl(*parent_module, target_base) + } + fn allocate_delay_chain( + &mut self, + len: usize, + layout: &TypeLayout, + first: Option, + last: Option, + mut from_allocation: impl FnMut(TypeIndexRange) -> T, + ) -> Vec { + match (len, first, last) { + (0, _, _) => Vec::new(), + (1, Some(v), _) | (1, None, Some(v)) => vec![v], + (2, Some(first), Some(last)) => vec![first, last], + (len, first, last) => { + let inner_len = len - first.is_some() as usize - last.is_some() as usize; + first + .into_iter() + .chain( + (0..inner_len) + .map(|_| from_allocation(self.insns.allocate_variable(layout))), + ) + .chain(last) + .collect() + } + } + } + fn allocate_delay_chain_small( + &mut self, + len: usize, + ty: CanonicalType, + first: Option>, + last: Option>, + ) -> Vec> { + self.allocate_delay_chain( + len, + &TypeLayout { + small_slots: StatePartLayout::scalar( + SlotDebugData { + name: Interned::default(), + ty, + }, + (), + ), + big_slots: StatePartLayout::empty(), + }, + first, + last, + |range| range.small_slots.start, + ) + } + fn compile_memory_port_rw_helper( + &mut self, + memory: StatePartIndex, + stride: usize, + mut start: usize, + data_layout: CompiledTypeLayout, + mask_layout: CompiledTypeLayout, + mut read: Option>, + mut write: Option>, + ) { + match data_layout.body { + CompiledTypeLayoutBody::Scalar => { + let CompiledTypeLayoutBody::Scalar = mask_layout.body else { + unreachable!(); + }; + let signed = match data_layout.ty { + CanonicalType::UInt(_) => false, + CanonicalType::SInt(_) => true, + CanonicalType::Bool(_) => false, + CanonicalType::Array(_) => unreachable!(), + CanonicalType::Enum(_) => false, + CanonicalType::Bundle(_) => unreachable!(), + CanonicalType::AsyncReset(_) => false, + CanonicalType::SyncReset(_) => false, + CanonicalType::Reset(_) => false, + CanonicalType::Clock(_) => false, + CanonicalType::PhantomConst(_) => unreachable!(), + }; + let width = data_layout.ty.bit_width(); + if let Some(MemoryPortReadInsns { + addr, + en: _, + write_mode: _, + data, + insns, + }) = read + { + insns.push( + match data.len() { + TypeLen::A_BIG_SLOT => { + let dest = data.big_slots.start; + if signed { + Insn::MemoryReadSInt { + dest, + memory, + addr, + stride, + start, + width, + } + } else { + Insn::MemoryReadUInt { + dest, + memory, + addr, + stride, + start, + width, + } + } + } + TypeLen::A_SMALL_SLOT => { + let _dest = data.small_slots.start; + todo!("memory ports' data are always big for now"); + } + _ => unreachable!(), + } + .into(), + ); + } + if let Some(MemoryPortWriteInsns { + addr, + en: _, + write_mode: _, + data, + mask, + insns, + }) = write + { + let end_label = self.insns.new_label(); + insns.push( + match mask.len() { + TypeLen::A_BIG_SLOT => Insn::BranchIfZero { + target: end_label.0, + value: mask.big_slots.start, + }, + TypeLen::A_SMALL_SLOT => Insn::BranchIfSmallZero { + target: end_label.0, + value: mask.small_slots.start, + }, + _ => unreachable!(), + } + .into(), + ); + insns.push( + match data.len() { + TypeLen::A_BIG_SLOT => { + let value = data.big_slots.start; + if signed { + Insn::MemoryWriteSInt { + value, + memory, + addr, + stride, + start, + width, + } + } else { + Insn::MemoryWriteUInt { + value, + memory, + addr, + stride, + start, + width, + } + } + } + TypeLen::A_SMALL_SLOT => { + let _value = data.small_slots.start; + todo!("memory ports' data are always big for now"); + } + _ => unreachable!(), + } + .into(), + ); + insns.push(end_label.into()); + } + } + CompiledTypeLayoutBody::Array { element } => { + let CompiledTypeLayoutBody::Array { + element: mask_element, + } = mask_layout.body + else { + unreachable!(); + }; + let ty = ::from_canonical(data_layout.ty); + let element_bit_width = ty.element().bit_width(); + let element_size = element.layout.len(); + let mask_element_size = mask_element.layout.len(); + for element_index in 0..ty.len() { + self.compile_memory_port_rw_helper( + memory, + stride, + start, + *element, + *mask_element, + read.as_mut().map( + |MemoryPortReadInsns { + addr, + en, + write_mode, + data, + insns, + }| MemoryPortReadInsns { + addr: *addr, + en: *en, + write_mode: *write_mode, + data: data.index_array(element_size, element_index), + insns, + }, + ), + write.as_mut().map( + |MemoryPortWriteInsns { + addr, + en, + write_mode, + data, + mask, + insns, + }| { + MemoryPortWriteInsns { + addr: *addr, + en: *en, + write_mode: *write_mode, + data: data.index_array(element_size, element_index), + mask: mask.index_array(mask_element_size, element_index), + insns, + } + }, + ), + ); + start += element_bit_width; + } + } + CompiledTypeLayoutBody::Bundle { fields } => { + let CompiledTypeLayoutBody::Bundle { + fields: mask_fields, + } = mask_layout.body + else { + unreachable!(); + }; + assert_eq!(fields.len(), mask_fields.len()); + for (field, mask_field) in fields.into_iter().zip(mask_fields) { + let field_index_range = + TypeIndexRange::new(field.offset, field.ty.layout.len()); + let mask_field_index_range = + TypeIndexRange::new(mask_field.offset, mask_field.ty.layout.len()); + self.compile_memory_port_rw_helper( + memory, + stride, + start, + field.ty, + mask_field.ty, + read.as_mut().map( + |MemoryPortReadInsns { + addr, + en, + write_mode, + data, + insns, + }| MemoryPortReadInsns { + addr: *addr, + en: *en, + write_mode: *write_mode, + data: data.slice(field_index_range), + insns, + }, + ), + write.as_mut().map( + |MemoryPortWriteInsns { + addr, + en, + write_mode, + data, + mask, + insns, + }| { + MemoryPortWriteInsns { + addr: *addr, + en: *en, + write_mode: *write_mode, + data: data.slice(field_index_range), + mask: mask.slice(mask_field_index_range), + insns, + } + }, + ), + ); + start = start + field.ty.ty.bit_width(); + } + } + } + } + fn compile_memory_port_rw( + &mut self, + memory: StatePartIndex, + data_layout: CompiledTypeLayout, + mask_layout: CompiledTypeLayout, + mut read: Option>, + mut write: Option>, + ) { + let read_else_label = read.as_mut().map( + |MemoryPortReadInsns { + addr: _, + en, + write_mode, + data: _, + insns, + }| { + let else_label = self.insns.new_label(); + insns.push( + Insn::BranchIfSmallZero { + target: else_label.0, + value: *en, + } + .into(), + ); + if let Some(write_mode) = *write_mode { + insns.push( + Insn::BranchIfSmallNonZero { + target: else_label.0, + value: write_mode, + } + .into(), + ); + } + else_label + }, + ); + let write_end_label = write.as_mut().map( + |MemoryPortWriteInsns { + addr: _, + en, + write_mode, + data: _, + mask: _, + insns, + }| { + let end_label = self.insns.new_label(); + insns.push( + Insn::BranchIfSmallZero { + target: end_label.0, + value: *en, + } + .into(), + ); + if let Some(write_mode) = *write_mode { + insns.push( + Insn::BranchIfSmallZero { + target: end_label.0, + value: write_mode, + } + .into(), + ); + } + end_label + }, + ); + self.compile_memory_port_rw_helper( + memory, + data_layout.ty.bit_width(), + 0, + data_layout, + mask_layout, + read.as_mut().map( + |MemoryPortReadInsns { + addr, + en, + write_mode, + data, + insns, + }| MemoryPortReadInsns { + addr: *addr, + en: *en, + write_mode: *write_mode, + data: *data, + insns: *insns, + }, + ), + write.as_mut().map( + |MemoryPortWriteInsns { + addr, + en, + write_mode, + data, + mask, + insns, + }| MemoryPortWriteInsns { + addr: *addr, + en: *en, + write_mode: *write_mode, + data: *data, + mask: *mask, + insns: *insns, + }, + ), + ); + if let ( + Some(else_label), + Some(MemoryPortReadInsns { + addr: _, + en: _, + write_mode: _, + data, + insns, + }), + ) = (read_else_label, read) + { + let end_label = self.insns.new_label(); + insns.push( + Insn::Branch { + target: end_label.0, + } + .into(), + ); + insns.push(else_label.into()); + let TypeIndexRange { + small_slots, + big_slots, + } = data; + for dest in small_slots.iter() { + insns.push(Insn::ConstSmall { dest, value: 0 }.into()); + } + for dest in big_slots.iter() { + insns.push( + Insn::Const { + dest, + value: BigInt::ZERO.intern_sized(), + } + .into(), + ); + } + insns.push(end_label.into()); + } + if let (Some(end_label), Some(write)) = (write_end_label, write) { + write.insns.push(end_label.into()); + } + } + fn compile_memory( + &mut self, + mem: Mem, + instantiated_module: InstantiatedModule, + conditions: Interned<[Cond]>, + trace_decls: &mut Vec, + ) { + let data_layout = CompiledTypeLayout::get(mem.array_type().element()); + let mask_layout = CompiledTypeLayout::get(mem.array_type().element().mask_type()); + let read_latency_plus_1 = mem + .read_latency() + .checked_add(1) + .expect("read latency too big"); + let write_latency_plus_1 = mem + .write_latency() + .get() + .checked_add(1) + .expect("write latency too big"); + let read_cycle = match mem.read_under_write() { + ReadUnderWrite::Old => 0, + ReadUnderWrite::New => mem.read_latency(), + ReadUnderWrite::Undefined => mem.read_latency() / 2, // something other than Old or New + }; + let memory = self + .insns + .state_layout + .memories + .allocate(&StatePartLayout::scalar( + (), + MemoryData { + array_type: mem.array_type(), + data: mem.initial_value().unwrap_or_else(|| { + Intern::intern_owned(BitVec::repeat( + false, + mem.array_type().type_properties().bit_width, + )) + }), + }, + )) + .start; + let (ports, trace_ports) = mem + .ports() + .iter() + .map(|&port| { + let target_base = TargetBase::MemPort(port); + let target = TargetInInstantiatedModule { + instantiated_module, + target: target_base.into(), + }; + self.decl_conditions.insert(target, conditions); + let TraceDecl::Scope(TraceScope::MemPort(trace_port)) = + self.make_trace_decl(instantiated_module, target_base) + else { + unreachable!(); + }; + let clk = Expr::field(port.to_expr(), "clk"); + let clk = self.compile_expr(instantiated_module, clk); + let clk = self.compiled_expr_to_value(clk, mem.source_location()); + let clk_triggered = self + .compile_clock(clk.map_ty(Clock::from_canonical), mem.source_location()) + .clk_triggered; + let en = Expr::field(port.to_expr(), "en"); + let en = self.compile_expr(instantiated_module, en); + let en = self.compiled_expr_to_value(en, mem.source_location()); + let en = self.compiled_value_bool_dest_is_small(en, mem.source_location()); + let addr = Expr::field(port.to_expr(), "addr"); + let addr = self.compile_expr(instantiated_module, addr); + let addr = self.compiled_expr_to_value(addr, mem.source_location()); + let addr_ty = addr.layout.ty; + let addr = self.compiled_value_to_dyn_array_index( + addr.map_ty(UInt::from_canonical), + mem.source_location(), + ); + let read_data = port.port_kind().rdata_name().map(|name| { + let read_data = + self.compile_expr(instantiated_module, Expr::field(port.to_expr(), name)); + let read_data = self.compiled_expr_to_value(read_data, mem.source_location()); + read_data.range + }); + let write_data = port.port_kind().wdata_name().map(|name| { + let write_data = + self.compile_expr(instantiated_module, Expr::field(port.to_expr(), name)); + let write_data = self.compiled_expr_to_value(write_data, mem.source_location()); + write_data.range + }); + let write_mask = port.port_kind().wmask_name().map(|name| { + let write_mask = + self.compile_expr(instantiated_module, Expr::field(port.to_expr(), name)); + let write_mask = self.compiled_expr_to_value(write_mask, mem.source_location()); + write_mask.range + }); + let write_mode = port.port_kind().wmode_name().map(|name| { + let write_mode = + self.compile_expr(instantiated_module, Expr::field(port.to_expr(), name)); + let write_mode = self.compiled_expr_to_value(write_mode, mem.source_location()); + self.compiled_value_bool_dest_is_small(write_mode, mem.source_location()) + }); + struct PortParts { + en_delayed_len: usize, + addr_delayed_len: usize, + read_data_delayed_len: usize, + write_data_delayed_len: usize, + write_mask_delayed_len: usize, + write_mode_delayed_len: usize, + read_cycle: Option, + write_cycle: Option, + } + let PortParts { + en_delayed_len, + addr_delayed_len, + read_data_delayed_len, + write_data_delayed_len, + write_mask_delayed_len, + write_mode_delayed_len, + read_cycle, + write_cycle, + } = match port.port_kind() { + PortKind::ReadOnly => PortParts { + en_delayed_len: read_cycle + 1, + addr_delayed_len: read_cycle + 1, + read_data_delayed_len: read_latency_plus_1 - read_cycle, + write_data_delayed_len: 0, + write_mask_delayed_len: 0, + write_mode_delayed_len: 0, + read_cycle: Some(read_cycle), + write_cycle: None, + }, + PortKind::WriteOnly => PortParts { + en_delayed_len: write_latency_plus_1, + addr_delayed_len: write_latency_plus_1, + read_data_delayed_len: 0, + write_data_delayed_len: write_latency_plus_1, + write_mask_delayed_len: write_latency_plus_1, + write_mode_delayed_len: 0, + read_cycle: None, + write_cycle: Some(mem.write_latency().get()), + }, + PortKind::ReadWrite => { + let can_rw_at_end = match mem.read_under_write() { + ReadUnderWrite::Old => false, + ReadUnderWrite::New | ReadUnderWrite::Undefined => true, + }; + let latency_plus_1 = read_latency_plus_1; + if latency_plus_1 != write_latency_plus_1 || !can_rw_at_end { + todo!( + "not sure what to do, issue: \ + https://github.com/chipsalliance/firrtl-spec/issues/263" + ); + } + PortParts { + en_delayed_len: latency_plus_1, + addr_delayed_len: latency_plus_1, + read_data_delayed_len: 1, + write_data_delayed_len: latency_plus_1, + write_mask_delayed_len: latency_plus_1, + write_mode_delayed_len: latency_plus_1, + read_cycle: Some(latency_plus_1 - 1), + write_cycle: Some(latency_plus_1 - 1), + } + } + }; + let addr_delayed = self.allocate_delay_chain_small( + addr_delayed_len, + addr_ty.canonical(), + Some(addr), + None, + ); + let en_delayed = self.allocate_delay_chain_small( + en_delayed_len, + Bool.canonical(), + Some(en), + None, + ); + let read_data_delayed = self.allocate_delay_chain( + read_data_delayed_len, + &data_layout.layout, + None, + read_data, + |v| v, + ); + let write_data_delayed = self.allocate_delay_chain( + write_data_delayed_len, + &data_layout.layout, + write_data, + None, + |v| v, + ); + let write_mask_delayed = self.allocate_delay_chain( + write_mask_delayed_len, + &mask_layout.layout, + write_mask, + None, + |v| v, + ); + let write_mode_delayed = self.allocate_delay_chain_small( + write_mode_delayed_len, + Bool.canonical(), + write_mode, + None, + ); + let mut read_insns = Vec::new(); + let mut write_insns = Vec::new(); + self.compile_memory_port_rw( + memory, + data_layout, + mask_layout, + read_cycle.map(|read_cycle| MemoryPortReadInsns { + addr: addr_delayed[read_cycle], + en: en_delayed[read_cycle], + write_mode: write_mode_delayed.get(read_cycle).copied(), + data: read_data_delayed[0], + insns: &mut read_insns, + }), + write_cycle.map(|write_cycle| MemoryPortWriteInsns { + addr: addr_delayed[write_cycle], + en: en_delayed[write_cycle], + write_mode: write_mode_delayed.get(write_cycle).copied(), + data: write_data_delayed[write_cycle], + mask: write_mask_delayed[write_cycle], + insns: &mut write_insns, + }), + ); + self.add_assignment(Interned::default(), read_insns, mem.source_location()); + ( + MemoryPort { + clk_triggered, + addr_delayed, + en_delayed, + data_layout, + read_data_delayed, + write_data_delayed, + write_mask_delayed, + write_mode_delayed, + write_insns, + }, + trace_port, + ) + }) + .unzip(); + let name = mem.scoped_name().1 .0; + let id = TraceMemoryId(self.memories.len()); + let stride = mem.array_type().element().bit_width(); + let trace = TraceMem { + id, + name, + stride, + element_type: self + .make_trace_decl_child( + instantiated_module, + MakeTraceDeclTarget::Memory { + id, + depth: mem.array_type().len(), + stride, + start: 0, + ty: mem.array_type().element(), + }, + name, + mem.source_location(), + ) + .intern_sized(), + ports: Intern::intern_owned(trace_ports), + array_type: mem.array_type(), + }; + trace_decls.push(trace.into()); + self.memories.push(Memory { + mem, + memory, + trace, + ports, + }); + } + fn compile_block( + &mut self, + parent_module: Interned, + block: Block, + conditions: Interned<[Cond]>, + trace_decls: &mut Vec, + ) { + let Block { memories, stmts } = block; + for memory in memories { + self.compile_memory(memory, *parent_module, conditions, trace_decls); + } + for stmt in stmts { + match stmt { + Stmt::Connect(StmtConnect { + lhs, + rhs, + source_location, + }) => self.compile_connect( + *parent_module, + conditions, + lhs, + *parent_module, + conditions, + rhs, + source_location, + ), + Stmt::Formal(StmtFormal { .. }) => todo!("implement simulating formal statements"), + Stmt::If(StmtIf { + cond, + source_location, + blocks: [then_block, else_block], + }) => { + let cond = self.compile_expr(*parent_module, Expr::canonical(cond)); + let cond = self.compiled_expr_to_value(cond, source_location); + let cond = cond.map_ty(Bool::from_canonical); + self.compile_block( + parent_module, + then_block, + Interned::from_iter(conditions.iter().copied().chain([Cond { + body: CondBody::IfTrue { cond }, + source_location, + }])), + trace_decls, + ); + self.compile_block( + parent_module, + else_block, + Interned::from_iter(conditions.iter().copied().chain([Cond { + body: CondBody::IfFalse { cond }, + source_location, + }])), + trace_decls, + ); + } + Stmt::Match(StmtMatch { + expr, + source_location, + blocks, + }) => { + let enum_expr = self.compile_expr(*parent_module, Expr::canonical(expr)); + let enum_expr = self.compiled_expr_to_value(enum_expr, source_location); + let enum_expr = enum_expr.map_ty(Enum::from_canonical); + let discriminant = self.compile_enum_discriminant(enum_expr, source_location); + for (variant_index, block) in blocks.into_iter().enumerate() { + self.compile_block( + parent_module, + block, + Interned::from_iter(conditions.iter().copied().chain([Cond { + body: CondBody::MatchArm { + discriminant, + variant_index, + }, + source_location, + }])), + trace_decls, + ); + } + } + Stmt::Declaration(declaration) => { + trace_decls.push(self.compile_declaration( + declaration, + parent_module, + conditions, + )); + } + } + } + } + fn compile_module(&mut self, module: Interned) -> &CompiledModule { + let mut trace_decls = Vec::new(); + let module_io = module + .leaf_module() + .module_io() + .iter() + .map( + |&AnnotatedModuleIO { + annotations: _, + module_io, + }| { + let target = TargetInInstantiatedModule { + instantiated_module: *module, + target: Target::from(module_io), + }; + self.decl_conditions.insert(target, Interned::default()); + trace_decls.push(self.make_trace_decl(*module, module_io.into())); + self.compile_value(target) + }, + ) + .collect(); + match module.leaf_module().body() { + ModuleBody::Normal(NormalModuleBody { body }) => { + self.compile_block(module, body, Interned::default(), &mut trace_decls); + } + ModuleBody::Extern(ExternModuleBody { + verilog_name: _, + parameters: _, + simulation, + }) => { + let Some(simulation) = simulation else { + panic!( + "can't simulate extern module without extern_module_simulation: {}", + module.leaf_module().source_location() + ); + }; + self.extern_modules.push(CompiledExternModule { + module_io_targets: module + .leaf_module() + .module_io() + .iter() + .map(|v| Target::from(v.module_io)) + .collect(), + module_io, + simulation, + }); + } + } + let hashbrown::hash_map::Entry::Vacant(entry) = self.modules.entry(*module) else { + unreachable!("compiled same instantiated module twice"); + }; + entry.insert(CompiledModule { + module_io, + trace_decls: TraceModule { + name: module.leaf_module().name(), + children: Intern::intern_owned(trace_decls), + }, + }) + } + fn process_assignments(&mut self) { + self.assignments + .finalize(self.insns.state_layout().ty.clone().into()); + if let Some(DebugOpaque(dump_assignments_dot)) = &self.dump_assignments_dot { + let graph = + petgraph::graph::DiGraph::<_, _, usize>::from_elements(self.assignments.elements()); + dump_assignments_dot(&petgraph::dot::Dot::new(&graph)); + } + let assignments_order: Vec<_> = match petgraph::algo::toposort(&self.assignments, None) { + Ok(nodes) => nodes + .into_iter() + .filter_map(|n| match n { + AssignmentOrSlotIndex::AssignmentIndex(v) => Some(v), + _ => None, + }) + .collect(), + Err(e) => match e.node_id() { + AssignmentOrSlotIndex::AssignmentIndex(assignment_index) => panic!( + "combinatorial logic cycle detected at: {}", + self.assignments.assignments()[assignment_index].source_location, + ), + AssignmentOrSlotIndex::SmallSlot(slot) => panic!( + "combinatorial logic cycle detected through: {}", + self.insns.state_layout().ty.small_slots.debug_data[slot.as_usize()].name, + ), + AssignmentOrSlotIndex::BigSlot(slot) => panic!( + "combinatorial logic cycle detected through: {}", + self.insns.state_layout().ty.big_slots.debug_data[slot.as_usize()].name, + ), + }, + }; + struct CondStackEntry<'a> { + cond: &'a Cond, + end_label: Label, + } + let mut cond_stack = Vec::>::new(); + for assignment_index in assignments_order { + let Assignment { + inputs: _, + outputs: _, + conditions, + insns, + source_location, + } = &self.assignments.assignments()[assignment_index]; + let mut same_len = 0; + for (index, (entry, cond)) in cond_stack.iter().zip(conditions).enumerate() { + if entry.cond != cond { + break; + } + same_len = index + 1; + } + while cond_stack.len() > same_len { + let CondStackEntry { cond: _, end_label } = + cond_stack.pop().expect("just checked len"); + self.insns.define_label_at_next_insn(end_label); + } + for cond in &conditions[cond_stack.len()..] { + let end_label = self.insns.new_label(); + match cond.body { + CondBody::IfTrue { cond: cond_value } + | CondBody::IfFalse { cond: cond_value } => { + let (branch_if_zero, branch_if_non_zero) = match cond_value.range.len() { + TypeLen::A_SMALL_SLOT => ( + Insn::BranchIfSmallZero { + target: end_label.0, + value: cond_value.range.small_slots.start, + }, + Insn::BranchIfSmallNonZero { + target: end_label.0, + value: cond_value.range.small_slots.start, + }, + ), + TypeLen::A_BIG_SLOT => ( + Insn::BranchIfZero { + target: end_label.0, + value: cond_value.range.big_slots.start, + }, + Insn::BranchIfNonZero { + target: end_label.0, + value: cond_value.range.big_slots.start, + }, + ), + _ => unreachable!(), + }; + self.insns.push( + if let CondBody::IfTrue { .. } = cond.body { + branch_if_zero + } else { + branch_if_non_zero + }, + cond.source_location, + ); + } + CondBody::MatchArm { + discriminant, + variant_index, + } => { + self.insns.push( + Insn::BranchIfSmallNeImmediate { + target: end_label.0, + lhs: discriminant, + rhs: variant_index as _, + }, + cond.source_location, + ); + } + } + cond_stack.push(CondStackEntry { cond, end_label }); + } + self.insns.extend(insns.iter().copied(), *source_location); + } + for CondStackEntry { cond: _, end_label } in cond_stack { + self.insns.define_label_at_next_insn(end_label); + } + } + fn process_clocks(&mut self) -> Interned<[StatePartIndex]> { + mem::take(&mut self.clock_triggers) + .into_iter() + .map( + |ClockTrigger { + last_clk_was_low, + clk, + clk_triggered, + source_location, + }| { + self.insns.push( + Insn::XorSmallImmediate { + dest: last_clk_was_low, + lhs: clk, + rhs: 1, + }, + source_location, + ); + clk_triggered + }, + ) + .collect() + } + fn process_registers(&mut self) { + for Register { + value, + clk_triggered, + reset, + source_location, + } in mem::take(&mut self.registers) + { + match reset { + Some(RegisterReset { + is_async, + init, + rst, + }) => { + let reg_end = self.insns.new_label(); + let reg_reset = self.insns.new_label(); + let branch_if_reset = Insn::BranchIfSmallNonZero { + target: reg_reset.0, + value: rst, + }; + let branch_if_not_triggered = Insn::BranchIfSmallZero { + target: reg_end.0, + value: clk_triggered, + }; + if is_async { + self.insns.push(branch_if_reset, source_location); + self.insns.push(branch_if_not_triggered, source_location); + } else { + self.insns.push(branch_if_not_triggered, source_location); + self.insns.push(branch_if_reset, source_location); + } + self.insns.extend( + value.range.insns_for_copy_from(value.write_value().range), + source_location, + ); + self.insns + .push(Insn::Branch { target: reg_end.0 }, source_location); + self.insns.define_label_at_next_insn(reg_reset); + self.insns + .extend(value.range.insns_for_copy_from(init.range), source_location); + self.insns.define_label_at_next_insn(reg_end); + } + None => { + let reg_end = self.insns.new_label(); + self.insns.push( + Insn::BranchIfSmallZero { + target: reg_end.0, + value: clk_triggered, + }, + source_location, + ); + self.insns.extend( + value.range.insns_for_copy_from(value.write_value().range), + source_location, + ); + self.insns.define_label_at_next_insn(reg_end); + } + } + } + } + fn process_memories(&mut self) { + for memory_index in 0..self.memories.len() { + let Memory { + mem, + memory: _, + trace: _, + ref mut ports, + } = self.memories[memory_index]; + for MemoryPort { + clk_triggered, + addr_delayed, + en_delayed, + data_layout: _, + read_data_delayed, + write_data_delayed, + write_mask_delayed, + write_mode_delayed, + write_insns, + } in mem::take(ports) + { + let port_end = self.insns.new_label(); + let small_shift_reg = + |this: &mut Self, values: &[StatePartIndex]| { + for pair in values.windows(2).rev() { + this.insns.push( + Insn::CopySmall { + dest: pair[1], + src: pair[0], + }, + mem.source_location(), + ); + } + }; + let shift_reg = |this: &mut Self, values: &[TypeIndexRange]| { + for pair in values.windows(2).rev() { + this.insns + .extend(pair[0].insns_for_copy_to(pair[1]), mem.source_location()); + } + }; + self.insns.push( + Insn::BranchIfSmallZero { + target: port_end.0, + value: clk_triggered, + }, + mem.source_location(), + ); + small_shift_reg(self, &addr_delayed); + small_shift_reg(self, &en_delayed); + shift_reg(self, &write_data_delayed); + shift_reg(self, &write_mask_delayed); + small_shift_reg(self, &write_mode_delayed); + shift_reg(self, &read_data_delayed); + self.insns.extend(write_insns, mem.source_location()); + self.insns.define_label_at_next_insn(port_end); + } + } + } + pub fn compile(mut self) -> Compiled { + let base_module = + *self.compile_module(InstantiatedModule::Base(self.base_module).intern_sized()); + self.process_assignments(); + self.process_registers(); + self.process_memories(); + let clocks_triggered = self.process_clocks(); + self.insns + .push(Insn::Return, self.base_module.source_location()); + Compiled { + insns: Insns::from(self.insns).intern_sized(), + base_module, + extern_modules: Intern::intern_owned(self.extern_modules), + io: Instance::new_unchecked( + ScopedNameId( + NameId("".intern(), Id::new()), + self.original_base_module.name_id(), + ), + self.original_base_module, + self.original_base_module.source_location(), + ), + traces: SimTraces(Intern::intern_owned(self.traces.0)), + trace_memories: Interned::from_iter(self.memories.iter().map( + |&Memory { + mem: _, + memory, + trace, + ports: _, + }| (memory, trace), + )), + clocks_triggered, + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +struct CompiledModule { + module_io: Interned<[CompiledValue]>, + trace_decls: TraceModule, +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct Compiled { + insns: Interned>, + base_module: CompiledModule, + extern_modules: Interned<[CompiledExternModule]>, + io: Instance, + traces: SimTraces]>>, + trace_memories: Interned<[(StatePartIndex, TraceMem)]>, + clocks_triggered: Interned<[StatePartIndex]>, +} + +impl Compiled { + pub fn new(module: Interned>) -> Self { + Self::from_canonical(Compiler::new(module.canonical().intern()).compile()) + } + pub fn canonical(self) -> Compiled { + let Self { + insns, + base_module, + extern_modules, + io, + traces, + trace_memories, + clocks_triggered, + } = self; + Compiled { + insns, + base_module, + extern_modules, + io: Instance::from_canonical(io.canonical()), + traces, + trace_memories, + clocks_triggered, + } + } + pub fn from_canonical(canonical: Compiled) -> Self { + let Compiled { + insns, + base_module, + extern_modules, + io, + traces, + trace_memories, + clocks_triggered, + } = canonical; + Self { + insns, + base_module, + extern_modules, + io: Instance::from_canonical(io.canonical()), + traces, + trace_memories, + clocks_triggered, + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TraceScalarId(usize); + +impl fmt::Debug for TraceScalarId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "TraceScalarId({})", self.0) + } +} + +impl TraceScalarId { + pub fn as_usize(self) -> usize { + self.0 + } +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TraceMemoryId(usize); + +impl fmt::Debug for TraceMemoryId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "TraceMemoryId({})", self.0) + } +} + +impl TraceMemoryId { + pub fn as_usize(self) -> usize { + self.0 + } +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct TraceMemoryLocation { + pub id: TraceMemoryId, + pub depth: usize, + pub stride: usize, + pub start: usize, + pub len: usize, +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum TraceLocation { + Scalar(TraceScalarId), + Memory(TraceMemoryLocation), +} + +impl fmt::Debug for TraceLocation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Scalar(v) => v.fmt(f), + Self::Memory(v) => v.fmt(f), + } + } +} + +macro_rules! impl_trace_decl { + ( + $( + #[kind = $category_kind:ident] + $(#[$category_meta:meta])* + $category_variant:ident($category_enum:ident { + fn $category_property_fn:ident(self) -> $category_property_fn_ret_ty:ty; + $( + $(#[$meta:meta])* + $variant:ident($struct:ident { + fn $property_fn:ident($property_fn_self:ident) -> _ $property_fn_block:block + $($(#[$field_meta:meta])* + $field_name:ident: $field_ty:ty,)* + }), + )* + }), + )* + ) => { + $( + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] + #[non_exhaustive] + $(#[$category_meta])* + pub enum $category_kind { + $($(#[$meta])* + $variant,)* + } + + impl From<$category_kind> for TraceKind { + fn from(v: $category_kind) -> Self { + TraceKind::$category_variant(v) + } + } + + #[derive(Copy, Clone, PartialEq, Eq, Hash)] + #[non_exhaustive] + $(#[$category_meta])* + pub enum $category_enum { + $($(#[$meta])* + $variant($struct),)* + } + + impl fmt::Debug for $category_enum { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + $(Self::$variant(v) => v.fmt(f),)* + } + } + } + + impl $category_enum { + pub fn kind(self) -> $category_kind { + match self { + $(Self::$variant(_) => $category_kind::$variant,)* + } + } + pub fn name(self) -> Interned { + match self { + $(Self::$variant(v) => v.name,)* + } + } + pub fn $category_property_fn(self) -> $category_property_fn_ret_ty { + match self { + $(Self::$variant(v) => v.$property_fn(),)* + } + } + } + + impl From<$category_enum> for TraceDecl { + fn from(v: $category_enum) -> Self { + TraceDecl::$category_variant(v) + } + } + + $( + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + #[non_exhaustive] + $(#[$meta])* + pub struct $struct { + $($(#[$field_meta])* + pub $field_name: $field_ty,)* + } + + impl $struct { + pub fn $property_fn($property_fn_self) -> $category_property_fn_ret_ty $property_fn_block + } + + impl From<$struct> for $category_enum { + fn from(v: $struct) -> Self { + $category_enum::$variant(v) + } + } + + impl From<$struct> for TraceDecl { + fn from(v: $struct) -> Self { + TraceDecl::$category_variant($category_enum::$variant(v)) + } + } + )* + )* + + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum TraceKind { + $($(#[$category_meta])* + $category_variant($category_kind),)* + } + + impl fmt::Debug for TraceKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + $(Self::$category_variant(v) => v.fmt(f),)* + } + } + } + + #[derive(Copy, Clone, PartialEq, Eq, Hash)] + pub enum TraceDecl { + $($(#[$category_meta])* + $category_variant($category_enum),)* + } + + impl fmt::Debug for TraceDecl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + $(Self::$category_variant(v) => v.fmt(f),)* + } + } + } + }; +} + +impl_trace_decl! { + #[kind = TraceScopeKind] + Scope(TraceScope { + fn children(self) -> Interned<[TraceDecl]>; + Module(TraceModule { + fn children(self) -> _ { + self.children + } + name: Interned, + children: Interned<[TraceDecl]>, + }), + Instance(TraceInstance { + fn children(self) -> _ { + [self.instance_io.into(), self.module.into()][..].intern() + } + name: Interned, + instance_io: TraceBundle, + module: TraceModule, + ty: Bundle, + }), + Mem(TraceMem { + fn children(self) -> _ { + Interned::from_iter([*self.element_type].into_iter().chain(self.ports.iter().map(|&v| v.into()))) + } + id: TraceMemoryId, + name: Interned, + stride: usize, + element_type: Interned, + ports: Interned<[TraceMemPort]>, + array_type: Array, + }), + MemPort(TraceMemPort { + fn children(self) -> _ { + [self.bundle.into()][..].intern() + } + name: Interned, + bundle: TraceBundle, + ty: Bundle, + }), + Wire(TraceWire { + fn children(self) -> _ { + [*self.child][..].intern() + } + name: Interned, + child: Interned, + ty: CanonicalType, + }), + Reg(TraceReg { + fn children(self) -> _ { + [*self.child][..].intern() + } + name: Interned, + child: Interned, + ty: CanonicalType, + }), + ModuleIO(TraceModuleIO { + fn children(self) -> _ { + [*self.child][..].intern() + } + name: Interned, + child: Interned, + ty: CanonicalType, + flow: Flow, + }), + Bundle(TraceBundle { + fn children(self) -> _ { + self.fields + } + name: Interned, + fields: Interned<[TraceDecl]>, + ty: Bundle, + flow: Flow, + }), + Array(TraceArray { + fn children(self) -> _ { + self.elements + } + name: Interned, + elements: Interned<[TraceDecl]>, + ty: Array, + flow: Flow, + }), + EnumWithFields(TraceEnumWithFields { + fn children(self) -> _ { + Interned::from_iter([self.discriminant.into()].into_iter().chain(self.non_empty_fields)) + } + name: Interned, + discriminant: TraceEnumDiscriminant, + non_empty_fields: Interned<[TraceDecl]>, + ty: Enum, + flow: Flow, + }), + }), + #[kind = TraceScalarKind] + Scalar(TraceScalar { + fn location(self) -> TraceLocation; + UInt(TraceUInt { + fn location(self) -> _ { + self.location + } + location: TraceLocation, + name: Interned, + ty: UInt, + flow: Flow, + }), + SInt(TraceSInt { + fn location(self) -> _ { + self.location + } + location: TraceLocation, + name: Interned, + ty: SInt, + flow: Flow, + }), + Bool(TraceBool { + fn location(self) -> _ { + self.location + } + location: TraceLocation, + name: Interned, + flow: Flow, + }), + FieldlessEnum(TraceFieldlessEnum { + fn location(self) -> _ { + self.location + } + location: TraceLocation, + name: Interned, + ty: Enum, + flow: Flow, + }), + EnumDiscriminant(TraceEnumDiscriminant { + fn location(self) -> _ { + self.location + } + location: TraceLocation, + name: Interned, + ty: Enum, + flow: Flow, + }), + Clock(TraceClock { + fn location(self) -> _ { + self.location + } + location: TraceLocation, + name: Interned, + flow: Flow, + }), + SyncReset(TraceSyncReset { + fn location(self) -> _ { + self.location + } + location: TraceLocation, + name: Interned, + flow: Flow, + }), + AsyncReset(TraceAsyncReset { + fn location(self) -> _ { + self.location + } + location: TraceLocation, + name: Interned, + flow: Flow, + }), + }), +} + +pub trait TraceWriterDecls: fmt::Debug + 'static + Sized { + type Error: std::error::Error + Send + Sync + 'static; + type TraceWriter: TraceWriter; + fn write_decls( + self, + module: TraceModule, + trace_scalar_id_count: usize, + trace_memory_id_count: usize, + ) -> Result; +} + +trait TraceWriterDeclsDynTrait: fmt::Debug { + fn write_decls_dyn( + self: Box, + module: TraceModule, + trace_scalar_id_count: usize, + trace_memory_id_count: usize, + ) -> std::io::Result; +} + +fn err_into_io(e: E) -> std::io::Error { + match ::downcast::(Box::new(e)) { + Ok(retval) => *retval, + Err(e) => std::io::Error::other(e), + } +} + +impl TraceWriterDeclsDynTrait for T { + fn write_decls_dyn( + self: Box, + module: TraceModule, + trace_scalar_id_count: usize, + trace_memory_id_count: usize, + ) -> std::io::Result { + Ok(DynTraceWriter(Box::new( + TraceWriterDecls::write_decls( + *self, + module, + trace_scalar_id_count, + trace_memory_id_count, + ) + .map_err(err_into_io)?, + ))) + } +} + +pub trait TraceWriter: fmt::Debug + 'static { + type Error: std::error::Error + Send + Sync + 'static; + fn finish_init(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + fn change_time_to(&mut self, instant: SimInstant) -> Result<(), Self::Error> { + let _ = instant; + Ok(()) + } + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + fn close(self) -> Result<(), Self::Error> + where + Self: Sized, + { + Ok(()) + } + fn set_memory_element( + &mut self, + memory: TraceMemoryId, + element_index: usize, + element_data: &BitSlice, + ) -> Result<(), Self::Error>; + fn set_signal_uint(&mut self, id: TraceScalarId, value: &BitSlice) -> Result<(), Self::Error>; + fn set_signal_sint(&mut self, id: TraceScalarId, value: &BitSlice) -> Result<(), Self::Error>; + fn set_signal_bool(&mut self, id: TraceScalarId, value: bool) -> Result<(), Self::Error> { + if value { + self.set_signal_uint(id, bits![1]) + } else { + self.set_signal_uint(id, bits![0]) + } + } + fn set_signal_clock(&mut self, id: TraceScalarId, value: bool) -> Result<(), Self::Error> { + self.set_signal_bool(id, value) + } + fn set_signal_sync_reset(&mut self, id: TraceScalarId, value: bool) -> Result<(), Self::Error> { + self.set_signal_bool(id, value) + } + fn set_signal_async_reset( + &mut self, + id: TraceScalarId, + value: bool, + ) -> Result<(), Self::Error> { + self.set_signal_bool(id, value) + } + fn set_signal_enum_discriminant( + &mut self, + id: TraceScalarId, + variant_index: usize, + ty: Enum, + ) -> Result<(), Self::Error>; +} + +pub struct DynTraceWriterDecls(Box); + +impl DynTraceWriterDecls { + pub fn new(writer: W) -> Self { + Self(Box::new(writer)) + } +} + +impl fmt::Debug for DynTraceWriterDecls { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl TraceWriterDecls for DynTraceWriterDecls { + type Error = std::io::Error; + type TraceWriter = DynTraceWriter; + fn write_decls( + self, + module: TraceModule, + trace_scalar_id_count: usize, + trace_memory_id_count: usize, + ) -> Result { + self.0 + .write_decls_dyn(module, trace_scalar_id_count, trace_memory_id_count) + } +} + +trait TraceWriterDynTrait: fmt::Debug + 'static { + fn finish_init_dyn(&mut self) -> std::io::Result<()>; + fn change_time_to_dyn(&mut self, instant: SimInstant) -> std::io::Result<()>; + fn flush_dyn(&mut self) -> std::io::Result<()>; + fn close_dyn(self: Box) -> std::io::Result<()>; + fn set_memory_element_dyn( + &mut self, + memory: TraceMemoryId, + element_index: usize, + element_data: &BitSlice, + ) -> std::io::Result<()>; + fn set_signal_uint_dyn(&mut self, id: TraceScalarId, value: &BitSlice) -> std::io::Result<()>; + fn set_signal_sint_dyn(&mut self, id: TraceScalarId, value: &BitSlice) -> std::io::Result<()>; + fn set_signal_bool_dyn(&mut self, id: TraceScalarId, value: bool) -> std::io::Result<()>; + fn set_signal_clock_dyn(&mut self, id: TraceScalarId, value: bool) -> std::io::Result<()>; + fn set_signal_sync_reset_dyn(&mut self, id: TraceScalarId, value: bool) -> std::io::Result<()>; + fn set_signal_async_reset_dyn(&mut self, id: TraceScalarId, value: bool) + -> std::io::Result<()>; + fn set_signal_enum_discriminant_dyn( + &mut self, + id: TraceScalarId, + variant_index: usize, + ty: Enum, + ) -> std::io::Result<()>; +} + +impl TraceWriterDynTrait for T { + fn finish_init_dyn(&mut self) -> std::io::Result<()> { + Ok(TraceWriter::finish_init(self).map_err(err_into_io)?) + } + fn change_time_to_dyn(&mut self, instant: SimInstant) -> std::io::Result<()> { + Ok(TraceWriter::change_time_to(self, instant).map_err(err_into_io)?) + } + fn flush_dyn(&mut self) -> std::io::Result<()> { + Ok(TraceWriter::flush(self).map_err(err_into_io)?) + } + fn close_dyn(self: Box) -> std::io::Result<()> { + Ok(TraceWriter::close(*self).map_err(err_into_io)?) + } + fn set_memory_element_dyn( + &mut self, + memory: TraceMemoryId, + element_index: usize, + element_data: &BitSlice, + ) -> std::io::Result<()> { + Ok( + TraceWriter::set_memory_element(self, memory, element_index, element_data) + .map_err(err_into_io)?, + ) + } + fn set_signal_uint_dyn(&mut self, id: TraceScalarId, value: &BitSlice) -> std::io::Result<()> { + Ok(TraceWriter::set_signal_uint(self, id, value).map_err(err_into_io)?) + } + fn set_signal_sint_dyn(&mut self, id: TraceScalarId, value: &BitSlice) -> std::io::Result<()> { + Ok(TraceWriter::set_signal_sint(self, id, value).map_err(err_into_io)?) + } + fn set_signal_bool_dyn(&mut self, id: TraceScalarId, value: bool) -> std::io::Result<()> { + Ok(TraceWriter::set_signal_bool(self, id, value).map_err(err_into_io)?) + } + fn set_signal_clock_dyn(&mut self, id: TraceScalarId, value: bool) -> std::io::Result<()> { + Ok(TraceWriter::set_signal_clock(self, id, value).map_err(err_into_io)?) + } + fn set_signal_sync_reset_dyn(&mut self, id: TraceScalarId, value: bool) -> std::io::Result<()> { + Ok(TraceWriter::set_signal_sync_reset(self, id, value).map_err(err_into_io)?) + } + fn set_signal_async_reset_dyn( + &mut self, + id: TraceScalarId, + value: bool, + ) -> std::io::Result<()> { + Ok(TraceWriter::set_signal_async_reset(self, id, value).map_err(err_into_io)?) + } + fn set_signal_enum_discriminant_dyn( + &mut self, + id: TraceScalarId, + variant_index: usize, + ty: Enum, + ) -> std::io::Result<()> { + Ok( + TraceWriter::set_signal_enum_discriminant(self, id, variant_index, ty) + .map_err(err_into_io)?, + ) + } +} + +pub struct DynTraceWriter(Box); + +impl fmt::Debug for DynTraceWriter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl TraceWriter for DynTraceWriter { + type Error = std::io::Error; + fn finish_init(&mut self) -> Result<(), Self::Error> { + self.0.finish_init_dyn() + } + fn flush(&mut self) -> Result<(), Self::Error> { + self.0.flush_dyn() + } + fn close(self) -> Result<(), Self::Error> { + self.0.close_dyn() + } + fn set_memory_element( + &mut self, + memory: TraceMemoryId, + element_index: usize, + element_data: &BitSlice, + ) -> Result<(), Self::Error> { + self.0 + .set_memory_element_dyn(memory, element_index, element_data) + } + fn change_time_to(&mut self, instant: SimInstant) -> Result<(), Self::Error> { + self.0.change_time_to_dyn(instant) + } + fn set_signal_uint(&mut self, id: TraceScalarId, value: &BitSlice) -> Result<(), Self::Error> { + self.0.set_signal_uint_dyn(id, value) + } + fn set_signal_sint(&mut self, id: TraceScalarId, value: &BitSlice) -> Result<(), Self::Error> { + self.0.set_signal_sint_dyn(id, value) + } + fn set_signal_bool(&mut self, id: TraceScalarId, value: bool) -> Result<(), Self::Error> { + self.0.set_signal_bool_dyn(id, value) + } + fn set_signal_clock(&mut self, id: TraceScalarId, value: bool) -> Result<(), Self::Error> { + self.0.set_signal_clock_dyn(id, value) + } + fn set_signal_sync_reset(&mut self, id: TraceScalarId, value: bool) -> Result<(), Self::Error> { + self.0.set_signal_sync_reset_dyn(id, value) + } + fn set_signal_async_reset( + &mut self, + id: TraceScalarId, + value: bool, + ) -> Result<(), Self::Error> { + self.0.set_signal_async_reset_dyn(id, value) + } + fn set_signal_enum_discriminant( + &mut self, + id: TraceScalarId, + variant_index: usize, + ty: Enum, + ) -> Result<(), Self::Error> { + self.0 + .set_signal_enum_discriminant_dyn(id, variant_index, ty) + } +} + +#[derive(Debug)] +enum TraceWriterState { + Decls(T), + Init(T::TraceWriter), + Running(T::TraceWriter), + Errored(Option), +} + +trait SimTraceDebug { + fn fmt(&self, id: I, f: &mut fmt::Formatter<'_>) -> fmt::Result; +} + +struct SimTraceDebugAsDebug(T, I); + +impl fmt::Debug for SimTraceDebugAsDebug<&'_ T, I> +where + T: SimTraceDebug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(self.1, f) + } +} + +impl SimTraceDebug for Vec +where + [T]: SimTraceDebug, +{ + fn fmt(&self, id: I, f: &mut fmt::Formatter<'_>) -> fmt::Result { + <[T]>::fmt(&**self, id, f) + } +} + +impl SimTraceDebug for Interned +where + T: SimTraceDebug, +{ + fn fmt(&self, id: I, f: &mut fmt::Formatter<'_>) -> fmt::Result { + T::fmt(&**self, id, f) + } +} + +impl SimTraceDebug for Box +where + T: SimTraceDebug, +{ + fn fmt(&self, id: I, f: &mut fmt::Formatter<'_>) -> fmt::Result { + T::fmt(&**self, id, f) + } +} + +impl SimTraceDebug<()> for [T] +where + T: SimTraceDebug, +{ + fn fmt(&self, _id: (), f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list() + .entries( + self.iter() + .enumerate() + .map(|(id, v)| SimTraceDebugAsDebug(v, TraceScalarId(id))), + ) + .finish() + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +struct SimTrace { + kind: K, + state: S, + last_state: S, +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +struct SimTraces(T); + +impl fmt::Debug for SimTraces +where + T: SimTraceDebug<()>, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt((), f) + } +} + +impl SimTraceDebug for SimTrace { + fn fmt(&self, id: TraceScalarId, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + kind, + state, + last_state, + } = self; + f.debug_struct("SimTrace") + .field("id", &id) + .field("kind", kind) + .field("state", state) + .field("last_state", last_state) + .finish() + } +} + +impl SimTraceDebug for SimTrace { + fn fmt(&self, id: TraceScalarId, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + kind, + state, + last_state, + } = self; + f.debug_struct("SimTrace") + .field("id", &id) + .field("kind", kind) + .field("state", &BitSliceWriteWithBase(state)) + .field("last_state", &BitSliceWriteWithBase(last_state)) + .finish() + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +enum SimTraceKind { + BigUInt { + index: StatePartIndex, + ty: UInt, + }, + BigSInt { + index: StatePartIndex, + ty: SInt, + }, + BigBool { + index: StatePartIndex, + }, + BigAsyncReset { + index: StatePartIndex, + }, + BigSyncReset { + index: StatePartIndex, + }, + BigClock { + index: StatePartIndex, + }, + SmallUInt { + index: StatePartIndex, + ty: UInt, + }, + SmallSInt { + index: StatePartIndex, + ty: SInt, + }, + SmallBool { + index: StatePartIndex, + }, + SmallAsyncReset { + index: StatePartIndex, + }, + SmallSyncReset { + index: StatePartIndex, + }, + SmallClock { + index: StatePartIndex, + }, + EnumDiscriminant { + index: StatePartIndex, + ty: Enum, + }, +} + +impl SimTraceKind { + fn make_state(self) -> BitVec { + match self { + SimTraceKind::BigUInt { index: _, ty } | SimTraceKind::SmallUInt { index: _, ty } => { + BitVec::repeat(false, ty.width) + } + SimTraceKind::BigSInt { index: _, ty } | SimTraceKind::SmallSInt { index: _, ty } => { + BitVec::repeat(false, ty.width) + } + SimTraceKind::BigBool { index: _ } + | SimTraceKind::BigAsyncReset { index: _ } + | SimTraceKind::BigSyncReset { index: _ } + | SimTraceKind::BigClock { index: _ } + | SimTraceKind::SmallBool { index: _ } + | SimTraceKind::SmallAsyncReset { index: _ } + | SimTraceKind::SmallSyncReset { index: _ } + | SimTraceKind::SmallClock { index: _ } => BitVec::repeat(false, 1), + SimTraceKind::EnumDiscriminant { index: _, ty } => { + BitVec::repeat(false, ty.discriminant_bit_width()) + } + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +enum MaybeNeedsSettle { + NeedsSettle(S), + NoSettleNeeded(N), +} + +impl MaybeNeedsSettle { + fn map(self, f: impl FnOnce(T) -> U) -> MaybeNeedsSettle { + match self { + MaybeNeedsSettle::NeedsSettle(v) => MaybeNeedsSettle::NeedsSettle(f(v)), + MaybeNeedsSettle::NoSettleNeeded(v) => MaybeNeedsSettle::NoSettleNeeded(f(v)), + } + } +} + +// workaround implementing FnOnce not being stable +trait MaybeNeedsSettleFn { + type Output; + + fn call(self, arg: A) -> Self::Output; +} + +impl O, A, O> MaybeNeedsSettleFn for T { + type Output = O; + + fn call(self, arg: A) -> Self::Output { + self(arg) + } +} + +impl MaybeNeedsSettle { + fn apply_no_settle(self, arg: T) -> MaybeNeedsSettle + where + N: MaybeNeedsSettleFn, + { + match self { + MaybeNeedsSettle::NeedsSettle(v) => MaybeNeedsSettle::NeedsSettle(v), + MaybeNeedsSettle::NoSettleNeeded(v) => MaybeNeedsSettle::NoSettleNeeded(v.call(arg)), + } + } +} + +struct SimulationModuleState { + base_targets: Vec, + uninitialized_ios: HashMap>, + io_targets: HashMap>, + did_initial_settle: bool, +} + +impl fmt::Debug for SimulationModuleState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + base_targets, + uninitialized_ios, + io_targets, + did_initial_settle, + } = self; + f.debug_struct("SimulationModuleState") + .field("base_targets", base_targets) + .field("uninitialized_ios", &SortedSetDebug(uninitialized_ios)) + .field("io_targets", &SortedSetDebug(io_targets)) + .field("did_initial_settle", did_initial_settle) + .finish() + } +} + +impl SimulationModuleState { + fn new(base_targets: impl IntoIterator)>) -> Self { + let mut retval = Self { + base_targets: Vec::new(), + uninitialized_ios: HashMap::default(), + io_targets: HashMap::default(), + did_initial_settle: false, + }; + for (base_target, value) in base_targets { + retval.base_targets.push(base_target); + retval.parse_io(base_target, value); + } + retval + } + /// returns `true` if `target` or any sub-targets are uninitialized inputs + fn parse_io(&mut self, target: Target, value: CompiledValue) -> bool { + self.io_targets.insert(target, value); + match value.layout.body { + CompiledTypeLayoutBody::Scalar => match target.flow() { + Flow::Source => false, + Flow::Sink => { + self.uninitialized_ios.insert(target, vec![]); + true + } + Flow::Duplex => unreachable!(), + }, + CompiledTypeLayoutBody::Array { .. } => { + let value = value.map_ty(Array::from_canonical); + let mut sub_targets = Vec::new(); + for index in 0..value.layout.ty.len() { + let sub_target = target.join( + TargetPathElement::from(TargetPathArrayElement { index }).intern_sized(), + ); + if self.parse_io(sub_target, value.element(index)) { + sub_targets.push(sub_target); + } + } + if sub_targets.is_empty() { + false + } else { + self.uninitialized_ios.insert(target, sub_targets); + true + } + } + CompiledTypeLayoutBody::Bundle { .. } => { + let value = value.map_ty(Bundle::from_canonical); + let mut sub_targets = Vec::new(); + for BundleField { name, .. } in value.layout.ty.fields() { + let sub_target = target.join( + TargetPathElement::from(TargetPathBundleField { name }).intern_sized(), + ); + if self.parse_io(sub_target, value.field_by_name(name)) { + sub_targets.push(sub_target); + } + } + if sub_targets.is_empty() { + false + } else { + self.uninitialized_ios.insert(target, sub_targets); + true + } + } + } + } + fn mark_target_as_initialized(&mut self, mut target: Target) { + fn remove_target_and_children( + uninitialized_ios: &mut HashMap>, + target: Target, + ) { + let Some(children) = uninitialized_ios.remove(&target) else { + return; + }; + for child in children { + remove_target_and_children(uninitialized_ios, child); + } + } + remove_target_and_children(&mut self.uninitialized_ios, target); + while let Some(target_child) = target.child() { + let parent = target_child.parent(); + for child in self + .uninitialized_ios + .get(&*parent) + .map(|v| &**v) + .unwrap_or(&[]) + { + if self.uninitialized_ios.contains_key(child) { + return; + } + } + target = *parent; + self.uninitialized_ios.remove(&target); + } + } + #[track_caller] + fn get_io( + &self, + mut target: Target, + which_module: WhichModule, + ) -> CompiledValue { + assert!( + target.canonical_ty().is_passive(), + "simulator read/write expression must have a passive type \ + (recursively contains no fields with `#[hdl(flip)]`)" + ); + if let Some(&retval) = self.io_targets.get(&target) { + return retval; + } + loop { + target = match target { + Target::Base(_) => break, + Target::Child(child) => { + match *child.path_element() { + TargetPathElement::BundleField(_) | TargetPathElement::ArrayElement(_) => {} + TargetPathElement::DynArrayElement(_) => panic!( + "simulator read/write expression must not have dynamic array indexes" + ), + } + *child.parent() + } + }; + } + match which_module { + WhichModule::Main => panic!( + "simulator read/write expression must be \ + an array element/field of `Simulation::io()`" + ), + WhichModule::Extern { .. } => panic!( + "simulator read/write expression must be \ + one of this module's inputs/outputs or an \ + array element/field of one of this module's inputs/outputs" + ), + } + } + #[track_caller] + fn read_helper( + &self, + io: Expr, + which_module: WhichModule, + ) -> MaybeNeedsSettle> { + let Some(target) = io.target() else { + match which_module { + WhichModule::Main => panic!( + "can't read from an expression that's not a field/element of `Simulation::io()`" + ), + WhichModule::Extern { .. } => panic!( + "can't read from an expression that's not based on one of this module's inputs/outputs" + ), + } + }; + let compiled_value = self.get_io(*target, which_module); + match target.flow() { + Flow::Source => { + if !self.uninitialized_ios.is_empty() { + match which_module { + WhichModule::Main => { + panic!("can't read from an output before initializing all inputs"); + } + WhichModule::Extern { .. } => { + panic!("can't read from an input before initializing all outputs"); + } + } + } + MaybeNeedsSettle::NeedsSettle(compiled_value) + } + Flow::Sink => { + if self.uninitialized_ios.contains_key(&*target) { + match which_module { + WhichModule::Main => panic!("can't read from an uninitialized input"), + WhichModule::Extern { .. } => { + panic!("can't read from an uninitialized output"); + } + } + } + MaybeNeedsSettle::NoSettleNeeded(compiled_value) + } + Flow::Duplex => unreachable!(), + } + } + #[track_caller] + fn write_helper( + &mut self, + io: Expr, + which_module: WhichModule, + ) -> CompiledValue { + let Some(target) = io.target() else { + match which_module { + WhichModule::Main => panic!( + "can't write to an expression that's not a field/element of `Simulation::io()`" + ), + WhichModule::Extern { .. } => panic!( + "can't write to an expression that's not based on one of this module's outputs" + ), + } + }; + let compiled_value = self.get_io(*target, which_module); + match target.flow() { + Flow::Source => match which_module { + WhichModule::Main => panic!("can't write to an output"), + WhichModule::Extern { .. } => panic!("can't write to an input"), + }, + Flow::Sink => {} + Flow::Duplex => unreachable!(), + } + if !self.did_initial_settle { + self.mark_target_as_initialized(*target); + } + compiled_value + } +} + +#[derive(Copy, Clone, Debug)] +enum WaitTarget { + Settle, + Instant(SimInstant), + Change { key: ChangeKey, value: ChangeValue }, +} + +#[derive(Clone)] +struct EarliestWaitTargets { + settle: bool, + instant: Option, + changes: HashMap, SimValue>, +} + +impl fmt::Debug for EarliestWaitTargets { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_set().entries(self.iter()).finish() + } +} + +impl Default for EarliestWaitTargets { + fn default() -> Self { + Self { + settle: false, + instant: None, + changes: HashMap::default(), + } + } +} + +impl EarliestWaitTargets { + fn settle() -> Self { + Self { + settle: true, + instant: None, + changes: HashMap::default(), + } + } + fn instant(instant: SimInstant) -> Self { + Self { + settle: false, + instant: Some(instant), + changes: HashMap::default(), + } + } + fn len(&self) -> usize { + self.settle as usize + self.instant.is_some() as usize + self.changes.len() + } + fn is_empty(&self) -> bool { + self.len() == 0 + } + fn clear(&mut self) { + let Self { + settle, + instant, + changes, + } = self; + *settle = false; + *instant = None; + changes.clear(); + } + fn insert( + &mut self, + value: impl std::borrow::Borrow, ChangeValue>>, + ) where + ChangeValue: std::borrow::Borrow>, + { + let value = value.borrow(); + match value { + WaitTarget::Settle => self.settle = true, + WaitTarget::Instant(instant) => { + if self.instant.is_none_or(|v| v > *instant) { + self.instant = Some(*instant); + } + } + WaitTarget::Change { key, value } => { + self.changes + .entry(*key) + .or_insert_with(|| value.borrow().clone()); + } + } + } + fn convert_earlier_instants_to_settle(&mut self, instant: SimInstant) { + if self.instant.is_some_and(|v| v <= instant) { + self.settle = true; + self.instant = None; + } + } + fn iter<'a>( + &'a self, + ) -> impl Clone + + Iterator, &'a SimValue>> + + 'a { + self.settle + .then_some(WaitTarget::Settle) + .into_iter() + .chain(self.instant.map(|instant| WaitTarget::Instant(instant))) + .chain( + self.changes + .iter() + .map(|(&key, value)| WaitTarget::Change { key, value }), + ) + } +} + +impl>> + Extend, ChangeValue>> for EarliestWaitTargets +{ + fn extend, ChangeValue>>>( + &mut self, + iter: T, + ) { + iter.into_iter().for_each(|v| self.insert(v)) + } +} + +impl<'a, ChangeValue: std::borrow::Borrow>> + Extend<&'a WaitTarget, ChangeValue>> for EarliestWaitTargets +{ + fn extend, ChangeValue>>>( + &mut self, + iter: T, + ) { + iter.into_iter().for_each(|v| self.insert(v)) + } +} + +impl FromIterator for EarliestWaitTargets +where + Self: Extend, +{ + fn from_iter>(iter: T) -> Self { + let mut retval = Self::default(); + retval.extend(iter); + retval + } +} + +struct SimulationExternModuleState { + module_state: SimulationModuleState, + sim: ExternModuleSimulation, + running_generator: Option + 'static>>>, + wait_targets: EarliestWaitTargets, +} + +impl fmt::Debug for SimulationExternModuleState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + module_state, + sim, + running_generator, + wait_targets, + } = self; + f.debug_struct("SimulationExternModuleState") + .field("module_state", module_state) + .field("sim", sim) + .field( + "running_generator", + &running_generator.as_ref().map(|_| DebugAsDisplay("...")), + ) + .field("wait_targets", wait_targets) + .finish() + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +enum WhichModule { + Main, + Extern { module_index: usize }, +} + +struct ReadBitFn { + compiled_value: CompiledValue, +} + +impl MaybeNeedsSettleFn<&'_ mut interpreter::State> for ReadBitFn { + type Output = bool; + + fn call(self, state: &mut interpreter::State) -> Self::Output { + match self.compiled_value.range.len() { + TypeLen::A_SMALL_SLOT => { + state.small_slots[self.compiled_value.range.small_slots.start] != 0 + } + TypeLen::A_BIG_SLOT => !state.big_slots[self.compiled_value.range.big_slots.start] + .clone() + .is_zero(), + _ => unreachable!(), + } + } +} + +struct ReadBoolOrIntFn { + compiled_value: CompiledValue, + io: Expr, +} + +impl MaybeNeedsSettleFn<&'_ mut interpreter::State> for ReadBoolOrIntFn { + type Output = I::Value; + + fn call(self, state: &mut interpreter::State) -> Self::Output { + let Self { compiled_value, io } = self; + match compiled_value.range.len() { + TypeLen::A_SMALL_SLOT => Expr::ty(io) + .value_from_int_wrapping(state.small_slots[compiled_value.range.small_slots.start]), + TypeLen::A_BIG_SLOT => Expr::ty(io).value_from_int_wrapping( + state.big_slots[compiled_value.range.big_slots.start].clone(), + ), + _ => unreachable!(), + } + } +} + +struct ReadFn { + compiled_value: CompiledValue, + io: Expr, +} + +impl MaybeNeedsSettleFn<&'_ mut interpreter::State> for ReadFn { + type Output = SimValue; + + fn call(self, state: &mut interpreter::State) -> Self::Output { + let Self { compiled_value, io } = self; + SimulationImpl::read_no_settle_helper( + state, + io, + compiled_value, + UIntValue::new(Arc::new(BitVec::repeat(false, Expr::ty(io).bit_width()))), + ) + } +} + +struct GeneratorWaker; + +impl std::task::Wake for GeneratorWaker { + fn wake(self: Arc) { + panic!("can't await other kinds of futures in function passed to ExternalModuleSimulation"); + } +} + +#[derive(Default)] +struct ReadyToRunSet { + state_ready_to_run: bool, + extern_modules_ready_to_run: Vec, +} + +impl ReadyToRunSet { + fn clear(&mut self) { + let Self { + state_ready_to_run, + extern_modules_ready_to_run, + } = self; + *state_ready_to_run = false; + extern_modules_ready_to_run.clear(); + } +} + +struct SimulationImpl { + state: interpreter::State, + io: Expr, + main_module: SimulationModuleState, + extern_modules: Box<[SimulationExternModuleState]>, + state_ready_to_run: bool, + trace_decls: TraceModule, + traces: SimTraces]>>, + trace_memories: HashMap, TraceMem>, + trace_writers: Vec>, + instant: SimInstant, + clocks_triggered: Interned<[StatePartIndex]>, + breakpoints: Option, + generator_waker: std::task::Waker, +} + +impl fmt::Debug for SimulationImpl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.debug_fmt(None, f) + } +} + +impl SimulationImpl { + fn debug_fmt(&self, io: Option<&dyn fmt::Debug>, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + state, + io: self_io, + main_module, + extern_modules, + state_ready_to_run, + trace_decls, + traces, + trace_memories, + trace_writers, + instant, + clocks_triggered, + breakpoints: _, + generator_waker: _, + } = self; + f.debug_struct("Simulation") + .field("state", state) + .field("io", io.unwrap_or(self_io)) + .field("main_module", main_module) + .field("extern_modules", extern_modules) + .field("state_ready_to_run", state_ready_to_run) + .field("trace_decls", trace_decls) + .field("traces", traces) + .field("trace_memories", trace_memories) + .field("trace_writers", trace_writers) + .field("instant", instant) + .field("clocks_triggered", clocks_triggered) + .finish_non_exhaustive() + } + fn new(compiled: Compiled) -> Self { + let io_target = Target::from(compiled.io); + let extern_modules = Box::from_iter(compiled.extern_modules.iter().map( + |&CompiledExternModule { + module_io_targets, + module_io, + simulation, + }| { + SimulationExternModuleState { + module_state: SimulationModuleState::new( + module_io_targets + .iter() + .copied() + .zip(module_io.iter().copied()), + ), + sim: simulation, + running_generator: None, + wait_targets: EarliestWaitTargets::settle(), + } + }, + )); + Self { + state: State::new(compiled.insns), + io: compiled.io.to_expr(), + main_module: SimulationModuleState::new( + compiled + .io + .ty() + .fields() + .into_iter() + .zip(compiled.base_module.module_io) + .map(|(BundleField { name, .. }, value)| { + ( + io_target.join( + TargetPathElement::from(TargetPathBundleField { name }) + .intern_sized(), + ), + value, + ) + }), + ), + extern_modules, + state_ready_to_run: true, + trace_decls: compiled.base_module.trace_decls, + traces: SimTraces(Box::from_iter(compiled.traces.0.iter().map( + |&SimTrace { + kind, + state: _, + last_state: _, + }| SimTrace { + kind, + state: kind.make_state(), + last_state: kind.make_state(), + }, + ))), + trace_memories: HashMap::from_iter(compiled.trace_memories.iter().copied()), + trace_writers: vec![], + instant: SimInstant::START, + clocks_triggered: compiled.clocks_triggered, + breakpoints: None, + generator_waker: Arc::new(GeneratorWaker).into(), + } + } + fn write_traces( + &mut self, + mut trace_writer: DynTraceWriter, + ) -> std::io::Result { + let mut set_memory_element = |memory: StatePartIndex, + trace_mem: &TraceMem, + element_index: usize| { + let start = trace_mem.stride * element_index; + let end = start + trace_mem.stride; + trace_writer.set_memory_element( + self.trace_memories[&memory].id, + element_index, + &self.state.memories[memory].data[start..end], + ) + }; + if ONLY_IF_CHANGED { + for &(memory, element_index) in &self.state.memory_write_log { + set_memory_element(memory, &self.trace_memories[&memory], element_index)?; + } + } else { + for (&memory, trace_mem) in &self.trace_memories { + for element_index in 0..trace_mem.array_type.len() { + set_memory_element(memory, trace_mem, element_index)?; + } + } + } + for ( + id, + &SimTrace { + kind, + ref state, + ref last_state, + }, + ) in self.traces.0.iter().enumerate() + { + if ONLY_IF_CHANGED && state == last_state { + continue; + } + let id = TraceScalarId(id); + match kind { + SimTraceKind::BigUInt { .. } | SimTraceKind::SmallUInt { .. } => { + trace_writer.set_signal_uint(id, state)?; + } + SimTraceKind::BigSInt { .. } | SimTraceKind::SmallSInt { .. } => { + trace_writer.set_signal_sint(id, state)?; + } + SimTraceKind::BigBool { .. } | SimTraceKind::SmallBool { .. } => { + trace_writer.set_signal_bool(id, state[0])?; + } + SimTraceKind::BigAsyncReset { .. } | SimTraceKind::SmallAsyncReset { .. } => { + trace_writer.set_signal_async_reset(id, state[0])?; + } + SimTraceKind::BigSyncReset { .. } | SimTraceKind::SmallSyncReset { .. } => { + trace_writer.set_signal_sync_reset(id, state[0])?; + } + SimTraceKind::BigClock { .. } | SimTraceKind::SmallClock { .. } => { + trace_writer.set_signal_clock(id, state[0])?; + } + SimTraceKind::EnumDiscriminant { ty, .. } => { + let mut variant_index = [0; mem::size_of::()]; + variant_index.view_bits_mut::()[0..state.len()] + .clone_from_bitslice(state); + trace_writer.set_signal_enum_discriminant( + id, + usize::from_le_bytes(variant_index), + ty, + )?; + } + } + } + Ok(trace_writer) + } + fn init_trace_writer( + &mut self, + trace_writer: DynTraceWriter, + ) -> std::io::Result { + let mut trace_writer = self.write_traces::(trace_writer)?; + trace_writer.finish_init()?; + Ok(trace_writer) + } + fn update_trace_writer( + &mut self, + trace_writer: DynTraceWriter, + ) -> std::io::Result { + self.write_traces::(trace_writer) + } + #[inline(never)] + fn read_traces(&mut self) { + for &mut SimTrace { + kind, + ref mut state, + ref mut last_state, + } in &mut self.traces.0 + { + if !IS_INITIAL_STEP { + mem::swap(state, last_state); + } + match kind { + SimTraceKind::BigUInt { index, ty: _ } | SimTraceKind::BigSInt { index, ty: _ } => { + let bigint = &self.state.big_slots[index]; + let mut bytes = bigint.to_signed_bytes_le(); + bytes.resize( + state.len().div_ceil(8), + if bigint.is_negative() { 0xFF } else { 0 }, + ); + let bitslice = BitSlice::::from_slice(&bytes); + let bitslice = &bitslice[..state.len()]; + state.clone_from_bitslice(bitslice); + } + SimTraceKind::BigBool { index } + | SimTraceKind::BigAsyncReset { index } + | SimTraceKind::BigSyncReset { index } + | SimTraceKind::BigClock { index } => { + state.set(0, !self.state.big_slots[index].is_zero()); + } + SimTraceKind::SmallUInt { index, ty: _ } + | SimTraceKind::SmallSInt { index, ty: _ } + | SimTraceKind::EnumDiscriminant { index, ty: _ } => { + let bytes = self.state.small_slots[index].to_le_bytes(); + let bitslice = BitSlice::::from_slice(&bytes); + let bitslice = &bitslice[..state.len()]; + state.clone_from_bitslice(bitslice); + } + SimTraceKind::SmallBool { index } + | SimTraceKind::SmallAsyncReset { index } + | SimTraceKind::SmallSyncReset { index } + | SimTraceKind::SmallClock { index } => { + state.set(0, self.state.small_slots[index] != 0); + } + } + if IS_INITIAL_STEP { + last_state.copy_from_bitslice(state); + } + } + } + #[track_caller] + fn advance_time(this_ref: &Rc>, duration: SimDuration) { + let run_target = this_ref.borrow().instant + duration; + Self::run_until(this_ref, run_target); + } + /// clears `targets` + #[must_use] + fn yield_wait<'a>( + this: Rc>, + module_index: usize, + targets: &'a mut EarliestWaitTargets, + ) -> impl Future + 'a { + struct MyGenerator<'a> { + sim: Rc>, + yielded_at_all: bool, + module_index: usize, + targets: &'a mut EarliestWaitTargets, + } + impl Future for MyGenerator<'_> { + type Output = (); + + fn poll( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll { + let this = &mut *self; + let mut sim = this.sim.borrow_mut(); + let sim = &mut *sim; + assert!(cx.waker().will_wake(&sim.generator_waker), "can't use ExternModuleSimulationState's methods outside of ExternModuleSimulation"); + this.targets.convert_earlier_instants_to_settle(sim.instant); + if this.targets.is_empty() { + this.targets.settle = true; + } + if this.targets.settle { + if this.yielded_at_all { + this.targets.clear(); + return Poll::Ready(()); + } + } + sim.extern_modules[this.module_index] + .wait_targets + .extend(this.targets.iter()); + this.targets.clear(); + this.yielded_at_all = true; + Poll::Pending + } + } + MyGenerator { + sim: this, + yielded_at_all: false, + module_index, + targets, + } + } + async fn yield_advance_time_or_settle( + this: Rc>, + module_index: usize, + duration: Option, + ) { + let mut targets = duration.map_or(EarliestWaitTargets::settle(), |duration| { + EarliestWaitTargets::instant(this.borrow().instant + duration) + }); + Self::yield_wait(this, module_index, &mut targets).await; + } + fn is_extern_module_ready_to_run(&mut self, module_index: usize) -> Option { + let module = &self.extern_modules[module_index]; + let mut retval = None; + for wait_target in module.wait_targets.iter() { + retval = match (wait_target, retval) { + (WaitTarget::Settle, _) => Some(self.instant), + (WaitTarget::Instant(instant), _) if instant <= self.instant => Some(self.instant), + (WaitTarget::Instant(instant), None) => Some(instant), + (WaitTarget::Instant(instant), Some(retval)) => Some(instant.min(retval)), + (WaitTarget::Change { key, value }, retval) => { + if Self::value_changed(&mut self.state, key, SimValue::bits(value).bits()) { + Some(self.instant) + } else { + retval + } + } + }; + if retval == Some(self.instant) { + break; + } + } + retval + } + fn get_ready_to_run_set(&mut self, ready_to_run_set: &mut ReadyToRunSet) -> Option { + ready_to_run_set.clear(); + let mut retval = None; + if self.state_ready_to_run { + ready_to_run_set.state_ready_to_run = true; + retval = Some(self.instant); + } + for module_index in 0..self.extern_modules.len() { + let Some(instant) = self.is_extern_module_ready_to_run(module_index) else { + continue; + }; + if let Some(retval) = &mut retval { + match instant.cmp(retval) { + std::cmp::Ordering::Less => ready_to_run_set.clear(), + std::cmp::Ordering::Equal => {} + std::cmp::Ordering::Greater => continue, + } + } else { + retval = Some(instant); + } + ready_to_run_set + .extern_modules_ready_to_run + .push(module_index); + } + retval + } + fn set_instant_no_sim(&mut self, instant: SimInstant) { + self.instant = instant; + self.for_each_trace_writer_storing_error(|this, mut trace_writer_state| { + match &mut trace_writer_state { + TraceWriterState::Decls(_) | TraceWriterState::Init(_) => unreachable!(), + TraceWriterState::Running(trace_writer) => { + trace_writer.change_time_to(this.instant)?; + } + TraceWriterState::Errored(_) => {} + } + Ok(trace_writer_state) + }); + } + #[must_use] + #[track_caller] + fn run_state_settle_cycle(&mut self) -> bool { + self.state_ready_to_run = false; + self.state.setup_call(0); + if self.breakpoints.is_some() { + loop { + match self + .state + .run(self.breakpoints.as_mut().expect("just checked")) + { + RunResult::Break(break_action) => { + println!( + "hit breakpoint at:\n{:?}", + self.state.debug_insn_at(self.state.pc), + ); + match break_action { + BreakAction::DumpStateAndContinue => { + println!("{self:#?}"); + } + BreakAction::Continue => {} + } + } + RunResult::Return(()) => break, + } + } + } else { + let RunResult::Return(()) = self.state.run(()); + } + if self + .clocks_triggered + .iter() + .any(|i| self.state.small_slots[*i] != 0) + { + self.state_ready_to_run = true; + true + } else { + false + } + } + #[track_caller] + fn run_extern_modules_cycle( + this_ref: &Rc>, + generator_waker: &std::task::Waker, + extern_modules_ready_to_run: &[usize], + ) { + let mut this = this_ref.borrow_mut(); + for module_index in extern_modules_ready_to_run.iter().copied() { + let extern_module = &mut this.extern_modules[module_index]; + extern_module.wait_targets.clear(); + let mut generator = if !extern_module.module_state.did_initial_settle { + let sim = extern_module.sim; + drop(this); + Box::into_pin(sim.run(ExternModuleSimulationState { + sim_impl: this_ref.clone(), + module_index, + wait_for_changes_wait_targets: EarliestWaitTargets::default(), + })) + } else if let Some(generator) = extern_module.running_generator.take() { + drop(this); + generator + } else { + continue; + }; + let generator = match generator + .as_mut() + .poll(&mut std::task::Context::from_waker(generator_waker)) + { + Poll::Ready(()) => None, + Poll::Pending => Some(generator), + }; + this = this_ref.borrow_mut(); + this.extern_modules[module_index] + .module_state + .did_initial_settle = true; + if !this.extern_modules[module_index] + .module_state + .uninitialized_ios + .is_empty() + { + panic!( + "extern module didn't initialize all outputs before \ + waiting, settling, or reading any inputs: {}", + this.extern_modules[module_index].sim.source_location + ); + } + this.extern_modules[module_index].running_generator = generator; + } + } + /// clears `targets` + #[track_caller] + fn run_until(this_ref: &Rc>, run_target: SimInstant) { + let mut this = this_ref.borrow_mut(); + let mut ready_to_run_set = ReadyToRunSet::default(); + let generator_waker = this.generator_waker.clone(); + assert!( + this.main_module.uninitialized_ios.is_empty(), + "didn't initialize all inputs", + ); + let run_target = run_target.max(this.instant); + let mut settle_cycle = 0; + let mut run_extern_modules = true; + loop { + assert!(settle_cycle < 100000, "settle(): took too many steps"); + settle_cycle += 1; + let next_wait_target = match this.get_ready_to_run_set(&mut ready_to_run_set) { + Some(next_wait_target) if next_wait_target <= run_target => next_wait_target, + _ => break, + }; + if next_wait_target > this.instant { + settle_cycle = 0; + this.set_instant_no_sim(next_wait_target); + } + if run_extern_modules { + drop(this); + Self::run_extern_modules_cycle( + this_ref, + &generator_waker, + &ready_to_run_set.extern_modules_ready_to_run, + ); + this = this_ref.borrow_mut(); + } + if ready_to_run_set.state_ready_to_run { + if this.run_state_settle_cycle() { + // wait for clocks to settle before running extern modules again + run_extern_modules = false; + } else { + run_extern_modules = true; + } + } + if this.main_module.did_initial_settle { + this.read_traces::(); + } else { + this.read_traces::(); + } + this.state.memory_write_log.sort_unstable(); + this.state.memory_write_log.dedup(); + this.main_module.did_initial_settle = true; + this.for_each_trace_writer_storing_error(|this, trace_writer_state| { + Ok(match trace_writer_state { + TraceWriterState::Decls(trace_writer_decls) => TraceWriterState::Running( + this.init_trace_writer(trace_writer_decls.write_decls( + this.trace_decls, + this.traces.0.len(), + this.trace_memories.len(), + )?)?, + ), + TraceWriterState::Init(trace_writer) => { + TraceWriterState::Running(this.init_trace_writer(trace_writer)?) + } + TraceWriterState::Running(trace_writer) => { + TraceWriterState::Running(this.update_trace_writer(trace_writer)?) + } + TraceWriterState::Errored(e) => TraceWriterState::Errored(e), + }) + }); + this.state.memory_write_log.clear(); + } + if run_target > this.instant { + this.set_instant_no_sim(run_target); + } + } + #[track_caller] + fn settle(this_ref: &Rc>) { + let run_target = this_ref.borrow().instant; + Self::run_until(this_ref, run_target); + } + fn get_module(&self, which_module: WhichModule) -> &SimulationModuleState { + match which_module { + WhichModule::Main => &self.main_module, + WhichModule::Extern { module_index } => &self.extern_modules[module_index].module_state, + } + } + fn get_module_mut(&mut self, which_module: WhichModule) -> &mut SimulationModuleState { + match which_module { + WhichModule::Main => &mut self.main_module, + WhichModule::Extern { module_index } => { + &mut self.extern_modules[module_index].module_state + } + } + } + #[track_caller] + fn read_bit( + &mut self, + io: Expr, + which_module: WhichModule, + ) -> MaybeNeedsSettle { + self.get_module(which_module) + .read_helper(Expr::canonical(io), which_module) + .map(|compiled_value| ReadBitFn { compiled_value }) + .apply_no_settle(&mut self.state) + } + #[track_caller] + fn write_bit(&mut self, io: Expr, value: bool, which_module: WhichModule) { + let compiled_value = self + .get_module_mut(which_module) + .write_helper(io, which_module); + self.state_ready_to_run = true; + match compiled_value.range.len() { + TypeLen::A_SMALL_SLOT => { + self.state.small_slots[compiled_value.range.small_slots.start] = value as _; + } + TypeLen::A_BIG_SLOT => { + self.state.big_slots[compiled_value.range.big_slots.start] = value.into() + } + _ => unreachable!(), + } + } + #[track_caller] + fn read_bool_or_int( + &mut self, + io: Expr, + which_module: WhichModule, + ) -> MaybeNeedsSettle, I::Value> { + self.get_module(which_module) + .read_helper(Expr::canonical(io), which_module) + .map(|compiled_value| ReadBoolOrIntFn { compiled_value, io }) + .apply_no_settle(&mut self.state) + } + #[track_caller] + fn write_bool_or_int( + &mut self, + io: Expr, + value: I::Value, + which_module: WhichModule, + ) { + let compiled_value = self + .get_module_mut(which_module) + .write_helper(Expr::canonical(io), which_module); + self.state_ready_to_run = true; + let value: BigInt = value.into(); + match compiled_value.range.len() { + TypeLen::A_SMALL_SLOT => { + let mut small_value = value.iter_u64_digits().next().unwrap_or(0); + if value.is_negative() { + small_value = small_value.wrapping_neg(); + } + self.state.small_slots[compiled_value.range.small_slots.start] = small_value; + } + TypeLen::A_BIG_SLOT => { + self.state.big_slots[compiled_value.range.big_slots.start] = value + } + _ => unreachable!(), + } + } + #[track_caller] + fn read_write_sim_value_helper( + state: &mut interpreter::State, + compiled_value: CompiledValue, + start_bit_index: usize, + bits: &mut Bits, + read_write_big_scalar: impl Fn(bool, std::ops::Range, &mut Bits, &mut BigInt) + Copy, + read_write_small_scalar: impl Fn(bool, std::ops::Range, &mut Bits, &mut SmallUInt) + Copy, + ) { + match compiled_value.layout.body { + CompiledTypeLayoutBody::Scalar => { + let signed = match compiled_value.layout.ty { + CanonicalType::UInt(_) => false, + CanonicalType::SInt(_) => true, + CanonicalType::Bool(_) => false, + CanonicalType::Array(_) => unreachable!(), + CanonicalType::Enum(_) => false, + CanonicalType::Bundle(_) => unreachable!(), + CanonicalType::AsyncReset(_) => false, + CanonicalType::SyncReset(_) => false, + CanonicalType::Reset(_) => false, + CanonicalType::Clock(_) => false, + CanonicalType::PhantomConst(_) => unreachable!(), + }; + let bit_indexes = + start_bit_index..start_bit_index + compiled_value.layout.ty.bit_width(); + match compiled_value.range.len() { + TypeLen::A_SMALL_SLOT => read_write_small_scalar( + signed, + bit_indexes, + bits, + &mut state.small_slots[compiled_value.range.small_slots.start], + ), + TypeLen::A_BIG_SLOT => read_write_big_scalar( + signed, + bit_indexes, + bits, + &mut state.big_slots[compiled_value.range.big_slots.start], + ), + _ => unreachable!(), + } + } + CompiledTypeLayoutBody::Array { element } => { + let ty = ::from_canonical(compiled_value.layout.ty); + let element_bit_width = ty.element().bit_width(); + for element_index in 0..ty.len() { + Self::read_write_sim_value_helper( + state, + CompiledValue { + layout: *element, + range: compiled_value + .range + .index_array(element.layout.len(), element_index), + write: None, + }, + start_bit_index + element_index * element_bit_width, + bits, + read_write_big_scalar, + read_write_small_scalar, + ); + } + } + CompiledTypeLayoutBody::Bundle { fields } => { + let ty = Bundle::from_canonical(compiled_value.layout.ty); + for ( + (_field, offset), + CompiledBundleField { + offset: layout_offset, + ty: field_layout, + }, + ) in ty.fields().iter().zip(ty.field_offsets()).zip(fields) + { + Self::read_write_sim_value_helper( + state, + CompiledValue { + layout: field_layout, + range: compiled_value.range.slice(TypeIndexRange::new( + layout_offset, + field_layout.layout.len(), + )), + write: None, + }, + start_bit_index + offset, + bits, + read_write_big_scalar, + read_write_small_scalar, + ); + } + } + } + } + #[track_caller] + fn read_no_settle_helper( + state: &mut interpreter::State, + io: Expr, + compiled_value: CompiledValue, + mut bits: UIntValue, + ) -> SimValue { + SimulationImpl::read_write_sim_value_helper( + state, + compiled_value, + 0, + bits.bits_mut(), + |_signed, bit_range, bits, value| { + ::copy_bits_from_bigint_wrapping(value, &mut bits[bit_range]); + }, + |_signed, bit_range, bits, value| { + let bytes = value.to_le_bytes(); + let bitslice = BitSlice::::from_slice(&bytes); + let bitslice = &bitslice[..bit_range.len()]; + bits[bit_range].clone_from_bitslice(bitslice); + }, + ); + SimValue::from_bits(Expr::ty(io), bits) + } + /// doesn't modify `bits` + fn value_changed( + state: &mut interpreter::State, + compiled_value: CompiledValue, + mut bits: &BitSlice, + ) -> bool { + assert_eq!(bits.len(), compiled_value.layout.ty.bit_width()); + let any_change = std::cell::Cell::new(false); + SimulationImpl::read_write_sim_value_helper( + state, + compiled_value, + 0, + &mut bits, + |_signed, bit_range, bits, value| { + if !::bits_equal_bigint_wrapping(value, &bits[bit_range]) { + any_change.set(true); + } + }, + |_signed, bit_range, bits, value| { + let bytes = value.to_le_bytes(); + let bitslice = BitSlice::::from_slice(&bytes); + let bitslice = &bitslice[..bit_range.len()]; + if bits[bit_range] != *bitslice { + any_change.set(true); + } + }, + ); + any_change.get() + } + #[track_caller] + fn read( + &mut self, + io: Expr, + which_module: WhichModule, + ) -> ( + CompiledValue, + MaybeNeedsSettle>, + ) { + let compiled_value = self.get_module(which_module).read_helper(io, which_module); + let value = compiled_value + .map(|compiled_value| ReadFn { compiled_value, io }) + .apply_no_settle(&mut self.state); + let (MaybeNeedsSettle::NeedsSettle(compiled_value) + | MaybeNeedsSettle::NoSettleNeeded(compiled_value)) = compiled_value; + (compiled_value, value) + } + #[track_caller] + fn write( + &mut self, + io: Expr, + value: &SimValue, + which_module: WhichModule, + ) { + let compiled_value = self + .get_module_mut(which_module) + .write_helper(io, which_module); + self.state_ready_to_run = true; + assert_eq!(Expr::ty(io), SimValue::ty(value)); + Self::read_write_sim_value_helper( + &mut self.state, + compiled_value, + 0, + &mut value.bits().bits(), + |signed, bit_range, bits, value| { + if signed { + *value = SInt::bits_to_bigint(&bits[bit_range]); + } else { + *value = UInt::bits_to_bigint(&bits[bit_range]); + } + }, + |signed, bit_range, bits, value| { + let mut small_value = [0; mem::size_of::()]; + if signed && bits[bit_range.clone()].last().as_deref().copied() == Some(true) { + small_value.fill(u8::MAX); + } + small_value.view_bits_mut::()[0..bit_range.len()] + .clone_from_bitslice(&bits[bit_range]); + *value = SmallUInt::from_le_bytes(small_value); + }, + ); + } + #[track_caller] + fn settle_if_needed(this_ref: &Rc>, v: MaybeNeedsSettle) -> O + where + for<'a> F: MaybeNeedsSettleFn<&'a mut interpreter::State, Output = O>, + { + match v { + MaybeNeedsSettle::NeedsSettle(v) => { + Self::settle(this_ref); + v.call(&mut this_ref.borrow_mut().state) + } + MaybeNeedsSettle::NoSettleNeeded(v) => v, + } + } + async fn yield_settle_if_needed( + this_ref: &Rc>, + module_index: usize, + v: MaybeNeedsSettle, + ) -> O + where + for<'a> F: MaybeNeedsSettleFn<&'a mut interpreter::State, Output = O>, + { + match v { + MaybeNeedsSettle::NeedsSettle(v) => { + Self::yield_advance_time_or_settle(this_ref.clone(), module_index, None).await; + v.call(&mut this_ref.borrow_mut().state) + } + MaybeNeedsSettle::NoSettleNeeded(v) => v, + } + } + fn close_all_trace_writers(&mut self) -> std::io::Result<()> { + let trace_writers = mem::take(&mut self.trace_writers); + let mut retval = Ok(()); + let close_trace_writer = + |trace_writer: TraceWriterState| match trace_writer { + TraceWriterState::Decls(v) => v + .write_decls( + self.trace_decls, + self.traces.0.len(), + self.trace_memories.len(), + )? + .close(), + TraceWriterState::Init(v) => v.close(), + TraceWriterState::Running(v) => v.close(), + TraceWriterState::Errored(Some(e)) => Err(e), + TraceWriterState::Errored(None) => Ok(()), + }; + for trace_writer in trace_writers { + retval = retval.and(close_trace_writer(trace_writer)); + } + retval + } + fn for_each_trace_writer_storing_error( + &mut self, + mut f: impl FnMut( + &mut Self, + TraceWriterState, + ) -> std::io::Result>, + ) { + let mut trace_writers = mem::take(&mut self.trace_writers); + for trace_writer in &mut trace_writers { + *trace_writer = match f( + self, + mem::replace(trace_writer, TraceWriterState::Errored(None)), + ) { + Ok(v) => v, + Err(e) => TraceWriterState::Errored(Some(e)), + }; + } + self.trace_writers = trace_writers; + } + fn for_each_trace_writer_getting_error( + &mut self, + mut f: impl FnMut( + &mut Self, + TraceWriterState, + ) -> std::io::Result>, + ) -> std::io::Result<()> { + let mut retval = Ok(()); + let mut trace_writers = mem::take(&mut self.trace_writers); + for trace_writer in &mut trace_writers { + *trace_writer = match f( + self, + mem::replace(trace_writer, TraceWriterState::Errored(None)), + ) { + Ok(v) => v, + Err(e) => { + if retval.is_ok() { + retval = Err(e); + TraceWriterState::Errored(None) + } else { + TraceWriterState::Errored(Some(e)) + } + } + }; + } + self.trace_writers = trace_writers; + retval + } + fn close(this: Rc>) -> std::io::Result<()> { + if this.borrow().main_module.did_initial_settle { + Self::settle(&this); + } + this.borrow_mut().close_all_trace_writers() + } + fn flush_traces(this_ref: &Rc>) -> std::io::Result<()> { + if this_ref.borrow().main_module.did_initial_settle { + Self::settle(this_ref); + } + this_ref.borrow_mut().for_each_trace_writer_getting_error( + |this, trace_writer: TraceWriterState| match trace_writer { + TraceWriterState::Decls(v) => { + let mut v = v.write_decls( + this.trace_decls, + this.traces.0.len(), + this.trace_memories.len(), + )?; + v.flush()?; + Ok(TraceWriterState::Init(v)) + } + TraceWriterState::Init(mut v) => { + v.flush()?; + Ok(TraceWriterState::Init(v)) + } + TraceWriterState::Running(mut v) => { + v.flush()?; + Ok(TraceWriterState::Running(v)) + } + TraceWriterState::Errored(Some(e)) => Err(e), + TraceWriterState::Errored(None) => Ok(TraceWriterState::Errored(None)), + }, + ) + } +} + +impl Drop for SimulationImpl { + fn drop(&mut self) { + self.close_all_trace_writers() + .expect("error closing trace writers"); + } +} + +pub struct Simulation { + sim_impl: Rc>, + io: Expr, +} + +struct SortedSetDebug<'a, T, V>(&'a HashMap); + +impl fmt::Debug for SortedSetDebug<'_, T, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut entries = Vec::from_iter(self.0.iter().map(|(v, _)| { + if f.alternate() { + format!("{v:#?}") + } else { + format!("{v:?}") + } + })); + entries.sort(); + f.debug_set() + .entries(entries.iter().map(DebugAsDisplay)) + .finish() + } +} + +struct SortedMapDebug<'a, K, V>(&'a HashMap); + +impl fmt::Debug for SortedMapDebug<'_, K, V> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut entries = Vec::from_iter(self.0.iter().map(|(k, v)| { + if f.alternate() { + (format!("{k:#?}"), format!("{v:#?}")) + } else { + (format!("{k:?}"), format!("{v:?}")) + } + })); + entries.sort(); + f.debug_map() + .entries( + entries + .iter() + .map(|(k, v)| (DebugAsDisplay(k), DebugAsDisplay(v))), + ) + .finish() + } +} + +impl fmt::Debug for Simulation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { sim_impl, io } = self; + sim_impl.borrow().debug_fmt(Some(io), f) + } +} + +macro_rules! impl_simulation_methods { + ( + async_await = ($($async:tt, $await:tt)?), + track_caller = ($(#[$track_caller:tt])?), + which_module = |$self:ident| $which_module:expr, + ) => { + $(#[$track_caller])? + pub $($async)? fn read_bool_or_int(&mut $self, io: Expr) -> I::Value { + let retval = $self + .sim_impl + .borrow_mut() + .read_bool_or_int(io, $which_module); + $self.settle_if_needed(retval)$(.$await)? + } + $(#[$track_caller])? + pub $($async)? fn write_bool_or_int( + &mut $self, + io: Expr, + value: impl ToExpr, + ) { + let value = value.to_expr(); + assert_eq!(Expr::ty(io), Expr::ty(value), "type mismatch"); + let value = value + .to_literal_bits() + .expect("the value that is being written to an input must be a literal"); + $self.sim_impl.borrow_mut().write_bool_or_int( + io, + I::bits_to_value(Cow::Borrowed(&value)), + $which_module, + ); + } + $(#[$track_caller])? + pub $($async)? fn write_clock(&mut $self, io: Expr, value: bool) { + $self.sim_impl + .borrow_mut() + .write_bit(Expr::canonical(io), value, $which_module); + } + $(#[$track_caller])? + pub $($async)? fn read_clock(&mut $self, io: Expr) -> bool { + let retval = $self + .sim_impl + .borrow_mut() + .read_bit(Expr::canonical(io), $which_module); + $self.settle_if_needed(retval)$(.$await)? + } + $(#[$track_caller])? + pub $($async)? fn write_bool(&mut $self, io: Expr, value: bool) { + $self.sim_impl + .borrow_mut() + .write_bit(Expr::canonical(io), value, $which_module); + } + $(#[$track_caller])? + pub $($async)? fn read_bool(&mut $self, io: Expr) -> bool { + let retval = $self + .sim_impl + .borrow_mut() + .read_bit(Expr::canonical(io), $which_module); + $self.settle_if_needed(retval)$(.$await)? + } + $(#[$track_caller])? + pub $($async)? fn write_reset(&mut $self, io: Expr, value: bool) { + $self.sim_impl + .borrow_mut() + .write_bit(Expr::canonical(io), value, $which_module); + } + $(#[$track_caller])? + pub $($async)? fn read_reset(&mut $self, io: Expr) -> bool { + let retval = $self + .sim_impl + .borrow_mut() + .read_bit(Expr::canonical(io), $which_module); + $self.settle_if_needed(retval)$(.$await)? + } + $(#[$track_caller])? + pub $($async)? fn read(&mut $self, io: Expr) -> SimValue { + let retval = $self + .sim_impl + .borrow_mut() + .read(Expr::canonical(io), $which_module).1; + SimValue::from_canonical($self.settle_if_needed(retval)$(.$await)?) + } + $(#[$track_caller])? + pub $($async)? fn write>(&mut $self, io: Expr, value: V) { + $self.sim_impl.borrow_mut().write( + Expr::canonical(io), + &SimValue::into_canonical(value.into_sim_value_with_type(Expr::ty(io))), + $which_module, + ); + } + }; +} + +impl Simulation { + pub fn new(module: Interned>) -> Self { + Self::from_compiled(Compiled::new(module)) + } + pub fn add_trace_writer(&mut self, writer: W) { + self.sim_impl + .borrow_mut() + .trace_writers + .push(TraceWriterState::Decls(DynTraceWriterDecls::new(writer))); + } + pub fn flush_traces(&mut self) -> std::io::Result<()> { + SimulationImpl::flush_traces(&self.sim_impl) + } + pub fn close(self) -> std::io::Result<()> { + SimulationImpl::close(self.sim_impl) + } + pub fn canonical(self) -> Simulation { + let Self { sim_impl, io } = self; + Simulation { + sim_impl, + io: Expr::as_bundle(io), + } + } + pub fn from_canonical(canonical: Simulation) -> Self { + let Simulation { sim_impl, io } = canonical; + Self { + sim_impl, + io: Expr::from_bundle(io), + } + } + pub fn io(&self) -> Expr { + self.io.to_expr() + } + pub fn from_compiled(compiled: Compiled) -> Self { + let sim_impl = SimulationImpl::new(compiled.canonical()); + Self { + io: Expr::from_bundle(sim_impl.io), + sim_impl: Rc::new(RefCell::new(sim_impl)), + } + } + #[track_caller] + pub fn settle(&mut self) { + SimulationImpl::settle(&self.sim_impl); + } + #[track_caller] + pub fn advance_time(&mut self, duration: SimDuration) { + SimulationImpl::advance_time(&self.sim_impl, duration); + } + #[track_caller] + fn settle_if_needed(&mut self, v: MaybeNeedsSettle) -> O + where + for<'a> F: MaybeNeedsSettleFn<&'a mut interpreter::State, Output = O>, + { + SimulationImpl::settle_if_needed(&self.sim_impl, v) + } + impl_simulation_methods!( + async_await = (), + track_caller = (#[track_caller]), + which_module = |self| WhichModule::Main, + ); + #[doc(hidden)] + /// This is explicitly unstable and may be changed/removed at any time + pub fn set_breakpoints_unstable(&mut self, pcs: HashSet, trace: bool) { + self.sim_impl.borrow_mut().breakpoints = Some(BreakpointsSet { + last_was_break: false, + set: pcs, + trace, + }); + } +} + +pub struct ExternModuleSimulationState { + sim_impl: Rc>, + module_index: usize, + wait_for_changes_wait_targets: EarliestWaitTargets, +} + +impl fmt::Debug for ExternModuleSimulationState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + sim_impl: _, + module_index, + wait_for_changes_wait_targets: _, + } = self; + f.debug_struct("ExternModuleSimulationState") + .field("sim_impl", &DebugAsDisplay("...")) + .field("module_index", module_index) + .finish_non_exhaustive() + } +} + +impl ExternModuleSimulationState { + pub async fn settle(&mut self) { + SimulationImpl::yield_advance_time_or_settle(self.sim_impl.clone(), self.module_index, None) + .await + } + pub async fn advance_time(&mut self, duration: SimDuration) { + SimulationImpl::yield_advance_time_or_settle( + self.sim_impl.clone(), + self.module_index, + Some(duration), + ) + .await + } + pub async fn wait_for_changes>( + &mut self, + iter: I, + timeout: Option, + ) { + self.wait_for_changes_wait_targets.clear(); + let which_module = WhichModule::Extern { + module_index: self.module_index, + }; + for io in iter { + let io = Expr::canonical(io.to_expr()); + let (key, value) = self.sim_impl.borrow_mut().read(io, which_module); + let value = self.settle_if_needed(value).await; + self.wait_for_changes_wait_targets + .insert(WaitTarget::Change { key, value }); + } + if let Some(timeout) = timeout { + self.wait_for_changes_wait_targets.instant = + Some(self.sim_impl.borrow().instant + timeout); + } + SimulationImpl::yield_wait( + self.sim_impl.clone(), + self.module_index, + &mut self.wait_for_changes_wait_targets, + ) + .await; + } + pub async fn wait_for_clock_edge(&mut self, clk: impl ToExpr) { + let clk = clk.to_expr(); + while self.read_clock(clk).await { + self.wait_for_changes([clk], None).await; + } + while !self.read_clock(clk).await { + self.wait_for_changes([clk], None).await; + } + } + async fn settle_if_needed(&mut self, v: MaybeNeedsSettle) -> O + where + for<'a> F: MaybeNeedsSettleFn<&'a mut interpreter::State, Output = O>, + { + SimulationImpl::yield_settle_if_needed(&self.sim_impl, self.module_index, v).await + } + impl_simulation_methods!( + async_await = (async, await), + track_caller = (), + which_module = |self| WhichModule::Extern { module_index: self.module_index }, + ); +} + +pub trait ExternModuleSimGenerator: Clone + Eq + Hash + Any + Send + Sync + fmt::Debug { + fn run<'a>(&'a self, sim: ExternModuleSimulationState) -> impl IntoFuture + 'a; +} + +pub struct SimGeneratorFn { + pub args: Args, + pub f: fn(Args, ExternModuleSimulationState) -> Fut, +} + +impl fmt::Debug for SimGeneratorFn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { args, f: _ } = self; + f.debug_struct("SimGeneratorFn") + .field("args", args) + .field("f", &DebugAsDisplay("...")) + .finish() + } +} + +impl Hash for SimGeneratorFn { + fn hash(&self, state: &mut H) { + let Self { args, f } = self; + args.hash(state); + f.hash(state); + } +} + +impl Eq for SimGeneratorFn {} + +impl PartialEq for SimGeneratorFn { + fn eq(&self, other: &Self) -> bool { + let Self { args, f } = self; + *args == other.args && *f == other.f + } +} + +impl Clone for SimGeneratorFn { + fn clone(&self) -> Self { + Self { + args: self.args.clone(), + f: self.f, + } + } +} + +impl Copy for SimGeneratorFn {} + +impl< + T: fmt::Debug + Clone + Eq + Hash + Send + Sync + 'static, + Fut: IntoFuture + 'static, + > ExternModuleSimGenerator for SimGeneratorFn +{ + fn run<'a>(&'a self, sim: ExternModuleSimulationState) -> impl IntoFuture + 'a { + (self.f)(self.args.clone(), sim) + } +} + +trait DynExternModuleSimGenerator: Any + Send + Sync + SupportsPtrEqWithTypeId + fmt::Debug { + fn dyn_run<'a>(&'a self, sim: ExternModuleSimulationState) + -> Box + 'a>; +} + +impl DynExternModuleSimGenerator for T { + fn dyn_run<'a>( + &'a self, + sim: ExternModuleSimulationState, + ) -> Box + 'a> { + Box::new(self.run(sim).into_future()) + } +} + +impl InternedCompare for dyn DynExternModuleSimGenerator { + type InternedCompareKey = PtrEqWithTypeId; + + fn interned_compare_key_ref(this: &Self) -> Self::InternedCompareKey { + this.get_ptr_eq_with_type_id() + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct ExternModuleSimulation { + generator: Interned, + source_location: SourceLocation, +} + +impl ExternModuleSimulation { + pub fn new_with_loc( + source_location: SourceLocation, + generator: G, + ) -> Self { + Self { + generator: Interned::cast_unchecked( + generator.intern(), + |v| -> &dyn DynExternModuleSimGenerator { v }, + ), + source_location, + } + } + #[track_caller] + pub fn new(generator: G) -> Self { + Self::new_with_loc(SourceLocation::caller(), generator) + } + fn run(&self, sim: ExternModuleSimulationState) -> Box + 'static> { + Interned::into_inner(self.generator).dyn_run(sim) + } +} diff --git a/crates/fayalite/src/sim/interpreter.rs b/crates/fayalite/src/sim/interpreter.rs new file mode 100644 index 0000000..de582f0 --- /dev/null +++ b/crates/fayalite/src/sim/interpreter.rs @@ -0,0 +1,3184 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + array::Array, + int::{BoolOrIntType, SInt, UInt}, + intern::{Intern, Interned, Memoize}, + source_location::SourceLocation, + ty::CanonicalType, + util::{get_many_mut, HashMap, HashSet}, +}; +use bitvec::{boxed::BitBox, slice::BitSlice}; +use num_bigint::BigInt; +use num_traits::{One, Signed, ToPrimitive, Zero}; +use std::{ + any::TypeId, + borrow::BorrowMut, + convert::Infallible, + fmt::{self, Write}, + hash::{Hash, Hasher}, + marker::PhantomData, + ops::{ControlFlow, Deref, DerefMut, Index, IndexMut}, +}; +use vec_map::VecMap; + +pub(crate) type SmallUInt = u64; +pub(crate) type SmallSInt = i64; +pub(crate) const MIN_BITS_FOR_NEEDING_BIG: usize = SmallUInt::BITS as usize + 1; + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub(crate) enum InsnFieldKind { + Input, + Output, + Memory, + Immediate, + BranchTarget, +} + +pub(crate) trait InsnFieldTypeTransform: Eq + Hash + fmt::Debug + Send + Sync { + type Type: Eq + Hash + fmt::Debug + Send + Sync; + fn empty_type() -> Self::Type<[(); 0]>; +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] +pub(crate) struct InsnFieldTypeTransformUnit; + +impl InsnFieldTypeTransform for InsnFieldTypeTransformUnit { + type Type = (); + fn empty_type() -> Self::Type<[(); 0]> { + () + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub(crate) struct InsnFieldTypeTransformRef<'a>(PhantomData<&'a ()>); + +impl<'a> InsnFieldTypeTransform for InsnFieldTypeTransformRef<'a> { + type Type = &'a FieldType; + fn empty_type() -> Self::Type<[(); 0]> { + &[] + } +} + +#[derive(PartialEq, Eq, Hash, Debug)] +pub(crate) struct InsnFieldTypeTransformRefMut<'a>(PhantomData<&'a mut ()>); + +impl<'a> InsnFieldTypeTransform for InsnFieldTypeTransformRefMut<'a> { + type Type = &'a mut FieldType; + fn empty_type() -> Self::Type<[(); 0]> { + &mut [] + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub(crate) struct InsnFieldTypeTransformValue; + +impl InsnFieldTypeTransform for InsnFieldTypeTransformValue { + type Type = FieldType; + fn empty_type() -> Self::Type<[(); 0]> { + [] + } +} + +pub trait InsnFieldTrait: Send + Sync + 'static + Copy + Eq + Hash + fmt::Debug { + const UNIT: InsnFieldType; + fn variant( + v: Transform::Type, + ) -> InsnFieldType; +} + +macro_rules! insn_field_enum { + ( + $enum_vis:vis enum $InsnFieldType:ident<$Transform:ident: $InsnFieldTypeTransform:ident> { + $($Variant:ident($Transform2:ident::$Type:ident<$variant_ty:ty>),)* + } + ) => { + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + $enum_vis enum $InsnFieldType<$Transform: $InsnFieldTypeTransform> { + $($Variant($Transform2::$Type<$variant_ty>),)* + } + + $(impl InsnFieldTrait for $variant_ty { + const UNIT: $InsnFieldType = $InsnFieldType::$Variant(()); + fn variant<$Transform2: $InsnFieldTypeTransform>( + v: $Transform2::$Type, + ) -> $InsnFieldType<$Transform2> { + $InsnFieldType::$Variant(v) + } + })* + }; +} + +insn_field_enum! { + pub(crate) enum InsnFieldType { + Memory(Transform::Type>), + SmallSlot(Transform::Type>), + BigSlot(Transform::Type>), + SmallSlotArrayIndexed(Transform::Type>), + BigSlotArrayIndexed(Transform::Type>), + SmallUInt(Transform::Type), + SmallSInt(Transform::Type), + InternedBigInt(Transform::Type>), + U8(Transform::Type), + USize(Transform::Type), + Empty(Transform::Type<[(); 0]>), + } +} + +impl InsnFieldType { + pub(crate) fn empty() -> Self { + Self::Empty(Transform::empty_type()) + } +} + +#[derive(PartialEq, Eq, Hash, Debug)] +pub(crate) struct InsnField { + pub(crate) ty: InsnFieldType, + pub(crate) kind: InsnFieldKind, +} + +impl Clone for InsnField +where + InsnFieldType: Clone, +{ + fn clone(&self) -> Self { + Self { + ty: self.ty.clone(), + kind: self.kind.clone(), + } + } +} + +impl Copy for InsnField where + InsnFieldType: Copy +{ +} + +fn make_array_into_iter( + input: [T; I], + mut default: impl FnMut() -> T, +) -> std::array::IntoIter { + const { + assert!(I <= N); + }; + let mut input = input.into_iter(); + let array = std::array::from_fn(|_| input.next().unwrap_or_else(&mut default)); + let mut retval = array.into_iter(); + // remove unneeded trailing elements + if I < N { + retval.nth_back(N - I - 1); + } + retval +} + +impl fmt::Debug for Insn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.debug_fmt::(f, None, None, None) + } +} + +struct PrefixLinesWrapper<'a, W> { + writer: W, + at_beginning_of_line: bool, + blank_line_prefix: &'a str, + line_prefix: &'a str, +} + +impl fmt::Write for PrefixLinesWrapper<'_, T> { + fn write_str(&mut self, input: &str) -> fmt::Result { + for part in input.split_inclusive('\n') { + if part.is_empty() { + continue; + } + if self.at_beginning_of_line { + let prefix = match part { + "\n" => self.blank_line_prefix, + _ => self.line_prefix, + }; + if !prefix.is_empty() { + self.writer.write_str(prefix)?; + } + self.at_beginning_of_line = false; + } + self.writer.write_str(part)?; + self.at_beginning_of_line = part.ends_with('\n'); + } + Ok(()) + } +} + +impl Insn { + fn debug_fmt( + &self, + f: &mut fmt::Formatter<'_>, + labels: Option<&Labels>, + state_layout: Option<&StateLayout>, + state: Option<&State>, + ) -> fmt::Result { + let (insn_name, fields) = self.fields_with_names(); + write!(f, "{insn_name}")?; + if fields.len() == 0 { + return Ok(()); + } + let mut f = PrefixLinesWrapper { + writer: f, + at_beginning_of_line: false, + blank_line_prefix: "", + line_prefix: " ", + }; + writeln!(f, " {{")?; + for (field_name, field) in fields { + write!(f, "{field_name}: ")?; + match field.kind { + InsnFieldKind::BranchTarget => match field.ty { + InsnFieldType::USize(&label_index) => { + if let Some(labels) = labels { + write!(f, "L{label_index}")?; + if let Some(label) = labels.labels.get(label_index) { + if let Some(address) = label.address { + write!(f, " (at {address})")?; + } else { + write!(f, " (not yet defined)")?; + } + } else { + write!(f, " (invalid)")?; + } + writeln!(f, ",")?; + continue; + } + } + InsnFieldType::Memory(_) + | InsnFieldType::SmallSlot(_) + | InsnFieldType::BigSlot(_) + | InsnFieldType::SmallSlotArrayIndexed(_) + | InsnFieldType::BigSlotArrayIndexed(_) + | InsnFieldType::SmallUInt(_) + | InsnFieldType::SmallSInt(_) + | InsnFieldType::InternedBigInt(_) + | InsnFieldType::U8(_) + | InsnFieldType::Empty(_) => {} + }, + InsnFieldKind::Input + | InsnFieldKind::Memory + | InsnFieldKind::Output + | InsnFieldKind::Immediate => {} + } + macro_rules! debug_fmt_state_part { + ($v:expr) => { + $v.debug_fmt(&mut f, ",", " // ", " // ", "", state_layout, state) + }; + } + match field.ty { + InsnFieldType::Memory(v) => { + debug_fmt_state_part!(v)?; + } + InsnFieldType::SmallSlot(v) => { + debug_fmt_state_part!(v)?; + } + InsnFieldType::BigSlot(v) => { + debug_fmt_state_part!(v)?; + } + InsnFieldType::SmallSlotArrayIndexed(v) => { + debug_fmt_state_part!(v)?; + } + InsnFieldType::BigSlotArrayIndexed(v) => { + debug_fmt_state_part!(v)?; + } + InsnFieldType::SmallUInt(v) => write!(f, "{v:#x}")?, + InsnFieldType::SmallSInt(v) => write!(f, "{v:#x}")?, + InsnFieldType::InternedBigInt(v) => write!(f, "{v:#x}")?, + InsnFieldType::U8(v) => write!(f, "{v:#x}")?, + InsnFieldType::USize(v) => write!(f, "{v}")?, + InsnFieldType::Empty(v) => write!(f, "{v:?}")?, + } + writeln!(f, ",")?; + } + write!(f.writer, "}}") + } +} + +pub(crate) trait Breakpoints { + type Break; + fn check_for_breakpoint(&mut self, pc: usize) -> ControlFlow; +} + +impl Breakpoints for &'_ mut T { + type Break = T::Break; + fn check_for_breakpoint(&mut self, pc: usize) -> ControlFlow { + T::check_for_breakpoint(self, pc) + } +} + +impl Breakpoints for Box { + type Break = T::Break; + fn check_for_breakpoint(&mut self, pc: usize) -> ControlFlow { + T::check_for_breakpoint(self, pc) + } +} + +impl Breakpoints for () { + type Break = Infallible; + fn check_for_breakpoint(&mut self, _pc: usize) -> ControlFlow { + ControlFlow::Continue(()) + } +} + +pub(crate) struct BreakpointsSet { + pub(crate) last_was_break: bool, + pub(crate) set: HashSet, + pub(crate) trace: bool, +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub(crate) enum BreakAction { + DumpStateAndContinue, + Continue, +} + +impl Breakpoints for BreakpointsSet { + type Break = BreakAction; + fn check_for_breakpoint(&mut self, pc: usize) -> ControlFlow { + let retval = if self.last_was_break { + ControlFlow::Continue(()) + } else if self.set.contains(&pc) { + ControlFlow::Break(BreakAction::DumpStateAndContinue) + } else if self.trace { + ControlFlow::Break(BreakAction::Continue) + } else { + ControlFlow::Continue(()) + }; + self.last_was_break = retval.is_break(); + retval + } +} + +pub(crate) enum RunResult { + Break(Break), + Return(Return), +} + +macro_rules! impl_insns { + ( + #[insn = $Insn:ident, next_macro = $next_macro:ident, branch_macro = $branch_macro:ident] + $vis:vis fn $State:ident::$run:ident(&mut $self:ident) -> $run_ret_ty:ty { + #[state] + let mut $state:ident = $state_init:expr; + setup! { + $($setup:tt)* + } + main_loop!(); + cleanup! { + $($cleanup:tt)* + } + } + $( + $(#[$insn_meta:meta])* + $insn_name:ident $({ + $( + #[kind = $field_kind:ident] + $(#[$field_meta:meta])* + $field_name:ident: $field_ty:ty, + )* + })? => $block:block + )* + ) => { + #[derive(Copy, Clone, Eq, PartialEq, Hash)] + $vis enum $Insn { + $( + $(#[$insn_meta])* + $insn_name $({ + $( + $(#[$field_meta])* + $field_name: $field_ty, + )* + })?, + )* + } + + impl $Insn { + $vis const MAX_FIELDS: usize = { + let mut retval = 0; + $($( + let fields = [$(stringify!($field_name),)*].len(); + if retval < fields { + retval = fields; + } + )?)* + retval + }; + } + + impl $Insn { + $vis const fn fields_unit(&self) -> &'static [InsnField] { + match self { + $( + $Insn::$insn_name {..} => &[ + $($(InsnField { + ty: <$field_ty as InsnFieldTrait>::UNIT, + kind: InsnFieldKind::$field_kind, + },)*)? + ], + )* + } + } + $vis fn fields<'a>(&'a self) -> std::array::IntoIter>, { $Insn::MAX_FIELDS }> { + match self { + $( + $Insn::$insn_name $({ + $($field_name,)* + })? => make_array_into_iter([ + $($(InsnField { + ty: <$field_ty as InsnFieldTrait>::variant($field_name), + kind: InsnFieldKind::$field_kind, + },)*)? + ], + || InsnField { + ty: InsnFieldType::empty(), + kind: InsnFieldKind::Immediate, + }, + ), + )* + } + } + $vis fn fields_with_names<'a>(&'a self) -> (&'static str, std::array::IntoIter<(&'static str, InsnField>), { $Insn::MAX_FIELDS }>) { + match self { + $( + $Insn::$insn_name $({ + $($field_name,)* + })? => ( + stringify!($insn_name), + make_array_into_iter([ + $($((stringify!($field_name), InsnField { + ty: <$field_ty as InsnFieldTrait>::variant($field_name), + kind: InsnFieldKind::$field_kind, + }),)*)? + ], + || ("", InsnField { + ty: InsnFieldType::empty(), + kind: InsnFieldKind::Immediate, + }), + ), + ), + )* + } + } + $vis fn fields_mut<'a>(&'a mut self) -> std::array::IntoIter>, { $Insn::MAX_FIELDS }> { + match self { + $( + $Insn::$insn_name $({ + $($field_name,)* + })? => make_array_into_iter([ + $($(InsnField { + ty: <$field_ty as InsnFieldTrait>::variant($field_name), + kind: InsnFieldKind::$field_kind, + },)*)? + ], + || InsnField { + ty: InsnFieldType::empty(), + kind: InsnFieldKind::Immediate, + }, + ), + )* + } + } + $vis fn into_fields(self) -> std::array::IntoIter, { $Insn::MAX_FIELDS }> { + match self { + $( + $Insn::$insn_name $({ + $($field_name,)* + })? => make_array_into_iter([ + $($(InsnField { + ty: <$field_ty as InsnFieldTrait>::variant($field_name), + kind: InsnFieldKind::$field_kind, + },)*)? + ], + || InsnField { + ty: InsnFieldType::empty(), + kind: InsnFieldKind::Immediate, + }, + ), + )* + } + } + } + + impl $State { + $vis fn $run(&mut $self, mut breakpoints: B) -> RunResult { + let mut $state = $state_init; + $($setup)* + let mut insn = $state.insns[$state.pc]; + let retval = 'main_loop: loop { + if let ControlFlow::Break(b) = breakpoints.check_for_breakpoint($state.pc) { + break RunResult::Break(b); + } + macro_rules! $next_macro { + () => { + $state.pc += 1; + insn = $state.insns[$state.pc]; + continue 'main_loop; + }; + } + macro_rules! $branch_macro { + ($next_pc:expr) => { + $state.pc = $next_pc; + insn = $state.insns[$state.pc]; + continue 'main_loop; + }; + } + let _: Infallible = match insn { + $( + $Insn::$insn_name $({ + $( + $field_name, + )* + })? => { + $block + } + )* + }; + }; + $($cleanup)* + retval + } + } + }; +} + +pub(crate) trait InsnsBuildingKind: Copy + Eq + fmt::Debug + Hash + Default { + type Vec: fmt::Debug + + Clone + + Eq + + Hash + + Send + + Sync + + 'static + + Default + + Deref + + FromIterator; + type Labels: Default; + fn labels(labels: &Self::Labels) -> Option<&Labels>; +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] +pub(crate) struct InsnsBuildingDone; + +impl InsnsBuildingKind for InsnsBuildingDone { + type Vec = Interned<[T]>; + type Labels = (); + fn labels(_labels: &Self::Labels) -> Option<&Labels> { + None + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)] +pub(crate) struct InsnsBuilding; + +impl InsnsBuildingKind for InsnsBuilding { + type Vec = Vec; + type Labels = Labels; + fn labels(labels: &Self::Labels) -> Option<&Labels> { + Some(labels) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub(crate) struct Label(pub(crate) usize); + +impl fmt::Debug for Label { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "L{}:", self.0) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub(crate) enum InsnOrLabel { + Insn(Insn), + Label(Label), +} + +impl From for InsnOrLabel { + fn from(value: Insn) -> Self { + Self::Insn(value) + } +} + +impl From