1
0
mirror of https://github.com/bspeice/qadapt synced 2024-11-21 21:38:10 -05:00

Merge pull request #1 from bspeice/edition_upgrade

Edition upgrade
This commit is contained in:
bspeice 2018-12-07 05:15:50 +00:00 committed by GitHub
commit 9b63f71f63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 191 additions and 86 deletions

View File

@ -23,11 +23,16 @@ matrix:
# Uniquely identifies the build that uploads to crates.io # Uniquely identifies the build that uploads to crates.io
- env: CRATESIO=TRUE - env: CRATESIO=TRUE
rust: nightly rust: nightly
- rust: nightly - rust: 1.31.0
# To build against specific Rust versions, include an item like the following:
# - rust: 1.30.0
- rust: nightly - rust: nightly
os: osx os: osx
- rust: stable
os: osx
- rust: beta
os: osx
- rust: nightly
- rust: stable
- rust: beta
before_install: before_install:
- set -e - set -e

View File

@ -13,17 +13,14 @@ categories = [
"memory-management" "memory-management"
] ]
repository = "https://github.com/bspeice/qadapt.git" repository = "https://github.com/bspeice/qadapt.git"
edition = "2018"
[badges] [badges]
maintenance = { status = "actively-developed" } maintenance = { status = "actively-developed" }
[dependencies] [dependencies]
libc = "0.2" libc = "0.2"
log = "0.4" spin = { git = "https://github.com/bspeice/spin-rs.git" }
spin = "0.4"
thread-id = "3.3" thread-id = "3.3"
qadapt-macro = { version = "0.7.1", path = "./qadapt-macro" } qadapt-macro = { version = "0.7.1", path = "./qadapt-macro" }
[dev-dependencies]
env_logger = "0.6"

View File

@ -13,7 +13,7 @@ readme: README.md
README.md: src/lib.rs README.md: src/lib.rs
@sed -i '/---/q' README.md @sed -i '/---/q' README.md
@cat src/lib.rs | grep '//!' | sed 's/^\/\/\! *//g' >> README.md @cat src/lib.rs | grep '//!' | sed -E 's/^\/\/\! ?//g' >> README.md
.PHONY: doc .PHONY: doc
doc: readme contributors doc: readme contributors

View File

