mirror of
https://github.com/bspeice/qadapt
synced 2024-12-22 12:38:08 -05:00
parent
000d0032a9
commit
d9bc210ba0
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
*.swp
|
|
||||||
|
@ -17,4 +17,6 @@ repository = "https://github.com/bspeice/qadapt.git"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
spin = "0.4"
|
spin = "0.4"
|
||||||
thread-id = "3.3"
|
thread-id = "3.3"
|
||||||
|
|
||||||
|
qadapt-macro = { version = "0.3.0", path = "./qadapt-macro" }
|
3
qadapt-macro/.gitignore
vendored
Normal file
3
qadapt-macro/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/target
|
||||||
|
**/*.rs.bk
|
||||||
|
Cargo.lock
|
18
qadapt-macro/Cargo.toml
Normal file
18
qadapt-macro/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "qadapt-macro"
|
||||||
|
version = "0.3.0"
|
||||||
|
authors = ["Bradlee Speice <bradlee@speice.io>"]
|
||||||
|
description = "The Quick And Dirty Allocation Profiling Tool - Support Macros"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
readme = "README.md"
|
||||||
|
categories = [
|
||||||
|
"development-tools",
|
||||||
|
"development-tools::debugging",
|
||||||
|
"development-tools::profiling",
|
||||||
|
"development-tools::testing",
|
||||||
|
"memory-management"
|
||||||
|
]
|
||||||
|
repository = "https://github.com/bspeice/qadapt.git"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
6
qadapt-macro/README.md
Normal file
6
qadapt-macro/README.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# QADAPT - Helper macros
|
||||||
|
|
||||||
|
Helper macros to use with the QADAPT allocator system
|
||||||
|
|
||||||
|
This crate is intended for managing the QADAPT allocator,
|
||||||
|
and is unusable on its own.
|
136
qadapt-macro/src/lib.rs
Normal file
136
qadapt-macro/src/lib.rs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
//! Helper macros to use with the QADAPT allocator system
|
||||||
|
//!
|
||||||
|
//! This crate is intended for managing the QADAPT allocator,
|
||||||
|
//! and is unusable on its own.
|
||||||
|
//!
|
||||||
|
// TODO: This causes issues, but I can't track down why
|
||||||
|
// #![deny(missing_docs)]
|
||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
use proc_macro::Delimiter;
|
||||||
|
use proc_macro::Spacing;
|
||||||
|
use proc_macro::Span;
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use proc_macro::TokenTree;
|
||||||
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
|
type TT = proc_macro::TokenTree;
|
||||||
|
type TS = proc_macro::TokenStream;
|
||||||
|
type G = proc_macro::Group;
|
||||||
|
type I = proc_macro::Ident;
|
||||||
|
type P = proc_macro::Punct;
|
||||||
|
|
||||||
|
fn release_guard(fn_name: &str) -> TokenStream {
|
||||||
|
let rel = I::new("release", Span::call_site());
|
||||||
|
let not_rel: Vec<TokenTree> = vec![
|
||||||
|
I::new("not", Span::call_site()).into(),
|
||||||
|
G::new(Delimiter::Parenthesis, TokenTree::Ident(rel).into()).into()
|
||||||
|
];
|
||||||
|
let cfg_not_rel: Vec<TokenTree> = vec![
|
||||||
|
I::new("cfg", Span::call_site()).into(),
|
||||||
|
G::new(Delimiter::Parenthesis, TS::from_iter(not_rel.into_iter())).into()
|
||||||
|
];
|
||||||
|
let guarded: Vec<TokenTree> = vec![
|
||||||
|
P::new('#', Spacing::Alone).into(),
|
||||||
|
G::new(Delimiter::Bracket, TS::from_iter(cfg_not_rel.into_iter())).into(),
|
||||||
|
P::new(':', Spacing::Joint).into(),
|
||||||
|
P::new(':', Spacing::Alone).into(),
|
||||||
|
I::new("qadapt", Span::call_site()).into(),
|
||||||
|
P::new(':', Spacing::Joint).into(),
|
||||||
|
P::new(':', Spacing::Alone).into(),
|
||||||
|
I::new(fn_name, Span::call_site()).into(),
|
||||||
|
G::new(Delimiter::Parenthesis, TokenStream::new()).into(),
|
||||||
|
];
|
||||||
|
|
||||||
|
TS::from_iter(guarded.into_iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn protected_body(fn_name: &str, args: G) -> TokenTree {
|
||||||
|
let mut args_filtered = Vec::new();
|
||||||
|
let mut waiting_for_comma = false;
|
||||||
|
let mut in_type = 0;
|
||||||
|
for tt in args.stream().into_iter() {
|
||||||
|
match tt {
|
||||||
|
TokenTree::Ident(ref _i) if !waiting_for_comma && in_type == 0 => {
|
||||||
|
args_filtered.push(tt.clone());
|
||||||
|
waiting_for_comma = true;
|
||||||
|
}
|
||||||
|
TokenTree::Punct(ref p) if p.as_char() == '<' => in_type += 1,
|
||||||
|
TokenTree::Punct(ref p) if p.as_char() == '>' => in_type -= 1,
|
||||||
|
TokenTree::Punct(ref p) if p.as_char() == ',' && in_type == 0 => {
|
||||||
|
waiting_for_comma = false;
|
||||||
|
args_filtered.push(tt.clone())
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let args_group = G::new(Delimiter::Parenthesis, TS::from_iter(args_filtered));
|
||||||
|
|
||||||
|
let tt: Vec<TT> = vec![
|
||||||
|
G::new(Delimiter::Brace, release_guard("enter_protected")).into(),
|
||||||
|
I::new("let", Span::call_site()).into(),
|
||||||
|
I::new("__ret__", Span::call_site()).into(),
|
||||||
|
P::new('=', Spacing::Alone).into(),
|
||||||
|
I::new(fn_name, Span::call_site()).into(),
|
||||||
|
args_group.into(),
|
||||||
|
P::new(';', Spacing::Alone).into(),
|
||||||
|
G::new(Delimiter::Brace, release_guard("exit_protected")).into(),
|
||||||
|
I::new("__ret__", Span::call_site()).into(),
|
||||||
|
];
|
||||||
|
|
||||||
|
G::new(Delimiter::Brace, TS::from_iter(tt)).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set up the QADAPT allocator to trigger a panic if any allocations happen during
|
||||||
|
/// calls to this function.
|
||||||
|
///
|
||||||
|
/// QADAPT will only track allocations in the thread that calls this function;
|
||||||
|
/// if (for example) this function receives the results of an allocation in a
|
||||||
|
/// separate thread, QADAPT will not trigger a panic.
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn allocate_panic(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
|
let mut original_fn: Vec<TokenTree> = Vec::new();
|
||||||
|
let mut protected_fn: Vec<TokenTree> = Vec::new();
|
||||||
|
|
||||||
|
let mut item_iter = item.into_iter();
|
||||||
|
|
||||||
|
// First, get the function name we're replacing
|
||||||
|
let mut fn_name = None;
|
||||||
|
let mut fn_args = None;
|
||||||
|
while let Some(tt) = item_iter.next() {
|
||||||
|
match tt {
|
||||||
|
TokenTree::Ident(ref i) if i.to_string() == "fn" => {
|
||||||
|
original_fn.push(tt.clone());
|
||||||
|
protected_fn.push(tt.clone());
|
||||||
|
}
|
||||||
|
TokenTree::Ident(ref i) if fn_args.is_none() => {
|
||||||
|
let changed_name = format!("__{}__", i.to_owned());
|
||||||
|
original_fn.push(TokenTree::Ident(I::new(&changed_name, Span::call_site())));
|
||||||
|
protected_fn.push(tt.clone());
|
||||||
|
fn_name = Some(changed_name);
|
||||||
|
}
|
||||||
|
TokenTree::Group(ref g) if g.delimiter() == Delimiter::Parenthesis && fn_args.is_none() => {
|
||||||
|
original_fn.push(tt.clone());
|
||||||
|
protected_fn.push(tt.clone());
|
||||||
|
fn_args = Some(g.clone());
|
||||||
|
}
|
||||||
|
TokenTree::Group(ref g) if g.delimiter() == Delimiter::Brace => {
|
||||||
|
original_fn.push(tt.clone());
|
||||||
|
protected_fn.push(protected_body(
|
||||||
|
&fn_name.take().unwrap(),
|
||||||
|
fn_args.take().unwrap(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
tt => {
|
||||||
|
original_fn.push(tt.clone());
|
||||||
|
protected_fn.push(tt.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut full = Vec::new();
|
||||||
|
full.push(TS::from_iter(original_fn));
|
||||||
|
full.push(TS::from_iter(protected_fn));
|
||||||
|
let ts = TS::from_iter(full.into_iter());
|
||||||
|
ts
|
||||||
|
}
|
41
src/lib.rs
41
src/lib.rs
@ -12,11 +12,15 @@
|
|||||||
//! for some helper macros to make working with QADAPT a bit easier.
|
//! for some helper macros to make working with QADAPT a bit easier.
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
|
extern crate qadapt_macro;
|
||||||
extern crate spin;
|
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;
|
extern crate thread_id;
|
||||||
|
|
||||||
|
// Re-export the proc macros to use by other code
|
||||||
|
pub use qadapt_macro::*;
|
||||||
|
|
||||||
use libc::c_void;
|
use libc::c_void;
|
||||||
use libc::free;
|
use libc::free;
|
||||||
use libc::malloc;
|
use libc::malloc;
|
||||||
@ -27,7 +31,6 @@ use std::thread;
|
|||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static PROTECTION_LEVEL: RwLock<usize> = RwLock::new(0);
|
static PROTECTION_LEVEL: RwLock<usize> = RwLock::new(0);
|
||||||
static IS_STARTED: RwLock<bool> = RwLock::new(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The QADAPT allocator itself
|
/// The QADAPT allocator itself
|
||||||
@ -40,10 +43,6 @@ pub fn enter_protected() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
IS_STARTED
|
|
||||||
.try_with(|b| assert!(*b.read(), "QADAPT not in use"))
|
|
||||||
.unwrap_or_else(|_e| ());
|
|
||||||
|
|
||||||
PROTECTION_LEVEL
|
PROTECTION_LEVEL
|
||||||
.try_with(|v| {
|
.try_with(|v| {
|
||||||
*v.write() += 1;
|
*v.write() += 1;
|
||||||
@ -71,17 +70,6 @@ pub fn exit_protected() {
|
|||||||
.unwrap_or_else(|_e| ());
|
.unwrap_or_else(|_e| ());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given a closure, run a function inside a guarded allocation block
|
|
||||||
pub fn do_protected<T, C>(mut c: C) -> T
|
|
||||||
where
|
|
||||||
C: FnMut() -> T
|
|
||||||
{
|
|
||||||
enter_protected();
|
|
||||||
let ret = c();
|
|
||||||
exit_protected();
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
||||||
@ -120,9 +108,6 @@ unsafe impl GlobalAlloc for QADAPT {
|
|||||||
return malloc(layout.size()) as *mut u8;
|
return malloc(layout.size()) as *mut u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
IS_STARTED.try_with(|b| *b.write() = true)
|
|
||||||
.unwrap_or_else(|_e| ());
|
|
||||||
|
|
||||||
// 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 spin until we can claim the INTERNAL_ALLOCATION lock for our thread.
|
||||||
claim_internal_alloc();
|
claim_internal_alloc();
|
||||||
@ -146,7 +131,7 @@ unsafe impl GlobalAlloc for QADAPT {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
|
||||||
if alloc_immediate() {
|
if alloc_immediate() {
|
||||||
return free(ptr as *mut c_void);
|
return free(ptr as *mut c_void);
|
||||||
}
|
}
|
||||||
@ -163,27 +148,15 @@ unsafe impl GlobalAlloc for QADAPT {
|
|||||||
// Tripped a bad dealloc, but make sure further memory access during unwind
|
// Tripped a bad dealloc, 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!(
|
||||||
"Unexpected deallocation for size {}, protection level: {}",
|
"Unexpected deallocation for size {}, protection level: {}",
|
||||||
layout.size(),
|
layout.size(),
|
||||||
v
|
v
|
||||||
)
|
)
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! alloc_panic{
|
|
||||||
($fn_body:expr) => {{
|
|
||||||
#[cfg(any(test, debug))]
|
|
||||||
{
|
|
||||||
::qadapt::do_protected(|| $fn_body)
|
|
||||||
}
|
|
||||||
#[cfg(not(any(test, debug)))]
|
|
||||||
{
|
|
||||||
$fn_body
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#[macro_use]
|
#![feature(asm)]
|
||||||
extern crate qadapt;
|
extern crate qadapt;
|
||||||
|
|
||||||
use qadapt::enter_protected;
|
use qadapt::enter_protected;
|
||||||
@ -9,14 +9,17 @@ use qadapt::QADAPT;
|
|||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
static Q: QADAPT = QADAPT;
|
static Q: QADAPT = QADAPT;
|
||||||
|
|
||||||
|
pub fn black_box<T>(dummy: T) -> T {
|
||||||
|
// Taken from test lib, need to mark the arg as non-introspectable
|
||||||
|
unsafe { asm!("" : : "r"(&dummy)) }
|
||||||
|
dummy
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_copy() {
|
fn test_copy() {
|
||||||
let z = alloc_panic!({
|
enter_protected();
|
||||||
let zero = 0;
|
black_box(0u8);
|
||||||
zero
|
exit_protected();
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(z, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -38,8 +41,16 @@ fn unit_result(b: bool) -> Result<(), ()> {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_unit_result() {
|
fn test_unit_result() {
|
||||||
enter_protected();
|
enter_protected();
|
||||||
unit_result(true).unwrap();
|
#[allow(unused)]
|
||||||
unit_result(false).unwrap_err();
|
{
|
||||||
|
black_box(unit_result(true));
|
||||||
|
}
|
||||||
|
black_box(unit_result(true)).unwrap();
|
||||||
|
#[allow(unused)]
|
||||||
|
{
|
||||||
|
black_box(unit_result(false));
|
||||||
|
}
|
||||||
|
black_box(unit_result(false)).unwrap_err();
|
||||||
exit_protected();
|
exit_protected();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,14 +80,14 @@ fn test_vec_push_capacity() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_vec_with_zero() {
|
fn test_vec_with_zero() {
|
||||||
enter_protected();
|
enter_protected();
|
||||||
let _v: Vec<u8> = Vec::with_capacity(0);
|
let _v: Vec<u8> = black_box(Vec::with_capacity(0));
|
||||||
exit_protected();
|
exit_protected();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vec_new() {
|
fn test_vec_new() {
|
||||||
enter_protected();
|
enter_protected();
|
||||||
let _v: Vec<u8> = Vec::new();
|
let _v: Vec<u8> = black_box(Vec::new());
|
||||||
exit_protected();
|
exit_protected();
|
||||||
}
|
}
|
||||||
|
|
89
tests/macros.rs
Normal file
89
tests/macros.rs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
extern crate qadapt;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use qadapt::allocate_panic;
|
||||||
|
use qadapt::QADAPT;
|
||||||
|
|
||||||
|
#[global_allocator]
|
||||||
|
static Q: QADAPT = QADAPT;
|
||||||
|
|
||||||
|
#[allocate_panic]
|
||||||
|
fn no_allocate() {
|
||||||
|
#[cfg(not(release))]
|
||||||
|
{
|
||||||
|
let _v = 0;
|
||||||
|
}
|
||||||
|
assert_eq!(::qadapt::protection_level(), 1);
|
||||||
|
let _v: Vec<()> = Vec::with_capacity(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn macro_no_allocate() {
|
||||||
|
no_allocate();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allocate_panic]
|
||||||
|
fn allocates() {
|
||||||
|
assert_eq!(::qadapt::protection_level(), 1);
|
||||||
|
// Without boxing, release profile can actually optimize out the allocation
|
||||||
|
let mut v = Box::new(Vec::new());
|
||||||
|
v.push(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn macro_allocates() {
|
||||||
|
allocates();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allocate_panic]
|
||||||
|
fn no_allocate_ret() -> bool {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn macro_return() {
|
||||||
|
assert!(no_allocate_ret());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allocate_panic]
|
||||||
|
fn no_allocate_implicit_ret() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn macro_implicit_return() {
|
||||||
|
assert!(no_allocate_implicit_ret());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allocate_panic]
|
||||||
|
fn no_allocate_arg(b: bool) -> bool {
|
||||||
|
b
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn macro_allocate_arg() {
|
||||||
|
no_allocate_arg(true);
|
||||||
|
no_allocate_arg(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allocate_panic]
|
||||||
|
fn no_allocate_args(_b: bool, _u: usize, i: i64) -> i64 {
|
||||||
|
i
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn macro_allocate_args() {
|
||||||
|
no_allocate_args(true, 0, -1);
|
||||||
|
no_allocate_args(false, 4, -90);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allocate_panic]
|
||||||
|
fn return_result(r: Result<usize, io::Error>) -> Result<Result<usize, io::Error>, ()> {
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn macro_return_result() {
|
||||||
|
return_result(Ok(16)).unwrap().unwrap();
|
||||||
|
}
|
@ -1,7 +0,0 @@
|
|||||||
extern crate qadapt;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic]
|
|
||||||
fn panic_not_using_qadapt() {
|
|
||||||
::qadapt::enter_protected();
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user