vectorscan/alloc.rs
1/* Copyright 2022-2023 Danny McClanahan */
2/* SPDX-License-Identifier: BSD-3-Clause */
3
4//! Routines for overriding the allocators used in several components of
5//! vectorscan.
6//!
7//! # Use Cases
8//! [`set_allocator()`] will set all of the allocators at once, while the
9//! `set_*_allocator()` methods such as [`set_db_allocator()`] enable overriding
10//! allocation logic for individual components of vectorscan. In either case,
11//! `get_*_allocator()` methods such as [`get_db_allocator()`] enable
12//! introspection of the active allocator (which defaults to [`libc::malloc()`]
13//! and [`libc::free()`] if unset).
14//!
15//! ## Nonstandard Allocators
16//! These methods can be used to wrap nonstandard allocators such as
17//! [jemalloc](https://docs.rs/jemallocator) for vectorscan usage:
18//!
19//!```
20//! #[cfg(feature = "compiler")]
21//! fn main() -> Result<(), vectorscan::error::VectorscanError> {
22//! use vectorscan::{expression::*, flags::*, matchers::*};
23//! use jemallocator::Jemalloc;
24//!
25//! // Use jemalloc for all vectorscan allocations.
26//! vectorscan::alloc::set_allocator(Jemalloc.into())?;
27//!
28//! // Everything works as normal.
29//! let expr: Expression = "(he)ll".parse()?;
30//! let db = expr.compile(Flags::default(), Mode::BLOCK)?;
31//!
32//! let mut scratch = db.allocate_scratch()?;
33//!
34//! let mut matches: Vec<&str> = Vec::new();
35//! scratch
36//! .scan_sync(&db, "hello".into(), |m| {
37//! matches.push(unsafe { m.source.as_str() });
38//! MatchResult::Continue
39//! })?;
40//! assert_eq!(&matches, &["hell"]);
41//! Ok(())
42//! }
43//! # #[cfg(not(feature = "compiler"))]
44//! # fn main() {}
45//! ```
46//!
47//! ## Inspecting Live Allocations
48//! However, this module also supports inspecting live allocations with
49//! [`LayoutTracker::current_allocations()`] without overriding the allocation
50//! logic, by wrapping the standard [`System`](std::alloc::System) allocator:
51//!
52//!```
53//! #[cfg(feature = "compiler")]
54//! fn main() -> Result<(), vectorscan::error::VectorscanError> {
55//! use vectorscan::{expression::*, flags::*, database::*, alloc::*};
56//! use std::{alloc::System, mem::ManuallyDrop};
57//!
58//! // Wrap the standard Rust System allocator.
59//! let tracker = LayoutTracker::new(System.into());
60//! // Register it as the allocator for databases.
61//! assert!(set_db_allocator(tracker)?.is_none());
62//!
63//! // Create a database.
64//! let expr: Expression = "asdf".parse()?;
65//! let mut db = expr.compile(Flags::SOM_LEFTMOST, Mode::BLOCK)?;
66//!
67//! // Get the database allocator we just registered and view its live allocations:
68//! let allocs = get_db_allocator().as_ref().unwrap().current_allocations();
69//! // Verify that only the single known db was allocated:
70//! assert_eq!(1, allocs.len());
71//! let (p, _layout) = allocs[0];
72//! // .as_ref_native() and .as_mut_native() provide references to the wrapped pointer:
73//! let db_ptr: *mut NativeDb = db.as_mut_native();
74//! assert_eq!(p.as_ptr() as *mut NativeDb, db_ptr);
75//!
76//! // Demonstrate that we can actually use this pointer as a reference to the database,
77//! // although we have to be careful about shared mutable access,
78//! // so we can't run the drop code for example.
79//! let db = ManuallyDrop::new(unsafe { Database::from_native(p.as_ptr() as *mut NativeDb) });
80//! // We can inspect properties of the database with this reference:
81//! assert_eq!(db.database_size()?, 936);
82//! Ok(())
83//! }
84//! # #[cfg(not(feature = "compiler"))]
85//! # fn main() {}
86//! ```
87//!
88//! # Global State
89//! These methods mutate global process state when setting function pointers for
90//! alloc and free, so this module requires the `"alloc"` feature, which itself
91//! requires the `"static"` feature which statically links the vectorscan native
92//! library to ensure exclusive access to this global state.
93//!
94//! ## Lifetimes and Dangling Pointers
95//! These methods enable resetting the registered allocator more than once over
96//! the lifetime of the program, but trying to drop any object allocated with a
97//! previous allocator will cause an error. The
98//! [`ManuallyDrop`](mem::ManuallyDrop) and `from_native()` methods (such as
99//! [`Database::from_native()`](crate::database::Database::from_native)) can be
100//! used to manage the lifetime of objects across multiple allocators:
101//!
102//!```
103//! #[cfg(feature = "compiler")]
104//! fn main() -> Result<(), vectorscan::error::VectorscanError> {
105//! use vectorscan::{expression::*, flags::*, database::*, matchers::*, alloc::*};
106//! use std::{alloc::System, mem::ManuallyDrop};
107//!
108//! // Set the process-global allocator to use for Database instances:
109//! let tracker = LayoutTracker::new(System.into());
110//! // There was no custom allocator registered yet.
111//! assert!(set_db_allocator(tracker)?.is_none());
112//!
113//! let expr: Expression = "asdf".parse()?;
114//! // Use ManuallyDrop to avoid calling the vectorscan db free method,
115//! // since we will be invalidating the pointer by changing the allocator,
116//! // and the .try_drop() method and Drop impl both call into
117//! // whatever allocator is currently active to free the pointer, which will error.
118//! let mut db = ManuallyDrop::new(expr.compile(Flags::SOM_LEFTMOST, Mode::BLOCK)?);
119//!
120//! // Change the allocator to a fresh LayoutTracker:
121//! let tracker = set_db_allocator(LayoutTracker::new(System.into()))?.unwrap();
122//! // Get the extant allocations from the old LayoutTracker:
123//! let allocs = tracker.current_allocations();
124//! // Verify that only the single known db was allocated:
125//! assert_eq!(1, allocs.len());
126//! let (p, layout) = allocs[0];
127//! let db_ptr: *mut NativeDb = db.as_mut_native();
128//! assert_eq!(p.as_ptr() as *mut NativeDb, db_ptr);
129//!
130//! // Despite having reset the allocator, our previous db is still valid
131//! // and can be used for matching:
132//! let mut scratch = db.allocate_scratch()?;
133//! let mut matches: Vec<&str> = Vec::new();
134//! scratch.scan_sync(&db, "asdf asdf".into(), |m| {
135//! matches.push(unsafe { m.source.as_str() });
136//! MatchResult::Continue
137//! })?;
138//! assert_eq!(&matches, &["asdf", "asdf"]);
139//!
140//! // We can deserialize something from somewhere else into the db handle:
141//! let expr: Literal = "hello".parse()?;
142//! let serialized_db = expr.compile(Flags::SOM_LEFTMOST, Mode::BLOCK)?.serialize()?;
143//! // Ensure the allocated database is large enough to contain the deserialized one:
144//! assert!(layout.size() >= serialized_db.deserialized_size()?);
145//! // NB: overwrite the old database!
146//! unsafe { serialized_db.deserialize_db_at(db.as_mut_native())?; }
147//!
148//! // Reuse the same database object now:
149//! scratch.setup_for_db(&db)?;
150//! matches.clear();
151//! scratch.scan_sync(&db, "hello hello".into(), |m| {
152//! matches.push(unsafe { m.source.as_str() });
153//! MatchResult::Continue
154//! })?;
155//! assert_eq!(&matches, &["hello", "hello"]);
156//!
157//! // Deallocate the db by hand here (ensure no other handles point to it):
158//! tracker.deallocate(p);
159//! // NB: `db` is now INVALID and points to FREED MEMORY!!!
160//! Ok(())
161//! }
162//! # #[cfg(not(feature = "compiler"))]
163//! # fn main() {}
164//! ```
165//!
166//! # Allocation Failures
167//! Allocation failure should cause vectorscan methods to fail with
168//! [`VectorscanRuntimeError::NoMem`]:
169//!
170//!```
171//! #[cfg(feature = "compiler")]
172//! fn main() -> Result<(), vectorscan::error::VectorscanError> {
173//! use vectorscan::{expression::*, flags::*, matchers::*, alloc::*, error::*};
174//! use std::{alloc::{GlobalAlloc, Layout}, mem::ManuallyDrop, ptr};
175//!
176//! let expr: Expression = "asdf".parse()?;
177//! // Wrap in ManuallyDrop because we will be clobbering the allocator,
178//! // including the free methods.
179//! let db = ManuallyDrop::new(expr.compile(Flags::SOM_LEFTMOST, Mode::BLOCK)?);
180//!
181//! struct BadAllocator;
182//! unsafe impl GlobalAlloc for BadAllocator {
183//! unsafe fn alloc(&self, _layout: Layout) -> *mut u8 { ptr::null_mut() }
184//! // If we wanted to cover allocations made before registering this one,
185//! // we could fall back to libc::free() for unrecognized pointers.
186//! unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {}
187//! }
188//!
189//! set_allocator(BadAllocator.into())?;
190//!
191//! // Most allocation methods fail with NoMem:
192//! assert!(matches!(
193//! db.allocate_scratch(),
194//! Err(VectorscanRuntimeError::NoMem),
195//! ));
196//!
197//! // Compile allocation errors fail slightly differently:
198//! match expr.compile(Flags::SOM_LEFTMOST, Mode::BLOCK) {
199//! Err(VectorscanCompileError::Compile(CompileError { message, expression })) => {
200//! assert!(message == "Unable to allocate memory.");
201//! assert!(expression == Some(ExpressionIndex(0)));
202//! },
203//! _ => unreachable!(),
204//! }
205//! Ok(())
206//! }
207//! # #[cfg(not(feature = "compiler"))]
208//! # fn main() {}
209//! ```
210
211use crate::{error::VectorscanRuntimeError, hs};
212
213use indexmap::IndexMap;
214use libc;
215use parking_lot::RwLock;
216
217use std::{
218 alloc::{GlobalAlloc, Layout},
219 mem, ops,
220 os::raw::c_void,
221 ptr::{self, NonNull},
222 sync::Arc,
223};
224
225/// The alloc/free interface required by vectorscan methods.
226///
227/// This is named "malloc-like" because it mirrors the interface provided by
228/// [`libc::malloc()`] and [`libc::free()`]. This differs from [`GlobalAlloc`]
229/// in two ways:
230/// - no [`Layout`] argument is provided to the deallocate method, so the
231/// implementation must record the size of each allocation somewhere.
232/// - only a `size` argument is provided to the allocate method, with the
233/// *alignment* requirements of the memory being relegated to comments and
234/// docstrings.
235pub trait MallocLikeAllocator {
236 /// Allocate a region of memory of at least `size` bytes.
237 ///
238 /// The alignment of the returned memory region is specified by the
239 /// implementation. Returns [`None`] if allocation fails for any reason.
240 fn allocate(&self, size: usize) -> Option<NonNull<u8>>;
241 /// Free up a previously-allocated memory region at `ptr`.
242 ///
243 /// The value must be the exact same location returned by a previous call to
244 /// [`Self::allocate()`]. The behavior if this method is called more than once
245 /// for a given `ptr` is unspecified.
246 fn deallocate(&self, ptr: NonNull<u8>);
247}
248
249/// An adapter for [`GlobalAlloc`] instances to implement
250/// [`MallocLikeAllocator`].
251///
252/// This struct also supports introspecting the current allocation table with
253/// [`Self::current_allocations()`]:
254///```
255/// use vectorscan::alloc::{LayoutTracker, MallocLikeAllocator};
256/// use std::{slice, alloc::System};
257///
258/// let tracker = LayoutTracker::new(System.into());
259/// let p1 = tracker.allocate(32).unwrap();
260/// let p2 = tracker.allocate(64).unwrap();
261///
262/// let allocs = tracker.current_allocations();
263/// assert_eq!(allocs.len(), 2);
264/// let (p1_p, p1_layout) = allocs[0];
265/// let (p2_p, p2_layout) = allocs[1];
266/// // The pointer value is the same as that returned by the allocate method:
267/// assert!(p1_p == p1);
268/// assert!(p2_p == p2);
269/// // The requested alignment is always 8:
270/// assert!(p1_layout.align() == 8);
271/// assert!(p2_layout.align() == 8);
272/// // The size itself may differ per allocation, though:
273/// assert!(p1_layout.size() == 32);
274/// assert!(p2_layout.size() == 64);
275///
276/// // Note that modifying pointers in use by other threads may cause race conditions
277/// // and undefined behavior!
278/// let s1 = unsafe { slice::from_raw_parts_mut(p1_p.as_ptr(), p1_layout.size()) };
279/// s1[..5].copy_from_slice(b"hello");
280/// let s2 = unsafe { slice::from_raw_parts_mut(p2_p.as_ptr(), p2_layout.size()) };
281/// s2[..5].copy_from_slice(&s1[..5]);
282/// assert_eq!(&s2[..5], b"hello");
283///
284/// // Free memory when done:
285/// tracker.deallocate(p1);
286/// tracker.deallocate(p2);
287/// ```
288pub struct LayoutTracker {
289 allocator: Arc<dyn GlobalAlloc>,
290 layouts: RwLock<IndexMap<NonNull<u8>, Layout>>,
291}
292
293unsafe impl Send for LayoutTracker {}
294unsafe impl Sync for LayoutTracker {}
295
296impl LayoutTracker {
297 /// Create a new allocation mapping which records the [`Layout`] argument for
298 /// each allocation that goes to the underlying `allocator`.
299 pub fn new(allocator: Arc<impl GlobalAlloc+'static>) -> Self {
300 Self {
301 allocator,
302 layouts: RwLock::new(IndexMap::new()),
303 }
304 }
305
306 /// Get a copy of the current live allocation mapping given a reference to
307 /// this allocator.
308 ///
309 /// This struct makes use of an [`RwLock`] to allow concurrent access. Note
310 /// that pointers may not be safe to dereference if they are freed after this
311 /// method is called!
312 pub fn current_allocations(&self) -> Vec<(NonNull<u8>, Layout)> {
313 self.layouts.read().iter().map(|(x, y)| (*x, *y)).collect()
314 }
315}
316
317/// [`LayoutTracker`] implements three additional guarantees over the base
318/// [`MallocLikeAllocator`]:
319/// - 0-size allocation requests always return [`None`] instead of allocating.
320/// - all allocations are aligned to at least 8 bytes.
321/// - attempted double frees will panic instead.
322impl MallocLikeAllocator for LayoutTracker {
323 fn allocate(&self, size: usize) -> Option<NonNull<u8>> {
324 /* .alloc() is undefined when provided a 0 size alloc, so handle it here. */
325 if size == 0 {
326 return None;
327 }
328 /* NB: Allocate everything with 8-byte alignment. Only the database allocator
329 * is documented to require 8-byte alignment; nothing else seems to break
330 * if we use it for everything! */
331 let layout = Layout::from_size_align(size, 8).unwrap();
332 /* This is part of the safety guarantee imposed by .alloc(): */
333 assert!(layout.size() > 0);
334 let ret = NonNull::new(unsafe { self.allocator.alloc(layout) })?;
335 assert!(self.layouts.write().insert(ret, layout).is_none());
336 Some(ret)
337 }
338
339 fn deallocate(&self, ptr: NonNull<u8>) {
340 let layout = self.layouts.write().remove(&ptr).unwrap();
341 unsafe {
342 self.allocator.dealloc(ptr.as_ptr(), layout);
343 }
344 }
345}
346
347unsafe fn alloc_or_libc_fallback(
348 allocator: &RwLock<Option<LayoutTracker>>,
349 size: usize,
350) -> *mut c_void {
351 match allocator.read().as_ref() {
352 Some(allocator) => allocator
353 .allocate(size)
354 .map(|p| mem::transmute(p.as_ptr()))
355 .unwrap_or(ptr::null_mut()),
356 None => libc::malloc(size),
357 }
358}
359
360unsafe fn dealloc_or_libc_fallback(allocator: &RwLock<Option<LayoutTracker>>, p: *mut c_void) {
361 match allocator.read().as_ref() {
362 Some(allocator) => {
363 if let Some(p) = NonNull::new(p as *mut u8) {
364 allocator.deallocate(p);
365 }
366 },
367 None => libc::free(p),
368 }
369}
370
371macro_rules! allocator {
372 ($lock_name:ident, $alloc_name:ident, $free_name:ident) => {
373 static $lock_name: RwLock<Option<LayoutTracker>> = RwLock::new(None);
374
375 pub(crate) unsafe extern "C" fn $alloc_name(size: usize) -> *mut c_void {
376 alloc_or_libc_fallback(&$lock_name, size)
377 }
378
379 pub(crate) unsafe extern "C" fn $free_name(p: *mut c_void) {
380 dealloc_or_libc_fallback(&$lock_name, p)
381 }
382 };
383}
384
385allocator![DB_ALLOCATOR, db_alloc_func, db_free_func];
386
387/// Reset the allocator used for [`Database`](crate::database::Database)
388/// instances.
389pub fn set_db_allocator(
390 tracker: LayoutTracker,
391) -> Result<Option<LayoutTracker>, VectorscanRuntimeError> {
392 let ret = DB_ALLOCATOR.write().replace(tracker);
393 VectorscanRuntimeError::from_native(unsafe {
394 hs::hs_set_database_allocator(Some(db_alloc_func), Some(db_free_func))
395 })?;
396 Ok(ret)
397}
398
399/// Get the allocator used for [`Database`](crate::database::Database)
400/// instances.
401pub fn get_db_allocator() -> impl ops::Deref<Target=Option<LayoutTracker>> { DB_ALLOCATOR.read() }
402
403allocator![MISC_ALLOCATOR, misc_alloc_func, misc_free_func];
404
405/// Reset the allocator used for
406/// [`MiscAllocation`](crate::database::alloc::MiscAllocation) instances.
407pub fn set_misc_allocator(
408 tracker: LayoutTracker,
409) -> Result<Option<LayoutTracker>, VectorscanRuntimeError> {
410 let ret = MISC_ALLOCATOR.write().replace(tracker);
411 VectorscanRuntimeError::from_native(unsafe {
412 hs::hs_set_misc_allocator(Some(misc_alloc_func), Some(misc_free_func))
413 })?;
414 Ok(ret)
415}
416
417/// Get the allocator used for
418/// [`MiscAllocation`](crate::database::alloc::MiscAllocation) instances.
419pub fn get_misc_allocator() -> impl ops::Deref<Target=Option<LayoutTracker>> {
420 MISC_ALLOCATOR.read()
421}
422
423allocator![SCRATCH_ALLOCATOR, scratch_alloc_func, scratch_free_func];
424
425/// Reset the allocator used for [`Scratch`](crate::state::Scratch) instances.
426pub fn set_scratch_allocator(
427 tracker: LayoutTracker,
428) -> Result<Option<LayoutTracker>, VectorscanRuntimeError> {
429 let ret = SCRATCH_ALLOCATOR.write().replace(tracker);
430 VectorscanRuntimeError::from_native(unsafe {
431 hs::hs_set_scratch_allocator(Some(scratch_alloc_func), Some(scratch_free_func))
432 })?;
433 Ok(ret)
434}
435
436/// Get the allocator used for [`Scratch`](crate::state::Scratch) instances.
437pub fn get_scratch_allocator() -> impl ops::Deref<Target=Option<LayoutTracker>> {
438 SCRATCH_ALLOCATOR.read()
439}
440
441allocator![STREAM_ALLOCATOR, stream_alloc_func, stream_free_func];
442
443/// Reset the allocator used for [`LiveStream`](crate::stream::LiveStream)
444/// instances.
445pub fn set_stream_allocator(
446 tracker: LayoutTracker,
447) -> Result<Option<LayoutTracker>, VectorscanRuntimeError> {
448 let ret = STREAM_ALLOCATOR.write().replace(tracker);
449 VectorscanRuntimeError::from_native(unsafe {
450 hs::hs_set_stream_allocator(Some(stream_alloc_func), Some(stream_free_func))
451 })?;
452 Ok(ret)
453}
454
455/// Get the allocator used for [`LiveStream`](crate::stream::LiveStream)
456/// instances.
457pub fn get_stream_allocator() -> impl ops::Deref<Target=Option<LayoutTracker>> {
458 STREAM_ALLOCATOR.read()
459}
460
461/// Convenience method to reset all vectorscan dynamic allocators at once.
462///
463/// Example: use [jemalloc](https://docs.rs/jemallocator) for all vectorscan allocations:
464///
465///```
466/// #[cfg(feature = "compiler")]
467/// fn main() -> Result<(), vectorscan::error::VectorscanError> {
468/// use vectorscan::{expression::*, flags::*, matchers::*};
469/// use jemallocator::Jemalloc;
470///
471/// // Use jemalloc for all vectorscan allocations.
472/// vectorscan::alloc::set_allocator(Jemalloc.into())?;
473///
474/// // Everything works as normal.
475/// let expr: Expression = "(he)ll".parse()?;
476/// let db = expr.compile(Flags::default(), Mode::BLOCK)?;
477///
478/// let mut scratch = db.allocate_scratch()?;
479///
480/// let mut matches: Vec<&str> = Vec::new();
481/// scratch
482/// .scan_sync(&db, "hello".into(), |m| {
483/// matches.push(unsafe { m.source.as_str() });
484/// MatchResult::Continue
485/// })?;
486/// assert_eq!(&matches, &["hell"]);
487/// Ok(())
488/// }
489/// # #[cfg(not(feature = "compiler"))]
490/// # fn main() {}
491/// ```
492pub fn set_allocator(
493 allocator: Arc<impl GlobalAlloc+'static>,
494) -> Result<(), VectorscanRuntimeError> {
495 for f in [
496 set_db_allocator,
497 set_misc_allocator,
498 set_scratch_allocator,
499 set_stream_allocator,
500 ]
501 .into_iter()
502 {
503 f(LayoutTracker::new(allocator.clone()))?;
504 }
505 Ok(())
506}
507
508/// Routines for overriding the allocators used in the chimera library.
509///
510/// # Use Cases
511/// As with the base [`vectorscan#Use Cases`](crate::alloc#use-cases), this has
512/// setter methods for all allocators at once, or for a single component at a
513/// time, as well as getter methods.
514///
515/// ## Nonstandard Allocators
516/// It can wrap nonstandard allocators such as [jemalloc](https://docs.rs/jemallocator):
517///
518///```
519/// # fn main() -> Result<(), vectorscan::error::chimera::ChimeraError> {
520/// use vectorscan::{expression::chimera::*, flags::chimera::*, matchers::chimera::*};
521/// use jemallocator::Jemalloc;
522///
523/// // Use jemalloc for all chimera allocations.
524/// vectorscan::alloc::chimera::set_chimera_allocator(Jemalloc.into())?;
525///
526/// // Everything works as normal.
527/// let expr: ChimeraExpression = "(he)ll".parse()?;
528/// let db = expr.compile(ChimeraFlags::default(), ChimeraMode::GROUPS)?;
529///
530/// let mut scratch = db.allocate_scratch()?;
531///
532/// let mut matches: Vec<&str> = Vec::new();
533/// let e = |_| ChimeraMatchResult::Continue;
534/// scratch
535/// .scan_sync(&db, "hello".into(), |m| {
536/// matches.push(unsafe { m.captures.unwrap()[1].unwrap().as_str() });
537/// ChimeraMatchResult::Continue
538/// }, e)?;
539/// assert_eq!(&matches, &["he"]);
540/// # Ok(())
541/// # }
542/// ```
543///
544/// ## Inspecting Live Allocations
545/// This module also supports inspecting live allocations with
546/// [`LayoutTracker::current_allocations()`]:
547///
548///```
549/// # fn main() -> Result<(), vectorscan::error::chimera::ChimeraError> {
550/// use vectorscan::{expression::chimera::*, flags::chimera::*, database::chimera::*, alloc::{*, chimera::*}};
551/// use std::{alloc::System, mem::ManuallyDrop};
552///
553/// // Wrap the standard Rust System allocator.
554/// let tracker = LayoutTracker::new(System.into());
555/// // Register it as the allocator for databases.
556/// assert!(set_chimera_db_allocator(tracker)?.is_none());
557///
558/// // Create a database.
559/// let expr: ChimeraExpression = "asdf".parse()?;
560/// let mut db = expr.compile(ChimeraFlags::default(), ChimeraMode::NOGROUPS)?;
561///
562/// // Get the database allocator we just registered and view its live allocations:
563/// let allocs = get_chimera_db_allocator().as_ref().unwrap().current_allocations();
564/// // Verify that only the single known db was allocated:
565/// assert_eq!(1, allocs.len());
566/// let (p, _layout) = allocs[0];
567/// let db_ptr: *mut NativeChimeraDb = db.as_mut_native();
568/// assert_eq!(p.as_ptr() as *mut NativeChimeraDb, db_ptr);
569///
570/// // Demonstrate that we can actually use this pointer as a reference to the database,
571/// // although we have to be careful not to run the drop code:
572/// let db = ManuallyDrop::new(unsafe { ChimeraDb::from_native(p.as_ptr() as *mut NativeChimeraDb) });
573/// // We can inspect properties of the database with this reference:
574/// assert_eq!(db.database_size()?, 1452);
575/// # Ok(())
576/// # }
577/// ```
578///
579/// # Global State
580/// Because the `"chimera"` feature already requires the `"static"` feature
581/// (this is [enforced by the build
582/// script](https://github.com/intel/hyperscan/blob/bc3b191ab56055e8560c7cdc161c289c4d76e3d2/CMakeLists.txt#L494)
583/// in the vectorscan/chimera codebase), there are no additional restrictions
584/// required to enable modification of process-global state (unlike the
585/// corresponding [`vectorscan#Global State`](crate::alloc#global-state)).
586///
587/// ## Lifetimes and Dangling Pointers
588/// Similar methods as in the parent
589/// [`vectorscan#Lifetimes`](crate::alloc#lifetimes-and-dangling-pointers) can
590/// be used to handle object lifetimes if multiple allocators are used over the
591/// lifetime of the program.
592///
593/// # Allocation Failures
594/// Allocation failure should cause chimera methods to fail with
595/// [`ChimeraRuntimeError::NoMem`](crate::error::chimera::ChimeraRuntimeError::NoMem):
596///
597///```
598/// # fn main() -> Result<(), vectorscan::error::chimera::ChimeraError> {
599/// use vectorscan::{expression::chimera::*, flags::chimera::*, matchers::*, alloc::chimera::*, error::chimera::*};
600/// use std::{alloc::{GlobalAlloc, Layout}, mem::ManuallyDrop, ptr};
601///
602/// let expr: ChimeraExpression = "asdf".parse()?;
603/// // Wrap in ManuallyDrop because we will be clobbering the allocator,
604/// // including the free methods.
605/// let db = ManuallyDrop::new(expr.compile(ChimeraFlags::default(), ChimeraMode::NOGROUPS)?);
606///
607/// struct BadAllocator;
608/// unsafe impl GlobalAlloc for BadAllocator {
609/// unsafe fn alloc(&self, _layout: Layout) -> *mut u8 { ptr::null_mut() }
610/// // If we wanted to cover allocations made before registering this one,
611/// // we could fall back to libc::free() for unrecognized pointers.
612/// unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {}
613/// }
614///
615/// set_chimera_allocator(BadAllocator.into())?;
616///
617/// // Most allocation methods fail with NoMem:
618/// assert!(matches!(
619/// db.allocate_scratch(),
620/// Err(ChimeraRuntimeError::NoMem),
621/// ));
622///
623/// // Compile allocation errors fail slightly differently:
624/// match expr.compile(ChimeraFlags::default(), ChimeraMode::NOGROUPS) {
625/// Err(ChimeraCompileError::Compile(ChimeraInnerCompileError { message, expression })) => {
626/// assert!(message == "Unable to allocate memory.");
627/// assert!(expression == Some(ExpressionIndex(0)));
628/// },
629/// _ => unreachable!(),
630/// }
631/// # Ok(())
632/// # }
633/// ```
634#[cfg(feature = "chimera")]
635#[cfg_attr(docsrs, doc(cfg(feature = "chimera")))]
636pub mod chimera {
637 use super::{alloc_or_libc_fallback, dealloc_or_libc_fallback, LayoutTracker};
638 use crate::{error::chimera::ChimeraRuntimeError, hs};
639
640 use parking_lot::RwLock;
641
642 use std::{alloc::GlobalAlloc, ops, os::raw::c_void, sync::Arc};
643
644 allocator![
645 CHIMERA_DB_ALLOCATOR,
646 chimera_db_alloc_func,
647 chimera_db_free_func
648 ];
649
650 /// Reset the allocator used for
651 /// [`ChimeraDb`](crate::database::chimera::ChimeraDb) instances.
652 pub fn set_chimera_db_allocator(
653 tracker: LayoutTracker,
654 ) -> Result<Option<LayoutTracker>, ChimeraRuntimeError> {
655 let ret = CHIMERA_DB_ALLOCATOR.write().replace(tracker);
656 ChimeraRuntimeError::from_native(unsafe {
657 hs::ch_set_database_allocator(Some(chimera_db_alloc_func), Some(chimera_db_free_func))
658 })?;
659 Ok(ret)
660 }
661
662 /// Get the allocator used for
663 /// [`ChimeraDb`](crate::database::chimera::ChimeraDb) instances.
664 pub fn get_chimera_db_allocator() -> impl ops::Deref<Target=Option<LayoutTracker>> {
665 CHIMERA_DB_ALLOCATOR.read()
666 }
667
668 allocator![
669 CHIMERA_MISC_ALLOCATOR,
670 chimera_misc_alloc_func,
671 chimera_misc_free_func
672 ];
673
674 /// Reset the allocator used for
675 /// [`ChimeraMiscAllocation`](crate::database::alloc::chimera::ChimeraMiscAllocation)
676 /// instances.
677 pub fn set_chimera_misc_allocator(
678 tracker: LayoutTracker,
679 ) -> Result<Option<LayoutTracker>, ChimeraRuntimeError> {
680 let ret = CHIMERA_MISC_ALLOCATOR.write().replace(tracker);
681 ChimeraRuntimeError::from_native(unsafe {
682 hs::ch_set_misc_allocator(Some(chimera_misc_alloc_func), Some(chimera_misc_free_func))
683 })?;
684 Ok(ret)
685 }
686
687 /// Get the allocator used for
688 /// [`ChimeraMiscAllocation`](crate::database::alloc::chimera::ChimeraMiscAllocation)
689 /// instances.
690 pub fn get_chimera_misc_allocator() -> impl ops::Deref<Target=Option<LayoutTracker>> {
691 CHIMERA_MISC_ALLOCATOR.read()
692 }
693
694 allocator![
695 CHIMERA_SCRATCH_ALLOCATOR,
696 chimera_scratch_alloc_func,
697 chimera_scratch_free_func
698 ];
699
700 /// Reset the allocator used for
701 /// [`ChimeraScratch`](crate::state::chimera::ChimeraScratch) instances.
702 pub fn set_chimera_scratch_allocator(
703 tracker: LayoutTracker,
704 ) -> Result<Option<LayoutTracker>, ChimeraRuntimeError> {
705 let ret = CHIMERA_SCRATCH_ALLOCATOR.write().replace(tracker);
706 ChimeraRuntimeError::from_native(unsafe {
707 hs::ch_set_scratch_allocator(
708 Some(chimera_scratch_alloc_func),
709 Some(chimera_scratch_free_func),
710 )
711 })?;
712 Ok(ret)
713 }
714
715 /// Get the allocator used for
716 /// [`ChimeraScratch`](crate::state::chimera::ChimeraScratch) instances.
717 pub fn get_chimera_scratch_allocator() -> impl ops::Deref<Target=Option<LayoutTracker>> {
718 CHIMERA_SCRATCH_ALLOCATOR.read()
719 }
720
721 /// Convenience method to reset all chimera dynamic allocators at once.
722 ///
723 /// Example: use [jemalloc](https://docs.rs/jemallocator) for all chimera allocations:
724 ///
725 ///```
726 /// # fn main() -> Result<(), vectorscan::error::chimera::ChimeraError> {
727 /// use vectorscan::{expression::chimera::*, flags::chimera::*, matchers::chimera::*};
728 /// use jemallocator::Jemalloc;
729 ///
730 /// // Use jemalloc for all chimera allocations.
731 /// vectorscan::alloc::chimera::set_chimera_allocator(Jemalloc.into())?;
732 ///
733 /// // Everything works as normal.
734 /// let expr: ChimeraExpression = "(he)ll".parse()?;
735 /// let db = expr.compile(ChimeraFlags::default(), ChimeraMode::GROUPS)?;
736 ///
737 /// let mut scratch = db.allocate_scratch()?;
738 ///
739 /// let mut matches: Vec<&str> = Vec::new();
740 /// let e = |_| ChimeraMatchResult::Continue;
741 /// scratch.scan_sync(&db, "hello".into(), |m| {
742 /// matches.push(unsafe { m.captures.unwrap()[1].unwrap().as_str() });
743 /// ChimeraMatchResult::Continue
744 /// }, e)?;
745 /// assert_eq!(&matches, &["he"]);
746 /// # Ok(())
747 /// # }
748 /// ```
749 pub fn set_chimera_allocator(
750 allocator: Arc<impl GlobalAlloc+'static>,
751 ) -> Result<(), ChimeraRuntimeError> {
752 for f in [
753 set_chimera_db_allocator,
754 set_chimera_misc_allocator,
755 set_chimera_scratch_allocator,
756 ]
757 .into_iter()
758 {
759 f(LayoutTracker::new(allocator.clone()))?;
760 }
761 Ok(())
762 }
763}