22//!
33//! Taking malloc's lock is the classic way for interposed code to deadlock a
44//! traced program (see the crate docs), so the preload library allocates
5- //! through this module instead. It stacks three layers and exposes only the
6- //! top one, [`arena`]. A stateless page allocator is the bottom: every
7- //! allocation is fresh pages from [`fspy_nostd::mm`] — an anonymous mapping
8- //! on Unix, a `VirtualAlloc` region on Windows.
9- //! `ChunkPool` sits on top of it and caches fixed-size chunks, so that
10- //! frequent short tracing calls can reuse memory instead of paying two
11- //! syscalls per call. [`arena`] creates one `bump_scope::Bump` per
12- //! intercepted call, drawing its chunks from the process-wide pool and
13- //! returning them on drop.
5+ //! through this crate instead. It stacks three layers: a stateless page
6+ //! allocator at the bottom, where every allocation is fresh pages from
7+ //! [`fspy_nostd::mm`] — an anonymous mapping on Unix, a `VirtualAlloc`
8+ //! region on Windows; a process-wide `ChunkPool` above it that caches
9+ //! fixed-size chunks; and `bump_scope::Bump`s on top. [`pooled_bump`]
10+ //! creates a bump that draws its chunks from the pool and returns them on
11+ //! drop, so frequent short-lived bumps reuse memory instead of paying two
12+ //! syscalls each.
1413
1514#![ cfg_attr( not( test) , no_std) ]
1615
@@ -24,6 +23,10 @@ mod pool;
2423mod virtual_alloc;
2524
2625use allocator_api2:: alloc:: Allocator ;
26+ /// The bump interface [`pooled_bump`] returns, re-exported so callers can
27+ /// name the bound and call its methods without a direct bump-scope
28+ /// dependency.
29+ pub use bump_scope:: traits:: BumpAllocator ;
2730use bump_scope:: {
2831 Bump ,
2932 alloc:: compat:: AllocatorApi2V02Compat ,
@@ -38,8 +41,8 @@ pub(crate) use virtual_alloc::VirtualAllocator as PageAllocator;
3841
3942/// Every cached chunk is 64 KiB: a whole multiple of the page size on all
4043/// supported targets, and big enough that most intercepted calls fit their
41- /// allocations into a single chunk. [`ArenaSettings `] pins the arenas' own
42- /// chunk sizing to this same value.
44+ /// allocations into a single chunk. [`PooledBumpSettings `] pins the bumps'
45+ /// own chunk sizing to this same value.
4346const CHUNK_SIZE : usize = 64 * 1024 ;
4447/// The alignment chunks are allocated with. Must be at least the alignment
4548/// `bump_scope::Bump` uses for its chunk requests — 16 (see
@@ -56,11 +59,12 @@ const SLOTS: usize = 64;
5659/// first allocation on — even before any constructor has run.
5760static CHUNK_POOL : ChunkPool < PageAllocator , CHUNK_SIZE , CHUNK_ALIGN , SLOTS > = ChunkPool :: new ( ) ;
5861
59- /// The `Bump` settings the arenas use — the defaults, with two changes:
62+ /// The `Bump` settings [`pooled_bump`] uses — the defaults, with two
63+ /// changes:
6064///
61- /// - `WithGuaranteedAllocated<false>`: an arena starts life without a chunk,
65+ /// - `WithGuaranteedAllocated<false>`: a bump starts life without a chunk,
6266/// so creating one allocates nothing.
63- /// - `WithMinimumChunkSize<CHUNK_SIZE>`: the arena 's first chunk request is
67+ /// - `WithMinimumChunkSize<CHUNK_SIZE>`: the bump 's first chunk request is
6468/// sized to the pool's chunks, making the coupling explicit — rather than
6569/// relying on the pool rounding the default 512-byte first request up to a
6670/// whole chunk. (Both end up serving the same memory: the pool answers any
@@ -70,10 +74,10 @@ static CHUNK_POOL: ChunkPool<PageAllocator, CHUNK_SIZE, CHUNK_ALIGN, SLOTS> = Ch
7074/// minimum — which is exactly what keeps a minimum-sized request within
7175/// the pool's `size <= CHUNK_SIZE` gate; the
7276/// `bump_chunk_requests_fit_the_pool_gates` test pins that fit.
73- type ArenaSettings = <<BumpSettings as BumpAllocatorSettings >:: WithGuaranteedAllocated < false > as BumpAllocatorSettings >:: WithMinimumChunkSize < CHUNK_SIZE > ;
77+ type PooledBumpSettings = <<BumpSettings as BumpAllocatorSettings >:: WithGuaranteedAllocated < false > as BumpAllocatorSettings >:: WithMinimumChunkSize < CHUNK_SIZE > ;
7478
7579/// `Bump::unallocated` requires its base allocator to implement `Default`
76- /// (an arena without chunks has nowhere to store an allocator value, so it
80+ /// (a bump without chunks has nowhere to store an allocator value, so it
7781/// conjures one on first use). Point defaulted references at the
7882/// process-wide pool. As an allocator, `&ChunkPool` already works through
7983/// allocator-api2's blanket `impl Allocator for &A`.
@@ -83,25 +87,24 @@ impl Default for &'static ChunkPool<PageAllocator, CHUNK_SIZE, CHUNK_ALIGN, SLOT
8387 }
8488}
8589
86- /// Creates a fresh bump arena for one intercepted call, backed by the
87- /// process-wide chunk pool.
90+ /// Creates a fresh bump backed by the process-wide chunk pool.
8891///
89- /// Creating the arena allocates nothing; the first allocation grabs a whole
90- /// chunk — usually a recycled one, so most calls touch no syscalls at all.
91- /// Deallocation only takes back the most recent allocation (bump-arena
92- /// semantics); everything is freed at once when the arena is dropped, and
92+ /// Creating the bump allocates nothing; the first allocation grabs a whole
93+ /// chunk — usually a recycled one, so most uses touch no syscalls at all.
94+ /// Deallocation only takes back the most recent allocation (bump
95+ /// semantics); everything is freed at once when the bump is dropped, and
9396/// its chunks go back to the pool.
9497///
95- /// The arena itself is single-owner — use one per call, do not share it
96- /// across threads. Creating one is safe anywhere, any time: the pool
97- /// underneath works in signal handlers, in the child of `fork()`, and under
98- /// the Windows loader lock (see `ChunkPool` and the platform page allocators
99- /// in this crate's source for why).
98+ /// The bump is single-owner — do not share it across threads. Creating one
99+ /// is safe anywhere, any time: the pool underneath works in signal
100+ /// handlers, in the child of `fork()`, and under the Windows loader lock
101+ /// (see `ChunkPool` and the platform page allocators in this crate's source
102+ /// for why).
100103#[ must_use]
101- pub fn arena ( ) -> impl Allocator {
104+ pub fn pooled_bump ( ) -> impl BumpAllocator + Allocator {
102105 Bump :: <
103106 AllocatorApi2V02Compat < & ' static ChunkPool < PageAllocator , CHUNK_SIZE , CHUNK_ALIGN , SLOTS > > ,
104- ArenaSettings ,
107+ PooledBumpSettings ,
105108 > :: unallocated ( )
106109}
107110
@@ -115,16 +118,17 @@ mod tests {
115118
116119 /// Pins the fit between `Bump`'s chunk requests and the pool's gates
117120 /// (alignment at most [`CHUNK_ALIGN`], size at most [`CHUNK_SIZE`]),
118- /// under the same [`ArenaSettings `] the arenas use — including that a
119- /// request under `WithMinimumChunkSize<CHUNK_SIZE>` still fits the
121+ /// under the same [`PooledBumpSettings `] the bumps use — including that
122+ /// a request under `WithMinimumChunkSize<CHUNK_SIZE>` still fits the
120123 /// `size <= CHUNK_SIZE` gate. bump-scope keeps its request parameters
121124 /// private, so this test is the enforcement: it fails if an upgrade
122125 /// ever changes them.
123- /// [`ArenaSettings`] with `GuaranteedAllocated` flipped back on: without
124- /// it, `Bump` demands a `Default` base allocator, and a reference to the
125- /// test's stack-local pool cannot provide one. Chunk request sizing —
126- /// what the test pins — is unaffected by that flag.
127- type TestSettings = <ArenaSettings as BumpAllocatorSettings >:: WithGuaranteedAllocated < true > ;
126+ /// [`PooledBumpSettings`] with `GuaranteedAllocated` flipped back on:
127+ /// without it, `Bump` demands a `Default` base allocator, and a
128+ /// reference to the test's stack-local pool cannot provide one. Chunk
129+ /// request sizing — what the test pins — is unaffected by that flag.
130+ type TestSettings =
131+ <PooledBumpSettings as BumpAllocatorSettings >:: WithGuaranteedAllocated < true > ;
128132
129133 #[ test]
130134 fn bump_chunk_requests_fit_the_pool_gates ( ) {
@@ -153,25 +157,25 @@ mod tests {
153157
154158 #[ test]
155159 #[ cfg( not( miri) ) ]
156- fn arena_allocates_and_returns_chunks_to_the_pool ( ) {
160+ fn pooled_bump_allocates_and_returns_chunks_to_the_pool ( ) {
157161 let layout = Layout :: from_size_align ( 100 , 8 ) . unwrap ( ) ;
158162
159- let first_arena = arena ( ) ;
160- let first = first_arena . allocate ( layout) . unwrap ( ) ;
163+ let first_bump = pooled_bump ( ) ;
164+ let first = first_bump . allocate ( layout) . unwrap ( ) ;
161165 assert ! ( first. len( ) >= 100 ) ;
162166 // SAFETY: fresh exclusive block of at least 100 bytes.
163167 unsafe { first. cast :: < u8 > ( ) . as_ptr ( ) . write_bytes ( 0x5A , 100 ) } ;
164- let second = first_arena . allocate ( layout) . unwrap ( ) ;
168+ let second = first_bump . allocate ( layout) . unwrap ( ) ;
165169 assert_ne ! ( first. cast:: <u8 >( ) . as_ptr( ) . addr( ) , second. cast:: <u8 >( ) . as_ptr( ) . addr( ) ) ;
166170 let first_addr = first. cast :: < u8 > ( ) . as_ptr ( ) . addr ( ) ;
167171 // Everything dies at once; the chunk goes back to the pool.
168- drop ( first_arena ) ;
172+ drop ( first_bump ) ;
169173
170- // No other test touches the process-wide pool, so a new arena draws
174+ // No other test touches the process-wide pool, so a new bump draws
171175 // the same chunk back and its first allocation lands at the same
172176 // address.
173- let second_arena = arena ( ) ;
174- let again = second_arena . allocate ( layout) . unwrap ( ) ;
177+ let second_bump = pooled_bump ( ) ;
178+ let again = second_bump . allocate ( layout) . unwrap ( ) ;
175179 assert_eq ! ( again. cast:: <u8 >( ) . as_ptr( ) . addr( ) , first_addr) ;
176180 }
177181}
0 commit comments