mirror of
				https://github.com/bspeice/qadapt
				synced 2025-10-31 01:20:36 -04:00 
			
		
		
		
	Don't use proc macro
I really don't like the new syntax though, much as I don't like proc_macro it may be coming back.
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +1,4 @@ | ||||
| /target | ||||
| **/*.rs.bk | ||||
| Cargo.lock | ||||
| *.swp | ||||
|  | ||||
| @ -17,6 +17,4 @@ repository = "https://github.com/bspeice/qadapt.git" | ||||
| [dependencies] | ||||
| libc = "0.2" | ||||
| spin = "0.4" | ||||
| thread-id = "3.3" | ||||
|  | ||||
| qadapt-macro = { version = "0.3.0", path = "./qadapt-macro" } | ||||
| thread-id = "3.3" | ||||
							
								
								
									
										3
									
								
								qadapt-macro/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								qadapt-macro/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +0,0 @@ | ||||
| /target | ||||
| **/*.rs.bk | ||||
| Cargo.lock | ||||
| @ -1,18 +0,0 @@ | ||||
| [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 | ||||
| @ -1,6 +0,0 @@ | ||||
| # 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. | ||||
| @ -1,136 +0,0 @@ | ||||
| //! 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,15 +12,11 @@ | ||||
| //! for some helper macros to make working with QADAPT a bit easier. | ||||
| #![deny(missing_docs)] | ||||
| extern crate libc; | ||||
| extern crate qadapt_macro; | ||||
| extern crate spin; | ||||
| // thread_id is necessary because `std::thread::current()` panics if we have not yet | ||||
| // allocated a `thread_local!{}` it depends on. | ||||
| extern crate thread_id; | ||||
|  | ||||
| // Re-export the proc macros to use by other code | ||||
| pub use qadapt_macro::*; | ||||
|  | ||||
| use libc::c_void; | ||||
| use libc::free; | ||||
| use libc::malloc; | ||||
| @ -31,6 +27,7 @@ use std::thread; | ||||
|  | ||||
| thread_local! { | ||||
|     static PROTECTION_LEVEL: RwLock<usize> = RwLock::new(0); | ||||
|     static IS_STARTED: RwLock<bool> = RwLock::new(false); | ||||
| } | ||||
|  | ||||
| /// The QADAPT allocator itself | ||||
| @ -43,6 +40,10 @@ pub fn enter_protected() { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     IS_STARTED | ||||
|         .try_with(|b| assert!(*b.read(), "QADAPT not in use")) | ||||
|         .unwrap_or_else(|_e| ()); | ||||
|  | ||||
|     PROTECTION_LEVEL | ||||
|         .try_with(|v| { | ||||
|             *v.write() += 1; | ||||
| @ -70,6 +71,17 @@ pub fn exit_protected() { | ||||
|         .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()); | ||||
|  | ||||
| /// Get the current "protection level" in QADAPT: calls to enter_protected() - exit_protected() | ||||
| @ -108,6 +120,9 @@ unsafe impl GlobalAlloc for QADAPT { | ||||
|             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, | ||||
|         // we need to spin until we can claim the INTERNAL_ALLOCATION lock for our thread. | ||||
|         claim_internal_alloc(); | ||||
| @ -131,7 +146,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() { | ||||
|             return free(ptr as *mut c_void); | ||||
|         } | ||||
| @ -148,15 +163,27 @@ unsafe impl GlobalAlloc for QADAPT { | ||||
|                 // Tripped a bad dealloc, but make sure further memory access during unwind | ||||
|                 // doesn't have issues | ||||
|                 PROTECTION_LEVEL.with(|v| *v.write() = 0); | ||||
|                 /* | ||||
|                 panic!( | ||||
|                     "Unexpected deallocation for size {}, protection level: {}", | ||||
|                     layout.size(), | ||||
|                     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 @@ | ||||
| #![feature(asm)] | ||||
| #[macro_use] | ||||
| extern crate qadapt; | ||||
| 
 | ||||
| use qadapt::enter_protected; | ||||
| @ -9,17 +9,14 @@ use qadapt::QADAPT; | ||||
| #[global_allocator] | ||||
| 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] | ||||
| fn test_copy() { | ||||
|     enter_protected(); | ||||
|     black_box(0u8); | ||||
|     exit_protected(); | ||||
|     let z = alloc_panic!({ | ||||
|         let zero = 0; | ||||
|         zero | ||||
|     }); | ||||
| 
 | ||||
|     assert_eq!(z, 0); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| @ -41,16 +38,8 @@ fn unit_result(b: bool) -> Result<(), ()> { | ||||
| #[test] | ||||
| fn test_unit_result() { | ||||
|     enter_protected(); | ||||
|     #[allow(unused)] | ||||
|     { | ||||
|         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(); | ||||
|     unit_result(true).unwrap(); | ||||
|     unit_result(false).unwrap_err(); | ||||
|     exit_protected(); | ||||
| } | ||||
| 
 | ||||
| @ -80,14 +69,14 @@ fn test_vec_push_capacity() { | ||||
| #[test] | ||||
| fn test_vec_with_zero() { | ||||
|     enter_protected(); | ||||
|     let _v: Vec<u8> = black_box(Vec::with_capacity(0)); | ||||
|     let _v: Vec<u8> = Vec::with_capacity(0); | ||||
|     exit_protected(); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| fn test_vec_new() { | ||||
|     enter_protected(); | ||||
|     let _v: Vec<u8> = black_box(Vec::new()); | ||||
|     let _v: Vec<u8> = Vec::new(); | ||||
|     exit_protected(); | ||||
| } | ||||
| 
 | ||||
| @ -1,89 +0,0 @@ | ||||
| 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(); | ||||
| } | ||||
							
								
								
									
										7
									
								
								tests/usage.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								tests/usage.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| extern crate qadapt; | ||||
|  | ||||
| #[test] | ||||
| #[should_panic] | ||||
| fn panic_not_using_qadapt() { | ||||
|     ::qadapt::enter_protected(); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user