1use proc_macro::TokenStream;
7use proc_macro2::Span;
8use quote::quote;
9use std::collections::HashSet;
10use std::iter;
11use syn::{
12 parse, parse_macro_input, spanned::Spanned, AttrStyle, Attribute, FnArg, Ident, Item, ItemFn,
13 ItemStatic, ReturnType, Stmt, Type, Visibility,
14};
15
16#[proc_macro_attribute]
17pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
18 let mut f = parse_macro_input!(input as ItemFn);
19
20 let valid_signature = f.sig.constness.is_none()
22 && f.vis == Visibility::Inherited
23 && f.sig.abi.is_none()
24 && f.sig.inputs.is_empty()
25 && f.sig.generics.params.is_empty()
26 && f.sig.generics.where_clause.is_none()
27 && f.sig.variadic.is_none()
28 && match f.sig.output {
29 ReturnType::Default => false,
30 ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)),
31 };
32
33 if !valid_signature {
34 return parse::Error::new(
35 f.span(),
36 "`#[entry]` function must have signature `[unsafe] fn() -> !`",
37 )
38 .to_compile_error()
39 .into();
40 }
41
42 if !args.is_empty() {
43 return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
44 .to_compile_error()
45 .into();
46 }
47
48 let (statics, stmts) = match extract_static_muts(f.block.stmts) {
50 Err(e) => return e.to_compile_error().into(),
51 Ok(x) => x,
52 };
53
54 f.sig.ident = Ident::new(&format!("__mips_rt_{}", f.sig.ident), Span::call_site());
55 f.sig.inputs.extend(statics.iter().map(|statik| {
56 let ident = &statik.ident;
57 let ty = &statik.ty;
58 let attrs = &statik.attrs;
59
60 syn::parse::<FnArg>(
63 quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &'static mut #ty).into(),
64 )
65 .unwrap()
66 }));
67 f.block.stmts = stmts;
68
69 let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site());
70 let ident = &f.sig.ident;
71
72 let resource_args = statics
73 .iter()
74 .map(|statik| {
75 let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone());
76 let ident = &statik.ident;
77 let ty = &statik.ty;
78 let expr = &statik.expr;
79 quote! {
80 #(#cfgs)*
81 {
82 #(#attrs)*
83 static mut #ident: #ty = #expr;
84 unsafe { &mut #ident }
85 }
86 }
87 })
88 .collect::<Vec<_>>();
89
90 if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Entry) {
91 return error;
92 }
93
94 let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
95
96 quote!(
97 #(#cfgs)*
98 #(#attrs)*
99 #[doc(hidden)]
100 #[export_name = "main"]
101 pub unsafe extern "C" fn #tramp_ident() {
102 #[allow(static_mut_refs)]
103 #ident(
104 #(#resource_args),*
105 )
106 }
107
108 #f
109 )
110 .into()
111}
112
113#[proc_macro_attribute]
114pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream {
115 let mut f = parse_macro_input!(input as ItemFn);
116
117 if !args.is_empty() {
118 return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
119 .to_compile_error()
120 .into();
121 }
122
123 if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Exception) {
124 return error;
125 }
126
127 let fspan = f.span();
128 let ident = f.sig.ident.clone();
129
130 let ident_s = ident.to_string();
131 let export_name = match &*ident_s {
132 "general_exception" => "_general_exception_handler",
133 "nmi" => "_nmi_handler",
134 _ => {
135 return parse::Error::new(ident.span(), "This is not a valid exception name")
136 .to_compile_error()
137 .into();
138 }
139 };
140
141 let valid_signature = f.sig.constness.is_none()
142 && f.vis == Visibility::Inherited
143 && f.sig.abi.is_none()
144 && f.sig.inputs.len() == 2
145 && f.sig.generics.params.is_empty()
146 && f.sig.generics.where_clause.is_none()
147 && f.sig.variadic.is_none()
148 && match f.sig.output {
149 ReturnType::Default => true,
150 ReturnType::Type(_, ref ty) => match **ty {
151 Type::Tuple(ref tuple) => tuple.elems.is_empty(),
152 Type::Never(..) => true,
153 _ => false,
154 },
155 };
156
157 if !valid_signature {
158 return parse::Error::new(
159 fspan,
160 "`#[exception]`exception handlers must have signature `unsafe fn(u32, u32) [-> !]`",
161 )
162 .to_compile_error()
163 .into();
164 }
165
166 f.sig.ident = Ident::new(&format!("__mips_rt_{}", f.sig.ident), Span::call_site());
167 let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site());
168 let ident = &f.sig.ident;
169
170 let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
171
172 quote!(
173 #(#cfgs)*
174 #(#attrs)*
175 #[doc(hidden)]
176 #[export_name = #export_name]
177 pub unsafe extern "C" fn #tramp_ident(cp0_cause: u32, cp0_status: u32) {
178 #ident(cp0_cause, cp0_status)
179 }
180
181 #f
182 )
183 .into()
184}
185
186#[proc_macro_attribute]
187pub fn interrupt(args: TokenStream, input: TokenStream) -> TokenStream {
188 let mut f: ItemFn = syn::parse(input).expect("`#[interrupt]` must be applied to a function");
189
190 if !args.is_empty() {
191 return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
192 .to_compile_error()
193 .into();
194 }
195
196 let fspan = f.span();
197 let ident = f.sig.ident.clone();
198 let ident_s = ident.to_string();
199
200 let valid_signature = f.sig.constness.is_none()
203 && f.vis == Visibility::Inherited
204 && f.sig.abi.is_none()
205 && f.sig.inputs.is_empty()
206 && f.sig.generics.params.is_empty()
207 && f.sig.generics.where_clause.is_none()
208 && f.sig.variadic.is_none()
209 && match f.sig.output {
210 ReturnType::Default => true,
211 ReturnType::Type(_, ref ty) => match **ty {
212 Type::Tuple(ref tuple) => tuple.elems.is_empty(),
213 Type::Never(..) => true,
214 _ => false,
215 },
216 };
217
218 if !valid_signature {
219 return parse::Error::new(
220 fspan,
221 "`#[interrupt]` handlers must have signature `[unsafe] fn() [-> !]`",
222 )
223 .to_compile_error()
224 .into();
225 }
226
227 let (statics, stmts) = match extract_static_muts(f.block.stmts.iter().cloned()) {
228 Err(e) => return e.to_compile_error().into(),
229 Ok(x) => x,
230 };
231
232 f.sig.ident = Ident::new(&format!("__mips_rt_{}", f.sig.ident), Span::call_site());
233 f.sig.inputs.extend(statics.iter().map(|statik| {
234 let ident = &statik.ident;
235 let ty = &statik.ty;
236 let attrs = &statik.attrs;
237 syn::parse::<FnArg>(quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &mut #ty).into())
238 .unwrap()
239 }));
240 f.block.stmts = iter::once(
241 syn::parse2(quote! {{
242 interrupt::Interrupt::#ident;
244 }})
245 .unwrap(),
246 )
247 .chain(stmts)
248 .collect();
249
250 let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site());
251 let ident = &f.sig.ident;
252
253 let resource_args = statics
254 .iter()
255 .map(|statik| {
256 let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone());
257 let ident = &statik.ident;
258 let ty = &statik.ty;
259 let expr = &statik.expr;
260 quote! {
261 #(#cfgs)*
262 {
263 #(#attrs)*
264 static mut #ident: #ty = #expr;
265 unsafe { &mut #ident }
266 }
267 }
268 })
269 .collect::<Vec<_>>();
270
271 if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Interrupt) {
272 return error;
273 }
274
275 let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
276
277 quote!(
278 #(#cfgs)*
279 #(#attrs)*
280 #[doc(hidden)]
281 #[export_name = #ident_s]
282 pub unsafe extern "C" fn #tramp_ident() {
283 #[allow(static_mut_refs)]
284 #ident(
285 #(#resource_args),*
286 )
287 }
288
289 #f
290 )
291 .into()
292}
293
294#[proc_macro_attribute]
295pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream {
296 let f = parse_macro_input!(input as ItemFn);
297
298 let valid_signature = f.sig.constness.is_none()
300 && f.vis == Visibility::Inherited
301 && f.sig.unsafety.is_some()
302 && f.sig.abi.is_none()
303 && f.sig.inputs.is_empty()
304 && f.sig.generics.params.is_empty()
305 && f.sig.generics.where_clause.is_none()
306 && f.sig.variadic.is_none()
307 && match f.sig.output {
308 ReturnType::Default => true,
309 ReturnType::Type(_, ref ty) => match **ty {
310 Type::Tuple(ref tuple) => tuple.elems.is_empty(),
311 _ => false,
312 },
313 };
314
315 if !valid_signature {
316 return parse::Error::new(
317 f.span(),
318 "`#[pre_init]` function must have signature `unsafe fn()`",
319 )
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 if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::PreInit) {
331 return error;
332 }
333
334 let attrs = f.attrs;
336 let ident = f.sig.ident;
337 let block = f.block;
338
339 quote!(
340 #[export_name = "__pre_init"]
341 #[allow(missing_docs)] #(#attrs)*
343 pub unsafe fn #ident() #block
344 )
345 .into()
346}
347
348fn extract_static_muts(
350 stmts: impl IntoIterator<Item = Stmt>,
351) -> Result<(Vec<ItemStatic>, Vec<Stmt>), parse::Error> {
352 let mut istmts = stmts.into_iter();
353
354 let mut seen = HashSet::new();
355 let mut statics = vec![];
356 let mut stmts = vec![];
357 for stmt in istmts.by_ref() {
358 match stmt {
359 Stmt::Item(Item::Static(var)) => match var.mutability {
360 syn::StaticMutability::Mut(_) => {
361 if seen.contains(&var.ident) {
362 return Err(parse::Error::new(
363 var.ident.span(),
364 format!("the name `{}` is defined multiple times", var.ident),
365 ));
366 }
367
368 seen.insert(var.ident.clone());
369 statics.push(var);
370 }
371 _ => stmts.push(Stmt::Item(Item::Static(var))),
372 },
373 _ => {
374 stmts.push(stmt);
375 break;
376 }
377 }
378 }
379
380 stmts.extend(istmts);
381
382 Ok((statics, stmts))
383}
384
385fn extract_cfgs(attrs: Vec<Attribute>) -> (Vec<Attribute>, Vec<Attribute>) {
386 let mut cfgs = vec![];
387 let mut not_cfgs = vec![];
388
389 for attr in attrs {
390 if eq(&attr, "cfg") {
391 cfgs.push(attr);
392 } else {
393 not_cfgs.push(attr);
394 }
395 }
396
397 (cfgs, not_cfgs)
398}
399
400enum WhiteListCaller {
401 Entry,
402 Exception,
403 Interrupt,
404 PreInit,
405}
406
407fn check_attr_whitelist(attrs: &[Attribute], caller: WhiteListCaller) -> Result<(), TokenStream> {
408 let whitelist = &[
409 "doc",
410 "link_section",
411 "cfg",
412 "allow",
413 "warn",
414 "deny",
415 "forbid",
416 "cold",
417 "naked",
418 ];
419
420 'o: for attr in attrs {
421 for val in whitelist {
422 if eq(attr, val) {
423 continue 'o;
424 }
425 }
426
427 let err_str = match caller {
428 WhiteListCaller::Entry => "this attribute is not allowed on a mips-rt entry point",
429 WhiteListCaller::Exception => {
430 "this attribute is not allowed on an exception handler controlled by mips-rt"
431 }
432 WhiteListCaller::Interrupt => {
433 "this attribute is not allowed on an interrupt handler controlled by mips-rt"
434 }
435 WhiteListCaller::PreInit => {
436 "this attribute is not allowed on a pre-init controlled by mips-rt"
437 }
438 };
439
440 return Err(parse::Error::new(attr.span(), err_str)
441 .to_compile_error()
442 .into());
443 }
444
445 Ok(())
446}
447
448fn eq(attr: &Attribute, name: &str) -> bool {
450 attr.style == AttrStyle::Outer && attr.path().is_ident(name)
451}