Compare commits

..

83 commits

Author SHA1 Message Date
Jacob Lifshay 3d5d8c54b6
add repository to cache key
All checks were successful
/ deps (push) Successful in 10m39s
/ test (push) Successful in 5m14s
2024-10-30 20:55:02 -07:00
Jacob Lifshay ee15fd2b94
support #[hdl] type aliases
All checks were successful
/ deps (push) Successful in 11m28s
/ test (push) Successful in 4m40s
2024-10-30 20:47:10 -07:00
Jacob Lifshay 20cf0abbcc
fix using #[hdl] types like S<{ 1 + 2 }> 2024-10-30 20:46:11 -07:00
Jacob Lifshay 5bd0de48b7
change to version 0.2.1 2024-10-30 19:36:05 -07:00
Jacob Lifshay 0c9c48a066
split out deps into separate workflow with better caching using deps.yml from cpu.git
All checks were successful
/ deps (push) Successful in 21s
/ test (push) Successful in 19m32s
2024-10-17 21:05:18 -07:00
Jacob Lifshay cb17913004
limit sby to one thread each since it seems not to respect job count in parallel mode
All checks were successful
/ test (push) Successful in 46m32s
2024-10-15 21:32:38 -07:00
Jacob Lifshay 42effd1132
switch to using a make job server for managing test parallelism
Some checks failed
/ test (push) Failing after 39m16s
2024-10-15 20:32:33 -07:00
Jacob Lifshay 3d0f95cfe5
formal: add workaround for wires disappearing because yosys optimizes them out
All checks were successful
/ test (push) Successful in 39m13s
2024-10-15 01:48:48 -07:00
Jacob Lifshay 3939ce2360
add Bundle and Enum to prelude
All checks were successful
/ test (push) Successful in 39m33s
2024-10-14 17:47:58 -07:00
Jacob Lifshay d0229fbcfb
get #[hdl] struct S<A: KnownSize, B: KnownSize> to work
All checks were successful
/ test (push) Successful in 39m2s
2024-10-11 17:30:49 -07:00
Jacob Lifshay 4909724995
add more thorough checks that bounds are properly handled on #[hdl] structs
All checks were successful
/ test (push) Successful in 37m37s
2024-10-10 23:34:46 -07:00
Jacob Lifshay d0694cbd52
add disabled test for #[hdl] struct S4<W: KnownSize, W2: KnownSize> which type errors
Some checks failed
/ test (push) Has been cancelled
2024-10-10 22:58:15 -07:00
Jacob Lifshay 1a2149b040
silence warnings for field names that start with _
All checks were successful
/ test (push) Successful in 37m30s
2024-10-10 20:53:29 -07:00
Jacob Lifshay 59cef3f398
add PhantomData as a hdl bundle
Some checks failed
/ test (push) Has been cancelled
2024-10-10 20:48:09 -07:00
Jacob Lifshay bf907c3872
cache results of formal proofs
All checks were successful
/ test (push) Successful in 39m54s
2024-10-07 23:31:24 -07:00
Jacob Lifshay 99180eb3b4
fix clippy lints in generated code
All checks were successful
/ test (push) Successful in 37m34s
2024-10-07 22:06:59 -07:00
Jacob Lifshay 017c14a2f1
don't use #[allow(..., reason = "...")] since that's not stable yet on rust 1.80.1 2024-10-07 22:06:59 -07:00
Jacob Lifshay ed1aea41f3
clean up some clippy warnings
Some checks failed
/ test (push) Failing after 3m41s
2024-10-07 21:49:18 -07:00
Jacob Lifshay f12322aa2a
remove interning contexts 2024-10-07 21:33:56 -07:00
Jacob Lifshay 44ca1a607a
remove unused AGCContext 2024-10-07 21:23:13 -07:00
Jacob Lifshay 30b9a5e48d
change NameId to have an opaque Id so output firrtl doesn't depend on how many modules of the same name were ever created
All checks were successful
/ test (push) Successful in 39m6s
2024-10-07 19:06:01 -07:00
Jacob Lifshay eed0afc6ab
add some utility From<Interned<T>> impls 2024-10-07 19:05:20 -07:00
Jacob Lifshay aec383c0af
try to fix ccache
All checks were successful
/ test (push) Successful in 1h19m11s
2024-10-06 20:57:42 -07:00
Jacob Lifshay f403eed7c0
only run tests once, since they are quite slow
Some checks failed
/ test (push) Has been cancelled
2024-10-06 20:08:39 -07:00
Jacob Lifshay 2e8b73d2fc
rename fire/fire_data to firing/firing_data 2024-10-06 19:04:48 -07:00
Jacob Lifshay e05c368688
change register names to end in _reg by convention 2024-10-06 18:50:09 -07:00
Jacob Lifshay ec77559e2b
fix cache action name
All checks were successful
/ test (push) Successful in 1h44m43s
2024-10-04 17:10:06 -07:00
Jacob Lifshay b7f1101164
reduce parallelism to fit within the number of available cpus even when running sby in prove mode (which likes to run 2 smt solvers in parallel)
Some checks failed
/ test (push) Has been cancelled
2024-10-04 17:03:51 -07:00
Jacob Lifshay 487af07154
yosys build runs out of memory
Some checks failed
/ test (push) Failing after 1h8m27s
2024-10-04 01:03:17 -07:00
Jacob Lifshay c0d4de56a9
try to make yosys build faster
Some checks failed
/ test (push) Failing after 24m17s
2024-10-03 23:40:44 -07:00
Jacob Lifshay 9f154e6b96
try caching ccache manually
Some checks are pending
/ test (push) Waiting to run
2024-10-03 23:36:39 -07:00
Jacob Lifshay 0d54b9a2a9
queue formal proof passes!
Some checks are pending
/ test (push) Has started running
2024-10-03 23:07:14 -07:00
Jacob Lifshay 343805f80b
fix #[hdl] to work with unusual identifier hygiene from macros 2024-10-03 23:04:14 -07:00
Jacob Lifshay 15a28aa7a7
install python3-click -- needed by symbiyosys
All checks were successful
/ test (push) Successful in 33m5s
2024-10-03 01:44:06 -07:00
Jacob Lifshay 4084a70485
switch default solver to z3 2024-10-03 01:43:46 -07:00
Jacob Lifshay 3e2fb9b94f
WIP getting queue formal to pass -- passes for capacity <= 2
Some checks failed
/ test (push) Has been cancelled
2024-10-03 01:08:01 -07:00
Jacob Lifshay bc26fe32fd
add ccache and clean up deps
Some checks failed
/ test (push) Has been cancelled
2024-10-03 01:01:06 -07:00
Jacob Lifshay eb65bec26e
add yosys deps
Some checks failed
/ test (push) Has been cancelled
2024-10-03 00:44:04 -07:00
Jacob Lifshay 4497f09ea0
fix wrong build steps
Some checks failed
/ test (push) Failing after 1m2s
2024-10-03 00:39:18 -07:00
Jacob Lifshay 1c63a441a9
add needed tools to CI
Some checks failed
/ test (push) Failing after 1m5s
2024-10-03 00:35:43 -07:00
Jacob Lifshay 0cf01600b3
add mod formal and move assert/assume/cover stuff to it 2024-10-01 19:56:17 -07:00
Jacob Lifshay f3d6528f5b
make annotations easier to use 2024-10-01 19:54:17 -07:00
Jacob Lifshay f35d88d2bb
remove unused valueless.rs 2024-10-01 18:41:41 -07:00
Jacob Lifshay e8c393f3bb
sort pub mod items 2024-10-01 18:40:52 -07:00
Jacob Lifshay d0b406d288
add more annotation kinds
All checks were successful
/ test (push) Successful in 4m45s
2024-10-01 18:33:32 -07:00
Jacob Lifshay 2a25dd9d7b
fix annotations getting lost 2024-10-01 18:31:44 -07:00
Jacob Lifshay 6e0b6c000d
remove stray debugging prints 2024-10-01 18:30:46 -07:00
Jacob Lifshay d089095667
change default to --simplify-enums=replace-with-bundle-of-uints
All checks were successful
/ test (push) Successful in 4m42s
2024-10-01 00:07:48 -07:00
Jacob Lifshay 9d66fcc548
improve ExportOptions support in assert_export_firrtl! 2024-10-01 00:05:39 -07:00
Jacob Lifshay 186488a82e
remove FIXME now that simplify_enums is fixed 2024-09-30 23:31:45 -07:00
Jacob Lifshay edcea1adc3
add firrtl comments when connecting expressions with different types
All checks were successful
/ test (push) Successful in 4m44s
2024-09-30 22:33:27 -07:00
Jacob Lifshay 30a38bc8da
fix simplify_enums to properly handle nested enums and connects with different types 2024-09-30 22:31:16 -07:00
Jacob Lifshay 1e2831da47
add validation of connects and matches when validating module
this is useful for catching errors in transformation passes
2024-09-30 21:20:35 -07:00
Jacob Lifshay d2ba313f0f
fix simplify_memories trying to connect Bool with UInt 2024-09-30 21:19:20 -07:00
Jacob Lifshay 04752c5037
add test for connect_any with nested enums with different-sized variant bodies
All checks were successful
/ test (push) Successful in 4m42s
simplify_enums is currently broken in that case
2024-09-25 21:55:52 -07:00
Jacob Lifshay e661aeab11
add WIP formal proof for queue()
All checks were successful
/ test (push) Successful in 5m27s
2024-09-25 02:00:06 -07:00
Jacob Lifshay 5fc7dbd6e9
add assert_formal helper for running formal proofs in rust tests 2024-09-25 02:00:06 -07:00
Jacob Lifshay 45dbb554d0
add formal subcommand 2024-09-25 02:00:06 -07:00
Jacob Lifshay bb860d54cc
add command line options for selecting which transforms to apply when generating firrtl 2024-09-25 02:00:06 -07:00
Jacob Lifshay efc3a539ed
support redirecting subprocesses' stdout/stderr to print!() so it gets captured for rust tests 2024-09-25 02:00:06 -07:00
Jacob Lifshay f32c0a7863
switch to #[derive(Parser)] instead of #[derive(Args)] 2024-09-25 01:28:11 -07:00
Jacob Lifshay 4ff01690a7
clean up deps and move missed deps to workspace 2024-09-25 01:22:35 -07:00
Jacob Lifshay 28aad19bf5
add assert/assume/cover
All checks were successful
/ test (push) Successful in 4m33s
2024-09-23 19:10:51 -07:00
Jacob Lifshay 716c65edcd
add WIP version of queue()
All checks were successful
/ test (push) Successful in 4m36s
2024-09-22 18:59:12 -07:00
Jacob Lifshay f6146048d1
add memory::splat_mask to generate mask types from a Bool 2024-09-22 18:57:30 -07:00
Jacob Lifshay a701f99fd6
add repeat() 2024-09-22 18:56:26 -07:00
Jacob Lifshay 78edfc97b2
split int::IntCmp into expr::HdlPartialEq and expr::HdlPartialOrd
All checks were successful
/ test (push) Successful in 4m32s
2024-09-22 17:28:46 -07:00
Jacob Lifshay 9ad4ec0f39
add ty.uninit()
All checks were successful
/ test (push) Successful in 4m30s
2024-09-22 17:26:23 -07:00
Jacob Lifshay 8449854cac
add ToExpr for usize/isize/NonZero<T>
All checks were successful
/ test (push) Successful in 4m32s
2024-09-22 17:19:58 -07:00
Jacob Lifshay 790bb15408
remove reset_default from proc-macro, forgot to remove when removing from RegBuilder 2024-09-22 16:03:20 -07:00
Jacob Lifshay bdbc6d89bd
add check-copyright to CI
All checks were successful
/ test (push) Successful in 4m33s
2024-09-22 15:30:53 -07:00
Jacob Lifshay 10ae95fac1
add missing copyright headers 2024-09-22 15:30:05 -07:00
Jacob Lifshay 053391b010
add script for checking copyright headers 2024-09-22 15:29:28 -07:00
Jacob Lifshay 51ce7b079e
add ReadyValid<T>
All checks were successful
/ test (push) Successful in 4m26s
2024-09-20 19:11:30 -07:00
Jacob Lifshay ff269e5def
add utility functions on HdlOption, inspired by Option's API 2024-09-20 18:49:12 -07:00
Jacob Lifshay df55a514e4
add support for incomplete_wire -- a wire that you can supply the type of later 2024-09-20 18:46:56 -07:00
Jacob Lifshay ff94dda922
support #[hdl] on functions -- enables #[hdl] usage in function body 2024-09-20 18:42:24 -07:00
Jacob Lifshay a8c804ef4a
some final cleanups
All checks were successful
/ test (push) Successful in 5m43s
2024-09-19 23:52:32 -07:00
Jacob Lifshay 2d293ae87b
#[hdl] match works! 2024-09-19 23:51:54 -07:00
Jacob Lifshay 9887d70f41
fix handling of const and size type generics when generating Index impls
All checks were successful
/ test (push) Successful in 5m1s
2024-09-19 18:45:04 -07:00
Jacob Lifshay 2c1afd1cd6
const generics on hdl_module work!
All checks were successful
/ test (push) Successful in 4m54s
2024-09-17 15:39:23 -07:00
Jacob Lifshay 76ea7f82c3
WIP adding const generics
Some checks failed
/ test (push) Failing after 1m3s
2024-09-16 16:47:10 -07:00
Jacob Lifshay 5835b995a9
WIP: use HdlOption[the_type_var] or UInt[123 + n] for creating types
All checks were successful
/ test (push) Successful in 4m56s
2024-08-21 22:27:21 -07:00
85 changed files with 20477 additions and 14947 deletions

View file

@ -0,0 +1,77 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# See Notices.txt for copyright information
on:
workflow_call:
outputs:
cache-primary-key:
value: ${{ jobs.deps.outputs.cache-primary-key }}
jobs:
deps:
runs-on: debian-12
outputs:
cache-primary-key: ${{ steps.restore-deps.outputs.cache-primary-key }}
steps:
- uses: https://code.forgejo.org/actions/checkout@v3
with:
fetch-depth: 0
- uses: https://code.forgejo.org/actions/cache/restore@v3
id: restore-deps
with:
path: deps
key: ${{ github.repository }}-deps-${{ runner.os }}-${{ hashFiles('.forgejo/workflows/deps.yml') }}
lookup-only: true
- name: Install Apt packages
if: steps.restore-deps.outputs.cache-hit != 'true'
run: |
apt-get update -qq
apt-get install -qq \
bison \
build-essential \
ccache \
clang \
cvc5 \
flex \
gawk \
g++ \
git \
libboost-filesystem-dev \
libboost-python-dev \
libboost-system-dev \
libffi-dev \
libreadline-dev \
lld \
pkg-config \
python3 \
python3-click \
tcl-dev \
zlib1g-dev
- name: Install Firtool
if: steps.restore-deps.outputs.cache-hit != 'true'
run: |
mkdir -p deps
wget -O deps/firrtl.tar.gz https://github.com/llvm/circt/releases/download/firtool-1.86.0/firrtl-bin-linux-x64.tar.gz
sha256sum -c - <<<'bf6f4ab18ae76f135c944efbd81e25391c31c1bd0617c58ab0592640abefee14 deps/firrtl.tar.gz'
tar -C deps -xvaf deps/firrtl.tar.gz
rm -rf deps/firtool
mv deps/firtool-1.86.0 deps/firtool
- 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
- 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
(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
make -C deps/yosys -j"$(nproc)"
- uses: https://code.forgejo.org/actions/cache/save@v3
if: steps.restore-deps.outputs.cache-hit != 'true'
with:
path: deps
key: ${{ steps.restore-deps.outputs.cache-primary-key }}

View file

@ -1,19 +1,60 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# See Notices.txt for copyright information
on: [push, pull_request]
jobs:
deps:
uses: ./.forgejo/workflows/deps.yml
test:
runs-on: debian-12
needs: deps
steps:
- uses: https://code.forgejo.org/actions/checkout@v3
with:
fetch-depth: 0
- run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.79.0
scripts/check-copyright.sh
- run: |
apt-get update -qq
apt-get install -qq \
bison \
build-essential \
ccache \
clang \
cvc5 \
flex \
gawk \
git \
libboost-filesystem-dev \
libboost-python-dev \
libboost-system-dev \
libffi-dev \
libreadline-dev \
lld \
pkg-config \
python3 \
python3-click \
tcl-dev \
z3 \
zlib1g-dev
- run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.80.1
source "$HOME/.cargo/env"
echo "$PATH" >> "$GITHUB_PATH"
- uses: https://code.forgejo.org/actions/cache/restore@v3
with:
path: deps
key: ${{ needs.deps.outputs.cache-primary-key }}
fail-on-cache-miss: true
- run: |
make -C deps/z3/build install
make -C deps/sby install
make -C deps/yosys install
export PATH="$(realpath deps/firtool/bin):$PATH"
echo "$PATH" >> "$GITHUB_PATH"
- uses: https://github.com/Swatinem/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/heads/master' }}
- run: cargo test
- run: cargo test --features=unstable-doc
- run: cargo build --tests --features=unstable-doc
- run: cargo doc --features=unstable-doc

2
.gitignore vendored
View file

@ -1,2 +1,4 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
# See Notices.txt for copyright information
/target
.vscode

190
Cargo.lock generated
View file

@ -56,7 +56,7 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391"
dependencies = [
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -66,9 +66,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
dependencies = [
"anstyle",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "arrayref"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
[[package]]
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "autocfg"
version = "1.1.0"
@ -109,6 +121,20 @@ dependencies = [
"wyz",
]
[[package]]
name = "blake3"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7"
dependencies = [
"arrayref",
"arrayvec",
"cc",
"cfg-if",
"constant_time_eq",
"serde",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
@ -118,6 +144,15 @@ dependencies = [
"generic-array",
]
[[package]]
name = "cc"
version = "1.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -170,6 +205,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
[[package]]
name = "constant_time_eq"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
[[package]]
name = "cpufeatures"
version = "0.2.12"
@ -189,6 +230,27 @@ dependencies = [
"typenum",
]
[[package]]
name = "ctor"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "derive_destructure2"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64b697ac90ff296f0fc031ee5a61c7ac31fb9fff50e3fb32873b09223613fc0c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "digest"
version = "0.10.7"
@ -218,7 +280,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -239,32 +301,37 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "fayalite"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"bitvec",
"blake3",
"clap",
"ctor",
"eyre",
"fayalite-proc-macros",
"fayalite-visit-gen",
"hashbrown",
"jobslot",
"num-bigint",
"num-traits",
"os_pipe",
"serde",
"serde_json",
"tempfile",
"trybuild",
"which",
]
[[package]]
name = "fayalite-proc-macros"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"fayalite-proc-macros-impl",
]
[[package]]
name = "fayalite-proc-macros-impl"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"base16ct",
"num-bigint",
@ -278,7 +345,7 @@ dependencies = [
[[package]]
name = "fayalite-visit-gen"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"indexmap",
"prettyplease",
@ -306,6 +373,17 @@ dependencies = [
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "glob"
version = "0.3.1"
@ -334,7 +412,7 @@ version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
dependencies = [
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -366,6 +444,20 @@ version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "jobslot"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe10868679d7a24c2c67d862d0e64a342ce9aef7cdde9ce8019bd35d353d458d"
dependencies = [
"cfg-if",
"derive_destructure2",
"getrandom",
"libc",
"scopeguard",
"windows-sys 0.59.0",
]
[[package]]
name = "libc"
version = "0.2.153"
@ -413,6 +505,16 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "os_pipe"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "prettyplease"
version = "0.2.20"
@ -457,7 +559,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -466,6 +568,12 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.202"
@ -509,6 +617,12 @@ dependencies = [
"digest",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "strsim"
version = "0.11.1"
@ -541,7 +655,7 @@ dependencies = [
"cfg-if",
"fastrand",
"rustix",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -612,6 +726,12 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "which"
version = "6.0.1"
@ -665,14 +785,24 @@ dependencies = [
]
[[package]]
name = "windows-targets"
version = "0.52.4"
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
@ -681,45 +811,51 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.4"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winsafe"

View file

@ -5,24 +5,30 @@ resolver = "2"
members = ["crates/*"]
[workspace.package]
version = "0.2.0"
version = "0.2.1"
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.79"
rust-version = "1.80.1"
[workspace.dependencies]
fayalite-proc-macros = { version = "=0.2.0", path = "crates/fayalite-proc-macros" }
fayalite-proc-macros-impl = { version = "=0.2.0", path = "crates/fayalite-proc-macros-impl" }
fayalite-visit-gen = { version = "=0.2.0", path = "crates/fayalite-visit-gen" }
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" }
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"] }
jobslot = "0.2.19"
num-bigint = "0.4.4"
num-traits = "0.2.16"
os_pipe = "1.2.1"
prettyplease = "0.2.20"
proc-macro2 = "1.0.83"
quote = "1.0.36"
@ -33,3 +39,4 @@ syn = { version = "2.0.66", features = ["full", "fold", "visit", "extra-traits"]
tempfile = "3.10.1"
thiserror = "1.0.61"
trybuild = "1.0"
which = "6.0.1"

View file

@ -1,3 +1,7 @@
<!--
SPDX-License-Identifier: LGPL-3.0-or-later
See Notices.txt for copyright information
-->
# Fayalite
Fayalite is a library for designing digital hardware -- a hardware description language (HDL) embedded in the Rust programming language. Fayalite's semantics are based on [FIRRTL] as interpreted by [LLVM CIRCT](https://circt.llvm.org/docs/Dialects/FIRRTL/FIRRTLAnnotations/).

View file

@ -13,11 +13,11 @@ rust-version.workspace = true
version.workspace = true
[dependencies]
base16ct = { workspace = true }
num-bigint = { workspace = true }
prettyplease = { workspace = true }
proc-macro2 = { workspace = true }
quote = { workspace = true }
sha2 = { workspace = true }
syn = { workspace = true }
tempfile = { workspace = true }
base16ct.workspace = true
num-bigint.workspace = true
prettyplease.workspace = true
proc-macro2.workspace = true
quote.workspace = true
sha2.workspace = true
syn.workspace = true
tempfile.workspace = true

View file

@ -224,25 +224,31 @@ forward_fold!(syn::ExprPath => fold_expr_path);
forward_fold!(syn::ExprRepeat => fold_expr_repeat);
forward_fold!(syn::ExprStruct => fold_expr_struct);
forward_fold!(syn::ExprTuple => fold_expr_tuple);
forward_fold!(syn::FieldPat => fold_field_pat);
forward_fold!(syn::Ident => fold_ident);
forward_fold!(syn::Member => fold_member);
forward_fold!(syn::Path => fold_path);
forward_fold!(syn::Type => fold_type);
forward_fold!(syn::TypePath => fold_type_path);
forward_fold!(syn::WherePredicate => fold_where_predicate);
no_op_fold!(proc_macro2::Span);
no_op_fold!(syn::parse::Nothing);
no_op_fold!(syn::token::Brace);
no_op_fold!(syn::token::Bracket);
no_op_fold!(syn::token::Group);
no_op_fold!(syn::token::Paren);
no_op_fold!(syn::Token![_]);
no_op_fold!(syn::Token![,]);
no_op_fold!(syn::Token![;]);
no_op_fold!(syn::Token![:]);
no_op_fold!(syn::Token![::]);
no_op_fold!(syn::Token![..]);
no_op_fold!(syn::Token![.]);
no_op_fold!(syn::Token![#]);
no_op_fold!(syn::Token![<]);
no_op_fold!(syn::Token![=]);
no_op_fold!(syn::Token![=>]);
no_op_fold!(syn::Token![>]);
no_op_fold!(syn::Token![|]);
no_op_fold!(syn::Token![enum]);
no_op_fold!(syn::Token![extern]);
@ -251,3 +257,4 @@ no_op_fold!(syn::Token![mut]);
no_op_fold!(syn::Token![static]);
no_op_fold!(syn::Token![struct]);
no_op_fold!(syn::Token![where]);
no_op_fold!(usize);

View file

@ -0,0 +1,859 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
hdl_type_common::{
common_derives, get_target, ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedField,
ParsedFieldsNamed, ParsedGenerics, SplitForImpl, TypesParser, WrappedInConst,
},
kw, Errors, HdlAttr, PairsIterExt,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote_spanned, ToTokens};
use syn::{
parse_quote, parse_quote_spanned,
punctuated::{Pair, Punctuated},
spanned::Spanned,
token::Brace,
AngleBracketedGenericArguments, Attribute, Field, FieldMutability, Fields, FieldsNamed,
GenericParam, Generics, Ident, ItemStruct, Path, Token, Type, Visibility,
};
#[derive(Clone, Debug)]
pub(crate) struct ParsedBundle {
pub(crate) attrs: Vec<Attribute>,
pub(crate) options: HdlAttr<ItemOptions, kw::hdl>,
pub(crate) vis: Visibility,
pub(crate) struct_token: Token![struct],
pub(crate) ident: Ident,
pub(crate) generics: MaybeParsed<ParsedGenerics, Generics>,
pub(crate) fields: MaybeParsed<ParsedFieldsNamed, FieldsNamed>,
pub(crate) field_flips: Vec<Option<HdlAttr<kw::flip, kw::hdl>>>,
pub(crate) mask_type_ident: Ident,
pub(crate) mask_type_match_variant_ident: Ident,
pub(crate) match_variant_ident: Ident,
pub(crate) builder_ident: Ident,
pub(crate) mask_type_builder_ident: Ident,
}
impl ParsedBundle {
fn parse_field(
errors: &mut Errors,
field: &mut Field,
index: usize,
) -> Option<HdlAttr<kw::flip, kw::hdl>> {
let Field {
attrs,
vis: _,
mutability,
ident,
colon_token,
ty,
} = field;
let ident = ident.get_or_insert_with(|| format_ident!("_{}", index, span = ty.span()));
if !matches!(mutability, FieldMutability::None) {
// FIXME: use mutability as the spanned tokens,
// blocked on https://github.com/dtolnay/syn/issues/1717
errors.error(&ident, "field mutability is not supported");
*mutability = FieldMutability::None;
}
*mutability = FieldMutability::None;
colon_token.get_or_insert(Token![:](ident.span()));
errors.unwrap_or_default(HdlAttr::parse_and_take_attr(attrs))
}
fn parse(item: ItemStruct) -> syn::Result<Self> {
let ItemStruct {
mut attrs,
vis,
struct_token,
ident,
mut generics,
fields,
semi_token,
} = item;
let mut errors = Errors::new();
let mut options = errors
.unwrap_or_default(HdlAttr::<ItemOptions, kw::hdl>::parse_and_take_attr(
&mut attrs,
))
.unwrap_or_default();
errors.ok(options.body.validate());
let ItemOptions {
outline_generated: _,
target: _,
custom_bounds,
no_static: _,
no_runtime_generics: _,
} = options.body;
let mut fields = match fields {
syn::Fields::Named(fields) => fields,
syn::Fields::Unnamed(fields) => {
errors.error(&fields, "#[hdl] struct must use curly braces: {}");
FieldsNamed {
brace_token: Brace(fields.paren_token.span),
named: fields.unnamed,
}
}
syn::Fields::Unit => {
errors.error(&fields, "#[hdl] struct must use curly braces: {}");
FieldsNamed {
brace_token: Brace(semi_token.unwrap_or_default().span),
named: Punctuated::default(),
}
}
};
let mut field_flips = Vec::with_capacity(fields.named.len());
for (index, field) in fields.named.iter_mut().enumerate() {
field_flips.push(Self::parse_field(&mut errors, field, index));
}
let generics = if custom_bounds.is_some() {
MaybeParsed::Unrecognized(generics)
} else if let Some(generics) = errors.ok(ParsedGenerics::parse(&mut generics)) {
MaybeParsed::Parsed(generics)
} else {
MaybeParsed::Unrecognized(generics)
};
let fields = TypesParser::maybe_run(generics.as_ref(), fields, &mut errors);
errors.finish()?;
Ok(Self {
attrs,
options,
vis,
struct_token,
generics,
fields,
field_flips,
mask_type_ident: format_ident!("__{}__MaskType", ident),
mask_type_match_variant_ident: format_ident!("__{}__MaskType__MatchVariant", ident),
match_variant_ident: format_ident!("__{}__MatchVariant", ident),
mask_type_builder_ident: format_ident!("__{}__MaskType__Builder", ident),
builder_ident: format_ident!("__{}__Builder", ident),
ident,
})
}
}
#[derive(Clone, Debug)]
struct Builder {
vis: Visibility,
struct_token: Token![struct],
ident: Ident,
target: Path,
generics: Generics,
fields: FieldsNamed,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
enum BuilderFieldState {
Unfilled,
Generic,
Filled,
}
impl Builder {
fn phantom_field_name(&self) -> Ident {
format_ident!("__phantom", span = self.ident.span())
}
fn phantom_field(&self) -> Field {
let target = &self.target;
let type_generics = self.generics.split_for_impl().1;
Field {
attrs: vec![],
vis: Visibility::Inherited,
mutability: FieldMutability::None,
ident: Some(self.phantom_field_name()),
colon_token: Some(Token![:](self.ident.span())),
ty: parse_quote_spanned! {self.ident.span()=>
::fayalite::__std::marker::PhantomData<#target #type_generics>
},
}
}
fn builder_struct_generics(
&self,
mut get_field_state: impl FnMut(usize) -> BuilderFieldState,
) -> Generics {
let mut retval = self.generics.clone();
for param in retval.params.iter_mut() {
match param {
GenericParam::Lifetime(_) => {}
GenericParam::Type(param) => param.default = None,
GenericParam::Const(param) => param.default = None,
}
}
for (field_index, field) in self.fields.named.iter().enumerate() {
match get_field_state(field_index) {
BuilderFieldState::Unfilled | BuilderFieldState::Filled => continue,
BuilderFieldState::Generic => {}
}
if !retval.params.empty_or_trailing() {
retval.params.push_punct(Token![,](self.ident.span()));
}
retval.params.push_value(GenericParam::Type(
type_var_for_field_name(field.ident.as_ref().unwrap()).into(),
));
}
retval
}
fn builder_struct_ty(
&self,
mut get_field_state: impl FnMut(usize) -> BuilderFieldState,
) -> Type {
let mut ty_arguments: AngleBracketedGenericArguments = if self.generics.params.is_empty() {
parse_quote_spanned! {self.ident.span()=>
<>
}
} else {
let builder_type_generics = self.generics.split_for_impl().1;
parse_quote! { #builder_type_generics }
};
for (field_index, Field { ident, ty, .. }) in self.fields.named.iter().enumerate() {
let ident = ident.as_ref().unwrap();
if !ty_arguments.args.empty_or_trailing() {
ty_arguments.args.push_punct(Token![,](self.ident.span()));
}
ty_arguments
.args
.push_value(match get_field_state(field_index) {
BuilderFieldState::Unfilled => parse_quote_spanned! {self.ident.span()=>
::fayalite::bundle::Unfilled<#ty>
},
BuilderFieldState::Generic => {
let type_var = type_var_for_field_name(ident);
parse_quote_spanned! {self.ident.span()=>
#type_var
}
}
BuilderFieldState::Filled => parse_quote_spanned! {self.ident.span()=>
::fayalite::expr::Expr<#ty>
},
});
}
let ident = &self.ident;
parse_quote_spanned! {ident.span()=>
#ident #ty_arguments
}
}
}
fn type_var_for_field_name(ident: &Ident) -> Ident {
format_ident!("__T_{}", ident)
}
fn field_fn_for_field_name(ident: &Ident) -> Ident {
format_ident!("field_{}", ident)
}
impl ToTokens for Builder {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
vis,
struct_token,
ident,
target,
generics: _,
fields,
} = self;
let phantom_field_name = self.phantom_field_name();
let builder_struct = ItemStruct {
attrs: vec![parse_quote_spanned! {ident.span()=>
#[allow(non_camel_case_types, dead_code)]
}],
vis: vis.clone(),
struct_token: *struct_token,
ident: ident.clone(),
generics: self.builder_struct_generics(|_| BuilderFieldState::Generic),
fields: Fields::Named(FieldsNamed {
brace_token: fields.brace_token,
named: Punctuated::from_iter(
[Pair::Punctuated(
self.phantom_field(),
Token![,](self.ident.span()),
)]
.into_iter()
.chain(fields.named.pairs().map_pair_value_ref(|field| {
let ident = field.ident.as_ref().unwrap();
let type_var = type_var_for_field_name(ident);
Field {
vis: Visibility::Inherited,
ty: parse_quote_spanned! {ident.span()=>
#type_var
},
..field.clone()
}
})),
),
}),
semi_token: None,
};
builder_struct.to_tokens(tokens);
let field_idents = Vec::from_iter(
self.fields
.named
.iter()
.map(|field| field.ident.as_ref().unwrap()),
);
for (
field_index,
Field {
vis,
ident: field_ident,
ty,
..
},
) in self.fields.named.iter().enumerate()
{
let field_ident = field_ident.as_ref().unwrap();
let fn_ident = field_fn_for_field_name(field_ident);
let fn_generics = self.builder_struct_generics(|i| {
if i == field_index {
BuilderFieldState::Unfilled
} else {
BuilderFieldState::Generic
}
});
let (impl_generics, _, where_clause) = fn_generics.split_for_impl();
let unfilled_ty = self.builder_struct_ty(|i| {
if i == field_index {
BuilderFieldState::Unfilled
} else {
BuilderFieldState::Generic
}
});
let filled_ty = self.builder_struct_ty(|i| {
if i == field_index {
BuilderFieldState::Filled
} else {
BuilderFieldState::Generic
}
});
let pat_fields =
Vec::from_iter(self.fields.named.iter().enumerate().map(|(i, field)| {
let field_ident = field.ident.as_ref().unwrap();
if field_index == i {
quote_spanned! {self.ident.span()=>
#field_ident: _,
}
} else {
quote_spanned! {self.ident.span()=>
#field_ident,
}
}
}));
quote_spanned! {self.ident.span()=>
#[automatically_derived]
#[allow(non_camel_case_types, non_snake_case, dead_code)]
impl #impl_generics #unfilled_ty
#where_clause
{
#vis fn #fn_ident(
self,
#field_ident: impl ::fayalite::expr::ToExpr<Type = #ty>,
) -> #filled_ty {
let Self {
#phantom_field_name: _,
#(#pat_fields)*
} = self;
let #field_ident = ::fayalite::expr::ToExpr::to_expr(&#field_ident);
#ident {
#phantom_field_name: ::fayalite::__std::marker::PhantomData,
#(#field_idents,)*
}
}
}
}
.to_tokens(tokens);
}
let unfilled_generics = self.builder_struct_generics(|_| BuilderFieldState::Unfilled);
let unfilled_ty = self.builder_struct_ty(|_| BuilderFieldState::Unfilled);
let (unfilled_impl_generics, _, unfilled_where_clause) = unfilled_generics.split_for_impl();
quote_spanned! {self.ident.span()=>
#[automatically_derived]
#[allow(non_camel_case_types, dead_code)]
impl #unfilled_impl_generics ::fayalite::__std::default::Default for #unfilled_ty
#unfilled_where_clause
{
fn default() -> Self {
#ident {
#phantom_field_name: ::fayalite::__std::marker::PhantomData,
#(#field_idents: ::fayalite::__std::default::Default::default(),)*
}
}
}
}
.to_tokens(tokens);
let filled_generics = self.builder_struct_generics(|_| BuilderFieldState::Filled);
let filled_ty = self.builder_struct_ty(|_| BuilderFieldState::Filled);
let (filled_impl_generics, _, filled_where_clause) = filled_generics.split_for_impl();
let type_generics = self.generics.split_for_impl().1;
quote_spanned! {self.ident.span()=>
#[automatically_derived]
#[allow(non_camel_case_types, dead_code)]
impl #filled_impl_generics ::fayalite::expr::ToExpr for #filled_ty
#filled_where_clause
{
type Type = #target #type_generics;
fn to_expr(
&self,
) -> ::fayalite::expr::Expr<<Self as ::fayalite::expr::ToExpr>::Type> {
let __ty = #target {
#(#field_idents: ::fayalite::expr::Expr::ty(self.#field_idents),)*
};
let __field_values = [
#(::fayalite::expr::Expr::canonical(self.#field_idents),)*
];
::fayalite::expr::ToExpr::to_expr(
&::fayalite::expr::ops::BundleLiteral::new(
__ty,
::fayalite::intern::Intern::intern(&__field_values[..]),
),
)
}
}
}
.to_tokens(tokens);
}
}
impl ToTokens for ParsedBundle {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
attrs,
options,
vis,
struct_token,
ident,
generics,
fields,
field_flips,
mask_type_ident,
mask_type_match_variant_ident,
match_variant_ident,
builder_ident,
mask_type_builder_ident,
} = self;
let span = ident.span();
let ItemOptions {
outline_generated: _,
target,
custom_bounds: _,
no_static,
no_runtime_generics,
} = &options.body;
let target = get_target(target, ident);
let mut item_attrs = attrs.clone();
item_attrs.push(common_derives(span));
ItemStruct {
attrs: item_attrs,
vis: vis.clone(),
struct_token: *struct_token,
ident: ident.clone(),
generics: generics.into(),
fields: Fields::Named(fields.clone().into()),
semi_token: None,
}
.to_tokens(tokens);
let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
if let (MaybeParsed::Parsed(generics), MaybeParsed::Parsed(fields), None) =
(generics, fields, no_runtime_generics)
{
generics.make_runtime_generics(tokens, vis, ident, &target, |context| {
let fields: Vec<_> = fields
.named
.iter()
.map(|ParsedField { ident, ty, .. }| {
let ident = ident.as_ref().unwrap();
let expr = ty.make_hdl_type_expr(context);
quote_spanned! {span=>
#ident: #expr,
}
})
.collect();
parse_quote_spanned! {span=>
#target {
#(#fields)*
}
}
})
}
let mut wrapped_in_const = WrappedInConst::new(tokens, span);
let tokens = wrapped_in_const.inner();
let builder = Builder {
vis: vis.clone(),
struct_token: *struct_token,
ident: builder_ident.clone(),
target: target.clone(),
generics: generics.into(),
fields: fields.clone().into(),
};
builder.to_tokens(tokens);
let unfilled_builder_ty = builder.builder_struct_ty(|_| BuilderFieldState::Unfilled);
let filled_builder_ty = builder.builder_struct_ty(|_| BuilderFieldState::Filled);
let mut mask_type_fields = FieldsNamed::from(fields.clone());
for Field { ty, .. } in &mut mask_type_fields.named {
*ty = parse_quote_spanned! {span=>
<#ty as ::fayalite::ty::Type>::MaskType
};
}
let mask_type_builder = Builder {
vis: vis.clone(),
struct_token: *struct_token,
ident: mask_type_builder_ident.clone(),
target: mask_type_ident.clone().into(),
generics: generics.into(),
fields: mask_type_fields.clone(),
};
mask_type_builder.to_tokens(tokens);
let unfilled_mask_type_builder_ty =
mask_type_builder.builder_struct_ty(|_| BuilderFieldState::Unfilled);
let filled_mask_type_builder_ty =
mask_type_builder.builder_struct_ty(|_| BuilderFieldState::Filled);
ItemStruct {
attrs: vec![
common_derives(span),
parse_quote_spanned! {span=>
#[allow(non_camel_case_types, dead_code)]
},
],
vis: vis.clone(),
struct_token: *struct_token,
ident: mask_type_ident.clone(),
generics: generics.into(),
fields: Fields::Named(mask_type_fields.clone()),
semi_token: None,
}
.to_tokens(tokens);
let mut mask_type_match_variant_fields = mask_type_fields;
for Field { ty, .. } in &mut mask_type_match_variant_fields.named {
*ty = parse_quote_spanned! {span=>
::fayalite::expr::Expr<#ty>
};
}
ItemStruct {
attrs: vec![
common_derives(span),
parse_quote_spanned! {span=>
#[allow(non_camel_case_types, dead_code)]
},
],
vis: vis.clone(),
struct_token: *struct_token,
ident: mask_type_match_variant_ident.clone(),
generics: generics.into(),
fields: Fields::Named(mask_type_match_variant_fields),
semi_token: None,
}
.to_tokens(tokens);
let mut match_variant_fields = FieldsNamed::from(fields.clone());
for Field { ty, .. } in &mut match_variant_fields.named {
*ty = parse_quote_spanned! {span=>
::fayalite::expr::Expr<#ty>
};
}
ItemStruct {
attrs: vec![
common_derives(span),
parse_quote_spanned! {span=>
#[allow(non_camel_case_types, dead_code)]
},
],
vis: vis.clone(),
struct_token: *struct_token,
ident: match_variant_ident.clone(),
generics: generics.into(),
fields: Fields::Named(match_variant_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);
let match_variant_body_fields = Vec::from_iter(fields.named().into_iter().map(|field| {
let ident: &Ident = field.ident().as_ref().unwrap();
let ident_str = ident.to_string();
quote_spanned! {span=>
#ident: ::fayalite::expr::Expr::field(#this_token, #ident_str),
}
}));
let mask_type_body_fields = Vec::from_iter(fields.named().into_iter().map(|field| {
let ident: &Ident = field.ident().as_ref().unwrap();
quote_spanned! {span=>
#ident: ::fayalite::ty::Type::mask_type(&#self_token.#ident),
}
}));
let from_canonical_body_fields =
Vec::from_iter(fields.named().into_iter().enumerate().zip(field_flips).map(
|((index, field), flip)| {
let ident: &Ident = field.ident().as_ref().unwrap();
let ident_str = ident.to_string();
let not_flipped = flip.is_none().then(|| Token![!](span));
quote_spanned! {span=>
#ident: {
let ::fayalite::bundle::BundleField {
name: __name,
flipped: __flipped,
ty: __ty,
} = #fields_token[#index];
::fayalite::__std::assert_eq!(&*__name, #ident_str);
::fayalite::__std::assert!(#not_flipped __flipped);
::fayalite::ty::Type::from_canonical(__ty)
},
}
},
));
let fields_body_fields = Vec::from_iter(fields.named().into_iter().zip(field_flips).map(
|(field, flip)| {
let ident: &Ident = field.ident().as_ref().unwrap();
let ident_str = ident.to_string();
let flipped = flip.is_some();
quote_spanned! {span=>
::fayalite::bundle::BundleField {
name: ::fayalite::intern::Intern::intern(#ident_str),
flipped: #flipped,
ty: ::fayalite::ty::Type::canonical(&#self_token.#ident),
},
}
},
));
let fields_len = fields.named().into_iter().len();
quote_spanned! {span=>
#[automatically_derived]
impl #impl_generics ::fayalite::ty::Type for #mask_type_ident #type_generics
#where_clause
{
type BaseType = ::fayalite::bundle::Bundle;
type MaskType = #mask_type_ident #type_generics;
type MatchVariant = #mask_type_match_variant_ident #type_generics;
type MatchActiveScope = ();
type MatchVariantAndInactiveScope = ::fayalite::ty::MatchVariantWithoutScope<
<Self as ::fayalite::ty::Type>::MatchVariant,
>;
type MatchVariantsIter = ::fayalite::__std::iter::Once<
<Self as ::fayalite::ty::Type>::MatchVariantAndInactiveScope,
>;
fn match_variants(
#this_token: ::fayalite::expr::Expr<Self>,
__source_location: ::fayalite::source_location::SourceLocation,
) -> <Self as ::fayalite::ty::Type>::MatchVariantsIter {
let __retval = #mask_type_match_variant_ident {
#(#match_variant_body_fields)*
};
::fayalite::__std::iter::once(::fayalite::ty::MatchVariantWithoutScope(__retval))
}
fn mask_type(&#self_token) -> <Self as ::fayalite::ty::Type>::MaskType {
*#self_token
}
fn canonical(&#self_token) -> ::fayalite::ty::CanonicalType {
::fayalite::ty::Type::canonical(&::fayalite::bundle::Bundle::new(::fayalite::bundle::BundleType::fields(#self_token)))
}
#[track_caller]
fn from_canonical(__canonical_type: ::fayalite::ty::CanonicalType) -> Self {
let ::fayalite::ty::CanonicalType::Bundle(__bundle) = __canonical_type else {
::fayalite::__std::panic!("expected bundle");
};
let #fields_token = ::fayalite::bundle::BundleType::fields(&__bundle);
::fayalite::__std::assert_eq!(#fields_token.len(), #fields_len, "bundle has wrong number of fields");
Self {
#(#from_canonical_body_fields)*
}
}
fn source_location() -> ::fayalite::source_location::SourceLocation {
::fayalite::source_location::SourceLocation::caller()
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::bundle::BundleType for #mask_type_ident #type_generics
#where_clause
{
type Builder = #unfilled_mask_type_builder_ty;
type FilledBuilder = #filled_mask_type_builder_ty;
fn fields(&#self_token) -> ::fayalite::intern::Interned<[::fayalite::bundle::BundleField]> {
::fayalite::intern::Intern::intern(&[#(#fields_body_fields)*][..])
}
}
impl #impl_generics #mask_type_ident #type_generics
#where_clause
{
#vis fn __bundle_builder() -> #unfilled_mask_type_builder_ty {
::fayalite::__std::default::Default::default()
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::ty::TypeWithDeref for #mask_type_ident #type_generics
#where_clause
{
fn expr_deref(#this_token: &::fayalite::expr::Expr<Self>) -> &<Self as ::fayalite::ty::Type>::MatchVariant {
let #this_token = *#this_token;
let __retval = #mask_type_match_variant_ident {
#(#match_variant_body_fields)*
};
::fayalite::intern::Interned::into_inner(::fayalite::intern::Intern::intern_sized(__retval))
}
}
#[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 MatchVariant = #match_variant_ident #type_generics;
type MatchActiveScope = ();
type MatchVariantAndInactiveScope = ::fayalite::ty::MatchVariantWithoutScope<
<Self as ::fayalite::ty::Type>::MatchVariant,
>;
type MatchVariantsIter = ::fayalite::__std::iter::Once<
<Self as ::fayalite::ty::Type>::MatchVariantAndInactiveScope,
>;
fn match_variants(
#this_token: ::fayalite::expr::Expr<Self>,
__source_location: ::fayalite::source_location::SourceLocation,
) -> <Self as ::fayalite::ty::Type>::MatchVariantsIter {
let __retval = #match_variant_ident {
#(#match_variant_body_fields)*
};
::fayalite::__std::iter::once(::fayalite::ty::MatchVariantWithoutScope(__retval))
}
fn mask_type(&#self_token) -> <Self as ::fayalite::ty::Type>::MaskType {
#mask_type_ident {
#(#mask_type_body_fields)*
}
}
fn canonical(&#self_token) -> ::fayalite::ty::CanonicalType {
::fayalite::ty::Type::canonical(&::fayalite::bundle::Bundle::new(::fayalite::bundle::BundleType::fields(#self_token)))
}
#[track_caller]
fn from_canonical(__canonical_type: ::fayalite::ty::CanonicalType) -> Self {
let ::fayalite::ty::CanonicalType::Bundle(__bundle) = __canonical_type else {
::fayalite::__std::panic!("expected bundle");
};
let #fields_token = ::fayalite::bundle::BundleType::fields(&__bundle);
::fayalite::__std::assert_eq!(#fields_token.len(), #fields_len, "bundle has wrong number of fields");
Self {
#(#from_canonical_body_fields)*
}
}
fn source_location() -> ::fayalite::source_location::SourceLocation {
::fayalite::source_location::SourceLocation::caller()
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::bundle::BundleType for #target #type_generics
#where_clause
{
type Builder = #unfilled_builder_ty;
type FilledBuilder = #filled_builder_ty;
fn fields(&#self_token) -> ::fayalite::intern::Interned<[::fayalite::bundle::BundleField]> {
::fayalite::intern::Intern::intern(&[#(#fields_body_fields)*][..])
}
}
impl #impl_generics #target #type_generics
#where_clause
{
#vis fn __bundle_builder() -> #unfilled_builder_ty {
::fayalite::__std::default::Default::default()
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::ty::TypeWithDeref for #target #type_generics
#where_clause
{
fn expr_deref(#this_token: &::fayalite::expr::Expr<Self>) -> &<Self as ::fayalite::ty::Type>::MatchVariant {
let #this_token = *#this_token;
let __retval = #match_variant_ident {
#(#match_variant_body_fields)*
};
::fayalite::intern::Interned::into_inner(::fayalite::intern::Intern::intern_sized(__retval))
}
}
}
.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) =
static_generics.split_for_impl();
let static_type_body_fields = Vec::from_iter(fields.named().into_iter().map(|field| {
let ident: &Ident = field.ident().as_ref().unwrap();
let ty = field.ty();
quote_spanned! {span=>
#ident: <#ty as ::fayalite::ty::StaticType>::TYPE,
}
}));
let static_mask_type_body_fields =
Vec::from_iter(fields.named().into_iter().map(|field| {
let ident: &Ident = field.ident().as_ref().unwrap();
let ty = field.ty();
quote_spanned! {span=>
#ident: <#ty as ::fayalite::ty::StaticType>::MASK_TYPE,
}
}));
let type_properties = format_ident!("__type_properties", span = span);
let type_properties_fields = Vec::from_iter(fields.named().into_iter().zip(field_flips).map(|(field, field_flip)| {
let flipped = field_flip.is_some();
let ty = field.ty();
quote_spanned! {span=>
let #type_properties = #type_properties.field(#flipped, <#ty as ::fayalite::ty::StaticType>::TYPE_PROPERTIES);
}
}));
let type_properties_mask_fields = Vec::from_iter(fields.named().into_iter().zip(field_flips).map(|(field, field_flip)| {
let flipped = field_flip.is_some();
let ty = field.ty();
quote_spanned! {span=>
let #type_properties = #type_properties.field(#flipped, <#ty as ::fayalite::ty::StaticType>::MASK_TYPE_PROPERTIES);
}
}));
quote_spanned! {span=>
#[automatically_derived]
impl #static_impl_generics ::fayalite::ty::StaticType for #mask_type_ident #static_type_generics
#static_where_clause
{
const TYPE: Self = Self {
#(#static_mask_type_body_fields)*
};
const MASK_TYPE: <Self as ::fayalite::ty::Type>::MaskType = Self {
#(#static_mask_type_body_fields)*
};
const TYPE_PROPERTIES: ::fayalite::ty::TypeProperties = {
let #type_properties = ::fayalite::bundle::BundleTypePropertiesBuilder::new();
#(#type_properties_mask_fields)*
#type_properties.finish()
};
const MASK_TYPE_PROPERTIES: ::fayalite::ty::TypeProperties = {
let #type_properties = ::fayalite::bundle::BundleTypePropertiesBuilder::new();
#(#type_properties_mask_fields)*
#type_properties.finish()
};
}
#[automatically_derived]
impl #static_impl_generics ::fayalite::ty::StaticType for #target #static_type_generics
#static_where_clause
{
const TYPE: Self = Self {
#(#static_type_body_fields)*
};
const MASK_TYPE: <Self as ::fayalite::ty::Type>::MaskType = #mask_type_ident {
#(#static_mask_type_body_fields)*
};
const TYPE_PROPERTIES: ::fayalite::ty::TypeProperties = {
let #type_properties = ::fayalite::bundle::BundleTypePropertiesBuilder::new();
#(#type_properties_fields)*
#type_properties.finish()
};
const MASK_TYPE_PROPERTIES: ::fayalite::ty::TypeProperties = {
let #type_properties = ::fayalite::bundle::BundleTypePropertiesBuilder::new();
#(#type_properties_mask_fields)*
#type_properties.finish()
};
}
}
.to_tokens(tokens);
}
}
}
pub(crate) fn hdl_bundle(item: ItemStruct) -> syn::Result<TokenStream> {
let item = ParsedBundle::parse(item)?;
let outline_generated = item.options.body.outline_generated;
let mut contents = item.to_token_stream();
if outline_generated.is_some() {
contents = crate::outline_generated(contents, "hdl-bundle-");
}
Ok(contents)
}

View file

@ -0,0 +1,664 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
hdl_type_common::{
common_derives, get_target, ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics,
ParsedType, SplitForImpl, TypesParser, WrappedInConst,
},
kw, Errors, HdlAttr, PairsIterExt,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote_spanned, ToTokens};
use syn::{
parse_quote_spanned,
punctuated::{Pair, Punctuated},
token::{Brace, Paren},
Attribute, Field, FieldMutability, Fields, FieldsNamed, FieldsUnnamed, Generics, Ident,
ItemEnum, ItemStruct, Token, Type, Variant, Visibility,
};
crate::options! {
#[options = VariantOptions]
pub(crate) enum VariantOption {}
}
crate::options! {
#[options = FieldOptions]
pub(crate) enum FieldOption {}
}
#[derive(Clone, Debug)]
pub(crate) struct ParsedVariantField {
pub(crate) paren_token: Paren,
pub(crate) attrs: Vec<Attribute>,
pub(crate) options: HdlAttr<FieldOptions, kw::hdl>,
pub(crate) ty: MaybeParsed<ParsedType, Type>,
pub(crate) comma_token: Option<Token![,]>,
}
#[derive(Clone, Debug)]
pub(crate) struct ParsedVariant {
pub(crate) attrs: Vec<Attribute>,
pub(crate) options: HdlAttr<VariantOptions, kw::hdl>,
pub(crate) ident: Ident,
pub(crate) field: Option<ParsedVariantField>,
}
impl ParsedVariant {
fn parse(
errors: &mut Errors,
variant: Variant,
generics: &MaybeParsed<ParsedGenerics, Generics>,
) -> Self {
let Variant {
mut attrs,
ident,
fields,
discriminant,
} = variant;
let options = errors
.unwrap_or_default(HdlAttr::parse_and_take_attr(&mut attrs))
.unwrap_or_default();
let field = match fields {
Fields::Unnamed(FieldsUnnamed {
paren_token,
unnamed,
}) if unnamed.len() == 1 => {
let (field, comma_token) = unnamed.into_pairs().next().unwrap().into_tuple();
let Field {
mut attrs,
vis,
mutability,
ident: _,
colon_token: _,
ty,
} = field;
let options = errors
.unwrap_or_default(HdlAttr::parse_and_take_attr(&mut attrs))
.unwrap_or_default();
if !matches!(vis, Visibility::Inherited) {
errors.error(
&vis,
"enum variant fields must not have a visibility specifier",
);
}
if !matches!(mutability, FieldMutability::None) {
// FIXME: use mutability as the spanned tokens,
// blocked on https://github.com/dtolnay/syn/issues/1717
errors.error(&ty, "field mutability is not supported");
}
Some(ParsedVariantField {
paren_token,
attrs,
options,
ty: TypesParser::maybe_run(generics.as_ref(), ty, errors),
comma_token,
})
}
Fields::Unit => None,
Fields::Unnamed(fields) if fields.unnamed.is_empty() => None,
Fields::Named(fields) if fields.named.is_empty() => None,
Fields::Unnamed(_) | Fields::Named(_) => {
errors.error(
fields,
"enum variant must either have no fields or a single parenthesized field",
);
None
}
};
if let Some((eq, _)) = discriminant {
errors.error(eq, "custom enum discriminants are not allowed");
}
Self {
attrs,
options,
ident,
field,
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct ParsedEnum {
pub(crate) attrs: Vec<Attribute>,
pub(crate) options: HdlAttr<ItemOptions, kw::hdl>,
pub(crate) vis: Visibility,
pub(crate) enum_token: Token![enum],
pub(crate) ident: Ident,
pub(crate) generics: MaybeParsed<ParsedGenerics, Generics>,
pub(crate) brace_token: Brace,
pub(crate) variants: Punctuated<ParsedVariant, Token![,]>,
pub(crate) match_variant_ident: Ident,
}
impl ParsedEnum {
fn parse(item: ItemEnum) -> syn::Result<Self> {
let ItemEnum {
mut attrs,
vis,
enum_token,
ident,
mut generics,
brace_token,
variants,
} = item;
let mut errors = Errors::new();
let mut options = errors
.unwrap_or_default(HdlAttr::<ItemOptions, kw::hdl>::parse_and_take_attr(
&mut attrs,
))
.unwrap_or_default();
errors.ok(options.body.validate());
let ItemOptions {
outline_generated: _,
target: _,
custom_bounds,
no_static: _,
no_runtime_generics: _,
} = options.body;
attrs.retain(|attr| {
if attr.path().is_ident("repr") {
errors.error(attr, "#[repr] is not supported on #[hdl] enums");
false
} else {
true
}
});
let generics = if custom_bounds.is_some() {
MaybeParsed::Unrecognized(generics)
} else if let Some(generics) = errors.ok(ParsedGenerics::parse(&mut generics)) {
MaybeParsed::Parsed(generics)
} else {
MaybeParsed::Unrecognized(generics)
};
let variants = Punctuated::from_iter(
variants
.into_pairs()
.map_pair_value(|v| ParsedVariant::parse(&mut errors, v, &generics)),
);
errors.finish()?;
Ok(Self {
attrs,
options,
vis,
enum_token,
generics,
brace_token,
variants,
match_variant_ident: format_ident!("__{}__MatchVariant", ident),
ident,
})
}
}
impl ToTokens for ParsedEnum {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
attrs,
options,
vis,
enum_token,
ident,
generics,
brace_token,
variants,
match_variant_ident,
} = self;
let span = ident.span();
let ItemOptions {
outline_generated: _,
target,
custom_bounds: _,
no_static,
no_runtime_generics,
} = &options.body;
let target = get_target(target, ident);
let mut struct_attrs = attrs.clone();
struct_attrs.push(common_derives(span));
struct_attrs.push(parse_quote_spanned! {span=>
#[allow(non_snake_case)]
});
let struct_fields = Punctuated::from_iter(variants.pairs().map_pair_value_ref(
|ParsedVariant {
attrs: _,
options,
ident,
field,
}| {
let VariantOptions {} = options.body;
let colon_token;
let ty = if let Some(ParsedVariantField {
paren_token,
attrs: _,
options,
ty,
comma_token: _,
}) = field
{
let FieldOptions {} = options.body;
colon_token = Token![:](paren_token.span.open());
ty.clone().into()
} else {
colon_token = Token![:](span);
parse_quote_spanned! {span=>
()
}
};
Field {
attrs: vec![],
vis: vis.clone(),
mutability: FieldMutability::None,
ident: Some(ident.clone()),
colon_token: Some(colon_token),
ty,
}
},
));
ItemStruct {
attrs: struct_attrs,
vis: vis.clone(),
struct_token: Token![struct](enum_token.span),
ident: ident.clone(),
generics: generics.into(),
fields: if struct_fields.is_empty() {
Fields::Unit
} else {
Fields::Named(FieldsNamed {
brace_token: *brace_token,
named: struct_fields,
})
},
semi_token: None,
}
.to_tokens(tokens);
let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
if let (MaybeParsed::Parsed(generics), None) = (generics, no_runtime_generics) {
generics.make_runtime_generics(tokens, vis, ident, &target, |context| {
let fields: Vec<_> = variants
.iter()
.map(|ParsedVariant { ident, field, .. }| {
if let Some(ParsedVariantField {
ty: MaybeParsed::Parsed(ty),
..
}) = field
{
let expr = ty.make_hdl_type_expr(context);
quote_spanned! {span=>
#ident: #expr,
}
} else {
quote_spanned! {span=>
#ident: (),
}
}
})
.collect();
parse_quote_spanned! {span=>
#target {
#(#fields)*
}
}
})
}
let mut wrapped_in_const = WrappedInConst::new(tokens, span);
let tokens = wrapped_in_const.inner();
{
let mut wrapped_in_const = WrappedInConst::new(tokens, span);
let tokens = wrapped_in_const.inner();
let mut enum_attrs = attrs.clone();
enum_attrs.push(parse_quote_spanned! {span=>
#[allow(dead_code)]
});
ItemEnum {
attrs: enum_attrs,
vis: vis.clone(),
enum_token: *enum_token,
ident: 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: ty.clone().into(),
},
*comma_token,
)]),
}),
None => Fields::Unit,
},
discriminant: None,
},
)),
}
.to_tokens(tokens);
}
let mut enum_attrs = attrs.clone();
enum_attrs.push(parse_quote_spanned! {span=>
#[allow(dead_code, non_camel_case_types)]
});
ItemEnum {
attrs: enum_attrs,
vis: vis.clone(),
enum_token: *enum_token,
ident: match_variant_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::expr::Expr<#ty>
},
},
*comma_token,
)]),
}),
None => Fields::Unit,
},
discriminant: None,
},
)),
}
.to_tokens(tokens);
let self_token = Token![self](span);
for (index, ParsedVariant { ident, field, .. }) in variants.iter().enumerate() {
if let Some(ParsedVariantField { ty, .. }) = field {
quote_spanned! {span=>
#[automatically_derived]
impl #impl_generics #target #type_generics
#where_clause
{
#[allow(non_snake_case, dead_code)]
#vis fn #ident<__V: ::fayalite::expr::ToExpr<Type = #ty>>(
#self_token,
v: __V,
) -> ::fayalite::expr::Expr<Self> {
::fayalite::expr::ToExpr::to_expr(
&::fayalite::expr::ops::EnumLiteral::new_by_index(
#self_token,
#index,
::fayalite::__std::option::Option::Some(
::fayalite::expr::Expr::canonical(
::fayalite::expr::ToExpr::to_expr(&v),
),
),
),
)
}
}
}
} else {
quote_spanned! {span=>
#[automatically_derived]
impl #impl_generics #target #type_generics
#where_clause
{
#[allow(non_snake_case, dead_code)]
#vis fn #ident(#self_token) -> ::fayalite::expr::Expr<Self> {
::fayalite::expr::ToExpr::to_expr(
&::fayalite::expr::ops::EnumLiteral::new_by_index(
#self_token,
#index,
::fayalite::__std::option::Option::None,
),
)
}
}
}
}
.to_tokens(tokens);
}
let variants_token = Ident::new("variants", span);
let from_canonical_body_fields = Vec::from_iter(variants.iter().enumerate().map(
|(index, ParsedVariant { ident, field, .. })| {
let ident_str = ident.to_string();
let val = if field.is_some() {
let missing_value_msg = format!("expected variant {ident} to have a field");
quote_spanned! {span=>
::fayalite::ty::Type::from_canonical(ty.expect(#missing_value_msg))
}
} else {
quote_spanned! {span=>
::fayalite::__std::assert!(ty.is_none());
}
};
quote_spanned! {span=>
#ident: {
let ::fayalite::enum_::EnumVariant {
name,
ty,
} = #variants_token[#index];
::fayalite::__std::assert_eq!(&*name, #ident_str);
#val
},
}
},
));
let variant_access_token = Ident::new("variant_access", span);
let match_active_scope_match_arms = Vec::from_iter(variants.iter().enumerate().map(
|(index, ParsedVariant { ident, field, .. })| {
if field.is_some() {
quote_spanned! {span=>
#index => #match_variant_ident::#ident(
::fayalite::expr::ToExpr::to_expr(
&::fayalite::expr::ops::VariantAccess::new_by_index(
#variant_access_token.base(),
#variant_access_token.variant_index(),
),
),
),
}
} else {
quote_spanned! {span=>
#index => #match_variant_ident::#ident,
}
}
},
));
let variants_body_variants = Vec::from_iter(variants.iter().map(
|ParsedVariant {
attrs: _,
options,
ident,
field,
}| {
let VariantOptions {} = options.body;
let ident_str = ident.to_string();
match field {
Some(ParsedVariantField { options, .. }) => {
let FieldOptions {} = options.body;
quote_spanned! {span=>
::fayalite::enum_::EnumVariant {
name: ::fayalite::intern::Intern::intern(#ident_str),
ty: ::fayalite::__std::option::Option::Some(
::fayalite::ty::Type::canonical(&#self_token.#ident),
),
},
}
}
None => quote_spanned! {span=>
::fayalite::enum_::EnumVariant {
name: ::fayalite::intern::Intern::intern(#ident_str),
ty: ::fayalite::__std::option::Option::None,
},
},
}
},
));
let variants_len = variants.len();
quote_spanned! {span=>
#[automatically_derived]
impl #impl_generics ::fayalite::ty::Type for #target #type_generics
#where_clause
{
type BaseType = ::fayalite::enum_::Enum;
type MaskType = ::fayalite::int::Bool;
type MatchVariant = #match_variant_ident #type_generics;
type MatchActiveScope = ::fayalite::module::Scope;
type MatchVariantAndInactiveScope = ::fayalite::enum_::EnumMatchVariantAndInactiveScope<Self>;
type MatchVariantsIter = ::fayalite::enum_::EnumMatchVariantsIter<Self>;
fn match_variants(
this: ::fayalite::expr::Expr<Self>,
source_location: ::fayalite::source_location::SourceLocation,
) -> <Self as ::fayalite::ty::Type>::MatchVariantsIter {
::fayalite::module::enum_match_variants_helper(this, source_location)
}
fn mask_type(&#self_token) -> <Self as ::fayalite::ty::Type>::MaskType {
::fayalite::int::Bool
}
fn canonical(&#self_token) -> ::fayalite::ty::CanonicalType {
::fayalite::ty::CanonicalType::Enum(::fayalite::enum_::Enum::new(::fayalite::enum_::EnumType::variants(#self_token)))
}
#[track_caller]
#[allow(non_snake_case)]
fn from_canonical(canonical_type: ::fayalite::ty::CanonicalType) -> Self {
let ::fayalite::ty::CanonicalType::Enum(enum_) = canonical_type else {
::fayalite::__std::panic!("expected enum");
};
let #variants_token = ::fayalite::enum_::EnumType::variants(&enum_);
::fayalite::__std::assert_eq!(#variants_token.len(), #variants_len, "enum has wrong number of variants");
Self {
#(#from_canonical_body_fields)*
}
}
fn source_location() -> ::fayalite::source_location::SourceLocation {
::fayalite::source_location::SourceLocation::caller()
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::enum_::EnumType for #target #type_generics
#where_clause
{
fn match_activate_scope(
v: <Self as ::fayalite::ty::Type>::MatchVariantAndInactiveScope,
) -> (<Self as ::fayalite::ty::Type>::MatchVariant, <Self as ::fayalite::ty::Type>::MatchActiveScope) {
let (#variant_access_token, scope) = v.activate();
(
match #variant_access_token.variant_index() {
#(#match_active_scope_match_arms)*
#variants_len.. => ::fayalite::__std::panic!("invalid variant index"),
},
scope,
)
}
fn variants(&#self_token) -> ::fayalite::intern::Interned<[::fayalite::enum_::EnumVariant]> {
::fayalite::intern::Intern::intern(&[
#(#variants_body_variants)*
][..])
}
}
}
.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) =
static_generics.split_for_impl();
let static_type_body_variants =
Vec::from_iter(variants.iter().map(|ParsedVariant { ident, field, .. }| {
if field.is_some() {
quote_spanned! {span=>
#ident: ::fayalite::ty::StaticType::TYPE,
}
} else {
quote_spanned! {span=>
#ident: (),
}
}
}));
let type_properties = format_ident!("__type_properties", span = span);
let type_properties_variants =
Vec::from_iter(variants.iter().map(|ParsedVariant { field, .. }| {
let variant = if let Some(ParsedVariantField { ty, .. }) = field {
quote_spanned! {span=>
::fayalite::__std::option::Option::Some(
<#ty as ::fayalite::ty::StaticType>::TYPE_PROPERTIES,
)
}
} else {
quote_spanned! {span=>
::fayalite::__std::option::Option::None
}
};
quote_spanned! {span=>
let #type_properties = #type_properties.variant(#variant);
}
}));
quote_spanned! {span=>
#[automatically_derived]
impl #static_impl_generics ::fayalite::ty::StaticType
for #target #static_type_generics
#static_where_clause
{
const TYPE: Self = Self {
#(#static_type_body_variants)*
};
const MASK_TYPE: <Self as ::fayalite::ty::Type>::MaskType =
::fayalite::int::Bool;
const TYPE_PROPERTIES: ::fayalite::ty::TypeProperties = {
let #type_properties = ::fayalite::enum_::EnumTypePropertiesBuilder::new();
#(#type_properties_variants)*
#type_properties.finish()
};
const MASK_TYPE_PROPERTIES: ::fayalite::ty::TypeProperties =
<::fayalite::int::Bool as ::fayalite::ty::StaticType>::TYPE_PROPERTIES;
}
}
.to_tokens(tokens);
}
}
}
pub(crate) fn hdl_enum(item: ItemEnum) -> syn::Result<TokenStream> {
let item = ParsedEnum::parse(item)?;
let outline_generated = item.options.body.outline_generated;
let mut contents = item.to_token_stream();
if outline_generated.is_some() {
contents = crate::outline_generated(contents, "hdl-enum-");
}
Ok(contents)
}

View file

@ -0,0 +1,133 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
hdl_type_common::{
get_target, ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType,
TypesParser,
},
kw, Errors, HdlAttr,
};
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{parse_quote_spanned, Attribute, Generics, Ident, ItemType, Token, Type, Visibility};
#[derive(Clone, Debug)]
pub(crate) struct ParsedTypeAlias {
pub(crate) attrs: Vec<Attribute>,
pub(crate) options: HdlAttr<ItemOptions, kw::hdl>,
pub(crate) vis: Visibility,
pub(crate) type_token: Token![type],
pub(crate) ident: Ident,
pub(crate) generics: MaybeParsed<ParsedGenerics, Generics>,
pub(crate) eq_token: Token![=],
pub(crate) ty: MaybeParsed<ParsedType, Type>,
pub(crate) semi_token: Token![;],
}
impl ParsedTypeAlias {
fn parse(item: ItemType) -> syn::Result<Self> {
let ItemType {
mut attrs,
vis,
type_token,
ident,
mut generics,
eq_token,
ty,
semi_token,
} = item;
let mut errors = Errors::new();
let mut options = errors
.unwrap_or_default(HdlAttr::<ItemOptions, kw::hdl>::parse_and_take_attr(
&mut attrs,
))
.unwrap_or_default();
errors.ok(options.body.validate());
let ItemOptions {
outline_generated: _,
target: _,
custom_bounds,
no_static,
no_runtime_generics: _,
} = options.body;
if let Some((no_static,)) = no_static {
errors.error(no_static, "no_static 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)) {
MaybeParsed::Parsed(generics)
} else {
MaybeParsed::Unrecognized(generics)
};
let ty = TypesParser::maybe_run(generics.as_ref(), *ty, &mut errors);
errors.finish()?;
Ok(Self {
attrs,
options,
vis,
type_token,
ident,
generics,
eq_token,
ty,
semi_token,
})
}
}
impl ToTokens for ParsedTypeAlias {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
attrs,
options,
vis,
type_token,
ident,
generics,
eq_token,
ty,
semi_token,
} = self;
let ItemOptions {
outline_generated: _,
target,
custom_bounds: _,
no_static: _,
no_runtime_generics,
} = &options.body;
let target = get_target(target, ident);
let mut type_attrs = attrs.clone();
type_attrs.push(parse_quote_spanned! {ident.span()=>
#[allow(type_alias_bounds)]
});
ItemType {
attrs: type_attrs,
vis: vis.clone(),
type_token: *type_token,
ident: ident.clone(),
generics: generics.into(),
eq_token: *eq_token,
ty: Box::new(ty.clone().into()),
semi_token: *semi_token,
}
.to_tokens(tokens);
if let (MaybeParsed::Parsed(generics), MaybeParsed::Parsed(ty), None) =
(generics, ty, no_runtime_generics)
{
generics.make_runtime_generics(tokens, vis, ident, &target, |context| {
ty.make_hdl_type_expr(context)
})
}
}
}
pub(crate) fn hdl_type_alias_impl(item: ItemType) -> syn::Result<TokenStream> {
let item = ParsedTypeAlias::parse(item)?;
let outline_generated = item.options.body.outline_generated;
let mut contents = item.to_token_stream();
if outline_generated.is_some() {
contents = crate::outline_generated(contents, "hdl-type-alias-");
}
Ok(contents)
}

File diff suppressed because it is too large Load diff

View file

@ -9,19 +9,33 @@ use syn::{
parse::{Parse, ParseStream, Parser},
parse_quote,
punctuated::Pair,
AttrStyle, Attribute, Error, Item, Token,
spanned::Spanned,
AttrStyle, Attribute, Error, Item, ItemFn, Token,
};
mod fold;
mod hdl_bundle;
mod hdl_enum;
mod hdl_type_alias;
mod hdl_type_common;
mod module;
mod value_derive_common;
mod value_derive_enum;
mod value_derive_struct;
pub(crate) trait CustomToken:
Copy
+ Spanned
+ ToTokens
+ std::fmt::Debug
+ Eq
+ std::hash::Hash
+ Default
+ quote::IdentFragment
+ Parse
{
const IDENT_STR: &'static str;
}
mod kw {
pub(crate) use syn::token::{
Enum as enum_, Extern as extern_, Static as static_, Struct as struct_, Where as where_,
};
pub(crate) use syn::token::Extern as extern_;
macro_rules! custom_keyword {
($kw:ident) => {
@ -38,25 +52,33 @@ mod kw {
}
crate::fold::no_op_fold!($kw);
impl crate::CustomToken for $kw {
const IDENT_STR: &'static str = stringify!($kw);
}
};
}
custom_keyword!(clock_domain);
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!(instance);
custom_keyword!(m);
custom_keyword!(memory);
custom_keyword!(memory_array);
custom_keyword!(memory_with_init);
custom_keyword!(no_reset);
custom_keyword!(no_runtime_generics);
custom_keyword!(no_static);
custom_keyword!(outline_generated);
custom_keyword!(output);
custom_keyword!(reg_builder);
custom_keyword!(reset);
custom_keyword!(reset_default);
custom_keyword!(skip);
custom_keyword!(target);
custom_keyword!(wire);
@ -65,34 +87,34 @@ mod kw {
type Pound = Token![#]; // work around https://github.com/rust-lang/rust/issues/50676
#[derive(Clone, Debug)]
pub(crate) struct HdlAttr<T> {
pub(crate) struct HdlAttr<T, KW> {
pub(crate) pound_token: Pound,
pub(crate) style: AttrStyle,
pub(crate) bracket_token: syn::token::Bracket,
pub(crate) hdl: kw::hdl,
pub(crate) kw: KW,
pub(crate) paren_token: Option<syn::token::Paren>,
pub(crate) body: T,
}
crate::fold::impl_fold! {
struct HdlAttr<T,> {
struct HdlAttr<T, KW,> {
pound_token: Pound,
style: AttrStyle,
bracket_token: syn::token::Bracket,
hdl: kw::hdl,
kw: KW,
paren_token: Option<syn::token::Paren>,
body: T,
}
}
#[allow(dead_code)]
impl<T> HdlAttr<T> {
pub(crate) fn split_body(self) -> (HdlAttr<()>, T) {
impl<T, KW> HdlAttr<T, KW> {
pub(crate) fn split_body(self) -> (HdlAttr<(), KW>, T) {
let Self {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body,
} = self;
@ -101,19 +123,19 @@ impl<T> HdlAttr<T> {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body: (),
},
body,
)
}
pub(crate) fn replace_body<T2>(self, body: T2) -> HdlAttr<T2> {
pub(crate) fn replace_body<T2>(self, body: T2) -> HdlAttr<T2, KW> {
let Self {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body: _,
} = self;
@ -121,17 +143,20 @@ impl<T> HdlAttr<T> {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body,
}
}
pub(crate) fn as_ref(&self) -> HdlAttr<&T> {
pub(crate) fn as_ref(&self) -> HdlAttr<&T, KW>
where
KW: Clone,
{
let Self {
pound_token,
style,
bracket_token,
hdl,
ref kw,
paren_token,
ref body,
} = *self;
@ -139,17 +164,20 @@ impl<T> HdlAttr<T> {
pound_token,
style,
bracket_token,
hdl,
kw: kw.clone(),
paren_token,
body,
}
}
pub(crate) fn try_map<R, E, F: FnOnce(T) -> Result<R, E>>(self, f: F) -> Result<HdlAttr<R>, E> {
pub(crate) fn try_map<R, E, F: FnOnce(T) -> Result<R, E>>(
self,
f: F,
) -> Result<HdlAttr<R, KW>, E> {
let Self {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body,
} = self;
@ -157,17 +185,17 @@ impl<T> HdlAttr<T> {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body: f(body)?,
})
}
pub(crate) fn map<R, F: FnOnce(T) -> R>(self, f: F) -> HdlAttr<R> {
pub(crate) fn map<R, F: FnOnce(T) -> R>(self, f: F) -> HdlAttr<R, KW> {
let Self {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body,
} = self;
@ -175,7 +203,7 @@ impl<T> HdlAttr<T> {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body: f(body),
}
@ -183,31 +211,32 @@ impl<T> HdlAttr<T> {
fn to_attr(&self) -> Attribute
where
T: ToTokens,
KW: ToTokens,
{
parse_quote! { #self }
}
}
impl<T: Default> Default for HdlAttr<T> {
impl<T: Default, KW: Default> Default for HdlAttr<T, KW> {
fn default() -> Self {
T::default().into()
}
}
impl<T> From<T> for HdlAttr<T> {
impl<T, KW: Default> From<T> for HdlAttr<T, KW> {
fn from(body: T) -> Self {
HdlAttr {
pound_token: Default::default(),
style: AttrStyle::Outer,
bracket_token: Default::default(),
hdl: Default::default(),
kw: Default::default(),
paren_token: Default::default(),
body,
}
}
}
impl<T: ToTokens> ToTokens for HdlAttr<T> {
impl<T: ToTokens, KW: ToTokens + Spanned> ToTokens for HdlAttr<T, KW> {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.pound_token.to_tokens(tokens);
match self.style {
@ -215,7 +244,7 @@ impl<T: ToTokens> ToTokens for HdlAttr<T> {
AttrStyle::Outer => {}
};
self.bracket_token.surround(tokens, |tokens| {
self.hdl.to_tokens(tokens);
self.kw.to_tokens(tokens);
match self.paren_token {
Some(paren_token) => {
paren_token.surround(tokens, |tokens| self.body.to_tokens(tokens))
@ -223,7 +252,7 @@ impl<T: ToTokens> ToTokens for HdlAttr<T> {
None => {
let body = self.body.to_token_stream();
if !body.is_empty() {
syn::token::Paren(self.hdl.span)
syn::token::Paren(self.kw.span())
.surround(tokens, |tokens| tokens.extend([body]));
}
}
@ -232,18 +261,24 @@ impl<T: ToTokens> ToTokens for HdlAttr<T> {
}
}
fn is_hdl_attr(attr: &Attribute) -> bool {
attr.path().is_ident("hdl")
fn is_hdl_attr<KW: CustomToken>(attr: &Attribute) -> bool {
attr.path().is_ident(KW::IDENT_STR)
}
impl<T: Parse> HdlAttr<T> {
fn parse_and_take_attr(attrs: &mut Vec<Attribute>) -> syn::Result<Option<Self>> {
impl<T: Parse, KW: Parse> HdlAttr<T, KW> {
fn parse_and_take_attr(attrs: &mut Vec<Attribute>) -> syn::Result<Option<Self>>
where
KW: ToTokens,
{
let mut retval = None;
let mut errors = Errors::new();
attrs.retain(|attr| {
if is_hdl_attr(attr) {
if let Ok(kw) = syn::parse2::<KW>(attr.path().to_token_stream()) {
if retval.is_some() {
errors.push(Error::new_spanned(attr, "more than one #[hdl] attribute"));
errors.push(Error::new_spanned(
attr,
format_args!("more than one #[{}] attribute", kw.to_token_stream()),
));
}
errors.unwrap_or_default(Self::parse_attr(attr).map(|v| retval = Some(v)));
false
@ -254,13 +289,19 @@ impl<T: Parse> HdlAttr<T> {
errors.finish()?;
Ok(retval)
}
fn parse_and_leave_attr(attrs: &[Attribute]) -> syn::Result<Option<Self>> {
fn parse_and_leave_attr(attrs: &[Attribute]) -> syn::Result<Option<Self>>
where
KW: ToTokens,
{
let mut retval = None;
let mut errors = Errors::new();
for attr in attrs {
if is_hdl_attr(attr) {
if let Ok(kw) = syn::parse2::<KW>(attr.path().to_token_stream()) {
if retval.is_some() {
errors.push(Error::new_spanned(attr, "more than one #[hdl] attribute"));
errors.push(Error::new_spanned(
attr,
format_args!("more than one #[{}] attribute", kw.to_token_stream()),
));
}
errors.unwrap_or_default(Self::parse_attr(attr).map(|v| retval = Some(v)));
}
@ -281,7 +322,7 @@ impl<T: Parse> HdlAttr<T> {
) -> syn::Result<Self> {
let bracket_content;
let bracket_token = bracketed!(bracket_content in input);
let hdl = bracket_content.parse()?;
let kw = bracket_content.parse()?;
let paren_content;
let body;
let paren_token;
@ -302,7 +343,7 @@ impl<T: Parse> HdlAttr<T> {
pound_token,
style,
bracket_token,
hdl,
kw,
paren_token,
body,
})
@ -519,6 +560,26 @@ macro_rules! impl_extra_traits_for_options {
) => {
impl Copy for $option_enum_name {}
impl PartialEq for $option_enum_name {
fn eq(&self, other: &Self) -> bool {
self.cmp(other).is_eq()
}
}
impl Eq for $option_enum_name {}
impl PartialOrd for $option_enum_name {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for $option_enum_name {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.variant().cmp(&other.variant())
}
}
impl quote::IdentFragment for $option_enum_name {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let _ = f;
@ -554,6 +615,66 @@ pub(crate) use impl_extra_traits_for_options;
macro_rules! options {
(
#[options = $options_name:ident]
$($tt:tt)*
) => {
crate::options! {
#[options = $options_name, punct = syn::Token![,], allow_duplicates = false]
$($tt)*
}
};
(
#[options = $options_name:ident, punct = $Punct:ty, allow_duplicates = true]
$(#[$($enum_meta:tt)*])*
$enum_vis:vis enum $option_enum_name:ident {
$($Variant:ident($key:ident $(, $value:ty)?),)*
}
) => {
crate::options! {
#[options = $options_name, punct = $Punct, allow_duplicates = (true)]
$(#[$($enum_meta)*])*
$enum_vis enum $option_enum_name {
$($Variant($key $(, $value)?),)*
}
}
impl Extend<$option_enum_name> for $options_name {
fn extend<T: IntoIterator<Item = $option_enum_name>>(&mut self, iter: T) {
iter.into_iter().for_each(|v| match v {
$($option_enum_name::$Variant(v) => {
self.$key = Some(v);
})*
});
}
}
impl FromIterator<$option_enum_name> for $options_name {
fn from_iter<T: IntoIterator<Item = $option_enum_name>>(iter: T) -> Self {
let mut retval = Self::default();
retval.extend(iter);
retval
}
}
impl Extend<$options_name> for $options_name {
fn extend<T: IntoIterator<Item = $options_name>>(&mut self, iter: T) {
iter.into_iter().for_each(|v| {
$(if let Some(v) = v.$key {
self.$key = Some(v);
})*
});
}
}
impl FromIterator<$options_name> for $options_name {
fn from_iter<T: IntoIterator<Item = $options_name>>(iter: T) -> Self {
let mut retval = Self::default();
retval.extend(iter);
retval
}
}
};
(
#[options = $options_name:ident, punct = $Punct:ty, allow_duplicates = $allow_duplicates:expr]
$(#[$($enum_meta:tt)*])*
$enum_vis:vis enum $option_enum_name:ident {
$($Variant:ident($key:ident $(, $value:ty)?),)*
@ -567,8 +688,11 @@ macro_rules! options {
}
#[derive(Clone, Debug, Default)]
#[allow(non_snake_case)]
$enum_vis struct $options_name {
$($enum_vis $key: Option<(crate::kw::$key, $(syn::token::Paren, $value)?)>,)*
$(
$enum_vis $key: Option<(crate::kw::$key, $(syn::token::Paren, $value)?)>,
)*
}
crate::fold::impl_fold! {
@ -577,6 +701,43 @@ macro_rules! options {
}
}
const _: () = {
#[derive(Clone, Debug)]
$enum_vis struct Iter($enum_vis $options_name);
impl IntoIterator for $options_name {
type Item = $option_enum_name;
type IntoIter = Iter;
fn into_iter(self) -> Self::IntoIter {
Iter(self)
}
}
impl Iterator for Iter {
type Item = $option_enum_name;
fn next(&mut self) -> Option<Self::Item> {
$(
if let Some(value) = self.0.$key.take() {
return Some($option_enum_name::$Variant(value));
}
)*
None
}
#[allow(unused_mut, unused_variables)]
fn fold<B, F: FnMut(B, Self::Item) -> B>(mut self, mut init: B, mut f: F) -> B {
$(
if let Some(value) = self.0.$key.take() {
init = f(init, $option_enum_name::$Variant(value));
}
)*
init
}
}
};
impl syn::parse::Parse for $options_name {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
#![allow(unused_mut, unused_variables, unreachable_code)]
@ -585,7 +746,7 @@ macro_rules! options {
let old_input = input.fork();
match input.parse::<$option_enum_name>()? {
$($option_enum_name::$Variant(v) => {
if retval.$key.replace(v).is_some() {
if retval.$key.replace(v).is_some() && !$allow_duplicates {
return Err(old_input.error(concat!("duplicate ", stringify!($key), " option")));
}
})*
@ -593,7 +754,7 @@ macro_rules! options {
if input.is_empty() {
break;
}
input.parse::<syn::Token![,]>()?;
input.parse::<$Punct>()?;
}
Ok(retval)
}
@ -602,7 +763,7 @@ macro_rules! options {
impl quote::ToTokens for $options_name {
#[allow(unused_mut, unused_variables, unused_assignments)]
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let mut separator: Option<syn::Token![,]> = None;
let mut separator: Option<$Punct> = None;
$(if let Some(v) = &self.$key {
separator.to_tokens(tokens);
separator = Some(Default::default());
@ -673,9 +834,24 @@ macro_rules! options {
}
}
}
impl $option_enum_name {
#[allow(dead_code)]
fn variant(&self) -> usize {
#[repr(usize)]
enum Variant {
$($Variant,)*
__Last, // so it doesn't complain about zero-variant enums
}
match *self {
$(Self::$Variant(..) => Variant::$Variant as usize,)*
}
}
}
};
}
use crate::hdl_type_alias::hdl_type_alias_impl;
pub(crate) use options;
pub(crate) fn outline_generated(contents: TokenStream, prefix: &str) -> TokenStream {
@ -686,6 +862,15 @@ pub(crate) fn outline_generated(contents: TokenStream, prefix: &str) -> TokenStr
.suffix(".tmp.rs")
.tempfile_in(out_dir)
.unwrap();
struct PrintOnPanic<'a>(&'a TokenStream);
impl Drop for PrintOnPanic<'_> {
fn drop(&mut self) {
if std::thread::panicking() {
println!("{}", self.0);
}
}
}
let _print_on_panic = PrintOnPanic(&contents);
let contents = prettyplease::unparse(&parse_quote! { #contents });
let hash = <sha2::Sha256 as sha2::Digest>::digest(&contents);
let hash = base16ct::HexDisplay(&hash[..5]);
@ -706,25 +891,33 @@ pub(crate) fn outline_generated(contents: TokenStream, prefix: &str) -> TokenStr
}
}
pub fn module(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
let options = syn::parse2::<module::ConfigOptions>(attr)?;
let options = HdlAttr::from(options);
let func = syn::parse2::<module::ModuleFn>(quote! { #options #item })?;
fn hdl_module_impl(item: ItemFn) -> syn::Result<TokenStream> {
let func = module::ModuleFn::parse_from_fn(item)?;
let options = func.config_options();
let mut contents = func.generate();
if options.body.outline_generated.is_some() {
if options.outline_generated.is_some() {
contents = outline_generated(contents, "module-");
}
Ok(contents)
}
pub fn value_derive(item: TokenStream) -> syn::Result<TokenStream> {
pub fn hdl_module(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
let kw = kw::hdl_module::default();
hdl_module_impl(syn::parse2(quote! { #[#kw(#attr)] #item })?)
}
pub fn hdl_attr(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
let kw = kw::hdl::default();
let item = quote! { #[#kw(#attr)] #item };
let item = syn::parse2::<Item>(item)?;
match item {
Item::Enum(item) => value_derive_enum::value_derive_enum(item),
Item::Struct(item) => value_derive_struct::value_derive_struct(item),
Item::Enum(item) => hdl_enum::hdl_enum(item),
Item::Struct(item) => hdl_bundle::hdl_bundle(item),
Item::Fn(item) => hdl_module_impl(item),
Item::Type(item) => hdl_type_alias_impl(item),
_ => Err(syn::Error::new(
Span::call_site(),
"derive(Value) can only be used on structs or enums",
"top-level #[hdl] can only be used on structs, enums, type aliases, or functions",
)),
}
}

View file

@ -1,7 +1,8 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
is_hdl_attr,
hdl_type_common::{ParsedGenerics, SplitForImpl},
kw,
module::transform_body::{HdlLet, HdlLetKindIO},
options, Errors, HdlAttr, PairsIterExt,
};
@ -9,7 +10,6 @@ use proc_macro2::TokenStream;
use quote::{format_ident, quote, quote_spanned, ToTokens};
use std::collections::HashSet;
use syn::{
parse::{Parse, ParseStream},
parse_quote,
visit::{visit_pat, Visit},
Attribute, Block, ConstParam, Error, FnArg, GenericParam, Generics, Ident, ItemFn, ItemStruct,
@ -57,26 +57,39 @@ impl Visit<'_> for CheckNameConflictsWithModuleBuilderVisitor<'_> {
}
}
fn retain_struct_attrs<F: FnMut(&Attribute) -> bool>(item: &mut ItemStruct, mut f: F) {
item.attrs.retain(&mut f);
for field in item.fields.iter_mut() {
field.attrs.retain(&mut f);
}
}
pub(crate) type ModuleIO = HdlLet<HdlLetKindIO>;
pub(crate) struct ModuleFn {
struct ModuleFnModule {
attrs: Vec<Attribute>,
config_options: HdlAttr<ConfigOptions>,
config_options: HdlAttr<ConfigOptions, kw::hdl_module>,
module_kind: ModuleKind,
vis: Visibility,
sig: Signature,
block: Box<Block>,
io: Vec<ModuleIO>,
struct_generics: Generics,
struct_generics: ParsedGenerics,
the_struct: TokenStream,
}
enum ModuleFnImpl {
Module(ModuleFnModule),
Fn {
attrs: Vec<Attribute>,
config_options: HdlAttr<ConfigOptions, kw::hdl>,
vis: Visibility,
sig: Signature,
block: Box<Block>,
},
}
options! {
pub(crate) enum HdlOrHdlModule {
Hdl(hdl),
HdlModule(hdl_module),
}
}
pub(crate) struct ModuleFn(ModuleFnImpl);
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub(crate) enum ModuleKind {
Extern,
@ -96,14 +109,25 @@ impl Visit<'_> for ContainsSkippedIdent<'_> {
}
}
impl Parse for ModuleFn {
fn parse(input: ParseStream) -> syn::Result<Self> {
impl ModuleFn {
pub(crate) fn config_options(&self) -> ConfigOptions {
let (ModuleFnImpl::Module(ModuleFnModule {
config_options: HdlAttr { body, .. },
..
})
| ModuleFnImpl::Fn {
config_options: HdlAttr { body, .. },
..
}) = &self.0;
body.clone()
}
pub(crate) fn parse_from_fn(item: ItemFn) -> syn::Result<Self> {
let ItemFn {
mut attrs,
vis,
mut sig,
block,
} = input.parse()?;
} = item;
let Signature {
ref constness,
ref asyncness,
@ -118,17 +142,33 @@ impl Parse for ModuleFn {
ref output,
} = sig;
let mut errors = Errors::new();
let config_options = errors
.unwrap_or_default(HdlAttr::parse_and_take_attr(&mut attrs))
.unwrap_or_default();
let Some(mut config_options) =
errors.unwrap_or_default(
HdlAttr::<ConfigOptions, HdlOrHdlModule>::parse_and_take_attr(&mut attrs),
)
else {
errors.error(sig.ident, "missing #[hdl] or #[hdl_module] attribute");
errors.finish()?;
unreachable!();
};
let ConfigOptions {
outline_generated: _,
extern_,
} = config_options.body;
let module_kind = match extern_ {
Some(_) => ModuleKind::Extern,
None => ModuleKind::Normal,
let module_kind = match (config_options.kw, extern_) {
(HdlOrHdlModule::Hdl(_), None) => None,
(HdlOrHdlModule::Hdl(_), Some(extern2)) => {
config_options.body.extern_ = None;
errors.error(
extern2.0,
"extern can only be used as #[hdl_module(extern)]",
);
None
}
(HdlOrHdlModule::HdlModule(_), None) => Some(ModuleKind::Normal),
(HdlOrHdlModule::HdlModule(_), Some(_)) => Some(ModuleKind::Extern),
};
if let HdlOrHdlModule::HdlModule(_) = config_options.kw {
for fn_arg in inputs {
match fn_arg {
FnArg::Receiver(_) => {
@ -156,20 +196,24 @@ impl Parse for ModuleFn {
if let Some(abi) = abi {
errors.push(syn::Error::new_spanned(abi, "extern not allowed here"));
}
}
let mut skipped_idents = HashSet::new();
let struct_generic_params = generics
.params
.pairs_mut()
.filter_map_pair_value_mut(|v| match v {
GenericParam::Lifetime(LifetimeParam { attrs, .. }) => {
errors
.unwrap_or_default(HdlAttr::<crate::kw::skip>::parse_and_take_attr(attrs));
errors.unwrap_or_default(
HdlAttr::<crate::kw::skip, kw::hdl>::parse_and_take_attr(attrs),
);
None
}
GenericParam::Type(TypeParam { attrs, ident, .. })
| GenericParam::Const(ConstParam { attrs, ident, .. }) => {
if errors
.unwrap_or_default(HdlAttr::<crate::kw::skip>::parse_and_take_attr(attrs))
.unwrap_or_default(
HdlAttr::<crate::kw::skip, kw::hdl>::parse_and_take_attr(attrs),
)
.is_some()
{
skipped_idents.insert(ident.clone());
@ -183,6 +227,7 @@ impl Parse for ModuleFn {
let struct_where_clause = generics
.where_clause
.as_mut()
.filter(|_| matches!(config_options.kw, HdlOrHdlModule::HdlModule(_)))
.map(|where_clause| WhereClause {
where_token: where_clause.where_token,
predicates: where_clause
@ -205,7 +250,8 @@ impl Parse for ModuleFn {
})
.collect(),
});
let struct_generics = Generics {
let struct_generics = if let HdlOrHdlModule::HdlModule(_) = config_options.kw {
let mut struct_generics = Generics {
lt_token: generics.lt_token,
params: struct_generic_params,
gt_token: generics.gt_token,
@ -220,34 +266,142 @@ impl Parse for ModuleFn {
"return type not allowed here",
));
}
let body_results = errors.ok(transform_body::transform_body(module_kind, block));
errors.finish()?;
let (block, io) = body_results.unwrap();
Ok(Self {
attrs,
config_options,
errors.ok(ParsedGenerics::parse(&mut struct_generics))
} else {
Some(ParsedGenerics::default())
};
let body_results = struct_generics.as_ref().and_then(|struct_generics| {
errors.ok(transform_body::transform_body(
module_kind,
block,
struct_generics,
))
});
errors.finish()?;
let struct_generics = struct_generics.unwrap();
let (block, io) = body_results.unwrap();
let config_options = match config_options {
HdlAttr {
pound_token,
style,
bracket_token,
kw: HdlOrHdlModule::Hdl((kw,)),
paren_token,
body,
} => {
debug_assert!(io.is_empty());
return Ok(Self(ModuleFnImpl::Fn {
attrs,
config_options: HdlAttr {
pound_token,
style,
bracket_token,
kw,
paren_token,
body,
},
vis,
sig,
block,
io,
struct_generics,
}));
}
HdlAttr {
pound_token,
style,
bracket_token,
kw: HdlOrHdlModule::HdlModule((kw,)),
paren_token,
body,
} => HdlAttr {
pound_token,
style,
bracket_token,
kw,
paren_token,
body,
},
};
let (_struct_impl_generics, _struct_type_generics, struct_where_clause) =
struct_generics.split_for_impl();
let struct_where_clause: Option<WhereClause> = parse_quote! { #struct_where_clause };
if let Some(struct_where_clause) = &struct_where_clause {
sig.generics
.where_clause
.get_or_insert_with(|| WhereClause {
where_token: struct_where_clause.where_token,
predicates: Default::default(),
})
.predicates
.extend(struct_where_clause.predicates.clone());
}
let fn_name = &sig.ident;
let io_flips = io
.iter()
.map(|io| match io.kind.kind {
ModuleIOKind::Input((input,)) => quote_spanned! {input.span=>
#[hdl(flip)]
},
ModuleIOKind::Output(_) => quote! {},
})
.collect::<Vec<_>>();
let io_types = io.iter().map(|io| &io.kind.ty).collect::<Vec<_>>();
let io_names = io.iter().map(|io| &io.name).collect::<Vec<_>>();
let the_struct: ItemStruct = parse_quote! {
#[allow(non_camel_case_types)]
#[hdl(no_runtime_generics, no_static)]
#vis struct #fn_name #struct_generics #struct_where_clause {
#(
#io_flips
#vis #io_names: #io_types,)*
}
};
let the_struct = crate::hdl_bundle::hdl_bundle(the_struct)?;
Ok(Self(ModuleFnImpl::Module(ModuleFnModule {
attrs,
config_options,
module_kind: module_kind.unwrap(),
vis,
sig,
block,
struct_generics,
the_struct,
})))
}
}
impl ModuleFn {
pub(crate) fn generate(self) -> TokenStream {
let Self {
let ModuleFnModule {
attrs,
config_options,
module_kind,
vis,
sig,
block,
io,
struct_generics,
} = self;
the_struct,
} = match self.0 {
ModuleFnImpl::Module(v) => v,
ModuleFnImpl::Fn {
attrs,
config_options,
vis,
sig,
block,
} => {
let ConfigOptions {
outline_generated: _,
extern_: _,
} = config_options.body;
return ItemFn {
attrs,
vis,
sig,
block,
}
.into_token_stream();
}
};
let ConfigOptions {
outline_generated: _,
extern_: _,
@ -273,19 +427,18 @@ impl ModuleFn {
});
name
}));
let module_kind_ty = match module_kind {
ModuleKind::Extern => quote! { ::fayalite::module::ExternModule },
ModuleKind::Normal => quote! { ::fayalite::module::NormalModule },
let module_kind_value = match module_kind {
ModuleKind::Extern => quote! { ::fayalite::module::ModuleKind::Extern },
ModuleKind::Normal => quote! { ::fayalite::module::ModuleKind::Normal },
};
let fn_name = &outer_sig.ident;
let (_struct_impl_generics, struct_type_generics, struct_where_clause) =
let (_struct_impl_generics, struct_type_generics, _struct_where_clause) =
struct_generics.split_for_impl();
let struct_ty = quote! {#fn_name #struct_type_generics};
body_sig.ident = parse_quote! {__body};
body_sig.inputs.insert(
0,
parse_quote! {m: &mut ::fayalite::module::ModuleBuilder<#struct_ty, #module_kind_ty>},
);
body_sig
.inputs
.insert(0, parse_quote! { m: &::fayalite::module::ModuleBuilder });
let body_fn = ItemFn {
attrs: vec![],
vis: Visibility::Inherited,
@ -294,50 +447,26 @@ impl ModuleFn {
};
outer_sig.output =
parse_quote! {-> ::fayalite::intern::Interned<::fayalite::module::Module<#struct_ty>>};
let io_flips = io
.iter()
.map(|io| match io.kind.kind {
ModuleIOKind::Input((input,)) => quote_spanned! {input.span=>
#[hdl(flip)]
},
ModuleIOKind::Output(_) => quote! {},
})
.collect::<Vec<_>>();
let io_types = io.iter().map(|io| &io.kind.ty).collect::<Vec<_>>();
let io_names = io.iter().map(|io| &io.name).collect::<Vec<_>>();
let fn_name_str = fn_name.to_string();
let (_, body_type_generics, _) = body_fn.sig.generics.split_for_impl();
let body_turbofish_type_generics = body_type_generics.as_turbofish();
let block = parse_quote! {{
#body_fn
::fayalite::module::ModuleBuilder::run(#fn_name_str, |m| __body #body_turbofish_type_generics(m, #(#param_names,)*))
}};
let static_type = io.iter().all(|io| io.kind.ty_expr.is_none());
let struct_options = if static_type {
quote! { #[hdl(static)] }
let body_lambda = if param_names.is_empty() {
quote! {
__body #body_turbofish_type_generics
}
} else {
quote! {}
};
let the_struct: ItemStruct = parse_quote! {
#[derive(::fayalite::__std::clone::Clone,
::fayalite::__std::hash::Hash,
::fayalite::__std::cmp::PartialEq,
::fayalite::__std::cmp::Eq,
::fayalite::__std::fmt::Debug)]
#[allow(non_camel_case_types)]
#struct_options
#vis struct #fn_name #struct_generics #struct_where_clause {
#(
#io_flips
#vis #io_names: #io_types,)*
quote! {
|m| __body #body_turbofish_type_generics(m, #(#param_names,)*)
}
};
let mut struct_without_hdl_attrs = the_struct.clone();
let mut struct_without_derives = the_struct;
retain_struct_attrs(&mut struct_without_hdl_attrs, |attr| !is_hdl_attr(attr));
retain_struct_attrs(&mut struct_without_derives, |attr| {
!attr.path().is_ident("derive")
});
let block = parse_quote! {{
#body_fn
::fayalite::module::ModuleBuilder::run(
#fn_name_str,
#module_kind_value,
#body_lambda,
)
}};
let outer_fn = ItemFn {
attrs,
vis,
@ -345,10 +474,7 @@ impl ModuleFn {
block,
};
let mut retval = outer_fn.into_token_stream();
struct_without_hdl_attrs.to_tokens(&mut retval);
retval.extend(
crate::value_derive_struct::value_derive_struct(struct_without_derives).unwrap(),
);
retval.extend(the_struct);
retval
}
}

View file

@ -2,11 +2,14 @@
// See Notices.txt for copyright information
use crate::{
fold::{impl_fold, DoFold},
hdl_type_common::{
known_items, ParseFailed, ParseTypes, ParsedGenerics, ParsedType, TypesParser,
},
is_hdl_attr, kw,
module::{check_name_conflicts_with_module_builder, ModuleIO, ModuleIOKind, ModuleKind},
options, Errors, HdlAttr,
};
use num_bigint::{BigInt, Sign};
use num_bigint::BigInt;
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned, ToTokens};
use std::{borrow::Borrow, convert::Infallible};
@ -31,6 +34,7 @@ options! {
Instance(instance),
RegBuilder(reg_builder),
Wire(wire),
IncompleteWire(incomplete_wire),
Memory(memory),
MemoryArray(memory_array),
MemoryWithInit(memory_with_init),
@ -87,9 +91,9 @@ macro_rules! with_debug_clone_and_fold {
pub(crate) use with_debug_clone_and_fold;
with_debug_clone_and_fold! {
pub(crate) struct HdlLetKindIO<Kind = ModuleIOKind> {
pub(crate) struct HdlLetKindIO<Kind = ModuleIOKind, Ty = ParsedType> {
pub(crate) colon_token: Token![:],
pub(crate) ty: Box<Type>,
pub(crate) ty: Box<Ty>,
pub(crate) m: kw::m,
pub(crate) dot_token: Token![.],
pub(crate) kind: Kind,
@ -98,6 +102,32 @@ with_debug_clone_and_fold! {
}
}
impl<Kind: Clone, T: ParseTypes<I>, I> ParseTypes<HdlLetKindIO<Kind, I>> for HdlLetKindIO<Kind, T> {
fn parse_types(
input: &mut HdlLetKindIO<Kind, I>,
parser: &mut TypesParser<'_>,
) -> Result<Self, ParseFailed> {
let HdlLetKindIO {
colon_token,
ty,
m,
dot_token,
kind,
paren,
ty_expr,
} = input;
Ok(Self {
colon_token: *colon_token,
ty: ParseTypes::parse_types(ty, parser)?,
m: *m,
dot_token: *dot_token,
kind: kind.clone(),
paren: *paren,
ty_expr: ty_expr.clone(),
})
}
}
pub(crate) fn parse_single_fn_arg(input: ParseStream) -> syn::Result<Box<Expr>> {
let retval = input.parse()?;
let _: Option<Token![,]> = input.parse()?;
@ -145,17 +175,19 @@ impl<Kind: ToTokens> HdlLetKindToTokens for HdlLetKindIO<Kind> {
#[derive(Clone, Debug)]
pub(crate) struct HdlLetKindInstance {
pub(crate) m: kw::m,
pub(crate) dot_token: Token![.],
pub(crate) instance: kw::instance,
pub(crate) paren: Paren,
pub(crate) module: Box<Expr>,
}
impl ParseTypes<Self> for HdlLetKindInstance {
fn parse_types(input: &mut Self, _parser: &mut TypesParser<'_>) -> Result<Self, ParseFailed> {
Ok(input.clone())
}
}
impl_fold! {
struct HdlLetKindInstance<> {
m: kw::m,
dot_token: Token![.],
instance: kw::instance,
paren: Paren,
module: Box<Expr>,
@ -167,14 +199,10 @@ impl HdlLetKindToTokens for HdlLetKindInstance {
fn expr_to_tokens(&self, tokens: &mut TokenStream) {
let Self {
m,
dot_token,
instance,
paren,
module,
} = self;
m.to_tokens(tokens);
dot_token.to_tokens(tokens);
instance.to_tokens(tokens);
paren.surround(tokens, |tokens| module.to_tokens(tokens));
}
@ -237,11 +265,6 @@ pub(crate) enum RegBuilderReset {
paren: Paren,
init_expr: Box<Expr>,
},
ResetDefault {
dot_token: Token![.],
reset_default: kw::reset_default,
paren: Paren,
},
}
impl_fold! {
@ -258,11 +281,6 @@ impl_fold! {
paren: Paren,
init_expr: Box<Expr>,
},
ResetDefault {
dot_token: Token![.],
reset_default: kw::reset_default,
paren: Paren,
},
}
}
@ -284,11 +302,6 @@ impl Parse for RegBuilderReset {
paren: parenthesized!(paren_contents in input),
init_expr: paren_contents.call(parse_single_fn_arg)?,
}),
RegBuilderMethod::ResetDefault(reset_default) => Ok(Self::ResetDefault {
dot_token,
reset_default,
paren: parenthesized!(paren_contents in input),
}),
}
}
}
@ -316,15 +329,6 @@ impl ToTokens for RegBuilderReset {
reset.to_tokens(tokens);
paren.surround(tokens, |tokens| init_expr.to_tokens(tokens));
}
RegBuilderReset::ResetDefault {
dot_token,
reset_default,
paren,
} => {
dot_token.to_tokens(tokens);
reset_default.to_tokens(tokens);
paren.surround(tokens, |_| {});
}
}
}
}
@ -373,27 +377,27 @@ make_builder_method_enum! {
NoReset(no_reset),
#[cond = need_reset]
Reset(reset),
#[cond = need_reset]
ResetDefault(reset_default),
}
}
#[derive(Clone, Debug)]
pub(crate) struct HdlLetKindRegBuilder {
pub(crate) ty: Option<(Token![:], Box<Type>)>,
pub(crate) m: kw::m,
pub(crate) dot_token: Token![.],
pub(crate) reg_builder: kw::reg_builder,
pub(crate) reg_builder_paren: Paren,
pub(crate) clock_domain: Option<RegBuilderClockDomain>,
pub(crate) reset: RegBuilderReset,
}
impl ParseTypes<Self> for HdlLetKindRegBuilder {
fn parse_types(input: &mut Self, _parser: &mut TypesParser<'_>) -> Result<Self, ParseFailed> {
Ok(input.clone())
}
}
impl_fold! {
struct HdlLetKindRegBuilder<> {
ty: Option<(Token![:], Box<Type>)>,
m: kw::m,
dot_token: Token![.],
reg_builder: kw::reg_builder,
reg_builder_paren: Paren,
clock_domain: Option<RegBuilderClockDomain>,
@ -406,32 +410,26 @@ impl HdlLetKindRegBuilder {
input: ParseStream,
parsed_ty: Option<(Token![:], Box<Type>)>,
_after_ty: Token![=],
m: kw::m,
dot_token: Token![.],
m_dot: Option<(kw::m, Token![.])>,
reg_builder: kw::reg_builder,
) -> syn::Result<Self> {
check_empty_m_dot(m_dot, reg_builder)?;
let _reg_builder_paren_inner;
let reg_builder_paren = parenthesized!(_reg_builder_paren_inner in input);
let mut clock_domain = None;
match RegBuilderMethod::parse_dot_prefixed(&input.fork(), true, true)?.1 {
RegBuilderMethod::ClockDomain(_) => clock_domain = Some(input.parse()?),
RegBuilderMethod::NoReset(_)
| RegBuilderMethod::Reset(_)
| RegBuilderMethod::ResetDefault(_) => {}
RegBuilderMethod::NoReset(_) | RegBuilderMethod::Reset(_) => {}
}
let reset = input.parse()?;
if clock_domain.is_none() {
match RegBuilderMethod::parse_dot_prefixed(&input.fork(), true, false)?.1 {
RegBuilderMethod::ClockDomain(_) => clock_domain = Some(input.parse()?),
RegBuilderMethod::NoReset(_)
| RegBuilderMethod::Reset(_)
| RegBuilderMethod::ResetDefault(_) => unreachable!(),
RegBuilderMethod::NoReset(_) | RegBuilderMethod::Reset(_) => unreachable!(),
}
}
Ok(Self {
ty: parsed_ty,
m,
dot_token,
reg_builder,
reg_builder_paren,
clock_domain,
@ -451,15 +449,11 @@ impl HdlLetKindToTokens for HdlLetKindRegBuilder {
fn expr_to_tokens(&self, tokens: &mut TokenStream) {
let Self {
ty: _,
m,
dot_token,
reg_builder,
reg_builder_paren,
clock_domain,
reset,
} = self;
m.to_tokens(tokens);
dot_token.to_tokens(tokens);
reg_builder.to_tokens(tokens);
reg_builder_paren.surround(tokens, |_tokens| {});
clock_domain.to_tokens(tokens);
@ -470,18 +464,20 @@ impl HdlLetKindToTokens for HdlLetKindRegBuilder {
#[derive(Clone, Debug)]
pub(crate) struct HdlLetKindWire {
pub(crate) ty: Option<(Token![:], Box<Type>)>,
pub(crate) m: kw::m,
pub(crate) dot_token: Token![.],
pub(crate) wire: kw::wire,
pub(crate) paren: Paren,
pub(crate) ty_expr: Option<Box<Expr>>,
}
impl ParseTypes<Self> for HdlLetKindWire {
fn parse_types(input: &mut Self, _parser: &mut TypesParser<'_>) -> Result<Self, ParseFailed> {
Ok(input.clone())
}
}
impl_fold! {
struct HdlLetKindWire<> {
ty: Option<(Token![:], Box<Type>)>,
m: kw::m,
dot_token: Token![.],
wire: kw::wire,
paren: Paren,
ty_expr: Option<Box<Expr>>,
@ -499,19 +495,50 @@ impl HdlLetKindToTokens for HdlLetKindWire {
fn expr_to_tokens(&self, tokens: &mut TokenStream) {
let Self {
ty: _,
m,
dot_token,
wire,
paren,
ty_expr,
} = self;
m.to_tokens(tokens);
dot_token.to_tokens(tokens);
wire.to_tokens(tokens);
paren.surround(tokens, |tokens| ty_expr.to_tokens(tokens));
}
}
options! {
pub(crate) enum LetFnKindIncomplete {
IncompleteWire(incomplete_wire),
}
}
#[derive(Clone, Debug)]
pub(crate) struct HdlLetKindIncomplete {
pub(crate) kind: LetFnKindIncomplete,
pub(crate) paren: Paren,
}
impl ParseTypes<Self> for HdlLetKindIncomplete {
fn parse_types(input: &mut Self, _parser: &mut TypesParser<'_>) -> Result<Self, ParseFailed> {
Ok(input.clone())
}
}
impl_fold! {
struct HdlLetKindIncomplete<> {
kind: LetFnKindIncomplete,
paren: Paren,
}
}
impl HdlLetKindToTokens for HdlLetKindIncomplete {
fn ty_to_tokens(&self, _tokens: &mut TokenStream) {}
fn expr_to_tokens(&self, tokens: &mut TokenStream) {
let Self { kind, paren } = self;
kind.to_tokens(tokens);
paren.surround(tokens, |_| {});
}
}
options! {
pub(crate) enum MemoryFnName {
Memory(memory),
@ -627,16 +654,18 @@ impl ToTokens for MemoryFn {
#[derive(Clone, Debug)]
pub(crate) struct HdlLetKindMemory {
pub(crate) ty: Option<(Token![:], Box<Type>)>,
pub(crate) m: kw::m,
pub(crate) dot_token: Token![.],
pub(crate) memory_fn: MemoryFn,
}
impl ParseTypes<Self> for HdlLetKindMemory {
fn parse_types(input: &mut Self, _parser: &mut TypesParser<'_>) -> Result<Self, ParseFailed> {
Ok(input.clone())
}
}
impl_fold! {
struct HdlLetKindMemory<> {
ty: Option<(Token![:], Box<Type>)>,
m: kw::m,
dot_token: Token![.],
memory_fn: MemoryFn,
}
}
@ -650,14 +679,7 @@ impl HdlLetKindToTokens for HdlLetKindMemory {
}
fn expr_to_tokens(&self, tokens: &mut TokenStream) {
let Self {
ty: _,
m,
dot_token,
memory_fn,
} = self;
m.to_tokens(tokens);
dot_token.to_tokens(tokens);
let Self { ty: _, memory_fn } = self;
memory_fn.to_tokens(tokens);
}
}
@ -667,22 +689,21 @@ impl HdlLetKindMemory {
input: ParseStream,
parsed_ty: Option<(Token![:], Box<Type>)>,
_after_ty: Token![=],
m: kw::m,
dot_token: Token![.],
m_dot: Option<(kw::m, Token![.])>,
memory_fn_name: MemoryFnName,
) -> syn::Result<Self> {
check_empty_m_dot(m_dot, memory_fn_name)?;
Ok(Self {
ty: parsed_ty,
m,
dot_token,
memory_fn: MemoryFn::parse_rest(input, memory_fn_name)?,
})
}
}
#[derive(Clone, Debug)]
pub(crate) enum HdlLetKind {
IO(HdlLetKindIO),
pub(crate) enum HdlLetKind<IOType = ParsedType> {
IO(HdlLetKindIO<ModuleIOKind, IOType>),
Incomplete(HdlLetKindIncomplete),
Instance(HdlLetKindInstance),
RegBuilder(HdlLetKindRegBuilder),
Wire(HdlLetKindWire),
@ -690,8 +711,9 @@ pub(crate) enum HdlLetKind {
}
impl_fold! {
enum HdlLetKind<> {
IO(HdlLetKindIO),
enum HdlLetKind<IOType,> {
IO(HdlLetKindIO<ModuleIOKind, IOType>),
Incomplete(HdlLetKindIncomplete),
Instance(HdlLetKindInstance),
RegBuilder(HdlLetKindRegBuilder),
Wire(HdlLetKindWire),
@ -699,6 +721,30 @@ impl_fold! {
}
}
impl<T: ParseTypes<I>, I> ParseTypes<HdlLetKind<I>> for HdlLetKind<T> {
fn parse_types(
input: &mut HdlLetKind<I>,
parser: &mut TypesParser<'_>,
) -> Result<Self, ParseFailed> {
match input {
HdlLetKind::IO(input) => ParseTypes::parse_types(input, parser).map(HdlLetKind::IO),
HdlLetKind::Incomplete(input) => {
ParseTypes::parse_types(input, parser).map(HdlLetKind::Incomplete)
}
HdlLetKind::Instance(input) => {
ParseTypes::parse_types(input, parser).map(HdlLetKind::Instance)
}
HdlLetKind::RegBuilder(input) => {
ParseTypes::parse_types(input, parser).map(HdlLetKind::RegBuilder)
}
HdlLetKind::Wire(input) => ParseTypes::parse_types(input, parser).map(HdlLetKind::Wire),
HdlLetKind::Memory(input) => {
ParseTypes::parse_types(input, parser).map(HdlLetKind::Memory)
}
}
}
}
fn parsed_ty_or_err(
parsed_ty: Option<(Token![:], Box<Type>)>,
after_ty: Token![=],
@ -710,15 +756,15 @@ fn parsed_ty_or_err(
}
}
impl HdlLetKindIO {
impl HdlLetKindIO<ModuleIOKind, Type> {
fn rest_of_parse(
input: ParseStream,
parsed_ty: Option<(Token![:], Box<Type>)>,
after_ty: Token![=],
m: kw::m,
dot_token: Token![.],
m_dot: Option<(kw::m, Token![.])>,
kind: ModuleIOKind,
) -> syn::Result<Self> {
let (m, dot_token) = unwrap_m_dot(m_dot, kind)?;
let (colon_token, ty) = parsed_ty_or_err(parsed_ty, after_ty)?;
let paren_contents;
Ok(Self {
@ -733,7 +779,36 @@ impl HdlLetKindIO {
}
}
impl HdlLetKindParse for HdlLetKind {
fn check_empty_m_dot(m_dot: Option<(kw::m, Token![.])>, kind: impl ToTokens) -> syn::Result<()> {
if let Some((m, dot_token)) = m_dot {
Err(Error::new_spanned(
quote! {#m #dot_token #kind},
format_args!(
"{} is a free function, not a method of ModuleBuilder: try removing the `m.`",
kind.to_token_stream()
),
))
} else {
Ok(())
}
}
fn unwrap_m_dot(
m_dot: Option<(kw::m, Token![.])>,
kind: impl ToTokens,
) -> syn::Result<(kw::m, Token![.])> {
m_dot.ok_or_else(|| {
Error::new_spanned(
&kind,
format_args!(
"{} is a ModuleBuilder method, not a free function: try prefixing it with `m.`",
kind.to_token_stream()
),
)
})
}
impl HdlLetKindParse for HdlLetKind<Type> {
type ParsedTy = Option<(Token![:], Box<Type>)>;
fn parse_ty(input: ParseStream) -> syn::Result<Self::ParsedTy> {
@ -753,16 +828,20 @@ impl HdlLetKindParse for HdlLetKind {
after_ty: Token![=],
input: ParseStream,
) -> syn::Result<Self> {
let m_dot = if input.peek(kw::m) && input.peek2(Token![.]) {
let m = input.parse()?;
let dot_token = input.parse()?;
Some((m, dot_token))
} else {
None
};
let kind: LetFnKind = input.parse()?;
match kind {
LetFnKind::Input(input_token) => HdlLetKindIO::rest_of_parse(
input,
parsed_ty,
after_ty,
m,
dot_token,
m_dot,
ModuleIOKind::Input(input_token),
)
.map(Self::IO),
@ -770,8 +849,7 @@ impl HdlLetKindParse for HdlLetKind {
input,
parsed_ty,
after_ty,
m,
dot_token,
m_dot,
ModuleIOKind::Output(output),
)
.map(Self::IO),
@ -782,41 +860,47 @@ impl HdlLetKindParse for HdlLetKind {
"type annotation not allowed for instance",
));
}
check_empty_m_dot(m_dot, kind)?;
let paren_contents;
Ok(Self::Instance(HdlLetKindInstance {
m,
dot_token,
instance,
paren: parenthesized!(paren_contents in input),
module: paren_contents.call(parse_single_fn_arg)?,
}))
}
LetFnKind::RegBuilder((reg_builder,)) => HdlLetKindRegBuilder::rest_of_parse(
input,
parsed_ty,
after_ty,
m,
dot_token,
reg_builder,
)
.map(Self::RegBuilder),
LetFnKind::RegBuilder((reg_builder,)) => {
HdlLetKindRegBuilder::rest_of_parse(input, parsed_ty, after_ty, m_dot, reg_builder)
.map(Self::RegBuilder)
}
LetFnKind::Wire((wire,)) => {
check_empty_m_dot(m_dot, wire)?;
let paren_contents;
Ok(Self::Wire(HdlLetKindWire {
ty: parsed_ty,
m,
dot_token,
wire,
paren: parenthesized!(paren_contents in input),
ty_expr: paren_contents.call(parse_optional_fn_arg)?,
}))
}
LetFnKind::IncompleteWire(incomplete_wire) => {
if let Some(parsed_ty) = parsed_ty {
return Err(Error::new_spanned(
parsed_ty.1,
"type annotation not allowed for incomplete_wire",
));
}
check_empty_m_dot(m_dot, kind)?;
let _paren_contents;
Ok(Self::Incomplete(HdlLetKindIncomplete {
kind: LetFnKindIncomplete::IncompleteWire(incomplete_wire),
paren: parenthesized!(_paren_contents in input),
}))
}
LetFnKind::Memory(fn_name) => HdlLetKindMemory::rest_of_parse(
input,
parsed_ty,
after_ty,
m,
dot_token,
m_dot,
MemoryFnName::Memory(fn_name),
)
.map(Self::Memory),
@ -824,8 +908,7 @@ impl HdlLetKindParse for HdlLetKind {
input,
parsed_ty,
after_ty,
m,
dot_token,
m_dot,
MemoryFnName::MemoryArray(fn_name),
)
.map(Self::Memory),
@ -833,8 +916,7 @@ impl HdlLetKindParse for HdlLetKind {
input,
parsed_ty,
after_ty,
m,
dot_token,
m_dot,
MemoryFnName::MemoryWithInit(fn_name),
)
.map(Self::Memory),
@ -846,6 +928,7 @@ impl HdlLetKindToTokens for HdlLetKind {
fn ty_to_tokens(&self, tokens: &mut TokenStream) {
match self {
HdlLetKind::IO(v) => v.ty_to_tokens(tokens),
HdlLetKind::Incomplete(v) => v.ty_to_tokens(tokens),
HdlLetKind::Instance(v) => v.ty_to_tokens(tokens),
HdlLetKind::RegBuilder(v) => v.ty_to_tokens(tokens),
HdlLetKind::Wire(v) => v.ty_to_tokens(tokens),
@ -856,6 +939,7 @@ impl HdlLetKindToTokens for HdlLetKind {
fn expr_to_tokens(&self, tokens: &mut TokenStream) {
match self {
HdlLetKind::IO(v) => v.expr_to_tokens(tokens),
HdlLetKind::Incomplete(v) => v.expr_to_tokens(tokens),
HdlLetKind::Instance(v) => v.expr_to_tokens(tokens),
HdlLetKind::RegBuilder(v) => v.expr_to_tokens(tokens),
HdlLetKind::Wire(v) => v.expr_to_tokens(tokens),
@ -868,7 +952,7 @@ with_debug_clone_and_fold! {
#[allow(dead_code)]
pub(crate) struct HdlLet<Kind = HdlLetKind> {
pub(crate) attrs: Vec<Attribute>,
pub(crate) hdl_attr: HdlAttr<Nothing>,
pub(crate) hdl_attr: HdlAttr<Nothing, kw::hdl>,
pub(crate) let_token: Token![let],
pub(crate) mut_token: Option<Token![mut]>,
pub(crate) name: Ident,
@ -878,6 +962,34 @@ with_debug_clone_and_fold! {
}
}
impl<T: ParseTypes<I>, I> ParseTypes<HdlLet<I>> for HdlLet<T> {
fn parse_types(
input: &mut HdlLet<I>,
parser: &mut TypesParser<'_>,
) -> Result<Self, ParseFailed> {
let HdlLet {
attrs,
hdl_attr,
let_token,
mut_token,
name,
eq_token,
kind,
semi_token,
} = input;
Ok(Self {
attrs: attrs.clone(),
hdl_attr: hdl_attr.clone(),
let_token: *let_token,
mut_token: *mut_token,
name: name.clone(),
eq_token: *eq_token,
kind: T::parse_types(kind, parser)?,
semi_token: *semi_token,
})
}
}
impl<Kind> HdlLet<Kind> {
pub(crate) fn try_map<Kind2, E>(
self,
@ -1006,7 +1118,7 @@ fn wrap_ty_with_expr(ty: impl ToTokens) -> Type {
fn unwrap_or_static_type(expr: Option<impl ToTokens>, span: Span) -> TokenStream {
expr.map(ToTokens::into_token_stream).unwrap_or_else(|| {
quote_spanned! {span=>
::fayalite::ty::StaticType::static_type()
::fayalite::ty::StaticType::TYPE
}
})
}
@ -1026,30 +1138,42 @@ impl<T: ToString> ToTokens for ImplicitName<T> {
}
}
struct Visitor {
module_kind: ModuleKind,
struct Visitor<'a> {
module_kind: Option<ModuleKind>,
errors: Errors,
io: Vec<ModuleIO>,
block_depth: usize,
parsed_generics: &'a ParsedGenerics,
}
impl Visitor {
fn take_hdl_attr<T: Parse>(&mut self, attrs: &mut Vec<Attribute>) -> Option<HdlAttr<T>> {
impl Visitor<'_> {
fn take_hdl_attr<T: Parse>(
&mut self,
attrs: &mut Vec<Attribute>,
) -> Option<HdlAttr<T, kw::hdl>> {
self.errors.unwrap_or(
HdlAttr::parse_and_take_attr(attrs),
Some(syn::parse2::<T>(quote! {}).unwrap().into()),
)
}
fn require_normal_module(&mut self, spanned: impl ToTokens) {
fn require_normal_module_or_fn(&mut self, spanned: impl ToTokens) {
match self.module_kind {
ModuleKind::Extern => {
Some(ModuleKind::Extern) => {
self.errors
.error(spanned, "not allowed in #[hdl_module(extern)]");
}
ModuleKind::Normal => {}
Some(ModuleKind::Normal) | None => {}
}
}
fn process_hdl_if(&mut self, hdl_attr: HdlAttr<Nothing>, expr_if: ExprIf) -> Expr {
fn require_module(&mut self, spanned: impl ToTokens) {
match self.module_kind {
None => {
self.errors.error(spanned, "not allowed in #[hdl] fn");
}
Some(_) => {}
}
}
fn process_hdl_if(&mut self, hdl_attr: HdlAttr<Nothing, kw::hdl>, expr_if: ExprIf) -> Expr {
let ExprIf {
attrs,
if_token,
@ -1057,7 +1181,7 @@ impl Visitor {
then_branch,
else_branch,
} = expr_if;
self.require_normal_module(if_token);
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,
@ -1086,7 +1210,7 @@ impl Visitor {
parse_quote_spanned! {if_token.span=>
#(#attrs)*
{
let mut __scope = m.if_(#cond);
let mut __scope = ::fayalite::module::if_(#cond);
let _: () = #then_branch;
let mut __scope = __scope.else_();
let _: () = #else_expr;
@ -1096,7 +1220,7 @@ impl Visitor {
parse_quote_spanned! {if_token.span=>
#(#attrs)*
{
let mut __scope = m.if_(#cond);
let mut __scope = ::fayalite::module::if_(#cond);
let _: () = #then_branch;
}
}
@ -1122,11 +1246,12 @@ impl Visitor {
.to_tokens(expr);
});
let mut attrs = hdl_let.attrs.clone();
self.require_module(kind);
match self.module_kind {
ModuleKind::Extern => attrs.push(parse_quote_spanned! {hdl_let.let_token.span=>
Some(ModuleKind::Extern) => attrs.push(parse_quote_spanned! {hdl_let.let_token.span=>
#[allow(unused_variables)]
}),
ModuleKind::Normal => {}
Some(ModuleKind::Normal) | None => {}
}
let let_stmt = Local {
attrs,
@ -1157,16 +1282,14 @@ impl Visitor {
eq_token,
kind:
HdlLetKindInstance {
m,
dot_token,
instance,
paren,
module,
},
semi_token,
} = hdl_let;
self.require_normal_module(instance);
let mut expr = quote! {#m #dot_token #instance};
self.require_normal_module_or_fn(instance);
let mut expr = instance.to_token_stream();
paren.surround(&mut expr, |expr| {
let name_str = ImplicitName {
name: &name,
@ -1191,11 +1314,9 @@ impl Visitor {
}
fn process_hdl_let_reg_builder(&mut self, hdl_let: HdlLet<HdlLetKindRegBuilder>) -> Local {
let name = &hdl_let.name;
let m = hdl_let.kind.m;
let dot = hdl_let.kind.dot_token;
let reg_builder = hdl_let.kind.reg_builder;
self.require_normal_module(reg_builder);
let mut expr = quote! {#m #dot #reg_builder};
self.require_normal_module_or_fn(reg_builder);
let mut expr = reg_builder.to_token_stream();
hdl_let.kind.reg_builder_paren.surround(&mut expr, |expr| {
let name_str = ImplicitName {
name,
@ -1219,7 +1340,7 @@ impl Visitor {
no_reset.to_tokens(&mut expr);
paren.surround(&mut expr, |expr| ty_expr.to_tokens(expr));
}
RegBuilderReset::Reset { .. } | RegBuilderReset::ResetDefault { .. } => {
RegBuilderReset::Reset { .. } => {
hdl_let.kind.reset.to_tokens(&mut expr);
}
}
@ -1244,12 +1365,10 @@ impl Visitor {
}
fn process_hdl_let_wire(&mut self, hdl_let: HdlLet<HdlLetKindWire>) -> Local {
let name = &hdl_let.name;
let m = hdl_let.kind.m;
let dot = hdl_let.kind.dot_token;
let wire = hdl_let.kind.wire;
self.require_normal_module(wire);
self.require_normal_module_or_fn(wire);
let ty_expr = unwrap_or_static_type(hdl_let.kind.ty_expr.as_ref(), wire.span());
let mut expr = quote! {#m #dot #wire};
let mut expr = wire.to_token_stream();
hdl_let.kind.paren.surround(&mut expr, |expr| {
let name_str = ImplicitName {
name,
@ -1277,20 +1396,46 @@ impl Visitor {
semi_token: hdl_let.semi_token,
}
}
fn process_hdl_let_incomplete(&mut self, hdl_let: HdlLet<HdlLetKindIncomplete>) -> Local {
let name = &hdl_let.name;
let kind = hdl_let.kind.kind;
self.require_normal_module_or_fn(kind);
let mut expr = kind.to_token_stream();
hdl_let.kind.paren.surround(&mut expr, |expr| {
ImplicitName {
name,
span: name.span(),
}
.to_tokens(expr);
});
let mut_token = &hdl_let.mut_token;
Local {
attrs: hdl_let.attrs.clone(),
let_token: hdl_let.let_token,
pat: parse_quote! { #mut_token #name },
init: Some(LocalInit {
eq_token: hdl_let.eq_token,
expr: parse_quote! { #expr },
diverge: None,
}),
semi_token: hdl_let.semi_token,
}
}
fn process_hdl_let_memory(&mut self, hdl_let: HdlLet<HdlLetKindMemory>) -> Local {
let name = &hdl_let.name;
let m = hdl_let.kind.m;
let dot = hdl_let.kind.dot_token;
let memory_fn = hdl_let.kind.memory_fn;
let memory_fn_name = memory_fn.name();
self.require_normal_module(memory_fn_name);
let mut expr = quote! {#m #dot #memory_fn_name};
self.require_normal_module_or_fn(memory_fn_name);
let mut expr = memory_fn_name.to_token_stream();
let (paren, arg) = match memory_fn {
MemoryFn::Memory {
memory,
paren,
ty_expr,
} => (paren, unwrap_or_static_type(ty_expr.as_ref(), memory.span())),
} => (
paren,
unwrap_or_static_type(ty_expr.as_ref(), memory.span()),
),
MemoryFn::MemoryArray {
memory_array,
paren,
@ -1345,6 +1490,7 @@ impl Visitor {
}
the_match! {
IO => process_hdl_let_io,
Incomplete => process_hdl_let_incomplete,
Instance => process_hdl_let_instance,
RegBuilder => process_hdl_let_reg_builder,
Wire => process_hdl_let_wire,
@ -1377,16 +1523,17 @@ impl Visitor {
let value: BigInt = self
.errors
.ok(base10_digits.parse().map_err(|e| Error::new(span, e)))?;
let (negative, bytes) = match value.sign() {
Sign::Minus => (true, value.magnitude().to_bytes_le()),
Sign::NoSign => (false, vec![]),
Sign::Plus => (false, value.magnitude().to_bytes_le()),
let bytes = value.to_signed_bytes_le();
let path = if signed {
known_items::SInt(span).path
} else {
known_items::UInt(span).path
};
Some(parse_quote_spanned! {span=>
::fayalite::int::make_int_literal::<#signed, #width>(
#negative,
<#path<#width> as ::fayalite::int::BoolOrIntType>::le_bytes_to_expr_wrapping(
&[#(#bytes,)*],
).to_int_expr()
#width,
)
})
}
fn process_literal(&mut self, literal: ExprLit) -> Expr {
@ -1449,7 +1596,7 @@ fn empty_let() -> Local {
}
}
impl Fold for Visitor {
impl Fold for Visitor<'_> {
fn fold_item(&mut self, item: Item) -> Item {
// don't process item interiors
item
@ -1461,7 +1608,7 @@ impl Fold for Visitor {
}
fn fold_attribute(&mut self, attr: Attribute) -> Attribute {
if is_hdl_attr(&attr) {
if is_hdl_attr::<kw::hdl>(&attr) {
self.errors
.error(&attr, "#[hdl] attribute not supported here");
}
@ -1521,8 +1668,6 @@ impl Fold for Visitor {
Repeat => process_hdl_repeat,
Struct => process_hdl_struct,
Tuple => process_hdl_tuple,
Call => process_hdl_call,
Path => process_hdl_path,
}
}
}
@ -1530,17 +1675,23 @@ impl Fold for Visitor {
fn fold_local(&mut self, let_stmt: Local) -> Local {
match self
.errors
.ok(HdlAttr::<Nothing>::parse_and_leave_attr(&let_stmt.attrs))
{
.ok(HdlAttr::<Nothing, kw::hdl>::parse_and_leave_attr(
&let_stmt.attrs,
)) {
None => return empty_let(),
Some(None) => return fold_local(self, let_stmt),
Some(Some(HdlAttr { .. })) => {}
};
let hdl_let = syn::parse2::<HdlLet>(let_stmt.into_token_stream());
let hdl_let = syn::parse2::<HdlLet<HdlLetKind<Type>>>(let_stmt.into_token_stream());
let Some(hdl_let) = self.errors.ok(hdl_let) else {
return empty_let();
};
let hdl_let = hdl_let.do_fold(self);
let mut hdl_let = hdl_let.do_fold(self);
let Ok(hdl_let) =
TypesParser::run_with_errors(self.parsed_generics, &mut hdl_let, &mut self.errors)
else {
return empty_let();
};
self.process_hdl_let(hdl_let)
}
@ -1561,14 +1712,16 @@ impl Fold for Visitor {
}
pub(crate) fn transform_body(
module_kind: ModuleKind,
module_kind: Option<ModuleKind>,
mut body: Box<Block>,
parsed_generics: &ParsedGenerics,
) -> syn::Result<(Box<Block>, Vec<ModuleIO>)> {
let mut visitor = Visitor {
module_kind,
errors: Errors::new(),
io: vec![],
block_depth: 0,
parsed_generics,
};
*body = syn::fold::fold_block(&mut visitor, *body);
visitor.errors.finish()?;

View file

@ -1,342 +1,19 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{module::transform_body::Visitor, options, Errors, HdlAttr, PairsIterExt};
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote_spanned, ToTokens, TokenStreamExt};
use crate::{kw, module::transform_body::Visitor, HdlAttr};
use quote::{format_ident, quote_spanned};
use syn::{
parse::Nothing,
parse_quote, parse_quote_spanned,
punctuated::{Pair, Punctuated},
spanned::Spanned,
token::{Brace, Paren},
Attribute, Expr, ExprArray, ExprCall, ExprGroup, ExprPath, ExprRepeat, ExprStruct, ExprTuple,
FieldValue, Ident, Index, Member, Path, PathArguments, PathSegment, Token, TypePath,
parse::Nothing, parse_quote, parse_quote_spanned, spanned::Spanned, Expr, ExprArray, ExprPath,
ExprRepeat, ExprStruct, ExprTuple, FieldValue, TypePath,
};
options! {
#[options = AggregateLiteralOptions]
#[no_ident_fragment]
pub(crate) enum AggregateLiteralOption {
Struct(struct_),
Enum(enum_),
}
}
#[derive(Clone, Debug)]
pub(crate) struct StructOrEnumPath {
pub(crate) ty: TypePath,
pub(crate) variant: Option<(TypePath, Ident)>,
}
#[derive(Debug, Copy, Clone)]
pub(crate) struct SingleSegmentVariant {
pub(crate) name: &'static str,
pub(crate) make_type_path: fn(Span, &PathArguments) -> Path,
}
impl StructOrEnumPath {
pub(crate) const SINGLE_SEGMENT_VARIANTS: &'static [SingleSegmentVariant] = {
fn make_option_type_path(span: Span, arguments: &PathArguments) -> Path {
let arguments = if arguments.is_none() {
quote_spanned! {span=>
<_>
}
} else {
arguments.to_token_stream()
};
parse_quote_spanned! {span=>
::fayalite::__std::option::Option #arguments
}
}
fn make_result_type_path(span: Span, arguments: &PathArguments) -> Path {
let arguments = if arguments.is_none() {
quote_spanned! {span=>
<_, _>
}
} else {
arguments.to_token_stream()
};
parse_quote_spanned! {span=>
::fayalite::__std::result::Result #arguments
}
}
&[
SingleSegmentVariant {
name: "Some",
make_type_path: make_option_type_path,
},
SingleSegmentVariant {
name: "None",
make_type_path: make_option_type_path,
},
SingleSegmentVariant {
name: "Ok",
make_type_path: make_result_type_path,
},
SingleSegmentVariant {
name: "Err",
make_type_path: make_result_type_path,
},
]
};
pub(crate) fn new(
errors: &mut Errors,
path: TypePath,
options: &AggregateLiteralOptions,
) -> Result<Self, ()> {
let Path {
leading_colon,
segments,
} = &path.path;
let qself_position = path.qself.as_ref().map(|qself| qself.position).unwrap_or(0);
let variant_name = if qself_position < segments.len() {
Some(segments.last().unwrap().ident.clone())
} else {
None
};
let enum_type = 'guess_enum_type: {
if options.enum_.is_some() {
if let Some((struct_,)) = options.struct_ {
errors.error(
struct_,
"can't specify both #[hdl(enum)] and #[hdl(struct)]",
);
}
break 'guess_enum_type Some(None);
}
if options.struct_.is_some() {
break 'guess_enum_type None;
}
if path.qself.is_none() && leading_colon.is_none() && segments.len() == 1 {
let PathSegment { ident, arguments } = &segments[0];
for &SingleSegmentVariant {
name,
make_type_path,
} in Self::SINGLE_SEGMENT_VARIANTS
{
if ident == name {
break 'guess_enum_type Some(Some(TypePath {
qself: None,
path: make_type_path(ident.span(), arguments),
}));
}
}
}
if segments.len() == qself_position + 2
&& segments[qself_position + 1].arguments.is_none()
&& (path.qself.is_some()
|| segments[qself_position].ident.to_string().as_bytes()[0]
.is_ascii_uppercase())
{
let mut ty = path.clone();
ty.path.segments.pop();
ty.path.segments.pop_punct();
break 'guess_enum_type Some(Some(ty));
}
None
};
if let Some(enum_type) = enum_type {
let ty = if let Some(enum_type) = enum_type {
enum_type
} else {
if qself_position >= segments.len() {
errors.error(path, "#[hdl]: can't figure out enum's type");
return Err(());
}
let mut ty = path.clone();
ty.path.segments.pop();
ty.path.segments.pop_punct();
ty
};
let Some(variant_name) = variant_name else {
errors.error(path, "#[hdl]: can't figure out enum's variant name");
return Err(());
};
Ok(Self {
ty,
variant: Some((path, variant_name)),
})
} else {
Ok(Self {
ty: path,
variant: None,
})
}
}
}
#[derive(Copy, Clone, Debug)]
pub(crate) enum BraceOrParen {
Brace(Brace),
Paren(Paren),
}
impl BraceOrParen {
pub(crate) fn surround(self, tokens: &mut TokenStream, f: impl FnOnce(&mut TokenStream)) {
match self {
BraceOrParen::Brace(v) => v.surround(tokens, f),
BraceOrParen::Paren(v) => v.surround(tokens, f),
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct StructOrEnumLiteralField {
pub(crate) attrs: Vec<Attribute>,
pub(crate) member: Member,
pub(crate) colon_token: Option<Token![:]>,
pub(crate) expr: Expr,
}
#[derive(Debug, Clone)]
pub(crate) struct StructOrEnumLiteral {
pub(crate) attrs: Vec<Attribute>,
pub(crate) path: TypePath,
pub(crate) brace_or_paren: BraceOrParen,
pub(crate) fields: Punctuated<StructOrEnumLiteralField, Token![,]>,
pub(crate) dot2_token: Option<Token![..]>,
pub(crate) rest: Option<Box<Expr>>,
}
impl StructOrEnumLiteral {
pub(crate) fn map_field_exprs(self, mut f: impl FnMut(Expr) -> Expr) -> Self {
self.map_fields(|mut field| {
field.expr = f(field.expr);
field
})
}
pub(crate) fn map_fields(
self,
f: impl FnMut(StructOrEnumLiteralField) -> StructOrEnumLiteralField,
) -> Self {
let Self {
attrs,
path,
brace_or_paren,
fields,
dot2_token,
rest,
} = self;
let fields = fields.into_pairs().map_pair_value(f).collect();
Self {
attrs,
path,
brace_or_paren,
fields,
dot2_token,
rest,
}
}
}
impl From<ExprStruct> for StructOrEnumLiteral {
fn from(value: ExprStruct) -> Self {
let ExprStruct {
attrs,
qself,
path,
brace_token,
fields,
dot2_token,
rest,
} = value;
Self {
attrs,
path: TypePath { qself, path },
brace_or_paren: BraceOrParen::Brace(brace_token),
fields: fields
.into_pairs()
.map_pair_value(
|FieldValue {
attrs,
member,
colon_token,
expr,
}| StructOrEnumLiteralField {
attrs,
member,
colon_token,
expr,
},
)
.collect(),
dot2_token,
rest,
}
}
}
fn expr_to_member(expr: &Expr) -> Option<Member> {
syn::parse2(expr.to_token_stream()).ok()
}
impl ToTokens for StructOrEnumLiteral {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
attrs,
path,
brace_or_paren,
fields,
dot2_token,
rest,
} = self;
tokens.append_all(attrs);
path.to_tokens(tokens);
brace_or_paren.surround(tokens, |tokens| {
match brace_or_paren {
BraceOrParen::Brace(_) => {
for (
StructOrEnumLiteralField {
attrs,
member,
mut colon_token,
expr,
},
comma,
) in fields.pairs().map(|v| v.into_tuple())
{
tokens.append_all(attrs);
if Some(member) != expr_to_member(expr).as_ref() {
colon_token = Some(<Token![:]>::default());
}
member.to_tokens(tokens);
colon_token.to_tokens(tokens);
expr.to_tokens(tokens);
comma.to_tokens(tokens);
}
}
BraceOrParen::Paren(_) => {
for (
StructOrEnumLiteralField {
attrs,
member: _,
colon_token: _,
expr,
},
comma,
) in fields.pairs().map(|v| v.into_tuple())
{
tokens.append_all(attrs);
expr.to_tokens(tokens);
comma.to_tokens(tokens);
}
}
}
if let Some(rest) = rest {
dot2_token.unwrap_or_default().to_tokens(tokens);
rest.to_tokens(tokens);
}
});
}
}
impl Visitor {
impl Visitor<'_> {
pub(crate) fn process_hdl_array(
&mut self,
hdl_attr: HdlAttr<Nothing>,
hdl_attr: HdlAttr<Nothing, kw::hdl>,
mut expr_array: ExprArray,
) -> Expr {
self.require_normal_module(hdl_attr);
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))
@ -346,195 +23,80 @@ impl Visitor {
}
pub(crate) fn process_hdl_repeat(
&mut self,
hdl_attr: HdlAttr<Nothing>,
hdl_attr: HdlAttr<Nothing, kw::hdl>,
mut expr_repeat: ExprRepeat,
) -> Expr {
self.require_normal_module(hdl_attr);
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)}
}
pub(crate) fn process_struct_enum(
pub(crate) fn process_hdl_struct(
&mut self,
hdl_attr: HdlAttr<AggregateLiteralOptions>,
mut literal: StructOrEnumLiteral,
hdl_attr: HdlAttr<Nothing, kw::hdl>,
expr_struct: ExprStruct,
) -> Expr {
let span = hdl_attr.hdl.span;
if let Some(rest) = literal.rest.take() {
self.errors
.error(rest, "#[hdl] struct functional update syntax not supported");
self.require_normal_module_or_fn(&hdl_attr);
let name_span = expr_struct.path.segments.last().unwrap().ident.span();
let builder_ident = format_ident!("__builder", span = name_span);
let empty_builder = if expr_struct.qself.is_some()
|| expr_struct
.path
.segments
.iter()
.any(|seg| !seg.arguments.is_none())
{
let ty = TypePath {
qself: expr_struct.qself,
path: expr_struct.path,
};
let builder_ty = quote_spanned! {name_span=>
<#ty as ::fayalite::bundle::BundleType>::Builder
};
quote_spanned! {name_span=>
<#builder_ty as ::fayalite::__std::default::Default>::default()
}
let mut next_var = 0usize;
let mut new_var = || -> Ident {
let retval = format_ident!("__v{}", next_var, span = span);
next_var += 1;
retval
} else {
let path = ExprPath {
attrs: vec![],
qself: expr_struct.qself,
path: expr_struct.path,
};
let infallible_var = new_var();
let retval_var = new_var();
let mut lets = vec![];
let mut build_steps = vec![];
let literal = literal.map_field_exprs(|expr| {
let field_var = new_var();
lets.push(quote_spanned! {span=>
let #field_var = ::fayalite::expr::ToExpr::to_expr(&#expr);
});
parse_quote! { #field_var }
});
let Ok(StructOrEnumPath { ty, variant }) =
StructOrEnumPath::new(&mut self.errors, literal.path.clone(), &hdl_attr.body)
else {
return parse_quote_spanned! {span=>
{}
quote_spanned! {name_span=>
#path::__bundle_builder()
}
};
};
for StructOrEnumLiteralField {
let field_calls = Vec::from_iter(expr_struct.fields.iter().map(
|FieldValue {
attrs: _,
member,
colon_token: _,
expr,
} in literal.fields.iter()
{
}| {
let field_fn = format_ident!("field_{}", member);
build_steps.push(quote_spanned! {span=>
let #retval_var = #retval_var.#field_fn(#expr);
});
quote_spanned! {member.span()=>
let #builder_ident = #builder_ident.#field_fn(#expr);
}
let check_literal = literal.map_field_exprs(|expr| {
parse_quote_spanned! {span=>
::fayalite::expr::value_from_expr_type(#expr, #infallible_var)
}
});
let make_expr_fn = if let Some((_variant_path, variant_ident)) = &variant {
let variant_fn = format_ident!("variant_{}", variant_ident);
build_steps.push(quote_spanned! {span=>
let #retval_var = #retval_var.#variant_fn();
});
quote_spanned! {span=>
::fayalite::expr::make_enum_expr
}
} else {
build_steps.push(quote_spanned! {span=>
let #retval_var = #retval_var.build();
});
quote_spanned! {span=>
::fayalite::expr::make_bundle_expr
}
};
let variant_or_type =
variant.map_or_else(|| ty.clone(), |(variant_path, _variant_ident)| variant_path);
parse_quote_spanned! {span=>
},
));
parse_quote_spanned! {name_span=>
{
#(#lets)*
#make_expr_fn::<#ty>(|#infallible_var| {
let #retval_var = #check_literal;
#[allow(unreachable_code)]
match #retval_var {
#variant_or_type { .. } => #retval_var,
#[allow(unreachable_patterns)]
_ => match #infallible_var {},
}
}, |#retval_var| {
#(#build_steps)*
#retval_var
})
let #builder_ident = #empty_builder;
#(#field_calls)*
::fayalite::expr::ToExpr::to_expr(&#builder_ident)
}
}
}
pub(crate) fn process_hdl_struct(
&mut self,
hdl_attr: HdlAttr<AggregateLiteralOptions>,
expr_struct: ExprStruct,
) -> Expr {
self.require_normal_module(&hdl_attr);
self.process_struct_enum(hdl_attr, expr_struct.into())
}
pub(crate) fn process_hdl_tuple(
&mut self,
hdl_attr: HdlAttr<Nothing>,
hdl_attr: HdlAttr<Nothing, kw::hdl>,
expr_tuple: ExprTuple,
) -> Expr {
self.require_normal_module(hdl_attr);
self.require_normal_module_or_fn(hdl_attr);
parse_quote_spanned! {expr_tuple.span()=>
::fayalite::expr::ToExpr::to_expr(&#expr_tuple)
}
}
pub(crate) fn process_hdl_path(
&mut self,
hdl_attr: HdlAttr<Nothing>,
expr_path: ExprPath,
) -> Expr {
self.require_normal_module(hdl_attr);
parse_quote_spanned! {expr_path.span()=>
::fayalite::expr::ToExpr::to_expr(&#expr_path)
}
}
pub(crate) fn process_hdl_call(
&mut self,
hdl_attr: HdlAttr<AggregateLiteralOptions>,
expr_call: ExprCall,
) -> Expr {
self.require_normal_module(&hdl_attr);
let ExprCall {
attrs: mut literal_attrs,
func,
paren_token,
args,
} = expr_call;
let mut path_expr = *func;
let path = loop {
break match path_expr {
Expr::Group(ExprGroup {
attrs,
group_token: _,
expr,
}) => {
literal_attrs.extend(attrs);
path_expr = *expr;
continue;
}
Expr::Path(ExprPath { attrs, qself, path }) => {
literal_attrs.extend(attrs);
TypePath { qself, path }
}
_ => {
self.errors.error(&path_expr, "missing tuple struct's name");
return parse_quote_spanned! {path_expr.span()=>
{}
};
}
};
};
let fields = args
.into_pairs()
.enumerate()
.map(|(index, p)| {
let (expr, comma) = p.into_tuple();
let mut index = Index::from(index);
index.span = hdl_attr.hdl.span;
Pair::new(
StructOrEnumLiteralField {
attrs: vec![],
member: Member::Unnamed(index),
colon_token: None,
expr,
},
comma,
)
})
.collect();
self.process_struct_enum(
hdl_attr,
StructOrEnumLiteral {
attrs: literal_attrs,
path,
brace_or_paren: BraceOrParen::Paren(paren_token),
fields,
dot2_token: None,
rest: None,
},
)
}
}

View file

@ -1,24 +1,22 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
fold::impl_fold,
module::transform_body::{
expand_aggregate_literals::{AggregateLiteralOptions, StructOrEnumPath},
with_debug_clone_and_fold, Visitor,
},
fold::{impl_fold, DoFold},
kw,
module::transform_body::{with_debug_clone_and_fold, Visitor},
Errors, HdlAttr, PairsIterExt,
};
use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, TokenStreamExt};
use quote::{format_ident, quote_spanned, ToTokens, TokenStreamExt};
use syn::{
fold::{fold_arm, fold_expr_match, fold_pat, Fold},
parse::Nothing,
parse_quote_spanned,
punctuated::{Pair, Punctuated},
punctuated::Punctuated,
spanned::Spanned,
token::{Brace, Paren},
Arm, Attribute, Expr, ExprMatch, FieldPat, Ident, Index, Member, Pat, PatIdent, PatOr,
PatParen, PatPath, PatRest, PatStruct, PatTupleStruct, PatWild, Path, Token, TypePath,
Arm, Attribute, Expr, ExprMatch, FieldPat, Ident, Member, Pat, PatIdent, PatOr, PatParen,
PatPath, PatRest, PatStruct, PatTupleStruct, PatWild, Path, PathSegment, Token, TypePath,
};
with_debug_clone_and_fold! {
@ -81,7 +79,7 @@ impl ToTokens for MatchPatWild {
with_debug_clone_and_fold! {
struct MatchPatStructField<> {
member: Member,
field_name: Ident,
colon_token: Option<Token![:]>,
pat: MatchPatSimple,
}
@ -90,12 +88,19 @@ with_debug_clone_and_fold! {
impl ToTokens for MatchPatStructField {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
member,
field_name,
colon_token,
pat,
} = self;
member.to_tokens(tokens);
colon_token.to_tokens(tokens);
field_name.to_tokens(tokens);
if let (None, MatchPatSimple::Binding(MatchPatBinding { ident })) = (colon_token, pat) {
if field_name == ident {
return;
}
}
colon_token
.unwrap_or_else(|| Token![:](field_name.span()))
.to_tokens(tokens);
pat.to_tokens(tokens);
}
}
@ -108,8 +113,16 @@ impl MatchPatStructField {
colon_token,
pat,
} = field_pat;
let field_name = if let Member::Named(field_name) = member {
field_name
} else {
state
.errors
.error(&member, "field name must not be a number");
format_ident!("_{}", member)
};
Ok(Self {
member,
field_name,
colon_token,
pat: MatchPatSimple::parse(state, *pat)?,
})
@ -118,7 +131,8 @@ impl MatchPatStructField {
with_debug_clone_and_fold! {
struct MatchPatStruct<> {
resolved_path: Path,
match_span: Span,
path: Path,
brace_token: Brace,
fields: Punctuated<MatchPatStructField, Token![,]>,
rest: Option<Token![..]>,
@ -128,12 +142,16 @@ with_debug_clone_and_fold! {
impl ToTokens for MatchPatStruct {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
resolved_path,
match_span,
path,
brace_token,
fields,
rest,
} = self;
resolved_path.to_tokens(tokens);
quote_spanned! {*match_span=>
__MatchTy::<#path>
}
.to_tokens(tokens);
brace_token.surround(tokens, |tokens| {
fields.to_tokens(tokens);
rest.to_tokens(tokens);
@ -141,6 +159,35 @@ impl ToTokens for MatchPatStruct {
}
}
with_debug_clone_and_fold! {
struct MatchPatEnumVariant<> {
match_span: Span,
variant_path: Path,
enum_path: Path,
variant_name: Ident,
field: Option<(Paren, MatchPatSimple)>,
}
}
impl ToTokens for MatchPatEnumVariant {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
match_span,
variant_path: _,
enum_path,
variant_name,
field,
} = self;
quote_spanned! {*match_span=>
__MatchTy::<#enum_path>::#variant_name
}
.to_tokens(tokens);
if let Some((paren_token, field)) = field {
paren_token.surround(tokens, |tokens| field.to_tokens(tokens));
}
}
}
#[derive(Debug, Clone)]
enum MatchPatSimple {
Paren(MatchPatParen<MatchPatSimple>),
@ -169,21 +216,70 @@ impl ToTokens for MatchPatSimple {
}
}
fn is_pat_ident_a_struct_or_enum_name(ident: &Ident) -> bool {
ident
.to_string()
.starts_with(|ch: char| ch.is_ascii_uppercase())
struct EnumPath {
variant_path: Path,
enum_path: Path,
variant_name: Ident,
}
fn parse_enum_path(variant_path: TypePath) -> Result<EnumPath, TypePath> {
let TypePath {
qself: None,
path: variant_path,
} = variant_path
else {
return Err(variant_path);
};
if variant_path.is_ident("HdlNone") || variant_path.is_ident("HdlSome") {
let ident = variant_path.get_ident().unwrap();
return Ok(EnumPath {
enum_path: parse_quote_spanned! {ident.span()=>
::fayalite::enum_::HdlOption::<_>
},
variant_name: ident.clone(),
variant_path,
});
}
if variant_path.segments.len() < 2 {
return Err(TypePath {
qself: None,
path: variant_path,
});
}
let mut enum_path = variant_path.clone();
let PathSegment {
ident: variant_name,
arguments,
} = enum_path.segments.pop().unwrap().into_value();
if !arguments.is_none() {
return Err(TypePath {
qself: None,
path: variant_path,
});
}
enum_path.segments.pop_punct();
Ok(EnumPath {
variant_path,
enum_path,
variant_name,
})
}
fn parse_enum_ident(ident: Ident) -> Result<EnumPath, Ident> {
parse_enum_path(TypePath {
qself: None,
path: ident.into(),
})
.map_err(|p| p.path.segments.into_iter().next().unwrap().ident)
}
trait ParseMatchPat: Sized {
fn simple(v: MatchPatSimple) -> Self;
fn or(v: MatchPatOr<Self>) -> Self;
fn paren(v: MatchPatParen<Self>) -> Self;
fn struct_(
state: &mut HdlMatchParseState<'_>,
v: MatchPatStruct,
struct_error_spanned: &dyn ToTokens,
) -> Result<Self, ()>;
fn struct_(state: &mut HdlMatchParseState<'_>, v: MatchPatStruct) -> Result<Self, ()>;
fn enum_variant(state: &mut HdlMatchParseState<'_>, v: MatchPatEnumVariant)
-> Result<Self, ()>;
fn parse(state: &mut HdlMatchParseState<'_>, pat: Pat) -> Result<Self, ()> {
match pat {
Pat::Ident(PatIdent {
@ -208,26 +304,24 @@ trait ParseMatchPat: Sized {
.errors
.error(at_token, "@ not allowed in #[hdl] patterns");
}
if is_pat_ident_a_struct_or_enum_name(&ident) {
let ident_span = ident.span();
let resolved_path = state.resolve_enum_struct_path(TypePath {
qself: None,
path: ident.clone().into(),
})?;
Self::struct_(
match parse_enum_ident(ident) {
Ok(EnumPath {
variant_path,
enum_path,
variant_name,
}) => Self::enum_variant(
state,
MatchPatStruct {
resolved_path,
brace_token: Brace(ident_span),
fields: Punctuated::new(),
rest: None,
MatchPatEnumVariant {
match_span: state.match_span,
variant_path,
enum_path,
variant_name,
field: None,
},
&ident,
)
} else {
Ok(Self::simple(MatchPatSimple::Binding(MatchPatBinding {
),
Err(ident) => Ok(Self::simple(MatchPatSimple::Binding(MatchPatBinding {
ident,
})))
}))),
}
}
Pat::Or(PatOr {
@ -254,18 +348,22 @@ trait ParseMatchPat: Sized {
qself,
path,
}) => {
let path = TypePath { qself, path };
let path_span = path.span();
let resolved_path = state.resolve_enum_struct_path(path.clone())?;
Self::struct_(
let EnumPath {
variant_path,
enum_path,
variant_name,
} = parse_enum_path(TypePath { qself, path }).map_err(|path| {
state.errors.error(path, "unsupported enum variant path");
})?;
Self::enum_variant(
state,
MatchPatStruct {
resolved_path,
brace_token: Brace(path_span),
fields: Punctuated::new(),
rest: None,
MatchPatEnumVariant {
match_span: state.match_span,
variant_path,
enum_path,
variant_name,
field: None,
},
&path,
)
}
Pat::Struct(PatStruct {
@ -282,12 +380,17 @@ trait ParseMatchPat: Sized {
MatchPatStructField::parse(state, field_pat).ok()
})
.collect();
let path = TypePath { qself, path };
let resolved_path = state.resolve_enum_struct_path(path.clone())?;
if qself.is_some() {
state
.errors
.error(TypePath { qself, path }, "unsupported struct path");
return Err(());
}
Self::struct_(
state,
MatchPatStruct {
resolved_path,
match_span: state.match_span,
path,
brace_token,
fields,
rest: rest.map(
@ -297,7 +400,6 @@ trait ParseMatchPat: Sized {
}| dot2_token,
),
},
&path,
)
}
Pat::TupleStruct(PatTupleStruct {
@ -307,45 +409,45 @@ trait ParseMatchPat: Sized {
paren_token,
mut elems,
}) => {
let rest = if let Some(&Pat::Rest(PatRest {
attrs: _,
dot2_token,
})) = elems.last()
{
elems.pop();
Some(dot2_token)
} else {
None
};
let fields = elems
.into_pairs()
.enumerate()
.filter_map(|(index, pair)| {
let (pat, punct) = pair.into_tuple();
let pat = MatchPatSimple::parse(state, pat).ok()?;
let mut index = Index::from(index);
index.span = state.span;
let field = MatchPatStructField {
member: index.into(),
colon_token: Some(Token![:](state.span)),
pat,
};
Some(Pair::new(field, punct))
let EnumPath {
variant_path,
enum_path,
variant_name,
} = parse_enum_path(TypePath { qself, path }).map_err(|path| {
state.errors.error(path, "unsupported enum variant path");
})?;
if elems.is_empty() {
let mut tokens = TokenStream::new();
paren_token.surround(&mut tokens, |_| {});
state.errors.error(
tokens,
"field-less enum variants must not be matched using parenthesis",
);
}
if elems.len() != 1 {
state.errors.error(
variant_path,
"enum variant pattern must have exactly one field",
);
return Err(());
}
let field = elems.pop().unwrap().into_value();
let field = if let Pat::Rest(rest) = field {
MatchPatSimple::Wild(MatchPatWild {
underscore_token: Token![_](rest.dot2_token.span()),
})
.collect();
let path = TypePath { qself, path };
let resolved_path = state.resolve_enum_struct_path(path.clone())?;
Self::struct_(
} else {
MatchPatSimple::parse(state, field)?
};
Self::enum_variant(
state,
MatchPatStruct {
resolved_path,
brace_token: Brace {
span: paren_token.span,
MatchPatEnumVariant {
match_span: state.match_span,
variant_path,
enum_path,
variant_name,
field: Some((paren_token, field)),
},
fields,
rest,
},
&path,
)
}
Pat::Rest(_) => {
@ -387,14 +489,21 @@ impl ParseMatchPat for MatchPatSimple {
Self::Paren(v)
}
fn struct_(
fn struct_(state: &mut HdlMatchParseState<'_>, v: MatchPatStruct) -> Result<Self, ()> {
state.errors.error(
v.path,
"matching structs is not yet implemented inside structs/enums in #[hdl] patterns",
);
Err(())
}
fn enum_variant(
state: &mut HdlMatchParseState<'_>,
_v: MatchPatStruct,
struct_error_spanned: &dyn ToTokens,
v: MatchPatEnumVariant,
) -> Result<Self, ()> {
state.errors.error(
struct_error_spanned,
"not yet implemented inside structs/enums in #[hdl] patterns",
v.variant_path,
"matching enum variants is not yet implemented inside structs/enums in #[hdl] patterns",
);
Err(())
}
@ -406,6 +515,7 @@ enum MatchPat {
Or(MatchPatOr<MatchPat>),
Paren(MatchPatParen<MatchPat>),
Struct(MatchPatStruct),
EnumVariant(MatchPatEnumVariant),
}
impl_fold! {
@ -414,6 +524,7 @@ impl_fold! {
Or(MatchPatOr<MatchPat>),
Paren(MatchPatParen<MatchPat>),
Struct(MatchPatStruct),
EnumVariant(MatchPatEnumVariant),
}
}
@ -430,13 +541,16 @@ impl ParseMatchPat for MatchPat {
Self::Paren(v)
}
fn struct_(
_state: &mut HdlMatchParseState<'_>,
v: MatchPatStruct,
_struct_error_spanned: &dyn ToTokens,
) -> Result<Self, ()> {
fn struct_(_state: &mut HdlMatchParseState<'_>, v: MatchPatStruct) -> Result<Self, ()> {
Ok(Self::Struct(v))
}
fn enum_variant(
_state: &mut HdlMatchParseState<'_>,
v: MatchPatEnumVariant,
) -> Result<Self, ()> {
Ok(Self::EnumVariant(v))
}
}
impl ToTokens for MatchPat {
@ -446,6 +560,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::EnumVariant(v) => v.to_tokens(tokens),
}
}
}
@ -511,23 +626,96 @@ impl Fold for RewriteAsCheckMatch {
i.colon_token = Some(Token![:](i.member.span()));
i
}
fn fold_pat(&mut self, i: Pat) -> Pat {
match i {
Pat::Ident(PatIdent {
fn fold_pat(&mut self, pat: Pat) -> Pat {
match pat {
Pat::Ident(mut pat_ident) => match parse_enum_ident(pat_ident.ident) {
Ok(EnumPath {
variant_path: _,
enum_path,
variant_name,
}) => parse_quote_spanned! {self.span=>
__MatchTy::<#enum_path>::#variant_name {}
},
Err(ident) => {
pat_ident.ident = ident;
Pat::Ident(self.fold_pat_ident(pat_ident))
}
},
Pat::Path(PatPath {
attrs: _,
qself,
path,
}) => match parse_enum_path(TypePath { qself, path }) {
Ok(EnumPath {
variant_path: _,
enum_path,
variant_name,
}) => parse_quote_spanned! {self.span=>
__MatchTy::<#enum_path>::#variant_name {}
},
Err(type_path) => parse_quote_spanned! {self.span=>
__MatchTy::<#type_path> {}
},
},
Pat::Struct(PatStruct {
attrs: _,
qself,
path,
brace_token,
fields,
rest,
}) => {
let type_path = TypePath { qself, path };
let path = parse_quote_spanned! {self.span=>
__MatchTy::<#type_path>
};
let fields = fields.do_fold(self);
Pat::Struct(PatStruct {
attrs: vec![],
qself: None,
path,
brace_token,
fields,
rest,
})
}
Pat::TupleStruct(PatTupleStruct {
attrs,
by_ref,
mutability,
ident,
subpat: None,
}) if is_pat_ident_a_struct_or_enum_name(&ident) => {
parse_quote_spanned! {ident.span()=>
#(#attrs)*
#by_ref
#mutability
#ident {}
qself,
path,
paren_token,
elems,
}) => match parse_enum_path(TypePath { qself, path }) {
Ok(EnumPath {
variant_path: _,
enum_path,
variant_name,
}) => {
let path = parse_quote_spanned! {self.span=>
__MatchTy::<#enum_path>::#variant_name
};
let elems = Punctuated::from_iter(
elems.into_pairs().map_pair_value(|p| fold_pat(self, p)),
);
Pat::TupleStruct(PatTupleStruct {
attrs,
qself: None,
path,
paren_token,
elems,
})
}
Err(TypePath { qself, path }) => {
Pat::TupleStruct(self.fold_pat_tuple_struct(PatTupleStruct {
attrs,
qself,
path,
paren_token,
elems,
}))
}
_ => fold_pat(self, i),
},
_ => fold_pat(self, pat),
}
}
fn fold_pat_ident(&mut self, mut i: PatIdent) -> PatIdent {
@ -555,30 +743,14 @@ impl Fold for RewriteAsCheckMatch {
}
struct HdlMatchParseState<'a> {
match_span: Span,
errors: &'a mut Errors,
span: Span,
}
impl HdlMatchParseState<'_> {
fn resolve_enum_struct_path(&mut self, path: TypePath) -> Result<Path, ()> {
let StructOrEnumPath { ty, variant } =
StructOrEnumPath::new(self.errors, path, &AggregateLiteralOptions::default())?;
Ok(if let Some((_variant_path, variant_name)) = variant {
parse_quote_spanned! {self.span=>
__MatchTy::<#ty>::#variant_name
}
} else {
parse_quote_spanned! {self.span=>
__MatchTy::<#ty>
}
})
}
}
impl Visitor {
impl Visitor<'_> {
pub(crate) fn process_hdl_match(
&mut self,
_hdl_attr: HdlAttr<Nothing>,
_hdl_attr: HdlAttr<Nothing, kw::hdl>,
expr_match: ExprMatch,
) -> Expr {
let span = expr_match.match_token.span();
@ -590,25 +762,24 @@ impl Visitor {
brace_token: _,
arms,
} = expr_match;
self.require_normal_module(match_token);
self.require_normal_module_or_fn(match_token);
let mut state = HdlMatchParseState {
match_span: span,
errors: &mut self.errors,
span,
};
let arms = Vec::from_iter(
arms.into_iter()
.filter_map(|arm| MatchArm::parse(&mut state, arm).ok()),
);
parse_quote_spanned! {span=>
let expr = quote_spanned! {span=>
{
type __MatchTy<V> =
<<V as ::fayalite::expr::ToExpr>::Type as ::fayalite::ty::Type>::MatchVariant;
type __MatchTy<T> = <T as ::fayalite::ty::Type>::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 m.match_(__match_expr) {
for __match_variant in ::fayalite::module::match_(__match_expr) {
let (__match_variant, __scope) =
::fayalite::ty::MatchVariantAndInactiveScope::match_activate_scope(
__match_variant,
@ -618,6 +789,7 @@ impl Visitor {
}
}
}
}
};
syn::parse2(expr).unwrap()
}
}

View file

@ -1,761 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{fold::impl_fold, kw, Errors, HdlAttr};
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, quote_spanned, ToTokens};
use std::collections::{BTreeMap, HashMap, HashSet};
use syn::{
fold::{fold_generics, Fold},
parse::{Parse, ParseStream},
parse_quote, parse_quote_spanned,
punctuated::Punctuated,
spanned::Spanned,
token::{Brace, Paren, Where},
Block, ConstParam, Expr, Field, Fields, FieldsNamed, FieldsUnnamed, GenericParam, Generics,
Ident, Index, ItemImpl, Lifetime, LifetimeParam, Member, Path, Token, Type, TypeParam,
TypePath, Visibility, WhereClause, WherePredicate,
};
#[derive(Clone, Debug)]
pub(crate) struct Bounds(pub(crate) Punctuated<WherePredicate, Token![,]>);
impl_fold! {
struct Bounds<>(Punctuated<WherePredicate, Token![,]>);
}
impl Parse for Bounds {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Bounds(Punctuated::parse_terminated(input)?))
}
}
impl From<Option<WhereClause>> for Bounds {
fn from(value: Option<WhereClause>) -> Self {
Self(value.map_or_else(Punctuated::new, |v| v.predicates))
}
}
impl ToTokens for Bounds {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.0.to_tokens(tokens)
}
}
#[derive(Debug, Clone)]
pub(crate) struct ParsedField<O> {
pub(crate) options: HdlAttr<O>,
pub(crate) vis: Visibility,
pub(crate) name: Member,
pub(crate) ty: Type,
}
impl<O> ParsedField<O> {
pub(crate) fn var_name(&self) -> Ident {
format_ident!("__v_{}", self.name)
}
}
pub(crate) fn get_field_name(
index: usize,
name: Option<Ident>,
ty_span: impl FnOnce() -> Span,
) -> Member {
match name {
Some(name) => Member::Named(name),
None => Member::Unnamed(Index {
index: index as _,
span: ty_span(),
}),
}
}
pub(crate) fn get_field_names(fields: &Fields) -> impl Iterator<Item = Member> + '_ {
fields
.iter()
.enumerate()
.map(|(index, field)| get_field_name(index, field.ident.clone(), || field.ty.span()))
}
impl<O: Parse + Default> ParsedField<O> {
pub(crate) fn parse_fields(
errors: &mut Errors,
fields: &mut Fields,
in_enum: bool,
) -> (FieldsKind, Vec<ParsedField<O>>) {
let mut unit_fields = Punctuated::new();
let (fields_kind, fields) = match fields {
Fields::Named(fields) => (FieldsKind::Named(fields.brace_token), &mut fields.named),
Fields::Unnamed(fields) => {
(FieldsKind::Unnamed(fields.paren_token), &mut fields.unnamed)
}
Fields::Unit => (FieldsKind::Unit, &mut unit_fields),
};
let fields = fields
.iter_mut()
.enumerate()
.map(|(index, field)| {
let options = errors
.unwrap_or_default(HdlAttr::parse_and_take_attr(&mut field.attrs))
.unwrap_or_default();
let name = get_field_name(index, field.ident.clone(), || field.ty.span());
if in_enum && !matches!(field.vis, Visibility::Inherited) {
errors.error(&field.vis, "field visibility not allowed in enums");
}
ParsedField {
options,
vis: field.vis.clone(),
name,
ty: field.ty.clone(),
}
})
.collect();
(fields_kind, fields)
}
}
#[derive(Copy, Clone, Debug)]
pub(crate) enum FieldsKind {
Unit,
Named(Brace),
Unnamed(Paren),
}
impl FieldsKind {
pub(crate) fn into_fields_named(
brace_token: Brace,
fields: impl IntoIterator<Item = syn::Field>,
) -> Fields {
Fields::Named(FieldsNamed {
brace_token,
named: Punctuated::from_iter(fields),
})
}
pub(crate) fn into_fields_unnamed(
paren_token: Paren,
fields: impl IntoIterator<Item = syn::Field>,
) -> Fields {
Fields::Unnamed(FieldsUnnamed {
paren_token,
unnamed: Punctuated::from_iter(fields),
})
}
pub(crate) fn into_fields(self, fields: impl IntoIterator<Item = syn::Field>) -> Fields {
match self {
FieldsKind::Unit => {
let mut fields = fields.into_iter().peekable();
let Some(first_field) = fields.peek() else {
return Fields::Unit;
};
if first_field.ident.is_some() {
Self::into_fields_named(Default::default(), fields)
} else {
Self::into_fields_unnamed(Default::default(), fields)
}
}
FieldsKind::Named(brace_token) => Self::into_fields_named(brace_token, fields),
FieldsKind::Unnamed(paren_token) => Self::into_fields_unnamed(paren_token, fields),
}
}
}
pub(crate) fn get_target(target: &Option<(kw::target, Paren, Path)>, item_ident: &Ident) -> Path {
match target {
Some((_, _, target)) => target.clone(),
None => item_ident.clone().into(),
}
}
pub(crate) struct ValueDeriveGenerics {
pub(crate) generics: Generics,
pub(crate) static_type_generics: Generics,
}
impl ValueDeriveGenerics {
pub(crate) fn get(mut generics: Generics, where_: &Option<(Where, Paren, Bounds)>) -> Self {
let mut static_type_generics = generics.clone();
if let Some((_, _, bounds)) = where_ {
generics
.make_where_clause()
.predicates
.extend(bounds.0.iter().cloned());
static_type_generics
.where_clause
.clone_from(&generics.where_clause);
} else {
let type_params = Vec::from_iter(generics.type_params().map(|v| v.ident.clone()));
let predicates = &mut generics.make_where_clause().predicates;
let static_type_predicates = &mut static_type_generics.make_where_clause().predicates;
for type_param in type_params {
predicates.push(parse_quote! {#type_param: ::fayalite::ty::Value<Type: ::fayalite::ty::Type<Value = #type_param>>});
static_type_predicates
.push(parse_quote! {#type_param: ::fayalite::ty::StaticValue});
}
}
Self {
generics,
static_type_generics,
}
}
}
pub(crate) fn derive_clone_hash_eq_partialeq_for_struct<Name: ToTokens>(
the_struct_ident: &Ident,
generics: &Generics,
field_names: &[Name],
) -> TokenStream {
let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
quote! {
#[automatically_derived]
impl #impl_generics ::fayalite::__std::clone::Clone for #the_struct_ident #type_generics
#where_clause
{
fn clone(&self) -> Self {
Self {
#(#field_names: ::fayalite::__std::clone::Clone::clone(&self.#field_names),)*
}
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::__std::hash::Hash for #the_struct_ident #type_generics
#where_clause
{
#[allow(unused_variables)]
fn hash<__H: ::fayalite::__std::hash::Hasher>(&self, hasher: &mut __H) {
#(::fayalite::__std::hash::Hash::hash(&self.#field_names, hasher);)*
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::__std::cmp::Eq for #the_struct_ident #type_generics
#where_clause
{
}
#[automatically_derived]
impl #impl_generics ::fayalite::__std::cmp::PartialEq for #the_struct_ident #type_generics
#where_clause
{
#[allow(unused_variables)]
#[allow(clippy::nonminimal_bool)]
fn eq(&self, other: &Self) -> ::fayalite::__std::primitive::bool {
true #(&& ::fayalite::__std::cmp::PartialEq::eq(
&self.#field_names,
&other.#field_names,
))*
}
}
}
}
pub(crate) fn append_field(fields: &mut Fields, mut field: Field) -> Member {
let ident = field.ident.clone().expect("ident is supplied");
match fields {
Fields::Named(FieldsNamed { named, .. }) => {
named.push(field);
Member::Named(ident)
}
Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => {
field.ident = None;
field.colon_token = None;
let index = unnamed.len();
unnamed.push(field);
Member::Unnamed(index.into())
}
Fields::Unit => {
*fields = Fields::Named(FieldsNamed {
brace_token: Default::default(),
named: Punctuated::from_iter([field]),
});
Member::Named(ident)
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct BuilderField {
pub(crate) names: HashSet<Member>,
pub(crate) mapped_value: Expr,
pub(crate) mapped_type: Type,
pub(crate) where_clause: Option<WhereClause>,
pub(crate) builder_field_name: Ident,
pub(crate) type_param: Ident,
}
#[derive(Debug)]
pub(crate) struct Builder {
struct_name: Ident,
vis: Visibility,
fields: BTreeMap<String, BuilderField>,
}
#[derive(Debug)]
pub(crate) struct BuilderWithFields {
struct_name: Ident,
vis: Visibility,
phantom_type_param: Ident,
phantom_type_field: Ident,
fields: Vec<(String, BuilderField)>,
}
impl Builder {
pub(crate) fn new(struct_name: Ident, vis: Visibility) -> Self {
Self {
struct_name,
vis,
fields: BTreeMap::new(),
}
}
pub(crate) fn insert_field(
&mut self,
name: Member,
map_value: impl FnOnce(&Ident) -> Expr,
map_type: impl FnOnce(&Ident) -> Type,
where_clause: impl FnOnce(&Ident) -> Option<WhereClause>,
) {
self.fields
.entry(name.to_token_stream().to_string())
.or_insert_with_key(|name| {
let builder_field_name =
format_ident!("field_{}", name, span = self.struct_name.span());
let type_param = format_ident!("__T_{}", name, span = self.struct_name.span());
BuilderField {
names: HashSet::new(),
mapped_value: map_value(&builder_field_name),
mapped_type: map_type(&type_param),
where_clause: where_clause(&type_param),
builder_field_name,
type_param,
}
})
.names
.insert(name);
}
pub(crate) fn finish_filling_in_fields(self) -> BuilderWithFields {
let Self {
struct_name,
vis,
fields,
} = self;
let fields = Vec::from_iter(fields);
BuilderWithFields {
phantom_type_param: Ident::new("__Phantom", struct_name.span()),
phantom_type_field: Ident::new("__phantom", struct_name.span()),
struct_name,
vis,
fields,
}
}
}
impl BuilderWithFields {
pub(crate) fn get_field(&self, name: &Member) -> Option<(usize, &BuilderField)> {
let index = self
.fields
.binary_search_by_key(&&*name.to_token_stream().to_string(), |v| &*v.0)
.ok()?;
Some((index, &self.fields[index].1))
}
pub(crate) fn ty(
&self,
specified_fields: impl IntoIterator<Item = (Member, Type)>,
phantom_type: Option<&Type>,
other_fields_are_any_type: bool,
) -> TypePath {
let Self {
struct_name,
vis: _,
phantom_type_param,
phantom_type_field: _,
fields,
} = self;
let span = struct_name.span();
let mut arguments =
Vec::from_iter(fields.iter().map(|(_, BuilderField { type_param, .. })| {
if other_fields_are_any_type {
parse_quote_spanned! {span=>
#type_param
}
} else {
parse_quote_spanned! {span=>
()
}
}
}));
for (name, ty) in specified_fields {
let Some((index, _)) = self.get_field(&name) else {
panic!("field not found: {}", name.to_token_stream());
};
arguments[index] = ty;
}
let phantom_type_param = phantom_type.is_none().then_some(phantom_type_param);
parse_quote_spanned! {span=>
#struct_name::<#phantom_type_param #phantom_type #(, #arguments)*>
}
}
pub(crate) fn append_generics(
&self,
specified_fields: impl IntoIterator<Item = Member>,
has_phantom_type_param: bool,
other_fields_are_any_type: bool,
generics: &mut Generics,
) {
let Self {
struct_name: _,
vis: _,
phantom_type_param,
phantom_type_field: _,
fields,
} = self;
if has_phantom_type_param {
generics.params.push(GenericParam::from(TypeParam::from(
phantom_type_param.clone(),
)));
}
if !other_fields_are_any_type {
return;
}
let mut type_params = Vec::from_iter(
fields
.iter()
.map(|(_, BuilderField { type_param, .. })| Some(type_param)),
);
for name in specified_fields {
let Some((index, _)) = self.get_field(&name) else {
panic!("field not found: {}", name.to_token_stream());
};
type_params[index] = None;
}
generics.params.extend(
type_params
.into_iter()
.filter_map(|v| Some(GenericParam::from(TypeParam::from(v?.clone())))),
);
}
pub(crate) fn make_build_method(
&self,
build_fn_name: &Ident,
specified_fields: impl IntoIterator<Item = (Member, Type)>,
generics: &Generics,
phantom_type: &Type,
return_ty: &Type,
mut body: Block,
) -> ItemImpl {
let Self {
struct_name,
vis,
phantom_type_param: _,
phantom_type_field,
fields,
} = self;
let span = struct_name.span();
let field_names = Vec::from_iter(fields.iter().map(|v| &v.1.builder_field_name));
let (impl_generics, _type_generics, where_clause) = generics.split_for_impl();
let empty_arg = parse_quote_spanned! {span=>
()
};
let mut ty_arguments = vec![empty_arg; fields.len()];
let empty_field_pat = quote_spanned! {span=>
: _
};
let mut field_pats = vec![Some(empty_field_pat); fields.len()];
for (name, ty) in specified_fields {
let Some((index, _)) = self.get_field(&name) else {
panic!("field not found: {}", name.to_token_stream());
};
ty_arguments[index] = ty;
field_pats[index] = None;
}
body.stmts.insert(
0,
parse_quote_spanned! {span=>
let Self {
#(#field_names #field_pats,)*
#phantom_type_field: _,
} = self;
},
);
parse_quote_spanned! {span=>
#[automatically_derived]
impl #impl_generics #struct_name<#phantom_type #(, #ty_arguments)*>
#where_clause
{
#[allow(non_snake_case, dead_code)]
#vis fn #build_fn_name(self) -> #return_ty
#body
}
}
}
}
impl ToTokens for BuilderWithFields {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
struct_name,
vis,
phantom_type_param,
phantom_type_field,
fields,
} = self;
let span = struct_name.span();
let mut any_generics = Generics::default();
self.append_generics([], true, true, &mut any_generics);
let empty_ty = self.ty([], None, false);
let field_names = Vec::from_iter(fields.iter().map(|v| &v.1.builder_field_name));
let field_type_params = Vec::from_iter(fields.iter().map(|v| &v.1.type_param));
quote_spanned! {span=>
#[allow(non_camel_case_types)]
#[non_exhaustive]
#vis struct #struct_name #any_generics {
#(#field_names: #field_type_params,)*
#phantom_type_field: ::fayalite::__std::marker::PhantomData<#phantom_type_param>,
}
#[automatically_derived]
impl<#phantom_type_param> #empty_ty {
fn new() -> Self {
Self {
#(#field_names: (),)*
#phantom_type_field: ::fayalite::__std::marker::PhantomData,
}
}
}
}
.to_tokens(tokens);
for (field_index, (_, field)) in self.fields.iter().enumerate() {
let initial_fields = &fields[..field_index];
let final_fields = &fields[field_index..][1..];
let initial_type_params =
Vec::from_iter(initial_fields.iter().map(|v| &v.1.type_param));
let final_type_params = Vec::from_iter(final_fields.iter().map(|v| &v.1.type_param));
let initial_field_names =
Vec::from_iter(initial_fields.iter().map(|v| &v.1.builder_field_name));
let final_field_names =
Vec::from_iter(final_fields.iter().map(|v| &v.1.builder_field_name));
let BuilderField {
names: _,
mapped_value,
mapped_type,
where_clause,
builder_field_name,
type_param,
} = field;
quote_spanned! {span=>
#[automatically_derived]
#[allow(non_camel_case_types, dead_code)]
impl<#phantom_type_param #(, #initial_type_params)* #(, #final_type_params)*>
#struct_name<
#phantom_type_param,
#(#initial_type_params,)*
(), #(#final_type_params,)*
>
{
#vis fn #builder_field_name<#type_param>(
self,
#builder_field_name: #type_param,
) -> #struct_name<
#phantom_type_param,
#(#initial_type_params,)*
#mapped_type,
#(#final_type_params,)*
>
#where_clause
{
let Self {
#(#initial_field_names,)*
#builder_field_name: (),
#(#final_field_names,)*
#phantom_type_field: _,
} = self;
let #builder_field_name = #mapped_value;
#struct_name {
#(#field_names,)*
#phantom_type_field: ::fayalite::__std::marker::PhantomData,
}
}
}
}
.to_tokens(tokens);
}
}
}
pub(crate) struct MapIdents {
pub(crate) map: HashMap<Ident, Ident>,
}
impl Fold for &MapIdents {
fn fold_ident(&mut self, i: Ident) -> Ident {
self.map.get(&i).cloned().unwrap_or(i)
}
}
pub(crate) struct DupGenerics<M> {
pub(crate) combined: Generics,
pub(crate) maps: M,
}
pub(crate) fn merge_punctuated<T, P: Default>(
target: &mut Punctuated<T, P>,
source: Punctuated<T, P>,
make_punct: impl FnOnce() -> P,
) {
if source.is_empty() {
return;
}
if target.is_empty() {
*target = source;
return;
}
if !target.trailing_punct() {
target.push_punct(make_punct());
}
target.extend(source.into_pairs());
}
pub(crate) fn merge_generics(target: &mut Generics, source: Generics) {
let Generics {
lt_token,
params,
gt_token,
where_clause,
} = source;
let span = lt_token.map(|v| v.span).unwrap_or_else(|| params.span());
target.lt_token = target.lt_token.or(lt_token);
merge_punctuated(&mut target.params, params, || Token![,](span));
target.gt_token = target.gt_token.or(gt_token);
if let Some(where_clause) = where_clause {
if let Some(target_where_clause) = &mut target.where_clause {
let WhereClause {
where_token,
predicates,
} = where_clause;
let span = where_token.span;
target_where_clause.where_token = where_token;
merge_punctuated(&mut target_where_clause.predicates, predicates, || {
Token![,](span)
});
} else {
target.where_clause = Some(where_clause);
}
}
}
impl DupGenerics<Vec<MapIdents>> {
pub(crate) fn new_dyn(generics: &Generics, count: usize) -> Self {
let mut maps = Vec::from_iter((0..count).map(|_| MapIdents {
map: HashMap::new(),
}));
for param in &generics.params {
let (GenericParam::Lifetime(LifetimeParam {
lifetime: Lifetime { ident, .. },
..
})
| GenericParam::Type(TypeParam { ident, .. })
| GenericParam::Const(ConstParam { ident, .. })) = param;
for (i, map_idents) in maps.iter_mut().enumerate() {
map_idents
.map
.insert(ident.clone(), format_ident!("__{}_{}", ident, i));
}
}
let mut combined = Generics::default();
for map_idents in maps.iter() {
merge_generics(
&mut combined,
fold_generics(&mut { map_idents }, generics.clone()),
);
}
Self { combined, maps }
}
}
impl<const COUNT: usize> DupGenerics<[MapIdents; COUNT]> {
pub(crate) fn new(generics: &Generics) -> Self {
let DupGenerics { combined, maps } = DupGenerics::new_dyn(generics, COUNT);
Self {
combined,
maps: maps.try_into().ok().unwrap(),
}
}
}
pub(crate) fn add_where_predicate(
target: &mut Generics,
span: Span,
where_predicate: WherePredicate,
) {
let WhereClause {
where_token: _,
predicates,
} = target.where_clause.get_or_insert_with(|| WhereClause {
where_token: Token![where](span),
predicates: Punctuated::new(),
});
if !predicates.empty_or_trailing() {
predicates.push_punct(Token![,](span));
}
predicates.push_value(where_predicate);
}
pub(crate) fn make_connect_impl(
connect_inexact: Option<(crate::kw::connect_inexact,)>,
generics: &Generics,
ty_ident: &Ident,
field_types: impl IntoIterator<Item = Type>,
) -> TokenStream {
let span = ty_ident.span();
let impl_generics;
let combined_generics;
let where_clause;
let lhs_generics;
let lhs_type_generics;
let rhs_generics;
let rhs_type_generics;
if connect_inexact.is_some() {
let DupGenerics {
mut combined,
maps: [lhs_map, rhs_map],
} = DupGenerics::new(generics);
for field_type in field_types {
let lhs_type = (&lhs_map).fold_type(field_type.clone());
let rhs_type = (&rhs_map).fold_type(field_type);
add_where_predicate(
&mut combined,
span,
parse_quote_spanned! {span=>
#lhs_type: ::fayalite::ty::Connect<#rhs_type>
},
);
}
combined_generics = combined;
(impl_generics, _, where_clause) = combined_generics.split_for_impl();
lhs_generics = (&lhs_map).fold_generics(generics.clone());
(_, lhs_type_generics, _) = lhs_generics.split_for_impl();
rhs_generics = (&rhs_map).fold_generics(generics.clone());
(_, rhs_type_generics, _) = rhs_generics.split_for_impl();
} else {
let mut generics = generics.clone();
for field_type in field_types {
add_where_predicate(
&mut generics,
span,
parse_quote_spanned! {span=>
#field_type: ::fayalite::ty::Connect<#field_type>
},
);
}
combined_generics = generics;
(impl_generics, lhs_type_generics, where_clause) = combined_generics.split_for_impl();
rhs_type_generics = lhs_type_generics.clone();
}
quote_spanned! {span=>
#[automatically_derived]
#[allow(non_camel_case_types)]
impl #impl_generics ::fayalite::ty::Connect<#ty_ident #rhs_type_generics>
for #ty_ident #lhs_type_generics
#where_clause
{
}
}
}

View file

@ -1,969 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
value_derive_common::{
append_field, derive_clone_hash_eq_partialeq_for_struct, get_field_names, get_target,
make_connect_impl, Bounds, Builder, FieldsKind, ParsedField, ValueDeriveGenerics,
},
value_derive_struct::{self, ParsedStruct, ParsedStructNames, StructOptions},
Errors, HdlAttr,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote, quote_spanned, ToTokens};
use syn::{
parse_quote, parse_quote_spanned, punctuated::Punctuated, spanned::Spanned, token::Brace,
Field, FieldMutability, Fields, FieldsNamed, Generics, Ident, Index, ItemEnum, ItemStruct,
Member, Path, Token, Type, Variant, Visibility,
};
crate::options! {
#[options = EnumOptions]
enum EnumOption {
OutlineGenerated(outline_generated),
ConnectInexact(connect_inexact),
Bounds(where_, Bounds),
Target(target, Path),
}
}
crate::options! {
#[options = VariantOptions]
enum VariantOption {}
}
crate::options! {
#[options = FieldOptions]
enum FieldOption {}
}
enum VariantValue {
None,
Direct {
value_type: Type,
},
Struct {
value_struct: ItemStruct,
parsed_struct: ParsedStruct,
},
}
impl VariantValue {
fn is_none(&self) -> bool {
matches!(self, Self::None)
}
fn value_ty(&self) -> Option<Type> {
match self {
VariantValue::None => None,
VariantValue::Direct { value_type } => Some(value_type.clone()),
VariantValue::Struct { value_struct, .. } => {
let (_, type_generics, _) = value_struct.generics.split_for_impl();
let ident = &value_struct.ident;
Some(parse_quote! { #ident #type_generics })
}
}
}
}
struct ParsedVariant {
options: HdlAttr<VariantOptions>,
ident: Ident,
fields_kind: FieldsKind,
fields: Vec<ParsedField<FieldOptions>>,
value: VariantValue,
}
impl ParsedVariant {
fn parse(
errors: &mut Errors,
variant: Variant,
enum_options: &EnumOptions,
enum_vis: &Visibility,
enum_ident: &Ident,
enum_generics: &Generics,
) -> Self {
let target = get_target(&enum_options.target, enum_ident);
let Variant {
mut attrs,
ident,
fields,
discriminant,
} = variant;
if let Some((eq, _)) = discriminant {
errors.error(eq, "#[derive(Value)]: discriminants not allowed");
}
let variant_options = errors
.unwrap_or_default(HdlAttr::parse_and_take_attr(&mut attrs))
.unwrap_or_default();
let (fields_kind, parsed_fields) =
ParsedField::parse_fields(errors, &mut fields.clone(), true);
let value = match (&fields_kind, &*parsed_fields) {
(FieldsKind::Unit, _) => VariantValue::None,
(
FieldsKind::Unnamed(_),
[ParsedField {
options,
vis: _,
name: Member::Unnamed(Index { index: 0, span: _ }),
ty,
}],
) => {
let FieldOptions {} = options.body;
VariantValue::Direct {
value_type: ty.clone(),
}
}
_ => {
let variant_value_struct_ident =
format_ident!("__{}__{}", enum_ident, ident, span = ident.span());
let variant_type_struct_ident =
format_ident!("__{}__{}__Type", enum_ident, ident, span = ident.span());
let mut value_struct_fields = fields.clone();
let (_, type_generics, _) = enum_generics.split_for_impl();
append_field(
&mut value_struct_fields,
Field {
attrs: vec![HdlAttr::from(value_derive_struct::FieldOptions {
flip: None,
skip: Some(Default::default()),
})
.to_attr()],
vis: enum_vis.clone(),
mutability: FieldMutability::None,
ident: Some(Ident::new("__phantom", ident.span())),
colon_token: None,
ty: parse_quote_spanned! {ident.span()=>
::fayalite::__std::marker::PhantomData<#target #type_generics>
},
},
);
let (value_struct_fields_kind, value_struct_parsed_fields) =
ParsedField::parse_fields(errors, &mut value_struct_fields, false);
let value_struct = ItemStruct {
attrs: vec![parse_quote! { #[allow(non_camel_case_types)] }],
vis: enum_vis.clone(),
struct_token: Token![struct](ident.span()),
ident: variant_value_struct_ident.clone(),
generics: enum_generics.clone(),
fields: value_struct_fields,
semi_token: None,
};
VariantValue::Struct {
value_struct,
parsed_struct: ParsedStruct {
options: StructOptions {
outline_generated: None,
static_: Some(Default::default()),
where_: Some((
Default::default(),
Default::default(),
ValueDeriveGenerics::get(
enum_generics.clone(),
&enum_options.where_,
)
.static_type_generics
.where_clause
.into(),
)),
target: None,
connect_inexact: enum_options.connect_inexact,
}
.into(),
vis: enum_vis.clone(),
struct_token: Default::default(),
generics: enum_generics.clone(),
fields_kind: value_struct_fields_kind,
fields: value_struct_parsed_fields,
semi_token: None, // it will fill in the semicolon if needed
skip_check_fields: true,
names: ParsedStructNames {
ident: variant_value_struct_ident.clone(),
type_struct_debug_ident: Some(format!("{enum_ident}::{ident}::Type")),
type_struct_ident: variant_type_struct_ident,
match_variant_ident: None,
builder_struct_ident: None,
mask_match_variant_ident: None,
mask_type_ident: None,
mask_type_debug_ident: Some(format!(
"AsMask<{enum_ident}::{ident}>::Type"
)),
mask_value_ident: None,
mask_value_debug_ident: Some(format!("AsMask<{enum_ident}::{ident}>")),
mask_builder_struct_ident: None,
},
},
}
}
};
ParsedVariant {
options: variant_options,
ident,
fields_kind,
fields: parsed_fields,
value,
}
}
}
struct ParsedEnum {
options: HdlAttr<EnumOptions>,
vis: Visibility,
enum_token: Token![enum],
ident: Ident,
generics: Generics,
brace_token: Brace,
variants: Vec<ParsedVariant>,
}
impl ParsedEnum {
fn parse(item: ItemEnum) -> syn::Result<Self> {
let ItemEnum {
mut attrs,
vis,
enum_token,
ident,
generics,
brace_token,
variants,
} = item;
let mut errors = Errors::new();
let enum_options = errors
.unwrap_or_default(HdlAttr::parse_and_take_attr(&mut attrs))
.unwrap_or_default();
let variants = variants
.into_iter()
.map(|variant| {
ParsedVariant::parse(
&mut errors,
variant,
&enum_options.body,
&vis,
&ident,
&generics,
)
})
.collect();
errors.finish()?;
Ok(ParsedEnum {
options: enum_options,
vis,
enum_token,
ident,
generics,
brace_token,
variants,
})
}
}
impl ToTokens for ParsedEnum {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
options,
vis,
enum_token,
ident: enum_ident,
generics: enum_generics,
brace_token,
variants,
} = self;
let EnumOptions {
outline_generated: _,
connect_inexact,
where_,
target,
} = &options.body;
let target = get_target(target, enum_ident);
let ValueDeriveGenerics {
generics: _,
static_type_generics,
} = ValueDeriveGenerics::get(enum_generics.clone(), where_);
let (static_type_impl_generics, static_type_type_generics, static_type_where_clause) =
static_type_generics.split_for_impl();
let type_struct_ident = format_ident!("__{}__Type", enum_ident);
let mut field_checks = vec![];
let mut make_type_struct_variant_type = |variant: &ParsedVariant| {
let VariantOptions {} = variant.options.body;
let (value_struct, parsed_struct) = match &variant.value {
VariantValue::None => {
return None;
}
VariantValue::Direct { value_type } => {
field_checks.push(quote_spanned! {value_type.span()=>
__check_field::<#value_type>();
});
return Some(parse_quote! { <#value_type as ::fayalite::expr::ToExpr>::Type });
}
VariantValue::Struct {
value_struct,
parsed_struct,
} => (value_struct, parsed_struct),
};
value_struct.to_tokens(tokens);
parsed_struct.to_tokens(tokens);
let mut field_names = Vec::from_iter(get_field_names(&value_struct.fields));
derive_clone_hash_eq_partialeq_for_struct(
&value_struct.ident,
&static_type_generics,
&field_names,
)
.to_tokens(tokens);
field_names = Vec::from_iter(
field_names
.into_iter()
.zip(parsed_struct.fields.iter())
.filter_map(|(member, field)| {
field.options.body.skip.is_none().then_some(member)
}),
);
let field_name_strs =
Vec::from_iter(field_names.iter().map(|v| v.to_token_stream().to_string()));
let debug_ident = format!("{enum_ident}::{}", variant.ident);
let debug_body = match variant.fields_kind {
FieldsKind::Unit => quote! {
f.debug_struct(#debug_ident).finish()
},
FieldsKind::Named(_) => quote! {
f.debug_struct(#debug_ident)
#(.field(#field_name_strs, &self.#field_names))*
.finish()
},
FieldsKind::Unnamed(_) => quote! {
f.debug_tuple(#debug_ident)#(.field(&self.#field_names))*.finish()
},
};
let value_struct_ident = &value_struct.ident;
quote! {
#[automatically_derived]
impl #static_type_impl_generics ::fayalite::__std::fmt::Debug
for #value_struct_ident #static_type_type_generics
#static_type_where_clause
{
fn fmt(
&self,
f: &mut ::fayalite::__std::fmt::Formatter<'_>,
) -> ::fayalite::__std::fmt::Result {
#debug_body
}
}
}
.to_tokens(tokens);
Some(parse_quote! {
<
#value_struct_ident #static_type_type_generics
as ::fayalite::expr::ToExpr
>::Type
})
};
let type_struct_variants = Punctuated::from_iter(variants.iter().filter_map(|variant| {
let VariantOptions {} = variant.options.body;
Some(Field {
attrs: vec![],
vis: vis.clone(),
mutability: FieldMutability::None,
ident: Some(variant.ident.clone()),
colon_token: None, // it will fill in the colon if needed
ty: make_type_struct_variant_type(variant)?,
})
}));
let type_struct = ItemStruct {
attrs: vec![
parse_quote! {#[allow(non_camel_case_types)]},
parse_quote! {#[allow(non_snake_case)]},
],
vis: vis.clone(),
struct_token: Token![struct](enum_token.span),
ident: type_struct_ident,
generics: static_type_generics.clone(),
fields: Fields::Named(FieldsNamed {
brace_token: *brace_token,
named: type_struct_variants,
}),
semi_token: None,
};
let type_struct_ident = &type_struct.ident;
let type_struct_debug_ident = format!("{enum_ident}::Type");
type_struct.to_tokens(tokens);
let non_empty_variant_names = Vec::from_iter(
variants
.iter()
.filter(|v| !v.value.is_none())
.map(|v| v.ident.clone()),
);
let non_empty_variant_name_strs =
Vec::from_iter(non_empty_variant_names.iter().map(|v| v.to_string()));
let debug_type_body = quote! {
f.debug_struct(#type_struct_debug_ident)
#(.field(#non_empty_variant_name_strs, &self.#non_empty_variant_names))*
.finish()
};
derive_clone_hash_eq_partialeq_for_struct(
type_struct_ident,
&static_type_generics,
&non_empty_variant_names,
)
.to_tokens(tokens);
let variant_names = Vec::from_iter(variants.iter().map(|v| &v.ident));
let variant_name_strs = Vec::from_iter(variant_names.iter().map(|v| v.to_string()));
let (variant_field_pats, variant_to_canonical_values): (Vec<_>, Vec<_>) = variants
.iter()
.map(|v| {
let field_names: Vec<_> = v.fields.iter().map(|field| &field.name).collect();
let var_names: Vec<_> = v.fields.iter().map(|field| field.var_name()).collect();
let field_pats = quote! {
#(#field_names: #var_names,)*
};
let to_canonical_value = match &v.value {
VariantValue::None => quote! { ::fayalite::__std::option::Option::None },
VariantValue::Direct { .. } => {
debug_assert_eq!(var_names.len(), 1);
quote! {
::fayalite::__std::option::Option::Some(
::fayalite::ty::DynValueTrait::to_canonical_dyn(#(#var_names)*),
)
}
}
VariantValue::Struct {
value_struct,
parsed_struct,
} => {
let value_struct_ident = &value_struct.ident;
let phantom_field_name = &parsed_struct
.fields
.last()
.expect("missing phantom field")
.name;
let type_generics = static_type_type_generics.as_turbofish();
quote! {
::fayalite::__std::option::Option::Some(
::fayalite::ty::DynValueTrait::to_canonical_dyn(
&#value_struct_ident #type_generics {
#(#field_names:
::fayalite::__std::clone::Clone::clone(#var_names),)*
#phantom_field_name: ::fayalite::__std::marker::PhantomData,
},
),
)
}
}
};
(field_pats, to_canonical_value)
})
.unzip();
let mut match_enum_variants = Punctuated::new();
let mut match_enum_debug_arms = vec![];
let mut match_enum_arms = vec![];
let mut variant_vars = vec![];
let mut from_canonical_type_variant_lets = vec![];
let mut non_empty_variant_vars = vec![];
let mut enum_type_variants = vec![];
let mut enum_type_variants_hint = vec![];
let match_enum_ident = format_ident!("__{}__MatchEnum", enum_ident);
let mut builder = Builder::new(format_ident!("__{}__Builder", enum_ident), vis.clone());
for variant in variants.iter() {
for field in variant.fields.iter() {
builder.insert_field(
field.name.clone(),
|v| {
parse_quote_spanned! {v.span()=>
::fayalite::expr::ToExpr::to_expr(&#v)
}
},
|t| {
parse_quote_spanned! {t.span()=>
::fayalite::expr::Expr<<
<#t as ::fayalite::expr::ToExpr>::Type
as ::fayalite::ty::Type
>::Value>
}
},
|t| {
parse_quote_spanned! {t.span()=>
where
#t: ::fayalite::expr::ToExpr,
}
},
);
}
}
let builder = builder.finish_filling_in_fields();
builder.to_tokens(tokens);
for (variant_index, variant) in variants.iter().enumerate() {
let variant_var = format_ident!("__v_{}", variant.ident);
let variant_name = &variant.ident;
let variant_name_str = variant.ident.to_string();
match_enum_variants.push(Variant {
attrs: vec![],
ident: variant.ident.clone(),
fields: variant.fields_kind.into_fields(variant.fields.iter().map(
|ParsedField {
options,
vis,
name,
ty,
}| {
let FieldOptions {} = options.body;
Field {
attrs: vec![],
vis: vis.clone(),
mutability: FieldMutability::None,
ident: if let Member::Named(name) = name {
Some(name.clone())
} else {
None
},
colon_token: None,
ty: parse_quote! { ::fayalite::expr::Expr<#ty> },
}
},
)),
discriminant: None,
});
let match_enum_field_names = Vec::from_iter(variant.fields.iter().map(
|ParsedField {
options,
vis: _,
name,
ty: _,
}| {
let FieldOptions {} = options.body;
name
},
));
let match_enum_field_name_strs = Vec::from_iter(variant.fields.iter().map(
|ParsedField {
options,
vis: _,
name,
ty: _,
}| {
let FieldOptions {} = options.body;
name.to_token_stream().to_string()
},
));
let match_enum_debug_vars = Vec::from_iter(variant.fields.iter().map(
|ParsedField {
options,
vis: _,
name,
ty: _,
}| {
let FieldOptions {} = options.body;
format_ident!("__v_{}", name)
},
));
match_enum_debug_arms.push(match variant.fields_kind {
FieldsKind::Unit | FieldsKind::Named(_) => quote! {
Self::#variant_name {
#(#match_enum_field_names: ref #match_enum_debug_vars,)*
} => f.debug_struct(#variant_name_str)
#(.field(#match_enum_field_name_strs, #match_enum_debug_vars))*
.finish(),
},
FieldsKind::Unnamed(_) => quote! {
Self::#variant_name(
#(ref #match_enum_debug_vars,)*
) => f.debug_tuple(#variant_name_str)
#(.field(#match_enum_debug_vars))*
.finish(),
},
});
if let Some(value_ty) = variant.value.value_ty() {
from_canonical_type_variant_lets.push(quote! {
let #variant_var =
#variant_var.from_canonical_type_helper_has_value(#variant_name_str);
});
non_empty_variant_vars.push(variant_var.clone());
enum_type_variants.push(quote! {
::fayalite::enum_::VariantType {
name: ::fayalite::intern::Intern::intern(#variant_name_str),
ty: ::fayalite::__std::option::Option::Some(
::fayalite::ty::DynType::canonical_dyn(&self.#variant_name),
),
}
});
enum_type_variants_hint.push(quote! {
::fayalite::enum_::VariantType {
name: ::fayalite::intern::Intern::intern(#variant_name_str),
ty: ::fayalite::__std::option::Option::Some(
::fayalite::bundle::TypeHint::<
<#value_ty as ::fayalite::expr::ToExpr>::Type,
>::intern_dyn(),
),
}
});
} else {
from_canonical_type_variant_lets.push(quote! {
#variant_var.from_canonical_type_helper_no_value(#variant_name_str);
});
enum_type_variants.push(quote! {
::fayalite::enum_::VariantType {
name: ::fayalite::intern::Intern::intern(#variant_name_str),
ty: ::fayalite::__std::option::Option::None,
}
});
enum_type_variants_hint.push(quote! {
::fayalite::enum_::VariantType {
name: ::fayalite::intern::Intern::intern(#variant_name_str),
ty: ::fayalite::__std::option::Option::None,
}
});
}
variant_vars.push(variant_var);
match_enum_arms.push(match &variant.value {
VariantValue::None => quote! {
#variant_index => #match_enum_ident::#variant_name,
},
VariantValue::Direct { value_type } => quote! {
#variant_index => #match_enum_ident::#variant_name {
#(#match_enum_field_names)*: ::fayalite::expr::ToExpr::to_expr(
&__variant_access.downcast_unchecked::<
<#value_type as ::fayalite::expr::ToExpr>::Type>(),
),
},
},
VariantValue::Struct {
value_struct: ItemStruct { ident, .. },
..
} => quote! {
#variant_index => {
let __variant_access = ::fayalite::expr::ToExpr::to_expr(
&__variant_access.downcast_unchecked::<<
#ident #static_type_type_generics
as ::fayalite::expr::ToExpr
>::Type>(),
);
#match_enum_ident::#variant_name {
#(#match_enum_field_names:
(*__variant_access).#match_enum_field_names,)*
}
},
},
});
let builder_field_and_types = Vec::from_iter(variant.fields.iter().map(
|ParsedField {
options,
vis: _,
name,
ty,
}| {
let FieldOptions {} = options.body;
(name, ty)
},
));
let builder_field_vars = Vec::from_iter(
builder_field_and_types
.iter()
.map(|(name, _)| &builder.get_field(name).unwrap().1.builder_field_name),
);
let build_body = match &variant.value {
VariantValue::None => parse_quote! {
{
::fayalite::expr::ToExpr::to_expr(
&::fayalite::expr::ops::EnumLiteral::<
#type_struct_ident #static_type_type_generics
>::new_unchecked(
::fayalite::__std::option::Option::None,
#variant_index,
::fayalite::ty::StaticType::static_type(),
),
)
}
},
VariantValue::Direct { value_type: _ } => parse_quote! {
{
::fayalite::expr::ToExpr::to_expr(
&::fayalite::expr::ops::EnumLiteral::<
#type_struct_ident #static_type_type_generics
>::new_unchecked(
::fayalite::__std::option::Option::Some(
#(#builder_field_vars)*.to_canonical_dyn(),
),
#variant_index,
::fayalite::ty::StaticType::static_type(),
),
)
}
},
VariantValue::Struct {
parsed_struct:
ParsedStruct {
names:
ParsedStructNames {
type_struct_ident: field_type_struct_ident,
..
},
..
},
..
} => parse_quote! {
{
let __builder = <
#field_type_struct_ident #static_type_type_generics
as ::fayalite::bundle::BundleType
>::builder();
#(let __builder = __builder.#builder_field_vars(#builder_field_vars);)*
::fayalite::expr::ToExpr::to_expr(
&::fayalite::expr::ops::EnumLiteral::<
#type_struct_ident #static_type_type_generics
>::new_unchecked(
::fayalite::__std::option::Option::Some(
__builder.build().to_canonical_dyn(),
),
#variant_index,
::fayalite::ty::StaticType::static_type(),
),
)
}
},
};
builder
.make_build_method(
&format_ident!("variant_{}", variant_name),
variant.fields.iter().map(
|ParsedField {
options,
vis: _,
name,
ty,
}| {
let FieldOptions {} = options.body;
(name.clone(), parse_quote! { ::fayalite::expr::Expr<#ty> })
},
),
&static_type_generics,
&parse_quote! {#type_struct_ident #static_type_type_generics},
&parse_quote! { ::fayalite::expr::Expr<#target #static_type_type_generics> },
build_body,
)
.to_tokens(tokens);
}
let match_enum = ItemEnum {
attrs: vec![parse_quote! {#[allow(non_camel_case_types)]}],
vis: vis.clone(),
enum_token: *enum_token,
ident: match_enum_ident,
generics: static_type_generics.clone(),
brace_token: *brace_token,
variants: match_enum_variants,
};
let match_enum_ident = &match_enum.ident;
match_enum.to_tokens(tokens);
make_connect_impl(
*connect_inexact,
&static_type_generics,
type_struct_ident,
variants.iter().flat_map(|variant| {
variant.fields.iter().map(|field| {
let ty = &field.ty;
parse_quote_spanned! {field.name.span()=>
<#ty as ::fayalite::expr::ToExpr>::Type
}
})
}),
)
.to_tokens(tokens);
let variant_count = variants.len();
let empty_builder_ty = builder.ty([], Some(&parse_quote! { Self }), false);
quote! {
#[automatically_derived]
impl #static_type_impl_generics ::fayalite::__std::fmt::Debug
for #match_enum_ident #static_type_type_generics
#static_type_where_clause
{
fn fmt(
&self,
f: &mut ::fayalite::__std::fmt::Formatter<'_>,
) -> ::fayalite::__std::fmt::Result {
match *self {
#(#match_enum_debug_arms)*
}
}
}
#[automatically_derived]
impl #static_type_impl_generics ::fayalite::ty::StaticType
for #type_struct_ident #static_type_type_generics
#static_type_where_clause
{
fn static_type() -> Self {
Self {
#(#non_empty_variant_names: ::fayalite::ty::StaticType::static_type(),)*
}
}
}
fn __check_field<T: ::fayalite::ty::Value>()
where
<T as ::fayalite::expr::ToExpr>::Type: ::fayalite::ty::Type<Value = T>,
{}
fn __check_fields #static_type_impl_generics(_: #target #static_type_type_generics)
#static_type_where_clause
{
#(#field_checks)*
}
#[automatically_derived]
impl #static_type_impl_generics ::fayalite::__std::fmt::Debug
for #type_struct_ident #static_type_type_generics
#static_type_where_clause
{
fn fmt(
&self,
f: &mut ::fayalite::__std::fmt::Formatter<'_>,
) -> ::fayalite::__std::fmt::Result {
#debug_type_body
}
}
#[automatically_derived]
impl #static_type_impl_generics ::fayalite::ty::Type
for #type_struct_ident #static_type_type_generics
#static_type_where_clause
{
type CanonicalType = ::fayalite::enum_::DynEnumType;
type Value = #target #static_type_type_generics;
type CanonicalValue = ::fayalite::enum_::DynEnum;
type MaskType = ::fayalite::int::UIntType<1>;
type MaskValue = ::fayalite::int::UInt<1>;
type MatchVariant = #match_enum_ident #static_type_type_generics;
type MatchActiveScope = ::fayalite::module::Scope;
type MatchVariantAndInactiveScope =
::fayalite::enum_::EnumMatchVariantAndInactiveScope<Self>;
type MatchVariantsIter = ::fayalite::enum_::EnumMatchVariantsIter<Self>;
fn match_variants<IO: ::fayalite::bundle::BundleValue>(
this: ::fayalite::expr::Expr<<Self as ::fayalite::ty::Type>::Value>,
module_builder: &mut ::fayalite::module::ModuleBuilder<
IO,
::fayalite::module::NormalModule,
>,
source_location: ::fayalite::source_location::SourceLocation,
) -> <Self as ::fayalite::ty::Type>::MatchVariantsIter
where
<IO as ::fayalite::expr::ToExpr>::Type:
::fayalite::bundle::BundleType<Value = IO>,
{
module_builder.enum_match_variants_helper(this, source_location)
}
fn mask_type(&self) -> <Self as ::fayalite::ty::Type>::MaskType {
::fayalite::int::UIntType::new()
}
fn canonical(&self) -> <Self as ::fayalite::ty::Type>::CanonicalType {
let variants = ::fayalite::enum_::EnumType::variants(self);
::fayalite::enum_::DynEnumType::new(variants)
}
fn source_location(&self) -> ::fayalite::source_location::SourceLocation {
::fayalite::source_location::SourceLocation::caller()
}
fn type_enum(&self) -> ::fayalite::ty::TypeEnum {
::fayalite::ty::TypeEnum::EnumType(::fayalite::ty::Type::canonical(self))
}
#[allow(non_snake_case)]
fn from_canonical_type(t: <Self as ::fayalite::ty::Type>::CanonicalType) -> Self {
let [#(#variant_vars),*] = *::fayalite::enum_::EnumType::variants(&t) else {
::fayalite::__std::panic!("wrong number of variants");
};
#(#from_canonical_type_variant_lets)*
Self {
#(#non_empty_variant_names: #non_empty_variant_vars,)*
}
}
}
#[automatically_derived]
#[allow(clippy::init_numbered_fields)]
impl #static_type_impl_generics ::fayalite::enum_::EnumType
for #type_struct_ident #static_type_type_generics
#static_type_where_clause
{
type Builder = #empty_builder_ty;
fn match_activate_scope(
v: <Self as ::fayalite::ty::Type>::MatchVariantAndInactiveScope,
) -> (
<Self as ::fayalite::ty::Type>::MatchVariant,
<Self as ::fayalite::ty::Type>::MatchActiveScope,
) {
let (__variant_access, __scope) = v.activate();
(
match ::fayalite::expr::ops::VariantAccess::variant_index(
&*__variant_access,
) {
#(#match_enum_arms)*
#variant_count.. => ::fayalite::__std::panic!("invalid variant index"),
},
__scope,
)
}
fn builder() -> <Self as ::fayalite::enum_::EnumType>::Builder {
#empty_builder_ty::new()
}
fn variants(&self) -> ::fayalite::intern::Interned<[::fayalite::enum_::VariantType<
::fayalite::intern::Interned<dyn ::fayalite::ty::DynCanonicalType>,
>]> {
::fayalite::intern::Intern::intern(&[#(#enum_type_variants,)*][..])
}
fn variants_hint() -> ::fayalite::enum_::VariantsHint {
::fayalite::enum_::VariantsHint::new([#(#enum_type_variants_hint,)*], false)
}
}
#[automatically_derived]
impl #static_type_impl_generics ::fayalite::expr::ToExpr
for #target #static_type_type_generics
#static_type_where_clause
{
type Type = #type_struct_ident #static_type_type_generics;
fn ty(&self) -> <Self as ::fayalite::expr::ToExpr>::Type {
::fayalite::ty::StaticType::static_type()
}
fn to_expr(&self) -> ::fayalite::expr::Expr<Self> {
::fayalite::expr::Expr::from_value(self)
}
}
#[automatically_derived]
impl #static_type_impl_generics ::fayalite::ty::Value
for #target #static_type_type_generics
#static_type_where_clause
{
fn to_canonical(&self) -> <
<Self as ::fayalite::expr::ToExpr>::Type
as ::fayalite::ty::Type
>::CanonicalValue
{
let __ty = ::fayalite::ty::Type::canonical(&::fayalite::expr::ToExpr::ty(self));
match self {
#(Self::#variant_names { #variant_field_pats } => {
::fayalite::enum_::DynEnum::new_by_name(
__ty,
::fayalite::intern::Intern::intern(#variant_name_strs),
#variant_to_canonical_values,
)
})*
}
}
}
#[automatically_derived]
impl #static_type_impl_generics ::fayalite::enum_::EnumValue
for #target #static_type_type_generics
#static_type_where_clause
{
}
}
.to_tokens(tokens);
}
}
pub(crate) fn value_derive_enum(item: ItemEnum) -> syn::Result<TokenStream> {
let item = ParsedEnum::parse(item)?;
let outline_generated = item.options.body.outline_generated;
let mut contents = quote! {
const _: () = {
#item
};
};
if outline_generated.is_some() {
contents = crate::outline_generated(contents, "value-enum-");
}
Ok(contents)
}

View file

@ -1,765 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
value_derive_common::{
append_field, derive_clone_hash_eq_partialeq_for_struct, get_target, make_connect_impl,
Bounds, Builder, FieldsKind, ParsedField, ValueDeriveGenerics,
},
Errors, HdlAttr,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote, quote_spanned, ToTokens};
use syn::{
parse_quote, parse_quote_spanned, spanned::Spanned, FieldMutability, Generics, Ident,
ItemStruct, Member, Path, Token, Visibility,
};
crate::options! {
#[options = StructOptions]
pub(crate) enum StructOption {
OutlineGenerated(outline_generated),
Static(static_),
ConnectInexact(connect_inexact),
Bounds(where_, Bounds),
Target(target, Path),
}
}
crate::options! {
#[options = FieldOptions]
pub(crate) enum FieldOption {
Flip(flip),
Skip(skip),
}
}
pub(crate) struct ParsedStructNames<I, S> {
pub(crate) ident: Ident,
pub(crate) type_struct_debug_ident: S,
pub(crate) type_struct_ident: Ident,
pub(crate) match_variant_ident: I,
pub(crate) builder_struct_ident: I,
pub(crate) mask_match_variant_ident: I,
pub(crate) mask_type_ident: I,
pub(crate) mask_type_debug_ident: S,
pub(crate) mask_value_ident: I,
pub(crate) mask_value_debug_ident: S,
pub(crate) mask_builder_struct_ident: I,
}
pub(crate) struct ParsedStruct {
pub(crate) options: HdlAttr<StructOptions>,
pub(crate) vis: Visibility,
pub(crate) struct_token: Token![struct],
pub(crate) generics: Generics,
pub(crate) fields_kind: FieldsKind,
pub(crate) fields: Vec<ParsedField<FieldOptions>>,
pub(crate) semi_token: Option<Token![;]>,
pub(crate) skip_check_fields: bool,
pub(crate) names: ParsedStructNames<Option<Ident>, Option<String>>,
}
impl ParsedStruct {
pub(crate) fn parse(item: &mut ItemStruct) -> syn::Result<Self> {
let ItemStruct {
attrs,
vis,
struct_token,
ident,
generics,
fields,
semi_token,
} = item;
let mut errors = Errors::new();
let struct_options = errors
.unwrap_or_default(HdlAttr::parse_and_take_attr(attrs))
.unwrap_or_default();
let (fields_kind, fields) = ParsedField::parse_fields(&mut errors, fields, false);
errors.finish()?;
Ok(ParsedStruct {
options: struct_options,
vis: vis.clone(),
struct_token: *struct_token,
generics: generics.clone(),
fields_kind,
fields,
semi_token: *semi_token,
skip_check_fields: false,
names: ParsedStructNames {
ident: ident.clone(),
type_struct_debug_ident: None,
type_struct_ident: format_ident!("__{}__Type", ident),
match_variant_ident: None,
builder_struct_ident: None,
mask_match_variant_ident: None,
mask_type_ident: None,
mask_type_debug_ident: None,
mask_value_ident: None,
mask_value_debug_ident: None,
mask_builder_struct_ident: None,
},
})
}
pub(crate) fn write_body(
&self,
target: Path,
names: ParsedStructNames<&Ident, &String>,
is_for_mask: bool,
tokens: &mut TokenStream,
) {
let Self {
options,
vis,
struct_token,
generics,
fields_kind,
fields,
semi_token,
skip_check_fields,
names: _,
} = self;
let skip_check_fields = *skip_check_fields || is_for_mask;
let ParsedStructNames {
ident: struct_ident,
type_struct_debug_ident,
type_struct_ident,
match_variant_ident,
builder_struct_ident,
mask_match_variant_ident: _,
mask_type_ident,
mask_type_debug_ident: _,
mask_value_ident,
mask_value_debug_ident,
mask_builder_struct_ident: _,
} = names;
let StructOptions {
outline_generated: _,
where_,
target: _,
static_,
connect_inexact,
} = &options.body;
let ValueDeriveGenerics {
generics,
static_type_generics,
} = ValueDeriveGenerics::get(generics.clone(), where_);
let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
let unskipped_fields = fields
.iter()
.filter(|field| field.options.body.skip.is_none());
let _field_names = Vec::from_iter(fields.iter().map(|field| field.name.clone()));
let unskipped_field_names =
Vec::from_iter(unskipped_fields.clone().map(|field| field.name.clone()));
let unskipped_field_name_strs = Vec::from_iter(
unskipped_field_names
.iter()
.map(|field_name| field_name.to_token_stream().to_string()),
);
let unskipped_field_vars = Vec::from_iter(
unskipped_field_names
.iter()
.map(|field_name| format_ident!("__v_{}", field_name)),
);
let unskipped_field_flips = Vec::from_iter(
unskipped_fields
.clone()
.map(|field| field.options.body.flip.is_some()),
);
let mut any_fields_skipped = false;
let type_fields = Vec::from_iter(fields.iter().filter_map(|field| {
let ParsedField {
options,
vis,
name,
ty,
} = field;
let FieldOptions { flip: _, skip } = &options.body;
if skip.is_some() {
any_fields_skipped = true;
return None;
}
let ty = if is_for_mask {
parse_quote! { ::fayalite::ty::AsMask<#ty> }
} else {
ty.to_token_stream()
};
Some(syn::Field {
attrs: vec![],
vis: vis.clone(),
mutability: FieldMutability::None,
ident: match name.clone() {
Member::Named(name) => Some(name),
Member::Unnamed(_) => None,
},
colon_token: None,
ty: parse_quote! { <#ty as ::fayalite::expr::ToExpr>::Type },
})
}));
let field_types = Vec::from_iter(type_fields.iter().map(|field| field.ty.clone()));
let match_variant_fields = Vec::from_iter(fields.iter().zip(&type_fields).map(
|(parsed_field, type_field)| {
let field_ty = &parsed_field.ty;
syn::Field {
ty: parse_quote! { ::fayalite::expr::Expr<#field_ty> },
..type_field.clone()
}
},
));
let mask_value_fields = Vec::from_iter(fields.iter().zip(&type_fields).map(
|(parsed_field, type_field)| {
let field_ty = &parsed_field.ty;
syn::Field {
ty: parse_quote! { ::fayalite::ty::AsMask<#field_ty> },
..type_field.clone()
}
},
));
let mut type_struct_fields = fields_kind.into_fields(type_fields);
let mut match_variant_fields = fields_kind.into_fields(match_variant_fields);
let mut mask_value_fields = fields_kind.into_fields(mask_value_fields);
let phantom_data_field_name = any_fields_skipped.then(|| {
let phantom_data_field_name = Ident::new("__phantom_data", type_struct_ident.span());
let member = append_field(
&mut type_struct_fields,
syn::Field {
attrs: vec![],
vis: vis.clone(),
mutability: FieldMutability::None,
ident: Some(phantom_data_field_name.clone()),
colon_token: None,
ty: parse_quote_spanned! {type_struct_ident.span()=>
::fayalite::__std::marker::PhantomData<#struct_ident #type_generics>
},
},
);
append_field(
&mut match_variant_fields,
syn::Field {
attrs: vec![],
vis: Visibility::Inherited,
mutability: FieldMutability::None,
ident: Some(phantom_data_field_name.clone()),
colon_token: None,
ty: parse_quote_spanned! {type_struct_ident.span()=>
::fayalite::__std::marker::PhantomData<#struct_ident #type_generics>
},
},
);
append_field(
&mut mask_value_fields,
syn::Field {
attrs: vec![],
vis: Visibility::Inherited,
mutability: FieldMutability::None,
ident: Some(phantom_data_field_name),
colon_token: None,
ty: parse_quote_spanned! {type_struct_ident.span()=>
::fayalite::__std::marker::PhantomData<#struct_ident #type_generics>
},
},
);
member
});
let phantom_data_field_name_slice = phantom_data_field_name.as_slice();
let type_struct = ItemStruct {
attrs: vec![parse_quote! {#[allow(non_camel_case_types)]}],
vis: vis.clone(),
struct_token: *struct_token,
ident: type_struct_ident.clone(),
generics: generics.clone(),
fields: type_struct_fields,
semi_token: *semi_token,
};
type_struct.to_tokens(tokens);
let match_variant_struct = ItemStruct {
attrs: vec![parse_quote! {#[allow(non_camel_case_types)]}],
vis: vis.clone(),
struct_token: *struct_token,
ident: match_variant_ident.clone(),
generics: generics.clone(),
fields: match_variant_fields,
semi_token: *semi_token,
};
match_variant_struct.to_tokens(tokens);
let mask_type_body = if is_for_mask {
quote! {
::fayalite::__std::clone::Clone::clone(self)
}
} else {
let mask_value_struct = ItemStruct {
attrs: vec![parse_quote! {#[allow(non_camel_case_types)]}],
vis: vis.clone(),
struct_token: *struct_token,
ident: mask_value_ident.clone(),
generics: generics.clone(),
fields: mask_value_fields,
semi_token: *semi_token,
};
mask_value_struct.to_tokens(tokens);
let debug_mask_value_body = match fields_kind {
FieldsKind::Unit => quote! {
f.debug_struct(#mask_value_debug_ident).finish()
},
FieldsKind::Named(_) => quote! {
f.debug_struct(#mask_value_debug_ident)
#(.field(#unskipped_field_name_strs, &self.#unskipped_field_names))*
.finish()
},
FieldsKind::Unnamed(_) => quote! {
f.debug_tuple(#mask_value_debug_ident)
#(.field(&self.#unskipped_field_names))*
.finish()
},
};
quote! {
#[automatically_derived]
impl #impl_generics ::fayalite::__std::fmt::Debug
for #mask_value_ident #type_generics
#where_clause
{
fn fmt(
&self,
f: &mut ::fayalite::__std::fmt::Formatter<'_>,
) -> ::fayalite::__std::fmt::Result {
#debug_mask_value_body
}
}
}
.to_tokens(tokens);
quote! {
#mask_type_ident {
#(#unskipped_field_names:
::fayalite::ty::Type::mask_type(&self.#unskipped_field_names),)*
#(#phantom_data_field_name_slice: ::fayalite::__std::marker::PhantomData,)*
}
}
};
let debug_type_body = match fields_kind {
FieldsKind::Unit => quote! {
f.debug_struct(#type_struct_debug_ident).finish()
},
FieldsKind::Named(_) => quote! {
f.debug_struct(#type_struct_debug_ident)
#(.field(#unskipped_field_name_strs, &self.#unskipped_field_names))*
.finish()
},
FieldsKind::Unnamed(_) => quote! {
f.debug_tuple(#type_struct_debug_ident)
#(.field(&self.#unskipped_field_names))*
.finish()
},
};
for the_struct_ident in [&type_struct_ident, match_variant_ident]
.into_iter()
.chain(is_for_mask.then_some(mask_value_ident))
{
derive_clone_hash_eq_partialeq_for_struct(
the_struct_ident,
&generics,
&Vec::from_iter(
unskipped_field_names
.iter()
.cloned()
.chain(phantom_data_field_name.clone()),
),
)
.to_tokens(tokens);
}
let check_v = format_ident!("__v");
let field_checks = Vec::from_iter(fields.iter().map(|ParsedField { ty, name, .. }| {
quote_spanned! {ty.span()=>
__check_field(#check_v.#name);
}
}));
if static_.is_some() {
let (impl_generics, type_generics, where_clause) =
static_type_generics.split_for_impl();
quote! {
#[automatically_derived]
impl #impl_generics ::fayalite::ty::StaticType for #type_struct_ident #type_generics
#where_clause
{
fn static_type() -> Self {
Self {
#(#unskipped_field_names: ::fayalite::ty::StaticType::static_type(),)*
#(#phantom_data_field_name_slice:
::fayalite::__std::marker::PhantomData,)*
}
}
}
}
.to_tokens(tokens);
}
if !skip_check_fields {
quote! {
fn __check_field<T: ::fayalite::ty::Value>(_v: T)
where
<T as ::fayalite::expr::ToExpr>::Type: ::fayalite::ty::Type<Value = T>,
{}
fn __check_fields #impl_generics(#check_v: #target #type_generics)
#where_clause
{
#(#field_checks)*
}
}
.to_tokens(tokens);
}
let mut builder = Builder::new(builder_struct_ident.clone(), vis.clone());
for field in unskipped_fields.clone() {
builder.insert_field(
field.name.clone(),
|v| {
parse_quote_spanned! {v.span()=>
::fayalite::expr::ToExpr::to_expr(&#v)
}
},
|t| {
parse_quote_spanned! {t.span()=>
::fayalite::expr::Expr<<
<#t as ::fayalite::expr::ToExpr>::Type
as ::fayalite::ty::Type
>::Value>
}
},
|t| {
parse_quote_spanned! {t.span()=>
where
#t: ::fayalite::expr::ToExpr,
}
},
);
}
let builder = builder.finish_filling_in_fields();
builder.to_tokens(tokens);
let build_type_fields =
Vec::from_iter(unskipped_fields.clone().map(|ParsedField { name, .. }| {
let builder_field_name = &builder.get_field(name).unwrap().1.builder_field_name;
quote_spanned! {struct_ident.span()=>
#name: ::fayalite::expr::ToExpr::ty(&#builder_field_name)
}
}));
let build_expr_fields =
Vec::from_iter(unskipped_fields.clone().map(|ParsedField { name, .. }| {
let builder_field_name = &builder.get_field(name).unwrap().1.builder_field_name;
quote_spanned! {struct_ident.span()=>
#builder_field_name.to_canonical_dyn()
}
}));
let build_specified_fields = unskipped_fields.clone().map(
|ParsedField {
options: _,
vis: _,
name,
ty,
}| {
let ty = if is_for_mask {
parse_quote_spanned! {name.span()=>
::fayalite::expr::Expr<::fayalite::ty::AsMask<#ty>>
}
} else {
parse_quote_spanned! {name.span()=>
::fayalite::expr::Expr<#ty>
}
};
(name.clone(), ty)
},
);
let build_body = parse_quote_spanned! {struct_ident.span()=>
{
::fayalite::expr::ToExpr::to_expr(
&::fayalite::expr::ops::BundleLiteral::new_unchecked(
::fayalite::intern::Intern::intern(&[#(
#build_expr_fields,
)*][..]),
#type_struct_ident {
#(#build_type_fields,)*
#(#phantom_data_field_name_slice:
::fayalite::__std::marker::PhantomData,)*
},
),
)
}
};
builder
.make_build_method(
&Ident::new("build", struct_ident.span()),
build_specified_fields,
&generics,
&parse_quote_spanned! {struct_ident.span()=>
#type_struct_ident #type_generics
},
&parse_quote_spanned! {struct_ident.span()=>
::fayalite::expr::Expr<#target #type_generics>
},
build_body,
)
.to_tokens(tokens);
make_connect_impl(
*connect_inexact,
&generics,
&type_struct_ident,
unskipped_fields.clone().map(|field| {
let ty = &field.ty;
parse_quote_spanned! {field.name.span()=>
<#ty as ::fayalite::expr::ToExpr>::Type
}
}),
)
.to_tokens(tokens);
let empty_builder_ty = builder.ty([], Some(&parse_quote! { Self }), false);
quote! {
#[automatically_derived]
impl #impl_generics ::fayalite::__std::fmt::Debug for #type_struct_ident #type_generics
#where_clause
{
fn fmt(
&self,
f: &mut ::fayalite::__std::fmt::Formatter<'_>,
) -> ::fayalite::__std::fmt::Result {
#debug_type_body
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::ty::Type for #type_struct_ident #type_generics
#where_clause
{
type CanonicalType = ::fayalite::bundle::DynBundleType;
type Value = #target #type_generics;
type CanonicalValue = ::fayalite::bundle::DynBundle;
type MaskType = #mask_type_ident #type_generics;
type MaskValue = #mask_value_ident #type_generics;
type MatchVariant = #match_variant_ident #type_generics;
type MatchActiveScope = ();
type MatchVariantAndInactiveScope = ::fayalite::ty::MatchVariantWithoutScope<
#match_variant_ident #type_generics,
>;
type MatchVariantsIter = ::fayalite::__std::iter::Once<
<Self as ::fayalite::ty::Type>::MatchVariantAndInactiveScope,
>;
#[allow(unused_variables)]
fn match_variants<IO: ::fayalite::bundle::BundleValue>(
this: ::fayalite::expr::Expr<<Self as ::fayalite::ty::Type>::Value>,
module_builder: &mut ::fayalite::module::ModuleBuilder<
IO,
::fayalite::module::NormalModule,
>,
source_location: ::fayalite::source_location::SourceLocation,
) -> <Self as ::fayalite::ty::Type>::MatchVariantsIter
where
<IO as ::fayalite::expr::ToExpr>::Type:
::fayalite::bundle::BundleType<Value = IO>,
{
::fayalite::__std::iter::once(::fayalite::ty::MatchVariantWithoutScope(
#match_variant_ident {
#(#unskipped_field_names: this.field(#unskipped_field_name_strs),)*
#(#phantom_data_field_name_slice:
::fayalite::__std::marker::PhantomData,)*
},
))
}
fn mask_type(&self) -> <Self as ::fayalite::ty::Type>::MaskType {
#mask_type_body
}
fn canonical(&self) -> <Self as ::fayalite::ty::Type>::CanonicalType {
let fields = ::fayalite::bundle::BundleType::fields(self);
::fayalite::bundle::DynBundleType::new(fields)
}
fn source_location(&self) -> ::fayalite::source_location::SourceLocation {
::fayalite::source_location::SourceLocation::caller()
}
fn type_enum(&self) -> ::fayalite::ty::TypeEnum {
::fayalite::ty::TypeEnum::BundleType(::fayalite::ty::Type::canonical(self))
}
fn from_canonical_type(t: <Self as ::fayalite::ty::Type>::CanonicalType) -> Self {
let [#(#unskipped_field_vars),*] = *::fayalite::bundle::BundleType::fields(&t)
else {
::fayalite::__std::panic!("wrong number of fields");
};
Self {
#(#unskipped_field_names: #unskipped_field_vars.from_canonical_type_helper(
#unskipped_field_name_strs,
#unskipped_field_flips,
),)*
#(#phantom_data_field_name_slice: ::fayalite::__std::marker::PhantomData,)*
}
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::ty::TypeWithDeref for #type_struct_ident #type_generics
#where_clause
{
#[allow(unused_variables)]
fn expr_deref(this: &::fayalite::expr::Expr<<Self as ::fayalite::ty::Type>::Value>)
-> &<Self as ::fayalite::ty::Type>::MatchVariant
{
::fayalite::intern::Interned::<_>::into_inner(
::fayalite::intern::Intern::intern_sized(#match_variant_ident {
#(#unskipped_field_names: this.field(#unskipped_field_name_strs),)*
#(#phantom_data_field_name_slice:
::fayalite::__std::marker::PhantomData,)*
}),
)
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::bundle::BundleType for #type_struct_ident #type_generics
#where_clause
{
type Builder = #empty_builder_ty;
fn builder() -> <Self as ::fayalite::bundle::BundleType>::Builder {
#empty_builder_ty::new()
}
fn fields(&self) -> ::fayalite::intern::Interned<
[::fayalite::bundle::FieldType<::fayalite::intern::Interned<
dyn ::fayalite::ty::DynCanonicalType,
>>],
>
{
::fayalite::intern::Intern::intern(&[#(
::fayalite::bundle::FieldType {
name: ::fayalite::intern::Intern::intern(#unskipped_field_name_strs),
flipped: #unskipped_field_flips,
ty: ::fayalite::ty::DynType::canonical_dyn(
&self.#unskipped_field_names,
),
},
)*][..])
}
fn fields_hint() -> ::fayalite::bundle::FieldsHint {
::fayalite::bundle::FieldsHint::new([#(
::fayalite::bundle::FieldType {
name: ::fayalite::intern::Intern::intern(#unskipped_field_name_strs),
flipped: #unskipped_field_flips,
ty: ::fayalite::bundle::TypeHint::<#field_types>::intern_dyn(),
},
)*], false)
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::expr::ToExpr for #target #type_generics
#where_clause
{
type Type = #type_struct_ident #type_generics;
fn ty(&self) -> <Self as ::fayalite::expr::ToExpr>::Type {
#type_struct_ident {
#(#unskipped_field_names: ::fayalite::expr::ToExpr::ty(
&self.#unskipped_field_names,
),)*
#(#phantom_data_field_name_slice: ::fayalite::__std::marker::PhantomData,)*
}
}
fn to_expr(&self) -> ::fayalite::expr::Expr<Self> {
::fayalite::expr::Expr::from_value(self)
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::ty::Value for #target #type_generics
#where_clause
{
fn to_canonical(&self) -> <
<Self as ::fayalite::expr::ToExpr>::Type
as ::fayalite::ty::Type
>::CanonicalValue
{
let ty = ::fayalite::ty::Type::canonical(&::fayalite::expr::ToExpr::ty(self));
::fayalite::bundle::DynBundle::new(ty, ::fayalite::__std::sync::Arc::new([
#(::fayalite::ty::DynValueTrait::to_canonical_dyn(
&self.#unskipped_field_names,
),)*
]))
}
}
#[automatically_derived]
impl #impl_generics ::fayalite::bundle::BundleValue for #target #type_generics
#where_clause
{
}
}
.to_tokens(tokens);
}
}
impl ToTokens for ParsedStruct {
fn to_tokens(&self, tokens: &mut TokenStream) {
let ParsedStructNames {
ident: struct_ident,
type_struct_debug_ident,
type_struct_ident,
match_variant_ident,
builder_struct_ident,
mask_match_variant_ident,
mask_type_ident,
mask_type_debug_ident,
mask_value_ident,
mask_value_debug_ident,
mask_builder_struct_ident,
} = &self.names;
macro_rules! unwrap_or_set {
($(let $var:ident =? $fallback_value:expr;)*) => {
$(let $var = $var.clone().unwrap_or_else(|| $fallback_value);)*
};
}
unwrap_or_set! {
let type_struct_debug_ident =? format!("{struct_ident}::Type");
let match_variant_ident =? format_ident!("__{}__MatchVariant", struct_ident);
let builder_struct_ident =? format_ident!("__{}__Builder", struct_ident);
let mask_match_variant_ident =? format_ident!("__AsMask__{}__MatchVariant", struct_ident);
let mask_type_ident =? format_ident!("__AsMask__{}__Type", struct_ident);
let mask_type_debug_ident =? format!("AsMask<{struct_ident}>::Type");
let mask_value_ident =? format_ident!("__AsMask__{}", struct_ident);
let mask_value_debug_ident =? format!("AsMask<{struct_ident}>");
let mask_builder_struct_ident =? format_ident!("__AsMask__{}__Builder", struct_ident);
}
let target = get_target(&self.options.body.target, struct_ident);
let names = ParsedStructNames {
ident: struct_ident.clone(),
type_struct_debug_ident: &type_struct_debug_ident,
type_struct_ident: type_struct_ident.clone(),
match_variant_ident: &match_variant_ident,
builder_struct_ident: &builder_struct_ident,
mask_match_variant_ident: &mask_match_variant_ident,
mask_type_ident: &mask_type_ident,
mask_type_debug_ident: &mask_type_debug_ident,
mask_value_ident: &mask_value_ident,
mask_value_debug_ident: &mask_value_debug_ident,
mask_builder_struct_ident: &mask_builder_struct_ident,
};
self.write_body(target, names, false, tokens);
let mask_names = ParsedStructNames {
ident: mask_value_ident.clone(),
type_struct_debug_ident: &mask_type_debug_ident,
type_struct_ident: mask_type_ident.clone(),
match_variant_ident: &mask_match_variant_ident,
builder_struct_ident: &mask_builder_struct_ident,
mask_match_variant_ident: &mask_match_variant_ident,
mask_type_ident: &mask_type_ident,
mask_type_debug_ident: &mask_type_debug_ident,
mask_value_ident: &mask_value_ident,
mask_value_debug_ident: &mask_value_debug_ident,
mask_builder_struct_ident: &mask_builder_struct_ident,
};
self.write_body(mask_value_ident.clone().into(), mask_names, true, tokens);
}
}
pub(crate) fn value_derive_struct(mut item: ItemStruct) -> syn::Result<TokenStream> {
let item = ParsedStruct::parse(&mut item)?;
let outline_generated = item.options.body.outline_generated;
let mut contents = quote! {
const _: () = {
#item
};
};
if outline_generated.is_some() {
contents = crate::outline_generated(contents, "value-struct-");
}
Ok(contents)
}

View file

@ -16,4 +16,4 @@ version.workspace = true
proc-macro = true
[dependencies]
fayalite-proc-macros-impl = { workspace = true }
fayalite-proc-macros-impl.workspace = true

View file

@ -2,7 +2,7 @@
// See Notices.txt for copyright information
//! proc macros for `fayalite`
//!
//! see `fayalite::hdl_module` and `fayalite::ty::Value` for docs
//! see `fayalite::hdl_module` and `fayalite::hdl` for docs
// intentionally not documented here, see `fayalite::hdl_module` for docs
#[proc_macro_attribute]
@ -10,16 +10,19 @@ pub fn hdl_module(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
match fayalite_proc_macros_impl::module(attr.into(), item.into()) {
match fayalite_proc_macros_impl::hdl_module(attr.into(), item.into()) {
Ok(retval) => retval.into(),
Err(err) => err.into_compile_error().into(),
}
}
// intentionally not documented here, see `fayalite::ty::Value` for docs
#[proc_macro_derive(Value, attributes(hdl))]
pub fn value_derive(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
match fayalite_proc_macros_impl::value_derive(item.into()) {
// intentionally not documented here, see `fayalite::hdl` for docs
#[proc_macro_attribute]
pub fn hdl(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
match fayalite_proc_macros_impl::hdl_attr(attr.into(), item.into()) {
Ok(retval) => retval.into(),
Err(err) => err.into_compile_error().into(),
}

View file

@ -13,11 +13,11 @@ rust-version.workspace = true
version.workspace = true
[dependencies]
indexmap = { workspace = true }
prettyplease = { workspace = true }
proc-macro2 = { workspace = true }
quote = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
syn = { workspace = true }
thiserror = { workspace = true }
indexmap.workspace = true
prettyplease.workspace = true
proc-macro2.workspace = true
quote.workspace = true
serde.workspace = true
serde_json.workspace = true
syn.workspace = true
thiserror.workspace = true

View file

@ -14,22 +14,27 @@ rust-version.workspace = true
version.workspace = true
[dependencies]
bitvec = { workspace = true }
hashbrown = { workspace = true }
num-bigint = { workspace = true }
num-traits = { workspace = true }
fayalite-proc-macros = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
clap = { version = "4.5.9", features = ["derive", "env"] }
eyre = "0.6.12"
which = "6.0.1"
bitvec.workspace = true
blake3.workspace = true
clap.workspace = true
ctor.workspace = true
eyre.workspace = true
fayalite-proc-macros.workspace = true
hashbrown.workspace = true
jobslot.workspace = true
num-bigint.workspace = true
num-traits.workspace = true
os_pipe.workspace = true
serde_json.workspace = true
serde.workspace = true
tempfile.workspace = true
which.workspace = true
[dev-dependencies]
trybuild = { workspace = true }
trybuild.workspace = true
[build-dependencies]
fayalite-visit-gen = { workspace = true }
fayalite-visit-gen.workspace = true
[features]
unstable-doc = []

View file

@ -4,6 +4,7 @@ use fayalite_visit_gen::parse_and_generate;
use std::{env, fs, path::Path};
fn main() {
println!("cargo::rustc-check-cfg=cfg(todo)");
let path = "visit_types.json";
println!("cargo::rerun-if-changed={path}");
println!("cargo::rerun-if-changed=build.rs");

View file

@ -1,11 +1,7 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use clap::Parser;
use fayalite::{
cli,
clock::{Clock, ClockDomain},
hdl_module,
int::{DynUInt, DynUIntType, IntCmp, IntTypeTrait, UInt},
reset::{SyncReset, ToReset},
};
use fayalite::{cli, prelude::*};
#[hdl_module]
fn blinky(clock_frequency: u64) {
@ -19,21 +15,21 @@ fn blinky(clock_frequency: u64) {
rst: rst.to_reset(),
};
let max_value = clock_frequency / 2 - 1;
let int_ty = DynUIntType::range_inclusive(0..=max_value);
let int_ty = UInt::range_inclusive(0..=max_value);
#[hdl]
let counter: DynUInt = m.reg_builder().clock_domain(cd).reset(int_ty.literal(0));
let counter_reg: UInt = reg_builder().clock_domain(cd).reset(0u8.cast_to(int_ty));
#[hdl]
let output_reg: UInt<1> = m.reg_builder().clock_domain(cd).reset_default();
let output_reg: Bool = reg_builder().clock_domain(cd).reset(false);
#[hdl]
if counter.cmp_eq(max_value) {
m.connect_any(counter, 0u8);
m.connect(output_reg, !output_reg);
if counter_reg.cmp_eq(max_value) {
connect_any(counter_reg, 0u8);
connect(output_reg, !output_reg);
} else {
m.connect_any(counter, counter + 1_hdl_u1);
connect_any(counter_reg, counter_reg + 1_hdl_u1);
}
#[hdl]
let led: UInt<1> = m.output();
m.connect(led, output_reg);
let led: Bool = m.output();
connect(led, output_reg);
}
#[derive(Parser)]

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
#![doc = include_str!("../README.md")]
//!
@ -10,7 +12,7 @@
//! function to add inputs/outputs and other components to that module.
//!
//! ```
//! # use fayalite::{hdl_module, int::UInt};
//! # use fayalite::prelude::*;
//! #
//! #[hdl_module]
//! pub fn example_module() {
@ -18,7 +20,7 @@
//! let an_input: UInt<10> = m.input(); // create an input that is a 10-bit unsigned integer
//! #[hdl]
//! let some_output: UInt<10> = m.output();
//! m.connect(some_output, an_input); // assigns the value of `an_input` to `some_output`
//! connect(some_output, an_input); // assigns the value of `an_input` to `some_output`
//! }
//! ```

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # Fayalite Modules
//!
//! The [`#[hdl_module]`][`crate::hdl_module`] attribute is applied to a Rust

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! These are for when you want to use modules written in
//! some other language, such as Verilog.
//!
@ -11,8 +13,6 @@
//! * [`parameter_raw_verilog()`][`ModuleBuilder::parameter_raw_verilog`]
//! * [`parameter()`][`ModuleBuilder::parameter`]
//!
//! These use the [`ExternModule`][`crate::module::ExternModule`] tag type.
//!
//! [inputs/outputs]: crate::_docs::modules::module_bodies::hdl_let_statements::inputs_outputs
#[allow(unused)]

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # Module Function Bodies
//!
//! The `#[hdl_module]` attribute lets you have statements/expressions with `#[hdl]` annotations

View file

@ -1,24 +1,26 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # `#[hdl]` Array Expressions
//!
//! `#[hdl]` can be used on Array Expressions to construct an [`Array<[T; N]>`][Array] expression:
//! `#[hdl]` can be used on Array Expressions to construct an [`Array<[T; N]>`][type@Array] expression:
//!
//! ```
//! # use fayalite::{hdl_module, int::UInt, array::Array};
//! # use fayalite::prelude::*;
//! # #[hdl_module]
//! # fn module() {
//! #[hdl]
//! let v: UInt<8> = m.input();
//! #[hdl]
//! let w: Array<[UInt<8>; 4]> = m.wire();
//! m.connect(
//! let w: Array<UInt<8>, 4> = wire();
//! connect(
//! w,
//! #[hdl]
//! [4_hdl_u8, v, 3_hdl_u8, (v + 7_hdl_u8).cast()] // you can make an array like this
//! [4_hdl_u8, v, 3_hdl_u8, (v + 7_hdl_u8).cast_to_static()] // you can make an array like this
//! );
//! m.connect(
//! connect(
//! w,
//! #[hdl]
//! [(v + 1_hdl_u8).cast(); 4] // or you can make an array repeat like this
//! [(v + 1_hdl_u8).cast_to_static(); 4] // or you can make an array repeat like this
//! );
//! # }
//! ```

View file

@ -1,10 +1,11 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # `#[hdl] if` Statements
//!
//! `#[hdl] if` statements behave similarly to Rust `if` statements, except they end up as muxes
//! and stuff in the final hardware instead of being run when the fayalite module is being created.
//!
//! The condition of an `#[hdl] if` statement must have type [`UInt<1>`] or [`DynUInt`] with
//! `width() == 1` or be an [expression][Expr] of one of those types.
//! The condition of an `#[hdl] if` statement must have type [`Expr<Bool>`][Bool].
//!
//! `#[hdl] if` statements' bodies must evaluate to type `()` for now.
//!
@ -14,7 +15,4 @@
//! [match]: super::hdl_match_statements
#[allow(unused)]
use crate::{
expr::Expr,
int::{DynUInt, UInt},
};
use crate::int::Bool;

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! ## `#[hdl] let` statements
pub mod inputs_outputs;

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! ### Inputs/Outputs
//!
//! Inputs/Outputs create a Rust variable with type [`Expr<T>`] where `T` is the type of the input/output.
@ -6,14 +8,14 @@
//! so you should read it.
//!
//! ```
//! # use fayalite::{hdl_module, int::UInt, expr::Expr, array::Array};
//! # use fayalite::prelude::*;
//! # #[hdl_module]
//! # fn module() {
//! #[hdl]
//! let my_input: UInt<10> = m.input();
//! let _: Expr<UInt<10>> = my_input; // my_input has type Expr<UInt<10>>
//! #[hdl]
//! let my_output: Array<[UInt<10>; 3]> = m.output();
//! let my_output: Array<UInt<10>, 3> = m.output();
//! # }
//! ```
//!

View file

@ -1,18 +1,20 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! ### Module Instances
//!
//! module instances are kinda like the hardware equivalent of calling a function,
//! you can create them like so:
//!
//! ```
//! # use fayalite::{hdl_module, int::UInt, array::Array};
//! # use fayalite::prelude::*;
//! # #[hdl_module]
//! # fn module() {
//! #[hdl]
//! let my_instance = m.instance(some_module());
//! let my_instance = instance(some_module());
//! // now you can use `my_instance`'s inputs/outputs like so:
//! #[hdl]
//! let v: UInt<3> = m.input();
//! m.connect(my_instance.a, v);
//! connect(my_instance.a, v);
//! #[hdl_module]
//! fn some_module() {
//! #[hdl]

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # Memories
//!
//! Memories are optimized for storing large amounts of data.
@ -7,12 +9,12 @@
//!
//! There are several different ways to create a memory:
//!
//! ## using [`ModuleBuilder::memory()`]
//! ## using [`memory()`]
//!
//! This way you have to set the [`depth`][`MemBuilder::depth`] separately.
//!
//! ```
//! # use fayalite::{hdl_module, int::UInt, clock::ClockDomain};
//! # use fayalite::prelude::*;
//! # #[hdl_module]
//! # fn module() {
//! // first, we need some IO
@ -25,45 +27,45 @@
//!
//! // now create the memory
//! #[hdl]
//! let mut my_memory = m.memory();
//! let mut my_memory = memory();
//! my_memory.depth(256); // the memory has 256 elements
//!
//! let read_port = my_memory.new_read_port();
//!
//! // connect up the read port
//! m.connect_any(read_port.addr, read_addr);
//! m.connect(read_port.en, 1_hdl_u1);
//! m.connect(read_port.clk, cd.clk);
//! m.connect(read_data, read_port.data);
//! connect_any(read_port.addr, read_addr);
//! connect(read_port.en, true);
//! connect(read_port.clk, cd.clk);
//! connect(read_data, read_port.data);
//!
//! // we need more IO for the write port
//! #[hdl]
//! let write_addr: UInt<8> = m.input();
//! #[hdl]
//! let do_write: UInt<1> = m.input();
//! let do_write: Bool = m.input();
//! #[hdl]
//! let write_data: UInt<8> = m.input();
//!
//! let write_port = my_memory.new_write_port();
//!
//! m.connect_any(write_port.addr, write_addr);
//! m.connect(write_port.en, do_write);
//! m.connect(write_port.clk, cd.clk);
//! m.connect(write_port.data, write_port.data);
//! m.connect(write_port.mask, 1_hdl_u1);
//! connect_any(write_port.addr, write_addr);
//! connect(write_port.en, do_write);
//! connect(write_port.clk, cd.clk);
//! connect(write_port.data, write_port.data);
//! connect(write_port.mask, true);
//! # }
//! ```
//!
//! ## using [`ModuleBuilder::memory_array()`]
//! ## using [`memory_array()`]
//!
//! this allows you to specify the memory's underlying array type directly.
//!
//! ```
//! # use fayalite::{hdl_module, int::UInt, memory::MemBuilder};
//! # use fayalite::prelude::*;
//! # #[hdl_module]
//! # fn module() {
//! #[hdl]
//! let mut my_memory: MemBuilder<[UInt<8>; 256]> = m.memory_array();
//! let mut my_memory: MemBuilder<UInt<8>, ConstUsize<256>> = memory_array();
//!
//! let read_port = my_memory.new_read_port();
//! // ...
@ -72,25 +74,22 @@
//! # }
//! ```
//!
//! ## using [`ModuleBuilder::memory_with_init()`]
//! ## using [`memory_with_init()`]
//!
//! This allows you to deduce the memory's array type from the data used to initialize the memory.
//!
//! ```
//! # use fayalite::{hdl_module, int::UInt};
//! # use fayalite::prelude::*;
//! # #[hdl_module]
//! # fn module() {
//! # #[hdl]
//! # let read_addr: UInt<2> = m.input();
//! #[hdl]
//! let mut my_memory = m.memory_with_init(
//! #[hdl]
//! [0x12_hdl_u8, 0x34_hdl_u8, 0x56_hdl_u8, 0x78_hdl_u8],
//! );
//! let mut my_memory = memory_with_init([0x12_hdl_u8, 0x34_hdl_u8, 0x56_hdl_u8, 0x78_hdl_u8]);
//!
//! let read_port = my_memory.new_read_port();
//! // note that `read_addr` is `UInt<2>` since the memory only has 4 elements
//! m.connect_any(read_port.addr, read_addr);
//! connect_any(read_port.addr, read_addr);
//! // ...
//! let write_port = my_memory.new_write_port();
//! // ...
@ -98,4 +97,4 @@
//! ```
#[allow(unused)]
use crate::{memory::MemBuilder, module::ModuleBuilder};
use crate::prelude::*;

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! ### Registers
//!
//! Registers are memory devices that will change their state only on a clock
@ -7,20 +9,23 @@
//!
//! Registers follow [connection semantics], which are unlike assignments in software, so you should read it.
//!
//! By convention, register names end in `_reg` -- this helps you tell which values are written
//! immediately or on the next clock edge when connecting to them.
//!
//! ```
//! # use fayalite::{hdl_module, int::UInt, array::Array, clock::ClockDomain};
//! # use fayalite::prelude::*;
//! # #[hdl_module]
//! # fn module() {
//! # #[hdl]
//! # let v: UInt<1> = m.input();
//! # let v: Bool = m.input();
//! #[hdl]
//! let cd: ClockDomain = m.input();
//! #[hdl]
//! let my_register: UInt<8> = m.reg_builder().clock_domain(cd).reset(8_hdl_u8);
//! let my_reg: UInt<8> = reg_builder().clock_domain(cd).reset(8_hdl_u8);
//! #[hdl]
//! if v {
//! // my_register is only changed when both `v` is set and `cd`'s clock edge occurs.
//! m.connect(my_register, 0x45_hdl_u8);
//! // my_reg is only changed when both `v` is set and `cd`'s clock edge occurs.
//! connect(my_reg, 0x45_hdl_u8);
//! }
//! # }
//! ```

View file

@ -1,27 +1,29 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! ### Wires
//!
//! Wires are kinda like variables, but unlike registers,
//! they have no memory (they're combinatorial).
//! You must [connect][`ModuleBuilder::connect`] to all wires, so they have a defined value.
//! You must [connect] to all wires, so they have a defined value.
//!
//! Wires create a Rust variable with type [`Expr<T>`] where `T` is the type of the wire.
//!
//! Wires follow [connection semantics], which are unlike assignments in software, so you should read it.
//!
//! ```
//! # use fayalite::{hdl_module, int::UInt, array::Array, clock::ClockDomain};
//! # use fayalite::prelude::*;
//! # #[hdl_module]
//! # fn module() {
//! # #[hdl]
//! # let v: UInt<1> = m.input();
//! # let v: Bool = m.input();
//! #[hdl]
//! let my_wire: UInt<8> = m.wire();
//! let my_wire: UInt<8> = wire();
//! #[hdl]
//! if v {
//! m.connect(my_wire, 0x45_hdl_u8);
//! connect(my_wire, 0x45_hdl_u8);
//! } else {
//! // wires must be connected to under all conditions
//! m.connect(my_wire, 0x23_hdl_u8);
//! connect(my_wire, 0x23_hdl_u8);
//! }
//! # }
//! ```
@ -29,4 +31,4 @@
//! [connection semantics]: crate::_docs::semantics::connection_semantics
#[allow(unused)]
use crate::{expr::Expr, module::ModuleBuilder};
use crate::prelude::*;

View file

@ -1,9 +1,10 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # `_hdl`-suffixed literals
//!
//! You can have integer literals with an arbitrary number of bits like so:
//!
//! `_hdl`-suffixed literals have type [`Expr<UInt<N>>`] or [`Expr<SInt<N>>`]
//! ... which are basically just [`UInt<N>`] or [`SInt<N>`] converted to an expression.
//! `_hdl`-suffixed literals have type [`Expr<UInt<N>>`] or [`Expr<SInt<N>>`].
//!
//! ```
//! # #[fayalite::hdl_module]

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # `#[hdl] match` Statements
//!
//! `#[hdl] match` statements behave similarly to Rust `match` statements, except they end up as muxes
@ -6,4 +8,4 @@
//! `#[hdl] match` statements' bodies must evaluate to type `()` for now.
//!
//! `#[hdl] match` statements can only match one level of struct/enum pattern for now,
//! e.g. you can match with the pattern `Some(v)`, but not `Some(Some(_))`.
//! e.g. you can match with the pattern `HdlSome(v)`, but not `HdlSome(HdlSome(_))`.

View file

@ -1,28 +1,27 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # `#[hdl]` Struct/Variant Expressions
//!
//! Note: Structs are also known as [Bundles] when used in Fayalite, the Bundle name comes from [FIRRTL].
//!
//! [Bundles]: crate::bundle::BundleValue
//! [Bundles]: crate::bundle::BundleType
//! [FIRRTL]: https://github.com/chipsalliance/firrtl-spec
//!
//! `#[hdl]` can be used on Struct/Variant Expressions to construct a value of that
//! struct/variant's type. They can also be used on tuples.
//! `#[hdl]` can be used on Struct Expressions to construct a value of that
//! struct's type. They can also be used on tuples.
//!
//! ```
//! # use fayalite::{hdl_module, int::UInt, array::Array, ty::Value};
//! #[derive(Value, Clone, PartialEq, Eq, Hash, Debug)]
//! #[hdl(static)]
//! # use fayalite::prelude::*;
//! #[hdl]
//! pub struct MyStruct {
//! pub a: UInt<8>,
//! pub b: UInt<16>,
//! }
//!
//! #[derive(Value, Clone, PartialEq, Eq, Hash, Debug)]
//! #[hdl]
//! pub enum MyEnum {
//! A,
//! B {
//! v: UInt<32>,
//! },
//! B(UInt<32>),
//! }
//!
//! # #[hdl_module]
@ -30,8 +29,8 @@
//! #[hdl]
//! let v: UInt<8> = m.input();
//! #[hdl]
//! let my_struct: MyStruct = m.wire();
//! m.connect(
//! let my_struct: MyStruct = wire();
//! connect(
//! my_struct,
//! #[hdl]
//! MyStruct {
@ -40,15 +39,14 @@
//! },
//! );
//! #[hdl]
//! let my_enum: MyEnum = m.wire();
//! m.connect(
//! let my_enum: MyEnum = wire();
//! connect(
//! my_enum,
//! #[hdl]
//! MyEnum::B { v: 12345678_hdl_u32 },
//! MyEnum.B(12345678_hdl_u32),
//! );
//! #[hdl]
//! let some_tuple: (UInt<4>, UInt<12>) = m.wire();
//! m.connect(
//! let some_tuple: (UInt<4>, UInt<12>) = wire();
//! connect(
//! some_tuple,
//! #[hdl]
//! (12_hdl_u4, 3421_hdl_u12),
@ -57,4 +55,4 @@
//! ```
#[allow(unused)]
use crate::array::Array;
use crate::prelude::*;

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # Normal Modules
//!
//! These use the [`NormalModule`][`crate::module::NormalModule`] tag type.
//!
//! See also: [Extern Modules][`super::extern_module`]
//! See also: [Module Bodies][`super::module_bodies`]

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # Fayalite Semantics
//!
//! Fayalite's semantics are based on [FIRRTL]. Due to their significance, some of the semantics are also documented here.

View file

@ -1,3 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
//! # Connection Semantics
//!
//! Fayalite's connection semantics are unlike assignments in software, so be careful!
@ -20,62 +22,60 @@
//! Connection Semantics Example:
//!
//! ```
//! # use fayalite::{hdl_module, int::UInt};
//! # use fayalite::prelude::*;
//! # #[hdl_module]
//! # fn module() {
//! #[hdl]
//! let a: UInt<8> = m.wire();
//! let a: UInt<8> = wire();
//! #[hdl]
//! let b: UInt<8> = m.output();
//!
//! // doesn't actually affect anything, since `a` is completely overwritten later
//! m.connect(a, 5_hdl_u8);
//! connect(a, 5_hdl_u8);
//!
//! // here `a` has value `7` since the last connection assigns
//! // `7` to `a`, so `b` has value `7` too.
//! m.connect(b, a);
//! connect(b, a);
//!
//! // this is the last `connect` to `a`, so this `connect` determines `a`'s value
//! m.connect(a, 7_hdl_u8);
//! connect(a, 7_hdl_u8);
//! # }
//! ```
//!
//! # Conditional Connection Semantics
//!
//! ```
//! # use fayalite::{hdl_module, int::UInt};
//! # use fayalite::prelude::*;
//! # #[hdl_module]
//! # fn module() {
//! #[hdl]
//! let cond: UInt<1> = m.input();
//! let cond: Bool = m.input();
//! #[hdl]
//! let a: UInt<8> = m.wire();
//! let a: UInt<8> = wire();
//! #[hdl]
//! let b: UInt<8> = m.output();
//!
//! // this is the last `connect` to `a` when `cond` is `0`
//! m.connect(a, 5_hdl_u8);
//! connect(a, 5_hdl_u8);
//!
//! // here `a` has value `7` if `cond` is `1` since the last connection assigns
//! // `7` to `a`, so `b` has value `7` too, otherwise `a` (and therefore `b`)
//! // have value `5` since then the connection assigning `7` is in a
//! // conditional block where the condition doesn't hold.
//! m.connect(b, a);
//! connect(b, a);
//!
//! #[hdl]
//! if cond {
//! // this is the last `connect` to `a` when `cond` is `1`
//! m.connect(a, 7_hdl_u8);
//! connect(a, 7_hdl_u8);
//! }
//! # }
//! ```
//!
//! [conditional block]: self#conditional-connection-semantics
//! [`connect()`]: ModuleBuilder::connect
//! [`connect_any()`]: ModuleBuilder::connect_any
//! [wire]: crate::_docs::modules::module_bodies::hdl_let_statements::wires
//! [if]: crate::_docs::modules::module_bodies::hdl_if_statements
//! [FIRRTL]: https://github.com/chipsalliance/firrtl-spec
#[allow(unused)]
use crate::module::ModuleBuilder;
use crate::prelude::*;

View file

@ -1,13 +1,14 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
expr::Target,
expr::target::Target,
intern::{Intern, Interned},
};
use serde::{Deserialize, Serialize};
use std::{
fmt,
hash::{Hash, Hasher},
iter::FusedIterator,
ops::Deref,
};
@ -118,12 +119,88 @@ pub struct CustomFirrtlAnnotation {
pub additional_fields: CustomFirrtlAnnotationFields,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
pub struct DontTouchAnnotation;
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
pub struct SVAttributeAnnotation {
pub text: Interned<str>,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
pub struct BlackBoxInlineAnnotation {
pub path: Interned<str>,
pub text: Interned<str>,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
pub struct BlackBoxPathAnnotation {
pub path: Interned<str>,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, Serialize, Deserialize)]
pub struct DocStringAnnotation {
pub text: Interned<str>,
}
macro_rules! make_annotation_enum {
(
$(#[$meta:meta])*
$vis:vis enum $Annotation:ident {
$($Variant:ident($T:ident),)*
}
) => {
$(#[$meta])*
$vis enum $Annotation {
$($Variant($T),)*
}
$(impl IntoAnnotations for $T {
type IntoAnnotations = [$Annotation; 1];
fn into_annotations(self) -> Self::IntoAnnotations {
[$Annotation::$Variant(self)]
}
}
impl IntoAnnotations for &'_ $T {
type IntoAnnotations = [$Annotation; 1];
fn into_annotations(self) -> Self::IntoAnnotations {
[$Annotation::$Variant(*self)]
}
}
impl IntoAnnotations for &'_ mut $T {
type IntoAnnotations = [$Annotation; 1];
fn into_annotations(self) -> Self::IntoAnnotations {
[$Annotation::$Variant(*self)]
}
}
impl IntoAnnotations for Box<$T> {
type IntoAnnotations = [$Annotation; 1];
fn into_annotations(self) -> Self::IntoAnnotations {
[$Annotation::$Variant(*self)]
}
})*
};
}
make_annotation_enum! {
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum Annotation {
DontTouch,
DontTouch(DontTouchAnnotation),
SVAttribute(SVAttributeAnnotation),
BlackBoxInline(BlackBoxInlineAnnotation),
BlackBoxPath(BlackBoxPathAnnotation),
DocString(DocStringAnnotation),
CustomFirrtl(CustomFirrtlAnnotation),
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct TargetedAnnotation {
@ -187,10 +264,70 @@ impl IntoAnnotations for &'_ mut Annotation {
}
}
impl<T: IntoIterator<Item = Annotation>> IntoAnnotations for T {
type IntoAnnotations = Self;
pub struct IterIntoAnnotations<T: Iterator<Item: IntoAnnotations>> {
outer: T,
inner: Option<<<T::Item as IntoAnnotations>::IntoAnnotations as IntoIterator>::IntoIter>,
}
impl<T: Iterator<Item: IntoAnnotations>> Iterator for IterIntoAnnotations<T> {
type Item = Annotation;
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(inner) = &mut self.inner {
let Some(retval) = inner.next() else {
self.inner = None;
continue;
};
return Some(retval);
} else {
self.inner = Some(self.outer.next()?.into_annotations().into_iter());
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
if let (0, Some(0)) = self.outer.size_hint() {
self.inner
.as_ref()
.map(|v| v.size_hint())
.unwrap_or((0, Some(0)))
} else {
(
self.inner.as_ref().map(|v| v.size_hint().0).unwrap_or(0),
None,
)
}
}
fn fold<B, F>(self, init: B, f: F) -> B
where
Self: Sized,
F: FnMut(B, Self::Item) -> B,
{
self.inner
.into_iter()
.chain(self.outer.map(|v| v.into_annotations().into_iter()))
.flatten()
.fold(init, f)
}
}
impl<
T: FusedIterator<
Item: IntoAnnotations<IntoAnnotations: IntoIterator<IntoIter: FusedIterator>>,
>,
> FusedIterator for IterIntoAnnotations<T>
{
}
impl<T: IntoIterator<Item: IntoAnnotations>> IntoAnnotations for T {
type IntoAnnotations = IterIntoAnnotations<T::IntoIter>;
fn into_annotations(self) -> Self::IntoAnnotations {
self
IterIntoAnnotations {
outer: self.into_iter(),
inner: None,
}
}
}

View file

@ -1,671 +1,220 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
bundle::{BundleType, BundleValue},
expr::{
ops::{ArrayIndex, ArrayLiteral, ExprIndex},
Expr, ToExpr,
},
intern::{Intern, Interned, InternedCompare, Memoize},
module::{
transform::visit::{Fold, Folder, Visit, Visitor},
ModuleBuilder, NormalModule,
},
expr::{ops::ArrayIndex, Expr, ToExpr},
int::{DynSize, KnownSize, Size, SizeType, DYN_SIZE},
intern::{Intern, Interned, LazyInterned},
module::transform::visit::{Fold, Folder, Visit, Visitor},
source_location::SourceLocation,
ty::{
CanonicalType, CanonicalTypeKind, CanonicalValue, Connect, DynCanonicalType,
DynCanonicalValue, DynType, DynValueTrait, MatchVariantWithoutScope, StaticType,
StaticValue, Type, TypeEnum, Value, ValueEnum,
CanonicalType, MatchVariantWithoutScope, StaticType, Type, TypeProperties, TypeWithDeref,
},
util::{ConstBool, GenericConstBool, MakeMutSlice},
util::ConstUsize,
};
use bitvec::{slice::BitSlice, vec::BitVec};
use std::{
any::Any,
borrow::{Borrow, BorrowMut},
fmt,
hash::Hash,
marker::PhantomData,
ops::IndexMut,
sync::Arc,
use std::ops::Index;
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct ArrayType<T: Type = CanonicalType, Len: Size = DynSize> {
element: LazyInterned<T>,
len: Len::SizeType,
type_properties: TypeProperties,
}
impl<T: Type, Len: Size> std::fmt::Debug for ArrayType<T, Len> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Array<{:?}, {}>", self.element, self.len())
}
}
pub type Array<T = CanonicalType, const LEN: usize = DYN_SIZE> = ArrayType<T, ConstUsize<LEN>>;
#[allow(non_upper_case_globals)]
pub const Array: ArrayWithoutGenerics = ArrayWithoutGenerics;
#[allow(non_upper_case_globals)]
pub const ArrayType: ArrayWithoutGenerics = ArrayWithoutGenerics;
impl<T: Type, Len: Size> ArrayType<T, Len> {
const fn make_type_properties(element: TypeProperties, len: usize) -> TypeProperties {
let TypeProperties {
is_passive,
is_storable,
is_castable_from_bits,
bit_width,
} = element;
let Some(bit_width) = bit_width.checked_mul(len) else {
panic!("array too big");
};
mod sealed {
pub trait Sealed {}
}
pub trait ValueArrayOrSlice:
sealed::Sealed
+ BorrowMut<[<Self as ValueArrayOrSlice>::Element]>
+ AsRef<[<Self as ValueArrayOrSlice>::Element]>
+ AsMut<[<Self as ValueArrayOrSlice>::Element]>
+ Hash
+ fmt::Debug
+ Eq
+ Send
+ Sync
+ 'static
+ IndexMut<usize, Output = <Self as ValueArrayOrSlice>::Element>
+ ToOwned
+ InternedCompare
{
type Element: Value<Type = <Self as ValueArrayOrSlice>::ElementType>;
type ElementType: Type<Value = <Self as ValueArrayOrSlice>::Element>;
type LenType: 'static + Copy + Ord + fmt::Debug + Hash + Send + Sync;
type Match: 'static
+ Clone
+ Eq
+ fmt::Debug
+ Hash
+ Send
+ Sync
+ BorrowMut<[Expr<Self::Element>]>;
type MaskVA: ValueArrayOrSlice<
Element = <Self::ElementType as Type>::MaskValue,
ElementType = <Self::ElementType as Type>::MaskType,
LenType = Self::LenType,
MaskVA = Self::MaskVA,
> + ?Sized;
type IsStaticLen: GenericConstBool;
const FIXED_LEN_TYPE: Option<Self::LenType>;
fn make_match(array: Expr<Array<Self>>) -> Self::Match;
fn len_from_len_type(v: Self::LenType) -> usize;
#[allow(clippy::result_unit_err)]
fn try_len_type_from_len(v: usize) -> Result<Self::LenType, ()>;
fn len_type(&self) -> Self::LenType;
fn len(&self) -> usize;
fn is_empty(&self) -> bool;
fn iter(&self) -> std::slice::Iter<Self::Element> {
Borrow::<[_]>::borrow(self).iter()
}
fn clone_to_arc(&self) -> Arc<Self>;
fn arc_make_mut(v: &mut Arc<Self>) -> &mut Self;
fn arc_to_arc_slice(self: Arc<Self>) -> Arc<[Self::Element]>;
}
impl<T> sealed::Sealed for [T] {}
impl<V: Value> ValueArrayOrSlice for [V]
where
V::Type: Type<Value = V>,
{
type Element = V;
type ElementType = V::Type;
type LenType = usize;
type Match = Box<[Expr<V>]>;
type MaskVA = [<Self::ElementType as Type>::MaskValue];
type IsStaticLen = ConstBool<false>;
const FIXED_LEN_TYPE: Option<Self::LenType> = None;
fn make_match(array: Expr<Array<Self>>) -> Self::Match {
(0..array.canonical_type().len())
.map(|index| ArrayIndex::<V::Type>::new_unchecked(array.canonical(), index).to_expr())
.collect()
}
fn len_from_len_type(v: Self::LenType) -> usize {
v
}
fn try_len_type_from_len(v: usize) -> Result<Self::LenType, ()> {
Ok(v)
}
fn len_type(&self) -> Self::LenType {
self.len()
}
fn len(&self) -> usize {
<[_]>::len(self)
}
fn is_empty(&self) -> bool {
<[_]>::is_empty(self)
}
fn clone_to_arc(&self) -> Arc<Self> {
Arc::from(self)
}
fn arc_make_mut(v: &mut Arc<Self>) -> &mut Self {
MakeMutSlice::make_mut_slice(v)
}
fn arc_to_arc_slice(self: Arc<Self>) -> Arc<[Self::Element]> {
self
}
}
impl<T, const N: usize> sealed::Sealed for [T; N] {}
impl<V: Value, const N: usize> ValueArrayOrSlice for [V; N]
where
V::Type: Type<Value = V>,
{
type Element = V;
type ElementType = V::Type;
type LenType = ();
type Match = [Expr<V>; N];
type MaskVA = [<Self::ElementType as Type>::MaskValue; N];
type IsStaticLen = ConstBool<true>;
const FIXED_LEN_TYPE: Option<Self::LenType> = Some(());
fn make_match(array: Expr<Array<Self>>) -> Self::Match {
std::array::from_fn(|index| {
ArrayIndex::<V::Type>::new_unchecked(array.canonical(), index).to_expr()
})
}
fn len_from_len_type(_v: Self::LenType) -> usize {
N
}
fn try_len_type_from_len(v: usize) -> Result<Self::LenType, ()> {
if v == N {
Ok(())
} else {
Err(())
}
}
fn len_type(&self) -> Self::LenType {}
fn len(&self) -> usize {
N
}
fn is_empty(&self) -> bool {
N == 0
}
fn clone_to_arc(&self) -> Arc<Self> {
Arc::new(self.clone())
}
fn arc_make_mut(v: &mut Arc<Self>) -> &mut Self {
Arc::make_mut(v)
}
fn arc_to_arc_slice(self: Arc<Self>) -> Arc<[Self::Element]> {
self
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ArrayType<VA: ValueArrayOrSlice + ?Sized> {
element: VA::ElementType,
len: VA::LenType,
bit_width: usize,
}
pub trait ArrayTypeTrait:
Type<
CanonicalType = ArrayType<[DynCanonicalValue]>,
Value = Array<<Self as ArrayTypeTrait>::ValueArrayOrSlice>,
CanonicalValue = Array<[DynCanonicalValue]>,
MaskType = ArrayType<
<<Self as ArrayTypeTrait>::ValueArrayOrSlice as ValueArrayOrSlice>::MaskVA,
>,
> + From<ArrayType<<Self as ArrayTypeTrait>::ValueArrayOrSlice>>
+ Into<ArrayType<<Self as ArrayTypeTrait>::ValueArrayOrSlice>>
+ BorrowMut<ArrayType<<Self as ArrayTypeTrait>::ValueArrayOrSlice>>
+ sealed::Sealed
+ Connect<Self>
{
type ValueArrayOrSlice: ValueArrayOrSlice<Element = Self::Element, ElementType = Self::ElementType>
+ ?Sized;
type Element: Value<Type = Self::ElementType>;
type ElementType: Type<Value = Self::Element>;
}
impl<VA: ValueArrayOrSlice + ?Sized> sealed::Sealed for ArrayType<VA> {}
impl<VA: ValueArrayOrSlice + ?Sized> ArrayTypeTrait for ArrayType<VA> {
type ValueArrayOrSlice = VA;
type Element = VA::Element;
type ElementType = VA::ElementType;
}
impl<VA: ValueArrayOrSlice + ?Sized> Clone for ArrayType<VA> {
fn clone(&self) -> Self {
Self {
element: self.element.clone(),
len: self.len,
bit_width: self.bit_width,
}
}
}
impl<VA: ValueArrayOrSlice + ?Sized> Copy for ArrayType<VA> where VA::ElementType: Copy {}
impl<VA: ValueArrayOrSlice + ?Sized> ArrayType<VA> {
pub fn element(&self) -> &VA::ElementType {
&self.element
}
pub fn len(&self) -> usize {
VA::len_from_len_type(self.len)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn bit_width(&self) -> usize {
self.bit_width
}
pub fn into_slice_type(self) -> ArrayType<[VA::Element]> {
ArrayType {
len: self.len(),
element: self.element,
bit_width: self.bit_width,
}
}
#[track_caller]
pub fn new_with_len(element: VA::ElementType, len: usize) -> Self {
Self::new_with_len_type(
element,
VA::try_len_type_from_len(len).expect("length should match"),
)
}
#[track_caller]
pub fn new_with_len_type(element: VA::ElementType, len: VA::LenType) -> Self {
let Some(bit_width) = VA::len_from_len_type(len).checked_mul(element.bit_width()) else {
panic!("array is too big: bit-width overflowed");
};
ArrayType {
element,
len,
TypeProperties {
is_passive,
is_storable,
is_castable_from_bits,
bit_width,
}
}
pub fn new(element: T, len: Len::SizeType) -> Self {
let type_properties =
Self::make_type_properties(element.canonical().type_properties(), Len::as_usize(len));
Self {
element: LazyInterned::Interned(element.intern_sized()),
len,
type_properties,
}
}
pub fn element(&self) -> T {
*self.element
}
pub fn len(self) -> usize {
Len::as_usize(self.len)
}
pub fn is_empty(self) -> bool {
self.len() == 0
}
pub fn type_properties(self) -> TypeProperties {
self.type_properties
}
pub fn as_dyn_array(self) -> Array {
Array::new_dyn(self.element().canonical(), self.len())
}
pub fn can_connect<T2: Type, Len2: Size>(self, rhs: ArrayType<T2, Len2>) -> bool {
self.len() == rhs.len()
&& self
.element()
.canonical()
.can_connect(rhs.element().canonical())
}
}
impl<VA: ValueArrayOrSlice + ?Sized, State: ?Sized + Folder> Fold<State> for ArrayType<VA>
where
VA::ElementType: Fold<State>,
{
impl<T: Type, Len: KnownSize + Size<SizeType = Len>> ArrayType<T, Len> {
pub fn new_static(element: T) -> Self {
Self::new(element, Len::SizeType::default())
}
}
impl<T: StaticType, Len: KnownSize> StaticType for ArrayType<T, Len> {
const TYPE: Self = Self {
element: LazyInterned::new_lazy(&|| T::TYPE.intern_sized()),
len: Len::SIZE,
type_properties: Self::TYPE_PROPERTIES,
};
const MASK_TYPE: Self::MaskType = ArrayType::<T::MaskType, Len> {
element: LazyInterned::new_lazy(&|| T::MASK_TYPE.intern_sized()),
len: Len::SIZE,
type_properties: Self::MASK_TYPE_PROPERTIES,
};
const TYPE_PROPERTIES: TypeProperties =
Self::make_type_properties(T::TYPE_PROPERTIES, Len::VALUE);
const MASK_TYPE_PROPERTIES: TypeProperties =
Self::make_type_properties(T::MASK_TYPE_PROPERTIES, Len::VALUE);
}
impl<T: Type> Array<T> {
pub fn new_dyn(element: T, len: usize) -> Self {
Self::new(element, len)
}
}
impl<T: Type + Fold<State>, Len: Size, State: Folder + ?Sized> Fold<State> for ArrayType<T, Len> {
fn fold(self, state: &mut State) -> Result<Self, State::Error> {
state.fold_array_type(self)
}
fn default_fold(self, state: &mut State) -> Result<Self, State::Error> {
Ok(Self::new_with_len_type(self.element.fold(state)?, self.len))
fn default_fold(self, state: &mut State) -> Result<Self, <State as Folder>::Error> {
Ok(ArrayType::new(self.element().fold(state)?, self.len))
}
}
impl<VA: ValueArrayOrSlice + ?Sized, State: ?Sized + Visitor> Visit<State> for ArrayType<VA>
where
VA::ElementType: Visit<State>,
impl<T: Type + Visit<State>, Len: Size, State: Visitor + ?Sized> Visit<State>
for ArrayType<T, Len>
{
fn visit(&self, state: &mut State) -> Result<(), State::Error> {
state.visit_array_type(self)
}
fn default_visit(&self, state: &mut State) -> Result<(), State::Error> {
self.element.visit(state)
self.element().visit(state)
}
}
impl<V: Value<Type: Type<Value = V>>, const N: usize> ArrayType<[V; N]> {
pub fn new_array(element: V::Type) -> Self {
ArrayType::new_with_len_type(element, ())
}
}
impl<V: StaticValue, const N: usize> StaticType for ArrayType<[V; N]> {
fn static_type() -> Self {
Self::new_array(StaticType::static_type())
}
}
impl<V: Value<Type: Type<Value = V>>> ArrayType<[V]> {
pub fn new_slice(element: V::Type, len: usize) -> Self {
ArrayType::new_with_len_type(element, len)
}
}
impl<VA: ValueArrayOrSlice + ?Sized> Type for ArrayType<VA> {
type CanonicalType = ArrayType<[DynCanonicalValue]>;
type Value = Array<VA>;
type CanonicalValue = Array<[DynCanonicalValue]>;
type MaskType = ArrayType<VA::MaskVA>;
type MaskValue = Array<VA::MaskVA>;
type MatchVariant = VA::Match;
impl<T: Type, Len: Size> Type for ArrayType<T, Len> {
type BaseType = Array;
type MaskType = ArrayType<T::MaskType, Len>;
type MatchVariant = Len::ArrayMatch<T>;
type MatchActiveScope = ();
type MatchVariantAndInactiveScope = MatchVariantWithoutScope<VA::Match>;
type MatchVariantAndInactiveScope = MatchVariantWithoutScope<Len::ArrayMatch<T>>;
type MatchVariantsIter = std::iter::Once<Self::MatchVariantAndInactiveScope>;
fn match_variants<IO: BundleValue>(
this: Expr<Self::Value>,
module_builder: &mut ModuleBuilder<IO, NormalModule>,
fn match_variants(
this: Expr<Self>,
source_location: SourceLocation,
) -> Self::MatchVariantsIter
where
IO::Type: BundleType<Value = IO>,
{
let _ = module_builder;
) -> Self::MatchVariantsIter {
let base = Expr::as_dyn_array(this);
let base_ty = Expr::ty(base);
let _ = source_location;
std::iter::once(MatchVariantWithoutScope(VA::make_match(this)))
let retval = Vec::from_iter((0..base_ty.len()).map(|i| ArrayIndex::new(base, i).to_expr()));
std::iter::once(MatchVariantWithoutScope(
Len::ArrayMatch::<T>::try_from(retval)
.ok()
.expect("unreachable"),
))
}
fn mask_type(&self) -> Self::MaskType {
#[derive(Clone, Hash, Eq, PartialEq)]
struct ArrayMaskTypeMemoize<T: ArrayTypeTrait>(PhantomData<T>);
impl<T: ArrayTypeTrait> Copy for ArrayMaskTypeMemoize<T> {}
impl<T: ArrayTypeTrait> Memoize for ArrayMaskTypeMemoize<T> {
type Input = ArrayType<T::ValueArrayOrSlice>;
type InputOwned = ArrayType<T::ValueArrayOrSlice>;
type Output = <ArrayType<T::ValueArrayOrSlice> as Type>::MaskType;
fn inner(self, input: &Self::Input) -> Self::Output {
ArrayType::new_with_len_type(input.element.mask_type(), input.len)
}
}
ArrayMaskTypeMemoize::<Self>(PhantomData).get(self)
ArrayType::new(self.element().mask_type(), self.len)
}
fn canonical(&self) -> Self::CanonicalType {
ArrayType {
element: self.element.canonical_dyn(),
len: self.len(),
bit_width: self.bit_width,
}
fn canonical(&self) -> CanonicalType {
CanonicalType::Array(Array::new_dyn(self.element().canonical(), self.len()))
}
fn source_location(&self) -> SourceLocation {
#[track_caller]
fn from_canonical(canonical_type: CanonicalType) -> Self {
let CanonicalType::Array(array) = canonical_type else {
panic!("expected array");
};
Self::new(
T::from_canonical(array.element()),
Len::from_usize(array.len()),
)
}
fn source_location() -> SourceLocation {
SourceLocation::builtin()
}
fn type_enum(&self) -> TypeEnum {
TypeEnum::ArrayType(self.canonical())
}
fn from_canonical_type(t: Self::CanonicalType) -> Self {
Self {
element: VA::ElementType::from_dyn_canonical_type(t.element),
len: VA::try_len_type_from_len(t.len).expect("length should match"),
bit_width: t.bit_width,
impl<T: Type, Len: Size> TypeWithDeref for ArrayType<T, Len> {
fn expr_deref(this: &Expr<Self>) -> &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()));
Interned::into_inner(Intern::intern_sized(
Len::ArrayMatch::<T>::try_from(retval)
.ok()
.expect("unreachable"),
))
}
}
fn as_dyn_canonical_type_impl(this: &Self) -> Option<&dyn DynCanonicalType> {
Some(<dyn Any>::downcast_ref::<ArrayType<[DynCanonicalValue]>>(
this,
)?)
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)]
pub struct ArrayWithoutGenerics;
impl<T: Type> Index<T> for ArrayWithoutGenerics {
type Output = ArrayWithoutLen<T>;
fn index(&self, element: T) -> &Self::Output {
Interned::into_inner(Intern::intern_sized(ArrayWithoutLen { element }))
}
}
impl<Lhs: ValueArrayOrSlice + ?Sized, Rhs: ValueArrayOrSlice + ?Sized> Connect<ArrayType<Rhs>>
for ArrayType<Lhs>
{
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct ArrayWithoutLen<T: Type> {
element: T,
}
impl CanonicalType for ArrayType<[DynCanonicalValue]> {
const CANONICAL_TYPE_KIND: CanonicalTypeKind = CanonicalTypeKind::ArrayType;
}
impl<T: Type, L: SizeType> Index<L> for ArrayWithoutLen<T> {
type Output = ArrayType<T, L::Size>;
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct Array<VA: ValueArrayOrSlice + ?Sized> {
element_ty: VA::ElementType,
value: Arc<VA>,
}
impl<VA: ValueArrayOrSlice + ?Sized> Clone for Array<VA> {
fn clone(&self) -> Self {
Self {
element_ty: self.element_ty.clone(),
value: self.value.clone(),
}
}
}
impl<VA: ValueArrayOrSlice + ?Sized> ToExpr for Array<VA> {
type Type = ArrayType<VA>;
fn ty(&self) -> Self::Type {
ArrayType::new_with_len_type(self.element_ty.clone(), self.value.len_type())
}
fn to_expr(&self) -> Expr<<Self::Type as Type>::Value> {
Expr::from_value(self)
}
}
impl<VA: ValueArrayOrSlice + ?Sized> Value for Array<VA> {
fn to_canonical(&self) -> <Self::Type as Type>::CanonicalValue {
Array {
element_ty: self.element_ty.canonical_dyn(),
value: AsRef::<[_]>::as_ref(&*self.value)
.iter()
.map(|v| v.to_canonical_dyn())
.collect(),
}
}
fn to_bits_impl(this: &Self) -> Interned<BitSlice> {
#[derive(Hash, Eq, PartialEq)]
struct ArrayToBitsMemoize<VA: ValueArrayOrSlice + ?Sized>(PhantomData<VA>);
impl<VA: ValueArrayOrSlice + ?Sized> Clone for ArrayToBitsMemoize<VA> {
fn clone(&self) -> Self {
*self
}
}
impl<VA: ValueArrayOrSlice + ?Sized> Copy for ArrayToBitsMemoize<VA> {}
impl<VA: ValueArrayOrSlice + ?Sized> Memoize for ArrayToBitsMemoize<VA> {
type Input = Array<VA>;
type InputOwned = Array<VA>;
type Output = Interned<BitSlice>;
fn inner(self, input: &Self::Input) -> Self::Output {
let mut bits = BitVec::with_capacity(input.ty().bit_width());
for element in AsRef::<[_]>::as_ref(&*input.value).iter() {
bits.extend_from_bitslice(&element.to_bits());
}
Intern::intern_owned(bits)
}
}
ArrayToBitsMemoize::<VA>(PhantomData).get(this)
}
}
impl CanonicalValue for Array<[DynCanonicalValue]> {
fn value_enum_impl(this: &Self) -> ValueEnum {
ValueEnum::Array(this.clone())
}
fn to_bits_impl(this: &Self) -> Interned<BitSlice> {
Value::to_bits_impl(this)
}
}
impl<VA: ValueArrayOrSlice + ?Sized> Array<VA> {
pub fn element_ty(&self) -> &VA::ElementType {
&self.element_ty
}
pub fn len(&self) -> usize {
VA::len_from_len_type(self.value.len_type())
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn value(&self) -> &Arc<VA> {
&self.value
}
pub fn set_element(&mut self, index: usize, element: VA::Element) {
assert_eq!(self.element_ty, element.ty());
VA::arc_make_mut(&mut self.value)[index] = element;
}
pub fn new(element_ty: VA::ElementType, value: Arc<VA>) -> Self {
for element in value.iter() {
assert_eq!(element_ty, element.ty());
}
Self { element_ty, value }
}
pub fn into_slice(self) -> Array<[VA::Element]> {
Array {
element_ty: self.element_ty,
value: self.value.arc_to_arc_slice(),
}
}
}
impl<VA: ValueArrayOrSlice + ?Sized, T: Into<Arc<VA>>> From<T> for Array<VA>
where
VA::Element: StaticValue,
{
fn from(value: T) -> Self {
Self::new(StaticType::static_type(), value.into())
}
}
impl<E: ToExpr<Type = T>, T: StaticType<MaskType: StaticType>> ToExpr for [E] {
type Type = ArrayType<[T::Value]>;
fn ty(&self) -> Self::Type {
ArrayType::new_with_len_type(StaticType::static_type(), self.len())
}
fn to_expr(&self) -> Expr<<Self::Type as Type>::Value> {
let elements = Intern::intern_owned(Vec::from_iter(
self.iter().map(|v| v.to_expr().to_canonical_dyn()),
));
ArrayLiteral::new_unchecked(elements, self.ty()).to_expr()
}
}
impl<E: ToExpr<Type = T>, T: StaticType<MaskType: StaticType>> ToExpr for Vec<E> {
type Type = ArrayType<[T::Value]>;
fn ty(&self) -> Self::Type {
<[E]>::ty(self)
}
fn to_expr(&self) -> Expr<<Self::Type as Type>::Value> {
<[E]>::to_expr(self)
}
}
impl<E: ToExpr<Type = T>, T: StaticType<MaskType: StaticType>, const N: usize> ToExpr for [E; N] {
type Type = ArrayType<[T::Value; N]>;
fn ty(&self) -> Self::Type {
ArrayType::new_with_len_type(StaticType::static_type(), ())
}
fn to_expr(&self) -> Expr<<Self::Type as Type>::Value> {
let elements = Intern::intern_owned(Vec::from_iter(
self.iter().map(|v| v.to_expr().to_canonical_dyn()),
));
ArrayLiteral::new_unchecked(elements, self.ty()).to_expr()
}
}
#[derive(Clone, Debug)]
pub struct ArrayIntoIter<VA: ValueArrayOrSlice> {
array: Arc<VA>,
indexes: std::ops::Range<usize>,
}
impl<VA: ValueArrayOrSlice> Iterator for ArrayIntoIter<VA> {
type Item = VA::Element;
fn next(&mut self) -> Option<Self::Item> {
Some(self.array[self.indexes.next()?].clone())
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.indexes.size_hint()
}
}
impl<VA: ValueArrayOrSlice> std::iter::FusedIterator for ArrayIntoIter<VA> {}
impl<VA: ValueArrayOrSlice> ExactSizeIterator for ArrayIntoIter<VA> {}
impl<VA: ValueArrayOrSlice> DoubleEndedIterator for ArrayIntoIter<VA> {
fn next_back(&mut self) -> Option<Self::Item> {
Some(self.array[self.indexes.next_back()?].clone())
}
}
impl<VA: ValueArrayOrSlice> Array<VA> {
pub fn iter(&self) -> std::slice::Iter<'_, VA::Element> {
self.value.iter()
}
}
impl<'a, VA: ValueArrayOrSlice> IntoIterator for &'a Array<VA> {
type Item = &'a VA::Element;
type IntoIter = std::slice::Iter<'a, VA::Element>;
fn into_iter(self) -> Self::IntoIter {
self.value.iter()
}
}
impl<VA: ValueArrayOrSlice> IntoIterator for Array<VA> {
type Item = VA::Element;
type IntoIter = ArrayIntoIter<VA>;
fn into_iter(self) -> Self::IntoIter {
ArrayIntoIter {
indexes: 0..self.len(),
array: self.value,
}
}
}
#[derive(Clone, Debug)]
pub struct ArrayExprIter<VA: ValueArrayOrSlice> {
array: Expr<Array<VA>>,
indexes: std::ops::Range<usize>,
}
impl<VA: ValueArrayOrSlice> Iterator for ArrayExprIter<VA> {
type Item = Expr<VA::Element>;
fn next(&mut self) -> Option<Self::Item> {
Some(ExprIndex::expr_index(self.array, self.indexes.next()?))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.indexes.size_hint()
}
}
impl<VA: ValueArrayOrSlice> std::iter::FusedIterator for ArrayExprIter<VA> {}
impl<VA: ValueArrayOrSlice> ExactSizeIterator for ArrayExprIter<VA> {}
impl<VA: ValueArrayOrSlice> DoubleEndedIterator for ArrayExprIter<VA> {
fn next_back(&mut self) -> Option<Self::Item> {
Some(ExprIndex::expr_index(self.array, self.indexes.next_back()?))
}
}
impl<VA: ValueArrayOrSlice> IntoIterator for Expr<Array<VA>> {
type Item = Expr<VA::Element>;
type IntoIter = ArrayExprIter<VA>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<VA: ValueArrayOrSlice> IntoIterator for &'_ Expr<Array<VA>> {
type Item = Expr<VA::Element>;
type IntoIter = ArrayExprIter<VA>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<VA: ValueArrayOrSlice> Expr<Array<VA>> {
pub fn len(self) -> usize {
self.canonical_type().len()
}
pub fn is_empty(self) -> bool {
self.canonical_type().is_empty()
}
pub fn iter(self) -> ArrayExprIter<VA> {
ArrayExprIter {
indexes: 0..self.len(),
array: self,
}
fn index(&self, len: L) -> &Self::Output {
Interned::into_inner(Intern::intern_sized(ArrayType::new(self.element, len)))
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,27 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
bundle::{BundleType, BundleValue, DynBundle},
firrtl,
bundle::{Bundle, BundleType},
firrtl::{self, ExportOptions},
intern::Interned,
module::Module,
util::{job_server::AcquiredJob, streaming_read_utf8::streaming_read_utf8},
};
use clap::{
builder::{OsStringValueParser, TypedValueParser},
Args, Parser, Subcommand, ValueEnum, ValueHint,
Parser, Subcommand, ValueEnum, ValueHint,
};
use eyre::{eyre, Report};
use std::{error, ffi::OsString, fmt, io, path::PathBuf, process};
use serde::{Deserialize, Serialize};
use std::{
error,
ffi::OsString,
fmt::{self, Write},
fs, io, mem,
path::{Path, PathBuf},
process,
};
use tempfile::TempDir;
pub type Result<T = (), E = CliError> = std::result::Result<T, E>;
@ -37,80 +49,157 @@ impl From<io::Error> for CliError {
pub trait RunPhase<Arg> {
type Output;
fn run(&self, arg: Arg) -> Result<Self::Output>;
fn run(&self, arg: Arg) -> Result<Self::Output> {
self.run_with_job(arg, &mut AcquiredJob::acquire())
}
fn run_with_job(&self, arg: Arg, acquired_job: &mut AcquiredJob) -> Result<Self::Output>;
}
#[derive(Args, Debug)]
#[derive(Parser, Debug, Clone)]
#[non_exhaustive]
pub struct BaseArgs {
/// the directory to put the generated main output file and associated files in
#[arg(short, long, value_hint = ValueHint::DirPath)]
pub output: PathBuf,
#[arg(short, long, value_hint = ValueHint::DirPath, required = true)]
pub output: Option<PathBuf>,
/// the stem of the generated main output file, e.g. to get foo.v, pass --file-stem=foo
#[arg(long)]
pub file_stem: Option<String>,
#[arg(long, env = "FAYALITE_KEEP_TEMP_DIR")]
pub keep_temp_dir: bool,
#[arg(skip = false)]
pub redirect_output_for_rust_test: bool,
}
impl BaseArgs {
pub fn to_firrtl_file_backend(&self) -> firrtl::FileBackend {
fn make_firrtl_file_backend(&self) -> Result<(firrtl::FileBackend, Option<TempDir>)> {
let (dir_path, temp_dir) = match &self.output {
Some(output) => (output.clone(), None),
None => {
let temp_dir = TempDir::new()?;
if self.keep_temp_dir {
let temp_dir = temp_dir.into_path();
println!("created temporary directory: {}", temp_dir.display());
(temp_dir, None)
} else {
(temp_dir.path().to_path_buf(), Some(temp_dir))
}
}
};
Ok((
firrtl::FileBackend {
dir_path: self.output.clone(),
dir_path,
top_fir_file_stem: self.file_stem.clone(),
circuit_name: None,
},
temp_dir,
))
}
/// handles possibly redirecting the command's output for Rust tests
pub fn run_external_command(
&self,
_acquired_job: &mut AcquiredJob,
mut command: process::Command,
mut captured_output: Option<&mut String>,
) -> io::Result<process::ExitStatus> {
if self.redirect_output_for_rust_test || captured_output.is_some() {
let (reader, writer) = os_pipe::pipe()?;
let mut reader = io::BufReader::new(reader);
command.stderr(writer.try_clone()?);
command.stdout(writer); // must not leave writer around after spawning child
command.stdin(process::Stdio::null());
let mut child = command.spawn()?;
drop(command); // close writers
Ok(loop {
let status = child.try_wait()?;
streaming_read_utf8(&mut reader, |s| {
if let Some(captured_output) = captured_output.as_deref_mut() {
captured_output.push_str(s);
}
// use print! so output goes to Rust test output capture
print!("{s}");
io::Result::Ok(())
})?;
if let Some(status) = status {
break status;
}
})
} else {
command.status()
}
}
}
#[derive(Args, Debug)]
#[derive(Parser, Debug, Clone)]
#[non_exhaustive]
pub struct FirrtlArgs {
#[command(flatten)]
pub base: BaseArgs,
#[command(flatten)]
pub export_options: ExportOptions,
}
#[derive(Debug)]
#[non_exhaustive]
pub struct FirrtlOutput {
pub file_stem: String,
pub top_module: String,
pub output_dir: PathBuf,
pub temp_dir: Option<TempDir>,
}
impl FirrtlOutput {
pub fn firrtl_file(&self, args: &FirrtlArgs) -> PathBuf {
let mut retval = args.base.output.join(&self.file_stem);
retval.set_extension("fir");
pub fn file_with_ext(&self, ext: &str) -> PathBuf {
let mut retval = self.output_dir.join(&self.file_stem);
retval.set_extension(ext);
retval
}
pub fn firrtl_file(&self) -> PathBuf {
self.file_with_ext("fir")
}
}
impl FirrtlArgs {
fn run_impl(&self, top_module: Module<DynBundle>) -> Result<FirrtlOutput> {
fn run_impl(
&self,
top_module: Module<Bundle>,
_acquired_job: &mut AcquiredJob,
) -> Result<FirrtlOutput> {
let (file_backend, temp_dir) = self.base.make_firrtl_file_backend()?;
let firrtl::FileBackend {
top_fir_file_stem, ..
} = firrtl::export(self.base.to_firrtl_file_backend(), &top_module)?;
top_fir_file_stem,
circuit_name,
dir_path,
} = firrtl::export(file_backend, &top_module, self.export_options)?;
Ok(FirrtlOutput {
file_stem: top_fir_file_stem.expect(
"export is known to set the file stem from the circuit name if not provided",
),
top_module: circuit_name.expect("export is known to set the circuit name"),
output_dir: dir_path,
temp_dir,
})
}
}
impl<T: BundleValue> RunPhase<Module<T>> for FirrtlArgs
where
T::Type: BundleType<Value = T>,
{
impl<T: BundleType> RunPhase<Module<T>> for FirrtlArgs {
type Output = FirrtlOutput;
fn run(&self, top_module: Module<T>) -> Result<Self::Output> {
self.run_impl(top_module.canonical())
fn run_with_job(
&self,
top_module: Module<T>,
acquired_job: &mut AcquiredJob,
) -> Result<Self::Output> {
self.run_impl(top_module.canonical(), acquired_job)
}
}
impl<T: BundleValue> RunPhase<Interned<Module<T>>> for FirrtlArgs
where
T::Type: BundleType<Value = T>,
{
impl<T: BundleType> RunPhase<Interned<Module<T>>> for FirrtlArgs {
type Output = FirrtlOutput;
fn run(&self, top_module: Interned<Module<T>>) -> Result<Self::Output> {
self.run(*top_module)
fn run_with_job(
&self,
top_module: Interned<Module<T>>,
acquired_job: &mut AcquiredJob,
) -> Result<Self::Output> {
self.run_with_job(*top_module, acquired_job)
}
}
@ -126,7 +215,22 @@ pub enum VerilogDialect {
Yosys,
}
impl fmt::Display for VerilogDialect {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl VerilogDialect {
pub fn as_str(self) -> &'static str {
match self {
VerilogDialect::Questa => "questa",
VerilogDialect::Spyglass => "spyglass",
VerilogDialect::Verilator => "verilator",
VerilogDialect::Vivado => "vivado",
VerilogDialect::Yosys => "yosys",
}
}
pub fn firtool_extra_args(self) -> &'static [&'static str] {
match self {
VerilogDialect::Questa => &["--lowering-options=emitWireInPorts"],
@ -144,7 +248,7 @@ impl VerilogDialect {
}
}
#[derive(Args, Debug)]
#[derive(Parser, Debug, Clone)]
#[non_exhaustive]
pub struct VerilogArgs {
#[command(flatten)]
@ -162,39 +266,94 @@ pub struct VerilogArgs {
/// adapt the generated Verilog for a particular toolchain
#[arg(long)]
pub verilog_dialect: Option<VerilogDialect>,
#[arg(long, short = 'g')]
pub debug: bool,
}
#[derive(Debug)]
#[non_exhaustive]
pub struct VerilogOutput {
pub firrtl: FirrtlOutput,
pub verilog_files: Vec<PathBuf>,
pub contents_hash: Option<blake3::Hash>,
}
impl VerilogOutput {
pub fn verilog_file(&self, args: &VerilogArgs) -> PathBuf {
let mut retval = args.firrtl.base.output.join(&self.firrtl.file_stem);
retval.set_extension("v");
retval
pub fn main_verilog_file(&self) -> PathBuf {
self.firrtl.file_with_ext("v")
}
fn unadjusted_verilog_file(&self) -> PathBuf {
self.firrtl.file_with_ext("unadjusted.v")
}
}
impl VerilogArgs {
fn run_impl(&self, firrtl_output: FirrtlOutput) -> Result<VerilogOutput> {
fn process_unadjusted_verilog_file(&self, mut output: VerilogOutput) -> Result<VerilogOutput> {
let input = fs::read_to_string(output.unadjusted_verilog_file())?;
let file_separator_prefix = "\n// ----- 8< ----- FILE \"";
let file_separator_suffix = "\" ----- 8< -----\n\n";
let mut input = &*input;
output.contents_hash = Some(blake3::hash(input.as_bytes()));
let main_verilog_file = output.main_verilog_file();
let mut file_name: Option<&Path> = Some(&main_verilog_file);
loop {
let (chunk, next_file_name) = if let Some((chunk, rest)) =
input.split_once(file_separator_prefix)
{
let Some((next_file_name, rest)) = rest.split_once(file_separator_suffix) else {
return Err(CliError(eyre!("parsing firtool's output failed: found {file_separator_prefix:?} but no {file_separator_suffix:?}")));
};
input = rest;
(chunk, Some(next_file_name.as_ref()))
} else {
(mem::take(&mut input), None)
};
let Some(file_name) = mem::replace(&mut file_name, next_file_name) else {
break;
};
let file_name = output.firrtl.output_dir.join(file_name);
fs::write(&file_name, chunk)?;
if let Some(extension) = file_name.extension() {
if extension == "v" || extension == "sv" {
output.verilog_files.push(file_name);
}
}
}
Ok(output)
}
fn run_impl(
&self,
firrtl_output: FirrtlOutput,
acquired_job: &mut AcquiredJob,
) -> Result<VerilogOutput> {
let Self {
firrtl,
firtool,
firtool_extra_args,
verilog_dialect,
debug,
} = self;
let output = VerilogOutput {
firrtl: firrtl_output,
verilog_files: vec![],
contents_hash: None,
};
let mut cmd = process::Command::new(&self.firtool);
cmd.arg(output.firrtl.firrtl_file(&self.firrtl));
let mut cmd = process::Command::new(firtool);
cmd.arg(output.firrtl.firrtl_file());
cmd.arg("-o");
cmd.arg(output.verilog_file(self));
if let Some(dialect) = self.verilog_dialect {
cmd.arg(output.unadjusted_verilog_file());
if *debug {
cmd.arg("-g");
cmd.arg("--preserve-values=all");
}
if let Some(dialect) = verilog_dialect {
cmd.args(dialect.firtool_extra_args());
}
cmd.args(&self.firtool_extra_args);
cmd.current_dir(&self.firrtl.base.output);
let status = cmd.status()?;
cmd.args(firtool_extra_args);
cmd.current_dir(&output.firrtl.output_dir);
let status = firrtl.base.run_external_command(acquired_job, cmd, None)?;
if status.success() {
Ok(output)
self.process_unadjusted_verilog_file(output)
} else {
Err(CliError(eyre!(
"running {} failed: {status}",
@ -209,9 +368,318 @@ where
FirrtlArgs: RunPhase<Arg, Output = FirrtlOutput>,
{
type Output = VerilogOutput;
fn run(&self, arg: Arg) -> Result<Self::Output> {
let firrtl_output = self.firrtl.run(arg)?;
self.run_impl(firrtl_output)
fn run_with_job(&self, arg: Arg, acquired_job: &mut AcquiredJob) -> Result<Self::Output> {
let firrtl_output = self.firrtl.run_with_job(arg, acquired_job)?;
self.run_impl(firrtl_output, acquired_job)
}
}
#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum FormalMode {
#[default]
BMC,
Prove,
Live,
Cover,
}
impl FormalMode {
pub fn as_str(self) -> &'static str {
match self {
FormalMode::BMC => "bmc",
FormalMode::Prove => "prove",
FormalMode::Live => "live",
FormalMode::Cover => "cover",
}
}
}
impl fmt::Display for FormalMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone)]
struct FormalAdjustArgs;
impl clap::FromArgMatches for FormalAdjustArgs {
fn from_arg_matches(_matches: &clap::ArgMatches) -> Result<Self, clap::Error> {
Ok(Self)
}
fn update_from_arg_matches(&mut self, _matches: &clap::ArgMatches) -> Result<(), clap::Error> {
Ok(())
}
}
impl clap::Args for FormalAdjustArgs {
fn augment_args(cmd: clap::Command) -> clap::Command {
cmd.mut_arg("output", |arg| arg.required(false))
.mut_arg("verilog_dialect", |arg| {
arg.default_value(VerilogDialect::Yosys.to_string())
.hide(true)
})
}
fn augment_args_for_update(cmd: clap::Command) -> clap::Command {
Self::augment_args(cmd)
}
}
#[derive(Parser, Clone)]
#[non_exhaustive]
pub struct FormalArgs {
#[command(flatten)]
pub verilog: VerilogArgs,
#[arg(
long,
default_value = "sby",
env = "SBY",
value_hint = ValueHint::CommandName,
value_parser = OsStringValueParser::new().try_map(which::which)
)]
pub sby: PathBuf,
#[arg(long)]
pub sby_extra_args: Vec<String>,
#[arg(long, default_value_t)]
pub mode: FormalMode,
#[arg(long, default_value_t = Self::DEFAULT_DEPTH)]
pub depth: u64,
#[arg(long, default_value = "z3")]
pub solver: String,
#[arg(long)]
pub smtbmc_extra_args: Vec<String>,
#[arg(long, default_value_t = true, env = "FAYALITE_CACHE_RESULTS")]
pub cache_results: bool,
#[command(flatten)]
_formal_adjust_args: FormalAdjustArgs,
}
impl fmt::Debug for FormalArgs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
verilog,
sby,
sby_extra_args,
mode,
depth,
solver,
smtbmc_extra_args,
cache_results,
_formal_adjust_args: _,
} = self;
f.debug_struct("FormalArgs")
.field("verilog", verilog)
.field("sby", sby)
.field("sby_extra_args", sby_extra_args)
.field("mode", mode)
.field("depth", depth)
.field("solver", solver)
.field("smtbmc_extra_args", smtbmc_extra_args)
.field("cache_results", cache_results)
.finish_non_exhaustive()
}
}
impl FormalArgs {
pub const DEFAULT_DEPTH: u64 = 20;
}
#[derive(Debug)]
#[non_exhaustive]
pub struct FormalOutput {
pub verilog: VerilogOutput,
}
impl FormalOutput {
pub fn sby_file(&self) -> PathBuf {
self.verilog.firrtl.file_with_ext("sby")
}
pub fn cache_file(&self) -> PathBuf {
self.verilog.firrtl.file_with_ext("cache.json")
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FormalCacheOutput {}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum FormalCacheVersion {
V1,
}
impl FormalCacheVersion {
pub const CURRENT: Self = Self::V1;
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub struct FormalCache {
pub version: FormalCacheVersion,
pub contents_hash: blake3::Hash,
pub stdout_stderr: String,
pub result: Result<FormalCacheOutput, String>,
}
impl FormalCache {
pub fn new(
version: FormalCacheVersion,
contents_hash: blake3::Hash,
stdout_stderr: String,
result: Result<FormalCacheOutput, String>,
) -> Self {
Self {
version,
contents_hash,
stdout_stderr,
result,
}
}
}
impl FormalArgs {
fn sby_contents(&self, output: &FormalOutput) -> Result<String> {
let Self {
verilog: _,
sby: _,
sby_extra_args: _,
mode,
depth,
smtbmc_extra_args,
solver,
cache_results: _,
_formal_adjust_args: _,
} = self;
let smtbmc_options = smtbmc_extra_args.join(" ");
let top_module = &output.verilog.firrtl.top_module;
let mut retval = format!(
"[options]\n\
mode {mode}\n\
depth {depth}\n\
wait on\n\
\n\
[engines]\n\
smtbmc {solver} -- -- {smtbmc_options}\n\
\n\
[script]\n"
);
for verilog_file in &output.verilog.verilog_files {
let verilog_file = verilog_file
.to_str()
.ok_or_else(|| CliError(eyre!("verilog file path is not UTF-8")))?;
if verilog_file.contains(|ch: char| {
(ch != ' ' && ch != '\t' && ch.is_ascii_whitespace()) || ch == '"'
}) {
return Err(CliError(eyre!(
"verilog file path contains characters that aren't permitted"
)));
}
writeln!(retval, "read_verilog -sv -formal \"{verilog_file}\"").unwrap();
}
// workaround for wires disappearing -- set `keep` on all wires
writeln!(retval, "hierarchy -top {top_module}").unwrap();
writeln!(retval, "proc").unwrap();
writeln!(retval, "setattr -set keep 1 w:\\*").unwrap();
writeln!(retval, "prep").unwrap();
Ok(retval)
}
fn run_impl(
&self,
verilog_output: VerilogOutput,
acquired_job: &mut AcquiredJob,
) -> Result<FormalOutput> {
let output = FormalOutput {
verilog: verilog_output,
};
let sby_file = output.sby_file();
let sby_contents = self.sby_contents(&output)?;
let contents_hash = output.verilog.contents_hash.map(|verilog_hash| {
let mut hasher = blake3::Hasher::new();
hasher.update(verilog_hash.as_bytes());
hasher.update(sby_contents.as_bytes());
hasher.update(&(self.sby_extra_args.len() as u64).to_le_bytes());
for sby_extra_arg in self.sby_extra_args.iter() {
hasher.update(&(sby_extra_arg.len() as u64).to_le_bytes());
hasher.update(sby_extra_arg.as_bytes());
}
hasher.finalize()
});
std::fs::write(&sby_file, sby_contents)?;
let mut cmd = process::Command::new(&self.sby);
cmd.arg("-j1"); // sby seems not to respect job count in parallel mode
cmd.arg("-f");
cmd.arg(sby_file.file_name().unwrap());
cmd.args(&self.sby_extra_args);
cmd.current_dir(&output.verilog.firrtl.output_dir);
let mut captured_output = String::new();
let cache_file = output.cache_file();
let do_cache = if let Some(contents_hash) = contents_hash.filter(|_| self.cache_results) {
if let Some(FormalCache {
version: FormalCacheVersion::CURRENT,
contents_hash: cache_contents_hash,
stdout_stderr,
result,
}) = fs::read(&cache_file)
.ok()
.and_then(|v| serde_json::from_slice(&v).ok())
{
if cache_contents_hash == contents_hash {
println!("Using cached formal result:\n{stdout_stderr}");
return match result {
Ok(FormalCacheOutput {}) => Ok(output),
Err(error) => Err(CliError(eyre::Report::msg(error))),
};
}
}
true
} else {
false
};
let _ = fs::remove_file(&cache_file);
let status = self.verilog.firrtl.base.run_external_command(
acquired_job,
cmd,
do_cache.then_some(&mut captured_output),
)?;
let result = if status.success() {
Ok(output)
} else {
Err(CliError(eyre!(
"running {} failed: {status}",
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"),
)?;
}
result
}
}
impl<Arg> RunPhase<Arg> for FormalArgs
where
VerilogArgs: RunPhase<Arg, Output = VerilogOutput>,
{
type Output = FormalOutput;
fn run_with_job(&self, arg: Arg, acquired_job: &mut AcquiredJob) -> Result<Self::Output> {
let verilog_output = self.verilog.run_with_job(arg, acquired_job)?;
self.run_impl(verilog_output, acquired_job)
}
}
@ -221,6 +689,8 @@ enum CliCommand {
Firrtl(FirrtlArgs),
/// Generate Verilog
Verilog(VerilogArgs),
/// Run a formal proof
Formal(FormalArgs),
}
/// a simple CLI
@ -228,7 +698,7 @@ enum CliCommand {
/// Use like:
///
/// ```no_run
/// # use fayalite::hdl_module;
/// # use fayalite::prelude::*;
/// # #[hdl_module]
/// # fn my_module() {}
/// use fayalite::cli;
@ -241,7 +711,7 @@ enum CliCommand {
/// You can also use it with a larger [`clap`]-based CLI like so:
///
/// ```no_run
/// # use fayalite::hdl_module;
/// # use fayalite::prelude::*;
/// # #[hdl_module]
/// # fn my_module() {}
/// use clap::{Subcommand, Parser};
@ -298,13 +768,16 @@ where
FirrtlArgs: RunPhase<T, Output = FirrtlOutput>,
{
type Output = ();
fn run(&self, arg: T) -> Result<Self::Output> {
fn run_with_job(&self, arg: T, acquired_job: &mut AcquiredJob) -> Result<Self::Output> {
match &self.subcommand {
CliCommand::Firrtl(c) => {
c.run(arg)?;
c.run_with_job(arg, acquired_job)?;
}
CliCommand::Verilog(c) => {
c.run(arg)?;
c.run_with_job(arg, acquired_job)?;
}
CliCommand::Formal(c) => {
c.run_with_job(arg, acquired_job)?;
}
}
Ok(())

View file

@ -2,111 +2,61 @@
// See Notices.txt for copyright information
use crate::{
expr::{Expr, ToExpr},
int::{UInt, UIntType},
intern::Interned,
hdl,
int::Bool,
reset::Reset,
source_location::SourceLocation,
ty::{
impl_match_values_as_self, CanonicalType, CanonicalTypeKind, CanonicalValue, Connect,
DynCanonicalType, StaticType, Type, TypeEnum, Value, ValueEnum,
},
util::interned_bit,
ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties},
};
use bitvec::slice::BitSlice;
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
pub struct ClockType;
pub struct Clock;
impl ClockType {
pub const fn new() -> Self {
Self
}
}
impl Type for Clock {
type BaseType = Clock;
type MaskType = Bool;
impl Type for ClockType {
type Value = Clock;
type CanonicalType = ClockType;
type CanonicalValue = Clock;
type MaskType = UIntType<1>;
type MaskValue = UInt<1>;
impl_match_values_as_self!();
impl_match_variant_as_self!();
fn mask_type(&self) -> Self::MaskType {
UIntType::new()
Bool
}
fn type_enum(&self) -> TypeEnum {
TypeEnum::Clock(*self)
fn canonical(&self) -> CanonicalType {
CanonicalType::Clock(*self)
}
fn from_canonical_type(t: Self::CanonicalType) -> Self {
t
}
fn canonical(&self) -> Self::CanonicalType {
*self
}
fn source_location(&self) -> SourceLocation {
fn source_location() -> SourceLocation {
SourceLocation::builtin()
}
fn as_dyn_canonical_type_impl(this: &Self) -> Option<&dyn DynCanonicalType> {
Some(this)
fn from_canonical(canonical_type: CanonicalType) -> Self {
let CanonicalType::Clock(retval) = canonical_type else {
panic!("expected Clock");
};
retval
}
}
impl Connect<Self> for ClockType {}
impl CanonicalType for ClockType {
const CANONICAL_TYPE_KIND: CanonicalTypeKind = CanonicalTypeKind::Clock;
impl Clock {
pub fn type_properties(self) -> TypeProperties {
Self::TYPE_PROPERTIES
}
impl StaticType for ClockType {
fn static_type() -> Self {
Self
pub fn can_connect(self, _rhs: Self) -> bool {
true
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct Clock(pub bool);
impl ToExpr for Clock {
type Type = ClockType;
fn ty(&self) -> Self::Type {
ClockType
}
fn to_expr(&self) -> Expr<Self> {
Expr::from_value(self)
}
}
impl Value for Clock {
fn to_canonical(&self) -> <Self::Type as Type>::CanonicalValue {
*self
}
fn to_bits_impl(this: &Self) -> Interned<BitSlice> {
interned_bit(this.0)
}
}
impl CanonicalValue for Clock {
fn value_enum_impl(this: &Self) -> ValueEnum {
ValueEnum::Clock(*this)
}
fn to_bits_impl(this: &Self) -> Interned<BitSlice> {
interned_bit(this.0)
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Value)]
#[hdl(static, outline_generated)]
pub struct ClockDomain {
pub clk: Clock,
pub rst: Reset,
impl StaticType for Clock {
const TYPE: Self = Self;
const MASK_TYPE: Self::MaskType = Bool;
const TYPE_PROPERTIES: TypeProperties = TypeProperties {
is_passive: true,
is_storable: false,
is_castable_from_bits: true,
bit_width: 1,
};
const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES;
}
pub trait ToClock {
@ -137,10 +87,10 @@ impl ToClock for Expr<Clock> {
}
}
impl ToClock for Clock {
fn to_clock(&self) -> Expr<Clock> {
self.to_expr()
}
#[hdl]
pub struct ClockDomain {
pub clk: Clock,
pub rst: Reset,
}
impl ToClock for bool {
@ -148,9 +98,3 @@ impl ToClock for bool {
self.to_expr().to_clock()
}
}
impl ToClock for UInt<1> {
fn to_clock(&self) -> Expr<Clock> {
self.to_expr().to_clock()
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,434 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
array::Array,
bundle::{Bundle, BundleField},
expr::Flow,
intern::{Intern, Interned},
memory::{DynPortType, MemPort},
module::{Instance, ModuleIO, TargetName},
reg::Reg,
source_location::SourceLocation,
ty::{CanonicalType, Type},
wire::Wire,
};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TargetPathBundleField {
pub name: Interned<str>,
}
impl fmt::Display for TargetPathBundleField {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, ".{}", self.name)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TargetPathArrayElement {
pub index: usize,
}
impl fmt::Display for TargetPathArrayElement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}]", self.index)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TargetPathDynArrayElement {}
impl fmt::Display for TargetPathDynArrayElement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[<dyn>]")
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TargetPathElement {
BundleField(TargetPathBundleField),
ArrayElement(TargetPathArrayElement),
DynArrayElement(TargetPathDynArrayElement),
}
impl From<TargetPathBundleField> for TargetPathElement {
fn from(value: TargetPathBundleField) -> Self {
Self::BundleField(value)
}
}
impl From<TargetPathArrayElement> for TargetPathElement {
fn from(value: TargetPathArrayElement) -> Self {
Self::ArrayElement(value)
}
}
impl From<TargetPathDynArrayElement> for TargetPathElement {
fn from(value: TargetPathDynArrayElement) -> Self {
Self::DynArrayElement(value)
}
}
impl fmt::Display for TargetPathElement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BundleField(v) => v.fmt(f),
Self::ArrayElement(v) => v.fmt(f),
Self::DynArrayElement(v) => v.fmt(f),
}
}
}
impl TargetPathElement {
pub fn canonical_ty(&self, parent: Interned<Target>) -> CanonicalType {
match self {
&Self::BundleField(TargetPathBundleField { name }) => {
let parent_ty = Bundle::from_canonical(parent.canonical_ty());
let field = parent_ty
.field_by_name(name)
.expect("field name is known to be a valid field of parent type");
field.ty
}
&Self::ArrayElement(TargetPathArrayElement { index }) => {
let parent_ty = Array::<CanonicalType>::from_canonical(parent.canonical_ty());
assert!(index < parent_ty.len());
parent_ty.element()
}
Self::DynArrayElement(_) => {
let parent_ty = Array::<CanonicalType>::from_canonical(parent.canonical_ty());
parent_ty.element()
}
}
}
pub fn flow(&self, parent: Interned<Target>) -> Flow {
match self {
Self::BundleField(v) => {
let parent_ty = Bundle::from_canonical(parent.canonical_ty());
let field = parent_ty
.field_by_name(v.name)
.expect("field name is known to be a valid field of parent type");
parent.flow().flip_if(field.flipped)
}
Self::ArrayElement(_) => parent.flow(),
Self::DynArrayElement(_) => parent.flow(),
}
}
pub fn is_static(&self) -> bool {
match self {
Self::BundleField(_) | Self::ArrayElement(_) => true,
Self::DynArrayElement(_) => false,
}
}
}
macro_rules! impl_target_base {
(
$(#[$enum_meta:meta])*
$enum_vis:vis enum $TargetBase:ident {
$(
#[is = $is_fn:ident]
#[to = $to_fn:ident]
$(#[$variant_meta:meta])*
$Variant:ident($VariantTy:ty),
)*
}
) => {
$(#[$enum_meta])*
$enum_vis enum $TargetBase {
$(
$(#[$variant_meta])*
$Variant($VariantTy),
)*
}
impl fmt::Debug for $TargetBase {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
$(Self::$Variant(v) => v.fmt(f),)*
}
}
}
$(
impl From<$VariantTy> for $TargetBase {
fn from(value: $VariantTy) -> Self {
Self::$Variant(value)
}
}
impl From<$VariantTy> for Target {
fn from(value: $VariantTy) -> Self {
$TargetBase::$Variant(value).into()
}
}
)*
impl $TargetBase {
$(
$enum_vis fn $is_fn(&self) -> bool {
self.$to_fn().is_some()
}
$enum_vis fn $to_fn(&self) -> Option<&$VariantTy> {
if let Self::$Variant(retval) = self {
Some(retval)
} else {
None
}
}
)*
$enum_vis fn must_connect_to(&self) -> bool {
match self {
$(Self::$Variant(v) => v.must_connect_to(),)*
}
}
$enum_vis fn flow(&self) -> Flow {
match self {
$(Self::$Variant(v) => v.flow(),)*
}
}
$enum_vis fn source_location(&self) -> SourceLocation {
match self {
$(Self::$Variant(v) => v.source_location(),)*
}
}
}
};
}
impl_target_base! {
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum TargetBase {
#[is = is_module_io]
#[to = module_io]
ModuleIO(ModuleIO<CanonicalType>),
#[is = is_mem_port]
#[to = mem_port]
MemPort(MemPort<DynPortType>),
#[is = is_reg]
#[to = reg]
Reg(Reg<CanonicalType>),
#[is = is_wire]
#[to = wire]
Wire(Wire<CanonicalType>),
#[is = is_instance]
#[to = instance]
Instance(Instance<Bundle>),
}
}
impl fmt::Display for TargetBase {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.target_name())
}
}
impl TargetBase {
pub fn target_name(&self) -> TargetName {
match self {
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::Wire(v) => TargetName(v.scoped_name(), None),
TargetBase::Instance(v) => TargetName(v.scoped_name(), None),
}
}
pub fn canonical_ty(&self) -> CanonicalType {
match self {
TargetBase::ModuleIO(v) => v.ty(),
TargetBase::MemPort(v) => v.ty().canonical(),
TargetBase::Reg(v) => v.ty(),
TargetBase::Wire(v) => v.ty(),
TargetBase::Instance(v) => v.ty().canonical(),
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct TargetChild {
parent: Interned<Target>,
path_element: Interned<TargetPathElement>,
canonical_ty: CanonicalType,
flow: Flow,
}
impl fmt::Debug for TargetChild {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
parent,
path_element,
canonical_ty: _,
flow: _,
} = self;
parent.fmt(f)?;
fmt::Display::fmt(path_element, f)
}
}
impl fmt::Display for TargetChild {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
parent,
path_element,
canonical_ty: _,
flow: _,
} = self;
parent.fmt(f)?;
path_element.fmt(f)
}
}
impl TargetChild {
pub fn new(parent: Interned<Target>, path_element: Interned<TargetPathElement>) -> Self {
Self {
parent,
path_element,
canonical_ty: path_element.canonical_ty(parent),
flow: path_element.flow(parent),
}
}
pub fn parent(self) -> Interned<Target> {
self.parent
}
pub fn path_element(self) -> Interned<TargetPathElement> {
self.path_element
}
pub fn canonical_ty(self) -> CanonicalType {
self.canonical_ty
}
pub fn flow(self) -> Flow {
self.flow
}
pub fn bundle_field(self) -> Option<BundleField> {
if let TargetPathElement::BundleField(TargetPathBundleField { name }) = *self.path_element {
let parent_ty = Bundle::from_canonical(self.parent.canonical_ty());
Some(
parent_ty
.field_by_name(name)
.expect("field name known to be a valid field of parent"),
)
} else {
None
}
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum Target {
Base(Interned<TargetBase>),
Child(TargetChild),
}
impl From<TargetBase> for Target {
fn from(value: TargetBase) -> Self {
Self::Base(Intern::intern_sized(value))
}
}
impl From<TargetChild> for Target {
fn from(value: TargetChild) -> Self {
Self::Child(value)
}
}
impl From<Interned<TargetBase>> for Target {
fn from(value: Interned<TargetBase>) -> Self {
Self::Base(value)
}
}
impl Target {
pub fn base(&self) -> Interned<TargetBase> {
let mut target = self;
loop {
match target {
Self::Base(v) => break *v,
Self::Child(v) => target = &v.parent,
}
}
}
pub fn child(&self) -> Option<TargetChild> {
match *self {
Target::Base(_) => None,
Target::Child(v) => Some(v),
}
}
pub fn is_static(&self) -> bool {
let mut target = self;
loop {
match target {
Self::Base(_) => return true,
Self::Child(v) if !v.path_element().is_static() => return false,
Self::Child(v) => target = &v.parent,
}
}
}
#[must_use]
pub fn join(&self, path_element: Interned<TargetPathElement>) -> Self {
TargetChild::new(self.intern(), path_element).into()
}
pub fn flow(&self) -> Flow {
match self {
Self::Base(v) => v.flow(),
Self::Child(v) => v.flow(),
}
}
pub fn canonical_ty(&self) -> CanonicalType {
match self {
Target::Base(v) => v.canonical_ty(),
Target::Child(v) => v.canonical_ty(),
}
}
}
impl fmt::Display for Target {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Base(v) => v.fmt(f),
Self::Child(v) => v.fmt(f),
}
}
}
impl fmt::Debug for Target {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Base(v) => v.fmt(f),
Self::Child(v) => v.fmt(f),
}
}
}
pub trait GetTarget {
fn target(&self) -> Option<Interned<Target>>;
}
impl GetTarget for bool {
fn target(&self) -> Option<Interned<Target>> {
None
}
}
impl<T: ?Sized + GetTarget + Send + Sync + 'static> GetTarget for Interned<T> {
fn target(&self) -> Option<Interned<Target>> {
T::target(self)
}
}
impl<T: ?Sized + GetTarget> GetTarget for &'_ T {
fn target(&self) -> Option<Interned<Target>> {
T::target(self)
}
}
impl<T: ?Sized + GetTarget> GetTarget for &'_ mut T {
fn target(&self) -> Option<Interned<Target>> {
T::target(self)
}
}
impl<T: ?Sized + GetTarget> GetTarget for Box<T> {
fn target(&self) -> Option<Interned<Target>> {
T::target(self)
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,247 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
int::BoolOrIntType,
intern::{Intern, Interned, Memoize},
prelude::*,
};
use std::sync::OnceLock;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum FormalKind {
Assert,
Assume,
Cover,
}
impl FormalKind {
pub fn as_str(self) -> &'static str {
match self {
Self::Assert => "assert",
Self::Assume => "assume",
Self::Cover => "cover",
}
}
}
#[track_caller]
pub fn formal_stmt_with_enable_and_loc(
kind: FormalKind,
clk: Expr<Clock>,
pred: Expr<Bool>,
en: Expr<Bool>,
text: &str,
source_location: SourceLocation,
) {
crate::module::add_stmt_formal(crate::module::StmtFormal {
kind,
clk,
pred,
en: en & !formal_reset().cast_to_static::<Bool>(),
text: text.intern(),
source_location,
});
}
#[track_caller]
pub fn formal_stmt_with_enable(
kind: FormalKind,
clk: Expr<Clock>,
pred: Expr<Bool>,
en: Expr<Bool>,
text: &str,
) {
formal_stmt_with_enable_and_loc(kind, clk, pred, en, text, SourceLocation::caller());
}
#[track_caller]
pub fn formal_stmt_with_loc(
kind: FormalKind,
clk: Expr<Clock>,
pred: Expr<Bool>,
text: &str,
source_location: SourceLocation,
) {
formal_stmt_with_enable_and_loc(kind, clk, pred, true.to_expr(), text, source_location);
}
#[track_caller]
pub fn formal_stmt(kind: FormalKind, clk: Expr<Clock>, pred: Expr<Bool>, text: &str) {
formal_stmt_with_loc(kind, clk, pred, text, SourceLocation::caller());
}
macro_rules! make_formal {
($kind:ident, $formal_stmt_with_enable_and_loc:ident, $formal_stmt_with_enable:ident, $formal_stmt_with_loc:ident, $formal_stmt:ident) => {
#[track_caller]
pub fn $formal_stmt_with_enable_and_loc(
clk: Expr<Clock>,
pred: Expr<Bool>,
en: Expr<Bool>,
text: &str,
source_location: SourceLocation,
) {
formal_stmt_with_enable_and_loc(
FormalKind::$kind,
clk,
pred,
en,
text,
source_location,
);
}
#[track_caller]
pub fn $formal_stmt_with_enable(
clk: Expr<Clock>,
pred: Expr<Bool>,
en: Expr<Bool>,
text: &str,
) {
formal_stmt_with_enable(FormalKind::$kind, clk, pred, en, text);
}
#[track_caller]
pub fn $formal_stmt_with_loc(
clk: Expr<Clock>,
pred: Expr<Bool>,
text: &str,
source_location: SourceLocation,
) {
formal_stmt_with_loc(FormalKind::$kind, clk, pred, text, source_location);
}
#[track_caller]
pub fn $formal_stmt(clk: Expr<Clock>, pred: Expr<Bool>, text: &str) {
formal_stmt(FormalKind::$kind, clk, pred, text);
}
};
}
make_formal!(
Assert,
hdl_assert_with_enable_and_loc,
hdl_assert_with_enable,
hdl_assert_with_loc,
hdl_assert
);
make_formal!(
Assume,
hdl_assume_with_enable_and_loc,
hdl_assume_with_enable,
hdl_assume_with_loc,
hdl_assume
);
make_formal!(
Cover,
hdl_cover_with_enable_and_loc,
hdl_cover_with_enable,
hdl_cover_with_loc,
hdl_cover
);
pub trait MakeFormalExpr: Type {}
impl<T: Type> MakeFormalExpr for T {}
#[hdl]
pub fn formal_global_clock() -> Expr<Clock> {
#[hdl_module(extern)]
fn formal_global_clock() {
#[hdl]
let clk: Clock = m.output();
m.annotate_module(BlackBoxInlineAnnotation {
path: "fayalite_formal_global_clock.v".intern(),
text: r"module __fayalite_formal_global_clock(output clk);
(* gclk *)
reg clk;
endmodule
"
.intern(),
});
m.verilog_name("__fayalite_formal_global_clock");
}
#[hdl]
let formal_global_clock = instance(formal_global_clock());
formal_global_clock.clk
}
#[hdl]
pub fn formal_reset() -> Expr<SyncReset> {
#[hdl_module(extern)]
fn formal_reset() {
#[hdl]
let rst: SyncReset = m.output();
m.annotate_module(BlackBoxInlineAnnotation {
path: "fayalite_formal_reset.v".intern(),
text: r"module __fayalite_formal_reset(output rst);
assign rst = $initstate;
endmodule
"
.intern(),
});
m.verilog_name("__fayalite_formal_reset");
}
static MOD: OnceLock<Interned<Module<formal_reset>>> = OnceLock::new();
#[hdl]
let formal_reset = instance(*MOD.get_or_init(formal_reset));
formal_reset.rst
}
macro_rules! make_any_const_fn {
($ident:ident, $verilog_attribute:literal) => {
#[hdl]
pub fn $ident<T: BoolOrIntType>(ty: T) -> Expr<T> {
#[hdl_module(extern)]
pub(super) fn $ident<T: BoolOrIntType>(ty: T) {
#[hdl]
let out: T = m.output(ty);
let width = ty.width();
let verilog_bitslice = if width == 1 {
String::new()
} else {
format!(" [{}:0]", width - 1)
};
m.annotate_module(BlackBoxInlineAnnotation {
path: Intern::intern_owned(format!(
"fayalite_{}_{width}.v",
stringify!($ident),
)),
text: Intern::intern_owned(format!(
r"module __fayalite_{}_{width}(output{verilog_bitslice} out);
(* {} *)
reg{verilog_bitslice} out;
endmodule
",
stringify!($ident),
$verilog_attribute,
)),
});
m.verilog_name(format!("__fayalite_{}_{width}", stringify!($ident)));
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
struct TheMemoize<T>(T);
impl<T: BoolOrIntType> Memoize for TheMemoize<T> {
type Input = ();
type InputOwned = ();
type Output = Option<Interned<Module<$ident<T>>>>;
fn inner(self, _input: &Self::Input) -> Self::Output {
if self.0.width() == 0 {
None
} else {
Some($ident(self.0))
}
}
}
let Some(module) = TheMemoize(ty).get_owned(()) else {
return 0_hdl_u0.cast_bits_to(ty);
};
#[hdl]
let $ident = instance(module);
$ident.out
}
};
}
make_any_const_fn!(any_const, "anyconst");
make_any_const_fn!(any_seq, "anyseq");
make_any_const_fn!(all_const, "allconst");
make_any_const_fn!(all_seq, "allseq");

View file

@ -1,368 +0,0 @@
use crate::{
intern::{Intern, Interned},
source_location::SourceLocation,
ty::{DynCanonicalType, Type, Value},
util::DebugAsDisplay,
};
use std::{fmt, hash::Hash};
mod sealed {
pub trait GenericArgsSealed {}
pub trait ToGenericArgSealed {}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct GenericParamName {
pub name: Interned<str>,
pub source_location: SourceLocation,
}
impl GenericParamName {
#[track_caller]
pub fn new(name: &str) -> Self {
Self::new_with_loc(name, SourceLocation::caller())
}
pub fn new_with_loc(name: &str, source_location: SourceLocation) -> Self {
let name = name.intern();
Self {
name,
source_location,
}
}
}
impl fmt::Debug for GenericParamName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.name.fmt(f)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct TypeParam {
pub name: GenericParamName,
pub default: Option<fn(earlier_args: &[GenericArg]) -> TypeArg>,
}
impl fmt::Debug for TypeParam {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TypeParam")
.field("name", &self.name)
.field("default", &self.default.map(|_| DebugAsDisplay("_")))
.finish()
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct ConstParam {
pub name: GenericParamName,
pub default: Option<fn(earlier_args: &[GenericArg]) -> ConstArg>,
}
impl fmt::Debug for ConstParam {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ConstParam")
.field("name", &self.name)
.field("default", &self.default.map(|_| DebugAsDisplay("_")))
.finish()
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum GenericParam {
Type(TypeParam),
Const(ConstParam),
}
impl fmt::Debug for GenericParam {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Type(v) => v.fmt(f),
Self::Const(v) => v.fmt(f),
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct TypeArg<T: Type = Interned<dyn DynCanonicalType>> {
pub ty: T,
}
impl<T: Type> TypeArg<T> {
pub fn canonical(&self) -> TypeArg {
TypeArg {
ty: self.ty.canonical_dyn(),
}
}
}
impl<T: Type> sealed::ToGenericArgSealed for T {}
impl<T: Type> ToGenericArg for T {
fn to_generic_arg(this: &Self) -> GenericArg {
GenericArg::Type(TypeArg {
ty: this.canonical_dyn(),
})
}
}
impl<T: Type> sealed::ToGenericArgSealed for TypeArg<T> {}
impl<T: Type> ToGenericArg for TypeArg<T> {
fn to_generic_arg(this: &Self) -> GenericArg {
GenericArg::Type(this.canonical())
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct ConstArg {
pub value: usize,
}
impl sealed::ToGenericArgSealed for usize {}
impl ToGenericArg for usize {
fn to_generic_arg(this: &Self) -> GenericArg {
GenericArg::Const(ConstArg { value: *this })
}
}
impl sealed::ToGenericArgSealed for ConstArg {}
impl ToGenericArg for ConstArg {
fn to_generic_arg(this: &Self) -> GenericArg {
GenericArg::Const(*this)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)]
pub struct DefaultArg;
impl sealed::ToGenericArgSealed for DefaultArg {}
impl ToGenericArg for DefaultArg {
fn to_generic_arg(this: &Self) -> GenericArg {
GenericArg::Default(*this)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum GenericArg<T: Type = Interned<dyn DynCanonicalType>> {
Type(TypeArg<T>),
Const(ConstArg),
Default(DefaultArg),
}
#[derive(Copy, Clone, Debug)]
pub struct NotATypeArg;
impl fmt::Display for NotATypeArg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("not a type argument")
}
}
impl std::error::Error for NotATypeArg {}
#[derive(Copy, Clone, Debug)]
pub struct NotAConstArg;
impl fmt::Display for NotAConstArg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("not a const argument")
}
}
impl std::error::Error for NotAConstArg {}
impl<T: Type> GenericArg<T> {
pub fn canonical(&self) -> GenericArg {
match self {
GenericArg::Type(v) => GenericArg::Type(v.canonical()),
GenericArg::Const(v) => GenericArg::Const(*v),
GenericArg::Default(v) => GenericArg::Default(*v),
}
}
pub fn ty(self) -> Result<T, NotATypeArg> {
match self {
GenericArg::Type(TypeArg { ty }) => Ok(ty),
GenericArg::Const(_) | GenericArg::Default(_) => Err(NotATypeArg),
}
}
pub fn const_(self) -> Result<usize, NotAConstArg> {
match self {
GenericArg::Const(ConstArg { value }) => Ok(value),
GenericArg::Type(_) | GenericArg::Default(_) => Err(NotAConstArg),
}
}
}
impl<T: Type> fmt::Debug for GenericArg<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Type(v) => v.fmt(f),
Self::Const(v) => v.fmt(f),
Self::Default(v) => v.fmt(f),
}
}
}
impl<T: Type> sealed::ToGenericArgSealed for GenericArg<T> {}
impl<T: Type> ToGenericArg for GenericArg<T> {
fn to_generic_arg(this: &Self) -> GenericArg {
this.canonical()
}
}
pub trait ToGenericArg: sealed::ToGenericArgSealed {
fn to_generic_arg(this: &Self) -> GenericArg;
}
pub trait GenericArgs: sealed::GenericArgsSealed {
fn generic_args(this: &Self) -> Vec<GenericArg>;
}
impl<T: GenericArgs + ?Sized + Send + Sync + 'static> sealed::GenericArgsSealed for Interned<T> {}
impl<T: GenericArgs + ?Sized + Send + Sync + 'static> GenericArgs for Interned<T> {
fn generic_args(this: &Self) -> Vec<GenericArg> {
T::generic_args(this)
}
}
impl<T: GenericArgs + ?Sized> sealed::GenericArgsSealed for Box<T> {}
impl<T: GenericArgs + ?Sized> GenericArgs for Box<T> {
fn generic_args(this: &Self) -> Vec<GenericArg> {
T::generic_args(this)
}
}
impl<T: GenericArgs + ?Sized> sealed::GenericArgsSealed for &'_ T {}
impl<T: GenericArgs + ?Sized> GenericArgs for &'_ T {
fn generic_args(this: &Self) -> Vec<GenericArg> {
T::generic_args(this)
}
}
impl<T: GenericArgs + ?Sized> sealed::GenericArgsSealed for &'_ mut T {}
impl<T: GenericArgs + ?Sized> GenericArgs for &'_ mut T {
fn generic_args(this: &Self) -> Vec<GenericArg> {
T::generic_args(this)
}
}
impl<T: ToGenericArg> sealed::GenericArgsSealed for [T] {}
impl<T: ToGenericArg> GenericArgs for [T] {
fn generic_args(this: &Self) -> Vec<GenericArg> {
Vec::from_iter(this.iter().map(ToGenericArg::to_generic_arg))
}
}
impl<T: ToGenericArg> sealed::GenericArgsSealed for Vec<T> {}
impl<T: ToGenericArg> GenericArgs for Vec<T> {
fn generic_args(this: &Self) -> Vec<GenericArg> {
Vec::from_iter(this.iter().map(ToGenericArg::to_generic_arg))
}
}
impl<T: ToGenericArg, const N: usize> sealed::GenericArgsSealed for [T; N] {}
impl<T: ToGenericArg, const N: usize> GenericArgs for [T; N] {
fn generic_args(this: &Self) -> Vec<GenericArg> {
Vec::from_iter(this.iter().map(ToGenericArg::to_generic_arg))
}
}
macro_rules! impl_generic_args_for_tuples {
($($v:ident: $T:ident),*) => {
impl<$($T: ToGenericArg,)*> sealed::GenericArgsSealed for ($($T,)*) {}
impl<$($T: ToGenericArg,)*> GenericArgs for ($($T,)*) {
fn generic_args(this: &Self) -> Vec<GenericArg> {
let ($($v,)*) = this;
vec![$($T::to_generic_arg($v),)*]
}
}
};
}
impl_generic_args_for_tuples!();
impl_generic_args_for_tuples!(a: A);
impl_generic_args_for_tuples!(a: A, b: B);
impl_generic_args_for_tuples!(a: A, b: B, c: C);
impl_generic_args_for_tuples!(a: A, b: B, c: C, d: D);
impl_generic_args_for_tuples!(a: A, b: B, c: C, d: D, e: E);
impl_generic_args_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F);
impl_generic_args_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F, g: G);
impl_generic_args_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H);
impl_generic_args_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I);
impl_generic_args_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J);
impl_generic_args_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K);
impl_generic_args_for_tuples!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L);
#[track_caller]
pub fn canonical_generic_args(
mut generic_args: Vec<GenericArg>,
generic_params: &[GenericParam],
) -> Interned<[GenericArg]> {
assert!(generic_params.len() >= generic_args.len());
for (i, generic_param) in generic_params.iter().enumerate() {
match generic_args.get(i) {
Some(GenericArg::Default(DefaultArg)) | None => {}
Some(GenericArg::Type(_)) | Some(GenericArg::Const(_)) => continue,
}
let generic_arg = match generic_param {
GenericParam::Type(TypeParam {
name: _,
default: Some(default),
}) => GenericArg::Type(default(&generic_args[..i])),
GenericParam::Type(TypeParam {
name:
GenericParamName {
name,
source_location,
},
default: None,
}) => panic!("Missing type argument for {name} declared here: {source_location}"),
GenericParam::Const(ConstParam {
name: _,
default: Some(default),
}) => GenericArg::Const(default(&generic_args[..i])),
GenericParam::Const(ConstParam {
name:
GenericParamName {
name,
source_location,
},
default: None,
}) => panic!("Missing const argument for {name} declared here: {source_location}"),
};
if i == generic_args.len() {
generic_args.push(generic_arg)
} else {
generic_args[i] = generic_arg;
}
}
debug_assert_eq!(generic_params.len(), generic_args.len());
Intern::intern_owned(generic_args)
}
pub trait GenericType {
type GenericArgs: GenericArgs;
type Type: Type<Value = Self::Value>;
type Value: Value<Type = Self::Type>;
fn generic_params() -> Interned<[GenericParam]>;
#[track_caller]
fn canonical_generic_args(generic_args: &Self::GenericArgs) -> Interned<[GenericArg]> {
canonical_generic_args(
GenericArgs::generic_args(&generic_args),
&Self::generic_params(),
)
}
fn ty(generic_args: Interned<[GenericArg]>) -> Self::Type;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -12,7 +12,6 @@ extern crate self as fayalite;
pub use std as __std;
#[doc(inline)]
#[doc(alias = "hdl")]
/// The `#[hdl_module]` attribute is applied to a Rust function so that that function creates
/// a [`Module`][`::fayalite::module::Module`] when called.
/// In the function body it will implicitly create a
@ -21,6 +20,13 @@ pub use std as __std;
/// See [Fayalite Modules][crate::_docs::modules]
pub use fayalite_proc_macros::hdl_module;
#[doc(inline)]
pub use fayalite_proc_macros::hdl;
/// struct used as a placeholder when applying defaults
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct __;
#[cfg(feature = "unstable-doc")]
pub mod _docs;
@ -32,15 +38,16 @@ pub mod clock;
pub mod enum_;
pub mod expr;
pub mod firrtl;
pub mod generics;
pub mod formal;
pub mod int;
pub mod intern;
pub mod memory;
pub mod module;
pub mod prelude;
pub mod reg;
pub mod reset;
pub mod source_location;
pub mod testing;
pub mod ty;
pub mod util;
pub mod valueless;
pub mod wire;

View file

@ -4,15 +4,16 @@
#![allow(clippy::multiple_bound_locations)]
use crate::{
annotations::{Annotation, IntoAnnotations, TargetedAnnotation},
array::{Array, ArrayType, ArrayTypeTrait, ValueArrayOrSlice},
bundle::{BundleType, BundleValue, DynBundle, DynBundleType},
clock::{Clock, ClockType},
expr::{Expr, ExprEnum, ExprTrait, Flow, ToExpr},
int::{DynUInt, DynUIntType, UInt, UIntType},
array::{Array, ArrayType},
bundle::{Bundle, BundleType},
clock::Clock,
expr::{ops::BundleLiteral, repeat, Expr, Flow, ToExpr, ToLiteralBits},
hdl,
int::{Bool, DynSize, Size, UInt, UIntType},
intern::{Intern, Interned},
module::ScopedNameId,
source_location::SourceLocation,
ty::{AsMask, DynCanonicalType, DynCanonicalValue, DynType, Type, Value},
ty::{AsMask, CanonicalType, Type},
util::DebugAsDisplay,
};
use bitvec::slice::BitSlice;
@ -20,37 +21,37 @@ use std::{
cell::RefCell,
fmt,
hash::{Hash, Hasher},
marker::PhantomData,
num::NonZeroU32,
rc::Rc,
};
#[derive(Value, Clone, PartialEq, Eq, Hash, Debug)]
pub struct ReadStruct<Element> {
pub addr: DynUInt,
pub en: UInt<1>,
#[hdl]
pub struct ReadStruct<Element, AddrWidth: Size> {
pub addr: UIntType<AddrWidth>,
pub en: Bool,
pub clk: Clock,
#[hdl(flip)]
pub data: Element,
}
#[derive(Value, Clone, PartialEq, Eq, Hash, Debug)]
pub struct WriteStruct<Element: Value> {
pub addr: DynUInt,
pub en: UInt<1>,
#[hdl]
pub struct WriteStruct<Element, AddrWidth: Size> {
pub addr: UIntType<AddrWidth>,
pub en: Bool,
pub clk: Clock,
pub data: Element,
pub mask: AsMask<Element>,
}
#[allow(clippy::multiple_bound_locations)]
#[derive(Value, Clone, PartialEq, Eq, Hash, Debug)]
pub struct ReadWriteStruct<Element: Value> {
pub addr: DynUInt,
pub en: UInt<1>,
#[hdl]
pub struct ReadWriteStruct<Element, AddrWidth: Size> {
pub addr: UIntType<AddrWidth>,
pub en: Bool,
pub clk: Clock,
#[hdl(flip)]
pub rdata: Element,
pub wmode: UInt<1>,
pub wmode: Bool,
pub wdata: Element,
pub wmask: AsMask<Element>,
}
@ -63,11 +64,10 @@ pub trait PortType:
sealed::Sealed + Clone + Eq + Hash + fmt::Debug + Send + Sync + 'static
{
type PortKindTy: Copy + Eq + Hash + fmt::Debug + Send + Sync + 'static;
type PortType: BundleType<Value = Self::PortValue>;
type PortValue: BundleValue<Type = Self::PortType>;
type Port: BundleType;
fn port_kind(port_kind: Self::PortKindTy) -> PortKind;
fn from_port_kind(port_kind: PortKind) -> Self::PortKindTy;
fn port_ty(port: &MemPort<Self>) -> Self::PortType;
fn port_ty(port: &MemPort<Self>) -> Self::Port;
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
@ -79,8 +79,7 @@ impl sealed::Sealed for DynPortType {}
impl PortType for DynPortType {
type PortKindTy = PortKind;
type PortType = DynBundleType;
type PortValue = DynBundle;
type Port = Bundle;
fn port_kind(port_kind: Self::PortKindTy) -> PortKind {
port_kind
@ -90,41 +89,38 @@ impl PortType for DynPortType {
port_kind
}
fn port_ty(port: &MemPort<Self>) -> Self::PortType {
match port.port_kind {
PortKind::ReadOnly => MemPort::<ReadStruct<DynCanonicalValue>>::from_canonical(*port)
fn port_ty(port: &MemPort<Self>) -> Self::Port {
Bundle::new(match port.port_kind {
PortKind::ReadOnly => {
MemPort::<ReadStruct<CanonicalType, DynSize>>::from_canonical(*port)
.ty()
.canonical(),
PortKind::WriteOnly => MemPort::<WriteStruct<DynCanonicalValue>>::from_canonical(*port)
.fields()
}
PortKind::WriteOnly => {
MemPort::<WriteStruct<CanonicalType, DynSize>>::from_canonical(*port)
.ty()
.canonical(),
.fields()
}
PortKind::ReadWrite => {
MemPort::<ReadWriteStruct<DynCanonicalValue>>::from_canonical(*port)
MemPort::<ReadWriteStruct<CanonicalType, DynSize>>::from_canonical(*port)
.ty()
.canonical()
}
.fields()
}
})
}
}
pub trait PortStruct:
BundleValue
+ sealed::Sealed
+ PortType<PortKindTy = (), PortType = <Self as ToExpr>::Type, PortValue = Self>
where
Self::Type: BundleType<Value = Self>,
{
type Element: Value<Type = Self::ElementType>;
type ElementType: Type<Value = Self::Element>;
pub trait PortStruct: BundleType + sealed::Sealed + PortType<PortKindTy = (), Port = Self> {
type Element: Type;
const PORT_KIND: PortKind;
fn addr(this: Expr<Self>) -> Expr<DynUInt>;
fn en(this: Expr<Self>) -> Expr<UInt<1>>;
fn addr(this: Expr<Self>) -> Expr<UInt>;
fn en(this: Expr<Self>) -> Expr<Bool>;
fn clk(this: Expr<Self>) -> Expr<Clock>;
fn rdata(this: Expr<Self>) -> Option<Expr<Self::Element>>;
fn wdata(this: Expr<Self>) -> Option<Expr<Self::Element>>;
fn wmask(this: Expr<Self>) -> Option<Expr<AsMask<Self::Element>>>;
fn wmode(this: Expr<Self>) -> Expr<UInt<1>>;
fn wmode(this: Expr<Self>) -> Expr<Bool>;
}
macro_rules! impl_port_struct {
@ -137,19 +133,11 @@ macro_rules! impl_port_struct {
$($body:tt)*
}
) => {
impl<$Element: Value> sealed::Sealed for $Struct<$Element>
where
$Element::Type: Type<Value = $Element>,
{
}
impl<$Element: Type> sealed::Sealed for $Struct<$Element, DynSize> {}
impl<$Element: Value> PortType for $Struct<$Element>
where
$Element::Type: Type<Value = $Element>,
{
impl<$Element: Type> PortType for $Struct<$Element, DynSize> {
type PortKindTy = ();
type PortType = <Self as ToExpr>::Type;
type PortValue = Self;
type Port = Self;
fn port_kind(_port_kind: Self::PortKindTy) -> PortKind {
Self::PORT_KIND
@ -164,18 +152,14 @@ macro_rules! impl_port_struct {
}
}
impl<$Element: Value> PortStruct for $Struct<$Element>
where
$Element::Type: Type<Value = $Element>,
{
impl<$Element: Type> PortStruct for $Struct<$Element, DynSize> {
type Element = $Element;
type ElementType = $Element::Type;
fn addr(this: Expr<Self>) -> Expr<DynUInt> {
fn addr(this: Expr<Self>) -> Expr<UInt> {
this.addr
}
fn en(this: Expr<Self>) -> Expr<UInt<1>> {
fn en(this: Expr<Self>) -> Expr<Bool> {
this.en
}
@ -190,14 +174,9 @@ macro_rules! impl_port_struct {
impl_port_struct! {
impl<Element> _ for ReadStruct {
fn port_ty(port: &MemPort<Self>) -> <MemPort<Self> as ToExpr>::Type {
type T<V> = <V as ToExpr>::Type;
T::<Self> {
addr: port.addr_type,
en: UIntType::new(),
clk: ClockType,
data: Element::Type::from_dyn_canonical_type(port.mem_element_type),
}
fn port_ty(port: &MemPort<Self>) -> Self {
let element = Element::from_canonical(port.mem_element_type);
ReadStruct[element][port.addr_type.width()]
}
const PORT_KIND: PortKind = PortKind::ReadOnly;
@ -214,7 +193,7 @@ impl_port_struct! {
None
}
fn wmode(_this: Expr<Self>) -> Expr<UInt<1>> {
fn wmode(_this: Expr<Self>) -> Expr<Bool> {
false.to_expr()
}
}
@ -222,16 +201,9 @@ impl_port_struct! {
impl_port_struct! {
impl<Element> _ for WriteStruct {
fn port_ty(port: &MemPort<Self>) -> <MemPort<Self> as ToExpr>::Type {
type T<V> = <V as ToExpr>::Type;
let element_ty = Element::Type::from_dyn_canonical_type(port.mem_element_type);
T::<Self> {
addr: port.addr_type,
en: UIntType::new(),
clk: ClockType,
mask: element_ty.mask_type(),
data: element_ty,
}
fn port_ty(port: &MemPort<Self>) -> Self {
let element = Element::from_canonical(port.mem_element_type);
WriteStruct[element][port.addr_type.width()]
}
const PORT_KIND: PortKind = PortKind::WriteOnly;
@ -248,7 +220,7 @@ impl_port_struct! {
Some(this.mask)
}
fn wmode(_this: Expr<Self>) -> Expr<UInt<1>> {
fn wmode(_this: Expr<Self>) -> Expr<Bool> {
true.to_expr()
}
}
@ -256,18 +228,9 @@ impl_port_struct! {
impl_port_struct! {
impl<Element> _ for ReadWriteStruct {
fn port_ty(port: &MemPort<Self>) -> <MemPort<Self> as ToExpr>::Type {
type T<V> = <V as ToExpr>::Type;
let element_ty = Element::Type::from_dyn_canonical_type(port.mem_element_type);
T::<Self> {
addr: port.addr_type,
en: UIntType::new(),
clk: ClockType,
rdata: element_ty.clone(),
wmode: UIntType::new(),
wmask: element_ty.mask_type(),
wdata: element_ty,
}
fn port_ty(port: &MemPort<Self>) -> Self {
let element = Element::from_canonical(port.mem_element_type);
ReadWriteStruct[element][port.addr_type.width()]
}
const PORT_KIND: PortKind = PortKind::ReadWrite;
@ -284,7 +247,7 @@ impl_port_struct! {
Some(this.wmask)
}
fn wmode(this: Expr<Self>) -> Expr<UInt<1>> {
fn wmode(this: Expr<Self>) -> Expr<Bool> {
this.wmode
}
}
@ -382,42 +345,31 @@ pub struct MemPort<T: PortType> {
source_location: SourceLocation,
port_kind: T::PortKindTy,
port_index: usize,
addr_type: DynUIntType,
mem_element_type: Interned<dyn DynCanonicalType>,
addr_type: UInt,
mem_element_type: CanonicalType,
}
impl<T: PortType> fmt::Debug for MemPort<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
mem_name,
source_location: _,
port_kind: _,
port_index: _,
addr_type,
mem_element_type,
} = self;
f.debug_struct("MemPort")
.field("mem_name", mem_name)
.field("port_name", &self.port_name())
.field("addr_type", addr_type)
.field("mem_element_type", mem_element_type)
.finish_non_exhaustive()
f.write_str("MemPort(")?;
self.mem_name.fmt(f)?;
f.write_str(".")?;
self.port_name().fmt(f)?;
f.write_str(": ")?;
match self.port_kind() {
PortKind::ReadOnly => f.write_str("ReadStruct<")?,
PortKind::WriteOnly => f.write_str("WriteStruct<")?,
PortKind::ReadWrite => f.write_str("ReadWriteStruct<")?,
}
}
impl<T: PortType> ToExpr for MemPort<T> {
type Type = T::PortType;
fn ty(&self) -> Self::Type {
T::port_ty(self)
}
fn to_expr(&self) -> Expr<<Self::Type as Type>::Value> {
Expr::new_unchecked(self.expr_enum())
self.mem_element_type.fmt(f)?;
f.write_str(">)")
}
}
impl<T: PortType> MemPort<T> {
pub fn ty(&self) -> T::Port {
T::port_ty(self)
}
pub fn source_location(&self) -> SourceLocation {
self.source_location
}
@ -436,10 +388,10 @@ impl<T: PortType> MemPort<T> {
index: self.port_index,
}
}
pub fn mem_element_type(&self) -> Interned<dyn DynCanonicalType> {
pub fn mem_element_type(&self) -> CanonicalType {
self.mem_element_type
}
pub fn addr_type(&self) -> DynUIntType {
pub fn addr_type(&self) -> UInt {
self.addr_type
}
pub fn canonical(&self) -> MemPort<DynPortType> {
@ -463,7 +415,6 @@ impl<T: PortType> MemPort<T> {
pub fn from_canonical(port: MemPort<DynPortType>) -> Self
where
T: PortStruct,
T::Type: BundleType<Value = T>,
{
let MemPort {
mem_name,
@ -488,8 +439,8 @@ impl<T: PortType> MemPort<T> {
mem_name: ScopedNameId,
source_location: SourceLocation,
port_name: PortName,
addr_type: DynUIntType,
mem_element_type: Interned<dyn DynCanonicalType>,
addr_type: UInt,
mem_element_type: CanonicalType,
) -> Self {
assert!(
mem_element_type.is_storable(),
@ -520,10 +471,10 @@ pub enum ReadUnderWrite {
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
struct MemImpl<T: ArrayTypeTrait, P> {
struct MemImpl<Element: Type, Len: Size, P> {
scoped_name: ScopedNameId,
source_location: SourceLocation,
array_type: T,
array_type: ArrayType<Element, Len>,
initial_value: Option<Interned<BitSlice>>,
ports: P,
read_latency: usize,
@ -533,11 +484,11 @@ struct MemImpl<T: ArrayTypeTrait, P> {
mem_annotations: Interned<[Annotation]>,
}
pub struct Mem<VA: ValueArrayOrSlice + ?Sized>(
Interned<MemImpl<ArrayType<VA>, Interned<[Interned<MemPort<DynPortType>>]>>>,
pub struct Mem<Element: Type = CanonicalType, Len: Size = DynSize>(
Interned<MemImpl<Element, Len, Interned<[MemPort<DynPortType>]>>>,
);
struct PortsDebug<'a>(&'a [Interned<MemPort<DynPortType>>]);
struct PortsDebug<'a>(&'a [MemPort<DynPortType>]);
impl fmt::Debug for PortsDebug<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -551,7 +502,7 @@ impl fmt::Debug for PortsDebug<'_> {
}
}
impl<VA: ValueArrayOrSlice + ?Sized> fmt::Debug for Mem<VA> {
impl<Element: Type, Len: Size> fmt::Debug for Mem<Element, Len> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let MemImpl {
scoped_name,
@ -579,37 +530,37 @@ impl<VA: ValueArrayOrSlice + ?Sized> fmt::Debug for Mem<VA> {
}
}
impl<VA: ValueArrayOrSlice + ?Sized> Hash for Mem<VA> {
impl<Element: Type, Len: Size> Hash for Mem<Element, Len> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<VA: ValueArrayOrSlice + ?Sized> Eq for Mem<VA> {}
impl<Element: Type, Len: Size> Eq for Mem<Element, Len> {}
impl<VA: ValueArrayOrSlice + ?Sized> PartialEq for Mem<VA> {
impl<Element: Type, Len: Size> PartialEq for Mem<Element, Len> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<VA: ValueArrayOrSlice + ?Sized> Copy for Mem<VA> {}
impl<Element: Type, Len: Size> Copy for Mem<Element, Len> {}
impl<VA: ValueArrayOrSlice + ?Sized> Clone for Mem<VA> {
impl<Element: Type, Len: Size> Clone for Mem<Element, Len> {
fn clone(&self) -> Self {
*self
}
}
impl<VA: ValueArrayOrSlice + ?Sized> Mem<VA> {
impl<Element: Type, Len: Size> Mem<Element, Len> {
#[allow(clippy::too_many_arguments)]
#[track_caller]
pub fn new_unchecked(
scoped_name: ScopedNameId,
source_location: SourceLocation,
array_type: ArrayType<VA>,
array_type: ArrayType<Element, Len>,
initial_value: Option<Interned<BitSlice>>,
ports: Interned<[Interned<MemPort<DynPortType>>]>,
ports: Interned<[MemPort<DynPortType>]>,
read_latency: usize,
write_latency: NonZeroU32,
read_under_write: ReadUnderWrite,
@ -617,14 +568,14 @@ impl<VA: ValueArrayOrSlice + ?Sized> Mem<VA> {
mem_annotations: Interned<[Annotation]>,
) -> Self {
if let Some(initial_value) = initial_value {
MemBuilder::<VA>::check_initial_value_bit_slice(
MemBuilder::<Element, Len>::check_initial_value_bit_slice(
array_type.element(),
Some(array_type.len()),
initial_value,
);
}
let addr_width = memory_addr_width(array_type.len());
let expected_mem_element_type = array_type.element().canonical_dyn();
let expected_mem_element_type = array_type.element().canonical();
assert!(
expected_mem_element_type.is_storable(),
"memory element type must be a storable type"
@ -637,7 +588,7 @@ impl<VA: ValueArrayOrSlice + ?Sized> Mem<VA> {
port_index,
addr_type,
mem_element_type,
} = **port;
} = *port;
assert_eq!(mem_name, scoped_name, "memory name must match with ports");
assert_eq!(
port_index, index,
@ -659,7 +610,7 @@ impl<VA: ValueArrayOrSlice + ?Sized> Mem<VA> {
};
assert_eq!(
Some(port),
ports.get(port.port_index).map(|v| &**v),
ports.get(port.port_index),
"port on memory must match annotation's target base"
);
}
@ -682,13 +633,13 @@ impl<VA: ValueArrayOrSlice + ?Sized> Mem<VA> {
pub fn source_location(self) -> SourceLocation {
self.0.source_location
}
pub fn array_type(self) -> ArrayType<VA> {
self.0.array_type.clone()
pub fn array_type(self) -> ArrayType<Element, Len> {
self.0.array_type
}
pub fn initial_value(self) -> Option<Interned<BitSlice>> {
self.0.initial_value
}
pub fn ports(self) -> Interned<[Interned<MemPort<DynPortType>>]> {
pub fn ports(self) -> Interned<[MemPort<DynPortType>]> {
self.0.ports
}
pub fn read_latency(self) -> usize {
@ -706,7 +657,7 @@ impl<VA: ValueArrayOrSlice + ?Sized> Mem<VA> {
pub fn mem_annotations(self) -> Interned<[Annotation]> {
self.0.mem_annotations
}
pub fn canonical(self) -> Mem<[DynCanonicalValue]> {
pub fn canonical(self) -> Mem {
let MemImpl {
scoped_name,
source_location,
@ -719,7 +670,7 @@ impl<VA: ValueArrayOrSlice + ?Sized> Mem<VA> {
port_annotations,
mem_annotations,
} = *self.0;
let array_type = array_type.canonical();
let array_type = array_type.as_dyn_array();
Mem(Intern::intern_sized(MemImpl {
scoped_name,
source_location,
@ -751,10 +702,10 @@ impl<T: fmt::Debug> fmt::Debug for MaybeSpecified<T> {
pub(crate) struct MemBuilderTarget {
pub(crate) scoped_name: ScopedNameId,
pub(crate) source_location: SourceLocation,
pub(crate) mem_element_type: Interned<dyn DynCanonicalType>,
pub(crate) mem_element_type: CanonicalType,
pub(crate) depth: Option<usize>,
pub(crate) initial_value: Option<Interned<BitSlice>>,
pub(crate) ports: Vec<Interned<MemPort<DynPortType>>>,
pub(crate) ports: Vec<MemPort<DynPortType>>,
pub(crate) read_latency: usize,
pub(crate) write_latency: NonZeroU32,
pub(crate) read_under_write: ReadUnderWrite,
@ -763,11 +714,11 @@ pub(crate) struct MemBuilderTarget {
}
impl MemBuilderTarget {
pub(crate) fn make_memory(&self) -> Option<Mem<[DynCanonicalValue]>> {
pub(crate) fn make_memory(&self) -> Option<Mem> {
Some(Mem::new_unchecked(
self.scoped_name,
self.source_location,
ArrayType::new_slice(self.mem_element_type, self.depth?),
ArrayType::new_dyn(self.mem_element_type, self.depth?),
self.initial_value,
Intern::intern(&self.ports),
self.read_latency,
@ -817,16 +768,18 @@ impl fmt::Debug for MemBuilderTarget {
}
}
pub struct MemBuilder<VA: ValueArrayOrSlice + ?Sized> {
mem_element_type: VA::ElementType,
pub struct MemBuilder<Element: Type, Len: Size = DynSize> {
mem_element_type: Element,
target: Rc<RefCell<MemBuilderTarget>>,
_phantom: PhantomData<Len>,
}
impl<VA: ValueArrayOrSlice + ?Sized> fmt::Debug for MemBuilder<VA> {
impl<Element: Type, Len: Size> fmt::Debug for MemBuilder<Element, Len> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
mem_element_type,
target,
_phantom: _,
} = &self;
target.borrow().debug_fmt("MemBuilder", mem_element_type, f)
}
@ -838,15 +791,16 @@ pub fn memory_addr_width(depth: usize) -> usize {
.map_or(usize::BITS, usize::ilog2) as usize
}
impl<VA: ValueArrayOrSlice + ?Sized> MemBuilder<VA> {
impl<Element: Type, Len: Size> MemBuilder<Element, Len> {
#[track_caller]
fn check_initial_value_bit_slice(
mem_element_type: &VA::ElementType,
mem_element_type: Element,
depth: Option<usize>,
initial_value: Interned<BitSlice>,
) -> Interned<BitSlice> {
let element_bit_width = mem_element_type.canonical().bit_width();
if let Some(depth) = depth {
let expected_len = depth.checked_mul(mem_element_type.bit_width()).expect(
let expected_len = depth.checked_mul(element_bit_width).expect(
"memory must be small enough that its initializer bit length fits in usize",
);
assert_eq!(
@ -858,7 +812,7 @@ impl<VA: ValueArrayOrSlice + ?Sized> MemBuilder<VA> {
assert!(
initial_value
.len()
.checked_rem(mem_element_type.bit_width())
.checked_rem(element_bit_width)
.unwrap_or(initial_value.len())
== 0,
"Mem's initializer bit length must be a multiple of the element type's bit width",
@ -867,14 +821,14 @@ impl<VA: ValueArrayOrSlice + ?Sized> MemBuilder<VA> {
}
#[track_caller]
fn check_initial_value_expr(
mem_element_type: &VA::ElementType,
mem_element_type: &Element,
depth: Option<usize>,
initial_value: Expr<Array<[DynCanonicalValue]>>,
initial_value: Expr<Array>,
) -> Interned<BitSlice> {
let initial_value_ty = initial_value.canonical_type();
let initial_value_ty = Expr::ty(initial_value);
assert_eq!(
*mem_element_type,
<VA::ElementType as DynType>::from_dyn_canonical_type(*initial_value_ty.element()),
Element::from_canonical(initial_value_ty.element()),
"Mem's element type must match initializer's element type",
);
if let Some(depth) = depth {
@ -889,7 +843,7 @@ impl<VA: ValueArrayOrSlice + ?Sized> MemBuilder<VA> {
};
debug_assert_eq!(
retval.len(),
initial_value_ty.bit_width(),
initial_value_ty.type_properties().bit_width,
"initial value produced wrong literal bits length"
);
retval
@ -898,9 +852,9 @@ impl<VA: ValueArrayOrSlice + ?Sized> MemBuilder<VA> {
pub(crate) fn new(
scoped_name: ScopedNameId,
source_location: SourceLocation,
mem_element_type: VA::ElementType,
mem_element_type: Element,
) -> (Self, Rc<RefCell<MemBuilderTarget>>) {
let canonical_mem_element_type = mem_element_type.canonical_dyn();
let canonical_mem_element_type = mem_element_type.canonical();
assert!(
canonical_mem_element_type.is_storable(),
"memory element type must be a storable type"
@ -909,7 +863,7 @@ impl<VA: ValueArrayOrSlice + ?Sized> MemBuilder<VA> {
scoped_name,
source_location,
mem_element_type: canonical_mem_element_type,
depth: VA::FIXED_LEN_TYPE.map(VA::len_from_len_type),
depth: Len::KNOWN_VALUE,
initial_value: None,
ports: vec![],
read_latency: 0,
@ -922,6 +876,7 @@ impl<VA: ValueArrayOrSlice + ?Sized> MemBuilder<VA> {
Self {
mem_element_type,
target: Rc::clone(&target),
_phantom: PhantomData,
},
target,
)
@ -931,19 +886,19 @@ impl<VA: ValueArrayOrSlice + ?Sized> MemBuilder<VA> {
&mut self,
source_location: SourceLocation,
port_kind: PortKind,
) -> Interned<MemPort<DynPortType>> {
) -> MemPort<DynPortType> {
let mut target = self.target.borrow_mut();
let Some(depth) = target.depth else {
panic!("MemBuilder::depth must be called before adding ports");
};
let port = Intern::intern_sized(MemPort {
let port = MemPort {
mem_name: target.scoped_name,
source_location,
port_kind,
port_index: target.ports.len(),
addr_type: DynUIntType::new(memory_addr_width(depth)),
addr_type: UInt::new(memory_addr_width(depth)),
mem_element_type: target.mem_element_type,
});
};
target.ports.push(port);
port
}
@ -952,50 +907,53 @@ impl<VA: ValueArrayOrSlice + ?Sized> MemBuilder<VA> {
&mut self,
source_location: SourceLocation,
kind: PortKind,
) -> Expr<DynBundle> {
Expr::new_unchecked(ExprEnum::MemPort(self.new_port_impl(source_location, kind)))
) -> Expr<Bundle> {
self.new_port_impl(source_location, kind).to_expr()
}
#[track_caller]
pub fn new_port(&mut self, kind: PortKind) -> Expr<DynBundle> {
pub fn new_port(&mut self, kind: PortKind) -> Expr<Bundle> {
self.new_port_with_loc(SourceLocation::caller(), kind)
}
#[track_caller]
pub fn new_read_port_with_loc(
&mut self,
source_location: SourceLocation,
) -> Expr<ReadStruct<VA::Element>> {
Expr::new_unchecked(ExprEnum::MemPort(
self.new_port_impl(source_location, PortKind::ReadOnly),
))
) -> Expr<ReadStruct<Element, DynSize>> {
Expr::from_bundle(
self.new_port_impl(source_location, PortKind::ReadOnly)
.to_expr(),
)
}
#[track_caller]
pub fn new_read_port(&mut self) -> Expr<ReadStruct<VA::Element>> {
pub fn new_read_port(&mut self) -> Expr<ReadStruct<Element, DynSize>> {
self.new_read_port_with_loc(SourceLocation::caller())
}
#[track_caller]
pub fn new_write_port_with_loc(
&mut self,
source_location: SourceLocation,
) -> Expr<WriteStruct<VA::Element>> {
Expr::new_unchecked(ExprEnum::MemPort(
self.new_port_impl(source_location, PortKind::WriteOnly),
))
) -> Expr<WriteStruct<Element, DynSize>> {
Expr::from_bundle(
self.new_port_impl(source_location, PortKind::WriteOnly)
.to_expr(),
)
}
#[track_caller]
pub fn new_write_port(&mut self) -> Expr<WriteStruct<VA::Element>> {
pub fn new_write_port(&mut self) -> Expr<WriteStruct<Element, DynSize>> {
self.new_write_port_with_loc(SourceLocation::caller())
}
#[track_caller]
pub fn new_rw_port_with_loc(
&mut self,
source_location: SourceLocation,
) -> Expr<ReadWriteStruct<VA::Element>> {
Expr::new_unchecked(ExprEnum::MemPort(
self.new_port_impl(source_location, PortKind::ReadWrite),
))
) -> Expr<ReadWriteStruct<Element, DynSize>> {
Expr::from_bundle(
self.new_port_impl(source_location, PortKind::ReadWrite)
.to_expr(),
)
}
#[track_caller]
pub fn new_rw_port(&mut self) -> Expr<ReadWriteStruct<VA::Element>> {
pub fn new_rw_port(&mut self) -> Expr<ReadWriteStruct<Element, DynSize>> {
self.new_rw_port_with_loc(SourceLocation::caller())
}
pub fn scoped_name(&self) -> ScopedNameId {
@ -1004,7 +962,7 @@ impl<VA: ValueArrayOrSlice + ?Sized> MemBuilder<VA> {
pub fn source_location(&self) -> SourceLocation {
self.target.borrow().source_location
}
pub fn get_mem_element_type(&self) -> &VA::ElementType {
pub fn get_mem_element_type(&self) -> &Element {
&self.mem_element_type
}
#[allow(clippy::result_unit_err)]
@ -1027,28 +985,28 @@ impl<VA: ValueArrayOrSlice + ?Sized> MemBuilder<VA> {
target.depth = Some(depth);
}
#[allow(clippy::result_unit_err)]
pub fn get_array_type(&self) -> Result<ArrayType<VA>, ()> {
Ok(ArrayType::new_with_len(
self.mem_element_type.clone(),
self.get_depth()?,
pub fn get_array_type(&self) -> Result<ArrayType<Element, Len>, ()> {
Ok(ArrayType::new(
self.mem_element_type,
Len::from_usize(self.get_depth()?),
))
}
pub fn get_initial_value(&self) -> Option<Interned<BitSlice>> {
self.target.borrow().initial_value
}
#[track_caller]
pub fn initial_value(&mut self, initial_value: impl ToExpr<Type = ArrayType<VA>>) {
pub fn initial_value(&mut self, initial_value: impl ToExpr<Type = ArrayType<Element, Len>>) {
let mut target = self.target.borrow_mut();
if target.initial_value.is_some() {
panic!("can't set Mem's initial value more than once");
}
let initial_value = initial_value.to_expr().canonical();
let initial_value = Expr::as_dyn_array(initial_value.to_expr());
target.initial_value = Some(Self::check_initial_value_expr(
&self.mem_element_type,
target.depth,
initial_value,
));
target.depth = Some(initial_value.ty().len());
target.depth = Some(Expr::ty(initial_value).len());
}
#[track_caller]
pub fn initial_value_bit_slice(&mut self, initial_value: Interned<BitSlice>) {
@ -1057,11 +1015,11 @@ impl<VA: ValueArrayOrSlice + ?Sized> MemBuilder<VA> {
panic!("can't set Mem's initial value more than once");
}
target.initial_value = Some(Self::check_initial_value_bit_slice(
&self.mem_element_type,
self.mem_element_type,
target.depth,
initial_value,
));
let element_bit_width = self.mem_element_type.bit_width();
let element_bit_width = self.mem_element_type.canonical().bit_width();
if element_bit_width != 0 {
target.depth = Some(initial_value.len() / element_bit_width);
}
@ -1092,3 +1050,32 @@ impl<VA: ValueArrayOrSlice + ?Sized> MemBuilder<VA> {
.extend(annotations.into_annotations());
}
}
pub fn splat_mask<T: Type>(ty: T, value: Expr<Bool>) -> Expr<AsMask<T>> {
let canonical_ty = ty.canonical();
match canonical_ty {
CanonicalType::UInt(_)
| CanonicalType::SInt(_)
| CanonicalType::Bool(_)
| CanonicalType::AsyncReset(_)
| CanonicalType::SyncReset(_)
| CanonicalType::Reset(_)
| CanonicalType::Clock(_)
| CanonicalType::Enum(_) => Expr::from_canonical(Expr::canonical(value)),
CanonicalType::Array(array) => Expr::from_canonical(Expr::canonical(repeat(
splat_mask(array.element(), value),
array.len(),
))),
CanonicalType::Bundle(bundle) => Expr::from_canonical(Expr::canonical(
BundleLiteral::new(
bundle.mask_type(),
bundle
.fields()
.iter()
.map(|field| splat_mask(field.ty, value))
.collect(),
)
.to_expr(),
)),
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2,18 +2,18 @@
// See Notices.txt for copyright information
use crate::{
annotations::TargetedAnnotation,
array::{Array, ArrayType, ValueArrayOrSlice},
bundle::{BundleType, BundleValue, DynBundle},
expr::{Expr, ExprEnum, ToExpr},
int::{DynSInt, DynSIntType, DynUInt, DynUIntType},
array::Array,
bundle::{Bundle, BundleType},
expr::{CastBitsTo, CastToBits, Expr, ExprEnum, ToExpr},
int::{Bool, SInt, Size, UInt},
intern::{Intern, Interned},
memory::{Mem, MemPort, PortType},
module::{
transform::visit::{Fold, Folder},
Block, Module, NameId, NameIdGen, ScopedNameId, Stmt, StmtConnect, StmtWire,
Block, Id, Module, NameId, ScopedNameId, Stmt, StmtConnect, StmtWire,
},
source_location::SourceLocation,
ty::{DynCanonicalValue, DynType, Type, TypeEnum},
ty::{CanonicalType, Type},
util::MakeMutSlice,
wire::Wire,
};
@ -28,26 +28,31 @@ use std::{
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
enum SingleType {
UInt(DynUIntType),
SInt(DynSIntType),
UIntArray(ArrayType<[DynUInt]>),
SIntArray(ArrayType<[DynSInt]>),
UInt(UInt),
SInt(SInt),
Bool(Bool),
UIntArray(Array<UInt>),
SIntArray(Array<SInt>),
BoolArray(Array<Bool>),
}
impl SingleType {
fn is_array_type(self, array_type: ArrayType<[DynCanonicalValue]>) -> bool {
fn is_array_type(self, array_type: Array) -> bool {
match self {
SingleType::UInt(_) | SingleType::SInt(_) => false,
SingleType::UIntArray(ty) => ty.canonical() == array_type,
SingleType::SIntArray(ty) => ty.canonical() == array_type,
SingleType::UInt(_) | SingleType::SInt(_) | SingleType::Bool(_) => false,
SingleType::UIntArray(ty) => ty.as_dyn_array() == array_type,
SingleType::SIntArray(ty) => ty.as_dyn_array() == array_type,
SingleType::BoolArray(ty) => ty.as_dyn_array() == array_type,
}
}
fn array_len(self) -> usize {
match self {
SingleType::UInt(_ty) => 1,
SingleType::SInt(_ty) => 1,
SingleType::Bool(_ty) => 1,
SingleType::UIntArray(ty) => ty.len(),
SingleType::SIntArray(ty) => ty.len(),
SingleType::BoolArray(ty) => ty.len(),
}
}
}
@ -58,7 +63,7 @@ enum MemSplit {
fields: Rc<[MemSplit]>,
},
Single {
output_mem: Option<Mem<[DynCanonicalValue]>>,
output_mem: Option<Mem>,
element_type: SingleType,
unchanged_element_type: bool,
},
@ -83,17 +88,17 @@ impl MemSplit {
MemSplit::Array { elements: _ } => self,
}
}
fn new(element_type: TypeEnum) -> Self {
fn new(element_type: CanonicalType) -> Self {
match element_type {
TypeEnum::BundleType(bundle_ty) => MemSplit::Bundle {
CanonicalType::Bundle(bundle_ty) => MemSplit::Bundle {
fields: bundle_ty
.fields()
.into_iter()
.map(|field| Self::new(field.ty.type_enum()).mark_changed_element_type())
.map(|field| Self::new(field.ty).mark_changed_element_type())
.collect(),
},
TypeEnum::ArrayType(ty) => {
let element = MemSplit::new(ty.element().type_enum());
CanonicalType::Array(ty) => {
let element = MemSplit::new(ty.element());
if let Self::Single {
output_mem: _,
element_type,
@ -103,7 +108,7 @@ impl MemSplit {
match element_type {
SingleType::UInt(element_type) => Self::Single {
output_mem: None,
element_type: SingleType::UIntArray(ArrayType::new_slice(
element_type: SingleType::UIntArray(Array::new_dyn(
element_type,
ty.len(),
)),
@ -111,7 +116,15 @@ impl MemSplit {
},
SingleType::SInt(element_type) => Self::Single {
output_mem: None,
element_type: SingleType::SIntArray(ArrayType::new_slice(
element_type: SingleType::SIntArray(Array::new_dyn(
element_type,
ty.len(),
)),
unchanged_element_type,
},
SingleType::Bool(element_type) => Self::Single {
output_mem: None,
element_type: SingleType::BoolArray(Array::new_dyn(
element_type,
ty.len(),
)),
@ -119,8 +132,8 @@ impl MemSplit {
},
SingleType::UIntArray(element_type) => Self::Single {
output_mem: None,
element_type: SingleType::UIntArray(ArrayType::new_slice(
*element_type.element(),
element_type: SingleType::UIntArray(Array::new_dyn(
element_type.element(),
ty.len()
.checked_mul(element_type.len())
.expect("memory element type can't be too big"),
@ -129,8 +142,18 @@ impl MemSplit {
},
SingleType::SIntArray(element_type) => Self::Single {
output_mem: None,
element_type: SingleType::SIntArray(ArrayType::new_slice(
*element_type.element(),
element_type: SingleType::SIntArray(Array::new_dyn(
element_type.element(),
ty.len()
.checked_mul(element_type.len())
.expect("memory element type can't be too big"),
)),
unchanged_element_type: false,
},
SingleType::BoolArray(element_type) => Self::Single {
output_mem: None,
element_type: SingleType::BoolArray(Array::new_dyn(
element_type.element(),
ty.len()
.checked_mul(element_type.len())
.expect("memory element type can't be too big"),
@ -145,25 +168,30 @@ impl MemSplit {
}
}
}
TypeEnum::UInt(ty) => Self::Single {
CanonicalType::UInt(ty) => Self::Single {
output_mem: None,
element_type: SingleType::UInt(ty),
unchanged_element_type: true,
},
TypeEnum::SInt(ty) => Self::Single {
CanonicalType::SInt(ty) => Self::Single {
output_mem: None,
element_type: SingleType::SInt(ty),
unchanged_element_type: true,
},
TypeEnum::EnumType(ty) => Self::Single {
CanonicalType::Bool(ty) => Self::Single {
output_mem: None,
element_type: SingleType::UInt(DynUIntType::new(ty.bit_width())),
element_type: SingleType::Bool(ty),
unchanged_element_type: true,
},
CanonicalType::Enum(ty) => Self::Single {
output_mem: None,
element_type: SingleType::UInt(UInt::new_dyn(ty.type_properties().bit_width)),
unchanged_element_type: false,
},
TypeEnum::Clock(_)
| TypeEnum::AsyncReset(_)
| TypeEnum::SyncReset(_)
| TypeEnum::Reset(_) => unreachable!("memory element type is a storable type"),
CanonicalType::Clock(_)
| CanonicalType::AsyncReset(_)
| CanonicalType::SyncReset(_)
| CanonicalType::Reset(_) => unreachable!("memory element type is a storable type"),
}
}
}
@ -173,9 +201,9 @@ struct MemState {
}
struct SplitState<'a> {
wire_rdata: Box<[Option<Expr<DynCanonicalValue>>]>,
wire_wdata: Box<[Option<Expr<DynCanonicalValue>>]>,
wire_wmask: Box<[Option<Expr<DynCanonicalValue>>]>,
wire_rdata: Box<[Option<Expr<CanonicalType>>]>,
wire_wdata: Box<[Option<Expr<CanonicalType>>]>,
wire_wmask: Box<[Option<Expr<CanonicalType>>]>,
initial_value: Option<Box<[&'a BitSlice]>>,
}
@ -223,7 +251,7 @@ impl<'a> SplitStateStack<'a> {
}
fn push_map(
&mut self,
mut wire_map: impl FnMut(Expr<DynCanonicalValue>) -> Expr<DynCanonicalValue>,
mut wire_map: impl FnMut(Expr<CanonicalType>) -> Expr<CanonicalType>,
mut initial_value_element_map: impl FnMut(&BitSlice) -> &BitSlice,
) {
let top_index = self.top_index + 1;
@ -260,10 +288,10 @@ impl<'a> SplitStateStack<'a> {
struct SplitMemState<'a, 'b> {
module_state: &'a mut ModuleState,
input_mem: Mem<[DynCanonicalValue]>,
output_mems: &'a mut Vec<Mem<[DynCanonicalValue]>>,
input_mem: Mem,
output_mems: &'a mut Vec<Mem>,
output_stmts: &'a mut Vec<Stmt>,
element_type: TypeEnum,
element_type: CanonicalType,
split: &'a mut MemSplit,
mem_name_path: &'a mut String,
split_state_stack: &'a mut SplitStateStack<'b>,
@ -275,7 +303,7 @@ impl SplitMemState<'_, '_> {
let outer_mem_name_path_len = self.mem_name_path.len();
match self.split {
MemSplit::Bundle { fields } => {
let TypeEnum::BundleType(bundle_type) = self.element_type else {
let CanonicalType::Bundle(bundle_type) = self.element_type else {
unreachable!();
};
for ((field, field_offset), split) in bundle_type
@ -289,7 +317,9 @@ impl SplitMemState<'_, '_> {
self.mem_name_path.push_str(&field.name);
let field_ty_bit_width = field.ty.bit_width();
self.split_state_stack.push_map(
|e: Expr<DynCanonicalValue>| e.with_type::<DynBundle>().field(&field.name),
|e: Expr<CanonicalType>| {
Expr::field(Expr::<Bundle>::from_canonical(e), &field.name)
},
|initial_value_element| {
&initial_value_element[field_offset..][..field_ty_bit_width]
},
@ -299,7 +329,7 @@ impl SplitMemState<'_, '_> {
input_mem: self.input_mem,
output_mems: self.output_mems,
output_stmts: self.output_stmts,
element_type: field.ty.type_enum(),
element_type: field.ty,
split,
mem_name_path: self.mem_name_path,
split_state_stack: self.split_state_stack,
@ -328,7 +358,7 @@ impl SplitMemState<'_, '_> {
.zip(self.mem_state.replacement_ports.iter())
{
let port_expr = port.to_expr();
let wire_expr = Expr::<DynBundle>::new_unchecked(*wire);
let wire_expr = Expr::<Bundle>::from_canonical(wire.to_expr());
for name in [
Some("addr"),
Some("clk"),
@ -340,8 +370,8 @@ impl SplitMemState<'_, '_> {
};
self.output_stmts.push(
StmtConnect {
lhs: port_expr.field(name),
rhs: wire_expr.field(name),
lhs: Expr::field(port_expr, name),
rhs: Expr::field(wire_expr, name),
source_location: port.source_location(),
}
.into(),
@ -352,18 +382,16 @@ impl SplitMemState<'_, '_> {
self.output_mems.push(new_mem);
}
MemSplit::Array { elements } => {
let TypeEnum::ArrayType(array_type) = self.element_type else {
let CanonicalType::Array(array_type) = self.element_type else {
unreachable!();
};
let element_type = array_type.element().type_enum();
let element_bit_width = array_type.element().bit_width();
let element_type = array_type.element();
let element_bit_width = element_type.bit_width();
for (index, split) in elements.make_mut_slice().iter_mut().enumerate() {
self.mem_name_path.truncate(outer_mem_name_path_len);
write!(self.mem_name_path, "_{index}").unwrap();
self.split_state_stack.push_map(
|e: Expr<DynCanonicalValue>| {
e.with_type::<Array<[DynCanonicalValue]>>()[index]
},
|e| Expr::<Array>::from_canonical(e)[index],
|initial_value_element| {
&initial_value_element[index * element_bit_width..][..element_bit_width]
},
@ -388,8 +416,7 @@ impl SplitMemState<'_, '_> {
}
struct ModuleState {
output_module: Option<Interned<Module<DynBundle>>>,
name_id_gen: NameIdGen,
output_module: Option<Interned<Module<Bundle>>>,
memories: HashMap<ScopedNameId, MemState>,
}
@ -397,18 +424,18 @@ impl ModuleState {
#[allow(clippy::too_many_arguments)]
fn connect_split_mem_port_arrays(
output_stmts: &mut Vec<Stmt>,
input_array_types: &[ArrayType<[DynCanonicalValue]>],
input_array_types: &[Array],
memory_element_array_range_start: usize,
memory_element_array_range_len: usize,
wire_rdata: Option<Expr<DynCanonicalValue>>,
wire_wdata: Option<Expr<DynCanonicalValue>>,
wire_wmask: Option<Expr<DynCanonicalValue>>,
port_rdata: Option<Expr<Array<[DynCanonicalValue]>>>,
port_wdata: Option<Expr<Array<[DynCanonicalValue]>>>,
port_wmask: Option<Expr<Array<[DynCanonicalValue]>>>,
connect_rdata: impl Copy + Fn(&mut Vec<Stmt>, Expr<DynCanonicalValue>, Expr<DynCanonicalValue>),
connect_wdata: impl Copy + Fn(&mut Vec<Stmt>, Expr<DynCanonicalValue>, Expr<DynCanonicalValue>),
connect_wmask: impl Copy + Fn(&mut Vec<Stmt>, Expr<DynCanonicalValue>, Expr<DynCanonicalValue>),
wire_rdata: Option<Expr<CanonicalType>>,
wire_wdata: Option<Expr<CanonicalType>>,
wire_wmask: Option<Expr<CanonicalType>>,
port_rdata: Option<Expr<Array>>,
port_wdata: Option<Expr<Array>>,
port_wmask: Option<Expr<Array>>,
connect_rdata: impl Copy + Fn(&mut Vec<Stmt>, Expr<CanonicalType>, Expr<CanonicalType>),
connect_wdata: impl Copy + Fn(&mut Vec<Stmt>, Expr<CanonicalType>, Expr<CanonicalType>),
connect_wmask: impl Copy + Fn(&mut Vec<Stmt>, Expr<CanonicalType>, Expr<CanonicalType>),
) {
let Some((input_array_type, input_array_types_rest)) = input_array_types.split_first()
else {
@ -430,8 +457,7 @@ impl ModuleState {
assert_eq!(memory_element_array_range_len % input_array_type.len(), 0);
let chunk_size = memory_element_array_range_len / input_array_type.len();
for index in 0..input_array_type.len() {
let map =
|e: Expr<DynCanonicalValue>| e.with_type::<Array<[DynCanonicalValue]>>()[index];
let map = |e| Expr::<Array>::from_canonical(e)[index];
let wire_rdata = wire_rdata.map(map);
let wire_wdata = wire_wdata.map(map);
let wire_wmask = wire_wmask.map(map);
@ -456,20 +482,20 @@ impl ModuleState {
fn connect_split_mem_port(
&mut self,
output_stmts: &mut Vec<Stmt>,
mut input_element_type: TypeEnum,
mut input_element_type: CanonicalType,
single_type: SingleType,
source_location: SourceLocation,
wire_rdata: Option<Expr<DynCanonicalValue>>,
wire_wdata: Option<Expr<DynCanonicalValue>>,
wire_wmask: Option<Expr<DynCanonicalValue>>,
port_rdata: Option<Expr<DynCanonicalValue>>,
port_wdata: Option<Expr<DynCanonicalValue>>,
port_wmask: Option<Expr<DynCanonicalValue>>,
wire_rdata: Option<Expr<CanonicalType>>,
wire_wdata: Option<Expr<CanonicalType>>,
wire_wmask: Option<Expr<CanonicalType>>,
port_rdata: Option<Expr<CanonicalType>>,
port_wdata: Option<Expr<CanonicalType>>,
port_wmask: Option<Expr<CanonicalType>>,
) {
let mut input_array_types = vec![];
let connect_read = |output_stmts: &mut Vec<Stmt>,
wire_read: Expr<DynCanonicalValue>,
port_read: Expr<DynCanonicalValue>| {
wire_read: Expr<CanonicalType>,
port_read: Expr<CanonicalType>| {
output_stmts.push(
StmtConnect {
lhs: wire_read,
@ -480,8 +506,8 @@ impl ModuleState {
);
};
let connect_write = |output_stmts: &mut Vec<Stmt>,
wire_write: Expr<DynCanonicalValue>,
port_write: Expr<DynCanonicalValue>| {
wire_write: Expr<CanonicalType>,
port_write: Expr<CanonicalType>| {
output_stmts.push(
StmtConnect {
lhs: port_write,
@ -491,32 +517,29 @@ impl ModuleState {
.into(),
);
};
let connect_read_enum =
|output_stmts: &mut Vec<Stmt>,
wire_read: Expr<DynCanonicalValue>,
port_read: Expr<DynCanonicalValue>| {
let connect_read_enum = |output_stmts: &mut Vec<Stmt>,
wire_read: Expr<CanonicalType>,
port_read: Expr<CanonicalType>| {
connect_read(
output_stmts,
wire_read,
port_read
.with_type::<DynUInt>()
.cast_bits_to(wire_read.ty()),
Expr::<UInt>::from_canonical(port_read).cast_bits_to(Expr::ty(wire_read)),
);
};
let connect_write_enum =
|output_stmts: &mut Vec<Stmt>,
wire_write: Expr<DynCanonicalValue>,
port_write: Expr<DynCanonicalValue>| {
wire_write: Expr<CanonicalType>,
port_write: Expr<CanonicalType>| {
connect_write(
output_stmts,
wire_write.cast_to_bits().to_canonical_dyn(),
Expr::canonical(wire_write.cast_to_bits()),
port_write,
);
};
loop {
match input_element_type {
TypeEnum::BundleType(_) => unreachable!("bundle types are always split"),
TypeEnum::EnumType(_)
CanonicalType::Bundle(_) => unreachable!("bundle types are always split"),
CanonicalType::Enum(_)
if input_array_types
.first()
.map(|&v| single_type.is_array_type(v))
@ -532,7 +555,7 @@ impl ModuleState {
connect_write(output_stmts, wire_wmask, port_wmask);
}
}
TypeEnum::EnumType(_) => Self::connect_split_mem_port_arrays(
CanonicalType::Enum(_) => Self::connect_split_mem_port_arrays(
output_stmts,
&input_array_types,
0,
@ -540,25 +563,19 @@ impl ModuleState {
wire_rdata,
wire_wdata,
wire_wmask,
port_rdata.map(|e: Expr<DynCanonicalValue>| {
e.with_type::<Array<[DynCanonicalValue]>>()
}),
port_wdata.map(|e: Expr<DynCanonicalValue>| {
e.with_type::<Array<[DynCanonicalValue]>>()
}),
port_wmask.map(|e: Expr<DynCanonicalValue>| {
e.with_type::<Array<[DynCanonicalValue]>>()
}),
port_rdata.map(Expr::from_canonical),
port_wdata.map(Expr::from_canonical),
port_wmask.map(Expr::from_canonical),
connect_read_enum,
connect_write_enum,
connect_write_enum,
connect_write,
),
TypeEnum::ArrayType(array_type) => {
CanonicalType::Array(array_type) => {
input_array_types.push(array_type);
input_element_type = array_type.element().type_enum();
input_element_type = array_type.element();
continue;
}
TypeEnum::UInt(_) | TypeEnum::SInt(_)
CanonicalType::UInt(_) | CanonicalType::SInt(_) | CanonicalType::Bool(_)
if input_array_types
.first()
.map(|&v| single_type.is_array_type(v))
@ -574,7 +591,8 @@ impl ModuleState {
connect_write(output_stmts, wire_wmask, port_wmask);
}
}
TypeEnum::UInt(_) | TypeEnum::SInt(_) => Self::connect_split_mem_port_arrays(
CanonicalType::UInt(_) | CanonicalType::SInt(_) | CanonicalType::Bool(_) => {
Self::connect_split_mem_port_arrays(
output_stmts,
&input_array_types,
0,
@ -582,51 +600,47 @@ impl ModuleState {
wire_rdata,
wire_wdata,
wire_wmask,
port_rdata.map(|e: Expr<DynCanonicalValue>| {
e.with_type::<Array<[DynCanonicalValue]>>()
}),
port_wdata.map(|e: Expr<DynCanonicalValue>| {
e.with_type::<Array<[DynCanonicalValue]>>()
}),
port_wmask.map(|e: Expr<DynCanonicalValue>| {
e.with_type::<Array<[DynCanonicalValue]>>()
}),
port_rdata.map(Expr::from_canonical),
port_wdata.map(Expr::from_canonical),
port_wmask.map(Expr::from_canonical),
connect_read,
connect_write,
connect_write,
),
TypeEnum::Clock(_)
| TypeEnum::AsyncReset(_)
| TypeEnum::SyncReset(_)
| TypeEnum::Reset(_) => unreachable!("memory element type is a storable type"),
)
}
CanonicalType::Clock(_)
| CanonicalType::AsyncReset(_)
| CanonicalType::SyncReset(_)
| CanonicalType::Reset(_) => unreachable!("memory element type is a storable type"),
}
break;
}
}
fn create_split_mem(
&mut self,
input_mem: Mem<[DynCanonicalValue]>,
input_mem: Mem,
output_stmts: &mut Vec<Stmt>,
input_element_type: TypeEnum,
input_element_type: CanonicalType,
single_type: SingleType,
mem_name_path: &str,
split_state: &SplitState<'_>,
) -> Mem<[DynCanonicalValue]> {
let mem_name = self.name_id_gen.gen(Intern::intern_owned(format!(
"{}{mem_name_path}",
input_mem.scoped_name().1 .0
)));
) -> Mem {
let mem_name = NameId(
Intern::intern_owned(format!("{}{mem_name_path}", input_mem.scoped_name().1 .0)),
Id::new(),
);
let mem_name = ScopedNameId(input_mem.scoped_name().0, mem_name);
let output_element_type = match single_type {
SingleType::UInt(ty) => ty.canonical_dyn(),
SingleType::SInt(ty) => ty.canonical_dyn(),
SingleType::UIntArray(ty) => ty.canonical_dyn(),
SingleType::SIntArray(ty) => ty.canonical_dyn(),
SingleType::UInt(ty) => ty.canonical(),
SingleType::SInt(ty) => ty.canonical(),
SingleType::Bool(ty) => ty.canonical(),
SingleType::UIntArray(ty) => ty.canonical(),
SingleType::SIntArray(ty) => ty.canonical(),
SingleType::BoolArray(ty) => ty.canonical(),
};
let output_array_type =
ArrayType::new_slice(output_element_type, input_mem.array_type().len());
let output_array_type = Array::new_dyn(output_element_type, input_mem.array_type().len());
let initial_value = split_state.initial_value.as_ref().map(|initial_value| {
let mut bits = BitVec::with_capacity(output_array_type.bit_width());
let mut bits = BitVec::with_capacity(output_array_type.type_properties().bit_width);
for element in initial_value.iter() {
bits.extend_from_bitslice(element);
}
@ -643,7 +657,6 @@ impl ModuleState {
port.addr_type(),
output_element_type,
)
.intern_sized()
})
.collect();
let output_mem = Mem::new_unchecked(
@ -676,15 +689,15 @@ impl ModuleState {
let port_rdata = port
.port_kind()
.rdata_name()
.map(|name| port_expr.field(name));
.map(|name| Expr::field(port_expr, name));
let port_wdata = port
.port_kind()
.wdata_name()
.map(|name| port_expr.field(name));
.map(|name| Expr::field(port_expr, name));
let port_wmask = port
.port_kind()
.wmask_name()
.map(|name| port_expr.field(name));
.map(|name| Expr::field(port_expr, name));
self.connect_split_mem_port(
output_stmts,
input_element_type,
@ -702,11 +715,11 @@ impl ModuleState {
}
fn process_mem(
&mut self,
input_mem: Mem<[DynCanonicalValue]>,
output_mems: &mut Vec<Mem<[DynCanonicalValue]>>,
input_mem: Mem,
output_mems: &mut Vec<Mem>,
output_stmts: &mut Vec<Stmt>,
) {
let element_type = input_mem.array_type().element().type_enum();
let element_type = input_mem.array_type().element();
let mut split = MemSplit::new(element_type);
let mem_state = match split {
MemSplit::Single {
@ -739,38 +752,39 @@ impl ModuleState {
let port_ty = port.ty();
let NameId(mem_name, _) = input_mem.scoped_name().1;
let port_name = port.port_name();
let wire_name = self
.name_id_gen
.gen(Intern::intern_owned(format!("{mem_name}_{port_name}")));
let wire_name = NameId(
Intern::intern_owned(format!("{mem_name}_{port_name}")),
Id::new(),
);
let wire = Wire::new_unchecked(
ScopedNameId(input_mem.scoped_name().0, wire_name),
port.source_location(),
port_ty,
);
let wire_expr = wire.to_expr();
let canonical_wire = wire.to_dyn_canonical_wire();
let canonical_wire = wire.canonical();
output_stmts.push(
StmtWire {
annotations: Default::default(),
wire: canonical_wire.clone(),
wire: canonical_wire,
}
.into(),
);
replacement_ports.push(ExprEnum::Wire(canonical_wire.intern_sized()));
replacement_ports.push(ExprEnum::Wire(canonical_wire));
wire_port_rdata.push(
port.port_kind()
.rdata_name()
.map(|name| wire_expr.field(name)),
.map(|name| Expr::field(wire_expr, name)),
);
wire_port_wdata.push(
port.port_kind()
.wdata_name()
.map(|name| wire_expr.field(name)),
.map(|name| Expr::field(wire_expr, name)),
);
wire_port_wmask.push(
port.port_kind()
.wmask_name()
.map(|name| wire_expr.field(name)),
.map(|name| Expr::field(wire_expr, name)),
);
}
let mem_state = MemState {
@ -815,8 +829,8 @@ impl ModuleState {
#[derive(Default)]
struct State {
modules: HashMap<Interned<Module<DynBundle>>, ModuleState>,
current_module: Option<Interned<Module<DynBundle>>>,
modules: HashMap<Interned<Module<Bundle>>, ModuleState>,
current_module: Option<Interned<Module<Bundle>>>,
}
impl State {
@ -828,11 +842,11 @@ impl State {
struct PushedState<'a> {
state: &'a mut State,
old_module: Option<Interned<Module<DynBundle>>>,
old_module: Option<Interned<Module<Bundle>>>,
}
impl<'a> PushedState<'a> {
fn push_module(state: &'a mut State, module: Interned<Module<DynBundle>>) -> Self {
fn push_module(state: &'a mut State, module: Interned<Module<Bundle>>) -> Self {
let old_module = state.current_module.replace(module);
Self { state, old_module }
}
@ -860,10 +874,7 @@ impl DerefMut for PushedState<'_> {
impl Folder for State {
type Error = Infallible;
fn fold_module<T: BundleValue>(&mut self, v: Module<T>) -> Result<Module<T>, Self::Error>
where
T::Type: BundleType<Value = T>,
{
fn fold_module<T: BundleType>(&mut self, v: Module<T>) -> Result<Module<T>, Self::Error> {
let module: Interned<_> = v.canonical().intern_sized();
if let Some(module_state) = self.modules.get(&module) {
return Ok(Module::from_canonical(
@ -876,7 +887,6 @@ impl Folder for State {
module,
ModuleState {
output_module: None,
name_id_gen: NameIdGen::for_module(*module),
memories: HashMap::new(),
},
);
@ -886,10 +896,10 @@ impl Folder for State {
Ok(Module::from_canonical(*module))
}
fn fold_mem<VA: ValueArrayOrSlice + ?Sized>(
fn fold_mem<Element: Type, Len: Size>(
&mut self,
_v: Mem<VA>,
) -> Result<Mem<VA>, Self::Error> {
_v: Mem<Element, Len>,
) -> Result<Mem<Element, Len>, Self::Error> {
unreachable!()
}
@ -933,7 +943,7 @@ impl Folder for State {
}
}
pub fn simplify_memories(module: Interned<Module<DynBundle>>) -> Interned<Module<DynBundle>> {
pub fn simplify_memories(module: Interned<Module<Bundle>>) -> Interned<Module<Bundle>> {
module
.fold(&mut State::default())
.unwrap_or_else(|v| match v {})

View file

@ -2,32 +2,36 @@
// See Notices.txt for copyright information
#![allow(clippy::multiple_bound_locations)]
use crate::{
annotations::{Annotation, CustomFirrtlAnnotation, TargetedAnnotation},
array::{Array, ArrayType, ArrayTypeTrait, ValueArrayOrSlice},
bundle::{BundleType, BundleValue, DynBundle, DynBundleType, FieldType},
clock::{Clock, ClockType},
enum_::{DynEnum, DynEnumType, EnumType, EnumValue, VariantType},
annotations::{
Annotation, BlackBoxInlineAnnotation, BlackBoxPathAnnotation, CustomFirrtlAnnotation,
DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation, TargetedAnnotation,
},
array::ArrayType,
bundle::{Bundle, BundleField, BundleType},
clock::Clock,
enum_::{Enum, EnumType, EnumVariant},
expr::{
ops, Expr, ExprEnum, Literal, Target, TargetBase, TargetChild, TargetPathArrayElement,
TargetPathBundleField, TargetPathDynArrayElement, TargetPathElement, ToExpr,
ops,
target::{
Target, TargetBase, TargetChild, TargetPathArrayElement, TargetPathBundleField,
TargetPathDynArrayElement, TargetPathElement,
},
int::{
DynInt, DynIntType, DynSInt, DynSIntType, DynUInt, DynUIntType, StaticOrDynIntType, IntType,
IntTypeTrait,
Expr, ExprEnum,
},
formal::FormalKind,
int::{Bool, SIntType, SIntValue, Size, UIntType, UIntValue},
intern::{Intern, Interned},
memory::{Mem, MemPort, PortKind, PortName, PortType, ReadUnderWrite},
module::{
AnnotatedModuleIO, Block, BlockId, ExternModuleBody, ExternModuleParameter,
ExternModuleParameterValue, Instance, Module, ModuleBody, ModuleIO, NameId,
NormalModuleBody, ScopedNameId, Stmt, StmtConnect, StmtDeclaration, StmtIf, StmtInstance,
StmtMatch, StmtReg, StmtWire,
NormalModuleBody, ScopedNameId, Stmt, StmtConnect, StmtDeclaration, StmtFormal, StmtIf,
StmtInstance, StmtMatch, StmtReg, StmtWire,
},
reg::Reg,
reset::{AsyncReset, AsyncResetType, Reset, ResetType, SyncReset, SyncResetType},
reset::{AsyncReset, Reset, SyncReset},
source_location::SourceLocation,
ty::{DynCanonicalType, DynCanonicalValue, DynType, Type, TypeEnum, Value, ValueEnum},
util::{ConstBool, GenericConstBool},
ty::{CanonicalType, Type},
wire::Wire,
};
use num_bigint::{BigInt, BigUint};
@ -473,7 +477,4 @@ impl<T: ?Sized + Visit<State>, State: ?Sized + Visitor> Visit<State> for &'_ mut
}
}
type InternedDynType = Interned<dyn DynType>;
type InternedDynCanonicalType = Interned<dyn DynCanonicalType>;
include!(concat!(env!("OUT_DIR"), "/visit.rs"));

View file

@ -0,0 +1,36 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
pub use crate::{
annotations::{
BlackBoxInlineAnnotation, BlackBoxPathAnnotation, CustomFirrtlAnnotation,
DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation,
},
array::{Array, ArrayType},
bundle::Bundle,
cli::Cli,
clock::{Clock, ClockDomain, ToClock},
enum_::{Enum, HdlNone, HdlOption, HdlSome},
expr::{
repeat, CastBitsTo, CastTo, CastToBits, Expr, HdlPartialEq, HdlPartialOrd, MakeUninitExpr,
ReduceBits, ToExpr,
},
formal::{
all_const, all_seq, any_const, any_seq, formal_global_clock, formal_reset, hdl_assert,
hdl_assert_with_enable, hdl_assume, hdl_assume_with_enable, hdl_cover,
hdl_cover_with_enable, MakeFormalExpr,
},
hdl, hdl_module,
int::{Bool, DynSize, KnownSize, SInt, SIntType, Size, UInt, UIntType},
memory::{Mem, MemBuilder, ReadUnderWrite},
module::{
annotate, connect, connect_any, incomplete_wire, instance, memory, memory_array,
memory_with_init, reg_builder, wire, Instance, Module, ModuleBuilder,
},
reg::Reg,
reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset},
source_location::SourceLocation,
ty::{AsMask, CanonicalType, Type},
util::{ConstUsize, GenericConstUsize},
wire::Wire,
__,
};

View file

@ -2,21 +2,21 @@
// See Notices.txt for copyright information
use crate::{
clock::ClockDomain,
expr::{Expr, ExprTrait, Flow, ToExpr},
expr::{Expr, Flow},
intern::Interned,
module::{NameId, ScopedNameId},
source_location::SourceLocation,
ty::{DynCanonicalType, DynType, Type},
ty::{CanonicalType, Type},
};
use std::fmt;
#[derive(Clone, Eq, PartialEq, Hash)]
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct Reg<T: Type> {
name: ScopedNameId,
source_location: SourceLocation,
ty: T,
clock_domain: Expr<ClockDomain>,
init: Option<Expr<T::Value>>,
init: Option<Expr<T>>,
}
impl<T: Type + fmt::Debug> fmt::Debug for Reg<T> {
@ -37,20 +37,8 @@ impl<T: Type + fmt::Debug> fmt::Debug for Reg<T> {
}
}
impl<T: Type> ToExpr for Reg<T> {
type Type = T;
fn ty(&self) -> Self::Type {
self.ty.clone()
}
fn to_expr(&self) -> Expr<<Self::Type as Type>::Value> {
Expr::new_unchecked(self.expr_enum())
}
}
impl<T: Type> Reg<T> {
pub fn canonical(&self) -> Reg<T::CanonicalType> {
pub fn canonical(&self) -> Reg<CanonicalType> {
let Self {
name,
source_location,
@ -66,49 +54,20 @@ impl<T: Type> Reg<T> {
init: init.map(Expr::canonical),
}
}
pub fn to_dyn_reg(&self) -> Reg<Interned<dyn DynType>> {
let Self {
name,
source_location,
ref ty,
clock_domain,
init,
} = *self;
Reg {
name,
source_location,
ty: ty.to_dyn(),
clock_domain,
init: init.map(Expr::to_dyn),
}
}
pub fn to_dyn_canonical_reg(&self) -> Reg<Interned<dyn DynCanonicalType>> {
let Self {
name,
source_location,
ref ty,
clock_domain,
init,
} = *self;
Reg {
name,
source_location,
ty: ty.canonical_dyn(),
clock_domain,
init: init.map(Expr::to_canonical_dyn),
}
}
#[track_caller]
pub fn new_unchecked(
scoped_name: ScopedNameId,
source_location: SourceLocation,
ty: T,
clock_domain: Expr<ClockDomain>,
init: Option<Expr<T::Value>>,
init: Option<Expr<T>>,
) -> Self {
assert!(ty.is_storable(), "register type must be a storable type");
assert!(
ty.canonical().is_storable(),
"register type must be a storable type"
);
if let Some(init) = init {
assert_eq!(ty, init.ty(), "register's type must match init type");
assert_eq!(ty, Expr::ty(init), "register's type must match init type");
}
Self {
name: scoped_name,
@ -118,6 +77,9 @@ impl<T: Type> Reg<T> {
init,
}
}
pub fn ty(&self) -> T {
self.ty
}
pub fn source_location(&self) -> SourceLocation {
self.source_location
}
@ -139,7 +101,7 @@ impl<T: Type> Reg<T> {
pub fn clock_domain(&self) -> Expr<ClockDomain> {
self.clock_domain
}
pub fn init(&self) -> Option<Expr<T::Value>> {
pub fn init(&self) -> Option<Expr<T>> {
self.init
}
pub fn flow(&self) -> Flow {

View file

@ -2,358 +2,119 @@
// See Notices.txt for copyright information
use crate::{
expr::{Expr, ToExpr},
int::{UInt, UIntType},
intern::Interned,
int::Bool,
source_location::SourceLocation,
ty::{
impl_match_values_as_self, CanonicalType, CanonicalTypeKind, CanonicalValue, Connect,
DynCanonicalType, StaticType, Type, TypeEnum, Value, ValueEnum,
},
util::interned_bit,
ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties},
};
use bitvec::slice::BitSlice;
pub trait ResetTypeTrait: CanonicalType + StaticType<MaskType = UIntType<1>> {}
mod sealed {
pub trait ResetTypeSealed {}
}
pub trait ResetType: StaticType<MaskType = Bool> + sealed::ResetTypeSealed {}
macro_rules! reset_type {
($name:ident, $Trait:ident::$trait_fn:ident, $is_castable_from_bits:literal) => {
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
pub struct AsyncResetType;
pub struct $name;
impl AsyncResetType {
pub const fn new() -> Self {
Self
}
}
impl Type for $name {
type BaseType = $name;
type MaskType = Bool;
impl Type for AsyncResetType {
type Value = AsyncReset;
type CanonicalType = AsyncResetType;
type CanonicalValue = AsyncReset;
type MaskType = UIntType<1>;
type MaskValue = UInt<1>;
impl_match_values_as_self!();
impl_match_variant_as_self!();
fn mask_type(&self) -> Self::MaskType {
UIntType::new()
Bool
}
fn canonical(&self) -> Self::CanonicalType {
*self
fn canonical(&self) -> CanonicalType {
CanonicalType::$name(*self)
}
fn source_location(&self) -> SourceLocation {
fn source_location() -> SourceLocation {
SourceLocation::builtin()
}
fn type_enum(&self) -> TypeEnum {
TypeEnum::AsyncReset(*self)
}
fn from_canonical_type(t: Self::CanonicalType) -> Self {
t
}
fn as_dyn_canonical_type_impl(this: &Self) -> Option<&dyn DynCanonicalType> {
Some(this)
fn from_canonical(canonical_type: CanonicalType) -> Self {
let CanonicalType::$name(retval) = canonical_type else {
panic!("expected {}", stringify!($name));
};
retval
}
}
impl Connect<Self> for AsyncResetType {}
impl CanonicalType for AsyncResetType {
const CANONICAL_TYPE_KIND: CanonicalTypeKind = CanonicalTypeKind::AsyncReset;
impl $name {
pub fn type_properties(self) -> TypeProperties {
Self::TYPE_PROPERTIES
}
impl StaticType for AsyncResetType {
fn static_type() -> Self {
Self
pub fn can_connect(self, _rhs: Self) -> bool {
true
}
}
impl ResetTypeTrait for AsyncResetType {}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct AsyncReset(pub bool);
impl ToExpr for AsyncReset {
type Type = AsyncResetType;
fn ty(&self) -> Self::Type {
AsyncResetType
impl StaticType for $name {
const TYPE: Self = Self;
const MASK_TYPE: Self::MaskType = Bool;
const TYPE_PROPERTIES: TypeProperties = TypeProperties {
is_passive: true,
is_storable: false,
is_castable_from_bits: $is_castable_from_bits,
bit_width: 1,
};
const MASK_TYPE_PROPERTIES: TypeProperties = Bool::TYPE_PROPERTIES;
}
fn to_expr(&self) -> Expr<Self> {
Expr::from_value(self)
}
}
impl sealed::ResetTypeSealed for $name {}
impl Value for AsyncReset {
fn to_canonical(&self) -> <Self::Type as Type>::CanonicalValue {
*self
}
fn to_bits_impl(this: &Self) -> Interned<BitSlice> {
interned_bit(this.0)
}
}
impl ResetType for $name {}
impl CanonicalValue for AsyncReset {
fn value_enum_impl(this: &Self) -> ValueEnum {
ValueEnum::AsyncReset(*this)
}
fn to_bits_impl(this: &Self) -> Interned<BitSlice> {
interned_bit(this.0)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
pub struct SyncResetType;
impl SyncResetType {
pub const fn new() -> Self {
Self
}
}
impl Type for SyncResetType {
type CanonicalType = SyncResetType;
type Value = SyncReset;
type CanonicalValue = SyncReset;
type MaskType = UIntType<1>;
type MaskValue = UInt<1>;
impl_match_values_as_self!();
fn mask_type(&self) -> Self::MaskType {
UIntType::new()
}
fn canonical(&self) -> Self::CanonicalType {
*self
}
fn source_location(&self) -> SourceLocation {
SourceLocation::builtin()
}
fn type_enum(&self) -> TypeEnum {
TypeEnum::SyncReset(*self)
}
fn from_canonical_type(t: Self::CanonicalType) -> Self {
t
}
fn as_dyn_canonical_type_impl(this: &Self) -> Option<&dyn DynCanonicalType> {
Some(this)
}
}
impl Connect<Self> for SyncResetType {}
impl CanonicalType for SyncResetType {
const CANONICAL_TYPE_KIND: CanonicalTypeKind = CanonicalTypeKind::SyncReset;
}
impl StaticType for SyncResetType {
fn static_type() -> Self {
Self
}
}
impl ResetTypeTrait for SyncResetType {}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
pub struct SyncReset(pub bool);
impl ToExpr for SyncReset {
type Type = SyncResetType;
fn ty(&self) -> Self::Type {
SyncResetType
}
fn to_expr(&self) -> Expr<Self> {
Expr::from_value(self)
}
}
impl Value for SyncReset {
fn to_canonical(&self) -> <Self::Type as Type>::CanonicalValue {
*self
}
fn to_bits_impl(this: &Self) -> Interned<BitSlice> {
interned_bit(this.0)
}
}
impl CanonicalValue for SyncReset {
fn value_enum_impl(this: &Self) -> ValueEnum {
ValueEnum::SyncReset(*this)
}
fn to_bits_impl(this: &Self) -> Interned<BitSlice> {
interned_bit(this.0)
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
pub struct ResetType;
impl ResetType {
pub const fn new() -> Self {
Self
}
}
impl Type for ResetType {
type Value = Reset;
type CanonicalType = ResetType;
type CanonicalValue = Reset;
type MaskType = UIntType<1>;
type MaskValue = UInt<1>;
impl_match_values_as_self!();
fn mask_type(&self) -> Self::MaskType {
UIntType::new()
}
fn canonical(&self) -> Self::CanonicalType {
*self
}
fn source_location(&self) -> SourceLocation {
SourceLocation::builtin()
}
fn type_enum(&self) -> TypeEnum {
TypeEnum::Reset(*self)
}
fn from_canonical_type(t: Self::CanonicalType) -> Self {
t
}
fn as_dyn_canonical_type_impl(this: &Self) -> Option<&dyn DynCanonicalType> {
Some(this)
}
}
impl Connect<Self> for ResetType {}
impl CanonicalType for ResetType {
const CANONICAL_TYPE_KIND: CanonicalTypeKind = CanonicalTypeKind::Reset;
}
impl StaticType for ResetType {
fn static_type() -> Self {
Self
}
}
impl ResetTypeTrait for ResetType {}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Reset {}
impl ToExpr for Reset {
type Type = ResetType;
fn ty(&self) -> Self::Type {
match *self {}
}
fn to_expr(&self) -> Expr<Self> {
Expr::from_value(self)
}
}
impl Value for Reset {
fn to_canonical(&self) -> <Self::Type as Type>::CanonicalValue {
*self
}
fn to_bits_impl(this: &Self) -> Interned<BitSlice> {
match *this {}
}
}
impl CanonicalValue for Reset {
fn value_enum_impl(this: &Self) -> ValueEnum {
ValueEnum::Reset(*this)
}
fn to_bits_impl(this: &Self) -> Interned<BitSlice> {
match *this {}
}
}
macro_rules! make_to_reset {
(
$(#[from_value($from_value_ty:ty)])*
$vis:vis trait $Trait:ident {
fn $fn:ident(&self) -> Expr<$T:ty>;
}
) => {
$vis trait $Trait {
fn $fn(&self) -> Expr<$T>;
pub trait $Trait {
fn $trait_fn(&self) -> Expr<$name>;
}
impl<T: ?Sized + $Trait> $Trait for &'_ T {
fn $fn(&self) -> Expr<$T> {
(**self).$fn()
fn $trait_fn(&self) -> Expr<$name> {
(**self).$trait_fn()
}
}
impl<T: ?Sized + $Trait> $Trait for &'_ mut T {
fn $fn(&self) -> Expr<$T> {
(**self).$fn()
fn $trait_fn(&self) -> Expr<$name> {
(**self).$trait_fn()
}
}
impl<T: ?Sized + $Trait> $Trait for Box<T> {
fn $fn(&self) -> Expr<$T> {
(**self).$fn()
fn $trait_fn(&self) -> Expr<$name> {
(**self).$trait_fn()
}
}
impl $Trait for Expr<$T> {
fn $fn(&self) -> Expr<$T> {
impl $Trait for Expr<$name> {
fn $trait_fn(&self) -> Expr<$name> {
*self
}
}
impl $Trait for $T {
fn $fn(&self) -> Expr<$T> {
self.to_expr()
}
}
$(impl $Trait for $from_value_ty {
fn $fn(&self) -> Expr<$T> {
self.to_expr().$fn()
}
})*
};
}
make_to_reset! {
#[from_value(SyncReset)]
#[from_value(AsyncReset)]
pub trait ToReset {
fn to_reset(&self) -> Expr<Reset>;
reset_type!(AsyncReset, ToAsyncReset::to_async_reset, true);
reset_type!(SyncReset, ToSyncReset::to_sync_reset, true);
reset_type!(
Reset,
ToReset::to_reset,
false // Reset is not castable from bits because we don't know if it's async or sync
);
impl ToSyncReset for bool {
fn to_sync_reset(&self) -> Expr<SyncReset> {
self.to_expr().to_sync_reset()
}
}
make_to_reset! {
#[from_value(bool)]
#[from_value(UInt<1>)]
pub trait ToAsyncReset {
fn to_async_reset(&self) -> Expr<AsyncReset>;
}
}
make_to_reset! {
#[from_value(bool)]
#[from_value(UInt<1>)]
pub trait ToSyncReset {
fn to_sync_reset(&self) -> Expr<SyncReset>;
impl ToAsyncReset for bool {
fn to_async_reset(&self) -> Expr<AsyncReset> {
self.to_expr().to_async_reset()
}
}

View file

@ -0,0 +1,122 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
cli::{FormalArgs, FormalMode, FormalOutput, RunPhase},
firrtl::ExportOptions,
};
use clap::Parser;
use hashbrown::HashMap;
use serde::Deserialize;
use std::{
fmt::Write,
path::{Path, PathBuf},
process::Command,
sync::{Mutex, OnceLock},
};
fn assert_formal_helper() -> FormalArgs {
static FORMAL_ARGS: OnceLock<FormalArgs> = OnceLock::new();
// ensure we only run parsing once, so errors from env vars don't produce overlapping output if we're called on multiple threads
FORMAL_ARGS
.get_or_init(|| FormalArgs::parse_from(["fayalite::testing::assert_formal"]))
.clone()
}
#[derive(Deserialize)]
struct CargoMetadata {
target_directory: String,
}
fn get_cargo_target_dir() -> &'static Path {
static RETVAL: OnceLock<PathBuf> = OnceLock::new();
RETVAL.get_or_init(|| {
let output = Command::new(
std::env::var_os("CARGO")
.as_deref()
.unwrap_or("cargo".as_ref()),
)
.arg("metadata")
.output()
.expect("can't run `cargo metadata`");
if !output.status.success() {
panic!(
"can't run `cargo metadata`:\n{}\nexited with status: {}",
String::from_utf8_lossy(&output.stderr),
output.status
);
}
let CargoMetadata { target_directory } =
serde_json::from_slice(&output.stdout).expect("can't parse output of `cargo metadata`");
PathBuf::from(target_directory)
})
}
#[track_caller]
fn get_assert_formal_target_path(test_name: &dyn std::fmt::Display) -> PathBuf {
static DIRS: Mutex<Option<HashMap<String, u64>>> = Mutex::new(None);
let test_name = test_name.to_string();
// don't use line/column numbers since that constantly changes as you edit tests
let file = std::panic::Location::caller().file();
// simple reproducible hash
let simple_hash = file.bytes().chain(test_name.bytes()).fold(
((file.len() as u32) << 16).wrapping_add(test_name.len() as u32),
|mut h, b| {
h = h.wrapping_mul(0xaa0d184b);
h ^= h.rotate_right(5);
h ^= h.rotate_right(13);
h.wrapping_add(b as u32)
},
);
let mut dir = String::with_capacity(64);
for ch in Path::new(file)
.file_stem()
.unwrap_or_default()
.to_str()
.unwrap()
.chars()
.chain(['-'])
.chain(test_name.chars())
{
dir.push(match ch {
ch if ch.is_alphanumeric() => ch,
'_' | '-' | '+' | '.' | ',' | ' ' => ch,
_ => '_',
});
}
write!(dir, "-{simple_hash:08x}").unwrap();
let index = *DIRS
.lock()
.unwrap()
.get_or_insert_with(HashMap::new)
.entry_ref(&dir)
.and_modify(|v| *v += 1)
.or_insert(0);
write!(dir, "-{index}").unwrap();
get_cargo_target_dir()
.join("fayalite_assert_formal")
.join(dir)
}
#[track_caller]
pub fn assert_formal<M>(
test_name: impl std::fmt::Display,
module: M,
mode: FormalMode,
depth: u64,
solver: Option<&str>,
export_options: ExportOptions,
) where
FormalArgs: RunPhase<M, Output = FormalOutput>,
{
let mut args = assert_formal_helper();
args.verilog.firrtl.base.redirect_output_for_rust_test = true;
args.verilog.firrtl.base.output = Some(get_assert_formal_target_path(&test_name));
args.verilog.firrtl.export_options = export_options;
args.verilog.debug = true;
args.mode = mode;
args.depth = depth;
if let Some(solver) = solver {
args.solver = solver.into();
}
args.run(module).expect("testing::assert_formal() failed");
}

File diff suppressed because it is too large Load diff

View file

@ -3,10 +3,15 @@
mod const_bool;
mod const_cmp;
mod const_usize;
mod misc;
mod scoped_ref;
pub(crate) mod streaming_read_utf8;
#[doc(inline)]
pub use const_bool::{ConstBool, ConstBoolDispatch, ConstBoolDispatchTag, GenericConstBool};
#[doc(inline)]
pub use const_usize::{ConstUsize, GenericConstUsize};
#[doc(inline)]
pub use const_cmp::{
@ -14,7 +19,13 @@ pub use const_cmp::{
const_usize_cmp,
};
#[doc(inline)]
pub use scoped_ref::ScopedRef;
#[doc(inline)]
pub use misc::{
interned_bit, iter_eq_by, BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice,
};
pub mod job_server;
pub mod ready_valid;

View file

@ -0,0 +1,29 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use std::{fmt::Debug, hash::Hash};
mod sealed {
pub trait Sealed {}
}
/// the only implementation is `ConstUsize<Self::VALUE>`
pub trait GenericConstUsize:
sealed::Sealed + Copy + Ord + Hash + Default + Debug + 'static + Send + Sync
{
const VALUE: usize;
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct ConstUsize<const VALUE: usize>;
impl<const VALUE: usize> Debug for ConstUsize<VALUE> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ConstUsize").field(&Self::VALUE).finish()
}
}
impl<const VALUE: usize> sealed::Sealed for ConstUsize<VALUE> {}
impl<const VALUE: usize> GenericConstUsize for ConstUsize<VALUE> {
const VALUE: usize = VALUE;
}

View file

@ -0,0 +1,193 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use ctor::ctor;
use jobslot::{Acquired, Client};
use std::{
ffi::OsString,
mem,
num::NonZeroUsize,
sync::{Condvar, Mutex, Once, OnceLock},
thread::spawn,
};
fn get_or_make_client() -> &'static Client {
#[ctor]
static CLIENT: OnceLock<Client> = unsafe {
match Client::from_env() {
Some(client) => OnceLock::from(client),
None => OnceLock::new(),
}
};
CLIENT.get_or_init(|| {
let mut available_parallelism = None;
let mut args = std::env::args_os().skip(1);
while let Some(arg) = args.next() {
const TEST_THREADS_OPTION: &'static [u8] = b"--test-threads";
if arg.as_encoded_bytes().starts_with(TEST_THREADS_OPTION) {
match arg.as_encoded_bytes().get(TEST_THREADS_OPTION.len()) {
Some(b'=') => {
let mut arg = arg.into_encoded_bytes();
arg.drain(..=TEST_THREADS_OPTION.len());
available_parallelism = Some(arg);
break;
}
None => {
available_parallelism = args.next().map(OsString::into_encoded_bytes);
break;
}
_ => {}
}
}
}
let available_parallelism = if let Some(available_parallelism) = available_parallelism
.as_deref()
.and_then(|v| std::str::from_utf8(v).ok())
.and_then(|v| v.parse().ok())
{
available_parallelism
} else if let Ok(available_parallelism) = std::thread::available_parallelism() {
available_parallelism
} else {
NonZeroUsize::new(1).unwrap()
};
Client::new_with_fifo(available_parallelism.get() - 1).expect("failed to create job server")
})
}
struct State {
waiting_count: usize,
available: Vec<Acquired>,
implicit_available: bool,
}
impl State {
fn total_available(&self) -> usize {
self.available.len() + self.implicit_available as usize
}
fn additional_waiting(&self) -> usize {
self.waiting_count.saturating_sub(self.total_available())
}
}
static STATE: Mutex<State> = Mutex::new(State {
waiting_count: 0,
available: Vec::new(),
implicit_available: true,
});
static COND_VAR: Condvar = Condvar::new();
#[derive(Debug)]
enum AcquiredJobInner {
FromJobServer(Acquired),
ImplicitJob,
}
#[derive(Debug)]
pub struct AcquiredJob {
job: AcquiredJobInner,
}
impl AcquiredJob {
fn start_acquire_thread() {
static STARTED_THREAD: Once = Once::new();
STARTED_THREAD.call_once(|| {
spawn(|| {
let mut acquired = None;
let client = get_or_make_client();
let mut state = STATE.lock().unwrap();
loop {
state = if state.additional_waiting() == 0 {
if acquired.is_some() {
drop(state);
drop(acquired.take()); // drop Acquired outside of lock
STATE.lock().unwrap()
} else {
COND_VAR.wait(state).unwrap()
}
} else if acquired.is_some() {
// allocate space before moving Acquired to ensure we
// drop Acquired outside of the lock on panic
state.available.reserve(1);
state.available.push(acquired.take().unwrap());
COND_VAR.notify_all();
state
} else {
drop(state);
acquired = Some(
client
.acquire()
.expect("can't acquire token from job server"),
);
STATE.lock().unwrap()
};
}
});
});
}
fn acquire_inner(block: bool) -> Option<Self> {
Self::start_acquire_thread();
let mut state = STATE.lock().unwrap();
loop {
if let Some(acquired) = state.available.pop() {
return Some(Self {
job: AcquiredJobInner::FromJobServer(acquired),
});
}
if state.implicit_available {
state.implicit_available = false;
return Some(Self {
job: AcquiredJobInner::ImplicitJob,
});
}
if !block {
return None;
}
state.waiting_count += 1;
state = COND_VAR.wait(state).unwrap();
state.waiting_count -= 1;
}
}
pub fn try_acquire() -> Option<Self> {
Self::acquire_inner(false)
}
pub fn acquire() -> Self {
Self::acquire_inner(true).expect("failed to acquire token")
}
pub fn run_command<R>(
&mut self,
cmd: std::process::Command,
f: impl FnOnce(&mut std::process::Command) -> std::io::Result<R>,
) -> std::io::Result<R> {
get_or_make_client().configure_make_and_run_with_fifo(cmd, f)
}
}
impl Drop for AcquiredJob {
fn drop(&mut self) {
let mut state = STATE.lock().unwrap();
match &self.job {
AcquiredJobInner::FromJobServer(_) => {
if state.waiting_count > state.available.len() + state.implicit_available as usize {
// allocate space before moving Acquired to ensure we
// drop Acquired outside of the lock on panic
state.available.reserve(1);
let AcquiredJobInner::FromJobServer(acquired) =
mem::replace(&mut self.job, AcquiredJobInner::ImplicitJob)
else {
unreachable!()
};
state.available.push(acquired);
COND_VAR.notify_all();
}
}
AcquiredJobInner::ImplicitJob => {
state.implicit_available = true;
if state.waiting_count > state.available.len() {
COND_VAR.notify_all();
}
}
}
}
}

View file

@ -0,0 +1,433 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{memory::splat_mask, prelude::*};
use std::num::NonZeroUsize;
#[hdl]
pub struct ReadyValid<T> {
pub data: HdlOption<T>,
#[hdl(flip)]
pub ready: Bool,
}
impl<T: Type> ReadyValid<T> {
#[hdl]
pub fn firing(expr: Expr<Self>) -> Expr<Bool> {
#[hdl]
let firing: Bool = wire();
#[hdl]
match expr.data {
HdlNone => connect(firing, false),
HdlSome(_) => connect(firing, expr.ready),
}
firing
}
#[hdl]
pub fn firing_data(expr: impl ToExpr<Type = Self>) -> Expr<HdlOption<T>> {
let expr = expr.to_expr();
let option_ty = Expr::ty(expr).data;
#[hdl]
let firing_data = wire(option_ty);
connect(firing_data, option_ty.HdlNone());
#[hdl]
if expr.ready {
connect(firing_data, expr.data);
}
firing_data
}
#[hdl]
pub fn map<R: Type>(
expr: Expr<Self>,
f: impl FnOnce(Expr<T>) -> Expr<R>,
) -> Expr<ReadyValid<R>> {
let data = HdlOption::map(expr.data, f);
#[hdl]
let mapped = wire(ReadyValid[Expr::ty(data).HdlSome]);
connect(mapped.data, data);
connect(expr.ready, mapped.ready);
mapped
}
}
#[hdl_module]
pub fn queue<T: Type>(
ty: T,
capacity: NonZeroUsize,
inp_ready_is_comb: bool,
out_valid_is_comb: bool,
) {
let count_ty = UInt::range_inclusive(0..=capacity.get());
let index_ty = UInt::range(0..capacity.get());
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let inp: ReadyValid<T> = m.input(ReadyValid[ty]);
#[hdl]
let out: ReadyValid<T> = m.output(ReadyValid[ty]);
#[hdl]
let count: UInt = m.output(count_ty);
#[hdl]
let inp_index_reg = reg_builder().clock_domain(cd).reset(0.cast_to(index_ty));
#[hdl]
let out_index_reg = reg_builder().clock_domain(cd).reset(0.cast_to(index_ty));
#[hdl]
let maybe_full_reg = reg_builder().clock_domain(cd).reset(false);
#[hdl]
let mut mem = memory(ty);
mem.depth(capacity.get());
let read_port = mem.new_read_port();
let write_port = mem.new_write_port();
#[hdl]
let inp_firing: Bool = wire();
connect(inp_firing, ReadyValid::firing(inp));
#[hdl]
let out_firing: Bool = wire();
connect(out_firing, ReadyValid::firing(out));
#[hdl]
let indexes_equal: Bool = wire();
connect(indexes_equal, inp_index_reg.cmp_eq(out_index_reg));
#[hdl]
let empty: Bool = wire();
connect(empty, indexes_equal & !maybe_full_reg);
#[hdl]
let full: Bool = wire();
connect(full, indexes_equal & maybe_full_reg);
connect(read_port.addr, out_index_reg);
connect(read_port.en, true);
connect(read_port.clk, cd.clk);
connect(write_port.addr, inp_index_reg);
connect(write_port.en, inp_firing);
connect(write_port.clk, cd.clk);
connect(write_port.data, HdlOption::unwrap_or(inp.data, ty.uninit()));
connect(write_port.mask, splat_mask(ty, true.to_expr()));
connect(inp.ready, !full);
if inp_ready_is_comb {
#[hdl]
if out.ready {
connect(inp.ready, true);
}
}
#[hdl]
if !empty {
connect(out.data, HdlSome(read_port.data));
} else {
if out_valid_is_comb {
connect(out.data, inp.data);
} else {
connect(out.data, HdlOption[ty].HdlNone());
}
}
#[hdl]
if inp_firing.cmp_ne(out_firing) {
connect(maybe_full_reg, inp_firing);
}
#[hdl]
if inp_firing {
#[hdl]
if inp_index_reg.cmp_eq(capacity.get() - 1) {
connect_any(inp_index_reg, 0_hdl_u0);
} else {
connect_any(inp_index_reg, inp_index_reg + 1_hdl_u1);
}
}
#[hdl]
if out_firing {
#[hdl]
if out_index_reg.cmp_eq(capacity.get() - 1) {
connect_any(out_index_reg, 0_hdl_u0);
} else {
connect_any(out_index_reg, out_index_reg + 1_hdl_u1);
}
}
#[hdl]
if indexes_equal {
#[hdl]
if maybe_full_reg {
connect_any(count, capacity);
} else {
connect_any(count, 0_hdl_u0);
}
} else {
if capacity.is_power_of_two() {
debug_assert_eq!(count_ty.width(), index_ty.width() + 1);
#[hdl]
let count_lower = wire(index_ty);
connect(
count_lower,
(inp_index_reg - out_index_reg).cast_to(index_ty),
); // wrap
connect(count, count_lower.cast_to(count_ty));
} else {
debug_assert_eq!(count_ty.width(), index_ty.width());
#[hdl]
if inp_index_reg.cmp_lt(out_index_reg) {
connect(count, inp_index_reg + capacity - out_index_reg);
} else {
connect(count, inp_index_reg - out_index_reg);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cli::FormalMode, firrtl::ExportOptions,
module::transform::simplify_enums::SimplifyEnumsKind, testing::assert_formal,
ty::StaticType,
};
use std::num::NonZero;
#[track_caller]
fn test_queue(capacity: NonZeroUsize, inp_ready_is_comb: bool, out_valid_is_comb: bool) {
assert_formal(
format_args!("test_queue_{capacity}_{inp_ready_is_comb}_{out_valid_is_comb}"),
queue_test(capacity, inp_ready_is_comb, out_valid_is_comb),
FormalMode::Prove,
14,
None,
ExportOptions {
simplify_enums: Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts),
..ExportOptions::default()
},
);
#[hdl_module]
fn queue_test(capacity: NonZeroUsize, inp_ready_is_comb: bool, out_valid_is_comb: bool) {
#[hdl]
let clk: Clock = m.input();
#[hdl]
let cd = wire();
connect(
cd,
#[hdl]
ClockDomain {
clk,
rst: formal_reset().to_reset(),
},
);
#[hdl]
let inp_data: HdlOption<UInt<8>> = wire();
#[hdl]
if any_seq(Bool) {
connect(inp_data, HdlSome(any_seq(UInt::<8>::TYPE)));
} else {
connect(inp_data, HdlNone());
}
#[hdl]
let out_ready: Bool = wire();
connect(out_ready, any_seq(Bool));
let index_ty: UInt<32> = UInt::TYPE;
#[hdl]
let index_to_check = wire();
connect(index_to_check, any_const(index_ty));
let index_max = !index_ty.zero();
// we saturate at index_max, so only check indexes where we properly maintain position
hdl_assume(clk, index_to_check.cmp_ne(index_max), "");
#[hdl]
let dut = instance(queue(
UInt[ConstUsize::<8>],
capacity,
inp_ready_is_comb,
out_valid_is_comb,
));
connect(dut.cd, cd);
connect(dut.inp.data, inp_data);
connect(dut.out.ready, out_ready);
hdl_assume(
clk,
index_to_check.cmp_ne(!Expr::ty(index_to_check).zero()),
"",
);
#[hdl]
let expected_count_reg = reg_builder().clock_domain(cd).reset(0u32);
#[hdl]
let next_expected_count = wire();
connect(next_expected_count, expected_count_reg);
connect(expected_count_reg, next_expected_count);
#[hdl]
if ReadyValid::firing(dut.inp) & !ReadyValid::firing(dut.out) {
connect_any(next_expected_count, expected_count_reg + 1u8);
} else if !ReadyValid::firing(dut.inp) & ReadyValid::firing(dut.out) {
connect_any(next_expected_count, expected_count_reg - 1u8);
}
hdl_assert(cd.clk, expected_count_reg.cmp_eq(dut.count), "");
#[hdl]
let prev_out_ready_reg = reg_builder().clock_domain(cd).reset(!0_hdl_u3);
connect_any(
prev_out_ready_reg,
(prev_out_ready_reg << 1) | out_ready.cast_to(UInt[1]),
);
#[hdl]
let prev_inp_valid_reg = reg_builder().clock_domain(cd).reset(!0_hdl_u3);
connect_any(
prev_inp_valid_reg,
(prev_inp_valid_reg << 1) | HdlOption::is_some(inp_data).cast_to(UInt[1]),
);
hdl_assume(
clk,
(prev_out_ready_reg & prev_inp_valid_reg).cmp_ne(0u8),
"",
);
#[hdl]
let inp_index_reg = reg_builder().clock_domain(cd).reset(index_ty.zero());
#[hdl]
let stored_inp_data_reg = reg_builder().clock_domain(cd).reset(0u8);
#[hdl]
if let HdlSome(data) = ReadyValid::firing_data(dut.inp) {
#[hdl]
if inp_index_reg.cmp_lt(index_max) {
connect_any(inp_index_reg, inp_index_reg + 1u8);
#[hdl]
if inp_index_reg.cmp_eq(index_to_check) {
connect(stored_inp_data_reg, data);
}
}
}
#[hdl]
if inp_index_reg.cmp_lt(index_to_check) {
hdl_assert(clk, stored_inp_data_reg.cmp_eq(0u8), "");
}
#[hdl]
let out_index_reg = reg_builder().clock_domain(cd).reset(index_ty.zero());
#[hdl]
let stored_out_data_reg = reg_builder().clock_domain(cd).reset(0u8);
#[hdl]
if let HdlSome(data) = ReadyValid::firing_data(dut.out) {
#[hdl]
if out_index_reg.cmp_lt(index_max) {
connect_any(out_index_reg, out_index_reg + 1u8);
#[hdl]
if out_index_reg.cmp_eq(index_to_check) {
connect(stored_out_data_reg, data);
}
}
}
#[hdl]
if out_index_reg.cmp_lt(index_to_check) {
hdl_assert(clk, stored_out_data_reg.cmp_eq(0u8), "");
}
hdl_assert(clk, inp_index_reg.cmp_ge(out_index_reg), "");
#[hdl]
if inp_index_reg.cmp_lt(index_max) & out_index_reg.cmp_lt(index_max) {
hdl_assert(
clk,
expected_count_reg.cmp_eq(inp_index_reg - out_index_reg),
"",
);
} else {
hdl_assert(
clk,
expected_count_reg.cmp_ge(inp_index_reg - out_index_reg),
"",
);
}
#[hdl]
if inp_index_reg.cmp_gt(index_to_check) & out_index_reg.cmp_gt(index_to_check) {
hdl_assert(clk, stored_inp_data_reg.cmp_eq(stored_out_data_reg), "");
}
}
}
#[test]
fn test_1_false_false() {
test_queue(NonZero::new(1).unwrap(), false, false);
}
#[test]
fn test_1_false_true() {
test_queue(NonZero::new(1).unwrap(), false, true);
}
#[test]
fn test_1_true_false() {
test_queue(NonZero::new(1).unwrap(), true, false);
}
#[test]
fn test_1_true_true() {
test_queue(NonZero::new(1).unwrap(), true, true);
}
#[test]
fn test_2_false_false() {
test_queue(NonZero::new(2).unwrap(), false, false);
}
#[test]
fn test_2_false_true() {
test_queue(NonZero::new(2).unwrap(), false, true);
}
#[test]
fn test_2_true_false() {
test_queue(NonZero::new(2).unwrap(), true, false);
}
#[test]
fn test_2_true_true() {
test_queue(NonZero::new(2).unwrap(), true, true);
}
#[test]
fn test_3_false_false() {
test_queue(NonZero::new(3).unwrap(), false, false);
}
#[test]
fn test_3_false_true() {
test_queue(NonZero::new(3).unwrap(), false, true);
}
#[test]
fn test_3_true_false() {
test_queue(NonZero::new(3).unwrap(), true, false);
}
#[test]
fn test_3_true_true() {
test_queue(NonZero::new(3).unwrap(), true, true);
}
#[test]
fn test_4_false_false() {
test_queue(NonZero::new(4).unwrap(), false, false);
}
#[test]
fn test_4_false_true() {
test_queue(NonZero::new(4).unwrap(), false, true);
}
#[test]
fn test_4_true_false() {
test_queue(NonZero::new(4).unwrap(), true, false);
}
#[test]
fn test_4_true_true() {
test_queue(NonZero::new(4).unwrap(), true, true);
}
}

View file

@ -0,0 +1,114 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
mod safety_boundary {
use std::{cell::Cell, ptr::NonNull};
pub(super) struct Impl<T: ?Sized>(Cell<Option<NonNull<T>>>);
impl<T: ?Sized> Impl<T> {
#[inline]
pub(super) const fn new() -> Self {
Self(Cell::new(None))
}
#[inline]
pub(super) const fn opt_static_ref(v: Option<&'static T>) -> Self {
Self(Cell::new(if let Some(v) = v {
// SAFETY: v is a valid reference for lifetime 'static
unsafe { Some(NonNull::new_unchecked(v as *const T as *mut T)) }
} else {
None
}))
}
/// set `self` to `value` for the duration of the `f()` call.
#[inline]
pub(super) fn set_opt<F: FnOnce() -> R, R>(&self, value: Option<&T>, f: F) -> R {
struct ResetOnDrop<'a, T: ?Sized> {
this: &'a Impl<T>,
old: Option<NonNull<T>>,
}
impl<T: ?Sized> Drop for ResetOnDrop<'_, T> {
fn drop(&mut self) {
self.this.0.set(self.old);
}
}
// reset to old value before exiting this function ensuring `self`
// is not set to `value` when its lifetime is expired
let _reset_on_drop = ResetOnDrop {
this: self,
old: self.0.replace(value.map(NonNull::from)),
};
f()
}
#[inline]
pub(super) fn get_ptr(&self) -> Option<NonNull<T>> {
self.0.get()
}
/// get the reference in `self` for the duration of the `f(...)` call.
#[inline]
pub(super) fn with_opt<F: for<'a> FnOnce(Option<&'a T>) -> R, R>(&self, f: F) -> R {
// SAFETY:
// `self.0` is only `Some` when inside some `set_opt` call or when set
// to some `&'static T`, which ensures that the pointer is live and valid.
//
// the reference we give away has its lifetime scoped to this
// function call which ensures that it won't escape
unsafe { f(self.0.get().map(|v| &*v.as_ptr())) }
}
}
}
/// holds a `Cell<Option<&'scoped T>>` where `'scoped` is erased. This is useful for holding references in TLS.
pub struct ScopedRef<T: ?Sized>(safety_boundary::Impl<T>);
impl<T: ?Sized> ScopedRef<T> {
/// create a new empty [`ScopedRef`]
#[inline]
pub const fn new() -> Self {
Self(safety_boundary::Impl::new())
}
#[inline]
pub const fn opt_static_ref(v: Option<&'static T>) -> Self {
Self(safety_boundary::Impl::opt_static_ref(v))
}
#[inline]
pub const fn static_ref(v: &'static T) -> Self {
Self::opt_static_ref(Some(v))
}
/// set `self` to `value` for the duration of the `f()` call.
#[inline]
pub fn set_opt<F: FnOnce() -> R, R>(&self, value: Option<&T>, f: F) -> R {
self.0.set_opt(value, f)
}
/// set `self` to `value` for the duration of the `f()` call.
#[inline]
pub fn set<F: FnOnce() -> R, R>(&self, value: &T, f: F) -> R {
self.0.set_opt(Some(value), f)
}
#[inline]
pub fn is_some(&self) -> bool {
self.0.get_ptr().is_some()
}
#[inline]
pub fn is_none(&self) -> bool {
self.0.get_ptr().is_none()
}
/// get the reference in `self` for the duration of the `f(...)` call. panics if no reference is set.
#[inline]
pub fn with<F: FnOnce(&T) -> R, R>(&self, f: F) -> R {
self.0.with_opt(
#[inline]
|v| f(v.expect("called ScopedRef::with on an empty ScopedRef")),
)
}
/// get the reference in `self` for the duration of the `f(...)` call.
#[inline]
pub fn with_opt<F: FnOnce(Option<&T>) -> R, R>(&self, f: F) -> R {
self.0.with_opt(f)
}
}
impl<T: ?Sized> Default for ScopedRef<T> {
fn default() -> Self {
Self::new()
}
}

View file

@ -0,0 +1,31 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use std::{
io::{self, BufRead},
str,
};
pub(crate) fn streaming_read_utf8<R: BufRead, E: From<io::Error>>(
reader: R,
mut callback: impl FnMut(&str) -> Result<(), E>,
) -> Result<(), E> {
let mut buf = [0; 4];
let mut buf_len = 0;
for byte in reader.bytes() {
buf[buf_len] = byte?;
buf_len += 1;
match str::from_utf8(&buf[..buf_len]) {
Ok(buf) => {
callback(buf)?;
buf_len = 0;
}
Err(e) => {
if e.error_len().is_some() {
callback("\u{FFFD}")?; // replacement character
buf_len = 0;
}
}
}
}
Ok(())
}

View file

@ -1,88 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
int::{DynIntType, DynSIntType, DynUIntType, IntTypeTrait, SIntType},
ty::{Type, Value},
};
use std::ops::RangeBounds;
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default)]
pub struct Valueless<T> {
pub ty: T,
}
impl<T: Type> Valueless<T> {
pub fn to_canonical(&self) -> Valueless<T::CanonicalType> {
Valueless {
ty: self.ty.canonical(),
}
}
pub fn from_canonical(v: Valueless<T::CanonicalType>) -> Self {
Valueless {
ty: T::from_canonical_type(v.ty),
}
}
}
mod sealed {
pub trait Sealed {}
}
pub trait ValuelessTr: sealed::Sealed {
type Type: Type<Value = Self::Value>;
type Value: Value<Type = Self::Type>;
}
impl<T> sealed::Sealed for Valueless<T> {}
impl<T: Type> ValuelessTr for Valueless<T> {
type Type = T;
type Value = T::Value;
}
impl<T: IntTypeTrait> Valueless<T> {
pub fn signum(&self) -> Valueless<SIntType<2>> {
Valueless::default()
}
pub fn as_same_width_uint(self) -> Valueless<T::SameWidthUInt> {
Valueless {
ty: self.ty.as_same_width_uint(),
}
}
pub fn as_same_width_sint(self) -> Valueless<T::SameWidthSInt> {
Valueless {
ty: self.ty.as_same_width_sint(),
}
}
pub fn as_same_value_uint(self) -> Valueless<DynUIntType> {
Valueless {
ty: self.ty.as_same_value_uint(),
}
}
pub fn as_same_value_sint(self) -> Valueless<DynSIntType> {
Valueless {
ty: self.ty.as_same_value_sint(),
}
}
pub fn concat<HighType: IntTypeTrait>(
&self,
high_part: Valueless<HighType>,
) -> Valueless<DynIntType<HighType::Signed>> {
let ty = DynIntType::new(
self.ty
.width()
.checked_add(high_part.ty.width())
.expect("result has too many bits"),
);
Valueless { ty }
}
pub fn repeat(&self, count: usize) -> Valueless<DynIntType<T::Signed>> {
let width = self.ty.width();
let ty = DynIntType::new(width.checked_mul(count).expect("result has too many bits"));
Valueless { ty }
}
pub fn slice<I: RangeBounds<usize>>(&self, index: I) -> Valueless<DynUIntType> {
let ty = self.ty.slice(index);
Valueless { ty }
}
}

View file

@ -1,15 +1,15 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
expr::{Expr, ExprTrait, Flow, ToExpr},
expr::{Expr, Flow, ToExpr},
intern::Interned,
module::{NameId, ScopedNameId},
module::{IncompleteDeclaration, NameId, ScopedNameId, StmtDeclaration, StmtWire},
source_location::SourceLocation,
ty::{DynCanonicalType, DynType, Type},
ty::{CanonicalType, Type},
};
use std::fmt;
use std::{cell::RefCell, fmt, rc::Rc};
#[derive(Clone, Eq, PartialEq, Hash)]
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct Wire<T: Type> {
name: ScopedNameId,
source_location: SourceLocation,
@ -18,27 +18,14 @@ pub struct Wire<T: Type> {
impl<T: Type + fmt::Debug> fmt::Debug for Wire<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Wire")
.field("name", &self.name)
.field("ty", &self.ty)
.finish_non_exhaustive()
}
}
impl<T: Type> ToExpr for Wire<T> {
type Type = T;
fn ty(&self) -> Self::Type {
self.ty.clone()
}
fn to_expr(&self) -> Expr<<Self::Type as Type>::Value> {
Expr::new_unchecked(self.expr_enum())
write!(f, "Wire({:?}: ", self.name)?;
self.ty.fmt(f)?;
f.write_str(")")
}
}
impl<T: Type> Wire<T> {
pub fn canonical(&self) -> Wire<T::CanonicalType> {
pub fn canonical(&self) -> Wire<CanonicalType> {
let Self {
name,
source_location,
@ -50,29 +37,8 @@ impl<T: Type> Wire<T> {
ty: ty.canonical(),
}
}
pub fn to_dyn_wire(&self) -> Wire<Interned<dyn DynType>> {
let Self {
name,
source_location,
ref ty,
} = *self;
Wire {
name,
source_location,
ty: ty.to_dyn(),
}
}
pub fn to_dyn_canonical_wire(&self) -> Wire<Interned<dyn DynCanonicalType>> {
let Self {
name,
source_location,
ref ty,
} = *self;
Wire {
name,
source_location,
ty: ty.canonical_dyn(),
}
pub fn ty(&self) -> T {
self.ty
}
pub fn new_unchecked(
scoped_name: ScopedNameId,
@ -110,3 +76,57 @@ impl<T: Type> Wire<T> {
true
}
}
#[derive(Clone)]
pub struct IncompleteWire {
pub(crate) declaration: Rc<RefCell<IncompleteDeclaration>>,
}
impl IncompleteWire {
#[track_caller]
pub fn complete<T: Type>(&mut self, ty: T) -> Expr<T> {
let canonical_type = ty.canonical();
let mut declaration = self.declaration.borrow_mut();
if let IncompleteDeclaration::Incomplete {
name,
source_location,
} = *declaration
{
*declaration = IncompleteDeclaration::Complete(
StmtWire {
annotations: (),
wire: Wire {
name,
source_location,
ty: canonical_type,
},
}
.into(),
);
}
match *declaration {
IncompleteDeclaration::Complete(StmtDeclaration::Wire(StmtWire {
wire:
Wire {
name,
source_location,
ty: wire_ty,
},
..
})) => {
drop(declaration);
assert_eq!(wire_ty, canonical_type, "type mismatch");
Wire {
name,
source_location,
ty,
}
.to_expr()
}
IncompleteDeclaration::Taken => panic!("can't use wire outside of containing module"),
IncompleteDeclaration::Complete(_) | IncompleteDeclaration::Incomplete { .. } => {
unreachable!()
}
}
}
}

View file

@ -0,0 +1,193 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use fayalite::{
bundle::BundleType,
enum_::EnumType,
int::{BoolOrIntType, IntType},
prelude::*,
ty::StaticType,
};
use std::marker::PhantomData;
#[hdl(outline_generated)]
pub struct S<T, Len: Size, T2> {
pub a: T,
b: UInt<3>,
pub(crate) c: ArrayType<UInt<1>, Len>,
pub d: T2,
pub _phantom: PhantomData<(T, Len)>,
}
#[hdl(outline_generated)]
pub struct S3<const LEN: usize, T> {
pub a: T,
b: UInt<3>,
pub(crate) c: Array<UInt<1>, LEN>,
pub d: S<T, ConstUsize<LEN>, ()>,
}
#[hdl(outline_generated)]
pub enum E<T> {
A,
B(UInt<3>),
C(T),
D(TyAlias2),
E(TyAlias<Bool, ConstUsize<1>, { 1 + 2 }>),
}
#[hdl(outline_generated)]
pub struct S2<T = ()> {
pub v: E<T>,
}
#[hdl(outline_generated)]
pub type TyAlias<T, Sz: Size, const C: usize, D = ()> = Array<S<T, Sz, D>, C>;
#[hdl(outline_generated)]
pub type TyAlias2 = TyAlias<UInt<8>, ConstUsize<24>, 5>;
// check that #[hdl] properly handles hygiene
macro_rules! types_in_macros {
($a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $f:ident, $A:ident, $B:ident, $C:ident, $D:ident, $E:ident, $F:ident) => {
#[hdl]
struct $F {}
#[hdl]
struct $A<$B, $C: Size, const $D: usize, $E = $F> {
$a: $B,
$b: UIntType<$C>,
$c: SInt<$D>,
$d: HdlOption<$E>,
$e: $E,
$f: $F,
}
#[allow(non_camel_case_types)]
#[hdl]
enum $B<$C: Size, const $D: usize, $E = $F> {
$a($A<(), $C, $D, $E>),
$b(UIntType<$C>),
$c(SInt<$D>),
$d,
$e($E),
$f($F),
}
};
// ensure every identifier has different hygiene
() => {
types_in_macros!(a);
};
($a:ident) => {
types_in_macros!($a, b);
};
($a:ident, $b:ident) => {
types_in_macros!($a, $b, c);
};
($a:ident, $b:ident, $c:ident) => {
types_in_macros!($a, $b, $c, d);
};
($a:ident, $b:ident, $c:ident, $d:ident) => {
types_in_macros!($a, $b, $c, $d, e);
};
($a:ident, $b:ident, $c:ident, $d:ident, $e:ident) => {
types_in_macros!($a, $b, $c, $d, $e, f);
};
($a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $f:ident) => {
types_in_macros!($a, $b, $c, $d, $e, $f, A);
};
($a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $f:ident, $A:ident) => {
types_in_macros!($a, $b, $c, $d, $e, $f, $A, B);
};
($a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $f:ident, $A:ident, $B:ident) => {
types_in_macros!($a, $b, $c, $d, $e, $f, $A, $B, C);
};
($a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $f:ident, $A:ident, $B:ident, $C:ident) => {
types_in_macros!($a, $b, $c, $d, $e, $f, $A, $B, $C, D);
};
($a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $f:ident, $A:ident, $B:ident, $C:ident, $D:ident) => {
types_in_macros!($a, $b, $c, $d, $e, $f, $A, $B, $C, $D, E);
};
($a:ident, $b:ident, $c:ident, $d:ident, $e:ident, $f:ident, $A:ident, $B:ident, $C:ident, $D:ident, $E:ident) => {
types_in_macros!($a, $b, $c, $d, $e, $f, $A, $B, $C, $D, $E, F);
};
}
types_in_macros!();
mod bound_kind {
use fayalite::prelude::*;
#[hdl]
pub struct Type<T> {
v: T,
}
#[hdl]
pub struct Size<T: ::fayalite::int::Size> {
v: UIntType<T>,
}
}
macro_rules! check_bounds {
($name:ident<$(#[$field:ident, $kind:ident] $var:ident: $($bound:ident +)*),*>) => {
#[hdl(outline_generated)]
struct $name<$($var: $($bound +)*,)*> {
$($field: bound_kind::$kind<$var>,)*
}
};
}
check_bounds!(CheckBoundsS0<#[a, Size] A: Size +>);
check_bounds!(CheckBoundsS1<#[a, Size] A: KnownSize +>);
check_bounds!(CheckBoundsT0<#[a, Type] A: Type +>);
check_bounds!(CheckBoundsT1<#[a, Type] A: BoolOrIntType +>);
check_bounds!(CheckBoundsT2<#[a, Type] A: BundleType +>);
check_bounds!(CheckBoundsT3<#[a, Type] A: EnumType +>);
check_bounds!(CheckBoundsT4<#[a, Type] A: IntType +>);
check_bounds!(CheckBoundsT5<#[a, Type] A: StaticType +>);
check_bounds!(CheckBoundsSS0<#[a, Size] A: Size +, #[b, Size] B: Size +>);
check_bounds!(CheckBoundsSS1<#[a, Size] A: KnownSize +, #[b, Size] B: Size +>);
check_bounds!(CheckBoundsST0<#[a, Size] A: Size +, #[b, Type] B: Type +>);
check_bounds!(CheckBoundsST1<#[a, Size] A: KnownSize +, #[b, Type] B: Type +>);
check_bounds!(CheckBoundsTS0<#[a, Type] A: Type +, #[b, Size] B: Size +>);
check_bounds!(CheckBoundsTS1<#[a, Type] A: BoolOrIntType +, #[b, Size] B: Size +>);
check_bounds!(CheckBoundsTS2<#[a, Type] A: BundleType +, #[b, Size] B: Size +>);
check_bounds!(CheckBoundsTS3<#[a, Type] A: EnumType +, #[b, Size] B: Size +>);
check_bounds!(CheckBoundsTS4<#[a, Type] A: IntType +, #[b, Size] B: Size +>);
check_bounds!(CheckBoundsTS5<#[a, Type] A: StaticType +, #[b, Size] B: Size +>);
check_bounds!(CheckBoundsTT0<#[a, Type] A: Type +, #[b, Type] B: Type +>);
check_bounds!(CheckBoundsTT1<#[a, Type] A: BoolOrIntType +, #[b, Type] B: Type +>);
check_bounds!(CheckBoundsTT2<#[a, Type] A: BundleType +, #[b, Type] B: Type +>);
check_bounds!(CheckBoundsTT3<#[a, Type] A: EnumType +, #[b, Type] B: Type +>);
check_bounds!(CheckBoundsTT4<#[a, Type] A: IntType +, #[b, Type] B: Type +>);
check_bounds!(CheckBoundsTT5<#[a, Type] A: StaticType +, #[b, Type] B: Type +>);
check_bounds!(CheckBoundsSSS0<#[a, Size] A: Size +, #[b, Size] B: Size +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsSSS1<#[a, Size] A: KnownSize +, #[b, Size] B: Size +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsSST0<#[a, Size] A: Size +, #[b, Size] B: Size +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsSST1<#[a, Size] A: KnownSize +, #[b, Size] B: Size +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsSTS0<#[a, Size] A: Size +, #[b, Type] B: Type +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsSTS1<#[a, Size] A: KnownSize +, #[b, Type] B: Type +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsSTT0<#[a, Size] A: Size +, #[b, Type] B: Type +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsSTT1<#[a, Size] A: KnownSize +, #[b, Type] B: Type +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTSS0<#[a, Type] A: Type +, #[b, Size] B: Size +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTSS1<#[a, Type] A: BoolOrIntType +, #[b, Size] B: Size +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTSS2<#[a, Type] A: BundleType +, #[b, Size] B: Size +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTSS3<#[a, Type] A: EnumType +, #[b, Size] B: Size +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTSS4<#[a, Type] A: IntType +, #[b, Size] B: Size +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTSS5<#[a, Type] A: StaticType +, #[b, Size] B: Size +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTST0<#[a, Type] A: Type +, #[b, Size] B: Size +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTST1<#[a, Type] A: BoolOrIntType +, #[b, Size] B: Size +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTST2<#[a, Type] A: BundleType +, #[b, Size] B: Size +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTST3<#[a, Type] A: EnumType +, #[b, Size] B: Size +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTST4<#[a, Type] A: IntType +, #[b, Size] B: Size +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTST5<#[a, Type] A: StaticType +, #[b, Size] B: Size +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTTS0<#[a, Type] A: Type +, #[b, Type] B: Type +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTTS1<#[a, Type] A: BoolOrIntType +, #[b, Type] B: Type +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTTS2<#[a, Type] A: BundleType +, #[b, Type] B: Type +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTTS3<#[a, Type] A: EnumType +, #[b, Type] B: Type +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTTS4<#[a, Type] A: IntType +, #[b, Type] B: Type +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTTS5<#[a, Type] A: StaticType +, #[b, Type] B: Type +, #[c, Size] C: Size +>);
check_bounds!(CheckBoundsTTT0<#[a, Type] A: Type +, #[b, Type] B: Type +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTTT1<#[a, Type] A: BoolOrIntType +, #[b, Type] B: Type +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTTT2<#[a, Type] A: BundleType +, #[b, Type] B: Type +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTTT3<#[a, Type] A: EnumType +, #[b, Type] B: Type +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTTT4<#[a, Type] A: IntType +, #[b, Type] B: Type +, #[c, Type] C: Type +>);
check_bounds!(CheckBoundsTTT5<#[a, Type] A: StaticType +, #[b, Type] B: Type +, #[c, Type] C: Type +>);

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use fayalite::ty::Value;
use fayalite::hdl;
#[derive(Value)]
#[hdl]
union U {
a: (),
}

View file

@ -0,0 +1,7 @@
error: top-level #[hdl] can only be used on structs, enums, type aliases, or functions
--> tests/ui/hdl_types.rs:5:1
|
5 | #[hdl]
| ^^^^^^
|
= note: this error originates in the attribute macro `hdl` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,7 +0,0 @@
error: derive(Value) can only be used on structs or enums
--> tests/ui/value_derive.rs:5:10
|
5 | #[derive(Value)]
| ^^^^^
|
= note: this error originates in the derive macro `Value` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -1,32 +0,0 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use fayalite::{
int::UInt,
ty::{StaticValue, Value},
};
#[derive(Value, Clone, Hash, PartialEq, Eq, Debug)]
#[hdl(outline_generated)]
pub struct S<T> {
pub a: T,
b: UInt<3>,
}
#[derive(Value, Clone, Hash, PartialEq, Eq, Debug)]
#[hdl(outline_generated)]
pub enum E<T> {
A,
B {},
C(),
D(UInt<3>),
E { a: UInt<3> },
F(UInt<3>, UInt<3>),
G(T),
H(T, UInt<1>),
}
#[derive(Value, Clone, Hash, PartialEq, Eq, Debug)]
#[hdl(outline_generated, static, where(T: StaticValue))]
pub struct S2<T> {
pub v: E<T>,
}

File diff suppressed because it is too large Load diff

74
scripts/check-copyright.sh Executable file
View file

@ -0,0 +1,74 @@
#!/bin/bash
# SPDX-License-Identifier: LGPL-3.0-or-later
# See Notices.txt for copyright information
set -e
function fail()
{
local error="$1"
echo "error: $error" >&2
exit 1
}
function fail_file()
{
local file="$1" line="$2" error="$3"
fail "$file:$((line + 1)): $error"
}
function check_file()
{
local file="$1" regexes=("${@:2}")
local lines
mapfile -t lines < "$file"
local line
for line in "${!regexes[@]}"; do
eval '[[ "${lines[i]}" =~ '"${regexes[i]}"' ]]' ||
fail_file "$file" "$line" "doesn't match regex: ${regexes[i]}"
done
}
POUND_HEADER=('^"# SPDX-License-Identifier: LGPL-3.0-or-later"$' '^"# See Notices.txt for copyright information"$')
SLASH_HEADER=('^"// SPDX-License-Identifier: LGPL-3.0-or-later"$' '^"// See Notices.txt for copyright information"$')
MD_HEADER=('^"<!--"$' '^"SPDX-License-Identifier: LGPL-3.0-or-later"$' '^"See Notices.txt for copyright information"$')
JSON_HEADER=('^"{"$' '^" \"license_header\": ["$' '^" \"SPDX-License-Identifier: LGPL-3.0-or-later\","$' '^" \"See Notices.txt for copyright information\""')
function main()
{
local IFS=$'\n'
[[ -z "$(git status --porcelain)" ]] || fail "git repo is dirty"
local file
for file in $(git ls-tree --name-only --full-tree -r HEAD); do
case "/$file" in
/Cargo.lock)
# generated file
;;
*/LICENSE.md|*/Notices.txt)
# copyright file
;;
/crates/fayalite/tests/ui/*.stderr)
# file that can't contain copyright header
;;
/.forgejo/workflows/*.yml|*/.gitignore|*.toml)
check_file "$file" "${POUND_HEADER[@]}"
;;
*.md)
check_file "$file" "${MD_HEADER[@]}"
;;
*.sh)
check_file "$file" '^'\''#!'\' "${POUND_HEADER[@]}"
;;
*.rs)
check_file "$file" "${SLASH_HEADER[@]}"
;;
*.json)
check_file "$file" "${JSON_HEADER[@]}"
;;
*)
fail_file "$file" 0 "unimplemented file kind -- you need to add it to $0"
;;
esac
done
}
main