pub struct Arena { /* private fields */ }Expand description
A dual-end bump-allocated arena.
Owns or borrows a fixed-size buffer of bytes. Two bump pointers track allocation positions at each end. The bottom end grows from low addresses upward. The top end grows from high addresses downward. Allocation fails when the two pointers would meet.
The arena is not the program’s #[global_allocator] and is not
intended to be one. It is designed for scoped per-region or
per-thread use through BottomHandle and TopHandle, which the
host passes to allocator-aware collection constructors. The standard
global allocator continues to handle every allocation that does not
route through an arena handle. Hosts that want every allocation in
the program to be arena-backed must wrap the arena in a thread-safe
allocator and install it via #[global_allocator]; this crate does
not provide such a wrapper because doing so well requires choices
that depend on the host’s threading and synchronization model.
§Generations and stale-pointer detection
The arena carries an epoch counter that increments on Arena::reset.
The ArenaHandle family of safe wrappers captures the epoch at
construction and validates it on access, returning Stale if the
arena has been reset since the handle was issued. The counter is
u64 and uses checked arithmetic. A saturated counter halts the
arena’s reset path with EpochSaturated. Saturation requires
roughly five hundred eighty four thousand years at one reset per
microsecond and is documentation rather than a real failure mode in
expected use.
In-process recovery from saturation is possible through
Arena::force_reset_epoch, which is unsafe and requires the
caller to certify that no ArenaHandle from any prior epoch is
reachable. Cross-process recovery for very long-lived deployments
uses checkpoint and restart against host-owned non-volatile storage.
ArenaHandle is intentionally not serializable because its pointer
is not stable across processes.
See the crate-level documentation for the design overview.
Implementations§
Source§impl Arena
impl Arena
Sourcepub fn with_capacity(capacity: usize) -> Arena
pub fn with_capacity(capacity: usize) -> Arena
Create an arena backed by a freshly allocated heap buffer of the given byte capacity.
Available only with the alloc feature. The buffer is zeroed at
construction and is allocated with 16-byte alignment, which
covers the alignment requirements of i64, f64, u128, and
most platform-native pointers and primitives.
Panics on allocation failure via the standard handle_alloc_error
path. A capacity of zero produces an arena that satisfies
allocation requests for zero-size layouts only; non-zero
allocations return AllocError.
§Examples
use allocator_api2::vec::Vec as ArenaVec;
use keleusma_arena::Arena;
let arena = Arena::with_capacity(1024);
let mut v: ArenaVec<i64, _> = ArenaVec::new_in(arena.stack_handle());
v.push(1);
v.push(2);
v.push(3);
assert_eq!(v.len(), 3);
assert!(arena.bottom_used() >= 24);Examples found in repository?
12fn run(label: &str, src: &str) -> Value {
13 let tokens = tokenize(src).expect("lex");
14 let program = parse(&tokens).expect("parse");
15 let module = compile(&program).expect("compile");
16 let arena = Arena::with_capacity(DEFAULT_ARENA_CAPACITY);
17 let mut vm = Vm::new(module, &arena).expect("verify");
18 match vm.call(&[]) {
19 Ok(VmState::Finished(v)) => {
20 println!("{}: {:?}", label, v);
21 v
22 }
23 other => panic!("{}: {:?}", label, other),
24 }
25}More examples
86fn compile_and_run(src: &str, target: &Target) {
87 let tokens = tokenize(src).expect("lex");
88 let program = parse(&tokens).expect("parse");
89 let module = compile_with_target(&program, target).expect("compile");
90 let arena = Arena::with_capacity(DEFAULT_ARENA_CAPACITY);
91 let mut vm = Vm::new(module, &arena).expect("verify");
92 match vm.call(&[]).expect("call") {
93 VmState::Finished(v) => println!(" result: {:?}", v),
94 other => panic!("unexpected: {:?}", other),
95 }
96 let _ = Value::Unit;
97}19fn main() {
20 let src = r#"
21 fn id<T>(x: T) -> T { x }
22 fn main() -> i64 { id(42) }
23 "#;
24 let tokens = tokenize(src).expect("lex");
25 let program = parse(&tokens).expect("parse");
26 let module = compile(&program).expect("compile");
27 let arena = Arena::with_capacity(DEFAULT_ARENA_CAPACITY);
28 let mut vm = Vm::new(module, &arena).expect("verify");
29 match vm.call(&[]) {
30 Ok(VmState::Finished(Value::Int(n))) => {
31 println!("id(42) = {}", n);
32 assert_eq!(n, 42);
33 println!("generic identity executed end to end");
34 }
35 other => panic!("unexpected: {:?}", other),
36 }
37}17fn main() {
18 let src = r#"
19 struct Cell<T> { value: T }
20 fn main() -> i64 {
21 let c = Cell { value: 42 };
22 c.value
23 }
24 "#;
25 let tokens = tokenize(src).expect("lex");
26 let program = parse(&tokens).expect("parse");
27 let module = compile(&program).expect("compile");
28 let arena = Arena::with_capacity(DEFAULT_ARENA_CAPACITY);
29 let mut vm = Vm::new(module, &arena).expect("verify");
30 match vm.call(&[]) {
31 Ok(VmState::Finished(Value::Int(n))) => {
32 println!("Cell {{ value: 42 }}.value = {}", n);
33 assert_eq!(n, 42);
34 println!("generic struct executed end to end");
35 }
36 other => panic!("unexpected: {:?}", other),
37 }
38}20fn main() {
21 let src = r#"
22 trait Doubler { fn double(x: i64) -> i64; }
23 impl Doubler for i64 { fn double(x: i64) -> i64 { x + x } }
24 fn main() -> i64 {
25 let n: i64 = 21;
26 n.double()
27 }
28 "#;
29 let tokens = tokenize(src).expect("lex");
30 let program = parse(&tokens).expect("parse");
31 let module = compile(&program).expect("compile");
32 let arena = Arena::with_capacity(DEFAULT_ARENA_CAPACITY);
33 let mut vm = Vm::new(module, &arena).expect("verify");
34 match vm.call(&[]) {
35 Ok(VmState::Finished(Value::Int(n))) => {
36 println!("21.double() = {}", n);
37 assert_eq!(n, 42);
38 println!("method call dispatch executed end to end");
39 }
40 other => panic!("unexpected: {:?}", other),
41 }
42}21fn main() {
22 let src = r#"
23 trait Doubler { fn double(x: i64) -> i64; }
24 impl Doubler for i64 { fn double(x: i64) -> i64 { x + x } }
25 fn use_doubler<T: Doubler>(x: T) -> i64 { x.double() }
26 fn main() -> i64 { use_doubler(21) }
27 "#;
28 let tokens = tokenize(src).expect("lex");
29 let program = parse(&tokens).expect("parse");
30 let module = compile(&program).expect("compile");
31 let arena = Arena::with_capacity(DEFAULT_ARENA_CAPACITY);
32 let mut vm = Vm::new(module, &arena).expect("verify");
33 match vm.call(&[]) {
34 Ok(VmState::Finished(Value::Int(n))) => {
35 println!("use_doubler(21) = {}", n);
36 assert_eq!(n, 42);
37 println!("monomorphization-driven method dispatch executed end to end");
38 }
39 other => panic!("unexpected: {:?}", other),
40 }
41}Sourcepub fn from_static_buffer(buffer: &'static mut [u8]) -> Arena
pub fn from_static_buffer(buffer: &'static mut [u8]) -> Arena
Create an arena backed by a static buffer.
The buffer must outlive the arena. The 'static mut requirement
satisfies this for typical embedded patterns where the buffer is
a static array placed in BSS or DATA. For shorter-lived buffers,
see Arena::from_buffer_unchecked.
Sourcepub unsafe fn from_buffer_unchecked(ptr: *mut u8, capacity: usize) -> Arena
pub unsafe fn from_buffer_unchecked(ptr: *mut u8, capacity: usize) -> Arena
Create an arena from a raw pointer and length.
The buffer’s base alignment does not need to match the alignment of any particular allocation type. The arena computes alignment against the actual buffer base address and pads as needed for each aligned allocation.
§Safety
The caller must uphold the following.
ptris non-null.ptris valid for reads and writes ofcapacitybytes for the entire lifetime of the returned arena.- No other code accesses the buffer through any path that would alias with the arena’s allocations during the arena’s lifetime.
This constructor is the only path that admits buffers with
non-'static lifetimes. It exists for embedded contexts where
the lifetime is known statically through other means but the
type system cannot express it. Most callers should prefer
Arena::from_static_buffer.
Sourcepub fn capacity(&self) -> usize
pub fn capacity(&self) -> usize
Total capacity of the arena in bytes.
Examples found in repository?
23fn main() {
24 // Compile.
25 let tokens = tokenize(SCRIPT).expect("lex error");
26 let program = parse(&tokens).expect("parse error");
27 let module = compile(&program).expect("compile error");
28
29 // Inspect the WCMU budget for each Stream chunk in the module.
30 let chunk_wcmu = verify::module_wcmu(&module, &[]).expect("module wcmu");
31 for (idx, chunk) in module.chunks.iter().enumerate() {
32 if matches!(chunk.block_type, keleusma::bytecode::BlockType::Stream) {
33 let (stack_bytes, heap_bytes) = chunk_wcmu[idx];
34 println!("chunk `{}`:", chunk.name);
35 println!(" stack WCMU: {} bytes", stack_bytes);
36 println!(" heap WCMU: {} bytes", heap_bytes);
37 println!(" total: {} bytes", stack_bytes + heap_bytes);
38 }
39 }
40
41 // Construct an auto-sized arena and a Vm that borrows it.
42 let cap = keleusma::vm::auto_arena_capacity_for(&module, &[]).expect("auto capacity");
43 let arena = keleusma::Arena::with_capacity(cap);
44 let mut vm = Vm::new(module, &arena).expect("vm construction");
45 println!();
46 println!("auto-sized arena capacity: {} bytes", vm.arena().capacity());
47
48 // Drive the coroutine through one yield.
49 match vm.call(&[Value::Int(21)]).expect("vm call") {
50 VmState::Yielded(v) => println!("yielded: {:?}", v),
51 other => panic!("expected yield, got {:?}", other),
52 }
53}More examples
26fn main() {
27 // Compile.
28 let tokens = tokenize(SCRIPT).expect("lex error");
29 let program = parse(&tokens).expect("parse error");
30 let module = compile(&program).expect("compile error");
31
32 // Inspect the WCMU budget with no native attestation. Heap is zero.
33 let chunk_wcmu_default = verify::module_wcmu(&module, &[]).expect("module wcmu");
34 println!("Default attestation (no host declaration):");
35 for (idx, chunk) in module.chunks.iter().enumerate() {
36 if matches!(chunk.block_type, keleusma::bytecode::BlockType::Stream) {
37 let (s, h) = chunk_wcmu_default[idx];
38 println!(" chunk `{}`: stack {} heap {}", chunk.name, s, h);
39 }
40 }
41
42 // Inspect with the host's declared attestation. Heap reflects the
43 // bound the host promises the native will not exceed.
44 let attested_native_wcmu = [256u32];
45 let chunk_wcmu_attested =
46 verify::module_wcmu(&module, &attested_native_wcmu).expect("module wcmu");
47 println!();
48 println!("Attested host::compute_value with 256 bytes:");
49 for (idx, chunk) in module.chunks.iter().enumerate() {
50 if matches!(chunk.block_type, keleusma::bytecode::BlockType::Stream) {
51 let (s, h) = chunk_wcmu_attested[idx];
52 println!(" chunk `{}`: stack {} heap {}", chunk.name, s, h);
53 }
54 }
55
56 // Construct a Vm with adequate capacity.
57 let arena = keleusma::Arena::with_capacity(4096);
58 let mut vm = Vm::new(module, &arena).expect("vm construction");
59
60 // Register the native and declare its WCET and WCMU bounds.
61 vm.register_fn("host::compute_value", |x: i64| -> i64 { x * 3 });
62 vm.set_native_bounds("host::compute_value", 25, 256)
63 .expect("set bounds");
64
65 // Re-verify resources with the declared attestations now in place.
66 vm.verify_resources().expect("verify_resources");
67 println!();
68 println!(
69 "verify_resources succeeded with arena {} bytes",
70 vm.arena().capacity()
71 );
72
73 // Drive the coroutine through one yield.
74 match vm.call(&[Value::Int(7)]).expect("vm call") {
75 VmState::Yielded(v) => println!("yielded: {:?}", v),
76 other => panic!("expected yield, got {:?}", other),
77 }
78}Sourcepub fn bottom_used(&self) -> usize
pub fn bottom_used(&self) -> usize
Bytes currently allocated from the bottom end.
Sourcepub fn bottom_peak(&self) -> usize
pub fn bottom_peak(&self) -> usize
Highest observed bottom usage in bytes since arena creation or
the most recent Arena::clear_peaks call.
Sourcepub fn top_peak(&self) -> usize
pub fn top_peak(&self) -> usize
Highest observed top usage in bytes since arena creation or the
most recent Arena::clear_peaks call.
Sourcepub fn bottom_mark(&self) -> BottomMark
pub fn bottom_mark(&self) -> BottomMark
Return a snapshot of the bottom-end bump pointer for later use
with Arena::rewind_bottom.
Sourcepub fn top_mark(&self) -> TopMark
pub fn top_mark(&self) -> TopMark
Return a snapshot of the top-end bump pointer for later use with
Arena::rewind_top.
Sourcepub fn reset(&mut self) -> Result<(), EpochSaturated>
pub fn reset(&mut self) -> Result<(), EpochSaturated>
Reset both ends, reclaiming all allocations.
Constant-time. Does not zero the buffer contents because
subsequent allocations will overwrite as needed. Does not clear
peak watermarks; use Arena::clear_peaks for that.
Advances the epoch counter, invalidating every outstanding
ArenaHandle. Returns EpochSaturated if the counter is
already at u64::MAX. See Arena::force_reset_epoch for
recovery.
Takes &mut self so the borrow checker prevents calling reset
while any handle borrows the arena. This guarantees no live
allocations through Allocator trait users at the moment of
reset.
Sourcepub unsafe fn reset_unchecked(&self) -> Result<(), EpochSaturated>
pub unsafe fn reset_unchecked(&self) -> Result<(), EpochSaturated>
Reset both ends and advance the epoch through a shared reference.
Companion to Arena::reset for callers that hold the arena
through a shared reference and cannot temporarily acquire
exclusive access. The interior-mutable bump pointers and epoch
counter make the implementation race-free for single-threaded
use.
§Safety
The caller must certify that no allocator-bound collection
holds storage in the arena at the moment of reset. Concretely,
no allocator_api2::vec::Vec<T, BottomHandle> or
allocator_api2::vec::Vec<T, TopHandle> value may have non-zero
capacity when this is called. Outstanding ArenaHandle values
are correctly invalidated by the epoch advance and remain safe.
Returns EpochSaturated when the epoch counter is at
u64::MAX. Recovery is via Arena::force_reset_epoch.
Sourcepub unsafe fn reset_top_unchecked(&self) -> Result<(), EpochSaturated>
pub unsafe fn reset_top_unchecked(&self) -> Result<(), EpochSaturated>
Reset the top end and advance the epoch through a shared reference, leaving the bottom end untouched.
Intended for hosts that use the bottom end for long-lived
allocator-bound collections (such as an operand stack) while
using the top end for short-lived scratch (such as dynamic
strings). The epoch advance invalidates every outstanding
ArenaHandle regardless of which end produced it. This is
the desired discipline because handles do not record which end
they came from and any handle that survives a reset is by
definition stale.
§Safety
The caller must certify that no allocator-bound collection
holds storage in the top end at the moment of reset. Bottom-end
allocator-bound collections are unaffected by this call and
retain their storage. Outstanding ArenaHandle values are
correctly invalidated by the epoch advance and remain safe.
Returns EpochSaturated when the epoch counter is at
u64::MAX. Recovery is via Arena::force_reset_epoch.
Sourcepub fn epoch(&self) -> u64
pub fn epoch(&self) -> u64
Current epoch counter value.
Captured by ArenaHandle at construction and compared on
access. Hosts performing long-running missions may consult this
alongside Arena::epoch_remaining to schedule a graceful
restart well before saturation.
Sourcepub fn epoch_remaining(&self) -> u64
pub fn epoch_remaining(&self) -> u64
Number of resets remaining before the epoch counter saturates.
Sourcepub unsafe fn force_reset_epoch(&mut self)
pub unsafe fn force_reset_epoch(&mut self)
Reset the epoch counter to zero.
Recovery path for EpochSaturated. Resets bump pointers as
well so the arena is in the same observable state as a freshly
constructed arena, except for retained capacity.
§Safety
The caller must certify that no ArenaHandle produced under
any prior epoch is reachable. Calling this while such handles
exist invalidates the stale-detection guarantee and may permit
use after invalidation that the type system would otherwise
catch through epoch comparison.
The intended use is recovery after a Arena::reset call has
returned EpochSaturated. The host halts every consumer of
the arena, drains every cache that holds an ArenaHandle,
and only then invokes this method.
Sourcepub fn clear_peaks(&mut self)
pub fn clear_peaks(&mut self)
Clear the peak watermarks for both ends.
Sets each peak to the current pointer value. After this call, peak readings reflect only allocations made after the call.
Sourcepub unsafe fn rewind_bottom(&self, mark: BottomMark)
pub unsafe fn rewind_bottom(&self, mark: BottomMark)
Rewind the bottom end to a previously recorded mark.
§Safety
The caller must ensure that no live values reference memory in
the range [mark.0, current_bottom_top). References obtained
through the Allocator trait, including those held by
allocator_api2::vec::Vec and similar collections, must be
dropped or otherwise abandoned before this call. Subsequent
allocations may overwrite the rewound region, which would alias
with any retained reference and produce undefined behavior.
Marks from a different arena are a logic error.
Sourcepub unsafe fn rewind_top(&self, mark: TopMark)
pub unsafe fn rewind_top(&self, mark: TopMark)
Sourcepub unsafe fn reset_bottom(&self)
pub unsafe fn reset_bottom(&self)
Clear the bottom end without checking for live references.
§Safety
The caller must ensure no live references into the bottom region
exist. Equivalent to Arena::rewind_bottom with a mark of
zero, with the same safety contract.
Sourcepub unsafe fn reset_top(&self)
pub unsafe fn reset_top(&self)
Clear the top end without checking for live references.
§Safety
The caller must ensure no live references into the top region
exist. Equivalent to Arena::rewind_top with a mark of
capacity, with the same safety contract.
Sourcepub fn fits_budget(&self, budget: &Budget) -> bool
pub fn fits_budget(&self, budget: &Budget) -> bool
Returns true if the given budget fits within the arena’s
capacity. The check is budget.bottom_bytes + budget.top_bytes <= capacity.
This is the generic budget contract referenced in the crate documentation. Producers compute a budget through whatever analysis they choose and use this method to verify admissibility before relying on the arena.
Sourcepub fn bottom_handle(&self) -> BottomHandle<'_>
pub fn bottom_handle(&self) -> BottomHandle<'_>
Obtain a bottom-end allocation handle.
Sourcepub fn top_handle(&self) -> TopHandle<'_>
pub fn top_handle(&self) -> TopHandle<'_>
Obtain a top-end allocation handle.
Sourcepub fn stack_handle(&self) -> BottomHandle<'_>
pub fn stack_handle(&self) -> BottomHandle<'_>
Alias for Arena::bottom_handle. Suitable for code that
treats the bottom end as a stack-like region.
Sourcepub fn heap_handle(&self) -> TopHandle<'_>
pub fn heap_handle(&self) -> TopHandle<'_>
Alias for Arena::top_handle. Suitable for code that treats
the top end as a heap-like region whose allocations are reset
together rather than freed individually.
Sourcepub fn alloc_bottom_bytes(&self, n: usize) -> Result<NonNull<[u8]>, AllocError>
pub fn alloc_bottom_bytes(&self, n: usize) -> Result<NonNull<[u8]>, AllocError>
Allocate n bytes from the bottom end with no alignment
requirement. Convenience wrapper for byte buffers and similar
allocations where the caller does not care about alignment.
Equivalent to allocating with a Layout::from_size_align(n, 1)
through the BottomHandle Allocator implementation.
Sourcepub fn alloc_top_bytes(&self, n: usize) -> Result<NonNull<[u8]>, AllocError>
pub fn alloc_top_bytes(&self, n: usize) -> Result<NonNull<[u8]>, AllocError>
Allocate n bytes from the top end with no alignment requirement.
Trait Implementations§
Auto Trait Implementations§
impl !Freeze for Arena
impl !RefUnwindSafe for Arena
impl !Send for Arena
impl !Sync for Arena
impl Unpin for Arena
impl UnsafeUnpin for Arena
impl UnwindSafe for Arena
Blanket Implementations§
Source§impl<T> ArchivePointee for T
impl<T> ArchivePointee for T
Source§type ArchivedMetadata = ()
type ArchivedMetadata = ()
Source§fn pointer_metadata(
_: &<T as ArchivePointee>::ArchivedMetadata,
) -> <T as Pointee>::Metadata
fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> LayoutRaw for T
impl<T> LayoutRaw for T
Source§fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
Source§impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
Source§unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
Source§fn resolve_niched(out: Place<NichedOption<T, N1>>)
fn resolve_niched(out: Place<NichedOption<T, N1>>)
out indicating that a T is niched.