diff --git a/Cargo.lock b/Cargo.lock index 2e50abc..23cdc34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -543,9 +543,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.83" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -647,9 +647,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.66" +version = "2.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 2cfa586..54de3a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ quote = "1.0.36" serde = { version = "1.0.202", features = ["derive"] } serde_json = { version = "1.0.117", features = ["preserve_order"] } sha2 = "0.10.8" -syn = { version = "2.0.66", features = ["full", "fold", "visit", "extra-traits"] } +syn = { version = "2.0.93", features = ["full", "fold", "visit", "extra-traits"] } tempfile = "3.10.1" thiserror = "1.0.61" trybuild = "1.0" diff --git a/crates/fayalite-proc-macros-impl/src/lib.rs b/crates/fayalite-proc-macros-impl/src/lib.rs index 0ffd4d4..6ba177b 100644 --- a/crates/fayalite-proc-macros-impl/src/lib.rs +++ b/crates/fayalite-proc-macros-impl/src/lib.rs @@ -3,14 +3,20 @@ #![cfg_attr(test, recursion_limit = "512")] use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; -use std::io::{ErrorKind, Write}; +use std::{ + collections::{hash_map::Entry, HashMap}, + io::{ErrorKind, Write}, +}; use syn::{ - bracketed, parenthesized, + bracketed, + ext::IdentExt, + parenthesized, parse::{Parse, ParseStream, Parser}, parse_quote, - punctuated::Pair, + punctuated::{Pair, Punctuated}, spanned::Spanned, - AttrStyle, Attribute, Error, Item, ItemFn, Token, + token::{Bracket, Paren}, + AttrStyle, Attribute, Error, Ident, Item, ItemFn, LitBool, LitStr, Meta, Token, }; mod fold; @@ -19,6 +25,7 @@ mod hdl_enum; mod hdl_type_alias; mod hdl_type_common; mod module; +mod process_cfg; pub(crate) trait CustomToken: Copy @@ -59,6 +66,11 @@ mod kw { }; } + custom_keyword!(__evaluated_cfgs); + custom_keyword!(all); + custom_keyword!(any); + custom_keyword!(cfg); + custom_keyword!(cfg_attr); custom_keyword!(clock_domain); custom_keyword!(connect_inexact); custom_keyword!(custom_bounds); @@ -75,6 +87,7 @@ mod kw { custom_keyword!(no_reset); custom_keyword!(no_runtime_generics); custom_keyword!(no_static); + custom_keyword!(not); custom_keyword!(outline_generated); custom_keyword!(output); custom_keyword!(reg_builder); @@ -901,15 +914,346 @@ fn hdl_module_impl(item: ItemFn) -> syn::Result { Ok(contents) } -pub fn hdl_module(attr: TokenStream, item: TokenStream) -> syn::Result { - let kw = kw::hdl_module::default(); - hdl_module_impl(syn::parse2(quote! { #[#kw(#attr)] #item })?) +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub(crate) enum CfgExpr { + Option { + ident: Ident, + value: Option<(Token![=], LitStr)>, + }, + All { + all: kw::all, + paren: Paren, + exprs: Punctuated, + }, + Any { + any: kw::any, + paren: Paren, + exprs: Punctuated, + }, + Not { + not: kw::not, + paren: Paren, + expr: Box, + trailing_comma: Option, + }, } -pub fn hdl_attr(attr: TokenStream, item: TokenStream) -> syn::Result { - let kw = kw::hdl::default(); - let item = quote! { #[#kw(#attr)] #item }; - let item = syn::parse2::(item)?; +impl Parse for CfgExpr { + fn parse(input: ParseStream) -> syn::Result { + match input.cursor().ident() { + Some((_, cursor)) if cursor.eof() => { + return Ok(CfgExpr::Option { + ident: input.call(Ident::parse_any)?, + value: None, + }); + } + _ => {} + } + if input.peek(Ident::peek_any) && input.peek2(Token![=]) { + return Ok(CfgExpr::Option { + ident: input.call(Ident::parse_any)?, + value: Some((input.parse()?, input.parse()?)), + }); + } + let contents; + if input.peek(kw::all) { + Ok(CfgExpr::All { + all: input.parse()?, + paren: parenthesized!(contents in input), + exprs: contents.call(Punctuated::parse_terminated)?, + }) + } else if input.peek(kw::any) { + Ok(CfgExpr::Any { + any: input.parse()?, + paren: parenthesized!(contents in input), + exprs: contents.call(Punctuated::parse_terminated)?, + }) + } else if input.peek(kw::not) { + Ok(CfgExpr::Not { + not: input.parse()?, + paren: parenthesized!(contents in input), + expr: contents.parse()?, + trailing_comma: contents.parse()?, + }) + } else { + Err(input.error("expected cfg-pattern")) + } + } +} + +impl ToTokens for CfgExpr { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + CfgExpr::Option { ident, value } => { + ident.to_tokens(tokens); + if let Some((eq, value)) = value { + eq.to_tokens(tokens); + value.to_tokens(tokens); + } + } + CfgExpr::All { all, paren, exprs } => { + all.to_tokens(tokens); + paren.surround(tokens, |tokens| exprs.to_tokens(tokens)); + } + CfgExpr::Any { any, paren, exprs } => { + any.to_tokens(tokens); + paren.surround(tokens, |tokens| exprs.to_tokens(tokens)); + } + CfgExpr::Not { + not, + paren, + expr, + trailing_comma, + } => { + not.to_tokens(tokens); + paren.surround(tokens, |tokens| { + expr.to_tokens(tokens); + trailing_comma.to_tokens(tokens); + }); + } + } + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub(crate) struct Cfg { + cfg: kw::cfg, + paren: Paren, + expr: CfgExpr, + trailing_comma: Option, +} + +impl Cfg { + fn parse_meta(meta: &Meta) -> syn::Result { + syn::parse2(meta.to_token_stream()) + } +} + +impl ToTokens for Cfg { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + cfg, + paren, + expr, + trailing_comma, + } = self; + cfg.to_tokens(tokens); + paren.surround(tokens, |tokens| { + expr.to_tokens(tokens); + trailing_comma.to_tokens(tokens); + }); + } +} + +impl Parse for Cfg { + fn parse(input: ParseStream) -> syn::Result { + let contents; + Ok(Self { + cfg: input.parse()?, + paren: parenthesized!(contents in input), + expr: contents.parse()?, + trailing_comma: contents.parse()?, + }) + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub(crate) struct CfgAttr { + cfg_attr: kw::cfg_attr, + paren: Paren, + expr: CfgExpr, + comma: Token![,], + attrs: Punctuated, +} + +impl CfgAttr { + pub(crate) fn to_cfg(&self) -> Cfg { + Cfg { + cfg: kw::cfg(self.cfg_attr.span), + paren: self.paren, + expr: self.expr.clone(), + trailing_comma: None, + } + } + fn parse_meta(meta: &Meta) -> syn::Result { + syn::parse2(meta.to_token_stream()) + } +} + +impl Parse for CfgAttr { + fn parse(input: ParseStream) -> syn::Result { + let contents; + Ok(Self { + cfg_attr: input.parse()?, + paren: parenthesized!(contents in input), + expr: contents.parse()?, + comma: contents.parse()?, + attrs: contents.call(Punctuated::parse_terminated)?, + }) + } +} + +pub(crate) struct CfgAndValue { + cfg: Cfg, + eq_token: Token![=], + value: LitBool, +} + +impl Parse for CfgAndValue { + fn parse(input: ParseStream) -> syn::Result { + Ok(Self { + cfg: input.parse()?, + eq_token: input.parse()?, + value: input.parse()?, + }) + } +} + +pub(crate) struct Cfgs { + pub(crate) bracket: Bracket, + pub(crate) cfgs_map: HashMap, + pub(crate) cfgs_list: Vec, +} + +impl Default for Cfgs { + fn default() -> Self { + Self { + bracket: Default::default(), + cfgs_map: Default::default(), + cfgs_list: Default::default(), + } + } +} + +impl Cfgs { + fn insert_cfg(&mut self, cfg: Cfg, value: T) { + match self.cfgs_map.entry(cfg) { + Entry::Occupied(_) => {} + Entry::Vacant(entry) => { + self.cfgs_list.push(entry.key().clone()); + entry.insert(value); + } + } + } +} + +impl Parse for Cfgs { + fn parse(input: ParseStream) -> syn::Result { + let contents; + let bracket = bracketed!(contents in input); + let mut cfgs_map = HashMap::new(); + let mut cfgs_list = Vec::new(); + for CfgAndValue { + cfg, + eq_token, + value, + } in contents.call(Punctuated::::parse_terminated)? + { + let _ = eq_token; + match cfgs_map.entry(cfg) { + Entry::Occupied(_) => {} + Entry::Vacant(entry) => { + cfgs_list.push(entry.key().clone()); + entry.insert(value.value); + } + } + } + Ok(Self { + bracket, + cfgs_map, + cfgs_list, + }) + } +} + +impl Parse for Cfgs<()> { + fn parse(input: ParseStream) -> syn::Result { + let contents; + let bracket = bracketed!(contents in input); + let mut cfgs_map = HashMap::new(); + let mut cfgs_list = Vec::new(); + for cfg in contents.call(Punctuated::::parse_terminated)? { + match cfgs_map.entry(cfg) { + Entry::Occupied(_) => {} + Entry::Vacant(entry) => { + cfgs_list.push(entry.key().clone()); + entry.insert(()); + } + } + } + Ok(Self { + bracket, + cfgs_map, + cfgs_list, + }) + } +} + +impl ToTokens for Cfgs<()> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + bracket, + cfgs_map: _, + cfgs_list, + } = self; + bracket.surround(tokens, |tokens| { + for cfg in cfgs_list { + cfg.to_tokens(tokens); + ::default().to_tokens(tokens); + } + }); + } +} + +fn hdl_main( + kw: impl CustomToken, + attr: TokenStream, + item: TokenStream, +) -> syn::Result { + fn parse_evaluated_cfgs_attr( + input: ParseStream, + parse_inner: impl FnOnce(ParseStream) -> syn::Result, + ) -> syn::Result { + let _: Token![#] = input.parse()?; + let bracket_content; + bracketed!(bracket_content in input); + let _: kw::__evaluated_cfgs = bracket_content.parse()?; + let paren_content; + parenthesized!(paren_content in bracket_content); + parse_inner(&paren_content) + } + let (evaluated_cfgs, item): (_, TokenStream) = Parser::parse2( + |input: ParseStream| { + let peek = input.fork(); + if parse_evaluated_cfgs_attr(&peek, |_| Ok(())).is_ok() { + let evaluated_cfgs = parse_evaluated_cfgs_attr(input, Cfgs::::parse)?; + Ok((Some(evaluated_cfgs), input.parse()?)) + } else { + Ok((None, input.parse()?)) + } + }, + item, + )?; + let cfgs = if let Some(cfgs) = evaluated_cfgs { + cfgs + } else { + let cfgs = process_cfg::collect_cfgs(syn::parse2(item.clone())?)?; + if cfgs.cfgs_list.is_empty() { + Cfgs::default() + } else { + return Ok(quote! { + ::fayalite::__cfg_expansion_helper! { + [] + #cfgs + {#[::fayalite::#kw(#attr)]} { #item } + } + }); + } + }; + let item = syn::parse2(quote! { #[#kw(#attr)] #item })?; + let Some(item) = process_cfg::process_cfgs(item, cfgs)? else { + return Ok(TokenStream::new()); + }; match item { Item::Enum(item) => hdl_enum::hdl_enum(item), Item::Struct(item) => hdl_bundle::hdl_bundle(item), @@ -921,3 +1265,11 @@ pub fn hdl_attr(attr: TokenStream, item: TokenStream) -> syn::Result syn::Result { + hdl_main(kw::hdl_module::default(), attr, item) +} + +pub fn hdl_attr(attr: TokenStream, item: TokenStream) -> syn::Result { + hdl_main(kw::hdl::default(), attr, item) +} diff --git a/crates/fayalite-proc-macros-impl/src/process_cfg.rs b/crates/fayalite-proc-macros-impl/src/process_cfg.rs new file mode 100644 index 0000000..5cff08f --- /dev/null +++ b/crates/fayalite-proc-macros-impl/src/process_cfg.rs @@ -0,0 +1,2527 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{Cfg, CfgAttr, Cfgs, Errors}; +use proc_macro2::Ident; +use std::{collections::VecDeque, marker::PhantomData}; +use syn::{ + punctuated::{Pair, Punctuated}, + Token, +}; + +struct State { + cfgs: Cfgs, + errors: Errors, + _phantom: PhantomData