@ -6,17 +6,54 @@
[![travisci](https://travis-ci.org/bspeice/qadapt.svg?branch=master)](https://travis-ci.org/bspeice/qadapt) [![travisci](https://travis-ci.org/bspeice/qadapt.svg?branch=master)](https://travis-ci.org/bspeice/qadapt)
[![appveyor](https://ci.appveyor.com/api/projects/status/km1p081tkjcptn1w/branch/master?svg=true)](https://ci.appveyor.com/project/bspeice/qadapt/branch/master) [![appveyor](https://ci.appveyor.com/api/projects/status/km1p081tkjcptn1w/branch/master?svg=true)](https://ci.appveyor.com/project/bspeice/qadapt/branch/master)
--- ---
# The Quick And Dirty Allocation Profiling Tool ## `debug_assert!` for your memory usage
This allocator is a helper for writing high-performance code that is allocation/drop free; This allocator is a helper for writing high-performance code that is memory-sensitive;
for functions annotated with `#[allocate_panic]`, QADAPT will detect when allocations/drops a thread panic will be triggered if a function annotated with `#[no_alloc]`,
happen during their execution (and execution of any functions they call) and throw a or code inside an `assert_no_alloc!` macro interacts with the allocator in any way.
thread panic if this occurs. QADAPT-related code is *stripped out during release builds*, Wanton allocations and unforeseen drops no more - this library lets you focus on
so no worries about random allocations crashing in production. writing code without worrying if Rust properly managed to inline the variable into the stack.
Currently this crate is Nightly-only, but will work once `const fn` is in Stable. Now, an allocator blowing up in production is a scary thought; that's why QADAPT
is designed to strip its own code out whenever you're running with a release build.
Just like the [`debug_assert!` macro](https://doc.rust-lang.org/std/macro.debug_assert.html)
in Rust's standard library, it's safe to use without worrying about a unforeseen
circumstance causing your application to crash.
Please also take a look at [qadapt-macro](https://github.com/bspeice/qadapt/tree/master/qadapt-macro) # Usage
for some helper macros to make working with QADAPT a bit easier.
Actually making use of QADAPT is straight-forward. To set up the allocator,
place the following snippet in either your program binaries (main.rs) or tests:
```rust,ignore
use qadapt::QADAPT;
#[global_allocator]
static Q: QADAPT = QADAPT;
```
After that, there are two ways of telling QADAPT that it should trigger a panic:
1. Annotate functions with the `#[no_alloc]` proc macro:
```rust,no_run
use qadapt::no_alloc;
#[no_alloc]
fn do_math() -> u8 {
2 + 2
}
```
2. Evaluate expressions with the `assert_no_alloc!` macro
```rust,no_run
use qadapt::assert_no_alloc;
fn do_work() {
// This code is allowed to trigger an allocation
let b = Box::new(8);
// This code would panic if an allocation occurred inside it
let x = assert_no_alloc!(*b + 2);
assert_eq!(x, 10);
}

View File

@ -11,6 +11,10 @@ environment:
# RUST_VERSION: nightly # RUST_VERSION: nightly
- TARGET: x86_64-pc-windows-msvc - TARGET: x86_64-pc-windows-msvc
RUST_VERSION: nightly RUST_VERSION: nightly
- TARGET: x86_64-pc-windows-msvc
RUST_VERSION: stable
- TARGET: x86_64-pc-windows-msvc
RUST_VERSION: beta
install: install:
- ps: >- - ps: >-

View File

@ -1,12 +1,10 @@
extern crate qadapt; use qadapt::no_alloc;
use qadapt::allocate_panic;
use qadapt::QADAPT; use qadapt::QADAPT;
#[global_allocator] #[global_allocator]
static Q: QADAPT = QADAPT; static Q: QADAPT = QADAPT;
#[allocate_panic] #[no_alloc]
fn does_allocate() -> Box<u8> { fn does_allocate() -> Box<u8> {
Box::new(0) Box::new(0)
} }

View File

@ -1,21 +0,0 @@
extern crate env_logger;
extern crate qadapt;
use qadapt::allocate_panic;
// Note that we're missing the `#[global_allocator]` attribute
#[allocate_panic]
fn does_allocate() -> Box<u8> {
Box::new(0)
}
fn main() {
// This code will warn that QADAPT isn't being used, but won't trigger a panic.
// Run with `RUST_LOG=warn cargo run --example setup_warning`
env_logger::init();
does_allocate();
// The warning will only trigger once though
does_allocate();
}

View File

@ -13,6 +13,7 @@ categories = [
"memory-management" "memory-management"
] ]
repository = "https://github.com/bspeice/qadapt.git" repository = "https://github.com/bspeice/qadapt.git"
edition = "2018"
[badges] [badges]
maintenance = { status = "actively-developed" } maintenance = { status = "actively-developed" }

View File

@ -111,7 +111,7 @@ fn escape_return(ts: TokenStream) -> TokenStream {
let mut tt_iter = ts.into_iter(); let mut tt_iter = ts.into_iter();
while let Some(tt) = tt_iter.next() { while let Some(tt) = tt_iter.next() {
let mut tokens = match tt { let tokens = match tt {
TokenTree::Group(ref g) if g.delimiter() == Delimiter::Brace && !in_closure => { TokenTree::Group(ref g) if g.delimiter() == Delimiter::Brace && !in_closure => {
vec![group!(Delimiter::Brace, escape_return(g.stream()))] vec![group!(Delimiter::Brace, escape_return(g.stream()))]
} }
@ -155,7 +155,7 @@ fn escape_return(ts: TokenStream) -> TokenStream {
/// separate thread, or defers allocations via closure/Future, those results /// separate thread, or defers allocations via closure/Future, those results
/// will not trigger an error. /// will not trigger an error.
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn allocate_panic(_attr: TokenStream, item: TokenStream) -> TokenStream { pub fn no_alloc(_attr: TokenStream, item: TokenStream) -> TokenStream {
let mut protected_fn: Vec<TokenTree> = Vec::new(); let mut protected_fn: Vec<TokenTree> = Vec::new();
let mut item_iter = item.into_iter(); let mut item_iter = item.into_iter();

View File

@ -1,24 +1,58 @@
//! # The Quick And Dirty Allocation Profiling Tool //! ## `debug_assert!` for your memory usage
//! //!
//! This allocator is a helper for writing high-performance code that is allocation/drop free; //! This allocator is a helper for writing high-performance code that is memory-sensitive;
//! for functions annotated with `#[allocate_panic]`, QADAPT will detect when allocations/drops //! a thread panic will be triggered if a function annotated with `#[no_alloc]`,
//! happen during their execution (and execution of any functions they call) and throw a //! or code inside an `assert_no_alloc!` macro interacts with the allocator in any way.
//! thread panic if this occurs. QADAPT-related code is *stripped out during release builds*, //! Wanton allocations and unforeseen drops no more - this library lets you focus on
//! so no worries about random allocations crashing in production. //! writing code without worrying if Rust properly managed to inline the variable into the stack.
//! //!
//! Currently this crate is Nightly-only, but will work once `const fn` is in Stable. //! Now, an allocator blowing up in production is a scary thought; that's why QADAPT
//! is designed to strip its own code out whenever you're running with a release build.
//! Just like the [`debug_assert!` macro](https://doc.rust-lang.org/std/macro.debug_assert.html)
//! in Rust's standard library, it's safe to use without worrying about a unforeseen
//! circumstance causing your application to crash.
//! //!
//! Please also take a look at [qadapt-macro](https://github.com/bspeice/qadapt/tree/master/qadapt-macro) //! # Usage
//! for some helper macros to make working with QADAPT a bit easier. //!
//! Actually making use of QADAPT is straight-forward. To set up the allocator,
//! place the following snippet in either your program binaries (main.rs) or tests:
//!
//! ```rust,ignore
//! use qadapt::QADAPT;
//!
//! #[global_allocator]
//! static Q: QADAPT = QADAPT;
//! ```
//!
//! After that, there are two ways of telling QADAPT that it should trigger a panic:
//!
//! 1. Annotate functions with the `#[no_alloc]` proc macro:
//! ```rust,no_run
//! use qadapt::no_alloc;
//!
//! #[no_alloc]
//! fn do_math() -> u8 {
//! 2 + 2
//! }
//! ```
//!
//! 2. Evaluate expressions with the `assert_no_alloc!` macro
//! ```rust,no_run
//! use qadapt::assert_no_alloc;
//!
//! fn do_work() {
//! // This code is allowed to trigger an allocation
//! let b = Box::new(8);
//!
//! // This code would panic if an allocation occurred inside it
//! let x = assert_no_alloc!(*b + 2);
//! assert_eq!(x, 10);
//! }
#![deny(missing_docs)] #![deny(missing_docs)]
extern crate libc;
#[macro_use]
extern crate log;
extern crate qadapt_macro;
extern crate spin;
// thread_id is necessary because `std::thread::current()` panics if we have not yet // thread_id is necessary because `std::thread::current()` panics if we have not yet
// allocated a `thread_local!{}` it depends on. // allocated a `thread_local!{}` it depends on.
extern crate thread_id; use thread_id;
// Re-export the proc macros to use by other code // Re-export the proc macros to use by other code
pub use qadapt_macro::*; pub use qadapt_macro::*;
@ -47,9 +81,8 @@ pub fn enter_protected() {
return; return;
} }
if *IS_ACTIVE.read() == false { if !*IS_ACTIVE.read() {
*IS_ACTIVE.write() = true; panic!("QADAPT not initialized when using allocation guards; please verify `#[global_allocator]` is set!");
warn!("QADAPT not initialized when using allocation guards; please verify `#[global_allocator]` is set!");
} }
PROTECTION_LEVEL PROTECTION_LEVEL
@ -83,17 +116,30 @@ pub fn exit_protected() {
} }
} }
/// Get the result of an expression, guaranteeing that no memory accesses occur
/// during its evaluation.
///
/// **Warning**: Unexpected behavior may occur when using the `return` keyword.
/// Because the macro cleanup logic will not be run, QADAPT may trigger a panic
/// in code that was not specifically intended to be allocation-free.
#[macro_export]
macro_rules! assert_no_alloc {
($e:expr) => {{
::qadapt::enter_protected();
let e = { $e };
::qadapt::exit_protected();
e
}};
}
static IS_ACTIVE: RwLock<bool> = RwLock::new(false); static IS_ACTIVE: RwLock<bool> = RwLock::new(false);
static INTERNAL_ALLOCATION: RwLock<usize> = RwLock::new(usize::max_value()); static INTERNAL_ALLOCATION: RwLock<usize> = RwLock::new(usize::max_value());
/// Get the current "protection level" in QADAPT: calls to enter_protected() - exit_protected() /// Get the current "protection level" in QADAPT: calls to enter_protected() - exit_protected()
pub fn protection_level() -> usize { pub fn protection_level() -> usize {
#[cfg(debug_assertions)] if cfg!(debug_assertions) {
{
PROTECTION_LEVEL.try_with(|v| *v.read()).unwrap_or(0) PROTECTION_LEVEL.try_with(|v| *v.read()).unwrap_or(0)
} } else {
#[cfg(not(debug_assertions))]
{
0 0
} }
} }
@ -123,7 +169,7 @@ fn alloc_immediate() -> bool {
unsafe impl GlobalAlloc for QADAPT { unsafe impl GlobalAlloc for QADAPT {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 { unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
if *IS_ACTIVE.read() == false { if !*IS_ACTIVE.read() {
*IS_ACTIVE.write() = true; *IS_ACTIVE.write() = true;
} }
@ -134,7 +180,7 @@ unsafe impl GlobalAlloc for QADAPT {
} }
// Because accessing PROTECTION_LEVEL has the potential to trigger an allocation, // Because accessing PROTECTION_LEVEL has the potential to trigger an allocation,
// we need to spin until we can claim the INTERNAL_ALLOCATION lock for our thread. // we need to acquire the INTERNAL_ALLOCATION lock for our thread.
claim_internal_alloc(); claim_internal_alloc();
let protection_level: Result<usize, ()> = let protection_level: Result<usize, ()> =
PROTECTION_LEVEL.try_with(|v| *v.read()).or(Ok(0)); PROTECTION_LEVEL.try_with(|v| *v.read()).or(Ok(0));
@ -170,7 +216,7 @@ unsafe impl GlobalAlloc for QADAPT {
free(ptr as *mut c_void); free(ptr as *mut c_void);
match protection_level { match protection_level {
Ok(v) if v > 0 => { Ok(v) if v > 0 => {
// Tripped a bad dealloc, but make sure further memory access during unwind // Tripped a bad drop, but make sure further memory access during unwind
// doesn't have issues // doesn't have issues
PROTECTION_LEVEL.with(|v| *v.write() = 0); PROTECTION_LEVEL.with(|v| *v.write() = 0);
panic!( panic!(

View File

@ -1,5 +1,3 @@
extern crate qadapt;
use qadapt::enter_protected; use qadapt::enter_protected;
use qadapt::exit_protected; use qadapt::exit_protected;
use qadapt::protection_level; use qadapt::protection_level;

30
tests/assert_macro.rs Normal file
View File

@ -0,0 +1,30 @@
use qadapt::assert_no_alloc;
use qadapt::QADAPT;
#[global_allocator]
static Q: QADAPT = QADAPT;
#[test]
fn math() {
let x = assert_no_alloc!(2 + 2);
assert_eq!(x, 4);
}
fn early_return() -> usize {
assert_no_alloc!(return 8)
}
fn into_box() -> Box<usize> {
Box::new(early_return())
}
#[test]
#[should_panic]
fn early_return_boxing() {
if cfg!(debug_assertions) {
// The release-mode compiler is able to optimize through the Box
into_box();
} else {
panic!("Intentional")
}
}

View File

@ -1,13 +1,12 @@
extern crate qadapt;
use std::io; use std::io;
use qadapt::allocate_panic; use qadapt::no_alloc;
use qadapt::QADAPT; use qadapt::QADAPT;
#[global_allocator] #[global_allocator]
static Q: QADAPT = QADAPT; static Q: QADAPT = QADAPT;
#[allocate_panic] #[no_alloc]
fn no_allocate() { fn no_allocate() {
let _v: Vec<()> = Vec::with_capacity(0); let _v: Vec<()> = Vec::with_capacity(0);
} }
@ -17,7 +16,7 @@ fn macro_no_allocate() {
no_allocate(); no_allocate();
} }
#[allocate_panic] #[no_alloc]
fn allocates() { fn allocates() {
assert_eq!(::qadapt::protection_level(), 1); assert_eq!(::qadapt::protection_level(), 1);
// Without boxing, release profile can actually optimize out the allocation // Without boxing, release profile can actually optimize out the allocation
@ -31,7 +30,7 @@ fn macro_allocates() {
allocates(); allocates();
} }
#[allocate_panic] #[no_alloc]
fn no_allocate_ret() -> bool { fn no_allocate_ret() -> bool {
return true; return true;
} }
@ -41,7 +40,7 @@ fn macro_return() {
assert!(no_allocate_ret()); assert!(no_allocate_ret());
} }
#[allocate_panic] #[no_alloc]
fn no_allocate_implicit_ret() -> bool { fn no_allocate_implicit_ret() -> bool {
true true
} }
@ -51,7 +50,7 @@ fn macro_implicit_return() {
assert!(no_allocate_implicit_ret()); assert!(no_allocate_implicit_ret());
} }
#[allocate_panic] #[no_alloc]
fn no_allocate_arg(b: bool) -> bool { fn no_allocate_arg(b: bool) -> bool {
b b
} }
@ -62,7 +61,7 @@ fn macro_allocate_arg() {
no_allocate_arg(false); no_allocate_arg(false);
} }
#[allocate_panic] #[no_alloc]
fn no_allocate_args(_b: bool, _u: usize, i: i64) -> i64 { fn no_allocate_args(_b: bool, _u: usize, i: i64) -> i64 {
i i
} }
@ -73,7 +72,7 @@ fn macro_allocate_args() {
no_allocate_args(false, 4, -90); no_allocate_args(false, 4, -90);
} }
#[allocate_panic] #[no_alloc]
fn return_result(r: Result<usize, io::Error>) -> Result<Result<usize, io::Error>, ()> { fn return_result(r: Result<usize, io::Error>) -> Result<Result<usize, io::Error>, ()> {
Ok(r) Ok(r)
} }
@ -83,7 +82,7 @@ fn macro_return_result() {
return_result(Ok(16)).unwrap().unwrap(); return_result(Ok(16)).unwrap().unwrap();
} }
#[allocate_panic] #[no_alloc]
fn branching_return(a: bool, b: bool, c: bool) -> u8 { fn branching_return(a: bool, b: bool, c: bool) -> u8 {
if a { if a {
if b { if b {
@ -132,7 +131,7 @@ fn run_closure(x: impl Fn(bool, bool) -> bool) -> bool {
x(true, false) x(true, false)
} }
#[allocate_panic] #[no_alloc]
fn example_closure() { fn example_closure() {
let c = run_closure(|a: bool, b| return a && b); let c = run_closure(|a: bool, b| return a && b);
assert!(!c); assert!(!c);
@ -146,7 +145,7 @@ fn macro_closure() {
} }
#[test] #[test]
#[allocate_panic] #[no_alloc]
fn macro_release_safe() { fn macro_release_safe() {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
{ {

11
tests/unused_panic.rs Normal file
View File

@ -0,0 +1,11 @@
use qadapt::enter_protected;
#[test]
#[should_panic]
fn guard_without_initialization() {
if cfg!(debug_assertions) {
enter_protected();
} else {
panic!("Intentional")
}
}