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}