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 }