tiny.reducer.bytes.storage
Defined in bytes.
Storage holds the backing memory a byte reduction run needs, allocated once up front and reused for every run: the workspace.
API (3)
Types and contracts
Public types and contracts.
Regions: Pair of memory lane slicesStorage.acquire()returns.Status: Snapshot of one workspace's state, taken by value at the moment of the call.Storage: Workspace, allocated up front and double-buffered, that carries reduction in steady state.
Source
Source: lib/reducer/src/bytes/root.zig:56
zig
/// Module holding the workspace, allocated up front and double-buffered, and/// the phase-checked lifecycle around it.pub const storage = @import("storage.zig");Source: lib/reducer/src/bytes/storage.zig
zig
//! `Storage` holds the backing memory a byte reduction run needs, allocated//! once up front and reused for every run: the *workspace*. The `alloc_phase`//! checker tracks the phase and establishes that the owner makes no further//! backing allocation once the workspace is activated into steady state.//!//! ## Workspace Lifecycle//!//! | Operation | Effect / Transition |//! | :--- | :--- |//! | `storage.activate()` | Moves `initialization` to `steady (idle)` |//! | `storage.acquire(bytes)` | Leases `steady (idle)` to `steady (acquired)` |//! | `result.deinit()` / `storage.release()` | Releases `steady (acquired)` back to `steady (idle)` |//! | `storage.deinit(allocator)` (unactivated) | Frees unactivated `initialization` to `teardown` |//! | `storage.deinit(allocator)` (idle) | Frees idle `steady` to `teardown` (invalid while acquired) |//!//! 1. **Initialization (`Phase.initialization`)**: `Storage.init()` derives the//! layout, which comes to $N + \max(N - 1, 0)$ bytes (its *capacity*), and//! allocates the backing buffer once. The instance starts out with//! `phase = .initialization`. A workspace that is never activated can be//! freed straight away by `storage.deinit(allocator)`, using the allocator//! `init()` received. Filling in `Storage` fields by hand is unsafe, so//! callers reach a valid workspace through `init()`.//! 2. **Activation (`Phase.steady`)**: the caller calls `storage.activate()`,//! which seals the initialization phase and moves the workspace to steady//! and idle. In steady state the workspace owner allocates nothing further.//! 3. **Acquisition (`in_use = true`)**: inside `reduce()`,//! `storage.acquire(input_bytes)` checks that the workspace is in steady//! phase, checks that no lease is out, and checks that `input_bytes` is//! within `max_input_bytes`. It sets `in_use = true` and returns the two//! byte lanes as `Regions`, returning slices, copying nothing into the//! witness lane and initializing nothing. An oversized input returns//! `error.InputCapacityExceeded`, and a request made while a lease is out//! returns `error.ReductionStorageInUse`, both before any workspace byte//! changes.//! 4. **Release and reuse (`in_use = false`)**: when a run finishes,//! `Result.deinit()`, or `storage.release()`, clears `in_use`. The workspace//! returns to steady and idle, and a later run can acquire it with no//! further allocation.//! 5. **Teardown (`Phase.teardown`)**: `storage.deinit(allocator)` checks that//! no lease is out, sets `phase = .teardown`, and frees the backing buffer//! with the same allocator `init()` received.//!//! ## Ownership and Concurrency Constraints//! - `Storage` offers no thread safety. `in_use` is a plain boolean that turns//! away a second use on one thread, so reaching one workspace from multiple//! threads at once is the caller's to synchronize.//! - Zig checks no linear or affine type at compile time, and a runtime check//! catches no shallow copy of a `Storage` or a `Result`, so one owner holds//! each of them, makes no shallow duplicate, and leaves the bookkeeping//! fields alone.//! - The capacity claim `reducer.byte_storage` covers the two internal byte//! lanes. It leaves out the caller's own initial input bytes, the predicate's//! context, and every allocation and foreign side effect the callback//! performs.const std = @import("std");const alloc_phase = @import("alloc_phase");const capacity_mod = @import("capacity.zig");const model = @import("model.zig");/// Pair of memory lane slices `Storage.acquire()` returns. `acquire()` copies/// nothing into these slices and initializes nothing in them: they are/// sub-slices of the backing allocation.pub const Regions = struct { /// Slice of the witness lane sized to this run's `input_bytes`. current: []u8, /// Scratch lane, whose length is `candidate_bytes`, which is /// $\max(N - 1, 0)$. candidate: []u8,};/// Snapshot of one workspace's state, taken by value at the moment of the call.pub const Status = struct { /// Workspace current phase, one of `.initialization`, `.steady`, or /// `.teardown`. phase: alloc_phase.capacity.Phase, /// Flag recording whether a running reduction or a `Result` whose /// `deinit()` has yet to be called currently holds the workspace. in_use: bool, /// Derived capacity the workspace was configured for, which is /// $N + \max(N - 1, 0)$ bytes. The figure is a record of the configured /// size, and it stays recorded after teardown. storage_bytes: usize, /// Longest input in bytes this workspace serves, which is $N$. max_input_bytes: usize,};/// Workspace, allocated up front and double-buffered, that carries reduction in/// steady state. It holds the backing allocation and tracks the phase it is in/// and whether a lease is out.pub const Storage = struct { /// Lifecycle phase that `alloc_phase` enforces. phase: alloc_phase.capacity.Phase, /// Layout derived while initializing. capacity: capacity_mod.Capacity, /// Entire contiguous backing byte allocation. bytes: []u8, /// Fixed sub-slice reserved for the witness lane, spanning $0 \dots N$. current: []u8, /// Fixed sub-slice reserved for the scratch lane, spanning /// $N \dots N + \max(N - 1, 0)$. candidate: []u8, /// Flag recording that a lease is out, turning away a reentrant acquisition /// and one made while an earlier result is still live. in_use: bool = false, /// Re-export of the capacity limits type. pub const Limits: type = capacity_mod.Limits; /// Re-export of the derived capacity type. pub const Capacity: type = capacity_mod.Capacity; /// Re-export of the exhaustion error set. pub const Exhaustion: type = model.Exhaustion; /// Errors `Storage.init()` may return. pub const InitError = std.mem.Allocator.Error || capacity_mod.DeriveError; /// Stardust capacity declaration that phase-directed allocation /// verification reads. It records that the workspace owner makes no further /// backing allocation once sealed into steady state. It bounds the storage /// by $2 \times \text{max\_input\_bytes}$. It leaves out the memory and the /// effects the caller's predicate owns. pub const claim: alloc_phase.capacity.Declaration = .{ .source = .{ .id = "reducer.byte_storage", .kind = .phase_static, .limit_source = .caller, .storage = .{ .covered = &.{ .{ .id = "accepted_input_and_borrowed_result_bytes", .lifetime = .steady, .detail = "accepted input and borrowed result bytes", }, .{ .id = "deletion_candidate_scratch_bytes", .lifetime = .steady, .detail = "deletion candidate scratch bytes", }, }, .excluded = &.{ "caller-owned initial input bytes", "caller-owned predicate context, allocation, and effects", }, }, .capacity = .{ .inputs = &.{ alloc_phase.capacity.bindInput(Limits, "max_input_bytes", "max_input_bytes"), }, .type_selectors = &.{}, .nodes = &.{ .{ .input = 0 }, .{ .scale = .{ .node = 0, .coefficient = .{ .literal = 2 } } }, }, .assertions = &.{.{ .scope = .closure_total, .measure = .retained, .relation = .upper_bound, .expression = 1, }}, }, .overload = .{ .kind = .reject_before_mutation, .detail = "oversize and concurrent requests fail before workspace bytes change", }, .risks = .{ .transitive = .{ .status = .witnessed, .detail = "reduction uses byte lanes; predicate effects remain caller-owned", }, .foreign = .{ .status = .excluded, .detail = "predicate and operating-system effects are caller-owned", }, }, .obligations = &.{ .{ .key = "reducer_byte_capacity", .role = .capacity_model }, .{ .key = "reducer_byte_acquisition", .role = .custom }, .{ .key = "reducer_byte_oom", .role = .custom }, .{ .key = "reducer_byte_boundaries", .role = .overload }, .{ .key = "reducer_byte_reuse", .role = .overload }, .{ .key = "reducer_byte_sealed", .role = .transitive_risk }, .{ .key = "reducer_byte_callback", .role = .foreign_risk }, .{ .key = "reducer_byte_root", .role = .custom }, }, }, .bindings = .{ .owner = @This(), .seal = .{ .family = alloc_phase.capacity.selector(@This().activate), .premise = .{ .class = .checked_semantic_fact, .authority = .checker, }, }, .teardown = .{ .family = alloc_phase.capacity.selector(@This().deinit), .premise = .{ .class = .checked_semantic_fact, .authority = .checker, }, }, }, }; /// Allocates a reduction workspace and divides it into the two lanes, sized /// for inputs up to `limits.max_input_bytes`. The new workspace is in /// `.initialization`, and the caller calls `storage.activate()` on it /// before passing it to `reduce()`. A workspace that is never activated can /// be freed directly by `storage.deinit()`. /// /// ## Errors /// - `error.CapacityOverflow` when $N + \max(N - 1, 0)$ overflows `usize`. /// - `error.OutOfMemory` when the allocator declines the request. pub fn init(allocator: std.mem.Allocator, limits: Limits) InitError!Storage { const capacity = try Capacity.derive(limits); const bytes = try allocator.alloc(u8, capacity.storage_bytes); const owner = Storage{ .phase = .initialization, .capacity = capacity, .bytes = bytes, .current = bytes[0..limits.max_input_bytes], .candidate = bytes[capacity.candidate_offset..][0..capacity.candidate_bytes], }; std.debug.assert(owner.current.len == limits.max_input_bytes); std.debug.assert(owner.candidate.len == capacity.candidate_bytes); std.debug.assert(owner.current.len + owner.candidate.len == owner.bytes.len); return owner; } /// Moves the workspace from `.initialization` to `.steady`. Once it /// returns, the workspace owner makes no further heap allocation. /// /// ## Preconditions /// - `self.phase` is `.initialization`. /// - The backing allocation's length equals `self.capacity.storage_bytes`. pub fn activate(self: *Storage) void { std.debug.assert(self.phase == .initialization); std.debug.assert(self.bytes.len == self.capacity.storage_bytes); self.phase = .steady; } /// Takes exclusive use of the workspace for a run over `input_bytes` bytes. /// It returns `Regions` holding sub-slices of the backing buffer. It copies /// nothing into `Regions.current`. /// /// ## Preconditions /// - `self.phase` is `.steady`. /// /// ## Errors /// - `error.ReductionStorageInUse` when `self.in_use` is already true. A /// reentrant call, and a call made while an earlier result is still live, /// are both turned away without disturbing the hold that is already out. /// - `error.InputCapacityExceeded` when `input_bytes` exceeds /// `max_input_bytes`. pub fn acquire(self: *Storage, input_bytes: usize) Exhaustion!Regions { std.debug.assert(self.phase == .steady); if (self.in_use) return error.ReductionStorageInUse; if (input_bytes > self.capacity.limits.max_input_bytes) { return error.InputCapacityExceeded; } std.debug.assert(self.current.len == self.capacity.limits.max_input_bytes); std.debug.assert(self.candidate.len == self.capacity.candidate_bytes); self.in_use = true; return .{ .current = self.current[0..input_bytes], .candidate = self.candidate, }; } /// Gives up this run's lease, returning the workspace to steady and idle so /// it can serve another run. `Result.deinit()` calls it, and it moves the /// workspace no closer to teardown. /// /// ## Preconditions /// - `self.phase` is `.steady`. /// - `self.in_use` is true. pub fn release(self: *Storage) void { std.debug.assert(self.phase == .steady); std.debug.assert(self.in_use); self.in_use = false; std.debug.assert(!self.in_use); } /// Returns a snapshot of this workspace, taken by value at the moment of /// the call. pub fn status(self: *const Storage) Status { return .{ .phase = self.phase, .in_use = self.in_use, .storage_bytes = self.capacity.storage_bytes, .max_input_bytes = self.capacity.limits.max_input_bytes, }; } /// Frees the backing allocation and moves the workspace to `.teardown`. It /// works on an idle steady workspace and on one still in `.initialization` /// that was never activated. It needs the same allocator `Storage.init()` /// received. /// /// ## Preconditions /// - `self.phase` is anything other than `.teardown`. /// - `self.in_use` is false, so every result has had its `deinit()` called. pub fn deinit(self: *Storage, allocator: std.mem.Allocator) void { std.debug.assert(self.phase != .teardown); std.debug.assert(!self.in_use); std.debug.assert(self.bytes.len == self.capacity.storage_bytes); self.phase = .teardown; allocator.free(self.bytes); self.bytes = &.{}; self.current = &.{}; self.candidate = &.{}; }};fn checkInitFailures(allocator: std.mem.Allocator) !void { var storage = try Storage.init(allocator, .{ .max_input_bytes = 9 }); storage.deinit(allocator);}test "byte reduction storage acquires one exact region" { comptime { @stardustClaim( @import("alloc_phase").capacity.witness(Storage, "reducer_byte_acquisition"), null, null, null, null, null, null, ); } var counting = std.testing.FailingAllocator.init(std.testing.allocator, .{}); const limits = capacity_mod.Limits{ .max_input_bytes = 9 }; const capacity = try capacity_mod.Capacity.derive(limits); var storage = try Storage.init(counting.allocator(), limits); defer storage.deinit(counting.allocator()); try std.testing.expectEqual(@as(usize, 1), counting.alloc_index); try std.testing.expectEqual(capacity.storage_bytes, counting.allocated_bytes); try std.testing.expectEqual(alloc_phase.capacity.Phase.initialization, storage.status().phase); storage.activate(); const regions = try storage.acquire(9); defer storage.release(); try std.testing.expectEqual(@intFromPtr(storage.bytes.ptr), @intFromPtr(regions.current.ptr)); try std.testing.expectEqual( @intFromPtr(storage.bytes.ptr) + capacity.candidate_offset, @intFromPtr(regions.candidate.ptr), );}test "byte reduction storage retries after every allocation failure" { comptime { @stardustClaim( @import("alloc_phase").capacity.witness(Storage, "reducer_byte_oom"), null, null, null, null, null, null, ); } try std.testing.checkAllAllocationFailures(std.testing.allocator, checkInitFailures, .{});}comptime { alloc_phase.capacity.requireAllocatorRejectingOwnerShape(Storage);}Audit
| Definitions | 2 |
|---|---|
| Public names | 2 |
| Members | 2 |
| Version | 26.7.0 |
| Revision | daab053ee433 |