msp430_rt_macros/lib.rs
1extern crate proc_macro;
2extern crate proc_macro2;
3extern crate quote;
4extern crate rand;
5extern crate rand_xoshiro;
6extern crate syn;
7
8use proc_macro::TokenStream;
9use std::{
10 collections::HashSet,
11 sync::atomic::{AtomicUsize, Ordering},
12 time::{SystemTime, UNIX_EPOCH},
13};
14
15use proc_macro2::Span;
16use quote::{quote, quote_spanned};
17use rand::Rng;
18use rand_xoshiro::rand_core::SeedableRng;
19use syn::{
20 parenthesized,
21 parse::{self, Parse},
22 parse_macro_input,
23 punctuated::Punctuated,
24 spanned::Spanned,
25 FnArg, Ident, Item, ItemFn, ItemStatic, Pat, PatIdent, PathArguments, PathSegment, ReturnType,
26 Stmt, Token, Type, TypePath, Visibility,
27};
28
29/// Attribute to declare the entry point of the program
30///
31/// The specified function will be called by the reset handler *after* RAM has been initialized.
32///
33/// The type of the specified function must be `[unsafe] fn([<name>: CriticalSection]) -> !` (never
34/// ending function), where the `CriticalSection` argument is optional.
35///
36/// # Properties
37///
38/// The entry point will be called by the reset handler. The program can't reference to the entry
39/// point, much less invoke it.
40///
41/// `static mut` variables declared within the entry point are safe to access. The compiler can't
42/// prove this is safe so the attribute will help by making a transformation to the source code: for
43/// this reason a variable like `static mut FOO: u32` will become `let FOO: &'static mut u32;`. Note
44/// that `&'static mut` references have move semantics.
45///
46/// ## Examples
47///
48/// - Simple entry point
49///
50/// ``` no_run
51/// # #![no_main]
52/// # use msp430_rt_macros::entry;
53/// #[entry]
54/// fn main() -> ! {
55/// loop {
56/// /* .. */
57/// }
58/// }
59/// ```
60///
61/// - `static mut` variables local to the entry point are safe to modify.
62///
63/// ``` no_run
64/// # #![no_main]
65/// # use msp430_rt_macros::entry;
66/// #[entry]
67/// fn main(_cs: CriticalSection) -> ! {
68/// static mut FOO: u32 = 0;
69///
70/// let foo: &'static mut u32 = FOO;
71/// assert_eq!(*foo, 0);
72/// *foo = 1;
73/// assert_eq!(*foo, 1);
74///
75/// loop {
76/// /* .. */
77/// }
78/// }
79/// ```
80///
81/// # Pre-entry Interrupt Enable
82///
83/// If the argument `interrupt_enable` is passed into the macro, interrupts will be enabled
84/// globally before the entry function runs. If this is enabled then the entry function will no
85/// longer accept `CriticalSection` as a parameter, since that it will be unsound.
86///
87/// The macro can also take arguments of the form `interrupt_enable(pre_interrupt = <init>)`, where
88/// `init` is the name of a function with the signature `fn(cs: CriticalSection) -> <Type>`. The
89/// entry function can then optionally take a parameter of `Type`. This makes `init` run before
90/// interrupts are enabled and possibly pass its return value into the entry function, allowing
91/// pre-interrupt initialization to be done.
92///
93/// Note that a function marked with the entry attribute is allowed to take no input parameters
94/// even if `init` returns a value, due to implementation details. To reduce code size, it is
95/// strongly recommended to put `#[inline(always)]` on `init` if it's used nowhere else.
96///
97/// ## Examples
98///
99/// - Enable interrupts before entry
100///
101/// ``` no_run
102/// # #![no_main]
103/// # use msp430_rt_macros::entry;
104/// #[entry(interrupt_enable)]
105/// fn main() -> ! {
106/// /* interrupts now enabled */
107/// loop {}
108/// }
109/// ```
110///
111/// - Pre-interrupt initialization
112///
113/// ``` no_run
114/// # #![no_main]
115/// # use msp430_rt_macros::entry;
116/// use msp430::interrupt::CriticalSection;
117///
118/// # struct Hal;
119/// #[inline(always)]
120/// fn init(cs: CriticalSection) -> Hal {
121/// /* initialize hardware */
122/// # Hal
123/// }
124///
125/// #[entry(interrupt_enable(pre_interrupt = init))]
126/// fn main(hal: Hal) -> ! {
127/// loop {
128/// /* do something with hal */
129/// }
130/// }
131/// ```
132///
133/// - Pre-interrupt initialization with no return
134///
135/// ``` no_run
136/// # #![no_main]
137/// # use msp430_rt_macros::entry;
138/// use msp430::interrupt::CriticalSection;
139///
140/// #[inline(always)]
141/// fn arg(cs: CriticalSection) {
142/// /* initialize */
143/// }
144///
145/// #[entry(interrupt_enable(pre_interrupt = arg))]
146/// fn main() -> ! {
147/// loop {}
148/// }
149/// ```
150///
151/// ## Note
152///
153/// The `CriticalSection`s passed into the entry and the pre-interrupt functions have their
154/// lifetimes restrained to their respective functions. Attempting to pass the `CriticalSection`
155/// outside its scope fails with a `borrowed value does not live long enough` error.
156#[proc_macro_attribute]
157pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
158 let interrupt_enable = if args.is_empty() {
159 None
160 } else {
161 Some(parse_macro_input!(args as EntryInterruptEnable))
162 };
163
164 let f = parse_macro_input!(input as ItemFn);
165
166 // check the function signature
167 let valid_signature = f.sig.constness.is_none()
168 && f.vis == Visibility::Inherited
169 && f.sig.abi.is_none()
170 && f.sig.generics.params.is_empty()
171 && f.sig.generics.where_clause.is_none()
172 && f.sig.variadic.is_none()
173 && match f.sig.output {
174 ReturnType::Default => false,
175 ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)),
176 };
177
178 let pair = match &interrupt_enable {
179 Some(interrupt_enable) => interrupt_enable.extract_init_arg(&f.sig.inputs),
180 None => extract_critical_section_arg(&f.sig.inputs),
181 };
182
183 if let (true, Ok(ParamArgPair { fn_param, fn_arg })) = (valid_signature, pair) {
184 // XXX should we blacklist other attributes?
185 let attrs = f.attrs;
186 let unsafety = f.sig.unsafety;
187 let hash = random_ident();
188 let (statics, stmts) = match extract_static_muts(f.block.stmts) {
189 Err(e) => return e.to_compile_error().into(),
190 Ok(x) => x,
191 };
192
193 let vars = statics
194 .into_iter()
195 .map(|var| {
196 let attrs = var.attrs;
197 let ident = var.ident;
198 let ty = var.ty;
199 let expr = var.expr;
200
201 quote!(
202 #[allow(non_snake_case)]
203 let #ident: &'static mut #ty = unsafe {
204 #(#attrs)*
205 static mut #ident: #ty = #expr;
206
207 &mut #ident
208 };
209 )
210 })
211 .collect::<Vec<_>>();
212
213 // Only generate the argument if fn_param exists, to handle the case where the argument
214 // expression does exist but the entry function doesn't accept any args
215 let arg_ident = fn_param
216 .as_ref()
217 .map(|_| Ident::new("arg", Span::mixed_site()));
218 let arg_def = fn_arg
219 .as_ref()
220 .map(|arg| quote_spanned!(Span::mixed_site()=> let arg = #arg; ));
221
222 quote!(
223 #[export_name = "main"]
224 #(#attrs)*
225 pub #unsafety fn #hash() -> ! {
226 #[inline(always)]
227 #unsafety fn #hash<'a>(#fn_param) -> ! {
228 #(#vars)*
229 #(#stmts)*
230 }
231 #arg_def
232 { #hash(#arg_ident) }
233 }
234 )
235 .into()
236 } else {
237 let err = match interrupt_enable {
238 None => parse::Error::new(
239 f.sig.span(),
240 "`#[entry]` function must have signature `[unsafe] fn([<ident> : CriticalSection]) -> !`",
241 ),
242 Some(EntryInterruptEnable { pre_interrupt: None }) => parse::Error::new(
243 f.sig.span(),
244 "`#[entry(interrupt_enable)]` function must have signature `[unsafe] fn() -> !`",
245 ),
246 Some(EntryInterruptEnable { pre_interrupt: Some(ident) }) => parse::Error::new(
247 f.sig.span(),
248 format!("`#[entry(interrupt_enable(pre_interrupt = {fname}))]` function must have signature `[unsafe] fn([<ident> : <Type>]) -> !`, where <Type> is the return value of {fname}", fname = ident)
249 ),
250 };
251 err.to_compile_error().into()
252 }
253}
254
255#[derive(Default)]
256struct ParamArgPair {
257 fn_param: Option<proc_macro2::TokenStream>,
258 fn_arg: Option<proc_macro2::TokenStream>,
259}
260
261struct EntryInterruptEnable {
262 pre_interrupt: Option<Ident>,
263}
264
265impl Parse for EntryInterruptEnable {
266 fn parse(input: parse::ParseStream) -> syn::Result<Self> {
267 let interrupt_enable = input.parse::<Ident>()?;
268 if interrupt_enable != "interrupt_enable" {
269 return Err(parse::Error::new(
270 interrupt_enable.span(),
271 "expected `interrupt_enable` or no arguments at all",
272 ));
273 }
274 let pre_interrupt = if input.peek(syn::token::Paren) {
275 let inner;
276 parenthesized!(inner in input);
277 let pre_interrupt = inner.parse::<Ident>()?;
278 if pre_interrupt != "pre_interrupt" {
279 return Err(parse::Error::new(
280 pre_interrupt.span(),
281 "expected `pre_interrupt`",
282 ));
283 }
284 inner.parse::<syn::token::Eq>()?;
285 Some(inner.parse::<Ident>()?)
286 } else {
287 None
288 };
289
290 Ok(EntryInterruptEnable { pre_interrupt })
291 }
292}
293
294impl EntryInterruptEnable {
295 fn extract_init_arg(&self, list: &Punctuated<FnArg, Token![,]>) -> Result<ParamArgPair, ()> {
296 if let Some(fn_name) = &self.pre_interrupt {
297 let hash = random_ident();
298 let fn_arg = Some(quote_spanned!(Span::mixed_site()=> {
299 let cs = unsafe { msp430::interrupt::CriticalSection::new() };
300
301 // This struct forces the lifetime of the CriticalSection to match the lifetime of
302 // the reference. Since the reference lifetime is restricted to this scope, the
303 // compiler has to constrain the lifetime of the CriticalSection as well,
304 // preventing the CriticalSection from being leaked as a return value.
305 #[allow(non_camel_case_types)]
306 struct #hash<'a>(&'a msp430::interrupt::CriticalSection<'a>);
307 let arg = #fn_name(*#hash(&cs).0);
308
309 unsafe { msp430::interrupt::enable() };
310 arg
311 }));
312
313 if let Some(first) = list.first() {
314 if let FnArg::Typed(pat_type) = first {
315 // Case where pre-init exists and entry takes a param
316 return Ok(ParamArgPair {
317 fn_param: Some(quote! { #pat_type }),
318 fn_arg,
319 });
320 }
321 } else {
322 // Case where pre-init exists but entry takes no params
323 return Ok(ParamArgPair {
324 fn_param: None,
325 fn_arg,
326 });
327 }
328 } else if list.is_empty() {
329 // Case where pre-init doesn't exist and entry takes no params
330 return Ok(ParamArgPair {
331 fn_param: None,
332 fn_arg: Some(quote!({
333 unsafe { msp430::interrupt::enable() };
334 })),
335 });
336 }
337 Err(())
338 }
339}
340
341/// Attribute to declare an interrupt handler
342///
343/// When the `device` feature is disabled this attribute can only be used to override the
344/// DefaultHandler.
345///
346/// When the `device` feature is enabled this attribute can be used to override other interrupt
347/// handlers but only when imported from a PAC (Peripheral Access Crate) crate which re-exports it.
348/// Importing this attribute from the `msp430-rt` crate and using it on a function will result in a
349/// compiler error.
350///
351/// # Syntax
352///
353/// ``` ignore
354/// extern crate device;
355///
356/// // the attribute comes from the device crate not from msp430-rt
357/// use device::interrupt;
358///
359/// #[interrupt]
360/// // Pass in optional CriticalSection
361/// fn USART1(cs: CriticalSection) {
362/// // ..
363/// }
364/// ```
365///
366/// where the name of the function must be `DefaultHandler` or one of the device interrupts.
367///
368/// # Usage
369///
370/// `#[interrupt] fn Name(..` overrides the default handler for the interrupt with the given `Name`.
371/// These handlers must have signature `[unsafe] fn([<name>: CriticalSection]) [-> !]`. It's
372/// possible to add state to these handlers by declaring `static mut` variables at the beginning of
373/// the body of the function. These variables will be safe to access from the function body.
374///
375/// If the interrupt handler has not been overridden it will be dispatched by the default interrupt
376/// handler (`DefaultHandler`).
377///
378/// `#[interrupt] fn DefaultHandler(..` can be used to override the default interrupt handler. When
379/// not overridden `DefaultHandler` defaults to an infinite loop.
380///
381/// `#[interrupt(wake_cpu)]` additionally returns the CPU to active mode after the interrupt
382/// returns. This cannot be done by naively writing to the status register, as the status register
383/// contents are pushed to the stack before an interrupt begins and this value is loaded back into
384/// the status register after an interrupt completes, effectively making any changes to the status
385/// register within an interrupt temporary.
386/// Using the `wake_cpu` variant incurs a delay of two instructions (6 cycles) before the interrupt
387/// handler begins.
388/// The following status register bits are cleared: SCG1, SCG0, OSC_OFF and CPU_OFF.
389///
390/// # Properties
391///
392/// Interrupts handlers can only be called by the hardware. Other parts of the program can't refer
393/// to the interrupt handlers, much less invoke them as if they were functions.
394///
395/// `static mut` variables declared within an interrupt handler are safe to access and can be used
396/// to preserve state across invocations of the handler. The compiler can't prove this is safe so
397/// the attribute will help by making a transformation to the source code: for this reason a
398/// variable like `static mut FOO: u32` will become `let FOO: &mut u32;`.
399///
400/// ## Examples
401///
402/// - Using state within an interrupt handler
403///
404/// ``` ignore
405/// extern crate device;
406///
407/// use device::interrupt;
408///
409/// #[interrupt]
410/// fn TIM2() {
411/// static mut COUNT: i32 = 0;
412///
413/// // `COUNT` is safe to access and has type `&mut i32`
414/// *COUNT += 1;
415///
416/// println!("{}", COUNT);
417/// }
418/// ```
419///
420/// ## Note
421///
422/// The `CriticalSection` passed into the interrupt function has its lifetime restrained to the
423/// function scope. Attempting to pass the `CriticalSection` outside its scope fails with a
424/// `borrowed value does not live long enough` error.
425#[proc_macro_attribute]
426pub fn interrupt(args: TokenStream, input: TokenStream) -> TokenStream {
427 let f: ItemFn = syn::parse(input).expect("`#[interrupt]` must be applied to a function");
428
429 let maybe_arg = parse_macro_input::parse::<Option<Ident>>(args.clone());
430
431 let wake_cpu = match maybe_arg {
432 Ok(None) => false,
433 Ok(Some(ident)) if ident == "wake_cpu" => true,
434 Ok(Some(_)) => {
435 return parse::Error::new(
436 Span::call_site(),
437 "this attribute accepts only 'wake_cpu' as an argument",
438 )
439 .to_compile_error()
440 .into()
441 }
442 Err(e) => return e.into_compile_error().into(),
443 };
444
445 let fspan = f.sig.span();
446 let ident = f.sig.ident;
447
448 let check = if ident == "DefaultHandler" {
449 None
450 } else if cfg!(feature = "device") {
451 Some(quote!(interrupt::#ident;))
452 } else {
453 return parse::Error::new(
454 ident.span(),
455 "only the DefaultHandler can be overridden when the `device` feature is disabled",
456 )
457 .to_compile_error()
458 .into();
459 };
460
461 // XXX should we blacklist other attributes?
462 let attrs = f.attrs;
463 let block = f.block;
464 let stmts = block.stmts;
465 let unsafety = f.sig.unsafety;
466
467 let valid_signature = f.sig.constness.is_none()
468 && f.vis == Visibility::Inherited
469 && f.sig.abi.is_none()
470 && f.sig.generics.params.is_empty()
471 && f.sig.generics.where_clause.is_none()
472 && f.sig.variadic.is_none()
473 && match f.sig.output {
474 ReturnType::Default => true,
475 ReturnType::Type(_, ref ty) => match **ty {
476 Type::Tuple(ref tuple) => tuple.elems.is_empty(),
477 Type::Never(..) => true,
478 _ => false,
479 },
480 };
481
482 let pair = extract_critical_section_arg(&f.sig.inputs);
483
484 if let (true, Ok(ParamArgPair { fn_arg, fn_param })) = (valid_signature, pair) {
485 let (statics, stmts) = match extract_static_muts(stmts) {
486 Err(e) => return e.to_compile_error().into(),
487 Ok(x) => x,
488 };
489
490 let vars = statics
491 .into_iter()
492 .map(|var| {
493 let attrs = var.attrs;
494 let ident = var.ident;
495 let ty = var.ty;
496 let expr = var.expr;
497
498 quote!(
499 #[allow(non_snake_case)]
500 let #ident: &mut #ty = unsafe {
501 #(#attrs)*
502 static mut #ident: #ty = #expr;
503
504 &mut #ident
505 };
506 )
507 })
508 .collect::<Vec<_>>();
509
510 let output = f.sig.output;
511 let hash = random_ident();
512 let ident = ident.to_string();
513 if wake_cpu {
514 quote!(
515 #[export_name = #ident]
516 #(#attrs)*
517 #[unsafe(naked)]
518 unsafe extern "msp430-interrupt" fn #hash() {
519 #[inline(always)]
520 #unsafety extern "msp430-interrupt" fn #hash<'a>(#fn_param) #output {
521 #check
522 #(#vars)*
523 #(#stmts)*
524 }
525 {
526 // Clear SCG1, SCG0, OSC_OFF, CPU_OFF in saved copy of SR register on stack
527 const MASK: u8 = (1<<7) + (1<<6) + (1<<5) + (1<<4);
528 core::arch::naked_asm!(
529 "bic.b #{mask}, 0(r1)",
530 "jmp {inner}",
531 inner = sym #hash,
532 mask = const MASK
533 );
534 }
535 }
536 )
537 } else {
538 quote!(
539 #[export_name = #ident]
540 #(#attrs)*
541 #unsafety extern "msp430-interrupt" fn #hash() {
542 #check
543
544 #[inline(always)]
545 #unsafety fn #hash<'a>(#fn_param) #output {
546 #(#vars)*
547 #(#stmts)*
548 }
549 { #hash(#fn_arg) }
550 }
551 )
552 }.into()
553 } else {
554 parse::Error::new(
555 fspan,
556 "`#[interrupt]` handlers must have signature `[unsafe] fn([<name>: CriticalSection]) [-> !]`",
557 )
558 .to_compile_error()
559 .into()
560 }
561}
562
563/// Attribute to mark which function will be called at the beginning of the reset handler.
564///
565/// **IMPORTANT**: This attribute can appear at most *once* in the dependency graph.
566///
567/// The function must have the signature of `unsafe fn()`.
568///
569/// The function passed will be called before static variables are initialized. Any access of static
570/// variables will result in undefined behavior.
571///
572/// ## Examples
573///
574/// ```
575/// # use msp430_rt_macros::pre_init;
576/// #[pre_init]
577/// unsafe fn before_main() {
578/// // do something here
579/// }
580///
581/// # fn main() {}
582/// ```
583#[proc_macro_attribute]
584pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream {
585 let f = parse_macro_input!(input as ItemFn);
586
587 // check the function signature
588 let valid_signature = f.sig.constness.is_none()
589 && f.vis == Visibility::Inherited
590 && f.sig.unsafety.is_some()
591 && f.sig.abi.is_none()
592 && f.sig.inputs.is_empty()
593 && f.sig.generics.params.is_empty()
594 && f.sig.generics.where_clause.is_none()
595 && f.sig.variadic.is_none()
596 && match f.sig.output {
597 ReturnType::Default => true,
598 ReturnType::Type(_, ref ty) => match **ty {
599 Type::Tuple(ref tuple) => tuple.elems.is_empty(),
600 _ => false,
601 },
602 };
603
604 if !valid_signature {
605 return parse::Error::new(
606 f.sig.span(),
607 "`#[pre_init]` function must have signature `unsafe fn()`",
608 )
609 .to_compile_error()
610 .into();
611 }
612
613 if !args.is_empty() {
614 return parse::Error::new(Span::call_site(), "this attribute accepts no arguments")
615 .to_compile_error()
616 .into();
617 }
618
619 // XXX should we blacklist other attributes?
620 let attrs = f.attrs;
621 let ident = f.sig.ident;
622 let block = f.block;
623
624 quote!(
625 #[export_name = "__pre_init"]
626 #(#attrs)*
627 pub unsafe fn #ident() #block
628 )
629 .into()
630}
631
632// Parses an optional `<name>: CriticalSection` from a list of function arguments.
633// Additional arguments are considered invalid
634fn extract_critical_section_arg(list: &Punctuated<FnArg, Token![,]>) -> Result<ParamArgPair, ()> {
635 let num_args = list.len();
636 if num_args == 0 {
637 return Ok(ParamArgPair::default());
638 } else if num_args == 1 {
639 if let FnArg::Typed(pat_type) = list.first().unwrap() {
640 if let (
641 Pat::Ident(PatIdent {
642 ident: name,
643 by_ref: None,
644 mutability: None,
645 subpat: None,
646 attrs,
647 }),
648 Type::Path(TypePath { qself: None, path }),
649 _,
650 [],
651 ) = (
652 &*pat_type.pat,
653 &*pat_type.ty,
654 pat_type.colon_token,
655 &*pat_type.attrs,
656 ) {
657 if path.segments.len() == 1 && attrs.is_empty() {
658 let seg = path.segments.first().unwrap();
659 if matches!(
660 seg,
661 PathSegment {
662 ident,
663 arguments: PathArguments::None,
664 } if ident == "CriticalSection"
665 ) {
666 return Ok(ParamArgPair {
667 fn_param: Some(
668 quote! { #name: msp430::interrupt::CriticalSection<'a> },
669 ),
670 fn_arg: Some(
671 quote! { unsafe { msp430::interrupt::CriticalSection::new() } },
672 ),
673 });
674 }
675 }
676 }
677 }
678 }
679 Err(())
680}
681
682// Creates a random identifier
683fn random_ident() -> Ident {
684 static CALL_COUNT: AtomicUsize = AtomicUsize::new(0);
685
686 let secs = SystemTime::now()
687 .duration_since(UNIX_EPOCH)
688 .unwrap()
689 .as_secs();
690
691 let count: u64 = CALL_COUNT.fetch_add(1, Ordering::SeqCst) as u64;
692 let mut seed: [u8; 16] = [0; 16];
693
694 for (i, v) in seed.iter_mut().take(8).enumerate() {
695 *v = ((secs >> (i * 8)) & 0xFF) as u8
696 }
697
698 for (i, v) in seed.iter_mut().skip(8).enumerate() {
699 *v = ((count >> (i * 8)) & 0xFF) as u8
700 }
701
702 let mut rng = rand_xoshiro::Xoshiro128PlusPlus::from_seed(seed);
703 Ident::new(
704 &(0..16)
705 .map(|i| {
706 if i == 0 || rng.gen() {
707 (b'a' + rng.gen::<u8>() % 25) as char
708 } else {
709 (b'0' + rng.gen::<u8>() % 10) as char
710 }
711 })
712 .collect::<String>(),
713 Span::call_site(),
714 )
715}
716
717/// Extracts `static mut` vars from the beginning of the given statements
718fn extract_static_muts(stmts: Vec<Stmt>) -> Result<(Vec<ItemStatic>, Vec<Stmt>), parse::Error> {
719 let mut istmts = stmts.into_iter();
720
721 let mut seen = HashSet::new();
722 let mut statics = vec![];
723 let mut stmts = vec![];
724 for stmt in istmts.by_ref() {
725 match stmt {
726 Stmt::Item(Item::Static(var)) => {
727 if var.mutability.is_some() {
728 if seen.contains(&var.ident) {
729 return Err(parse::Error::new(
730 var.ident.span(),
731 format!("the name `{}` is defined multiple times", var.ident),
732 ));
733 }
734
735 seen.insert(var.ident.clone());
736 statics.push(var);
737 } else {
738 stmts.push(Stmt::Item(Item::Static(var)));
739 }
740 }
741 _ => {
742 stmts.push(stmt);
743 break;
744 }
745 }
746 }
747
748 stmts.extend(istmts);
749
750 Ok((statics, stmts))
751}