From ff94dda922ebd2a57b371ce9dd257001103edd9a Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 20 Sep 2024 18:42:24 -0700 Subject: [PATCH 1/4] support #[hdl] on functions -- enables #[hdl] usage in function body --- .../src/hdl_bundle.rs | 10 +- .../fayalite-proc-macros-impl/src/hdl_enum.rs | 12 +- .../src/hdl_type_common.rs | 28 ++- crates/fayalite-proc-macros-impl/src/lib.rs | 137 ++++++---- .../fayalite-proc-macros-impl/src/module.rs | 236 +++++++++++++----- .../src/module/transform_body.rs | 49 ++-- .../expand_aggregate_literals.rs | 18 +- .../src/module/transform_body/expand_match.rs | 5 +- crates/fayalite/tests/ui/hdl_types.stderr | 2 +- 9 files changed, 341 insertions(+), 156 deletions(-) diff --git a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs index f29f43a..7f7d626 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_bundle.rs @@ -19,13 +19,13 @@ use syn::{ #[derive(Clone, Debug)] pub(crate) struct ParsedBundle { pub(crate) attrs: Vec, - pub(crate) options: HdlAttr, + pub(crate) options: HdlAttr, pub(crate) vis: Visibility, pub(crate) struct_token: Token![struct], pub(crate) ident: Ident, pub(crate) generics: MaybeParsed, pub(crate) fields: MaybeParsed, - pub(crate) field_flips: Vec>>, + pub(crate) field_flips: Vec>>, pub(crate) mask_type_ident: Ident, pub(crate) mask_type_match_variant_ident: Ident, pub(crate) match_variant_ident: Ident, @@ -38,7 +38,7 @@ impl ParsedBundle { errors: &mut Errors, field: &mut Field, index: usize, - ) -> Option> { + ) -> Option> { let Field { attrs, vis: _, @@ -71,7 +71,9 @@ impl ParsedBundle { } = item; let mut errors = Errors::new(); let mut options = errors - .unwrap_or_default(HdlAttr::::parse_and_take_attr(&mut attrs)) + .unwrap_or_default(HdlAttr::::parse_and_take_attr( + &mut attrs, + )) .unwrap_or_default(); errors.ok(options.body.validate()); let ItemOptions { diff --git a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs index 1e0b66b..d7e5b61 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_enum.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_enum.rs @@ -3,7 +3,7 @@ use crate::{ common_derives, get_target, ItemOptions, MakeHdlTypeExpr, MaybeParsed, ParsedGenerics, ParsedType, SplitForImpl, TypesParser, WrappedInConst, }, - Errors, HdlAttr, PairsIterExt, + kw, Errors, HdlAttr, PairsIterExt, }; use proc_macro2::TokenStream; use quote::{format_ident, quote_spanned, ToTokens}; @@ -29,7 +29,7 @@ crate::options! { pub(crate) struct ParsedVariantField { pub(crate) paren_token: Paren, pub(crate) attrs: Vec, - pub(crate) options: HdlAttr, + pub(crate) options: HdlAttr, pub(crate) ty: MaybeParsed, pub(crate) comma_token: Option, } @@ -37,7 +37,7 @@ pub(crate) struct ParsedVariantField { #[derive(Clone, Debug)] pub(crate) struct ParsedVariant { pub(crate) attrs: Vec, - pub(crate) options: HdlAttr, + pub(crate) options: HdlAttr, pub(crate) ident: Ident, pub(crate) field: Option, } @@ -119,7 +119,7 @@ impl ParsedVariant { #[derive(Clone, Debug)] pub(crate) struct ParsedEnum { pub(crate) attrs: Vec, - pub(crate) options: HdlAttr, + pub(crate) options: HdlAttr, pub(crate) vis: Visibility, pub(crate) enum_token: Token![enum], pub(crate) ident: Ident, @@ -142,7 +142,9 @@ impl ParsedEnum { } = item; let mut errors = Errors::new(); let mut options = errors - .unwrap_or_default(HdlAttr::::parse_and_take_attr(&mut attrs)) + .unwrap_or_default(HdlAttr::::parse_and_take_attr( + &mut attrs, + )) .unwrap_or_default(); errors.ok(options.body.validate()); let ItemOptions { diff --git a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs index 88da9a7..e33d0e0 100644 --- a/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs +++ b/crates/fayalite-proc-macros-impl/src/hdl_type_common.rs @@ -1745,7 +1745,7 @@ impl, I, P: Clone> ParseTypes> for Punctuated< pub(crate) enum UnparsedGenericParam { Type { attrs: Vec, - options: HdlAttr, + options: HdlAttr, ident: Ident, colon_token: Token![:], bounds: ParsedBounds, @@ -1753,7 +1753,7 @@ pub(crate) enum UnparsedGenericParam { }, Const { attrs: Vec, - options: HdlAttr, + options: HdlAttr, const_token: Token![const], ident: Ident, colon_token: Token![:], @@ -2278,7 +2278,7 @@ impl ParsedBound { #[derive(Debug, Clone)] pub(crate) struct ParsedTypeParam { pub(crate) attrs: Vec, - pub(crate) options: HdlAttr, + pub(crate) options: HdlAttr, pub(crate) ident: Ident, pub(crate) colon_token: Token![:], pub(crate) bounds: ParsedTypeBounds, @@ -2312,7 +2312,7 @@ impl ToTokens for ParsedTypeParam { #[derive(Debug, Clone)] pub(crate) struct ParsedSizeTypeParam { pub(crate) attrs: Vec, - pub(crate) options: HdlAttr, + pub(crate) options: HdlAttr, pub(crate) ident: Ident, pub(crate) colon_token: Token![:], pub(crate) bounds: ParsedSizeTypeBounds, @@ -2356,7 +2356,7 @@ pub(crate) struct ParsedConstParamWhereBounds { #[derive(Debug, Clone)] pub(crate) struct ParsedConstParam { pub(crate) attrs: Vec, - pub(crate) options: HdlAttr, + pub(crate) options: HdlAttr, pub(crate) const_token: Token![const], pub(crate) ident: Ident, pub(crate) colon_token: Token![:], @@ -2413,7 +2413,7 @@ impl ParsedGenericParam { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub(crate) struct ParsedGenerics { pub(crate) lt_token: Option, pub(crate) params: Punctuated, @@ -2863,9 +2863,11 @@ impl ParsedGenerics { let (input_param, punct) = input_param.into_tuple(); let (unparsed_param, late_parsed_param) = match input_param { GenericParam::Lifetime(param) => { - errors.unwrap_or_default(HdlAttr::::parse_and_take_attr( - &mut param.attrs, - )); + errors.unwrap_or_default( + HdlAttr::::parse_and_take_attr( + &mut param.attrs, + ), + ); errors.error(param, "lifetime generics are not supported by #[hdl]"); continue; } @@ -2879,7 +2881,9 @@ impl ParsedGenerics { }) => { let span = ident.span(); let options = errors - .unwrap_or_default(HdlAttr::::parse_and_take_attr(attrs)) + .unwrap_or_default( + HdlAttr::::parse_and_take_attr(attrs), + ) .unwrap_or_default(); let colon_token = colon_token.unwrap_or_else(|| Token![:](span)); if !bounds.is_empty() { @@ -2917,7 +2921,9 @@ impl ParsedGenerics { default, }) => { let options = errors - .unwrap_or_default(HdlAttr::::parse_and_take_attr(attrs)) + .unwrap_or_default( + HdlAttr::::parse_and_take_attr(attrs), + ) .unwrap_or_default(); if let Some(default) = default { let _ = eq_token; diff --git a/crates/fayalite-proc-macros-impl/src/lib.rs b/crates/fayalite-proc-macros-impl/src/lib.rs index 94fb040..3ec00bf 100644 --- a/crates/fayalite-proc-macros-impl/src/lib.rs +++ b/crates/fayalite-proc-macros-impl/src/lib.rs @@ -9,7 +9,8 @@ use syn::{ parse::{Parse, ParseStream, Parser}, parse_quote, punctuated::Pair, - AttrStyle, Attribute, Error, Item, Token, + spanned::Spanned, + AttrStyle, Attribute, Error, Item, ItemFn, Token, }; mod fold; @@ -17,8 +18,20 @@ mod hdl_bundle; mod hdl_enum; mod hdl_type_common; mod module; -//mod value_derive_common; -//mod value_derive_struct; + +pub(crate) trait CustomToken: + Copy + + Spanned + + ToTokens + + std::fmt::Debug + + Eq + + std::hash::Hash + + Default + + quote::IdentFragment + + Parse +{ + const IDENT_STR: &'static str; +} mod kw { pub(crate) use syn::token::Extern as extern_; @@ -38,6 +51,10 @@ mod kw { } crate::fold::no_op_fold!($kw); + + impl crate::CustomToken for $kw { + const IDENT_STR: &'static str = stringify!($kw); + } }; } @@ -46,6 +63,7 @@ mod kw { custom_keyword!(custom_bounds); custom_keyword!(flip); custom_keyword!(hdl); + custom_keyword!(hdl_module); custom_keyword!(input); custom_keyword!(instance); custom_keyword!(m); @@ -68,34 +86,34 @@ mod kw { type Pound = Token![#]; // work around https://github.com/rust-lang/rust/issues/50676 #[derive(Clone, Debug)] -pub(crate) struct HdlAttr { +pub(crate) struct HdlAttr { pub(crate) pound_token: Pound, pub(crate) style: AttrStyle, pub(crate) bracket_token: syn::token::Bracket, - pub(crate) hdl: kw::hdl, + pub(crate) kw: KW, pub(crate) paren_token: Option, pub(crate) body: T, } crate::fold::impl_fold! { - struct HdlAttr { + struct HdlAttr { pound_token: Pound, style: AttrStyle, bracket_token: syn::token::Bracket, - hdl: kw::hdl, + kw: KW, paren_token: Option, body: T, } } #[allow(dead_code)] -impl HdlAttr { - pub(crate) fn split_body(self) -> (HdlAttr<()>, T) { +impl HdlAttr { + pub(crate) fn split_body(self) -> (HdlAttr<(), KW>, T) { let Self { pound_token, style, bracket_token, - hdl, + kw, paren_token, body, } = self; @@ -104,19 +122,19 @@ impl HdlAttr { pound_token, style, bracket_token, - hdl, + kw, paren_token, body: (), }, body, ) } - pub(crate) fn replace_body(self, body: T2) -> HdlAttr { + pub(crate) fn replace_body(self, body: T2) -> HdlAttr { let Self { pound_token, style, bracket_token, - hdl, + kw, paren_token, body: _, } = self; @@ -124,17 +142,20 @@ impl HdlAttr { pound_token, style, bracket_token, - hdl, + kw, paren_token, body, } } - pub(crate) fn as_ref(&self) -> HdlAttr<&T> { + pub(crate) fn as_ref(&self) -> HdlAttr<&T, KW> + where + KW: Clone, + { let Self { pound_token, style, bracket_token, - hdl, + ref kw, paren_token, ref body, } = *self; @@ -142,17 +163,20 @@ impl HdlAttr { pound_token, style, bracket_token, - hdl, + kw: kw.clone(), paren_token, body, } } - pub(crate) fn try_map Result>(self, f: F) -> Result, E> { + pub(crate) fn try_map Result>( + self, + f: F, + ) -> Result, E> { let Self { pound_token, style, bracket_token, - hdl, + kw, paren_token, body, } = self; @@ -160,17 +184,17 @@ impl HdlAttr { pound_token, style, bracket_token, - hdl, + kw, paren_token, body: f(body)?, }) } - pub(crate) fn map R>(self, f: F) -> HdlAttr { + pub(crate) fn map R>(self, f: F) -> HdlAttr { let Self { pound_token, style, bracket_token, - hdl, + kw, paren_token, body, } = self; @@ -178,7 +202,7 @@ impl HdlAttr { pound_token, style, bracket_token, - hdl, + kw, paren_token, body: f(body), } @@ -186,31 +210,32 @@ impl HdlAttr { fn to_attr(&self) -> Attribute where T: ToTokens, + KW: ToTokens, { parse_quote! { #self } } } -impl Default for HdlAttr { +impl Default for HdlAttr { fn default() -> Self { T::default().into() } } -impl From for HdlAttr { +impl From for HdlAttr { fn from(body: T) -> Self { HdlAttr { pound_token: Default::default(), style: AttrStyle::Outer, bracket_token: Default::default(), - hdl: Default::default(), + kw: Default::default(), paren_token: Default::default(), body, } } } -impl ToTokens for HdlAttr { +impl ToTokens for HdlAttr { fn to_tokens(&self, tokens: &mut TokenStream) { self.pound_token.to_tokens(tokens); match self.style { @@ -218,7 +243,7 @@ impl ToTokens for HdlAttr { AttrStyle::Outer => {} }; self.bracket_token.surround(tokens, |tokens| { - self.hdl.to_tokens(tokens); + self.kw.to_tokens(tokens); match self.paren_token { Some(paren_token) => { paren_token.surround(tokens, |tokens| self.body.to_tokens(tokens)) @@ -226,7 +251,7 @@ impl ToTokens for HdlAttr { None => { let body = self.body.to_token_stream(); if !body.is_empty() { - syn::token::Paren(self.hdl.span) + syn::token::Paren(self.kw.span()) .surround(tokens, |tokens| tokens.extend([body])); } } @@ -235,18 +260,24 @@ impl ToTokens for HdlAttr { } } -fn is_hdl_attr(attr: &Attribute) -> bool { - attr.path().is_ident("hdl") +fn is_hdl_attr(attr: &Attribute) -> bool { + attr.path().is_ident(KW::IDENT_STR) } -impl HdlAttr { - fn parse_and_take_attr(attrs: &mut Vec) -> syn::Result> { +impl HdlAttr { + fn parse_and_take_attr(attrs: &mut Vec) -> syn::Result> + where + KW: ToTokens, + { let mut retval = None; let mut errors = Errors::new(); attrs.retain(|attr| { - if is_hdl_attr(attr) { + if let Ok(kw) = syn::parse2::(attr.path().to_token_stream()) { if retval.is_some() { - errors.push(Error::new_spanned(attr, "more than one #[hdl] attribute")); + errors.push(Error::new_spanned( + attr, + format_args!("more than one #[{}] attribute", kw.to_token_stream()), + )); } errors.unwrap_or_default(Self::parse_attr(attr).map(|v| retval = Some(v))); false @@ -257,13 +288,19 @@ impl HdlAttr { errors.finish()?; Ok(retval) } - fn parse_and_leave_attr(attrs: &[Attribute]) -> syn::Result> { + fn parse_and_leave_attr(attrs: &[Attribute]) -> syn::Result> + where + KW: ToTokens, + { let mut retval = None; let mut errors = Errors::new(); for attr in attrs { - if is_hdl_attr(attr) { + if let Ok(kw) = syn::parse2::(attr.path().to_token_stream()) { if retval.is_some() { - errors.push(Error::new_spanned(attr, "more than one #[hdl] attribute")); + errors.push(Error::new_spanned( + attr, + format_args!("more than one #[{}] attribute", kw.to_token_stream()), + )); } errors.unwrap_or_default(Self::parse_attr(attr).map(|v| retval = Some(v))); } @@ -284,7 +321,7 @@ impl HdlAttr { ) -> syn::Result { let bracket_content; let bracket_token = bracketed!(bracket_content in input); - let hdl = bracket_content.parse()?; + let kw = bracket_content.parse()?; let paren_content; let body; let paren_token; @@ -305,7 +342,7 @@ impl HdlAttr { pound_token, style, bracket_token, - hdl, + kw, paren_token, body, }) @@ -852,25 +889,31 @@ pub(crate) fn outline_generated(contents: TokenStream, prefix: &str) -> TokenStr } } -pub fn hdl_module(attr: TokenStream, item: TokenStream) -> syn::Result { - let options = syn::parse2::(attr)?; - let options = HdlAttr::from(options); - let func = syn::parse2::(quote! { #options #item })?; +fn hdl_module_impl(item: ItemFn) -> syn::Result { + let func = module::ModuleFn::parse_from_fn(item)?; + let options = func.config_options(); let mut contents = func.generate(); - if options.body.outline_generated.is_some() { + if options.outline_generated.is_some() { contents = outline_generated(contents, "module-"); } Ok(contents) } +pub fn hdl_module(attr: TokenStream, item: TokenStream) -> syn::Result { + let kw = kw::hdl_module::default(); + hdl_module_impl(syn::parse2(quote! { #[#kw(#attr)] #item })?) +} + pub fn hdl_attr(attr: TokenStream, item: TokenStream) -> syn::Result { - let item = syn::parse2::(quote! { #[hdl(#attr)] #item })?; + let kw = kw::hdl::default(); + let item = syn::parse2::(quote! { #[#kw(#attr)] #item })?; match item { Item::Enum(item) => hdl_enum::hdl_enum(item), Item::Struct(item) => hdl_bundle::hdl_bundle(item), + Item::Fn(item) => hdl_module_impl(item), _ => Err(syn::Error::new( Span::call_site(), - "top-level #[hdl] can only be used on structs or enums", + "top-level #[hdl] can only be used on structs, enums, or functions", )), } } diff --git a/crates/fayalite-proc-macros-impl/src/module.rs b/crates/fayalite-proc-macros-impl/src/module.rs index 0945abb..6363bb3 100644 --- a/crates/fayalite-proc-macros-impl/src/module.rs +++ b/crates/fayalite-proc-macros-impl/src/module.rs @@ -2,6 +2,7 @@ // See Notices.txt for copyright information use crate::{ hdl_type_common::{ParsedGenerics, SplitForImpl}, + kw, module::transform_body::{HdlLet, HdlLetKindIO}, options, Errors, HdlAttr, PairsIterExt, }; @@ -9,7 +10,6 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote, quote_spanned, ToTokens}; use std::collections::HashSet; use syn::{ - parse::{Parse, ParseStream}, parse_quote, visit::{visit_pat, Visit}, Attribute, Block, ConstParam, Error, FnArg, GenericParam, Generics, Ident, ItemFn, ItemStruct, @@ -59,9 +59,9 @@ impl Visit<'_> for CheckNameConflictsWithModuleBuilderVisitor<'_> { pub(crate) type ModuleIO = HdlLet; -pub(crate) struct ModuleFn { +struct ModuleFnModule { attrs: Vec, - config_options: HdlAttr, + config_options: HdlAttr, module_kind: ModuleKind, vis: Visibility, sig: Signature, @@ -70,6 +70,26 @@ pub(crate) struct ModuleFn { the_struct: TokenStream, } +enum ModuleFnImpl { + Module(ModuleFnModule), + Fn { + attrs: Vec, + config_options: HdlAttr, + vis: Visibility, + sig: Signature, + block: Box, + }, +} + +options! { + pub(crate) enum HdlOrHdlModule { + Hdl(hdl), + HdlModule(hdl_module), + } +} + +pub(crate) struct ModuleFn(ModuleFnImpl); + #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub(crate) enum ModuleKind { Extern, @@ -89,14 +109,25 @@ impl Visit<'_> for ContainsSkippedIdent<'_> { } } -impl Parse for ModuleFn { - fn parse(input: ParseStream) -> syn::Result { +impl ModuleFn { + pub(crate) fn config_options(&self) -> ConfigOptions { + let (ModuleFnImpl::Module(ModuleFnModule { + config_options: HdlAttr { body, .. }, + .. + }) + | ModuleFnImpl::Fn { + config_options: HdlAttr { body, .. }, + .. + }) = &self.0; + body.clone() + } + pub(crate) fn parse_from_fn(item: ItemFn) -> syn::Result { let ItemFn { mut attrs, vis, mut sig, block, - } = input.parse()?; + } = item; let Signature { ref constness, ref asyncness, @@ -111,43 +142,60 @@ impl Parse for ModuleFn { ref output, } = sig; let mut errors = Errors::new(); - let config_options = errors - .unwrap_or_default(HdlAttr::parse_and_take_attr(&mut attrs)) - .unwrap_or_default(); + let Some(mut config_options) = + errors.unwrap_or_default( + HdlAttr::::parse_and_take_attr(&mut attrs), + ) + else { + errors.error(sig.ident, "missing #[hdl] or #[hdl_module] attribute"); + errors.finish()?; + unreachable!(); + }; let ConfigOptions { outline_generated: _, extern_, } = config_options.body; - let module_kind = match extern_ { - Some(_) => ModuleKind::Extern, - None => ModuleKind::Normal, + let module_kind = match (config_options.kw, extern_) { + (HdlOrHdlModule::Hdl(_), None) => None, + (HdlOrHdlModule::Hdl(_), Some(extern2)) => { + config_options.body.extern_ = None; + errors.error( + extern2.0, + "extern can only be used as #[hdl_module(extern)]", + ); + None + } + (HdlOrHdlModule::HdlModule(_), None) => Some(ModuleKind::Normal), + (HdlOrHdlModule::HdlModule(_), Some(_)) => Some(ModuleKind::Extern), }; - for fn_arg in inputs { - match fn_arg { - FnArg::Receiver(_) => { - errors.push(syn::Error::new_spanned(fn_arg, "self not allowed here")); - } - FnArg::Typed(fn_arg) => { - visit_pat( - &mut CheckNameConflictsWithModuleBuilderVisitor { - errors: &mut errors, - }, - &fn_arg.pat, - ); + if let HdlOrHdlModule::HdlModule(_) = config_options.kw { + for fn_arg in inputs { + match fn_arg { + FnArg::Receiver(_) => { + errors.push(syn::Error::new_spanned(fn_arg, "self not allowed here")); + } + FnArg::Typed(fn_arg) => { + visit_pat( + &mut CheckNameConflictsWithModuleBuilderVisitor { + errors: &mut errors, + }, + &fn_arg.pat, + ); + } } } - } - if let Some(constness) = constness { - errors.push(syn::Error::new_spanned(constness, "const not allowed here")); - } - if let Some(asyncness) = asyncness { - errors.push(syn::Error::new_spanned(asyncness, "async not allowed here")); - } - if let Some(unsafety) = unsafety { - errors.push(syn::Error::new_spanned(unsafety, "unsafe not allowed here")); - } - if let Some(abi) = abi { - errors.push(syn::Error::new_spanned(abi, "extern not allowed here")); + if let Some(constness) = constness { + errors.push(syn::Error::new_spanned(constness, "const not allowed here")); + } + if let Some(asyncness) = asyncness { + errors.push(syn::Error::new_spanned(asyncness, "async not allowed here")); + } + if let Some(unsafety) = unsafety { + errors.push(syn::Error::new_spanned(unsafety, "unsafe not allowed here")); + } + if let Some(abi) = abi { + errors.push(syn::Error::new_spanned(abi, "extern not allowed here")); + } } let mut skipped_idents = HashSet::new(); let struct_generic_params = generics @@ -155,14 +203,17 @@ impl Parse for ModuleFn { .pairs_mut() .filter_map_pair_value_mut(|v| match v { GenericParam::Lifetime(LifetimeParam { attrs, .. }) => { - errors - .unwrap_or_default(HdlAttr::::parse_and_take_attr(attrs)); + errors.unwrap_or_default( + HdlAttr::::parse_and_take_attr(attrs), + ); None } GenericParam::Type(TypeParam { attrs, ident, .. }) | GenericParam::Const(ConstParam { attrs, ident, .. }) => { if errors - .unwrap_or_default(HdlAttr::::parse_and_take_attr(attrs)) + .unwrap_or_default( + HdlAttr::::parse_and_take_attr(attrs), + ) .is_some() { skipped_idents.insert(ident.clone()); @@ -176,6 +227,7 @@ impl Parse for ModuleFn { let struct_where_clause = generics .where_clause .as_mut() + .filter(|_| matches!(config_options.kw, HdlOrHdlModule::HdlModule(_))) .map(|where_clause| WhereClause { where_token: where_clause.where_token, predicates: where_clause @@ -198,22 +250,26 @@ impl Parse for ModuleFn { }) .collect(), }); - let struct_generics = Generics { - lt_token: generics.lt_token, - params: struct_generic_params, - gt_token: generics.gt_token, - where_clause: struct_where_clause, + let struct_generics = if let HdlOrHdlModule::HdlModule(_) = config_options.kw { + let mut struct_generics = Generics { + lt_token: generics.lt_token, + params: struct_generic_params, + gt_token: generics.gt_token, + where_clause: struct_where_clause, + }; + if let Some(variadic) = variadic { + errors.push(syn::Error::new_spanned(variadic, "... not allowed here")); + } + if !matches!(output, ReturnType::Default) { + errors.push(syn::Error::new_spanned( + output, + "return type not allowed here", + )); + } + errors.ok(ParsedGenerics::parse(&mut struct_generics)) + } else { + Some(ParsedGenerics::default()) }; - if let Some(variadic) = variadic { - errors.push(syn::Error::new_spanned(variadic, "... not allowed here")); - } - if !matches!(output, ReturnType::Default) { - errors.push(syn::Error::new_spanned( - output, - "return type not allowed here", - )); - } - let struct_generics = errors.ok(ParsedGenerics::parse(&mut { struct_generics })); let body_results = struct_generics.as_ref().and_then(|struct_generics| { errors.ok(transform_body::transform_body( module_kind, @@ -224,6 +280,47 @@ impl Parse for ModuleFn { errors.finish()?; let struct_generics = struct_generics.unwrap(); let (block, io) = body_results.unwrap(); + let config_options = match config_options { + HdlAttr { + pound_token, + style, + bracket_token, + kw: HdlOrHdlModule::Hdl((kw,)), + paren_token, + body, + } => { + debug_assert!(io.is_empty()); + return Ok(Self(ModuleFnImpl::Fn { + attrs, + config_options: HdlAttr { + pound_token, + style, + bracket_token, + kw, + paren_token, + body, + }, + vis, + sig, + block, + })); + } + HdlAttr { + pound_token, + style, + bracket_token, + kw: HdlOrHdlModule::HdlModule((kw,)), + paren_token, + body, + } => HdlAttr { + pound_token, + style, + bracket_token, + kw, + paren_token, + body, + }, + }; let (_struct_impl_generics, _struct_type_generics, struct_where_clause) = struct_generics.split_for_impl(); let struct_where_clause: Option = parse_quote! { #struct_where_clause }; @@ -259,22 +356,22 @@ impl Parse for ModuleFn { } }; let the_struct = crate::hdl_bundle::hdl_bundle(the_struct)?; - Ok(Self { + Ok(Self(ModuleFnImpl::Module(ModuleFnModule { attrs, config_options, - module_kind, + module_kind: module_kind.unwrap(), vis, sig, block, struct_generics, the_struct, - }) + }))) } } impl ModuleFn { pub(crate) fn generate(self) -> TokenStream { - let Self { + let ModuleFnModule { attrs, config_options, module_kind, @@ -283,7 +380,28 @@ impl ModuleFn { block, struct_generics, the_struct, - } = self; + } = match self.0 { + ModuleFnImpl::Module(v) => v, + ModuleFnImpl::Fn { + attrs, + config_options, + vis, + sig, + block, + } => { + let ConfigOptions { + outline_generated: _, + extern_: _, + } = config_options.body; + return ItemFn { + attrs, + vis, + sig, + block, + } + .into_token_stream(); + } + }; let ConfigOptions { outline_generated: _, extern_: _, diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs index 76e1d69..d4d92b2 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs @@ -925,7 +925,7 @@ with_debug_clone_and_fold! { #[allow(dead_code)] pub(crate) struct HdlLet { pub(crate) attrs: Vec, - pub(crate) hdl_attr: HdlAttr, + pub(crate) hdl_attr: HdlAttr, pub(crate) let_token: Token![let], pub(crate) mut_token: Option, pub(crate) name: Ident, @@ -1112,7 +1112,7 @@ impl ToTokens for ImplicitName { } struct Visitor<'a> { - module_kind: ModuleKind, + module_kind: Option, errors: Errors, io: Vec, block_depth: usize, @@ -1120,22 +1120,33 @@ struct Visitor<'a> { } impl Visitor<'_> { - fn take_hdl_attr(&mut self, attrs: &mut Vec) -> Option> { + fn take_hdl_attr( + &mut self, + attrs: &mut Vec, + ) -> Option> { self.errors.unwrap_or( HdlAttr::parse_and_take_attr(attrs), Some(syn::parse2::(quote! {}).unwrap().into()), ) } - fn require_normal_module(&mut self, spanned: impl ToTokens) { + fn require_normal_module_or_fn(&mut self, spanned: impl ToTokens) { match self.module_kind { - ModuleKind::Extern => { + Some(ModuleKind::Extern) => { self.errors .error(spanned, "not allowed in #[hdl_module(extern)]"); } - ModuleKind::Normal => {} + Some(ModuleKind::Normal) | None => {} } } - fn process_hdl_if(&mut self, hdl_attr: HdlAttr, expr_if: ExprIf) -> Expr { + fn require_module(&mut self, spanned: impl ToTokens) { + match self.module_kind { + None => { + self.errors.error(spanned, "not allowed in #[hdl] fn"); + } + Some(_) => {} + } + } + fn process_hdl_if(&mut self, hdl_attr: HdlAttr, expr_if: ExprIf) -> Expr { let ExprIf { attrs, if_token, @@ -1143,7 +1154,7 @@ impl Visitor<'_> { then_branch, else_branch, } = expr_if; - self.require_normal_module(if_token); + self.require_normal_module_or_fn(if_token); let else_expr = else_branch.unzip().1.map(|else_expr| match *else_expr { Expr::If(expr_if) => self.process_hdl_if(hdl_attr.clone(), expr_if), expr => expr, @@ -1208,11 +1219,12 @@ impl Visitor<'_> { .to_tokens(expr); }); let mut attrs = hdl_let.attrs.clone(); + self.require_module(kind); match self.module_kind { - ModuleKind::Extern => attrs.push(parse_quote_spanned! {hdl_let.let_token.span=> + Some(ModuleKind::Extern) => attrs.push(parse_quote_spanned! {hdl_let.let_token.span=> #[allow(unused_variables)] }), - ModuleKind::Normal => {} + Some(ModuleKind::Normal) | None => {} } let let_stmt = Local { attrs, @@ -1249,7 +1261,7 @@ impl Visitor<'_> { }, semi_token, } = hdl_let; - self.require_normal_module(instance); + self.require_normal_module_or_fn(instance); let mut expr = instance.to_token_stream(); paren.surround(&mut expr, |expr| { let name_str = ImplicitName { @@ -1276,7 +1288,7 @@ impl Visitor<'_> { fn process_hdl_let_reg_builder(&mut self, hdl_let: HdlLet) -> Local { let name = &hdl_let.name; let reg_builder = hdl_let.kind.reg_builder; - self.require_normal_module(reg_builder); + self.require_normal_module_or_fn(reg_builder); let mut expr = reg_builder.to_token_stream(); hdl_let.kind.reg_builder_paren.surround(&mut expr, |expr| { let name_str = ImplicitName { @@ -1327,7 +1339,7 @@ impl Visitor<'_> { fn process_hdl_let_wire(&mut self, hdl_let: HdlLet) -> Local { let name = &hdl_let.name; let wire = hdl_let.kind.wire; - self.require_normal_module(wire); + self.require_normal_module_or_fn(wire); let ty_expr = unwrap_or_static_type(hdl_let.kind.ty_expr.as_ref(), wire.span()); let mut expr = wire.to_token_stream(); hdl_let.kind.paren.surround(&mut expr, |expr| { @@ -1361,7 +1373,7 @@ impl Visitor<'_> { let name = &hdl_let.name; let memory_fn = hdl_let.kind.memory_fn; let memory_fn_name = memory_fn.name(); - self.require_normal_module(memory_fn_name); + self.require_normal_module_or_fn(memory_fn_name); let mut expr = memory_fn_name.to_token_stream(); let (paren, arg) = match memory_fn { MemoryFn::Memory { @@ -1543,7 +1555,7 @@ impl Fold for Visitor<'_> { } fn fold_attribute(&mut self, attr: Attribute) -> Attribute { - if is_hdl_attr(&attr) { + if is_hdl_attr::(&attr) { self.errors .error(&attr, "#[hdl] attribute not supported here"); } @@ -1610,8 +1622,9 @@ impl Fold for Visitor<'_> { fn fold_local(&mut self, let_stmt: Local) -> Local { match self .errors - .ok(HdlAttr::::parse_and_leave_attr(&let_stmt.attrs)) - { + .ok(HdlAttr::::parse_and_leave_attr( + &let_stmt.attrs, + )) { None => return empty_let(), Some(None) => return fold_local(self, let_stmt), Some(Some(HdlAttr { .. })) => {} @@ -1646,7 +1659,7 @@ impl Fold for Visitor<'_> { } pub(crate) fn transform_body( - module_kind: ModuleKind, + module_kind: Option, mut body: Box, parsed_generics: &ParsedGenerics, ) -> syn::Result<(Box, Vec)> { diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs index 00ee706..b5a0ad3 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use crate::{module::transform_body::Visitor, HdlAttr}; +use crate::{kw, module::transform_body::Visitor, HdlAttr}; use quote::{format_ident, quote_spanned}; use syn::{ parse::Nothing, parse_quote, parse_quote_spanned, spanned::Spanned, Expr, ExprArray, ExprPath, @@ -10,10 +10,10 @@ use syn::{ impl Visitor<'_> { pub(crate) fn process_hdl_array( &mut self, - hdl_attr: HdlAttr, + hdl_attr: HdlAttr, mut expr_array: ExprArray, ) -> Expr { - self.require_normal_module(hdl_attr); + self.require_normal_module_or_fn(hdl_attr); for elem in &mut expr_array.elems { *elem = parse_quote_spanned! {elem.span()=> ::fayalite::expr::ToExpr::to_expr(&(#elem)) @@ -23,10 +23,10 @@ impl Visitor<'_> { } pub(crate) fn process_hdl_repeat( &mut self, - hdl_attr: HdlAttr, + hdl_attr: HdlAttr, mut expr_repeat: ExprRepeat, ) -> Expr { - self.require_normal_module(hdl_attr); + self.require_normal_module_or_fn(hdl_attr); let repeated_value = &expr_repeat.expr; *expr_repeat.expr = parse_quote_spanned! {repeated_value.span()=> ::fayalite::expr::ToExpr::to_expr(&(#repeated_value)) @@ -35,10 +35,10 @@ impl Visitor<'_> { } pub(crate) fn process_hdl_struct( &mut self, - hdl_attr: HdlAttr, + hdl_attr: HdlAttr, expr_struct: ExprStruct, ) -> Expr { - self.require_normal_module(&hdl_attr); + self.require_normal_module_or_fn(&hdl_attr); let name_span = expr_struct.path.segments.last().unwrap().ident.span(); let builder_ident = format_ident!("__builder", span = name_span); let empty_builder = if expr_struct.qself.is_some() @@ -91,10 +91,10 @@ impl Visitor<'_> { } pub(crate) fn process_hdl_tuple( &mut self, - hdl_attr: HdlAttr, + hdl_attr: HdlAttr, expr_tuple: ExprTuple, ) -> Expr { - self.require_normal_module(hdl_attr); + self.require_normal_module_or_fn(hdl_attr); parse_quote_spanned! {expr_tuple.span()=> ::fayalite::expr::ToExpr::to_expr(&#expr_tuple) } diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs index ae21a73..1d53104 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_match.rs @@ -2,6 +2,7 @@ // See Notices.txt for copyright information use crate::{ fold::{impl_fold, DoFold}, + kw, module::transform_body::{with_debug_clone_and_fold, Visitor}, Errors, HdlAttr, PairsIterExt, }; @@ -749,7 +750,7 @@ struct HdlMatchParseState<'a> { impl Visitor<'_> { pub(crate) fn process_hdl_match( &mut self, - _hdl_attr: HdlAttr, + _hdl_attr: HdlAttr, expr_match: ExprMatch, ) -> Expr { let span = expr_match.match_token.span(); @@ -761,7 +762,7 @@ impl Visitor<'_> { brace_token: _, arms, } = expr_match; - self.require_normal_module(match_token); + self.require_normal_module_or_fn(match_token); let mut state = HdlMatchParseState { match_span: span, errors: &mut self.errors, diff --git a/crates/fayalite/tests/ui/hdl_types.stderr b/crates/fayalite/tests/ui/hdl_types.stderr index e01b04f..a65d796 100644 --- a/crates/fayalite/tests/ui/hdl_types.stderr +++ b/crates/fayalite/tests/ui/hdl_types.stderr @@ -1,4 +1,4 @@ -error: top-level #[hdl] can only be used on structs or enums +error: top-level #[hdl] can only be used on structs, enums, or functions --> tests/ui/hdl_types.rs:5:1 | 5 | #[hdl] From df55a514e442d07e1ef76275bcf481146381989d Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 20 Sep 2024 18:46:56 -0700 Subject: [PATCH 2/4] add support for incomplete_wire -- a wire that you can supply the type of later --- crates/fayalite-proc-macros-impl/src/lib.rs | 1 + .../src/module/transform_body.rs | 83 +++++++++++++++++ crates/fayalite/src/module.rs | 91 ++++++++++++++++++- crates/fayalite/src/prelude.rs | 4 +- crates/fayalite/src/wire.rs | 60 +++++++++++- 5 files changed, 231 insertions(+), 8 deletions(-) diff --git a/crates/fayalite-proc-macros-impl/src/lib.rs b/crates/fayalite-proc-macros-impl/src/lib.rs index 3ec00bf..903983f 100644 --- a/crates/fayalite-proc-macros-impl/src/lib.rs +++ b/crates/fayalite-proc-macros-impl/src/lib.rs @@ -65,6 +65,7 @@ mod kw { custom_keyword!(hdl); custom_keyword!(hdl_module); custom_keyword!(input); + custom_keyword!(incomplete_wire); custom_keyword!(instance); custom_keyword!(m); custom_keyword!(memory); diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs index d4d92b2..1f9565a 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs @@ -34,6 +34,7 @@ options! { Instance(instance), RegBuilder(reg_builder), Wire(wire), + IncompleteWire(incomplete_wire), Memory(memory), MemoryArray(memory_array), MemoryWithInit(memory_with_init), @@ -533,6 +534,41 @@ impl HdlLetKindToTokens for HdlLetKindWire { } } +options! { + pub(crate) enum LetFnKindIncomplete { + IncompleteWire(incomplete_wire), + } +} + +#[derive(Clone, Debug)] +pub(crate) struct HdlLetKindIncomplete { + pub(crate) kind: LetFnKindIncomplete, + pub(crate) paren: Paren, +} + +impl ParseTypes for HdlLetKindIncomplete { + fn parse_types(input: &mut Self, _parser: &mut TypesParser<'_>) -> Result { + Ok(input.clone()) + } +} + +impl_fold! { + struct HdlLetKindIncomplete<> { + kind: LetFnKindIncomplete, + paren: Paren, + } +} + +impl HdlLetKindToTokens for HdlLetKindIncomplete { + fn ty_to_tokens(&self, _tokens: &mut TokenStream) {} + + fn expr_to_tokens(&self, tokens: &mut TokenStream) { + let Self { kind, paren } = self; + kind.to_tokens(tokens); + paren.surround(tokens, |_| {}); + } +} + options! { pub(crate) enum MemoryFnName { Memory(memory), @@ -697,6 +733,7 @@ impl HdlLetKindMemory { #[derive(Clone, Debug)] pub(crate) enum HdlLetKind { IO(HdlLetKindIO), + Incomplete(HdlLetKindIncomplete), Instance(HdlLetKindInstance), RegBuilder(HdlLetKindRegBuilder), Wire(HdlLetKindWire), @@ -706,6 +743,7 @@ pub(crate) enum HdlLetKind { impl_fold! { enum HdlLetKind { IO(HdlLetKindIO), + Incomplete(HdlLetKindIncomplete), Instance(HdlLetKindInstance), RegBuilder(HdlLetKindRegBuilder), Wire(HdlLetKindWire), @@ -720,6 +758,9 @@ impl, I> ParseTypes> for HdlLetKind { ) -> Result { match input { HdlLetKind::IO(input) => ParseTypes::parse_types(input, parser).map(HdlLetKind::IO), + HdlLetKind::Incomplete(input) => { + ParseTypes::parse_types(input, parser).map(HdlLetKind::Incomplete) + } HdlLetKind::Instance(input) => { ParseTypes::parse_types(input, parser).map(HdlLetKind::Instance) } @@ -871,6 +912,20 @@ impl HdlLetKindParse for HdlLetKind { ty_expr: paren_contents.call(parse_optional_fn_arg)?, })) } + LetFnKind::IncompleteWire(incomplete_wire) => { + if let Some(parsed_ty) = parsed_ty { + return Err(Error::new_spanned( + parsed_ty.1, + "type annotation not allowed for incomplete_wire", + )); + } + check_empty_m_dot(m_dot, kind)?; + let _paren_contents; + Ok(Self::Incomplete(HdlLetKindIncomplete { + kind: LetFnKindIncomplete::IncompleteWire(incomplete_wire), + paren: parenthesized!(_paren_contents in input), + })) + } LetFnKind::Memory(fn_name) => HdlLetKindMemory::rest_of_parse( input, parsed_ty, @@ -903,6 +958,7 @@ impl HdlLetKindToTokens for HdlLetKind { fn ty_to_tokens(&self, tokens: &mut TokenStream) { match self { HdlLetKind::IO(v) => v.ty_to_tokens(tokens), + HdlLetKind::Incomplete(v) => v.ty_to_tokens(tokens), HdlLetKind::Instance(v) => v.ty_to_tokens(tokens), HdlLetKind::RegBuilder(v) => v.ty_to_tokens(tokens), HdlLetKind::Wire(v) => v.ty_to_tokens(tokens), @@ -913,6 +969,7 @@ impl HdlLetKindToTokens for HdlLetKind { fn expr_to_tokens(&self, tokens: &mut TokenStream) { match self { HdlLetKind::IO(v) => v.expr_to_tokens(tokens), + HdlLetKind::Incomplete(v) => v.expr_to_tokens(tokens), HdlLetKind::Instance(v) => v.expr_to_tokens(tokens), HdlLetKind::RegBuilder(v) => v.expr_to_tokens(tokens), HdlLetKind::Wire(v) => v.expr_to_tokens(tokens), @@ -1369,6 +1426,31 @@ impl Visitor<'_> { semi_token: hdl_let.semi_token, } } + fn process_hdl_let_incomplete(&mut self, hdl_let: HdlLet) -> Local { + let name = &hdl_let.name; + let kind = hdl_let.kind.kind; + self.require_normal_module_or_fn(kind); + let mut expr = kind.to_token_stream(); + hdl_let.kind.paren.surround(&mut expr, |expr| { + ImplicitName { + name, + span: name.span(), + } + .to_tokens(expr); + }); + let mut_token = &hdl_let.mut_token; + Local { + attrs: hdl_let.attrs.clone(), + let_token: hdl_let.let_token, + pat: parse_quote! { #mut_token #name }, + init: Some(LocalInit { + eq_token: hdl_let.eq_token, + expr: parse_quote! { #expr }, + diverge: None, + }), + semi_token: hdl_let.semi_token, + } + } fn process_hdl_let_memory(&mut self, hdl_let: HdlLet) -> Local { let name = &hdl_let.name; let memory_fn = hdl_let.kind.memory_fn; @@ -1438,6 +1520,7 @@ impl Visitor<'_> { } the_match! { IO => process_hdl_let_io, + Incomplete => process_hdl_let_incomplete, Instance => process_hdl_let_instance, RegBuilder => process_hdl_let_reg_builder, Wire => process_hdl_let_wire, diff --git a/crates/fayalite/src/module.rs b/crates/fayalite/src/module.rs index cb57758..3a17343 100644 --- a/crates/fayalite/src/module.rs +++ b/crates/fayalite/src/module.rs @@ -22,7 +22,7 @@ use crate::{ source_location::SourceLocation, ty::{CanonicalType, Type}, util::ScopedRef, - wire::Wire, + wire::{IncompleteWire, Wire}, }; use hashbrown::{hash_map::Entry, HashMap, HashSet}; use num_bigint::BigInt; @@ -118,9 +118,35 @@ pub trait BlockRef: 'static + Send + Sync + Copy + Eq + Hash + fmt::Debug {} impl BlockRef for BlockId {} +pub(crate) enum IncompleteDeclaration { + Incomplete { + name: ScopedNameId, + source_location: SourceLocation, + }, + Complete(StmtDeclaration), + Taken, +} + +impl fmt::Debug for IncompleteDeclaration { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Incomplete { + name, + source_location: _, + } => f + .debug_struct("Incomplete") + .field("name", name) + .finish_non_exhaustive(), + Self::Complete(v) => v.fmt(f), + Self::Taken => f.write_str("Taken"), + } + } +} + #[derive(Debug)] pub struct BuilderBlock { memories: Vec>>, + incomplete_declarations: Vec>>, stmts: Vec>, } @@ -831,13 +857,34 @@ impl From> for NormalModuleBody { annotations_map: &mut HashMap, Vec>, block_id: BlockId, ) -> Block { - let BuilderBlock { memories, stmts } = &mut blocks[block_id.as_usize()]; + let BuilderBlock { + memories, + incomplete_declarations, + stmts, + } = &mut blocks[block_id.as_usize()]; let memories = Interned::from_iter( memories .drain(..) .filter_map(|memory| memory.borrow().make_memory()), ); - let stmts = std::mem::take(stmts); + let stmts = Vec::from_iter( + incomplete_declarations + .drain(..) + .map(|decl| { + match std::mem::replace( + &mut *decl.borrow_mut(), + IncompleteDeclaration::Taken, + ) { + IncompleteDeclaration::Incomplete { + name, + source_location, + } => panic!("incomplete declaration: {name:?}\nat: {source_location}"), + IncompleteDeclaration::Complete(v) => Stmt::Declaration(v), + IncompleteDeclaration::Taken => unreachable!(), + } + }) + .chain(stmts.drain(..)), + ); let stmts = Interned::from_iter(stmts.into_iter().map(|stmt| { match stmt { Stmt::Connect(stmt) => stmt.into(), @@ -908,6 +955,7 @@ impl NormalModuleBody { let index = self.body.blocks.len(); self.body.blocks.push(BuilderBlock { memories: vec![], + incomplete_declarations: vec![], stmts: vec![], }); BlockId(index) @@ -1943,6 +1991,7 @@ impl ModuleBuilder { body: BuilderModuleBody { blocks: vec![BuilderBlock { memories: vec![], + incomplete_declarations: vec![], stmts: vec![], }], annotations_map: HashMap::new(), @@ -2156,6 +2205,42 @@ pub fn wire(implicit_name: ImplicitName<'_>, ty: T) -> Expr { wire_with_loc(implicit_name.0, SourceLocation::caller(), ty) } +#[track_caller] +fn incomplete_declaration( + name: &str, + source_location: SourceLocation, +) -> Rc> { + ModuleBuilder::with(|m| { + let mut impl_ = m.impl_.borrow_mut(); + let scoped_name = ScopedNameId(m.name, impl_.name_id_gen.gen(name.intern())); + drop(impl_); + let retval = Rc::new(RefCell::new(IncompleteDeclaration::Incomplete { + name: scoped_name, + source_location, + })); + let mut impl_ = m.impl_.borrow_mut(); + impl_ + .body + .builder_normal_body() + .block(m.block_stack.top()) + .incomplete_declarations + .push(retval.clone()); + retval + }) +} + +#[track_caller] +pub fn incomplete_wire_with_loc(name: &str, source_location: SourceLocation) -> IncompleteWire { + IncompleteWire { + declaration: incomplete_declaration(name, source_location), + } +} + +#[track_caller] +pub fn incomplete_wire(implicit_name: ImplicitName<'_>) -> IncompleteWire { + incomplete_wire_with_loc(implicit_name.0, SourceLocation::caller()) +} + #[track_caller] pub fn reg_builder_with_loc(name: &str, source_location: SourceLocation) -> RegBuilder<(), (), ()> { ModuleBuilder::with(|m| { diff --git a/crates/fayalite/src/prelude.rs b/crates/fayalite/src/prelude.rs index 14c3aa7..bedece2 100644 --- a/crates/fayalite/src/prelude.rs +++ b/crates/fayalite/src/prelude.rs @@ -9,8 +9,8 @@ pub use crate::{ int::{Bool, DynSize, IntCmp, KnownSize, SInt, SIntType, Size, UInt, UIntType}, memory::{Mem, MemBuilder, ReadUnderWrite}, module::{ - annotate, connect, connect_any, instance, memory, memory_array, memory_with_init, - reg_builder, wire, Instance, Module, ModuleBuilder, + annotate, connect, connect_any, incomplete_wire, instance, memory, memory_array, + memory_with_init, reg_builder, wire, Instance, Module, ModuleBuilder, }, reg::Reg, reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset}, diff --git a/crates/fayalite/src/wire.rs b/crates/fayalite/src/wire.rs index b84d6ae..85ab342 100644 --- a/crates/fayalite/src/wire.rs +++ b/crates/fayalite/src/wire.rs @@ -1,13 +1,13 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information use crate::{ - expr::Flow, + expr::{Expr, Flow, ToExpr}, intern::Interned, - module::{NameId, ScopedNameId}, + module::{IncompleteDeclaration, NameId, ScopedNameId, StmtDeclaration, StmtWire}, source_location::SourceLocation, ty::{CanonicalType, Type}, }; -use std::fmt; +use std::{cell::RefCell, fmt, rc::Rc}; #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct Wire { @@ -76,3 +76,57 @@ impl Wire { true } } + +#[derive(Clone)] +pub struct IncompleteWire { + pub(crate) declaration: Rc>, +} + +impl IncompleteWire { + #[track_caller] + pub fn complete(&mut self, ty: T) -> Expr { + let canonical_type = ty.canonical(); + let mut declaration = self.declaration.borrow_mut(); + if let IncompleteDeclaration::Incomplete { + name, + source_location, + } = *declaration + { + *declaration = IncompleteDeclaration::Complete( + StmtWire { + annotations: (), + wire: Wire { + name, + source_location, + ty: canonical_type, + }, + } + .into(), + ); + } + match *declaration { + IncompleteDeclaration::Complete(StmtDeclaration::Wire(StmtWire { + wire: + Wire { + name, + source_location, + ty: wire_ty, + }, + .. + })) => { + drop(declaration); + assert_eq!(wire_ty, canonical_type, "type mismatch"); + Wire { + name, + source_location, + ty, + } + .to_expr() + } + IncompleteDeclaration::Taken => panic!("can't use wire outside of containing module"), + IncompleteDeclaration::Complete(_) | IncompleteDeclaration::Incomplete { .. } => { + unreachable!() + } + } + } +} From ff269e5def6395f4533180ceea51083d76801c07 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 20 Sep 2024 18:49:12 -0700 Subject: [PATCH 3/4] add utility functions on HdlOption, inspired by Option's API --- crates/fayalite/src/enum_.rs | 311 ++++++++++++++++++++++++++++++++++- 1 file changed, 308 insertions(+), 3 deletions(-) diff --git a/crates/fayalite/src/enum_.rs b/crates/fayalite/src/enum_.rs index 384414c..13724ef 100644 --- a/crates/fayalite/src/enum_.rs +++ b/crates/fayalite/src/enum_.rs @@ -7,14 +7,14 @@ use crate::{ int::Bool, intern::{Intern, Interned}, module::{ - enum_match_variants_helper, EnumMatchVariantAndInactiveScopeImpl, - EnumMatchVariantsIterImpl, Scope, + connect, enum_match_variants_helper, incomplete_wire, wire, + EnumMatchVariantAndInactiveScopeImpl, EnumMatchVariantsIterImpl, Scope, }, source_location::SourceLocation, ty::{CanonicalType, MatchVariantAndInactiveScope, StaticType, Type, TypeProperties}, }; use hashbrown::HashMap; -use std::{fmt, iter::FusedIterator}; +use std::{convert::Infallible, fmt, iter::FusedIterator}; #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct EnumVariant { @@ -364,3 +364,308 @@ pub fn HdlSome(value: impl ToExpr) -> Expr> { let value = value.to_expr(); HdlOption[Expr::ty(value)].HdlSome(value) } + +impl HdlOption { + #[track_caller] + pub fn try_map( + expr: Expr, + f: impl FnOnce(Expr) -> Result, E>, + ) -> Result>, E> { + Self::try_and_then(expr, |v| Ok(HdlSome(f(v)?))) + } + #[track_caller] + pub fn map( + expr: Expr, + f: impl FnOnce(Expr) -> Expr, + ) -> Expr> { + Self::and_then(expr, |v| HdlSome(f(v))) + } + #[hdl] + #[track_caller] + pub fn try_and_then( + expr: Expr, + f: impl FnOnce(Expr) -> Result>, E>, + ) -> Result>, E> { + // manually run match steps so we can extract the return type to construct HdlNone + type Wrap = T; + #[hdl] + let mut and_then_out = incomplete_wire(); + let mut iter = Self::match_variants(expr, SourceLocation::caller()); + let none = iter.next().unwrap(); + let some = iter.next().unwrap(); + assert!(iter.next().is_none()); + let (Wrap::<::MatchVariant>::HdlSome(value), some_scope) = + Self::match_activate_scope(some) + else { + unreachable!(); + }; + let value = f(value).map_err(|e| { + and_then_out.complete(()); // avoid error + e + })?; + let and_then_out = and_then_out.complete(Expr::ty(value)); + connect(and_then_out, value); + drop(some_scope); + let (Wrap::<::MatchVariant>::HdlNone, none_scope) = + Self::match_activate_scope(none) + else { + unreachable!(); + }; + connect(and_then_out, Expr::ty(and_then_out).HdlNone()); + drop(none_scope); + Ok(and_then_out) + } + #[track_caller] + pub fn and_then( + expr: Expr, + f: impl FnOnce(Expr) -> Expr>, + ) -> Expr> { + match Self::try_and_then(expr, |v| Ok::<_, Infallible>(f(v))) { + Ok(v) => v, + Err(e) => match e {}, + } + } + #[hdl] + #[track_caller] + pub fn and(expr: Expr, opt_b: Expr>) -> Expr> { + #[hdl] + let and_out = wire(Expr::ty(opt_b)); + connect(and_out, Expr::ty(opt_b).HdlNone()); + #[hdl] + if let HdlSome(_) = expr { + connect(and_out, opt_b); + } + and_out + } + #[hdl] + #[track_caller] + pub fn try_filter( + expr: Expr, + f: impl FnOnce(Expr) -> Result, E>, + ) -> Result, E> { + #[hdl] + let filtered = wire(Expr::ty(expr)); + connect(filtered, Expr::ty(expr).HdlNone()); + let mut f = Some(f); + #[hdl] + if let HdlSome(v) = expr { + #[hdl] + if f.take().unwrap()(v)? { + connect(filtered, HdlSome(v)); + } + } + Ok(filtered) + } + #[hdl] + #[track_caller] + pub fn filter(expr: Expr, f: impl FnOnce(Expr) -> Expr) -> Expr { + match Self::try_filter(expr, |v| Ok::<_, Infallible>(f(v))) { + Ok(v) => v, + Err(e) => match e {}, + } + } + #[hdl] + #[track_caller] + pub fn try_inspect( + expr: Expr, + f: impl FnOnce(Expr) -> Result<(), E>, + ) -> Result, E> { + let mut f = Some(f); + #[hdl] + if let HdlSome(v) = expr { + f.take().unwrap()(v)?; + } + Ok(expr) + } + #[hdl] + #[track_caller] + pub fn inspect(expr: Expr, f: impl FnOnce(Expr)) -> Expr { + let mut f = Some(f); + #[hdl] + if let HdlSome(v) = expr { + f.take().unwrap()(v); + } + expr + } + #[hdl] + #[track_caller] + pub fn is_none(expr: Expr) -> Expr { + #[hdl] + let is_none_out: Bool = wire(); + connect(is_none_out, false); + #[hdl] + if let HdlNone = expr { + connect(is_none_out, true); + } + is_none_out + } + #[hdl] + #[track_caller] + pub fn is_some(expr: Expr) -> Expr { + #[hdl] + let is_some_out: Bool = wire(); + connect(is_some_out, false); + #[hdl] + if let HdlSome(_) = expr { + connect(is_some_out, true); + } + is_some_out + } + #[hdl] + #[track_caller] + pub fn map_or( + expr: Expr, + default: Expr, + f: impl FnOnce(Expr) -> Expr, + ) -> Expr { + #[hdl] + let mapped = wire(Expr::ty(default)); + let mut f = Some(f); + #[hdl] + match expr { + HdlSome(v) => connect(mapped, f.take().unwrap()(v)), + HdlNone => connect(mapped, default), + } + mapped + } + #[hdl] + #[track_caller] + pub fn map_or_else( + expr: Expr, + default: impl FnOnce() -> Expr, + f: impl FnOnce(Expr) -> Expr, + ) -> Expr { + #[hdl] + let mut mapped = incomplete_wire(); + let mut default = Some(default); + let mut f = Some(f); + let mut retval = None; + #[hdl] + match expr { + HdlSome(v) => { + let v = f.take().unwrap()(v); + let mapped = *retval.get_or_insert_with(|| mapped.complete(Expr::ty(v))); + connect(mapped, v); + } + HdlNone => { + let v = default.take().unwrap()(); + let mapped = *retval.get_or_insert_with(|| mapped.complete(Expr::ty(v))); + connect(mapped, v); + } + } + retval.unwrap() + } + #[hdl] + #[track_caller] + pub fn or(expr: Expr, opt_b: Expr) -> Expr { + #[hdl] + let or_out = wire(Expr::ty(expr)); + connect(or_out, opt_b); + #[hdl] + if let HdlSome(_) = expr { + connect(or_out, expr); + } + or_out + } + #[hdl] + #[track_caller] + pub fn or_else(expr: Expr, f: impl FnOnce() -> Expr) -> Expr { + #[hdl] + let or_else_out = wire(Expr::ty(expr)); + connect(or_else_out, f()); + #[hdl] + if let HdlSome(_) = expr { + connect(or_else_out, expr); + } + or_else_out + } + #[hdl] + #[track_caller] + pub fn unwrap_or(expr: Expr, default: Expr) -> Expr { + #[hdl] + let unwrap_or_else_out = wire(Expr::ty(default)); + connect(unwrap_or_else_out, default); + #[hdl] + if let HdlSome(v) = expr { + connect(unwrap_or_else_out, v); + } + unwrap_or_else_out + } + #[hdl] + #[track_caller] + pub fn unwrap_or_else(expr: Expr, f: impl FnOnce() -> Expr) -> Expr { + #[hdl] + let unwrap_or_else_out = wire(Expr::ty(expr).HdlSome); + connect(unwrap_or_else_out, f()); + #[hdl] + if let HdlSome(v) = expr { + connect(unwrap_or_else_out, v); + } + unwrap_or_else_out + } + #[hdl] + #[track_caller] + pub fn xor(expr: Expr, opt_b: Expr) -> Expr { + #[hdl] + let xor_out = wire(Expr::ty(expr)); + #[hdl] + if let HdlSome(_) = expr { + #[hdl] + if let HdlNone = opt_b { + connect(xor_out, expr); + } else { + connect(xor_out, Expr::ty(expr).HdlNone()); + } + } else { + connect(xor_out, opt_b); + } + xor_out + } + #[hdl] + #[track_caller] + pub fn zip(expr: Expr, other: Expr>) -> Expr> { + #[hdl] + let zip_out = wire(HdlOption[(Expr::ty(expr).HdlSome, Expr::ty(other).HdlSome)]); + connect(zip_out, Expr::ty(zip_out).HdlNone()); + #[hdl] + if let HdlSome(l) = expr { + #[hdl] + if let HdlSome(r) = other { + connect(zip_out, HdlSome((l, r))); + } + } + zip_out + } +} + +impl HdlOption> { + #[hdl] + #[track_caller] + pub fn flatten(expr: Expr) -> Expr> { + #[hdl] + let flattened = wire(Expr::ty(expr).HdlSome); + #[hdl] + match expr { + HdlSome(v) => connect(flattened, v), + HdlNone => connect(flattened, Expr::ty(expr).HdlSome.HdlNone()), + } + flattened + } +} + +impl HdlOption<(T, U)> { + #[hdl] + #[track_caller] + pub fn unzip(expr: Expr) -> Expr<(HdlOption, HdlOption)> { + let (t, u) = Expr::ty(expr).HdlSome; + #[hdl] + let unzipped = wire((HdlOption[t], HdlOption[u])); + connect(unzipped, (HdlOption[t].HdlNone(), HdlOption[u].HdlNone())); + #[hdl] + if let HdlSome(v) = expr { + connect(unzipped.0, HdlSome(v.0)); + connect(unzipped.1, HdlSome(v.1)); + } + unzipped + } +} From 51ce7b079ec1b0ea53c31348be86f5c2fb52c124 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Fri, 20 Sep 2024 19:11:30 -0700 Subject: [PATCH 4/4] add ReadyValid --- crates/fayalite/src/util.rs | 2 ++ crates/fayalite/src/util/ready_valid.rs | 34 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 crates/fayalite/src/util/ready_valid.rs diff --git a/crates/fayalite/src/util.rs b/crates/fayalite/src/util.rs index fc5daf4..5b97e3b 100644 --- a/crates/fayalite/src/util.rs +++ b/crates/fayalite/src/util.rs @@ -25,3 +25,5 @@ pub use scoped_ref::ScopedRef; pub use misc::{ interned_bit, iter_eq_by, BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice, }; + +pub mod ready_valid; diff --git a/crates/fayalite/src/util/ready_valid.rs b/crates/fayalite/src/util/ready_valid.rs new file mode 100644 index 0000000..a5893cf --- /dev/null +++ b/crates/fayalite/src/util/ready_valid.rs @@ -0,0 +1,34 @@ +use crate::prelude::*; + +#[hdl] +pub struct ReadyValid { + pub data: HdlOption, + #[hdl(flip)] + pub ready: Bool, +} + +impl ReadyValid { + #[hdl] + pub fn fire(expr: Expr) -> Expr { + #[hdl] + let fire: Bool = wire(); + #[hdl] + match expr.data { + HdlNone => connect(fire, false), + HdlSome(_) => connect(fire, expr.ready), + } + fire + } + #[hdl] + pub fn map( + expr: Expr, + f: impl FnOnce(Expr) -> Expr, + ) -> Expr> { + let data = HdlOption::map(expr.data, f); + #[hdl] + let mapped = wire(ReadyValid[Expr::ty(data).HdlSome]); + connect(mapped.data, data); + connect(expr.ready, mapped.ready); + mapped + } +}