From 5781e9391fe3d7d3dabec620cb782d38f5f9cb9e Mon Sep 17 00:00:00 2001 From: Brian Cully Date: Fri, 10 May 2019 11:50:53 -0400 Subject: Update to 0.2.0: Add HandlerArray type. * Create HandlerArray as a safe wrapper around Handler. * Add Cargo features for HandlerArray size. * Move Handler into sub-module. * Add CriticalSection sub-module for architecture dependent support of interrupt-free contexts. * Add build rules to pull in cortex-m support for CriticalSection automatically. --- .gitignore | 1 + Cargo.lock | 2 +- Cargo.toml | 17 +++- README.md | 6 ++ benches/handler-call.rs | 32 +++++++ examples/scope.rs | 21 ++++ src/array.rs | 171 +++++++++++++++++++++++++++++++++ src/cs/cortex.rs | 20 ++++ src/cs/dummy.rs | 18 ++++ src/cs/mod.rs | 38 ++++++++ src/fnnop.rs | 19 ++++ src/handler.rs | 185 +++++++++++++++++++++++++++++++++++ src/lib.rs | 218 ++++-------------------------------------- tests/compile-fail/array.rs | 14 +++ tests/compile-fail/handler.rs | 50 ++++++++++ tests/compile-fail/mutable.rs | 51 ---------- 16 files changed, 609 insertions(+), 254 deletions(-) create mode 100755 benches/handler-call.rs create mode 100644 examples/scope.rs create mode 100644 src/array.rs create mode 100644 src/cs/cortex.rs create mode 100644 src/cs/dummy.rs create mode 100644 src/cs/mod.rs create mode 100644 src/fnnop.rs create mode 100644 src/handler.rs mode change 100755 => 100644 src/lib.rs create mode 100644 tests/compile-fail/array.rs create mode 100644 tests/compile-fail/handler.rs delete mode 100644 tests/compile-fail/mutable.rs diff --git a/.gitignore b/.gitignore index 53eaa21..4689e8a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target **/*.rs.bk +.gdb_history diff --git a/Cargo.lock b/Cargo.lock index c33c30a..cfcfe67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,7 +72,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "clint" -version = "0.1.0" +version = "0.2.0" dependencies = [ "compiletest_rs 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", "cortex-m 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 21623b4..2ea48ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clint" -version = "0.1.0" +version = "0.2.0" description = "CLosure INTerrupt handlers." categories = ["no-std", "embedded", "hardware-support", "asynchronous"] keywords = ["interrupt", "peripheral"] @@ -15,3 +15,18 @@ compiletest_rs = "~0.3" [dev-dependencies.cortex-m-rt] version = "~0.6" + +[target.'cfg(all(target_arch = "arm", target_os = "none"))'.dependencies] +cortex-m = "~0.6" + +[features] +default = ["isr-32"] + +# Number of ISR closures to hold in a HandlerTable. This is pretty +# clumsy, but doesn't require const generics. +isr-8 = [] +isr-16 = [] +isr-32 = [] +isr-64 = [] +isr-128 = [] +isr-256 = [] diff --git a/README.md b/README.md index 36a8e9d..c3c6de5 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,12 @@ This crate allows you to use closures for interrupt handlers in a heapless, no-std environment. +## Cargo features +The `HandlerTable` type uses a backing array for its closures. To +configure the number of available slots, specify one of the following +cargo features: `isr-8`, `isr-16`, `isr-32`, `isr-64`, `isr-128`, or +`isr-256`. By default, 32 slots are available. + # Example Code See the `examples` directory for some simple examples. diff --git a/benches/handler-call.rs b/benches/handler-call.rs new file mode 100755 index 0000000..9a9edec --- /dev/null +++ b/benches/handler-call.rs @@ -0,0 +1,32 @@ +#![feature(test)] + +extern crate test; +use test::Bencher; + +use clint::Handler; + +const ITER_COUNT: usize = 10_000; + +#[bench] +fn bench_bare_fn(b: &mut Bencher) { + static mut X: usize = 0; + #[inline(never)] + fn inc() { + unsafe { X += 1 }; + } + + let n = test::black_box(ITER_COUNT); + b.iter(|| (0..n).for_each(|_| inc())); + assert!(unsafe { X } > 0); +} + +#[bench] +fn bench_handler(b: &mut Bencher) { + static mut X: usize = 0; + let mut handler = Handler::new(); + unsafe { handler.replace(&move || X += 1) }; + + let n = test::black_box(ITER_COUNT); + b.iter(|| (0..n).for_each(|_| unsafe { handler.call() })); + assert!(unsafe { X } > 0); +} diff --git a/examples/scope.rs b/examples/scope.rs new file mode 100644 index 0000000..cd17570 --- /dev/null +++ b/examples/scope.rs @@ -0,0 +1,21 @@ +use clint::HandlerArray; + +static HANDLERS: HandlerArray = HandlerArray::new(); + +fn main() { + let mut cl = || println!("whoa!"); + + HANDLERS.with_overrides(|arr| { + arr.register(0, &mut cl); + + dummy_int(); + dummy2_int(); + }); +} + +fn dummy_int() { + HANDLERS.call(0) +} +fn dummy2_int() { + HANDLERS.call(1) +} diff --git a/src/array.rs b/src/array.rs new file mode 100644 index 0000000..e01fe74 --- /dev/null +++ b/src/array.rs @@ -0,0 +1,171 @@ +//! Safe wrapper for closure-based interrupt handlers. +//! +//! # Notes +//! +//! The number of entries allowed is defined by Cargo features. The +//! default is 32 as this seems a reasonable comprimise between the +//! size of the array and utility. Each array entry costs two words of +//! space for the closure reference. Thus a full array of 256 entries +//! on a 32-bit architecture costs 2048 bytes of memory, which can be +//! quite a lot on resource constrained devices. +//! +//! One day, when const-generics are stabilized, this will be more +//! elegant. +//! +//! # Examples +//! +//! ``` no_run +//! use clint::HandlerArray; +//! use cortex_m_rt::exception; +//! +//! static HANDLERS: HandlerArray = HandlerArray::new(); +//! +//! fn main() { +//! // NB: This closure has to be created outside of `with_overrides` to +//! // ensure it lives as long as `with_overrides` scope lasts. +//! let mut cl = || { +//! // Your interrupt handling code. +//! }; +//! HANDLERS.with_overrides(|arr| { +//! arr.register(0, &mut cl); +//! +//! loop { +//! // Your main loop. +//! } +//! }) +//! } +//! +//! #[exception] +//! fn SysTick() { +//! HANDLERS.call(0); +//! } +//! ``` + +use crate::cs::{CriticalSection, Locker}; +use crate::Handler; + +use core::cell::UnsafeCell; + +// Define features for the underlying array size so that we can +// statically allocate it. +// TODO: Use const generics when available. +#[cfg(feature = "isr-8")] +const NR_ISR: usize = 8; +#[cfg(feature = "isr-16")] +const NR_ISR: usize = 16; +#[cfg(feature = "isr-32")] +const NR_ISR: usize = 32; +#[cfg(feature = "isr-64")] +const NR_ISR: usize = 64; +#[cfg(feature = "isr-128")] +const NR_ISR: usize = 128; +#[cfg(feature = "isr-256")] +const NR_ISR: usize = 256; + +/// Safely use `Handler`s by enclosing them in an array. +/// +/// This type provides a safe wrapper around `Handler` by ensuring +/// that closures are swapped safely using critical sections, and that +/// the lifetime of those handlers is sufficient by using the inner +/// scope of `with_overrides`/`lock_overrides`. +#[derive(Debug)] +pub struct HandlerArray<'a> { + h: UnsafeCell<[Handler<'a>; NR_ISR]>, +} + +impl<'a> HandlerArray<'a> { + /// Create a new `HandlerArray` filled with no-op handlers. + pub const fn new() -> Self { + Self { + h: UnsafeCell::new([Handler::new(); NR_ISR]), + } + } + + /// Register `f` for entry `nr` in this array using the default + /// critical section locker. + pub fn register(&self, nr: usize, f: &'a mut F) + where + F: FnMut() + Send + 'a, + { + self.lock_register(&Locker::new(), nr, f) + } + + /// Register `f` for entry `nr` in this array using `cs` to create + /// a critical section for updating the array. + pub fn lock_register(&self, cs: &CS, nr: usize, f: &'a mut F) + where + F: FnMut() + Send + 'a, + CS: CriticalSection, + { + cs.with_lock(|| unsafe { (*self.h.get())[nr].replace(f) }); + } + + /// Call the handler for entry `nr`. + pub fn call(&self, nr: usize) { + // Unsafe: there's always a valid handler to call except for + // when it's being actively replaced. As long as that happens + // while in a critical section, there's no risk of data races. + unsafe { (*self.h.get())[nr].call() } + } + + /// Create a new array for use in `f`'s scope. The existing + /// handlers can be overridden using `register` or + /// `lock_register`. When `f` exits, all previous handlers are + /// restored. + pub fn with_overrides<'b>(&self, f: impl FnOnce(&HandlerArray<'b>)) { + self.lock_overrides(&Locker::new(), f) + } + + /// Same as `with_overrides` but allows you to specify your own + /// implementation of `CriticalSection` instead of using the + /// default. + pub fn lock_overrides<'b, CS>(&self, cs: &CS, f: impl FnOnce(&HandlerArray<'b>)) + where + CS: CriticalSection, + { + // Create a shorter-lived array from `self` that matches the + // lifetime of `f` so we can make sure `register` is only + // called with closures that will live as long as `f` does. + // + // Unsafe: This requires that we back up and restore the handlers + // in the array to make sure there's always something alive in + // whatever the real scope of `array' is. + let tmp: &HandlerArray<'b> = unsafe { core::mem::transmute(self) }; + + // Back up old handlers before entering inner scope so we can + // restore them on exit. + let bk = HandlerArray::new(); + unsafe { core::ptr::copy_nonoverlapping(tmp.h.get(), bk.h.get(), 1) } + f(tmp); + + // Put the old handlers back inside a critical section to avoid + // data races. + cs.with_lock(|| unsafe { core::ptr::copy_nonoverlapping(bk.h.get(), tmp.h.get(), 1) }); + } +} + +// Unsafe: as long as `register` and `with_overrides` use critical +// sections appropriately, it should be safe to share this between +// threads. +unsafe impl<'a> Sync for HandlerArray<'a> {} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn overrides_unwind() { + static mut CALLS: usize = 0; + let mut cl = || unsafe { CALLS += 1 }; + let cl_ref = &mut cl; + + let ht = HandlerArray::new(); + ht.with_overrides(|t| { + t.register(0, cl_ref); + ht.call(0); + }); + unsafe { assert_eq!(CALLS, 1) }; + ht.call(0); + unsafe { assert_eq!(CALLS, 1) }; + } +} diff --git a/src/cs/cortex.rs b/src/cs/cortex.rs new file mode 100644 index 0000000..cc97a12 --- /dev/null +++ b/src/cs/cortex.rs @@ -0,0 +1,20 @@ +use super::CriticalSection; + +use cortex_m::interrupt; + +pub struct Locker {} + +impl Locker { + pub const fn new() -> Self { + Self {} + } +} + +impl CriticalSection for Locker { + fn with_lock(&self, f: F) -> R + where + F: FnOnce() -> R, + { + interrupt::free(|_cs| f()) + } +} diff --git a/src/cs/dummy.rs b/src/cs/dummy.rs new file mode 100644 index 0000000..86f4e90 --- /dev/null +++ b/src/cs/dummy.rs @@ -0,0 +1,18 @@ +use super::CriticalSection; + +pub struct Locker {} + +impl Locker { + pub const fn new() -> Self { + Self {} + } +} + +impl CriticalSection for Locker { + fn with_lock(&self, f: F) -> R + where + F: FnOnce() -> R, + { + f() + } +} diff --git a/src/cs/mod.rs b/src/cs/mod.rs new file mode 100644 index 0000000..8f97b6e --- /dev/null +++ b/src/cs/mod.rs @@ -0,0 +1,38 @@ +//! Critical Section support. +//! +//! Types that implement the `CriticalSection` trait can be used to +//! create critical sections to prevent data races between interrupt +//! and non-interrupt contexts. +//! +//! # Note +//! +//! Critical sections are only acquired for updating a +//! `HandlerTable`'s entries. They are *not* used when calling into a +//! closure. This is because the expected implementation of critical +//! sections turns off interrupts entirely. Given that interrupts are +//! off, it is impossible to call an ISR, and thus no data race can +//! occur. Additionally, because critcal sections are only enforced on +//! updates, deadlock is impossible between updating a `HandlerTable` +//! entry and calling into it. +//! +//! However, if you are going to implement your own `CriticalSection`, +//! you need to be aware of this limitation and its rationale to avoid +//! getting into trouble. + +/// Generic trait which supplies the ability to create a critical +/// section. +pub trait CriticalSection { + /// Execute `f` within a critical section. + fn with_lock(&self, f: F) -> R + where + F: FnOnce() -> R; +} + +#[cfg_attr(any(all(target_arch = "arm", target_os = "none")), path = "cortex.rs")] +#[cfg_attr( + not(any(all(target_arch = "arm", target_os = "none"))), + path = "dummy.rs" +)] +mod csimpl; + +pub use csimpl::Locker; diff --git a/src/fnnop.rs b/src/fnnop.rs new file mode 100644 index 0000000..871a6ab --- /dev/null +++ b/src/fnnop.rs @@ -0,0 +1,19 @@ +// This module needs the following features. +// +//#![feature(unboxed_closures)] +//#![feature(fn_traits)] + +pub struct FnNOP(); + +impl Fn<()> for FnNOP { + extern "rust-call" fn call(&self, _args: ()) {} +} +impl FnMut<()> for FnNOP { + extern "rust-call" fn call_mut(&mut self, _args: ()) {} +} +impl FnOnce<()> for FnNOP { + type Output = (); + extern "rust-call" fn call_once(self, _args: ()) {} +} + +static mut NOP: FnNOP = FnNOP(); diff --git a/src/handler.rs b/src/handler.rs new file mode 100644 index 0000000..ec1f22e --- /dev/null +++ b/src/handler.rs @@ -0,0 +1,185 @@ +//! Call closures from interrupt handlers. +//! +//! # Motivation +//! +//! Existing solutions for interrupt handlers typically revolve around +//! wrapping resources needed by the handler in an `Option`, wrapped +//! in a `RefCell` wrapped in an `Mutex`, incurring some run-time +//! overhead every time the resource is required in the interrupt +//! handler, in addition to a fair amount of boilerplate. This module +//! attempts to leverage Rust's borrow checker and move semantics to +//! allow interrupt handlers to directly use their resources with a +//! minimum of overhead. +//! +//! To accomplish this, we use a closure which is called by an +//! interrupt handler. Because the closure has access to its +//! environment, we can use `move`, references, and mutable references +//! to ensure that variables are available as necessary to the +//! interrupt handler, while leveraging the borrow checker to ensure +//! safety at compile time. The only overhead is what it takes to call +//! the closure itself. +//! +//! # Safety +//! +//! While this module endeavors to use Rust's safety guarantees to +//! allow for use of resources inside interrupt handlers, due to +//! expected use-cases, closure semantics, and how interrupt handlers +//! are invoked from hardware, certain operations cannot be done +//! safely at this level. +//! +//! Notably, for the handler to be useful when called from interrupt +//! context, it needs to be stored in a `static mut` variable. This +//! means that the closure you supply it must also be effectively +//! `static` or replaced with a longer-lived closure before it goes +//! out of scope. `Handler::default_handler()` is provided for this +//! purpose. +//! +//! Additionally, replacement of an interrupt handler's closure may +//! race with the calling of the interrupt handler's closure (i.e., +//! `Handler.replace()` may happen concurrently with +//! `Handler.call()`). You need to avoid this situation however is +//! appropriate for your code. The expected usage would be replacing +//! the handler`s closure once, while interrupts are disabled, thus +//! preventing the simultaneous replace/call problem. As this module +//! makes no assumptions about the environment in which it will be +//! used, this cannot be done for you. +//! +//! # Examples +//! +//! This example for an ARM Cortex-M system demonstrates safe usage by +//! only replacing the closure for `SYSTICK_HANDLER` inside a critical +//! section obtained by `cortex_m::interrupt::free()`, and shows how +//! it is called via the `SysTick()` function, which is called +//! directly from hardware. +//! +//! ``` no_run +//! use clint::Handler; +//! use cortex_m_rt::exception; +//! +//! static mut SYSTICK_HANDLER: Handler = Handler::new(); +//! +//! fn main() { +//! // NB: `closure` is in the lexical scope of `main`, and thus +//! // cannot go out of scope. +//! let closure = || { +//! // Your interrupt handling code. +//! }; +//! // Replace the handler for SysTick with closure while interrupts are +//! // disabled. +//! cortex_m::interrupt::free(|_| { +//! unsafe { SYSTICK_HANDLER.replace(&closure) }; +//! }); +//! +//! loop { +//! // Your main loop. +//! } +//! } +//! +//! #[exception] +//! fn SysTick() { +//! unsafe { SYSTICK_HANDLER.call() }; +//! } +//! ``` + +pub struct Handler<'a> { + // Handler that will be executed on `call`. + h: *const dyn FnMut(), + lifetime: core::marker::PhantomData<&'a dyn FnMut()>, +} + +impl<'a> Handler<'a> { + /// Returns a new Handler that initially does nothing when + /// called. Override its behavior by using `replace`. + pub const fn new() -> Self { + Self { + h: &Self::default_handler, + lifetime: core::marker::PhantomData, + } + } + + /// Replace the behavior of this handler with `f`. + /// + /// # Safety + /// + /// There is no exclusion on replacing the handler's behavior + /// while it is being executed. It is your responsibility to make + /// sure that it's not being executed when you call `replace`. + pub unsafe fn replace(&mut self, f: &(dyn FnMut() + Send + 'a)) { + self.h = core::mem::transmute::<_, &'a _>(f); + } + + /// Execute this handler. + /// + /// # Safety + /// + /// This function assumes that a replace is not occurring when the + /// closure is being looked up. You need to ensure that `replace` + /// and `call` can not occur at the same time. + pub unsafe fn call(&self) { + let f: &mut dyn FnMut() = &mut *(self.h as *mut dyn FnMut()); + f(); + } + + /// Do nothing handler. Needed by `call` until `replace` is used + /// to set specific behavior. Can also be used to replace a + /// closure that is about to go out of scope. + pub fn default_handler() {} +} + +impl<'a> core::fmt::Debug for Handler<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let (f0, f1) = unsafe { core::mem::transmute::<_, (usize, usize)>(self.h) }; + write!(f, "Handler{{ h: (0x{:x}, 0x{:x}) }}", f0, f1) + } +} + +// FIXME: This probably shouldn't be Copy/Clone, but it needs to be in +// order for array initialization to work with [Handler::new(); 32]. +impl<'a> core::marker::Copy for Handler<'a> {} +impl<'a> core::clone::Clone for Handler<'a> { + fn clone(&self) -> Self { + *self + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn replace() { + static mut X: usize = 0; + + let mut handler = Handler::new(); + unsafe { + handler.replace(&|| X += 1); + assert_eq!(X, 0); + handler.call(); + handler.call(); + assert_eq!(X, 2); + } + } + + #[test] + fn replace_static() { + static mut HANDLER: Handler = Handler::new(); + static mut X: usize = 0; + + unsafe { + HANDLER.replace(&|| X += 1); + assert_eq!(X, 0); + HANDLER.call(); + HANDLER.call(); + assert_eq!(X, 2); + } + } + + #[test] + fn replace_with_default() { + let mut handler = Handler::new(); + unsafe { + handler.replace(&Handler::default_handler); + handler.call() + } + } +} diff --git a/src/lib.rs b/src/lib.rs old mode 100755 new mode 100644 index 39cbfb0..8e16b26 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,209 +1,25 @@ -#![no_std] -#![feature(const_fn)] -#![feature(test)] - -//! Call closures from interrupt handlers. -//! -//! # Motivation -//! -//! Existing solutions for interrupt handlers typically revolve around -//! wrapping resources needed by the handler in an `Option`, wrapped -//! in a `RefCell` wrapped in an `Mutex`, incurring some run-time -//! overhead every time the resource is required in the interrupt -//! handler, in addition to a fair amount of boilerplate. This module -//! attempts to leverage Rust's borrow checker and move semantics to -//! allow interrupt handlers to directly use their resources with a -//! minimum of overhead. -//! -//! To accomplish this, we use a closure which is called by an -//! interrupt handler. Because the closure has access to its -//! environment, we can use `move`, references, and mutable references -//! to ensure that variables are available as necessary to the -//! interrupt handler, while leveraging the borrow checker to ensure -//! safety at compile time. The only overhead is what it takes to call -//! the closure itself. -//! -//! # Safety +//! CLosure INTerrupt handlers //! -//! While this module endeavors to use Rust's safety guarantees to -//! allow for use of resources inside interrupt handlers, due to -//! expected use-cases, closure semantics, and how interrupt handlers -//! are invoked from hardware, certain operations cannot be done -//! safely at this level. +//! Use closures as interrupt service routines to leverage Rust's +//! borrow checker for safe, exclusive usage of device peripherals and +//! other data without locking. //! -//! Notably, for the handler to be useful when called from interrupt -//! context, it needs to be stored in a `static mut` variable. This -//! means that the closure you supply it must also be effectively -//! `static` or replaced with a longer-lived closure before it goes -//! out of scope. `Handler::default_handler()` is provided for this -//! purpose. +//! # Layout //! -//! Additionally, replacement of an interrupt handler's closure may -//! race with the calling of the interrupt handler's closure (i.e., -//! `Handler.replace()` may happen concurrently with -//! `Handler.call()`). You need to avoid this situation however is -//! appropriate for your code. The expected usage would be replacing -//! the handler`s closure once, while interrupts are disabled, thus -//! preventing the simultaneous replace/call problem. As this module -//! makes no assumptions about the environment in which it will be -//! used, this cannot be done for you. +//! See [`array`'s module documentation](table/index.html#examples) for basic, +//! safe usage. //! -//! # Examples +//! The [`handler`](handler) module contains the underyling, unsafe +//! implementation. //! -//! This example for an ARM Cortex-M system demonstrates safe usage by -//! only replacing the closure for `SYSTICK_HANDLER` inside a critical -//! section obtained by `cortex_m::interrupt::free()`, and shows how -//! it is called via the `SysTick()` function, which is called -//! directly from hardware. -//! -//! ``` no_run -//! use clint::Handler; -//! use cortex_m_rt::exception; -//! -//! static mut SYSTICK_HANDLER: Handler = Handler::new(); -//! -//! fn main() { -//! // NB: `closure` is in the lexical scope of `main`, and thus -//! // cannot go out of scope. -//! let closure = || { -//! // Your interrupt handling code. -//! }; -//! // Replace the handler for SysTick with closure while interrupts are -//! // disabled. -//! cortex_m::interrupt::free(|_| { -//! unsafe { SYSTICK_HANDLER.replace(&closure) }; -//! }); -//! -//! loop { -//! // Your main loop. -//! } -//! } -//! -//! #[exception] -//! fn SysTick() { -//! unsafe { SYSTICK_HANDLER.call() }; -//! } -//! ``` - -pub struct Handler { - // Handler that will be executed on `call`. - h: *const dyn FnMut(), -} - -impl Handler { - /// Returns a new Handler that initially does nothing when - /// called. Override it's behavior by using `replace`. - pub const fn new() -> Self { - Self { - h: &Self::default_handler, - } - } +//! Critical section support is supplied by the [`cs` module](cs). - /// Replace the behavior of this handler with `f`. - /// - /// # Safety - /// - /// There is no exclusion on replacing the handler's behavior - /// while it is being executed. It is your responsibility to make - /// sure that it's not being executed when you call `replace`. - pub unsafe fn replace(&mut self, f: &F) - where - F: FnMut() + 'static, - { - self.h = f; - } - - /// Execute this handler. - /// - /// # Safety - /// - /// This function assumes that a replace is not occurring when the - /// closure is being looked up. You need to ensure that `replace` - /// and `call` can not occur at the same time. - pub unsafe fn call(&self) { - let f: &mut dyn FnMut() = &mut *(self.h as *mut dyn FnMut()); - f(); - } - - /// Do nothing handler. Needed by `call` until `replace` is used - /// to set specific behavior. Can also be used to replace a - /// closure that is about to go out of scope. - pub fn default_handler() {} -} - -impl core::fmt::Debug for Handler { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - let (f0, f1) = unsafe { core::mem::transmute::<_, (usize, usize)>(self.h) }; - write!(f, "Handler{{ h: (0x{:x}, 0x{:x}) }}", f0, f1) - } -} - -#[cfg(test)] -mod test { - extern crate test; - use super::*; - use test::Bencher; - - #[test] - fn replace() { - static mut X: usize = 0; - - let mut handler = Handler::new(); - unsafe { - handler.replace(&|| X += 1); - assert_eq!(X, 0); - handler.call(); - handler.call(); - assert_eq!(X, 2); - } - } - - #[test] - fn replace_static() { - static mut HANDLER: Handler = Handler::new(); - static mut X: usize = 0; - - unsafe { - HANDLER.replace(&|| X += 1); - assert_eq!(X, 0); - HANDLER.call(); - HANDLER.call(); - assert_eq!(X, 2); - } - } - - #[test] - fn replace_with_default() { - let mut handler = Handler::new(); - unsafe { - handler.replace(&Handler::default_handler); - handler.call() - } - } - - const ITER_COUNT: usize = 10_000; - - #[bench] - fn bench_bare_fn(b: &mut Bencher) { - static mut X: usize = 0; - #[inline(never)] - fn inc() { - unsafe { X += 1 }; - } - - let n = test::black_box(ITER_COUNT); - b.iter(|| (0..n).for_each(|_| inc())); - assert!(unsafe { X } > 0); - } +#![no_std] +#![feature(const_fn)] - #[bench] - fn bench_handler(b: &mut Bencher) { - static mut X: usize = 0; - let mut handler = Handler::new(); - unsafe { handler.replace(&move || X += 1) }; +pub mod array; +pub mod cs; +pub mod handler; - let n = test::black_box(ITER_COUNT); - b.iter(|| (0..n).for_each(|_| unsafe { handler.call() })); - assert!(unsafe { X } > 0); - } -} +pub use array::HandlerArray; +pub use handler::Handler; diff --git a/tests/compile-fail/array.rs b/tests/compile-fail/array.rs new file mode 100644 index 0000000..c5492e8 --- /dev/null +++ b/tests/compile-fail/array.rs @@ -0,0 +1,14 @@ +extern crate clint; + +use clint::cs::Locker; +use clint::HandlerArray; + +fn main() { + let mut hs = HandlerArray::new(); + hs.with_overrides(|new_hs| nested(new_hs)); +} + +fn nested(hs: &HandlerArray) { + let mut c = || println!("Short-lived closure."); + hs.register(0, &mut c) //~ ERROR `c` does not live long enough +} diff --git a/tests/compile-fail/handler.rs b/tests/compile-fail/handler.rs new file mode 100644 index 0000000..fdc464a --- /dev/null +++ b/tests/compile-fail/handler.rs @@ -0,0 +1,50 @@ +extern crate clint; + +use clint::Handler; + +static mut HANDLER: Handler = Handler::new(); + +fn main() { + need_move(); + borrow_error(); + no_borrow_needed(); +} + +fn need_move() { + let x = vec![1, 2, 3]; + let c = || { + println!("x(h-c): {:?}", x); //~ ERROR does not live long enough + }; + unsafe { + HANDLER.replace(&c); + HANDLER.call(); + HANDLER.call(); + } + println!("x(h-o): {:?}", x); +} + +fn borrow_error() { + let x = vec![1, 2, 3]; + let c = move || { + println!("x(h-c): {:?}", x); + }; + unsafe { + HANDLER.replace(&c); + HANDLER.call(); + HANDLER.call(); + } + println!("x(h-o): {:?}", x); //~ ERROR borrow of moved value +} + +fn no_borrow_needed() { + let x = vec![1, 2, 3]; + let c = || { + println!("x(h-c): hi!"); + }; + unsafe { + HANDLER.replace(&c); + HANDLER.call(); + HANDLER.call(); + } + println!("x(h-o): {:?}", x); +} diff --git a/tests/compile-fail/mutable.rs b/tests/compile-fail/mutable.rs deleted file mode 100644 index e888503..0000000 --- a/tests/compile-fail/mutable.rs +++ /dev/null @@ -1,51 +0,0 @@ -extern crate clint; - -use clint::Handler; - -static mut HANDLER: Handler = Handler::new(); - -fn main() { - need_move(); - borrow_error(); - no_borrow_needed(); -} - -fn need_move() { - let x = vec![1, 2, 3]; - let c = || { - //~^ ERROR closure may outlive - println!("x(h-c): {:?}", x); - }; - unsafe { - HANDLER.replace(&c); - HANDLER.call(); - HANDLER.call(); - } - println!("x(h-o): {:?}", x); -} - -fn borrow_error() { - let x = vec![1, 2, 3]; - let c = move || { - println!("x(h-c): {:?}", x); - }; - unsafe { - HANDLER.replace(&c); - HANDLER.call(); - HANDLER.call(); - } - println!("x(h-o): {:?}", x); //~ ERROR borrow of moved value -} - -fn no_borrow_needed() { - let x = vec![1, 2, 3]; - let c = || { - println!("x(h-c): hi!"); - }; - unsafe { - HANDLER.replace(&c); - HANDLER.call(); - HANDLER.call(); - } - println!("x(h-o): {:?}", x); -} -- cgit v1.2.3