diff options
author | 2018-09-06 00:44:19 +0000 | |
---|---|---|
committer | 2018-09-06 00:44:19 +0000 | |
commit | 7854e96f69f98570504c701ae860175efc7a25d9 (patch) | |
tree | eaf897d43263606e24b6cca9f5f6620498648dc6 | |
parent | 0fb051055a0340ad6c5b59d18183c260468e455f (diff) | |
parent | 31713aba3f4a4aacd55d12b8a3435334ae61986a (diff) | |
download | cortex-m-7854e96f69f98570504c701ae860175efc7a25d9.tar.gz cortex-m-7854e96f69f98570504c701ae860175efc7a25d9.tar.zst cortex-m-7854e96f69f98570504c701ae860175efc7a25d9.zip |
Merge #90
90: turn macros into attributes r=adamgreig a=japaric
This is a PoC implementation of RFC #82. Assuming that we are OK with this implementation we should
add some compile fail tests (e.g. using a function with the wrong signature as the entry point)
before landing this.
Look at the diff of the examples to get an overview of the changes.
cc @rust-embedded/cortex-m
Co-authored-by: Jorge Aparicio <jorge@japaric.io>
45 files changed, 1205 insertions, 319 deletions
diff --git a/cortex-m-rt/.gitignore b/cortex-m-rt/.gitignore index c71d6db..d8748d6 100644 --- a/cortex-m-rt/.gitignore +++ b/cortex-m-rt/.gitignore @@ -1,4 +1,5 @@ **/*.rs.bk +.#* Cargo.lock bin/*.after bin/*.before diff --git a/cortex-m-rt/.travis.yml b/cortex-m-rt/.travis.yml index caa29ef..fd45f63 100644 --- a/cortex-m-rt/.travis.yml +++ b/cortex-m-rt/.travis.yml @@ -3,24 +3,29 @@ language: rust matrix: include: - env: TARGET=x86_64-unknown-linux-gnu - rust: stable - if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - - - env: TARGET=thumbv6m-none-eabi - rust: stable - if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - - - env: TARGET=thumbv7m-none-eabi - rust: stable - if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - - - env: TARGET=thumbv7em-none-eabi - rust: stable + # TODO switch to 1.30-beta + rust: nightly if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - - env: TARGET=thumbv7em-none-eabihf - rust: stable - if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) + # TODO enable when 1.30-beta is out + # - env: TARGET=thumbv6m-none-eabi + # rust: beta + # if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) + + # TODO enable when 1.30-beta is out + # - env: TARGET=thumbv7m-none-eabi + # rust: beta + # if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) + + # TODO enable when 1.30-beta is out + # - env: TARGET=thumbv7em-none-eabi + # rust: beta + # if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) + + # TODO enable when 1.30-beta is out + # - env: TARGET=thumbv7em-none-eabihf + # rust: beta + # if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - env: TARGET=thumbv6m-none-eabi rust: nightly diff --git a/cortex-m-rt/Cargo.toml b/cortex-m-rt/Cargo.toml index 0dfbaf7..9cf2c14 100644 --- a/cortex-m-rt/Cargo.toml +++ b/cortex-m-rt/Cargo.toml @@ -12,11 +12,15 @@ version = "0.5.3" [dependencies] r0 = "0.2.1" +cortex-m-rt-macros = { path = "macros", version = "0.1.0" } [dev-dependencies] -panic-semihosting = "0.3.0" -panic-abort = "0.2.0" cortex-m = "0.5.4" +panic-abort = "0.3.0" +panic-semihosting = "0.4.0" + +[target.'cfg(not(target_os = "none"))'.dev-dependencies] +compiletest_rs = "0.3.14" [features] device = [] diff --git a/cortex-m-rt/ci/script.sh b/cortex-m-rt/ci/script.sh index 7d1cc67..cecb975 100644 --- a/cortex-m-rt/ci/script.sh +++ b/cortex-m-rt/ci/script.sh @@ -5,18 +5,31 @@ main() { cargo check --target $TARGET --features device + if [ $TARGET = x86_64-unknown-linux-gnu ]; then + ( cd macros && cargo check && cargo test ) + + cargo test --test compiletest + fi + local examples=( alignment - minimal + divergent-default-handler + divergent-exception + entry-static main + minimal override-exception pre_init state + unsafe-default-handler + unsafe-hard-fault + unsafe-entry + unsafe-exception ) local fail_examples=( data_overflow ) - if [ $TRAVIS_RUST_VERSION = nightly ]; then + if [ $TARGET != x86_64-unknown-linux-gnu ]; then # linking with GNU LD for ex in "${examples[@]}"; do cargo rustc --target $TARGET --example $ex -- \ @@ -48,29 +61,23 @@ main() { # linking with rustc's LLD for ex in "${examples[@]}"; do cargo rustc --target $TARGET --example $ex -- \ - -C linker=rust-lld \ -C link-arg=-Tlink.x cargo rustc --target $TARGET --example $ex --release -- \ - -C linker=rust-lld \ -C link-arg=-Tlink.x done for ex in "${fail_examples[@]}"; do ! cargo rustc --target $TARGET --example $ex -- \ - -C linker=rust-lld \ -C link-arg=-Tlink.x ! cargo rustc --target $TARGET --example $ex --release -- \ - -C linker=rust-lld \ -C link-arg=-Tlink.x done cargo rustc --target $TARGET --example device --features device -- \ - -C linker=rust-lld \ -C link-arg=-Tlink.x cargo rustc --target $TARGET --example device --features device --release -- \ - -C linker=rust-lld \ -C link-arg=-Tlink.x fi diff --git a/cortex-m-rt/examples/alignment.rs b/cortex-m-rt/examples/alignment.rs index 5635851..25d755d 100644 --- a/cortex-m-rt/examples/alignment.rs +++ b/cortex-m-rt/examples/alignment.rs @@ -4,13 +4,12 @@ #![no_main] #![no_std] -#[macro_use(entry)] extern crate cortex_m_rt as rt; extern crate panic_abort; use core::ptr; -entry!(main); +use rt::entry; static mut BSS1: u16 = 0; static mut BSS2: u8 = 0; @@ -19,6 +18,7 @@ static mut DATA2: u16 = 1; static RODATA1: &[u8; 3] = b"012"; static RODATA2: &[u8; 2] = b"34"; +#[entry] fn main() -> ! { unsafe { let _bss1 = ptr::read_volatile(&BSS1); diff --git a/cortex-m-rt/examples/data_overflow.rs b/cortex-m-rt/examples/data_overflow.rs index 396f1c8..ceec18b 100644 --- a/cortex-m-rt/examples/data_overflow.rs +++ b/cortex-m-rt/examples/data_overflow.rs @@ -5,13 +5,12 @@ #![no_main] #![no_std] -#[macro_use(entry)] extern crate cortex_m_rt as rt; extern crate panic_abort; use core::ptr; -entry!(main); +use rt::entry; // This large static array uses most of .rodata static RODATA: [u8; 48*1024] = [1u8; 48*1024]; @@ -20,6 +19,7 @@ static RODATA: [u8; 48*1024] = [1u8; 48*1024]; // without also overflowing RAM. static mut DATA: [u8; 16*1024] = [1u8; 16*1024]; +#[entry] fn main() -> ! { unsafe { let _bigdata = ptr::read_volatile(&RODATA as *const u8); diff --git a/cortex-m-rt/examples/device.rs b/cortex-m-rt/examples/device.rs index 4395db2..950a564 100644 --- a/cortex-m-rt/examples/device.rs +++ b/cortex-m-rt/examples/device.rs @@ -5,13 +5,12 @@ #![no_main] #![no_std] -#[macro_use(entry)] extern crate cortex_m_rt as rt; extern crate panic_semihosting; -// the program entry point -entry!(main); +use rt::entry; +#[entry] fn main() -> ! { loop {} } diff --git a/cortex-m-rt/examples/divergent-default-handler.rs b/cortex-m-rt/examples/divergent-default-handler.rs new file mode 100644 index 0000000..cbb8bb1 --- /dev/null +++ b/cortex-m-rt/examples/divergent-default-handler.rs @@ -0,0 +1,19 @@ +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, exception}; + +#[entry] +fn foo() -> ! { + loop {} +} + +#[exception] +fn DefaultHandler(_irqn: i16) -> ! { + loop {} +} diff --git a/cortex-m-rt/examples/divergent-exception.rs b/cortex-m-rt/examples/divergent-exception.rs new file mode 100644 index 0000000..9998884 --- /dev/null +++ b/cortex-m-rt/examples/divergent-exception.rs @@ -0,0 +1,18 @@ +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, exception}; + +#[entry] +fn foo() -> ! { + loop {} +} + +#[exception] +fn SysTick() -> ! { + loop {} +} diff --git a/cortex-m-rt/examples/entry-static.rs b/cortex-m-rt/examples/entry-static.rs new file mode 100644 index 0000000..1b2e118 --- /dev/null +++ b/cortex-m-rt/examples/entry-static.rs @@ -0,0 +1,20 @@ +//! `static mut` variables local to the entry point are safe to use + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate cortex_m_rt as rt; +extern crate panic_semihosting; + +use rt::entry; + +#[entry] +fn main() -> ! { + static mut COUNT: u32 = 0; + + loop { + *COUNT += 1; + } +} diff --git a/cortex-m-rt/examples/minimal.rs b/cortex-m-rt/examples/minimal.rs index a036046..6f60180 100644 --- a/cortex-m-rt/examples/minimal.rs +++ b/cortex-m-rt/examples/minimal.rs @@ -5,13 +5,13 @@ #![no_main] #![no_std] -#[macro_use(entry)] extern crate cortex_m_rt as rt; extern crate panic_semihosting; -// the program entry point -entry!(main); +use rt::entry; +// the program entry point +#[entry] fn main() -> ! { loop {} } diff --git a/cortex-m-rt/examples/override-exception.rs b/cortex-m-rt/examples/override-exception.rs index 2f100a2..3e0af25 100644 --- a/cortex-m-rt/examples/override-exception.rs +++ b/cortex-m-rt/examples/override-exception.rs @@ -6,29 +6,24 @@ #![no_std] extern crate cortex_m; -#[macro_use(entry, exception)] extern crate cortex_m_rt as rt; extern crate panic_semihosting; use cortex_m::asm; -use rt::ExceptionFrame; - -// the program entry point -entry!(main); +use rt::{entry, exception, ExceptionFrame}; +#[entry] fn main() -> ! { loop {} } -exception!(*, default_handler); - -fn default_handler(_irqn: i16) { +#[exception] +fn DefaultHandler(_irqn: i16) { asm::bkpt(); } -exception!(HardFault, hard_fault); - -fn hard_fault(_ef: &ExceptionFrame) -> ! { +#[exception] +fn HardFault(_ef: &ExceptionFrame) -> ! { asm::bkpt(); loop {} diff --git a/cortex-m-rt/examples/pre_init.rs b/cortex-m-rt/examples/pre_init.rs index 7258936..00e2f2c 100644 --- a/cortex-m-rt/examples/pre_init.rs +++ b/cortex-m-rt/examples/pre_init.rs @@ -4,19 +4,17 @@ #![no_main] #![no_std] -#[macro_use(entry, pre_init)] extern crate cortex_m_rt as rt; extern crate panic_semihosting; -pre_init!(disable_watchdog); +use rt::{entry, pre_init}; +#[pre_init] unsafe fn disable_watchdog() { // Do what you need to disable the watchdog. } -// the program entry point -entry!(main); - +#[entry] fn main() -> ! { loop {} } diff --git a/cortex-m-rt/examples/state.rs b/cortex-m-rt/examples/state.rs index dbacdaf..573914f 100644 --- a/cortex-m-rt/examples/state.rs +++ b/cortex-m-rt/examples/state.rs @@ -5,20 +5,20 @@ #![no_main] #![no_std] -#[macro_use(entry, exception)] extern crate cortex_m_rt as rt; extern crate panic_semihosting; -// the program entry point -entry!(main); +use rt::{entry, exception}; +#[entry] fn main() -> ! { loop {} } // exception handler with state -exception!(SysTick, sys_tick, state: u32 = 0); +#[exception] +fn SysTick() { + static mut STATE: u32 = 0; -fn sys_tick(state: &mut u32) { - *state += 1; + *STATE += 1; } diff --git a/cortex-m-rt/examples/unsafe-default-handler.rs b/cortex-m-rt/examples/unsafe-default-handler.rs new file mode 100644 index 0000000..48bd31e --- /dev/null +++ b/cortex-m-rt/examples/unsafe-default-handler.rs @@ -0,0 +1,16 @@ +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, exception}; + +#[entry] +fn foo() -> ! { + loop {} +} + +#[exception] +unsafe fn DefaultHandler(_irqn: i16) {} diff --git a/cortex-m-rt/examples/unsafe-entry.rs b/cortex-m-rt/examples/unsafe-entry.rs new file mode 100644 index 0000000..feb6f44 --- /dev/null +++ b/cortex-m-rt/examples/unsafe-entry.rs @@ -0,0 +1,13 @@ +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::entry; + +#[entry] +unsafe fn foo() -> ! { + loop {} +} diff --git a/cortex-m-rt/examples/unsafe-exception.rs b/cortex-m-rt/examples/unsafe-exception.rs new file mode 100644 index 0000000..d67f06f --- /dev/null +++ b/cortex-m-rt/examples/unsafe-exception.rs @@ -0,0 +1,16 @@ +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, exception}; + +#[entry] +fn foo() -> ! { + loop {} +} + +#[exception] +unsafe fn SysTick() {} diff --git a/cortex-m-rt/examples/unsafe-hard-fault.rs b/cortex-m-rt/examples/unsafe-hard-fault.rs new file mode 100644 index 0000000..b091d47 --- /dev/null +++ b/cortex-m-rt/examples/unsafe-hard-fault.rs @@ -0,0 +1,18 @@ +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, exception, ExceptionFrame}; + +#[entry] +fn foo() -> ! { + loop {} +} + +#[exception] +unsafe fn HardFault(_ef: &ExceptionFrame) -> ! { + loop {} +} diff --git a/cortex-m-rt/macros/Cargo.toml b/cortex-m-rt/macros/Cargo.toml new file mode 100644 index 0000000..d0644ee --- /dev/null +++ b/cortex-m-rt/macros/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "cortex-m-rt-macros" +version = "0.1.0" +authors = ["Jorge Aparicio <jorge@japaric.io>"] + +[lib] +proc-macro = true + +[dependencies] +quote = "0.6.6" +rand = "0.5.5" +proc-macro2 = "0.4.15" + +[dependencies.syn] +features = ["extra-traits", "full"] +version = "0.14.8" + +[dev-dependencies] +cortex-m-rt = { path = ".." } diff --git a/cortex-m-rt/macros/src/lib.rs b/cortex-m-rt/macros/src/lib.rs new file mode 100644 index 0000000..bd34b04 --- /dev/null +++ b/cortex-m-rt/macros/src/lib.rs @@ -0,0 +1,534 @@ +#![deny(warnings)] + +extern crate proc_macro; +extern crate rand; +#[macro_use] +extern crate quote; +extern crate proc_macro2; +extern crate syn; + +use proc_macro2::Span; +use rand::Rng; +use syn::{FnArg, Ident, Item, ItemFn, ItemStatic, ReturnType, Stmt, Type, Visibility}; + +use proc_macro::TokenStream; + +/// Attribute to declare the entry point of the program +/// +/// **IMPORTANT**: This attribute must be used once in the dependency graph and must be used on a +/// reachable item (i.e. there must be no private modules between the item and the root of the +/// crate). If the item is in the root of the crate you'll be fine. +/// +/// The specified function will be called by the reset handler *after* RAM has been initialized. In +/// the case of the `thumbv7em-none-eabihf` target the FPU will also be enabled before the function +/// is called. +/// +/// The type of the specified function must be `[unsafe] fn() -> !` (never ending function) +/// +/// # Properties +/// +/// The entry point will be called by the reset handler. The program can't reference to the entry +/// point, much less invoke it. +/// +/// `static mut` variables declared within the entry point are safe to access. The compiler can't +/// prove this is safe so the attribute will help by making a transformation to the source code: for +/// this reason a variable like `static mut FOO: u32` will become `let FOO: &'static mut u32;`. Note +/// that `&'static mut` references have move semantics. +/// +/// # Examples +/// +/// - Simple entry point +/// +/// ``` no_run +/// # #![no_main] +/// # use cortex_m_rt_macros::entry; +/// #[entry] +/// fn main() -> ! { +/// loop { +/// /* .. */ +/// } +/// } +/// ``` +/// +/// - `static mut` variables local to the entry point are safe to modify. +/// +/// ``` no_run +/// # #![no_main] +/// # use cortex_m_rt_macros::entry; +/// #[entry] +/// fn main() -> ! { +/// static mut FOO: u32 = 0; +/// +/// let foo: &'static mut u32 = FOO; +/// assert_eq!(*foo, 0); +/// *foo = 1; +/// assert_eq!(*foo, 1); +/// +/// loop { +/// /* .. */ +/// } +/// } +/// ``` +#[proc_macro_attribute] +pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { + let f: ItemFn = syn::parse(input).expect("`#[entry]` must be applied to a function"); + + // check the function signature + assert!( + f.constness.is_none() + && f.vis == Visibility::Inherited + && f.abi.is_none() + && f.decl.inputs.is_empty() + && f.decl.generics.params.is_empty() + && f.decl.generics.where_clause.is_none() + && f.decl.variadic.is_none() + && match f.decl.output { + ReturnType::Default => false, + ReturnType::Type(_, ref ty) => match **ty { + Type::Never(_) => true, + _ => false, + }, + }, + "`#[entry]` function must have signature `[unsafe] fn() -> !`" + ); + + assert!( + args.to_string() == "", + "`entry` attribute must have no arguments" + ); + + // XXX should we blacklist other attributes? + let attrs = f.attrs; + let ident = f.ident; + let (statics, stmts) = extract_static_muts(f.block.stmts); + + let vars = statics + .into_iter() + .map(|var| { + let ident = var.ident; + // `let` can't shadow a `static mut` so we must give the `static` a different + // name. We'll create a new name by appending an underscore to the original name + // of the `static`. + let mut ident_ = ident.to_string(); + ident_.push('_'); + let ident_ = Ident::new(&ident_, Span::call_site()); + let ty = var.ty; + let expr = var.expr; + + quote!( + static mut #ident_: #ty = #expr; + #[allow(non_snake_case)] + let #ident: &'static mut #ty = unsafe { &mut #ident_ }; + ) + }).collect::<Vec<_>>(); + + quote!( + // TODO(forbid) see tests/compile-fail/entry-hidden.rs + // #[forbid(dead_code)] + #[export_name = "main"] + #(#attrs)* + pub fn #ident() -> ! { + #(#vars)* + + #(#stmts)* + } + ).into() +} + +/// Attribute to declare an exception handler +/// +/// **IMPORTANT**: This attribute must be used on reachable items (i.e. there must be no private +/// modules between the item and the root of the crate). If the item is in the root of the crate +/// you'll be fine. +/// +/// # Syntax +/// +/// ``` +/// # use cortex_m_rt_macros::exception; +/// #[exception] +/// fn SysTick() { +/// // .. +/// } +/// +/// # fn main() {} +/// ``` +/// +/// where the name of the function must be one of: +/// +/// - `DefaultHandler` +/// - `NonMaskableInt` +/// - `HardFault` +/// - `MemoryManagement` (a) +/// - `BusFault` (a) +/// - `UsageFault` (a) +/// - `SecureFault` (b) +/// - `SVCall` +/// - `DebugMonitor` (a) +/// - `PendSV` +/// - `SysTick` +/// +/// (a) Not available on Cortex-M0 variants (`thumbv6m-none-eabi`) +/// +/// (b) Only available on ARMv8-M +/// +/// # Usage +/// +/// `#[exception] fn HardFault(..` sets the hard fault handler. The handler must have signature +/// `[unsafe] fn(&ExceptionFrame) -> !`. This handler is not allowed to return as that can cause +/// undefined behavior. +/// +/// `#[exception] fn DefaultHandler(..` sets the *default* handler. All exceptions which have not +/// been assigned a handler will be serviced by this handler. This handler must have signature +/// `[unsafe] fn(irqn: i16) [-> !]`. `irqn` is the IRQ number (See CMSIS); `irqn` will be a negative +/// number when the handler is servicing a core exception; `irqn` will be a positive number when the +/// handler is servicing a device specific exception (interrupt). +/// +/// `#[exception] fn Name(..` overrides the default handler for the exception with the given `Name`. +/// These handlers must have signature `[unsafe] fn() [-> !]`. When overriding these other exception +/// it's possible to add state to them by declaring `static mut` variables at the beginning of the +/// body of the function. These variables will be safe to access from the function body. +/// +/// # Properties +/// +/// Exception handlers can only be called by the hardware. Other parts of the program can't refer to +/// the exception handlers, much less invoke them as if they were functions. +/// +/// `static mut` variables declared within an exception handler are safe to access and can be used +/// to preserve state across invocations of the handler. The compiler can't prove this is safe so +/// the attribute will help by making a transformation to the source code: for this reason a +/// variable like `static mut FOO: u32` will become `let FOO: &mut u32;`. +/// +/// # Examples +/// +/// - Setting the `HardFault` handler +/// +/// ``` +/// # extern crate cortex_m_rt; +/// # extern crate cortex_m_rt_macros; +/// # use cortex_m_rt_macros::exception; +/// #[exception] +/// fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! { +/// // prints the exception frame as a panic message +/// panic!("{:#?}", ef); +/// } +/// +/// # fn main() {} +/// ``` +/// +/// - Setting the default handler +/// +/// ``` +/// # use cortex_m_rt_macros::exception; +/// #[exception] +/// fn DefaultHandler(irqn: i16) { +/// println!("IRQn = {}", irqn); +/// } +/// +/// # fn main() {} +/// ``` +/// +/// - Overriding the `SysTick` handler +/// +/// ``` +/// extern crate cortex_m_rt as rt; +/// +/// use rt::exception; +/// +/// #[exception] +/// fn SysTick() { +/// static mut COUNT: i32 = 0; +/// +/// // `COUNT` is safe to access and has type `&mut i32` +/// *COUNT += 1; +/// +/// println!("{}", COUNT); +/// } +/// +/// # fn main() {} +/// ``` +#[proc_macro_attribute] +pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { + let f: ItemFn = syn::parse(input).expect("`#[exception]` must be applied to a function"); + + assert!( + args.to_string() == "", + "`exception` attribute must have no arguments" + ); + + let ident = f.ident; + + enum Exception { + DefaultHandler, + HardFault, + Other, + } + + let ident_s = ident.to_string(); + let exn = match &*ident_s { + "DefaultHandler" => Exception::DefaultHandler, + "HardFault" => Exception::HardFault, + // NOTE that at this point we don't check if the exception is available on the target (e.g. + // MemoryManagement is not available on Cortex-M0) + "NonMaskableInt" | "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" + | "SVCall" | "DebugMonitor" | "PendSV" | "SysTick" => Exception::Other, + _ => panic!("{} is not a valid exception name", ident_s), + }; + + // XXX should we blacklist other attributes? + let attrs = f.attrs; + let block = f.block; + let stmts = block.stmts; + + let hash = random_ident(); + match exn { + Exception::DefaultHandler => { + assert!( + f.constness.is_none() + && f.vis == Visibility::Inherited + && f.abi.is_none() + && f.decl.inputs.len() == 1 + && f.decl.generics.params.is_empty() + && f.decl.generics.where_clause.is_none() + && f.decl.variadic.is_none() + && match f.decl.output { + ReturnType::Default => true, + ReturnType::Type(_, ref ty) => match **ty { + Type::Tuple(ref tuple) => tuple.elems.is_empty(), + Type::Never(..) => true, + _ => false, + }, + }, + "`DefaultHandler` exception must have signature `[unsafe] fn(i16) [-> !]`" + ); + + let arg = match f.decl.inputs[0] { + FnArg::Captured(ref arg) => arg, + _ => unreachable!(), + }; + + quote!( + #[export_name = #ident_s] + #(#attrs)* + pub extern "C" fn #hash() { + extern crate core; + + const SCB_ICSR: *const u32 = 0xE000_ED04 as *const u32; + + let #arg = unsafe { core::ptr::read(SCB_ICSR) as u8 as i16 - 16 }; + + #(#stmts)* + } + ).into() + } + Exception::HardFault => { + assert!( + f.constness.is_none() + && f.vis == Visibility::Inherited + && f.abi.is_none() + && f.decl.inputs.len() == 1 + && match f.decl.inputs[0] { + FnArg::Captured(ref arg) => match arg.ty { + Type::Reference(ref r) => { + r.lifetime.is_none() && r.mutability.is_none() + } + _ => false, + }, + _ => false, + } + && f.decl.generics.params.is_empty() + && f.decl.generics.where_clause.is_none() + && f.decl.variadic.is_none() + && match f.decl.output { + ReturnType::Default => false, + ReturnType::Type(_, ref ty) => match **ty { + Type::Never(_) => true, + _ => false, + }, + }, + "`HardFault` exception must have signature `[unsafe] fn(&ExceptionFrame) -> !`" + ); + + let arg = match f.decl.inputs[0] { + FnArg::Captured(ref arg) => arg, + _ => unreachable!(), + }; + + let pat = &arg.pat; + + quote!( + #[export_name = "UserHardFault"] + #(#attrs)* + pub extern "C" fn #hash(#arg) -> ! { + extern crate cortex_m_rt; + + // further type check of the input argument + let #pat: &cortex_m_rt::ExceptionFrame = #pat; + + #(#stmts)* + } + ).into() + } + Exception::Other => { + assert!( + f.constness.is_none() + && f.vis == Visibility::Inherited + && f.abi.is_none() + && f.decl.inputs.is_empty() + && f.decl.generics.params.is_empty() + && f.decl.generics.where_clause.is_none() + && f.decl.variadic.is_none() + && match f.decl.output { + ReturnType::Default => true, + ReturnType::Type(_, ref ty) => match **ty { + Type::Tuple(ref tuple) => tuple.elems.is_empty(), + Type::Never(..) => true, + _ => false, + }, + }, + "`#[exception]` functions other than `DefaultHandler` and `HardFault` must \ + have signature `[unsafe] fn() [-> !]`" + ); + + let (statics, stmts) = extract_static_muts(stmts); + + let vars = statics + .into_iter() + .map(|var| { + let ident = var.ident; + // `let` can't shadow a `static mut` so we must give the `static` a different + // name. We'll create a new name by appending an underscore to the original name + // of the `static`. + let mut ident_ = ident.to_string(); + ident_.push('_'); + let ident_ = Ident::new(&ident_, Span::call_site()); + let ty = var.ty; + let expr = var.expr; + + quote!( + static mut #ident_: #ty = #expr; + #[allow(non_snake_case)] + let #ident: &mut #ty = unsafe { &mut #ident_ }; + ) + }).collect::<Vec<_>>(); + + quote!( + #[export_name = #ident_s] + #(#attrs)* + pub fn #hash() { + extern crate cortex_m_rt; + + // check that this exception actually exists + cortex_m_rt::Exception::#ident; + + #(#vars)* + + #(#stmts)* + } + ).into() + } + } +} + +/// Attribute to mark which function will be called at the beginning of the reset handler. +/// +/// **IMPORTANT**: This attribute must be used once in the dependency graph and must be used on a +/// reachable item (i.e. there must be no private modules between the item and the root of the +/// crate). If the item is in the root of the crate you'll be fine. +/// +/// The function must have the signature of `unsafe fn()`. +/// +/// The function passed will be called before static variables are initialized. Any access of static +/// variables will result in undefined behavior. +/// +/// # Examples +/// +/// ``` +/// # use cortex_m_rt_macros::pre_init; +/// #[pre_init] +/// unsafe fn before_main() { +/// // do something here +/// } +/// +/// # fn main() {} +/// ``` +#[proc_macro_attribute] +pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream { + let f: ItemFn = syn::parse(input).expect("`#[pre_init]` must be applied to a function"); + + // check the function signature + assert!( + f.constness.is_none() + && f.vis == Visibility::Inherited + && f.unsafety.is_some() + && f.abi.is_none() + && f.decl.inputs.is_empty() + && f.decl.generics.params.is_empty() + && f.decl.generics.where_clause.is_none() + && f.decl.variadic.is_none() + && match f.decl.output { + ReturnType::Default => true, + ReturnType::Type(_, ref ty) => match **ty { + Type::Tuple(ref tuple) => tuple.elems.is_empty(), + _ => false, + }, + }, + "`#[pre_init]` function must have signature `unsafe fn()`" + ); + + assert!( + args.to_string() == "", + "`pre_init` attribute must have no arguments" + ); + + // XXX should we blacklist other attributes? + let attrs = f.attrs; + let ident = f.ident; + let block = f.block; + + quote!( + #[export_name = "__pre_init"] + #(#attrs)* + pub unsafe fn #ident() #block + ).into() +} + +// Creates a random identifier +fn random_ident() -> Ident { + let mut rng = rand::thread_rng(); + Ident::new( + &(0..16) + .map(|i| { + if i == 0 || rng.gen() { + ('a' as u8 + rng.gen::<u8>() % 25) as char + } else { + ('0' as u8 + rng.gen::<u8>() % 10) as char + } + }).collect::<String>(), + Span::call_site(), + ) +} + +/// Extracts `static mut` vars from the beginning of the given statements +fn extract_static_muts(stmts: Vec<Stmt>) -> (Vec<ItemStatic>, Vec<Stmt>) { + let mut istmts = stmts.into_iter(); + + let mut statics = vec![]; + let mut stmts = vec![]; + while let Some(stmt) = istmts.next() { + match stmt { + Stmt::Item(Item::Static(var)) => if var.mutability.is_some() { + statics.push(var); + } else { + stmts.push(Stmt::Item(Item::Static(var))); + }, + _ => { + stmts.push(stmt); + break; + } + } + } + + stmts.extend(istmts); + + (statics, stmts) +} diff --git a/cortex-m-rt/src/lib.rs b/cortex-m-rt/src/lib.rs index 10f60f6..17c4a63 100644 --- a/cortex-m-rt/src/lib.rs +++ b/cortex-m-rt/src/lib.rs @@ -14,9 +14,16 @@ //! //! - Enabling the FPU before the program entry point if the target is `thumbv7em-none-eabihf`. //! -//! This crate also provides a mechanism to set exception handlers: see the [`exception!`] macro. +//! This crate also provides the following attributes: //! -//! [`exception!`]: macro.exception.html +//! - [`#[entry]`] to declare the entry point of the program +//! - [`#[exception]`] to override an exception handler. If not overridden all exception handlers +//! default to an infinite loop. +//! - [`#[pre_init]`] to run code *before* `static` variables are initialized +//! +//! [`#[entry]`]: ../cortex_m_rt_macros/fn.entry.html +//! [`#[exception]`]: ../cortex_m_rt_macros/fn.exception.html +//! [`#[pre_init]`]: ../cortex_m_rt_macros/fn.pre_init.html //! //! # Requirements //! @@ -87,25 +94,23 @@ //! This section presents a minimal application built on top of `cortex-m-rt`. Apart from the //! mandatory `memory.x` linker script describing the memory layout of the device, the hard fault //! handler and the default exception handler must also be defined somewhere in the dependency -//! graph (cf. [`exception!`]). In this example we define them in the binary crate: +//! graph (see [`#[exception]`]). In this example we define them in the binary crate: //! //! ``` ignore //! // IMPORTANT the standard `main` interface is not used because it requires nightly //! #![no_main] //! #![no_std] //! -//! #[macro_use(entry, exception)] //! extern crate cortex_m_rt as rt; //! //! // makes `panic!` print messages to the host stderr using semihosting //! extern crate panic_semihosting; //! -//! use rt::ExceptionFrame; +//! use rt::entry; //! //! // use `main` as the entry point of this application -//! entry!(main); -//! //! // `main` is not allowed to return +//! #[entry] //! fn main() -> ! { //! // initialization //! @@ -113,20 +118,6 @@ //! // application logic //! } //! } -//! -//! // define the hard fault handler -//! exception!(HardFault, hard_fault); -//! -//! fn hard_fault(ef: &ExceptionFrame) -> ! { -//! panic!("{:#?}", ef); -//! } -//! -//! // define the default exception handler -//! exception!(*, default_handler); -//! -//! fn default_handler(irqn: i16) { -//! panic!("unhandled exception (IRQn={})", irqn); -//! } //! ``` //! //! To actually build this program you need to place a `memory.x` linker script somewhere the linker @@ -207,15 +198,15 @@ //! //! [`entry!`]: macro.entry.html //! -//! - `DefaultHandler`. This is the default handler. This function will contain, or call, the -//! function you declared in the second argument of `exception!(*, ..)`. +//! - `DefaultHandler`. This is the default handler. If not overridden using `#[exception] fn +//! DefaultHandler(..` this will be an infinite loop. //! //! - `HardFault`. This is the hard fault handler. This function is simply a trampoline that jumps -//! into the user defined hard fault handler: `UserHardFault`. The trampoline is required to set up -//! the pointer to the stacked exception frame. +//! into the user defined hard fault handler named `UserHardFault`. The trampoline is required to +//! set up the pointer to the stacked exception frame. //! -//! - `UserHardFault`. This is the user defined hard fault handler. This function will contain, or -//! call, the function you declared in the second argument of `exception!(HardFault, ..)` +//! - `UserHardFault`. This is the user defined hard fault handler. If not overridden using +//! `#[exception] fn HardFault(..` this will be an infinite loop. //! //! - `__STACK_START`. This is the first entry in the `.vector_table` section. This symbol contains //! the initial value of the stack pointer; this is where the stack will be located -- the stack @@ -356,8 +347,8 @@ //! PROVIDE(Bar = DefaultHandler); //! ``` //! -//! This weakly aliases both `Foo` and `Bar`. `DefaultHandler` is the default exception handler that -//! the user provides via `exception!(*, ..)` and that the core exceptions use unless overridden. +//! This weakly aliases both `Foo` and `Bar`. `DefaultHandler` is the default exception handler and +//! that the core exceptions use unless overridden. //! //! Because this linker script is provided by a dependency of the final application the dependency //! must contain build script that puts `device.x` somewhere the linker can find. An example of such @@ -397,11 +388,14 @@ #![deny(warnings)] #![no_std] +extern crate cortex_m_rt_macros as macros; extern crate r0; use core::fmt; use core::sync::atomic::{self, Ordering}; +pub use macros::{entry, exception, pre_init}; + /// Registers stacked (pushed into the stack) during an exception #[derive(Clone, Copy)] #[repr(C)] @@ -474,8 +468,6 @@ pub static __RESET_VECTOR: unsafe extern "C" fn() -> ! = Reset; #[no_mangle] pub unsafe extern "C" fn Reset() -> ! { extern "C" { - // This symbol will be provided by the user via the `entry!` macro - fn main() -> !; // These symbols come from `link.x` static mut __sbss: u32; @@ -485,11 +477,17 @@ pub unsafe extern "C" fn Reset() -> ! { static mut __edata: u32; static __sidata: u32; + } + + extern "Rust" { + // This symbol will be provided by the user via `#[entry]` + fn main() -> !; + + // This symbol will be provided by the user via `#[pre_init]` fn __pre_init(); } - let pre_init: unsafe extern "C" fn() = __pre_init; - pre_init(); + __pre_init(); // Initialize RAM r0::zero_bss(&mut __sbss, &mut __ebss); @@ -551,31 +549,6 @@ pub unsafe extern "C" fn DefaultHandler_() -> ! { #[no_mangle] pub unsafe extern "C" fn DefaultPreInit() {} -/// Macro to define the entry point of the program -/// -/// **NOTE** This macro must be invoked once and must be invoked from an accessible module, ideally -/// from the root of the crate. -/// -/// Usage: `entry!(path::to::entry::point)` -/// -/// The specified function will be called by the reset handler *after* RAM has been initialized. In -/// the case of the `thumbv7em-none-eabihf` target the FPU will also be enabled before the function -/// is called. -/// -/// The signature of the specified function must be `fn() -> !` (never ending function) -#[macro_export] -macro_rules! entry { - ($path:expr) => { - #[export_name = "main"] - pub extern "C" fn __impl_main() -> ! { - // validate the signature of the program entry point - let f: fn() -> ! = $path; - - f() - } - }; -} - /* Exceptions */ #[doc(hidden)] pub enum Exception { @@ -721,206 +694,3 @@ pub static __INTERRUPTS: [unsafe extern "C" fn(); 32] = [{ DefaultHandler }; 32]; - -/// Macro to set or override a processor core exception handler -/// -/// **NOTE** This macro must be invoked from an accessible module, ideally from the root of the -/// crate. -/// -/// # Syntax -/// -/// ``` ignore -/// exception!( -/// // Name of the exception -/// $Name:ident, -/// -/// // Path to the exception handler (a function) -/// $handler:expr, -/// -/// // Optional, state preserved across invocations of the handler -/// state: $State:ty = $initial_state:expr, -/// ); -/// ``` -/// -/// where `$Name` can be one of: -/// -/// - `*` -/// - `NonMaskableInt` -/// - `HardFault` -/// - `MemoryManagement` (a) -/// - `BusFault` (a) -/// - `UsageFault` (a) -/// - `SecureFault` (b) -/// - `SVCall` -/// - `DebugMonitor` (a) -/// - `PendSV` -/// - `SysTick` -/// -/// (a) Not available on Cortex-M0 variants (`thumbv6m-none-eabi`) -/// -/// (b) Only available on ARMv8-M -/// -/// # Usage -/// -/// `exception!(HardFault, ..)` sets the hard fault handler. The handler must have signature -/// `fn(&ExceptionFrame) -> !`. This handler is not allowed to return as that can cause undefined -/// behavior. It's mandatory to set the `HardFault` handler somewhere in the dependency graph of an -/// application. -/// -/// `exception!(*, ..)` sets the *default* handler. All exceptions which have not been assigned a -/// handler will be serviced by this handler. This handler must have signature `fn(irqn: i16)`. -/// `irqn` is the IRQ number (cf. CMSIS); `irqn` will be a negative number when the handler is -/// servicing a core exception; `irqn` will be a positive number when the handler is servicing a -/// device specific exception (interrupt). It's mandatory to set the default handler somewhere -/// in the dependency graph of an application. -/// -/// `exception!($Exception, ..)` overrides the default handler for `$Exception`. All exceptions, -/// except for `HardFault`, can be assigned some `$State`. -/// -/// # Examples -/// -/// - Setting the `HardFault` handler -/// -/// ``` -/// #[macro_use(exception)] -/// extern crate cortex_m_rt as rt; -/// -/// use rt::ExceptionFrame; -/// -/// exception!(HardFault, hard_fault); -/// -/// fn hard_fault(ef: &ExceptionFrame) -> ! { -/// // prints the exception frame as a panic message -/// panic!("{:#?}", ef); -/// } -/// -/// # fn main() {} -/// ``` -/// -/// - Setting the default handler -/// -/// ``` -/// #[macro_use(exception)] -/// extern crate cortex_m_rt as rt; -/// -/// exception!(*, default_handler); -/// -/// fn default_handler(irqn: i16) { -/// println!("IRQn = {}", irqn); -/// } -/// -/// # fn main() {} -/// ``` -/// -/// - Overriding the `SysTick` handler -/// -/// ``` -/// #[macro_use(exception)] -/// extern crate cortex_m_rt as rt; -/// -/// exception!(SysTick, sys_tick, state: u32 = 0); -/// -/// fn sys_tick(count: &mut u32) { -/// println!("count = {}", *count); -/// -/// *count += 1; -/// } -/// -/// # fn main() {} -/// ``` -#[macro_export] -macro_rules! exception { - (* , $handler:expr) => { - #[allow(unsafe_code)] - #[deny(private_no_mangle_fns)] // raise an error if this item is not accessible - #[no_mangle] - pub unsafe extern "C" fn DefaultHandler() { - extern crate core; - - // validate the signature of the user provided handler - let f: fn(i16) = $handler; - - const SCB_ICSR: *const u32 = 0xE000_ED04 as *const u32; - - // NOTE not volatile so the compiler can opt the load operation away if the value is - // unused - f(core::ptr::read(SCB_ICSR) as u8 as i16 - 16) - } - }; - - (HardFault, $handler:expr) => { - #[allow(unsafe_code)] - #[deny(private_no_mangle_fns)] // raise an error if this item is not accessible - #[no_mangle] - pub unsafe extern "C" fn UserHardFault(ef: &$crate::ExceptionFrame) { - // validate the signature of the user provided handler - let f: fn(&$crate::ExceptionFrame) -> ! = $handler; - - f(ef) - } - }; - - ($Name:ident, $handler:expr,state: $State:ty = $initial_state:expr) => { - #[allow(unsafe_code)] - #[deny(private_no_mangle_fns)] // raise an error if this item is not accessible - #[no_mangle] - pub unsafe extern "C" fn $Name() { - static mut STATE: $State = $initial_state; - - // check that this exception exists - let _ = $crate::Exception::$Name; - - // validate the signature of the user provided handler - let f: fn(&mut $State) = $handler; - - f(&mut STATE) - } - }; - - ($Name:ident, $handler:expr) => { - #[allow(unsafe_code)] - #[deny(private_no_mangle_fns)] // raise an error if this item is not accessible - #[no_mangle] - pub unsafe extern "C" fn $Name() { - // check that this exception exists - let _ = $crate::Exception::$Name; - - // validate the signature of the user provided handler - let f: fn() = $handler; - - f() - } - }; -} - -/// Macro to set the function to be called at the beginning of the reset handler. -/// -/// The function must have the signature of `unsafe fn()`. -/// -/// The function passed will be called before static variables are initialized. Any access of static -/// variables will result in undefined behavior. -/// -/// # Examples -/// -/// ``` ignore -/// pre_init!(foo::bar); -/// -/// mod foo { -/// pub unsafe fn bar() { -/// // do something here -/// } -/// } -/// ``` -#[macro_export] -macro_rules! pre_init { - ($handler:path) => { - #[allow(unsafe_code)] - #[deny(private_no_mangle_fns)] // raise an error if this item is not accessible - #[no_mangle] - pub unsafe extern "C" fn __pre_init() { - // validate user handler - let f: unsafe fn() = $handler; - f(); - } - }; -} diff --git a/cortex-m-rt/tests/compile-fail/default-handler-bad-signature-1.rs b/cortex-m-rt/tests/compile-fail/default-handler-bad-signature-1.rs new file mode 100644 index 0000000..037e9c8 --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/default-handler-bad-signature-1.rs @@ -0,0 +1,16 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, exception}; + +#[entry] +fn foo() -> ! { + loop {} +} + +#[exception] //~ ERROR custom attribute panicked +//~^ HELP `DefaultHandler` exception must have signature `[unsafe] fn(i16) [-> !]` +fn DefaultHandler(_irqn: i16, undef: u32) {} diff --git a/cortex-m-rt/tests/compile-fail/default-handler-bad-signature-2.rs b/cortex-m-rt/tests/compile-fail/default-handler-bad-signature-2.rs new file mode 100644 index 0000000..8fc4a7e --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/default-handler-bad-signature-2.rs @@ -0,0 +1,18 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, exception}; + +#[entry] +fn foo() -> ! { + loop {} +} + +#[exception] //~ ERROR custom attribute panicked +//~^ HELP `DefaultHandler` exception must have signature `[unsafe] fn(i16) [-> !]` +fn DefaultHandler(_irqn: i16) -> u32 { + 0 +} diff --git a/cortex-m-rt/tests/compile-fail/default-handler-hidden.rs b/cortex-m-rt/tests/compile-fail/default-handler-hidden.rs new file mode 100644 index 0000000..059c10b --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/default-handler-hidden.rs @@ -0,0 +1,22 @@ +// ignore-test :sadface: it's not possible to prevent this user error at compile time +// see rust-lang/rust#53975 for details + +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, exception}; + +#[entry] +fn foo() -> ! { + loop {} +} + +mod hidden { + use cortex_m_rt::exception; + + #[exception] + fn DefaultHandler(_irqn: i16) {} +} diff --git a/cortex-m-rt/tests/compile-fail/default-handler-twice.rs b/cortex-m-rt/tests/compile-fail/default-handler-twice.rs new file mode 100644 index 0000000..7d6ad98 --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/default-handler-twice.rs @@ -0,0 +1,22 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, exception}; + +#[entry] +fn foo() -> ! { + loop {} +} + +#[exception] +fn DefaultHandler(_irqn: i16) {} + +pub mod reachable { + use cortex_m_rt::exception; + + #[exception] //~ ERROR symbol `DefaultHandler` is already defined + fn DefaultHandler(_irqn: i16) {} +} diff --git a/cortex-m-rt/tests/compile-fail/entry-args.rs b/cortex-m-rt/tests/compile-fail/entry-args.rs new file mode 100644 index 0000000..07cb4bd --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/entry-args.rs @@ -0,0 +1,13 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::entry; + +#[entry(foo)] //~ ERROR custom attribute panicked +//~^ HELP `entry` attribute must have no arguments +fn foo() -> ! { + loop {} +} diff --git a/cortex-m-rt/tests/compile-fail/entry-bad-signature-1.rs b/cortex-m-rt/tests/compile-fail/entry-bad-signature-1.rs new file mode 100644 index 0000000..5eeb49f --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/entry-bad-signature-1.rs @@ -0,0 +1,11 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::entry; + +#[entry] //~ ERROR custom attribute panicked +//~^ HELP `#[entry]` function must have signature `[unsafe] fn() -> !` +fn foo() {} diff --git a/cortex-m-rt/tests/compile-fail/entry-bad-signature-2.rs b/cortex-m-rt/tests/compile-fail/entry-bad-signature-2.rs new file mode 100644 index 0000000..18bbaed --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/entry-bad-signature-2.rs @@ -0,0 +1,11 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::entry; + +#[entry] //~ ERROR custom attribute panicked +//~^ HELP `#[entry]` function must have signature `[unsafe] fn() -> !` +fn foo(undef: i32) -> ! {} diff --git a/cortex-m-rt/tests/compile-fail/entry-bad-signature-3.rs b/cortex-m-rt/tests/compile-fail/entry-bad-signature-3.rs new file mode 100644 index 0000000..09b75e9 --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/entry-bad-signature-3.rs @@ -0,0 +1,13 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::entry; + +#[entry] //~ ERROR custom attribute panicked +//~^ HELP `#[entry]` function must have signature `[unsafe] fn() -> !` +extern "C" fn foo() -> ! { + loop {} +} diff --git a/cortex-m-rt/tests/compile-fail/entry-hidden.rs b/cortex-m-rt/tests/compile-fail/entry-hidden.rs new file mode 100644 index 0000000..7d74063 --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/entry-hidden.rs @@ -0,0 +1,19 @@ +// ignore-test :sadface: it's not possible to prevent this user error at compile time +// see rust-lang/rust#53975 for details + +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +mod hidden { + use cortex_m_rt::entry; + + // this function needs to be "reachable" (all modules between it and the crate root must be + // `pub`) or linking will fail + #[entry] + fn foo() -> ! { //~ ERROR function is never used + loop {} + } +} diff --git a/cortex-m-rt/tests/compile-fail/entry-twice.rs b/cortex-m-rt/tests/compile-fail/entry-twice.rs new file mode 100644 index 0000000..b2819f6 --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/entry-twice.rs @@ -0,0 +1,17 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::entry; + +#[entry] +fn foo() -> ! { + loop {} +} + +#[entry] //~ ERROR symbol `main` is already defined +fn bar() -> ! { + loop {} +} diff --git a/cortex-m-rt/tests/compile-fail/exception-args.rs b/cortex-m-rt/tests/compile-fail/exception-args.rs new file mode 100644 index 0000000..85613ff --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/exception-args.rs @@ -0,0 +1,16 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, exception}; + +#[entry] +fn foo() -> ! { + loop {} +} + +#[exception(SysTick)] //~ ERROR custom attribute panicked +//~^ HELP `exception` attribute must have no arguments +fn SysTick() {} diff --git a/cortex-m-rt/tests/compile-fail/exception-bad-signature-1.rs b/cortex-m-rt/tests/compile-fail/exception-bad-signature-1.rs new file mode 100644 index 0000000..966493e --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/exception-bad-signature-1.rs @@ -0,0 +1,16 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, exception}; + +#[entry] +fn foo() -> ! { + loop {} +} + +#[exception] //~ ERROR custom attribute panicked +//~^ HELP `#[exception]` functions other than `DefaultHandler` and `HardFault` must have signature `[unsafe] fn() [-> !]` +fn SysTick(undef: u32) {} diff --git a/cortex-m-rt/tests/compile-fail/exception-bad-signature-2.rs b/cortex-m-rt/tests/compile-fail/exception-bad-signature-2.rs new file mode 100644 index 0000000..8504771 --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/exception-bad-signature-2.rs @@ -0,0 +1,18 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, exception}; + +#[entry] +fn foo() -> ! { + loop {} +} + +#[exception] //~ ERROR custom attribute panicked +//~^ HELP `#[exception]` functions other than `DefaultHandler` and `HardFault` must have signature `[unsafe] fn() [-> !]` +fn SysTick() -> u32 { + 0 +} diff --git a/cortex-m-rt/tests/compile-fail/exception-hidden.rs b/cortex-m-rt/tests/compile-fail/exception-hidden.rs new file mode 100644 index 0000000..053c81c --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/exception-hidden.rs @@ -0,0 +1,22 @@ +// ignore-test :sadface: it's not possible to prevent this user error at compile time +// see rust-lang/rust#53975 for details + +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, exception}; + +#[entry] +fn foo() -> ! { + loop {} +} + +mod hidden { + use cortex_m_rt::exception; + + #[exception] + fn SysTick() {} +} diff --git a/cortex-m-rt/tests/compile-fail/exception-twice.rs b/cortex-m-rt/tests/compile-fail/exception-twice.rs new file mode 100644 index 0000000..5377fce --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/exception-twice.rs @@ -0,0 +1,22 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, exception}; + +#[entry] +fn foo() -> ! { + loop {} +} + +#[exception] +fn SysTick() {} + +pub mod reachable { + use cortex_m_rt::exception; + + #[exception] //~ ERROR symbol `SysTick` is already defined + fn SysTick() {} +} diff --git a/cortex-m-rt/tests/compile-fail/hard-fault-bad-signature-1.rs b/cortex-m-rt/tests/compile-fail/hard-fault-bad-signature-1.rs new file mode 100644 index 0000000..83fda5f --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/hard-fault-bad-signature-1.rs @@ -0,0 +1,18 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, exception, ExceptionFrame}; + +#[entry] +fn foo() -> ! { + loop {} +} + +#[exception] //~ ERROR custom attribute panicked +//~^ HELP `HardFault` exception must have signature `[unsafe] fn(&ExceptionFrame) -> !` +fn HardFault(_ef: &ExceptionFrame, undef: u32) -> ! { + loop {} +} diff --git a/cortex-m-rt/tests/compile-fail/hard-fault-hidden.rs b/cortex-m-rt/tests/compile-fail/hard-fault-hidden.rs new file mode 100644 index 0000000..31237c4 --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/hard-fault-hidden.rs @@ -0,0 +1,24 @@ +// ignore-test :sadface: it's not possible to prevent this user error at compile time +// see rust-lang/rust#53975 for details + +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, exception, ExceptionFrame}; + +#[entry] +fn foo() -> ! { + loop {} +} + +mod hidden { + use cortex_m_rt::{exception, ExceptionFrame}; + + #[exception] + fn HardFault(_ef: &ExceptionFrame) -> ! { + loop {} + } +} diff --git a/cortex-m-rt/tests/compile-fail/hard-fault-twice.rs b/cortex-m-rt/tests/compile-fail/hard-fault-twice.rs new file mode 100644 index 0000000..90270e5 --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/hard-fault-twice.rs @@ -0,0 +1,26 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, exception, ExceptionFrame}; + +#[entry] +fn foo() -> ! { + loop {} +} + +#[exception] +fn HardFault(_ef: &ExceptionFrame) -> ! { + loop {} +} + +pub mod reachable { + use cortex_m_rt::{exception, ExceptionFrame}; + + #[exception] //~ ERROR symbol `UserHardFault` is already defined + fn HardFault(_ef: &ExceptionFrame) -> ! { + loop {} + } +} diff --git a/cortex-m-rt/tests/compile-fail/pre-init-args.rs b/cortex-m-rt/tests/compile-fail/pre-init-args.rs new file mode 100644 index 0000000..716b211 --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/pre-init-args.rs @@ -0,0 +1,16 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, pre_init}; + +#[pre_init(foo)] //~ ERROR custom attribute panicked +//~^ HELP `pre_init` attribute must have no arguments +unsafe fn foo() {} + +#[entry] +fn baz() -> ! { + loop {} +} diff --git a/cortex-m-rt/tests/compile-fail/pre-init-bad-signature-1.rs b/cortex-m-rt/tests/compile-fail/pre-init-bad-signature-1.rs new file mode 100644 index 0000000..58d3022 --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/pre-init-bad-signature-1.rs @@ -0,0 +1,16 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, pre_init}; + +#[pre_init] //~ ERROR custom attribute panicked +//~^ HELP `#[pre_init]` function must have signature `unsafe fn()` +fn foo() {} + +#[entry] +fn bar() -> ! { + loop {} +} diff --git a/cortex-m-rt/tests/compile-fail/pre-init-bad-signature-2.rs b/cortex-m-rt/tests/compile-fail/pre-init-bad-signature-2.rs new file mode 100644 index 0000000..e47ed59 --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/pre-init-bad-signature-2.rs @@ -0,0 +1,16 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, pre_init}; + +#[pre_init] //~ ERROR custom attribute panicked +//~^ HELP `#[pre_init]` function must have signature `unsafe fn()` +unsafe fn foo(undef: i32) {} + +#[entry] +fn bar() -> ! { + loop {} +} diff --git a/cortex-m-rt/tests/compile-fail/pre-init-hidden.rs b/cortex-m-rt/tests/compile-fail/pre-init-hidden.rs new file mode 100644 index 0000000..f512d62 --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/pre-init-hidden.rs @@ -0,0 +1,23 @@ +// ignore-test :sadface: it's not possible to prevent this user error at compile time +// see rust-lang/rust#53975 for details + +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +mod hidden { + use cortex_m_rt::pre_init; + + // this function needs to be "reachable" (all modules between it and the crate root must be + // `pub`) or the function will be ignored + #[entry] + unsafe fn pre_init() {} //~ ERROR function is never used +} + +#[entry] +fn foo() -> ! { + //~ ERROR function is never used + loop {} +} diff --git a/cortex-m-rt/tests/compile-fail/pre-init-twice.rs b/cortex-m-rt/tests/compile-fail/pre-init-twice.rs new file mode 100644 index 0000000..5fb1ade --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/pre-init-twice.rs @@ -0,0 +1,18 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_semihosting; + +use cortex_m_rt::{entry, pre_init}; + +#[pre_init] +unsafe fn foo() {} + +#[pre_init] //~ ERROR symbol `__pre_init` is already defined +unsafe fn bar() {} + +#[entry] +fn baz() -> ! { + loop {} +} diff --git a/cortex-m-rt/tests/compiletest.rs b/cortex-m-rt/tests/compiletest.rs new file mode 100644 index 0000000..6cea3ac --- /dev/null +++ b/cortex-m-rt/tests/compiletest.rs @@ -0,0 +1,21 @@ +extern crate compiletest_rs as compiletest; + +use std::path::PathBuf; + +fn run_mode(mode: &'static str) { + let mut config = compiletest::Config::default(); + + config.mode = mode.parse().expect("Invalid mode"); + config.src_base = PathBuf::from(format!("tests/{}", mode)); + // config.link_deps(); // Populate config.target_rustcflags with dependencies on the path + config.target_rustcflags = + Some("-L target/debug -L target/debug/deps -C panic=abort".to_owned()); + // config.clean_rmeta(); // If your tests import the parent crate, this helps with E0464 + + compiletest::run_tests(&config); +} + +#[test] +fn compile_test() { + run_mode("compile-fail"); +} |