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 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_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 unsafe { Self::from_ptr(reg) }
103 }
104
105 #[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 if meta.path.is_ident("C") {
224 repr_c = true;
225 return Ok(());
226 }
227
228 if meta.path.is_ident("transparent") {
230 return Err(meta.error("RegMap derive does not support #[repr(transparent)]"));
232 }
233
234 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 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 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}