lib/alloc/phase/src/capacity/fixture.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const capacity = @import("capacity");
  3 
  4 /// Demonstrates an implementation of the caller-provisioned exact owner
  5 /// protocol, re-exported from the package root as `ProvisionedExactFixture`.
  6 /// The owner accepts requested capacities from 1 to 64 bytes and requires
  7 /// caller-provided storage at least as long as the requested count. It retains
  8 /// the entire caller slice across its active lifecycle and returns that exact
  9 /// slice upon deinitialization, even when the provided buffer exceeds the
 10 /// requested capacity. Lifecycle transitions are guarded by debug assertions
 11 /// across `initialization`, `steady`, and `teardown` phases without allocating
 12 /// memory or holding an internal allocator. The instance remains caller-backed
 13 /// throughout its existence. It represents a concrete demonstration fixture, so
 14 /// its specific buffer retention and assertion choices should not be taken as
 15 /// universal requirements for all provisioned owners.
 16 pub const ExactOwner = struct {
 17     /// This constant defines the 16-byte storage alignment required by
 18     /// `ExactOwner.Storage`, which the caller must satisfy by supplying an
 19     /// aligned slice.
 20     pub const storage_alignment: usize = 16;
 21     /// This type is a mutable caller-provided slice of []align(16)u8 borrowed
 22     /// until owner deinit. It may be backed by a caller heap allocation or
 23     /// another suitable buffer. The fixture does not own or free the underlying
 24     /// allocation.
 25     pub const Storage = []align(storage_alignment) u8;
 26     /// Specifies the input limit configuration for `ExactOwner`, containing a
 27     /// single `bytes: usize` field. For this demonstration fixture, valid
 28     /// values are restricted to the range from 1 through 64 bytes.
 29     pub const Limits = struct {
 30         bytes: usize,
 31     };
 32     /// Represents the derived capacity of `ExactOwner`, storing the required
 33     /// byte count in `storage_bytes: usize`. This value reflects the storage
 34     /// quantity computed from the input limits, rather than the total length of
 35     /// the backing slice supplied by the caller.
 36     pub const Capacity = struct {
 37         storage_bytes: usize,
 38 
 39         /// This derivation error reports `EmptyStorage` for 0 requested bytes
 40         /// and `CapacityExceeded` when requested bytes exceed 64. The error set
 41         /// represents capacity derivation limits and does not include
 42         /// allocation failure.
 43         pub const DeriveError = error{
 44             EmptyStorage,
 45             CapacityExceeded,
 46         };
 47 
 48         /// Calculates the required `Capacity` from the provided `Limits`
 49         /// without performing memory allocation. Returns `error.EmptyStorage`
 50         /// when `limits.bytes` is zero, and `error.CapacityExceeded` when
 51         /// `limits.bytes` exceeds 64. For valid inputs between 1 and 64, it
 52         /// returns a `Capacity` instance whose `storage_bytes` equals
 53         /// `limits.bytes`.
 54         pub fn derive(limits: Limits) DeriveError!Capacity {
 55             if (limits.bytes == 0) return error.EmptyStorage;
 56             if (limits.bytes > storage_bytes_max) return error.CapacityExceeded;
 57             return .{ .storage_bytes = limits.bytes };
 58         }
 59     };
 60     /// Defines the error set returned by `init`, formed as the union of
 61     /// `Capacity.DeriveError` (`error{EmptyStorage, CapacityExceeded}`) and
 62     /// `error{StorageTooShort}`. Because initialization relies entirely on
 63     /// caller-provided memory without invoking dynamic allocators, the error
 64     /// set excludes `error.OutOfMemory`.
 65     pub const InitError = Capacity.DeriveError || error{StorageTooShort};
 66     /// This declaration specifies literal declared work bounds with
 67     /// transition_steps_max set to 1, cleanup_steps_per_call_max set to 0, and
 68     /// cleanup_calls_at_capacity_max set to 0. These values represent
 69     /// structural bounds whose numeric shape passes validation rather than
 70     /// measured CPU counts or independently established semantic unit proofs.
 71     pub const work_limits: capacity.WorkLimits = .{
 72         .transition_steps_max = 1,
 73         .cleanup_steps_per_call_max = 0,
 74         .cleanup_calls_at_capacity_max = 0,
 75     };
 76     /// Declares the compile-time `Declaration` capturing formal specification
 77     /// metadata for `ExactOwner`. It records the source envelope and typed
 78     /// bindings, specifying the capacity model, the overload policy
 79     /// `reject_before_seal`, open risk statuses for transitive and foreign
 80     /// risks, the transition work equation, and obligation keys. This
 81     /// declaration documents formal properties and links verification
 82     /// obligations for external analysis tools. The presence of the declaration
 83     /// does not itself prove that the implementation satisfies the stated
 84     /// invariants.
 85     pub const claim: capacity.Declaration = .{
 86         .source = .{
 87             .id = "alloc.provisioned_exact_fixture",
 88             .kind = .phase_static,
 89             .limit_source = .caller,
 90             .storage = .{
 91                 .covered = &.{
 92                     .{
 93                         .id = "caller_byte_storage",
 94                         .lifetime = .transferred,
 95                         .detail = "caller byte storage",
 96                     },
 97                 },
 98                 .excluded = &.{
 99                     "caller-owned payloads",
100                 },
101             },
102             .capacity = .{
103                 .inputs = &.{
104                     capacity.bindInput(Limits, "bytes", "bytes"),
105                 },
106                 .type_selectors = &.{},
107                 .nodes = &.{
108                     .{ .input = 0 },
109                 },
110                 .assertions = &.{.{
111                     .scope = .closure_total,
112                     .measure = .retained,
113                     .relation = .exact,
114                     .expression = 0,
115                 }},
116             },
117             .overload = .{
118                 .kind = .reject_before_seal,
119                 .detail = "short storage rejects before owner construction",
120             },
121             .risks = .{
122                 .transitive = .{
123                     .status = .open,
124                     .detail = "fixture has no callees",
125                 },
126                 .foreign = .{
127                     .status = .open,
128                     .detail = "fixture has no foreign resources",
129                 },
130             },
131             .work = .{ .equation = "transition_steps <= transition_steps_max" },
132             .obligations = &.{
133                 .{ .key = "alloc_provisioned_exact_capacity_capacity_model", .role = .capacity_model },
134                 .{ .key = "alloc_provisioned_exact_capacity_overload", .role = .overload },
135                 .{ .key = "alloc_provisioned_exact_capacity_work_bound", .role = .work_bound },
136             },
137         },
138         .bindings = .{
139             .owner = @This(),
140             .seal = .{
141                 .family = capacity.selector(@This().activate),
142                 .premise = .{
143                     .class = .checked_semantic_fact,
144                     .authority = .checker,
145                 },
146             },
147             .teardown = .{
148                 .family = capacity.selector(@This().deinit),
149                 .premise = .{
150                     .class = .checked_semantic_fact,
151                     .authority = .checker,
152                 },
153             },
154         },
155     };
156 
157     phase: capacity.Phase,
158     capacity: Capacity,
159     storage: Storage,
160 
161     const storage_bytes_max: usize = 64;
162 
163     /// Initializes an `ExactOwner` instance using caller-supplied storage and
164     /// configuration limits. It executes `Capacity.derive` first, returning
165     /// `error.StorageTooShort` without constructing an owner instance if
166     /// `storage.len` is less than `derived.storage_bytes`. On success, it
167     /// retains the entire caller slice, sets the internal phase to
168     /// `initialization`, and records the derived capacity. The function
169     /// performs no heap allocations.
170     pub fn init(storage: Storage, limits: Limits) InitError!ExactOwner {
171         const derived = try Capacity.derive(limits);
172         if (storage.len < derived.storage_bytes) return error.StorageTooShort;
173         return .{
174             .phase = .initialization,
175             .capacity = derived,
176             .storage = storage,
177         };
178     }
179 
180     /// Transitions the owner from `initialization` to `steady` phase. It
181     /// verifies that the current phase is `initialization` using a debug
182     /// assertion, which may be disabled in non-debug compilation modes and
183     /// therefore does not guarantee a panic in all release configurations.
184     /// Activation acquires no system resources and performs no memory
185     /// allocations.
186     pub fn activate(self: *ExactOwner) void {
187         std.debug.assert(self.phase == .initialization);
188         self.phase = .steady;
189     }
190 
191     /// Returns the active byte capacity of the owner, guarded by a debug
192     /// assertion that the owner is in the `steady` phase. The returned value is
193     /// `capacity.storage_bytes`, representing the required count derived from
194     /// initialization limits, rather than the length of the underlying storage
195     /// slice.
196     pub fn bytes(self: *const ExactOwner) usize {
197         std.debug.assert(self.phase == .steady);
198         return self.capacity.storage_bytes;
199     }
200 
201     /// Deinitializes the owner, transitioning the internal phase from `steady`
202     /// to `teardown`, invalidating the owner instance by setting it to
203     /// `undefined`, and returning the original caller-supplied slice in its
204     /// entirety. The function relies on a debug assertion to check the `steady`
205     /// phase prerequisite. It performs no implicit cleanup or byte zeroing and
206     /// does not free memory, leaving full ownership and custody of the returned
207     /// buffer with the caller.
208     pub fn deinit(self: *ExactOwner) Storage {
209         std.debug.assert(self.phase == .steady);
210         self.phase = .teardown;
211         const storage = self.storage;
212         self.* = undefined;
213         return storage;
214     }
215 };
216 
217 /// Demonstrates an implementation of the caller-provisioned rejecting owner
218 /// protocol, re-exported from the package root as
219 /// `ProvisionedRejectingFixture`. The owner manages a bounded number of slots,
220 /// up to 8, with each slot having a positive byte width, backed by
221 /// caller-supplied 16-byte aligned memory. Calling submit when full returns
222 /// Full after incrementing both submitted and rejected counts if both
223 /// increments fit, or returns AccountingOverflow before mutation if an
224 /// increment would overflow usize. The payload is preserved along either
225 /// rejection path. Calling cleanupOne pops and zeroes a single slot, while
226 /// deinit returns the entire original slice without executing a cleanup loop,
227 /// requiring the caller to keep borrowed bytes alive.
228 pub const RejectingOwner = struct {
229     /// This constant specifies a maximum of 8 allowed slots for
230     /// `RejectingOwner`. This slot limit applies regardless of the slot width,
231     /// which must be positive and yield a product that fits in `usize`.
232     pub const slots_max: usize = 8;
233     /// This constant specifies the 16-byte storage alignment required by
234     /// `RejectingOwner.Storage`, which remains the responsibility of the caller
235     /// to supply.
236     pub const storage_alignment: usize = 16;
237     /// Defines the storage type accepted and used by `RejectingOwner`, declared
238     /// as `[]align(16) u8`. This slice represents a mutable buffer borrowed
239     /// from the caller for the duration of the owner's active life. Ownership
240     /// of the memory remains with the caller.
241     pub const Storage = []align(storage_alignment) u8;
242     /// Specifies the configuration limits for `RejectingOwner`, containing
243     /// `slots: usize` and `slot_bytes: usize`. Valid limits require both fields
244     /// to be positive, `slots` to be less than or equal to 8, and their
245     /// arithmetic product to fit within `usize` without overflow.
246     pub const Limits = struct {
247         slots: usize,
248         slot_bytes: usize,
249     };
250     /// Stores the derived dimensional parameters of `RejectingOwner`, including
251     /// `slots: usize`, `slot_bytes: usize`, and `storage_bytes: usize`. The
252     /// `storage_bytes` field holds the exact product of `slots` and
253     /// `slot_bytes`, representing the total storage required by the owner.
254     pub const Capacity = struct {
255         slots: usize,
256         slot_bytes: usize,
257         storage_bytes: usize,
258 
259         /// This error type reports `InvalidLimit` on zero slots or width,
260         /// `CapacityOverflow` if their product overflows `usize`, and
261         /// `CapacityExceeded` if the slot count exceeds 8 after the product
262         /// check. These outcomes represent derivation failures rather than
263         /// domain `Exhaustion` conditions like `Full` or `AccountingOverflow`.
264         pub const DeriveError = error{
265             InvalidLimit,
266             CapacityOverflow,
267             CapacityExceeded,
268         };
269 
270         /// Derives the required `Capacity` from input `Limits` without
271         /// performing memory allocation. It evaluates error conditions in a
272         /// strict sequence: returns `error.InvalidLimit` if either `slots` or
273         /// `slot_bytes` is zero, returns `error.CapacityOverflow` if computing
274         /// `slots * slot_bytes` overflows `usize`, and returns
275         /// `error.CapacityExceeded` if `slots` exceeds 8 after the checked
276         /// product succeeds. On valid inputs, it returns a `Capacity` instance
277         /// recording the slot count, slot width, and computed storage byte
278         /// count.
279         pub fn derive(limits: Limits) DeriveError!Capacity {
280             if (limits.slots == 0 or limits.slot_bytes == 0) {
281                 return error.InvalidLimit;
282             }
283             const storage_bytes = try capacity.mul(
284                 usize,
285                 limits.slots,
286                 limits.slot_bytes,
287             );
288             if (limits.slots > slots_max) return error.CapacityExceeded;
289             return .{
290                 .slots = limits.slots,
291                 .slot_bytes = limits.slot_bytes,
292                 .storage_bytes = storage_bytes,
293             };
294         }
295     };
296     /// This error set comprises Full and AccountingOverflow. Full indicates
297     /// that all slots are occupied and the attempt is successfully recorded in
298     /// submitted and rejected counters while payload and used count remain
299     /// unchanged. AccountingOverflow occurs when an increment required by the
300     /// selected submission path would exceed usize, rather than when a valid
301     /// increment merely reaches the maximum value. This error returns before
302     /// any payload or counter mutation occurs. Only counters relevant to the
303     /// selected path are checked, specifically submitted and rejected for full
304     /// rejection, or submitted and accepted for successful admission.
305     pub const Exhaustion = error{
306         Full,
307         AccountingOverflow,
308     };
309     /// Specifies the error set returned by `init`, formed as the union of
310     /// `Capacity.DeriveError` (`error{InvalidLimit, CapacityOverflow,
311     /// CapacityExceeded}`) and `error{StorageTooShort}`. It captures failure
312     /// during limit derivation or when the supplied caller storage slice
313     /// contains fewer bytes than derived `storage_bytes`.
314     pub const InitError = Capacity.DeriveError || error{StorageTooShort};
315     /// Maintains diagnostic accounting counters for `RejectingOwner`, tracking
316     /// `submitted`, `accepted`, and `rejected` operations initialized to zero.
317     /// Submissions increment counters only along execution paths where
318     /// arithmetic fits within `usize` without overflow. An attempt that fails
319     /// because the owner is full is recorded by incrementing both `submitted`
320     /// and `rejected`, leaving `accepted` unchanged.
321     pub const Usage = struct {
322         submitted: usize = 0,
323         accepted: usize = 0,
324         rejected: usize = 0,
325     };
326     /// Declares the static `WorkLimits` for `RejectingOwner`, configuring
327     /// `transition_steps_max` as 1, `cleanup_steps_per_call_max` as 1, and
328     /// `cleanup_calls_at_capacity_max` as 8. These numbers represent declared
329     /// owner work units rather than elapsed execution time or hardware cycle
330     /// counts. In particular, each cleanup invocation executes `@memset` across
331     /// an entire slot, so the actual runtime work scales with `slot_bytes`.
332     pub const work_limits: capacity.WorkLimits = .{
333         .transition_steps_max = 1,
334         .cleanup_steps_per_call_max = 1,
335         .cleanup_calls_at_capacity_max = slots_max,
336     };
337     /// Declares the compile-time `Declaration` capturing formal specification
338     /// metadata for `RejectingOwner`. It binds the slot product capacity model,
339     /// the overload policy `reject_before_mutation` documenting payload
340     /// preservation alongside diagnostic counter updates, open risk statuses
341     /// for transitive and foreign risks, the bounded cleanup work equation, and
342     /// obligation keys. This declaration documents design contracts and
343     /// provides metadata for verification tools without executing runtime
344     /// checks or proving implementation correctness.
345     pub const claim: capacity.Declaration = .{
346         .source = .{
347             .id = "alloc.provisioned_rejecting_fixture",
348             .kind = .phase_static,
349             .limit_source = .caller,
350             .storage = .{
351                 .covered = &.{
352                     .{
353                         .id = "caller_slot_storage",
354                         .lifetime = .transferred,
355                         .detail = "caller slot storage",
356                     },
357                 },
358                 .excluded = &.{
359                     "caller-owned payloads",
360                 },
361             },
362             .capacity = .{
363                 .inputs = &.{
364                     capacity.bindInput(Limits, "slots", "slots"),
365                     capacity.bindInput(Limits, "slot_bytes", "slot_bytes"),
366                 },
367                 .type_selectors = &.{},
368                 .nodes = &.{
369                     .{ .input = 0 },
370                     .{ .input = 1 },
371                     .{ .product = .{ .left = 0, .right = 1 } },
372                 },
373                 .assertions = &.{.{
374                     .scope = .closure_total,
375                     .measure = .retained,
376                     .relation = .exact,
377                     .expression = 2,
378                 }},
379             },
380             .overload = .{
381                 .kind = .reject_before_mutation,
382                 .detail = "full submission changes only submitted and rejected counters",
383             },
384             .risks = .{
385                 .transitive = .{
386                     .status = .open,
387                     .detail = "fixture has no callees",
388                 },
389                 .foreign = .{
390                     .status = .open,
391                     .detail = "fixture has no foreign resources",
392                 },
393             },
394             .work = .{ .equation = "transition <= 1 and cleanup <= slots" },
395             .obligations = &.{
396                 .{ .key = "alloc_provisioned_rejecting_capacity_capacity_model", .role = .capacity_model },
397                 .{ .key = "alloc_provisioned_rejecting_capacity_overload", .role = .overload },
398                 .{ .key = "alloc_provisioned_rejecting_capacity_work_bound", .role = .work_bound },
399             },
400         },
401         .bindings = .{
402             .owner = @This(),
403             .seal = .{
404                 .family = capacity.selector(@This().activate),
405                 .premise = .{
406                     .class = .checked_semantic_fact,
407                     .authority = .checker,
408                 },
409             },
410             .teardown = .{
411                 .family = capacity.selector(@This().deinit),
412                 .premise = .{
413                     .class = .checked_semantic_fact,
414                     .authority = .checker,
415                 },
416             },
417         },
418     };
419 
420     phase: capacity.Phase,
421     capacity: Capacity,
422     storage: Storage,
423     used: usize = 0,
424     usage: Usage = .{},
425 
426     /// Initializes a `RejectingOwner` instance using caller storage and
427     /// configuration limits. It executes `Capacity.derive` first and then
428     /// checks that `storage.len` is at least `derived.storage_bytes`, returning
429     /// `error.StorageTooShort` if the buffer is insufficient. On success, it
430     /// preserves the original storage slice, sets `phase` to `initialization`,
431     /// initializes `used` to 0, and sets `usage` counters to 0. The function
432     /// performs no dynamic memory allocation.
433     pub fn init(storage: Storage, limits: Limits) InitError!RejectingOwner {
434         const derived = try Capacity.derive(limits);
435         if (storage.len < derived.storage_bytes) return error.StorageTooShort;
436         return .{
437             .phase = .initialization,
438             .capacity = derived,
439             .storage = storage,
440         };
441     }
442 
443     /// Transitions the owner from `initialization` to `steady` phase. It
444     /// verifies that the current phase is `initialization` using a debug
445     /// assertion. Activation acquires no system resources and performs no
446     /// memory allocations.
447     pub fn activate(self: *RejectingOwner) void {
448         std.debug.assert(self.phase == .initialization);
449         self.phase = .steady;
450     }
451 
452     /// Submits a byte value into the owner, guarded by a debug assertion that
453     /// the current phase is `steady`. When capacity is exhausted because `used`
454     /// equals `capacity.slots`, the function checks that `submitted` and
455     /// `rejected` will not overflow `std.math.maxInt(usize)`. If accounting
456     /// fits, it increments both counters and returns `error.Full` while
457     /// preserving stored payload and slot count. If any counter addition would
458     /// overflow `usize`, it returns `error.AccountingOverflow` before any
459     /// mutation occurs. On successful submission, it increments
460     /// `usage.submitted`, fills all bytes of the next slot with `byte`,
461     /// increments `used`, and increments `usage.accepted`. The operation
462     /// performs no hidden allocations and provides no automatic state reset or
463     /// buffer reuse beyond calling `cleanupOne`.
464     pub fn submit(self: *RejectingOwner, byte: u8) Exhaustion!void {
465         std.debug.assert(self.phase == .steady);
466         if (self.used == self.capacity.slots) {
467             if (self.usage.submitted == std.math.maxInt(usize)) {
468                 return error.AccountingOverflow;
469             }
470             if (self.usage.rejected == std.math.maxInt(usize)) {
471                 return error.AccountingOverflow;
472             }
473             self.usage.submitted += 1;
474             self.usage.rejected += 1;
475             return error.Full;
476         }
477         if (self.usage.submitted == std.math.maxInt(usize)) {
478             return error.AccountingOverflow;
479         }
480         if (self.usage.accepted == std.math.maxInt(usize)) {
481             return error.AccountingOverflow;
482         }
483         self.usage.submitted += 1;
484         const offset = self.used * self.capacity.slot_bytes;
485         @memset(self.storage[offset..][0..self.capacity.slot_bytes], byte);
486         self.used += 1;
487         self.usage.accepted += 1;
488     }
489 
490     /// Removes a single slot from the owner, guarded by a debug assertion that
491     /// the phase is `steady`. If `used` is 0, it returns `false` without
492     /// modifying state. When slots are present, it decrements `used`, zeroes
493     /// all bytes in the removed slot using `@memset`, and returns `true`. The
494     /// function leaves `usage` accounting counters unchanged. It performs no
495     /// dynamic memory allocation, does not terminate or teardown the owner, and
496     /// executes cleanup work that scales with `slot_bytes`.
497     pub fn cleanupOne(self: *RejectingOwner) bool {
498         std.debug.assert(self.phase == .steady);
499         if (self.used == 0) return false;
500         self.used -= 1;
501         const offset = self.used * self.capacity.slot_bytes;
502         @memset(self.storage[offset..][0..self.capacity.slot_bytes], 0);
503         return true;
504     }
505 
506     /// Deinitializes the owner, asserting that the owner is in the `steady`
507     /// phase. It transitions `phase` to `teardown`, sets the owner instance to
508     /// `undefined`, and returns the original caller-supplied slice in its
509     /// entirety. The function does not call `cleanupOne` or zero out remaining
510     /// occupied slot payloads, leaving any required scrubbing or memory
511     /// sanitization to the caller.
512     pub fn deinit(self: *RejectingOwner) Storage {
513         std.debug.assert(self.phase == .steady);
514         self.phase = .teardown;
515         const storage = self.storage;
516         self.* = undefined;
517         return storage;
518     }
519 };
520 
521 comptime {
522     capacity.requireProvisionedExactOwnerShape(ExactOwner);
523 }
524 
525 comptime {
526     capacity.requireProvisionedRejectingOwnerShape(RejectingOwner);
527 }