, +} + +impl State

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

, P: Phase> Process

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

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

, V: Process

, P: Phase> Process

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

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

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

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

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

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

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

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

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

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

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

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

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

) -> Option> { + match self.0 { + syn::Item::Const(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Enum(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::ExternCrate(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Fn(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::ForeignMod(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Impl(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Macro(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Mod(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Static(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Struct(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Trait(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::TraitAlias(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Type(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Union(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + syn::Item::Use(item) => Some(item.process(state)?.map(Into::into).map(TopItem)), + _ => Some(Output::new(self)), + } + } +} + +pub(crate) fn process_cfgs(item: syn::Item, cfgs: Cfgs) -> syn::Result> { + let mut state = State:: { + cfgs, + errors: Errors::new(), + _phantom: PhantomData, + }; + let retval = TopItem(item).process(&mut state).map(|v| v.0 .0); + state.errors.finish()?; + Ok(retval) +} + +pub(crate) fn collect_cfgs(item: syn::Item) -> syn::Result> { + let mut state = State:: { + cfgs: Cfgs::default(), + errors: Errors::new(), + _phantom: PhantomData, + }; + let (None | Some(Output(()))) = TopItem(item).process(&mut state); + state.errors.finish()?; + Ok(state.cfgs) +} diff --git a/crates/fayalite/build.rs b/crates/fayalite/build.rs index 24d8f31..c6680d5 100644 --- a/crates/fayalite/build.rs +++ b/crates/fayalite/build.rs @@ -5,6 +5,9 @@ use std::{env, fs, path::Path}; fn main() { println!("cargo::rustc-check-cfg=cfg(todo)"); + println!("cargo::rustc-check-cfg=cfg(cfg_false_for_tests)"); + println!("cargo::rustc-check-cfg=cfg(cfg_true_for_tests)"); + println!("cargo::rustc-cfg=cfg_true_for_tests"); let path = "visit_types.json"; println!("cargo::rerun-if-changed={path}"); println!("cargo::rerun-if-changed=build.rs"); diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index fba7ada..88fe169 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -11,6 +11,59 @@ extern crate self as fayalite; #[doc(hidden)] pub use std as __std; +#[doc(hidden)] +#[macro_export] +macro_rules! __cfg_expansion_helper { + ( + [ + $($evaluated_cfgs:ident($($evaluated_exprs:tt)*) = $evaluated_results:ident,)* + ] + [ + $cfg:ident($($expr:tt)*), + $($unevaluated_cfgs:ident($($unevaluated_exprs:tt)*),)* + ] + // pass as tt so we get right span for attribute + $after_evaluation_attr:tt $after_evaluation_body:tt + ) => { + #[$cfg($($expr)*)] + $crate::__cfg_expansion_helper! { + [ + $($evaluated_cfgs($($evaluated_exprs)*) = $evaluated_results,)* + $cfg($($expr)*) = true, + ] + [ + $($unevaluated_cfgs($($unevaluated_exprs)*),)* + ] + $after_evaluation_attr $after_evaluation_body + } + #[$cfg(not($($expr)*))] + $crate::__cfg_expansion_helper! { + [ + $($evaluated_cfgs($($evaluated_exprs)*) = $evaluated_results,)* + $cfg($($expr)*) = false, + ] + [ + $($unevaluated_cfgs($($unevaluated_exprs)*),)* + ] + $after_evaluation_attr $after_evaluation_body + } + }; + ( + [ + $($evaluated_cfgs:ident($($evaluated_exprs:tt)*) = $evaluated_results:ident,)* + ] + [] + // don't use #[...] so we get right span for `#` and `[]` of attribute + {$($after_evaluation_attr:tt)*} {$($after_evaluation_body:tt)*} + ) => { + $($after_evaluation_attr)* + #[__evaluated_cfgs([ + $($evaluated_cfgs($($evaluated_exprs)*) = $evaluated_results,)* + ])] + $($after_evaluation_body)* + }; +} + #[doc(inline)] /// The `#[hdl_module]` attribute is applied to a Rust function so that that function creates /// a [`Module`][`::fayalite::module::Module`] when called. diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index 10fbb92..cb6228d 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -1601,6 +1601,14 @@ impl MakeTraceDeclTarget { } } +struct DebugOpaque(T); + +impl fmt::Debug for DebugOpaque { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("<...>") + } +} + #[derive(Debug)] pub struct Compiler { insns: Insns, @@ -1622,6 +1630,7 @@ pub struct Compiler { registers: Vec, traces: SimTraces>>, memories: Vec, + dump_assignments_dot: Option>>, } impl Compiler { @@ -1647,8 +1656,14 @@ impl Compiler { registers: Vec::new(), traces: SimTraces(Vec::new()), memories: Vec::new(), + dump_assignments_dot: None, } } + #[doc(hidden)] + /// This is explicitly unstable and may be changed/removed at any time + pub fn dump_assignments_dot(&mut self, callback: Box) { + self.dump_assignments_dot = Some(DebugOpaque(callback)); + } fn new_sim_trace(&mut self, kind: SimTraceKind) -> TraceScalarId { let id = TraceScalarId(self.traces.0.len()); self.traces.0.push(SimTrace { @@ -4650,12 +4665,11 @@ impl Compiler { fn process_assignments(&mut self) { self.assignments .finalize(self.insns.state_layout().ty.clone().into()); - println!( - "{:#?}", - petgraph::dot::Dot::new(&petgraph::graph::DiGraph::<_, _, usize>::from_elements( - self.assignments.elements() - )) - ); + if let Some(DebugOpaque(dump_assignments_dot)) = &self.dump_assignments_dot { + let graph = + petgraph::graph::DiGraph::<_, _, usize>::from_elements(self.assignments.elements()); + dump_assignments_dot(&petgraph::dot::Dot::new(&graph)); + } let assignments_order: Vec<_> = match petgraph::algo::toposort(&self.assignments, None) { Ok(nodes) => nodes .into_iter() @@ -6429,7 +6443,7 @@ impl_to_sim_value_for_int_value!(SIntValue, SInt, SIntType); struct SimulationImpl { state: interpreter::State, io: Expr, - uninitialized_inputs: HashSet, + uninitialized_inputs: HashMap>, io_targets: HashMap>, made_initial_step: bool, needs_settle: bool, @@ -6483,37 +6497,52 @@ impl SimulationImpl { .field("clocks_triggered", clocks_triggered) .finish_non_exhaustive() } - fn parse_io(&mut self, target: Target, value: CompiledValue) { + /// returns `true` if `target` or any sub-targets are uninitialized inputs + fn parse_io(&mut self, target: Target, value: CompiledValue) -> bool { self.io_targets.insert(target, value); match value.layout.body { CompiledTypeLayoutBody::Scalar => match target.flow() { - Flow::Source => {} + Flow::Source => false, Flow::Sink => { - self.uninitialized_inputs.insert(target); + self.uninitialized_inputs.insert(target, vec![]); + true } Flow::Duplex => unreachable!(), }, CompiledTypeLayoutBody::Array { .. } => { let value = value.map_ty(Array::from_canonical); + let mut sub_targets = Vec::new(); for index in 0..value.layout.ty.len() { - self.parse_io( - target.join( - TargetPathElement::from(TargetPathArrayElement { index }) - .intern_sized(), - ), - value.element(index), + let sub_target = target.join( + TargetPathElement::from(TargetPathArrayElement { index }).intern_sized(), ); + if self.parse_io(sub_target, value.element(index)) { + sub_targets.push(sub_target); + } + } + if sub_targets.is_empty() { + false + } else { + self.uninitialized_inputs.insert(target, sub_targets); + true } } CompiledTypeLayoutBody::Bundle { .. } => { let value = value.map_ty(Bundle::from_canonical); + let mut sub_targets = Vec::new(); for BundleField { name, .. } in value.layout.ty.fields() { - self.parse_io( - target.join( - TargetPathElement::from(TargetPathBundleField { name }).intern_sized(), - ), - value.field_by_name(name), + let sub_target = target.join( + TargetPathElement::from(TargetPathBundleField { name }).intern_sized(), ); + if self.parse_io(sub_target, value.field_by_name(name)) { + sub_targets.push(sub_target); + } + } + if sub_targets.is_empty() { + false + } else { + self.uninitialized_inputs.insert(target, sub_targets); + true } } } @@ -6522,7 +6551,7 @@ impl SimulationImpl { let mut retval = Self { state: State::new(compiled.insns), io: compiled.io.to_expr(), - uninitialized_inputs: HashSet::new(), + uninitialized_inputs: HashMap::new(), io_targets: HashMap::new(), made_initial_step: false, needs_settle: true, @@ -6791,6 +6820,35 @@ impl SimulationImpl { }; panic!("simulator read/write expression must not have dynamic array indexes"); } + fn mark_target_as_initialized(&mut self, mut target: Target) { + fn remove_target_and_children( + uninitialized_inputs: &mut HashMap>, + target: Target, + ) { + let Some(children) = uninitialized_inputs.remove(&target) else { + return; + }; + for child in children { + remove_target_and_children(uninitialized_inputs, child); + } + } + remove_target_and_children(&mut self.uninitialized_inputs, target); + while let Some(target_child) = target.child() { + let parent = target_child.parent(); + for child in self + .uninitialized_inputs + .get(&*parent) + .map(|v| &**v) + .unwrap_or(&[]) + { + if self.uninitialized_inputs.contains_key(child) { + return; + } + } + target = *parent; + self.uninitialized_inputs.remove(&target); + } + } #[track_caller] fn read_helper(&mut self, io: Expr) -> CompiledValue { let Some(target) = io.target() else { @@ -6810,7 +6868,7 @@ impl SimulationImpl { self.settle(); } Flow::Sink => { - if self.uninitialized_inputs.contains(&*target) { + if self.uninitialized_inputs.contains_key(&*target) { panic!("can't read from an uninitialized input"); } } @@ -6833,7 +6891,7 @@ impl SimulationImpl { Flow::Duplex => unreachable!(), } if !self.made_initial_step { - self.uninitialized_inputs.remove(&*target); + self.mark_target_as_initialized(*target); } self.needs_settle = true; compiled_value @@ -7136,11 +7194,11 @@ pub struct Simulation { io: Expr, } -struct SortedSetDebug<'a, T>(&'a HashSet); +struct SortedSetDebug<'a, T, V>(&'a HashMap); -impl fmt::Debug for SortedSetDebug<'_, T> { +impl fmt::Debug for SortedSetDebug<'_, T, V> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut entries = Vec::from_iter(self.0.iter().map(|v| { + let mut entries = Vec::from_iter(self.0.iter().map(|(v, _)| { if f.alternate() { format!("{v:#?}") } else { diff --git a/crates/fayalite/src/sim/vcd.rs b/crates/fayalite/src/sim/vcd.rs index b8248e3..fde30be 100644 --- a/crates/fayalite/src/sim/vcd.rs +++ b/crates/fayalite/src/sim/vcd.rs @@ -5,6 +5,7 @@ use crate::{ enum_::{Enum, EnumType}, expr::Flow, int::UInt, + intern::{Intern, Interned}, sim::{ time::{SimDuration, SimInstant}, TraceArray, TraceAsyncReset, TraceBool, TraceBundle, TraceClock, TraceDecl, @@ -15,12 +16,73 @@ use crate::{ }, }; use bitvec::{order::Lsb0, slice::BitSlice}; +use hashbrown::{hash_map::Entry, HashMap}; use std::{ - fmt, - io::{self, Write}, - mem, + fmt::{self, Write as _}, + io, mem, }; +#[derive(Default)] +struct Scope { + last_inserted: HashMap, usize>, +} + +#[derive(Copy, Clone)] +struct VerilogIdentifier { + unescaped_name: Interned, +} + +impl VerilogIdentifier { + fn needs_escape(self) -> bool { + // we only allow ascii, so we can just check bytes + let Some((&first, rest)) = self.unescaped_name.as_bytes().split_first() else { + unreachable!("Scope::new_identifier guarantees a non-empty name"); + }; + if !first.is_ascii_alphabetic() && first != b'_' { + true + } else { + rest.iter() + .any(|&ch| !ch.is_ascii_alphanumeric() && ch != b'_' && ch != b'$') + } + } +} + +impl fmt::Display for VerilogIdentifier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.needs_escape() { + f.write_str("\\")?; + } + write!(f, "{}", Escaped(self.unescaped_name)) + } +} + +impl Scope { + fn new_identifier(&mut self, unescaped_name: Interned) -> VerilogIdentifier { + let next_disambiguator = match self.last_inserted.entry(unescaped_name) { + Entry::Vacant(entry) => { + entry.insert(1); + return VerilogIdentifier { unescaped_name }; + } + Entry::Occupied(entry) => entry.get() + 1, + }; + let mut disambiguated_name = String::from(&*unescaped_name); + for disambiguator in next_disambiguator.. { + disambiguated_name.truncate(unescaped_name.len()); + write!(disambiguated_name, "_{disambiguator}").expect("can't fail"); + if let Entry::Vacant(entry) = self.last_inserted.entry((*disambiguated_name).intern()) { + let retval = VerilogIdentifier { + unescaped_name: *entry.key(), + }; + entry.insert(1); + // speed up future searches + self.last_inserted.insert(unescaped_name, disambiguator); + return retval; + } + } + panic!("too many names"); + } +} + pub struct VcdWriterDecls { writer: W, timescale: SimDuration, @@ -97,14 +159,20 @@ impl fmt::Debug for VcdWriterDecls { } } +/// pass in scope to ensure it's not available in child scope fn write_vcd_scope( writer: &mut W, scope_type: &str, - scope_name: &str, - f: impl FnOnce(&mut W) -> io::Result, + scope_name: Interned, + scope: &mut Scope, + f: impl FnOnce(&mut W, &mut Scope) -> io::Result, ) -> io::Result { - writeln!(writer, "$scope {scope_type} {scope_name} $end")?; - let retval = f(writer)?; + writeln!( + writer, + "$scope {scope_type} {} $end", + scope.new_identifier(scope_name), + )?; + let retval = f(writer, &mut Scope::default())?; writeln!(writer, "$upscope $end")?; Ok(retval) } @@ -143,24 +211,28 @@ trait_arg! { struct ArgModule<'a> { properties: &'a mut VcdWriterProperties, + scope: &'a mut Scope, } impl<'a> ArgModule<'a> { fn reborrow(&mut self) -> ArgModule<'_> { ArgModule { properties: self.properties, + scope: self.scope, } } } struct ArgModuleBody<'a> { properties: &'a mut VcdWriterProperties, + scope: &'a mut Scope, } impl<'a> ArgModuleBody<'a> { fn reborrow(&mut self) -> ArgModuleBody<'_> { ArgModuleBody { properties: self.properties, + scope: self.scope, } } } @@ -170,6 +242,7 @@ struct ArgInType<'a> { sink_var_type: &'static str, duplex_var_type: &'static str, properties: &'a mut VcdWriterProperties, + scope: &'a mut Scope, } impl<'a> ArgInType<'a> { @@ -179,6 +252,7 @@ impl<'a> ArgInType<'a> { sink_var_type: self.sink_var_type, duplex_var_type: self.duplex_var_type, properties: self.properties, + scope: self.scope, } } } @@ -226,55 +300,42 @@ fn write_vcd_id(writer: &mut W, mut id: usize) -> io::Result<()> { Ok(()) } -fn write_escaped(writer: &mut W, value: impl fmt::Display) -> io::Result<()> { - // escaping rules from function GTKWave uses to decode VCD strings: - // https://github.com/gtkwave/gtkwave/blob/491f24d7e8619cfc1fcc65704ee5c967d1083c18/lib/libfst/fstapi.c#L7090 - struct Wrapper(W); - impl io::Write for Wrapper { - fn write(&mut self, buf: &[u8]) -> io::Result { - if buf.is_empty() { - return self.0.write(buf); - } - let mut retval = 0; - for &byte in buf { - match byte { - b'\\' | b'\'' | b'"' | b'?' => self.0.write_all(&[b'\\', byte])?, - b'\n' => self.0.write_all(br"\n")?, - b'\r' => self.0.write_all(br"\r")?, - b'\t' => self.0.write_all(br"\t")?, - 0x7 => self.0.write_all(br"\a")?, - 0x8 => self.0.write_all(br"\b")?, - 0xC => self.0.write_all(br"\f")?, - 0xB => self.0.write_all(br"\v")?, - _ => { - if byte.is_ascii_graphic() { - self.0.write_all(&[byte])?; - } else { - write!(self.0, r"\x{byte:02x}")?; +struct Escaped(T); + +impl fmt::Display for Escaped { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // escaping rules from function GTKWave uses to decode VCD strings: + // https://github.com/gtkwave/gtkwave/blob/491f24d7e8619cfc1fcc65704ee5c967d1083c18/lib/libfst/fstapi.c#L7090 + struct Wrapper(W); + impl fmt::Write for Wrapper { + fn write_str(&mut self, s: &str) -> fmt::Result { + for byte in s.bytes() { + match byte { + b'\\' | b'\'' | b'"' | b'?' => { + self.0.write_str("\\")?; + self.0.write_char(byte as char)?; + } + b'\n' => self.0.write_str(r"\n")?, + b'\r' => self.0.write_str(r"\r")?, + b'\t' => self.0.write_str(r"\t")?, + 0x7 => self.0.write_str(r"\a")?, + 0x8 => self.0.write_str(r"\b")?, + 0xC => self.0.write_str(r"\f")?, + 0xB => self.0.write_str(r"\v")?, + _ => { + if byte.is_ascii_graphic() { + self.0.write_char(byte as char)?; + } else { + write!(self.0, r"\x{byte:02x}")?; + } } } } - retval += 1; + Ok(()) } - Ok(retval) - } - - fn flush(&mut self) -> io::Result<()> { - self.0.flush() } + write!(Wrapper(f), "{}", self.0) } - write!(Wrapper(writer), "{value}") -} - -fn is_unescaped_verilog_identifier(ident: &str) -> bool { - // we only allow ascii, so we can just check bytes - let Some((&first, rest)) = ident.as_bytes().split_first() else { - return false; // empty string is not an identifier - }; - (first.is_ascii_alphabetic() || first == b'_') - && rest - .iter() - .all(|&ch| ch.is_ascii_alphanumeric() || ch == b'_' || ch == b'$') } fn write_vcd_var( @@ -284,7 +345,7 @@ fn write_vcd_var( var_type: &str, size: usize, location: TraceLocation, - name: &str, + name: VerilogIdentifier, ) -> io::Result<()> { let id = match location { TraceLocation::Scalar(id) => id.as_usize(), @@ -319,12 +380,7 @@ fn write_vcd_var( }; write!(writer, "$var {var_type} {size} ")?; write_vcd_id(writer, id)?; - writer.write_all(b" ")?; - if !is_unescaped_verilog_identifier(name) { - writer.write_all(b"\\")?; - } - write_escaped(writer, name)?; - writer.write_all(b" $end\n") + writeln!(writer, " {name} $end") } impl WriteTrace for TraceUInt { @@ -334,6 +390,7 @@ impl WriteTrace for TraceUInt { sink_var_type, duplex_var_type, properties, + scope, } = arg.in_type(); let Self { location, @@ -356,7 +413,7 @@ impl WriteTrace for TraceUInt { var_type, ty.width(), location, - &name, + scope.new_identifier(name), ) } } @@ -421,6 +478,7 @@ impl WriteTrace for TraceEnumDiscriminant { sink_var_type: _, duplex_var_type: _, properties, + scope, } = arg.in_type(); let Self { location, @@ -435,7 +493,7 @@ impl WriteTrace for TraceEnumDiscriminant { "string", 1, location, - &name, + scope.new_identifier(name), ) } } @@ -507,11 +565,11 @@ impl WriteTrace for TraceScope { impl WriteTrace for TraceModule { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { - let ArgModule { properties } = arg.module(); + let ArgModule { properties, scope } = arg.module(); let Self { name, children } = self; - write_vcd_scope(writer, "module", &name, |writer| { + write_vcd_scope(writer, "module", name, scope, |writer, scope| { for child in children { - child.write_trace(writer, ArgModuleBody { properties })?; + child.write_trace(writer, ArgModuleBody { properties, scope })?; } Ok(()) }) @@ -520,7 +578,7 @@ impl WriteTrace for TraceModule { impl WriteTrace for TraceInstance { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { - let ArgModuleBody { properties } = arg.module_body(); + let ArgModuleBody { properties, scope } = arg.module_body(); let Self { name: _, instance_io, @@ -534,15 +592,16 @@ impl WriteTrace for TraceInstance { sink_var_type: "wire", duplex_var_type: "wire", properties, + scope, }, )?; - module.write_trace(writer, ArgModule { properties }) + module.write_trace(writer, ArgModule { properties, scope }) } } impl WriteTrace for TraceMem { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { - let ArgModuleBody { properties } = arg.module_body(); + let ArgModuleBody { properties, scope } = arg.module_body(); let Self { id, name, @@ -551,27 +610,41 @@ impl WriteTrace for TraceMem { ports, array_type, } = self; - write_vcd_scope(writer, "struct", &*name, |writer| { - write_vcd_scope(writer, "struct", "contents", |writer| { - for element_index in 0..array_type.len() { - write_vcd_scope(writer, "struct", &format!("[{element_index}]"), |writer| { - properties.memory_properties[id.as_usize()].element_index = element_index; - properties.memory_properties[id.as_usize()].element_part_index = 0; - element_type.write_trace( + write_vcd_scope(writer, "struct", name, scope, |writer, scope| { + write_vcd_scope( + writer, + "struct", + "contents".intern(), + scope, + |writer, scope| { + for element_index in 0..array_type.len() { + write_vcd_scope( writer, - ArgInType { - source_var_type: "reg", - sink_var_type: "reg", - duplex_var_type: "reg", - properties, + "struct", + Intern::intern_owned(format!("[{element_index}]")), + scope, + |writer, scope| { + properties.memory_properties[id.as_usize()].element_index = + element_index; + properties.memory_properties[id.as_usize()].element_part_index = 0; + element_type.write_trace( + writer, + ArgInType { + source_var_type: "reg", + sink_var_type: "reg", + duplex_var_type: "reg", + properties, + scope, + }, + ) }, - ) - })?; - } - Ok(()) - })?; + )?; + } + Ok(()) + }, + )?; for port in ports { - port.write_trace(writer, ArgModuleBody { properties })?; + port.write_trace(writer, ArgModuleBody { properties, scope })?; } Ok(()) }) @@ -580,7 +653,7 @@ impl WriteTrace for TraceMem { impl WriteTrace for TraceMemPort { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { - let ArgModuleBody { properties } = arg.module_body(); + let ArgModuleBody { properties, scope } = arg.module_body(); let Self { name: _, bundle, @@ -593,6 +666,7 @@ impl WriteTrace for TraceMemPort { sink_var_type: "wire", duplex_var_type: "wire", properties, + scope, }, ) } @@ -600,7 +674,7 @@ impl WriteTrace for TraceMemPort { impl WriteTrace for TraceWire { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { - let ArgModuleBody { properties } = arg.module_body(); + let ArgModuleBody { properties, scope } = arg.module_body(); let Self { name: _, child, @@ -613,6 +687,7 @@ impl WriteTrace for TraceWire { sink_var_type: "wire", duplex_var_type: "wire", properties, + scope, }, ) } @@ -620,7 +695,7 @@ impl WriteTrace for TraceWire { impl WriteTrace for TraceReg { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { - let ArgModuleBody { properties } = arg.module_body(); + let ArgModuleBody { properties, scope } = arg.module_body(); let Self { name: _, child, @@ -633,6 +708,7 @@ impl WriteTrace for TraceReg { sink_var_type: "reg", duplex_var_type: "reg", properties, + scope, }, ) } @@ -640,7 +716,7 @@ impl WriteTrace for TraceReg { impl WriteTrace for TraceModuleIO { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { - let ArgModuleBody { properties } = arg.module_body(); + let ArgModuleBody { properties, scope } = arg.module_body(); let Self { name: _, child, @@ -654,6 +730,7 @@ impl WriteTrace for TraceModuleIO { sink_var_type: "wire", duplex_var_type: "wire", properties, + scope, }, ) } @@ -661,16 +738,31 @@ impl WriteTrace for TraceModuleIO { impl WriteTrace for TraceBundle { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { - let mut arg = arg.in_type(); + let ArgInType { + source_var_type, + sink_var_type, + duplex_var_type, + properties, + scope, + } = arg.in_type(); let Self { name, fields, ty: _, flow: _, } = self; - write_vcd_scope(writer, "struct", &name, |writer| { + write_vcd_scope(writer, "struct", name, scope, |writer, scope| { for field in fields { - field.write_trace(writer, arg.reborrow())?; + field.write_trace( + writer, + ArgInType { + source_var_type, + sink_var_type, + duplex_var_type, + properties, + scope, + }, + )?; } Ok(()) }) @@ -679,16 +771,31 @@ impl WriteTrace for TraceBundle { impl WriteTrace for TraceArray { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { - let mut arg = arg.in_type(); + let ArgInType { + source_var_type, + sink_var_type, + duplex_var_type, + properties, + scope, + } = arg.in_type(); let Self { name, elements, ty: _, flow: _, } = self; - write_vcd_scope(writer, "struct", &name, |writer| { + write_vcd_scope(writer, "struct", name, scope, |writer, scope| { for element in elements { - element.write_trace(writer, arg.reborrow())?; + element.write_trace( + writer, + ArgInType { + source_var_type, + sink_var_type, + duplex_var_type, + properties, + scope, + }, + )?; } Ok(()) }) @@ -697,7 +804,13 @@ impl WriteTrace for TraceArray { impl WriteTrace for TraceEnumWithFields { fn write_trace(self, writer: &mut W, mut arg: A) -> io::Result<()> { - let mut arg = arg.in_type(); + let ArgInType { + source_var_type, + sink_var_type, + duplex_var_type, + properties, + scope, + } = arg.in_type(); let Self { name, discriminant, @@ -705,10 +818,28 @@ impl WriteTrace for TraceEnumWithFields { ty: _, flow: _, } = self; - write_vcd_scope(writer, "struct", &name, |writer| { - discriminant.write_trace(writer, arg.reborrow())?; + write_vcd_scope(writer, "struct", name, scope, |writer, scope| { + discriminant.write_trace( + writer, + ArgInType { + source_var_type, + sink_var_type, + duplex_var_type, + properties, + scope, + }, + )?; for field in non_empty_fields { - field.write_trace(writer, arg.reborrow())?; + field.write_trace( + writer, + ArgInType { + source_var_type, + sink_var_type, + duplex_var_type, + properties, + scope, + }, + )?; } Ok(()) }) @@ -744,6 +875,7 @@ impl TraceWriterDecls for VcdWriterDecls { &mut writer, ArgModule { properties: &mut properties, + scope: &mut Scope::default(), }, )?; writeln!(writer, "$enddefinitions $end")?; @@ -798,9 +930,7 @@ fn write_string_value_change( value: impl fmt::Display, id: usize, ) -> io::Result<()> { - writer.write_all(b"s")?; - write_escaped(writer, value)?; - writer.write_all(b" ")?; + write!(writer, "s{} ", Escaped(value))?; write_vcd_id(writer, id)?; writer.write_all(b"\n") } @@ -946,3 +1076,49 @@ impl fmt::Debug for VcdWriter { .finish_non_exhaustive() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_scope() { + let mut scope = Scope::default(); + assert_eq!(&*scope.new_identifier("foo".intern()).unescaped_name, "foo"); + assert_eq!( + &*scope.new_identifier("foo_0".intern()).unescaped_name, + "foo_0" + ); + assert_eq!( + &*scope.new_identifier("foo_1".intern()).unescaped_name, + "foo_1" + ); + assert_eq!( + &*scope.new_identifier("foo_3".intern()).unescaped_name, + "foo_3" + ); + assert_eq!( + &*scope.new_identifier("foo".intern()).unescaped_name, + "foo_2" + ); + assert_eq!( + &*scope.new_identifier("foo".intern()).unescaped_name, + "foo_4" + ); + assert_eq!( + &*scope.new_identifier("foo_0".intern()).unescaped_name, + "foo_0_2" + ); + assert_eq!( + &*scope.new_identifier("foo_1".intern()).unescaped_name, + "foo_1_2" + ); + for i in 5..1000u64 { + // verify it actually picks the next available identifier with no skips or duplicates + assert_eq!( + *scope.new_identifier("foo".intern()).unescaped_name, + format!("foo_{i}"), + ); + } + } +} diff --git a/crates/fayalite/src/util/ready_valid.rs b/crates/fayalite/src/util/ready_valid.rs index 82d3f1e..ac08a64 100644 --- a/crates/fayalite/src/util/ready_valid.rs +++ b/crates/fayalite/src/util/ready_valid.rs @@ -49,6 +49,18 @@ impl ReadyValid { } } +/// This debug port is only meant to assist the formal proof of the queue. +#[cfg(test)] +#[doc(hidden)] +#[hdl] +pub struct QueueDebugPort { + #[hdl(flip)] + index_to_check: Index, + stored: Element, + inp_index: Index, + out_index: Index, +} + #[hdl_module] pub fn queue( ty: T, @@ -178,6 +190,22 @@ pub fn queue( } } } + // These debug ports expose some internal state during the Induction phase + // of Formal Verification. They are not present in normal use. + #[cfg(test)] + { + #[hdl] + let dbg: QueueDebugPort = m.output(QueueDebugPort[ty][index_ty]); + // read the memory word currently stored at some fixed index + let debug_port = mem.new_read_port(); + connect(debug_port.addr, dbg.index_to_check); + connect(debug_port.en, true); + connect(debug_port.clk, cd.clk); + connect(dbg.stored, debug_port.data); + // also expose the current read and write indices + connect(dbg.inp_index, inp_index_reg); + connect(dbg.out_index, out_index_reg); + } } #[cfg(test)] @@ -196,13 +224,23 @@ mod tests { 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, + 2, None, ExportOptions { simplify_enums: Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts), ..ExportOptions::default() }, ); + /// Formal verification of the FIFO queue + /// + /// The strategy derives from the observation that, if we filter its + /// input and output streams to consider just one in every N reads and + /// writes (where N is the FIFO capacity), then the FIFO effectively + /// behaves as a one-entry FIFO. + /// + /// In particular, any counterexample of the full FIFO behaving badly + /// will also be caught by one of the filtered versions (one which + /// happens to be in phase with the offending input or output). #[hdl_module] fn queue_test(capacity: NonZeroUsize, inp_ready_is_comb: bool, out_valid_is_comb: bool) { #[hdl] @@ -217,6 +255,8 @@ mod tests { rst: formal_reset().to_reset(), }, ); + + // random input data #[hdl] let inp_data: HdlOption> = wire(); #[hdl] @@ -225,16 +265,26 @@ mod tests { } else { connect(inp_data, HdlNone()); } + + // assert output ready at random #[hdl] let out_ready: Bool = wire(); connect(out_ready, any_seq(Bool)); - let index_ty: UInt<32> = UInt::TYPE; + + // The current number of elements in the FIFO ranges from zero to + // maximum capacity, inclusive. + let count_ty = UInt::range_inclusive(0..=capacity.get()); + // type for counters that wrap around at the FIFO capacity + let index_ty = UInt::range(0..capacity.get()); + + // among all entries of the FIFO internal circular memory, choose + // one at random to check #[hdl] - let index_to_check = wire(); + let index_to_check = wire(index_ty); 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_assume(clk, index_to_check.cmp_lt(capacity.get()), ""); + + // instantiate and connect the queue #[hdl] let dut = instance(queue( UInt[ConstUsize::<8>], @@ -245,109 +295,172 @@ mod tests { 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()), - "", - ); + // Keep an independent count of words in the FIFO. Ensure that + // it's always correct, and never overflows. #[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); + let expected_count_reg = reg_builder().clock_domain(cd).reset(count_ty.zero()); #[hdl] if ReadyValid::firing(dut.inp) & !ReadyValid::firing(dut.out) { - connect_any(next_expected_count, expected_count_reg + 1u8); + hdl_assert(clk, expected_count_reg.cmp_ne(capacity.get()), ""); + connect_any(expected_count_reg, 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(clk, expected_count_reg.cmp_ne(count_ty.zero()), ""); + connect_any(expected_count_reg, 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_assert(clk, expected_count_reg.cmp_eq(dut.count), ""); + // keep an independent write index into the FIFO's circular buffer #[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) { + if ReadyValid::firing(dut.inp) { #[hdl] - if inp_index_reg.cmp_lt(index_max) { + if inp_index_reg.cmp_ne(capacity.get() - 1) { 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); - } + } else { + connect_any(inp_index_reg, 0_hdl_u0); } } - #[hdl] - if inp_index_reg.cmp_lt(index_to_check) { - hdl_assert(clk, stored_inp_data_reg.cmp_eq(0u8), ""); - } - + // keep an independent read index into the FIFO's circular buffer #[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) { + if ReadyValid::firing(dut.out) { #[hdl] - if out_index_reg.cmp_lt(index_max) { + if out_index_reg.cmp_ne(capacity.get() - 1) { connect_any(out_index_reg, out_index_reg + 1u8); + } else { + connect_any(out_index_reg, 0_hdl_u0); + } + } + + // filter the input data stream, predicated by the read index + // matching the chosen position in the FIFO's circular buffer + #[hdl] + let inp_index_matches = wire(); + connect(inp_index_matches, inp_index_reg.cmp_eq(index_to_check)); + #[hdl] + let inp_firing_data = wire(); + connect(inp_firing_data, HdlNone()); + #[hdl] + if inp_index_matches { + connect(inp_firing_data, ReadyValid::firing_data(dut.inp)); + } + + // filter the output data stream, predicated by the write index + // matching the chosen position in the FIFO's circular buffer + #[hdl] + let out_index_matches = wire(); + connect(out_index_matches, out_index_reg.cmp_eq(index_to_check)); + #[hdl] + let out_firing_data = wire(); + connect(out_firing_data, HdlNone()); + #[hdl] + if out_index_matches { + connect(out_firing_data, ReadyValid::firing_data(dut.out)); + } + + // Implement a one-entry FIFO and ensure its equivalence to the + // filtered FIFO. + // + // the holding register for our one-entry FIFO + #[hdl] + let stored_reg = reg_builder().clock_domain(cd).reset(HdlNone()); + #[hdl] + match stored_reg { + // If the holding register is empty... + HdlNone => { #[hdl] - if out_index_reg.cmp_eq(index_to_check) { - connect(stored_out_data_reg, data); + match inp_firing_data { + // ... and we are not receiving data, then we must not + // transmit any data. + HdlNone => hdl_assert(clk, HdlOption::is_none(out_firing_data), ""), + // If we are indeed receiving some data... + HdlSome(data_in) => { + #[hdl] + match out_firing_data { + // ... and transmitting at the same time, we + // must be transmitting the input data itself, + // since the holding register is empty. + HdlSome(data_out) => hdl_assert(clk, data_out.cmp_eq(data_in), ""), + // If we are receiving, but not transmitting, + // store the received data in the holding + // register. + HdlNone => connect(stored_reg, HdlSome(data_in)), + } + } + } + } + // If there is some value stored in the holding register... + HdlSome(stored) => { + #[hdl] + match out_firing_data { + // ... and we are not transmitting it, we cannot + // receive any more data. + HdlNone => hdl_assert(clk, HdlOption::is_none(inp_firing_data), ""), + // If we are transmitting a previously stored value... + HdlSome(data_out) => { + // ... it must be the same data we stored earlier. + hdl_assert(clk, data_out.cmp_eq(stored), ""); + // Also, accept new data, if any. Otherwise, + // let the holding register become empty. + connect(stored_reg, inp_firing_data); + } } } } + // from now on, some extra assertions in order to pass induction + + // sync the holding register, when it's occupied, to the + // corresponding entry in the FIFO's circular buffer + connect(dut.dbg.index_to_check, index_to_check); #[hdl] - if out_index_reg.cmp_lt(index_to_check) { - hdl_assert(clk, stored_out_data_reg.cmp_eq(0u8), ""); + if let HdlSome(stored) = stored_reg { + hdl_assert(clk, stored.cmp_eq(dut.dbg.stored), ""); } - hdl_assert(clk, inp_index_reg.cmp_ge(out_index_reg), ""); + // sync the read and write indices + hdl_assert(clk, inp_index_reg.cmp_eq(dut.dbg.inp_index), ""); + hdl_assert(clk, out_index_reg.cmp_eq(dut.dbg.out_index), ""); + // the indices should never go past the capacity, but induction + // doesn't know that... + hdl_assert(clk, inp_index_reg.cmp_lt(capacity.get()), ""); + hdl_assert(clk, out_index_reg.cmp_lt(capacity.get()), ""); + + // strongly constrain the state of the holding register + // + // The holding register is full if and only if the corresponding + // FIFO entry was written to and not yet read. In other words, if + // the number of pending reads until the chosen entry is read out + // is greater than the current FIFO count, then the entry couldn't + // be in the FIFO in the first place. #[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), - "", - ); + let pending_reads: UInt = wire(index_ty); + // take care of wrap-around when subtracting indices, add the + // capacity amount to keep the result positive if necessary + #[hdl] + if index_to_check.cmp_ge(out_index_reg) { + connect(pending_reads, index_to_check - out_index_reg); } else { - hdl_assert( - clk, - expected_count_reg.cmp_ge(inp_index_reg - out_index_reg), - "", + connect( + pending_reads, + index_to_check + capacity.get() - out_index_reg, ); } - + // check whether the chosen entry is in the FIFO #[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), ""); - } + let expected_stored: Bool = wire(); + connect(expected_stored, pending_reads.cmp_lt(dut.count)); + // sync with the state of the holding register + hdl_assert( + clk, + expected_stored.cmp_eq(HdlOption::is_some(stored_reg)), + "", + ); } } @@ -430,4 +543,24 @@ mod tests { fn test_4_true_true() { test_queue(NonZero::new(4).unwrap(), true, true); } + + #[test] + fn test_many_false_false() { + test_queue(NonZero::new((2 << 16) - 5).unwrap(), false, false); + } + + #[test] + fn test_many_false_true() { + test_queue(NonZero::new((2 << 16) - 5).unwrap(), false, true); + } + + #[test] + fn test_many_true_false() { + test_queue(NonZero::new((2 << 16) - 5).unwrap(), true, false); + } + + #[test] + fn test_many_true_true() { + test_queue(NonZero::new((2 << 16) - 5).unwrap(), true, true); + } } diff --git a/crates/fayalite/tests/module.rs b/crates/fayalite/tests/module.rs index 4cb3057..4e56df4 100644 --- a/crates/fayalite/tests/module.rs +++ b/crates/fayalite/tests/module.rs @@ -4287,3 +4287,61 @@ circuit check_deduce_resets: ", }; } + +// intentionally not outline_generated to ensure we get correct macro hygiene +#[hdl_module] +pub fn check_cfgs<#[cfg(cfg_false_for_tests)] A: Type, #[cfg(cfg_true_for_tests)] B: Type>( + #[cfg(cfg_false_for_tests)] a: A, + #[cfg(cfg_true_for_tests)] b: B, +) { + #[hdl] + struct S<#[cfg(cfg_false_for_tests)] A, #[cfg(cfg_true_for_tests)] B> { + #[cfg(cfg_false_for_tests)] + a: A, + #[cfg(cfg_true_for_tests)] + b: B, + } + #[hdl] + #[cfg(cfg_false_for_tests)] + let i_a: A = m.input(a); + #[hdl] + #[cfg(cfg_true_for_tests)] + let i_b: B = m.input(b); + #[hdl] + let w: S> = wire(); + #[cfg(cfg_false_for_tests)] + { + #[hdl] + let o_a: A = m.output(a); + connect(o_a, w.a.cast_bits_to(a)); + connect_any(w.a, i_a.cast_to_bits()); + } + #[cfg(cfg_true_for_tests)] + { + #[hdl] + let o_b: B = m.output(b); + connect(o_b, w.b.cast_bits_to(b)); + connect_any(w.b, i_b.cast_to_bits()); + } +} + +#[test] +fn test_cfgs() { + let _n = SourceLocation::normalize_files_for_tests(); + let m = check_cfgs(UInt[8]); + dbg!(m); + #[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/6161 + assert_export_firrtl! { + m => + "/test/check_cfgs.fir": r"FIRRTL version 3.2.0 +circuit check_cfgs: + type Ty0 = {b: UInt<8>} + module check_cfgs: @[the_test_file.rs 9962:1] + input i_b: UInt<8> @[the_test_file.rs 9979:20] + output o_b: UInt<8> @[the_test_file.rs 9992:24] + wire w: Ty0 @[the_test_file.rs 9981:25] + connect o_b, w.b @[the_test_file.rs 9993:9] + connect w.b, i_b @[the_test_file.rs 9994:9] +", + }; +} diff --git a/crates/fayalite/tests/sim.rs b/crates/fayalite/tests/sim.rs index bea7d93..13e84eb 100644 --- a/crates/fayalite/tests/sim.rs +++ b/crates/fayalite/tests/sim.rs @@ -255,8 +255,14 @@ fn test_shift_register() { let mut sim = Simulation::new(shift_register()); let mut writer = RcWriter::default(); sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); - sim.write_clock(sim.io().cd.clk, false); - sim.write_reset(sim.io().cd.rst, true); + sim.write( + sim.io().cd, + #[hdl] + ClockDomain { + clk: false.to_clock(), + rst: true.to_sync_reset(), + }, + ); sim.write_bool(sim.io().d, false); sim.advance_time(SimDuration::from_micros(1)); sim.write_clock(sim.io().cd.clk, true); @@ -1240,3 +1246,33 @@ fn test_memories3() { panic!(); } } + +#[hdl_module(outline_generated)] +pub fn duplicate_names() { + #[hdl] + let w = wire(); + connect(w, 5u8); + #[hdl] + let w = wire(); + connect(w, 6u8); +} + +#[test] +fn test_duplicate_names() { + let _n = SourceLocation::normalize_files_for_tests(); + let mut sim = Simulation::new(duplicate_names()); + let mut writer = RcWriter::default(); + sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); + sim.advance_time(SimDuration::from_micros(1)); + sim.flush_traces().unwrap(); + let vcd = String::from_utf8(writer.take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + if vcd != include_str!("sim/expected/duplicate_names.vcd") { + panic!(); + } + let sim_debug = format!("{sim:#?}"); + println!("#######\n{sim_debug}\n#######"); + if sim_debug != include_str!("sim/expected/duplicate_names.txt") { + panic!(); + } +} diff --git a/crates/fayalite/tests/sim/expected/duplicate_names.txt b/crates/fayalite/tests/sim/expected/duplicate_names.txt new file mode 100644 index 0000000..8a59861 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/duplicate_names.txt @@ -0,0 +1,153 @@ +Simulation { + state: State { + insns: Insns { + state_layout: StateLayout { + ty: TypeLayout { + small_slots: StatePartLayout { + len: 0, + debug_data: [], + .. + }, + big_slots: StatePartLayout { + len: 4, + debug_data: [ + SlotDebugData { + name: "InstantiatedModule(duplicate_names: duplicate_names).duplicate_names::w", + ty: UInt<8>, + }, + SlotDebugData { + name: "", + ty: UInt<8>, + }, + SlotDebugData { + name: "InstantiatedModule(duplicate_names: duplicate_names).duplicate_names::w", + ty: UInt<8>, + }, + SlotDebugData { + name: "", + ty: UInt<8>, + }, + ], + .. + }, + }, + memories: StatePartLayout { + len: 0, + debug_data: [], + layout_data: [], + .. + }, + }, + insns: [ + // at: module-XXXXXXXXXX.rs:1:1 + 0: Const { + dest: StatePartIndex(3), // (0x6) SlotDebugData { name: "", ty: UInt<8> }, + value: 0x6, + }, + // at: module-XXXXXXXXXX.rs:5:1 + 1: Copy { + dest: StatePartIndex(2), // (0x6) SlotDebugData { name: "InstantiatedModule(duplicate_names: duplicate_names).duplicate_names::w", ty: UInt<8> }, + src: StatePartIndex(3), // (0x6) SlotDebugData { name: "", ty: UInt<8> }, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 2: Const { + dest: StatePartIndex(1), // (0x5) SlotDebugData { name: "", ty: UInt<8> }, + value: 0x5, + }, + // at: module-XXXXXXXXXX.rs:3:1 + 3: Copy { + dest: StatePartIndex(0), // (0x5) SlotDebugData { name: "InstantiatedModule(duplicate_names: duplicate_names).duplicate_names::w", ty: UInt<8> }, + src: StatePartIndex(1), // (0x5) SlotDebugData { name: "", ty: UInt<8> }, + }, + // at: module-XXXXXXXXXX.rs:1:1 + 4: Return, + ], + .. + }, + pc: 4, + memory_write_log: [], + memories: StatePart { + value: [], + }, + small_slots: StatePart { + value: [], + }, + big_slots: StatePart { + value: [ + 5, + 5, + 6, + 6, + ], + }, + }, + io: Instance { + name: ::duplicate_names, + instantiated: Module { + name: duplicate_names, + .. + }, + }, + uninitialized_inputs: {}, + io_targets: {}, + made_initial_step: true, + needs_settle: false, + trace_decls: TraceModule { + name: "duplicate_names", + children: [ + TraceWire { + name: "w", + child: TraceUInt { + location: TraceScalarId(0), + name: "w", + ty: UInt<8>, + flow: Duplex, + }, + ty: UInt<8>, + }, + TraceWire { + name: "w", + child: TraceUInt { + location: TraceScalarId(1), + name: "w", + ty: UInt<8>, + flow: Duplex, + }, + ty: UInt<8>, + }, + ], + }, + traces: [ + SimTrace { + id: TraceScalarId(0), + kind: BigUInt { + index: StatePartIndex(0), + ty: UInt<8>, + }, + state: 0x05, + last_state: 0x05, + }, + SimTrace { + id: TraceScalarId(1), + kind: BigUInt { + index: StatePartIndex(2), + ty: UInt<8>, + }, + state: 0x06, + last_state: 0x06, + }, + ], + trace_memories: {}, + trace_writers: [ + Running( + VcdWriter { + finished_init: true, + timescale: 1 ps, + .. + }, + ), + ], + instant: 1 μs, + clocks_triggered: [], + .. +} \ No newline at end of file diff --git a/crates/fayalite/tests/sim/expected/duplicate_names.vcd b/crates/fayalite/tests/sim/expected/duplicate_names.vcd new file mode 100644 index 0000000..1e9f6c6 --- /dev/null +++ b/crates/fayalite/tests/sim/expected/duplicate_names.vcd @@ -0,0 +1,11 @@ +$timescale 1 ps $end +$scope module duplicate_names $end +$var wire 8 ! w $end +$var wire 8 " w_2 $end +$upscope $end +$enddefinitions $end +$dumpvars +b101 ! +b110 " +$end +#1000000 diff --git a/crates/fayalite/tests/sim/expected/memories.vcd b/crates/fayalite/tests/sim/expected/memories.vcd index 72af410..bedc354 100644 --- a/crates/fayalite/tests/sim/expected/memories.vcd +++ b/crates/fayalite/tests/sim/expected/memories.vcd @@ -24,97 +24,97 @@ $upscope $end $upscope $end $scope struct mem $end $scope struct contents $end -$scope struct [0] $end +$scope struct \[0] $end $scope struct mem $end $var reg 8 9 \0 $end $var reg 8 I \1 $end $upscope $end $upscope $end -$scope struct [1] $end +$scope struct \[1] $end $scope struct mem $end $var reg 8 : \0 $end $var reg 8 J \1 $end $upscope $end $upscope $end -$scope struct [2] $end +$scope struct \[2] $end $scope struct mem $end $var reg 8 ; \0 $end $var reg 8 K \1 $end $upscope $end $upscope $end -$scope struct [3] $end +$scope struct \[3] $end $scope struct mem $end $var reg 8 < \0 $end $var reg 8 L \1 $end $upscope $end $upscope $end -$scope struct [4] $end +$scope struct \[4] $end $scope struct mem $end $var reg 8 = \0 $end $var reg 8 M \1 $end $upscope $end $upscope $end -$scope struct [5] $end +$scope struct \[5] $end $scope struct mem $end $var reg 8 > \0 $end $var reg 8 N \1 $end $upscope $end $upscope $end -$scope struct [6] $end +$scope struct \[6] $end $scope struct mem $end $var reg 8 ? \0 $end $var reg 8 O \1 $end $upscope $end $upscope $end -$scope struct [7] $end +$scope struct \[7] $end $scope struct mem $end $var reg 8 @ \0 $end $var reg 8 P \1 $end $upscope $end $upscope $end -$scope struct [8] $end +$scope struct \[8] $end $scope struct mem $end $var reg 8 A \0 $end $var reg 8 Q \1 $end $upscope $end $upscope $end -$scope struct [9] $end +$scope struct \[9] $end $scope struct mem $end $var reg 8 B \0 $end $var reg 8 R \1 $end $upscope $end $upscope $end -$scope struct [10] $end +$scope struct \[10] $end $scope struct mem $end $var reg 8 C \0 $end $var reg 8 S \1 $end $upscope $end $upscope $end -$scope struct [11] $end +$scope struct \[11] $end $scope struct mem $end $var reg 8 D \0 $end $var reg 8 T \1 $end $upscope $end $upscope $end -$scope struct [12] $end +$scope struct \[12] $end $scope struct mem $end $var reg 8 E \0 $end $var reg 8 U \1 $end $upscope $end $upscope $end -$scope struct [13] $end +$scope struct \[13] $end $scope struct mem $end $var reg 8 F \0 $end $var reg 8 V \1 $end $upscope $end $upscope $end -$scope struct [14] $end +$scope struct \[14] $end $scope struct mem $end $var reg 8 G \0 $end $var reg 8 W \1 $end $upscope $end $upscope $end -$scope struct [15] $end +$scope struct \[15] $end $scope struct mem $end $var reg 8 H \0 $end $var reg 8 X \1 $end diff --git a/crates/fayalite/tests/sim/expected/memories2.vcd b/crates/fayalite/tests/sim/expected/memories2.vcd index bd48f24..4039754 100644 --- a/crates/fayalite/tests/sim/expected/memories2.vcd +++ b/crates/fayalite/tests/sim/expected/memories2.vcd @@ -11,31 +11,31 @@ $var wire 1 ' wmask $end $upscope $end $scope struct mem $end $scope struct contents $end -$scope struct [0] $end +$scope struct \[0] $end $scope struct mem $end $var string 1 1 \$tag $end $var reg 1 6 HdlSome $end $upscope $end $upscope $end -$scope struct [1] $end +$scope struct \[1] $end $scope struct mem $end $var string 1 2 \$tag $end $var reg 1 7 HdlSome $end $upscope $end $upscope $end -$scope struct [2] $end +$scope struct \[2] $end $scope struct mem $end $var string 1 3 \$tag $end $var reg 1 8 HdlSome $end $upscope $end $upscope $end -$scope struct [3] $end +$scope struct \[3] $end $scope struct mem $end $var string 1 4 \$tag $end $var reg 1 9 HdlSome $end $upscope $end $upscope $end -$scope struct [4] $end +$scope struct \[4] $end $scope struct mem $end $var string 1 5 \$tag $end $var reg 1 : HdlSome $end diff --git a/crates/fayalite/tests/sim/expected/memories3.vcd b/crates/fayalite/tests/sim/expected/memories3.vcd index 328fcaa..5768560 100644 --- a/crates/fayalite/tests/sim/expected/memories3.vcd +++ b/crates/fayalite/tests/sim/expected/memories3.vcd @@ -42,7 +42,7 @@ $upscope $end $upscope $end $scope struct mem $end $scope struct contents $end -$scope struct [0] $end +$scope struct \[0] $end $scope struct mem $end $var reg 8 ] \[0] $end $var reg 8 e \[1] $end @@ -54,7 +54,7 @@ $var reg 8 /" \[6] $end $var reg 8 7" \[7] $end $upscope $end $upscope $end -$scope struct [1] $end +$scope struct \[1] $end $scope struct mem $end $var reg 8 ^ \[0] $end $var reg 8 f \[1] $end @@ -66,7 +66,7 @@ $var reg 8 0" \[6] $end $var reg 8 8" \[7] $end $upscope $end $upscope $end -$scope struct [2] $end +$scope struct \[2] $end $scope struct mem $end $var reg 8 _ \[0] $end $var reg 8 g \[1] $end @@ -78,7 +78,7 @@ $var reg 8 1" \[6] $end $var reg 8 9" \[7] $end $upscope $end $upscope $end -$scope struct [3] $end +$scope struct \[3] $end $scope struct mem $end $var reg 8 ` \[0] $end $var reg 8 h \[1] $end @@ -90,7 +90,7 @@ $var reg 8 2" \[6] $end $var reg 8 :" \[7] $end $upscope $end $upscope $end -$scope struct [4] $end +$scope struct \[4] $end $scope struct mem $end $var reg 8 a \[0] $end $var reg 8 i \[1] $end @@ -102,7 +102,7 @@ $var reg 8 3" \[6] $end $var reg 8 ;" \[7] $end $upscope $end $upscope $end -$scope struct [5] $end +$scope struct \[5] $end $scope struct mem $end $var reg 8 b \[0] $end $var reg 8 j \[1] $end @@ -114,7 +114,7 @@ $var reg 8 4" \[6] $end $var reg 8 <" \[7] $end $upscope $end $upscope $end -$scope struct [6] $end +$scope struct \[6] $end $scope struct mem $end $var reg 8 c \[0] $end $var reg 8 k \[1] $end @@ -126,7 +126,7 @@ $var reg 8 5" \[6] $end $var reg 8 =" \[7] $end $upscope $end $upscope $end -$scope struct [7] $end +$scope struct \[7] $end $scope struct mem $end $var reg 8 d \[0] $end $var reg 8 l \[1] $end