gba_proc_macro/
lib.rs

1// Note(Lokathor): this extern crate is necessary even in 2018 for whatever
2// reason that I'm sure is stupid.
3extern crate proc_macro;
4extern crate rand;
5
6use core::str::FromStr;
7use proc_macro::TokenStream;
8use proc_macro2::Span;
9use quote::quote;
10use rand::{Rng, SeedableRng};
11use std::sync::atomic::{AtomicUsize, Ordering};
12use std::time::{SystemTime, UNIX_EPOCH};
13use syn::{
14  parse::{self, Parse, ParseStream, Result},
15  parse_macro_input,
16  spanned::Spanned,
17  Attribute, Error, Ident, ItemFn, LitInt, ReturnType, Token, Type, TypePath, Visibility,
18};
19
20static CALL_COUNT: AtomicUsize = AtomicUsize::new(0);
21
22// Phantom Fields
23
24enum PhantomEntry {
25  Enum {
26    attributes: Vec<Attribute>,
27    name: String,
28    start: u64,
29    end: u64,
30    enum_type: Ident,
31    variant_list: Vec<String>,
32  },
33  Integer {
34    attributes: Vec<Attribute>,
35    name: String,
36    start: u64,
37    end: u64,
38  },
39  Bool {
40    attributes: Vec<Attribute>,
41    name: String,
42    bit: u64,
43  },
44}
45
46struct PhantomFields {
47  self_member_type: Type,
48  entries: Vec<PhantomEntry>,
49}
50
51impl Parse for PhantomFields {
52  fn parse(input: ParseStream) -> Result<Self> {
53    let _ = input.parse::<Token![self]>()?;
54    let _ = input.parse::<Token![.]>()?;
55    let lit = input.parse::<LitInt>()?;
56    if lit.value() != 0 {
57      return Err(Error::new(lit.span(), "Currently only self.0 is supported"));
58    }
59    let _ = input.parse::<Token![:]>()?;
60    let self_member_type: Type = {
61      let tp = input.parse::<TypePath>()?;
62      let tp_end_string = match tp.path.segments.last().expect("no type given") {
63        syn::punctuated::Pair::Punctuated(path_segment, _colon2) => path_segment.ident.to_string(),
64        syn::punctuated::Pair::End(path_segment) => path_segment.ident.to_string(),
65      };
66      match tp_end_string.as_ref() {
67        "u8" | "i8" | "u16" | "i16" | "u32" | "i32" | "usize" | "isize" | "u64" | "i64" => Type::Path(tp),
68        _ => {
69          return Err(Error::new(tp.span(), format!("Unsupported target type: {:?}", tp_end_string)));
70        }
71      }
72    };
73    let _ = input.parse::<Token![,]>()?;
74    //
75    let mut entries: Vec<PhantomEntry> = vec![];
76    'entry_loop: loop {
77      if input.is_empty() {
78        break;
79      }
80      let attributes = input.call(Attribute::parse_outer)?;
81      let name = input.parse::<Ident>()?.to_string();
82      let _ = input.parse::<Token![:]>()?;
83      let start = input.parse::<LitInt>()?.value();
84      let lookahead = input.lookahead1();
85      if lookahead.peek(Token![,]) {
86        // bool entry
87        entries.push(PhantomEntry::Bool {
88          attributes,
89          name,
90          bit: start,
91        });
92        let _ = input.parse::<Token![,]>()?;
93        continue 'entry_loop;
94      } else if lookahead.peek(Token![-]) {
95        // spanning entry
96        let _ = input.parse::<Token![-]>()?;
97        let end = input.parse::<LitInt>()?.value();
98        let lookahead = input.lookahead1();
99        if lookahead.peek(Token![=]) {
100          // enum span
101          let _ = input.parse::<Token![=]>()?;
102          let enum_type = input.parse::<Ident>()?;
103          let mut variant_list = vec![];
104          let _ = input.parse::<Token![<]>()?;
105          'variant_gather_loop: loop {
106            variant_list.push(input.parse::<Ident>()?.to_string());
107            let lookahead = input.lookahead1();
108            if lookahead.peek(Token![>]) {
109              // end of list
110              let _ = input.parse::<Token![>]>()?;
111              break 'variant_gather_loop;
112            } else if lookahead.peek(Token![,]) {
113              // more to gather
114              let _ = input.parse::<Token![,]>()?;
115              continue 'variant_gather_loop;
116            } else {
117              return Err(lookahead.error());
118            }
119          }
120          entries.push(PhantomEntry::Enum {
121            attributes,
122            name,
123            start,
124            end,
125            enum_type,
126            variant_list,
127          });
128          let _ = input.parse::<Token![,]>()?;
129          continue 'entry_loop;
130        } else if lookahead.peek(Token![,]) {
131          // int span
132          entries.push(PhantomEntry::Integer {
133            attributes,
134            name,
135            start,
136            end,
137          });
138          let _ = input.parse::<Token![,]>()?;
139          continue 'entry_loop;
140        } else {
141          return Err(lookahead.error());
142        }
143      } else {
144        return Err(lookahead.error());
145      }
146    }
147    Ok(PhantomFields { self_member_type, entries })
148  }
149}
150
151#[proc_macro]
152pub fn phantom_fields(input: TokenStream) -> TokenStream {
153  let PhantomFields { self_member_type, entries } = parse_macro_input!(input as PhantomFields);
154
155  let mut out_text = String::new();
156
157  for entry in entries.into_iter() {
158    match entry {
159      PhantomEntry::Enum {
160        attributes,
161        name,
162        start,
163        end,
164        enum_type,
165        variant_list,
166      } => {
167        for attribute in attributes.into_iter() {
168          out_text.push_str(&format!("{}\n", TokenStream::from(quote! { #attribute })));
169        }
170        let mask_name = Ident::new(&format!("{}_MASK", name.to_uppercase()), Span::call_site());
171        let read_name = Ident::new(&name.clone(), Span::call_site());
172        let with_name = Ident::new(&format!("with_{}", name), Span::call_site());
173        let width = (end - start) + 1;
174        out_text.push_str(&format!(
175          "{}\n",
176          TokenStream::from(quote! {
177            #[allow(clippy::identity_op)]
178            pub const #mask_name: #self_member_type = ((1<<(#width))-1) << #start;
179
180            #[allow(missing_docs)]
181            pub fn #read_name(self) -> #enum_type
182          })
183        ));
184        out_text.push('{');
185        out_text.push_str(&format!(
186          "{}\n",
187          TokenStream::from(quote! {
188            match (self.0 & Self::#mask_name) >> #start
189          })
190        ));
191        out_text.push('{');
192        let enum_type_string = enum_type.to_string();
193        for (i, variant) in variant_list.iter().enumerate() {
194          out_text.push_str(&format!("{} => {}::{},\n", i, enum_type_string, variant));
195        }
196        if variant_list.len() == (1 << (width - 1)) {
197          out_text.push_str("_ => core::hint::unreachable_unchecked(),");
198        } else {
199          out_text.push_str("_ => unreachable!(),");
200        }
201        out_text.push_str("} }\n");
202        out_text.push_str(&format!(
203          "{}\n",
204          TokenStream::from(quote! {
205            #[allow(missing_docs)]
206            pub const fn #with_name(self, #read_name: #enum_type) -> Self {
207              Self((self.0 & !Self::#mask_name) | (((#read_name as #self_member_type) << #start) & Self::#mask_name))
208            }
209          })
210        ));
211      }
212      PhantomEntry::Integer {
213        attributes,
214        name,
215        start,
216        end,
217      } => {
218        for attribute in attributes.into_iter() {
219          out_text.push_str(&format!("{}\n", TokenStream::from(quote! { #attribute })));
220        }
221        let mask_name = Ident::new(&format!("{}_MASK", name.to_uppercase()), Span::call_site());
222        let read_name = Ident::new(&name.clone(), Span::call_site());
223        let with_name = Ident::new(&format!("with_{}", name), Span::call_site());
224        let width = (end - start) + 1;
225        out_text.push_str(&format!(
226          "{}\n",
227          TokenStream::from(quote! {
228            #[allow(clippy::identity_op)]
229            pub const #mask_name: #self_member_type = ((1<<(#width))-1) << #start;
230
231            #[allow(missing_docs)]
232            pub const fn #read_name(self) -> #self_member_type {
233              (self.0 & Self::#mask_name) >> #start
234            }
235
236            #[allow(missing_docs)]
237            pub const fn #with_name(self, #read_name: #self_member_type) -> Self {
238              Self((self.0 & !Self::#mask_name) | ((#read_name << #start) & Self::#mask_name))
239            }
240          })
241        ));
242      }
243      PhantomEntry::Bool { attributes, name, bit } => {
244        for attribute in attributes.into_iter() {
245          out_text.push_str(&format!("{}\n", TokenStream::from(quote! { #attribute })));
246        }
247        let const_name = Ident::new(&format!("{}_BIT", name.to_uppercase()), Span::call_site());
248        let read_name = Ident::new(&name.clone(), Span::call_site());
249        let with_name = Ident::new(&format!("with_{}", name), Span::call_site());
250        out_text.push_str(&format!(
251          "{}\n",
252          TokenStream::from(quote! {
253            #[allow(clippy::identity_op)]
254            pub const #const_name: #self_member_type = 1 << #bit;
255
256            #[allow(missing_docs)]
257            pub const fn #read_name(self) -> bool {
258              (self.0 & Self::#const_name) != 0
259            }
260
261            // https://graphics.stanford.edu/~seander/bithacks.html#ConditionalSetOrClearBitsWithoutBranching
262            #[allow(missing_docs)]
263            pub const fn #with_name(self, bit: bool) -> Self {
264              Self(self.0 ^ (((#self_member_type::wrapping_sub(0, bit as #self_member_type) ^ self.0) & Self::#const_name)))
265            }
266          })
267        ));
268      }
269    };
270  }
271
272  TokenStream::from_str(&out_text).map_err(|e| panic!("{:?}", e)).unwrap()
273}
274
275/// Attribute to declare the entry point of the program.
276///
277/// **IMPORTANT**: This attribute must appear exactly *once* in the dependency graph.  Also, if you
278/// are using Rust 1.30 the attribute must be used on a reachable item (i.e. there must be no
279/// private modules between the item and the root of the crate); if the item is in the root of the
280/// crate you'll be fine.  This reachability restriction doesn't apply to Rust 1.31 onwards.
281///
282/// The specified function will be called by the crt0 *after* RAM has been initialized.  It must
283/// take no arguments, and must be divergent (in other words, its type must be either `fn() -> !`
284/// or `unsafe fn() -> !`).  The program can't reference the entry point, much less invoke it.
285///
286/// # Example
287///
288/// ``` no_run
289/// # #![no_main]
290/// # use gba_proc_macro::entry;
291/// #[entry]
292/// fn main() -> ! {
293///     loop {
294///         /* .. */
295///     }
296/// }
297/// ```
298#[proc_macro_attribute]
299pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
300  let f = parse_macro_input!(input as ItemFn);
301
302  // check the function signature
303  let valid_signature = f.constness.is_none()
304    && f.vis == Visibility::Inherited
305    && f.abi.is_none()
306    && f.decl.inputs.is_empty()
307    && f.decl.generics.params.is_empty()
308    && f.decl.generics.where_clause.is_none()
309    && f.decl.variadic.is_none()
310    && match f.decl.output {
311      ReturnType::Default => false,
312      ReturnType::Type(_, ref ty) => match **ty {
313        Type::Never(_) => true,
314        _ => false,
315      },
316    };
317
318  if !valid_signature {
319    return parse::Error::new(f.span(), "`#[entry]` function must have signature `[unsafe] fn() -> !`")
320      .to_compile_error()
321      .into();
322  }
323
324  if !args.is_empty() {
325    return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
326      .to_compile_error()
327      .into();
328  }
329
330  // XXX should we blacklist other attributes?
331  let attrs = f.attrs;
332  let block = f.block;
333  let hash = random_ident();
334  let unsafety = f.unsafety;
335
336  quote!(
337      #[export_name = "main"]
338      #(#attrs)*
339      pub #unsafety fn #hash() -> ! #block
340  )
341  .into()
342}
343
344// Creates a random identifier
345fn random_ident() -> Ident {
346  let secs = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
347
348  let count: u64 = CALL_COUNT.fetch_add(1, Ordering::SeqCst) as u64;
349  let mut seed: [u8; 16] = [0; 16];
350
351  for (i, v) in seed.iter_mut().take(8).enumerate() {
352    *v = ((secs >> (i * 8)) & 0xFF) as u8
353  }
354
355  for (i, v) in seed.iter_mut().skip(8).enumerate() {
356    *v = ((count >> (i * 8)) & 0xFF) as u8
357  }
358
359  let mut rng = rand::rngs::SmallRng::from_seed(seed);
360  Ident::new(
361    &(0..16)
362      .map(|i| {
363        if i == 0 || rng.gen() {
364          ('a' as u8 + rng.gen::<u8>() % 25) as char
365        } else {
366          ('0' as u8 + rng.gen::<u8>() % 10) as char
367        }
368      })
369      .collect::<String>(),
370    Span::call_site(),
371  )
372}