reg_map_derive/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::quote;
4use syn::{Data, DataStruct, DeriveInput, Fields, Ident, Result, Type, TypeArray, Visibility};
5
6macro_rules! bail {
7    ($msg:expr) => {
8        return ::core::result::Result::Err(::syn::Error::new(
9            ::proc_macro2::Span::call_site(),
10            $msg,
11        ))
12    };
13    ($span:expr, $msg:expr) => {
14        return ::core::result::Result::Err(::syn::Error::new_spanned($span, $msg))
15    };
16}
17
18#[proc_macro_derive(RegMap, attributes(reg))]
19pub fn reg_map_derive(input: TokenStream) -> TokenStream {
20    let input = syn::parse_macro_input!(input);
21
22    // Build the trait implementation
23    impl_reg(&input).unwrap_or_else(|err| err.into_compile_error().into())
24}
25
26fn impl_reg(ast: &DeriveInput) -> Result<TokenStream> {
27    let name = &ast.ident;
28    let vis = &ast.vis;
29    let ptr_vis = parse_visibility(vis)?;
30
31    // check if using a compatible repr
32    check_repr(ast)?;
33
34    if let Data::Struct(DataStruct {
35        struct_token: _,
36        ref fields,
37        semi_token: _,
38    }) = ast.data
39    {
40        let ptr_name = Ident::new(&format!("{}Ptr", name), Span::call_site());
41        let mod_name = Ident::new(&format!("_mod_{}", name), Span::call_site());
42        let mut all_methods = quote!();
43        if let Fields::Named(named) = fields {
44            for field in named.named.iter() {
45                all_methods.extend(parse_field(field)?);
46            }
47        } else {
48            bail!(ast, "RegMap derive supports only structs with named fields");
49        }
50        let doc_msg_top = format!("A pointer to the register map `{name}`.");
51        let doc_msg_from_nonnull = format!(
52            "\
53            Creates a new `{ptr_name}`, a pointer to `{name}`.\n\
54            \n\
55            # Safety\n\
56            - `ptr` must point to a valid instance of `{name}`;\n\
57            - `ptr` must be valid for the whole lifetime `'a`;\n\
58            - all fields of `{name}` must allow volatile reads/writes."
59        );
60        let doc_msg_from_ptr = format!(
61            "\
62            Creates a new `{ptr_name}`, a pointer to `{name}`.\n\
63            \n\
64            # Safety\n\
65            - `ptr` must not be null;\n\
66            - `ptr` must point to a valid instance of `{name}`;\n\
67            - `ptr` must be valid for the whole lifetime `'a`;\n\
68            - all fields of `{name}` must allow volatile reads/writes."
69        );
70        let doc_msg_from_mut =
71            format!("Return a pointer to `{name}` from a mutable (exclusive) reference.");
72        let all = quote!(
73            #[allow(non_snake_case)]
74            mod #mod_name {
75                use super::*;
76                #[doc = #doc_msg_top]
77                #ptr_vis struct #ptr_name<'a> {
78                    ptr: ::core::ptr::NonNull<#name>,
79                    _ref: ::core::marker::PhantomData<&'a #name>,
80                }
81                impl<'a> #ptr_name<'a> {
82                    #[doc = #doc_msg_from_nonnull]
83                    #[inline]
84                    const unsafe fn from_nonnull(ptr: ::core::ptr::NonNull<#name>) -> Self {
85                        Self {
86                            ptr,
87                            _ref: ::core::marker::PhantomData,
88                        }
89                    }
90
91                    #[doc = #doc_msg_from_ptr]
92                    #[inline]
93                    pub const unsafe fn from_ptr(ptr: *mut #name) -> Self {
94                        Self::from_nonnull(::core::ptr::NonNull::new_unchecked(ptr))
95                    }
96
97                    #[doc = #doc_msg_from_mut]
98                    #[inline]
99                    pub fn from_mut(reg: &'a mut #name) -> Self {
100                        // safe because we are the only borrowers (&mut)
101                        // and the borrow is valid for 'a
102                        unsafe { Self::from_ptr(reg) }
103                    }
104
105                    /// Returns a raw pointer to the underlying register map.
106                    #[inline]
107                    pub const fn as_ptr(&self) -> *mut #name {
108                        self.ptr.as_ptr()
109                    }
110                    #all_methods
111                }
112                unsafe impl<'a> ::reg_map::RegMapPtr<'a> for #ptr_name<'a> {
113                    type RegMap = #name;
114                    #[inline]
115                    unsafe fn from_nonnull(ptr: ::core::ptr::NonNull<Self::RegMap>) -> Self {
116                        Self::from_nonnull(ptr)
117                    }
118                    #[inline]
119                    unsafe fn from_ptr(ptr: *mut Self::RegMap) -> Self {
120                        Self::from_ptr(ptr)
121                    }
122                    #[inline]
123                    fn from_mut(reg: &'a mut Self::RegMap) -> Self {
124                        Self::from_mut(reg)
125                    }
126                    #[inline]
127                    fn as_ptr(&self) -> *mut Self::RegMap {
128                        self.as_ptr()
129                    }
130                }
131            }
132            #vis use #mod_name::#ptr_name;
133        );
134        Ok(all.into())
135    } else {
136        bail!(ast, "RegMap derive supports only structs")
137    }
138}
139
140fn parse_visibility(vis: &Visibility) -> Result<proc_macro2::TokenStream> {
141    Ok(match vis {
142        Visibility::Inherited => quote!(pub(super)),
143        Visibility::Public(_) => quote!(pub),
144        Visibility::Restricted(vis_restricted) => {
145            if vis_restricted.in_token.is_some() {
146                bail!(
147                    vis,
148                    "RegMap derive does not support `pub(in ...)` visibilities"
149                );
150            } else {
151                let path = &vis_restricted.path;
152                if path.is_ident("crate") {
153                    quote!(pub(crate))
154                } else if path.is_ident("super") {
155                    quote!(pub(in super::super))
156                } else if path.is_ident("self") {
157                    quote!(pub(super))
158                } else {
159                    bail!(vis, "RegMap derive found an unexpected visibility");
160                }
161            }
162        }
163    })
164}
165
166fn is_integer(ident: &Ident) -> bool {
167    ident == "u8"
168        || ident == "u16"
169        || ident == "u32"
170        || ident == "u64"
171        || ident == "u128"
172        || ident == "i8"
173        || ident == "i16"
174        || ident == "i32"
175        || ident == "i64"
176        || ident == "i128"
177}
178
179mod kw {
180    syn::custom_keyword!(RO);
181    syn::custom_keyword!(WO);
182    syn::custom_keyword!(RW);
183}
184#[derive(Default)]
185enum RegAccess {
186    RO,
187    WO,
188    #[default]
189    RW,
190}
191impl syn::parse::Parse for RegAccess {
192    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
193        let lookahead = input.lookahead1();
194        if lookahead.peek(kw::RO) {
195            input.parse::<kw::RO>().map(|_| RegAccess::RO)
196        } else if lookahead.peek(kw::WO) {
197            input.parse::<kw::WO>().map(|_| RegAccess::WO)
198        } else if lookahead.peek(kw::RW) {
199            input.parse::<kw::RW>().map(|_| RegAccess::RW)
200        } else {
201            Err(lookahead.error())
202        }
203    }
204}
205impl quote::ToTokens for RegAccess {
206    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
207        match self {
208            RegAccess::RO => tokens.extend(quote!(::reg_map::access::ReadOnly)),
209            RegAccess::WO => tokens.extend(quote!(::reg_map::access::WriteOnly)),
210            RegAccess::RW => tokens.extend(quote!(::reg_map::access::ReadWrite)),
211        }
212    }
213}
214
215fn check_repr(input: &DeriveInput) -> Result<()> {
216    let mut repr_c = false;
217    let mut repr_align = None::<usize>;
218
219    for attr in &input.attrs {
220        if attr.path().is_ident("repr") {
221            attr.parse_nested_meta(|meta| {
222                // #[repr(C)]
223                if meta.path.is_ident("C") {
224                    repr_c = true;
225                    return Ok(());
226                }
227
228                // #[repr(transparent)]
229                if meta.path.is_ident("transparent") {
230                    // TODO: this is possibly OK, investigate...
231                    return Err(meta.error("RegMap derive does not support #[repr(transparent)]"));
232                }
233
234                // #[repr(align(N))]
235                if meta.path.is_ident("align") {
236                    let content;
237                    syn::parenthesized!(content in meta.input);
238                    let lit: syn::LitInt = content.parse()?;
239                    let n: usize = lit.base10_parse()?;
240                    repr_align = Some(n);
241                    return Ok(());
242                }
243
244                // #[repr(packed)] or #[repr(packed(N))], omitted N means 1
245                if meta.path.is_ident("packed") {
246                    return Err(meta.error("RegMap derive does not support #[repr(packed)]"));
247                }
248
249                Err(meta.error("RegMap derive found an unrecognized #[repr(...)] attribute"))
250            })?;
251        }
252    }
253
254    if repr_c {
255        Ok(())
256    } else {
257        bail!("RegMap derive requires #[repr(C)]")
258    }
259}
260
261fn parse_field(field: &syn::Field) -> Result<proc_macro2::TokenStream> {
262    let name = field.ident.as_ref().expect("struct fields are named");
263    let ty = &field.ty;
264    let ret_sig = parse_ret_type(field, ty)?;
265    let doc = parse_docs(field);
266    Ok(match ty {
267        Type::Array(TypeArray { .. }) => quote!(
268            #doc
269            #[inline]
270            pub fn #name (&self) -> #ret_sig {
271                unsafe { ::reg_map::RegArray::__MACRO_ONLY__from_ptr(::core::ptr::addr_of_mut!((*self.as_ptr()).#name)) }
272            }
273        ),
274        Type::Path(ref type_path) => {
275            let ident = &type_path.path.segments[0].ident;
276            if is_integer(ident) {
277                quote!(
278                    #doc
279                    #[inline]
280                    pub fn #name (&self) -> #ret_sig {
281                        unsafe { ::reg_map::Reg::__MACRO_ONLY__from_ptr(::core::ptr::addr_of_mut!((*self.as_ptr()).#name)) }
282                    }
283                )
284            } else {
285                let ptr_ty = Ident::new(&format!("{}Ptr", ident), Span::call_site());
286                quote!(
287                    #doc
288                    #[inline]
289                    pub fn #name (&self) -> #ret_sig {
290                        unsafe { #ptr_ty::from_ptr(::core::ptr::addr_of_mut!((*self.as_ptr()).#name)) }
291                    }
292                )
293            }
294        }
295        _ => bail!(
296            field,
297            "RegMap derive supports only field of type Path or Array"
298        ),
299    })
300}
301
302fn parse_ret_type(field: &syn::Field, ty: &Type) -> Result<proc_macro2::TokenStream> {
303    match ty {
304        Type::Array(TypeArray { elem, len, .. }) => {
305            // recursive!
306            let inner_sig = parse_ret_type(field, elem)?;
307            Ok(quote!(::reg_map::RegArray<'a, #inner_sig, {#len}>))
308        }
309        Type::Path(ref type_path) => {
310            let ident = &type_path.path.segments[0].ident;
311            if is_integer(ident) {
312                let mut access = RegAccess::default();
313                for attr in &field.attrs {
314                    if attr.path().is_ident("reg") {
315                        access = attr.parse_args()?;
316                    }
317                }
318                Ok(quote!(::reg_map::Reg<'a, #ident, #access>))
319            } else {
320                let ptr_ty = Ident::new(&format!("{}Ptr", ident), Span::call_site());
321                Ok(quote!(#ptr_ty<'a>))
322            }
323        }
324        _ => bail!(
325            field,
326            "RegMap derive supports only field of type Path or Array"
327        ),
328    }
329}
330fn parse_docs(field: &syn::Field) -> proc_macro2::TokenStream {
331    let mut docs = quote!();
332    for attr in &field.attrs {
333        if attr.path().is_ident("doc") {
334            let text = &attr
335                .meta
336                .require_name_value()
337                .expect("doc attributes are name-value")
338                .value;
339            docs.extend(quote!(#[doc = #text]));
340        }
341    }
342    docs
343}