lib/choir/src/core/context/memory.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const alloc_fixed = @import("alloc_fixed");
  3 
  4 const Alignment = std.mem.Alignment;
  5 const Allocator = std.mem.Allocator;
  6 const FixedBufferAllocator = alloc_fixed.FixedBuffer;
  7 
  8 /// The smallest alignment `Capacity.derive` accepts.
  9 ///
 10 /// Every segment is placed under the limits' `maximum_alignment`, and the
 11 /// storage opens with the `Memory` header, so an alignment under the
 12 /// header's own would put the header where its own type cannot be read.
 13 /// `derive` refuses one with `error.CapacityOverflow`.
 14 ///
 15 /// THE FLOOR IS PUBLIC BECAUSE A CALLER STATES THE ALIGNMENT. `Memory` is
 16 /// not one of the names `Context` re-exports, so a caller that lets a person
 17 /// state this figure has no other way to ask what the smallest legal one is,
 18 /// and a number copied into the caller drifts the day this one moves.
 19 pub const minimum_alignment: Alignment = .fromByteUnits(@alignOf(Memory));
 20 
 21 pub const Limits = struct {
 22     maximum_alignment: Alignment,
 23     configuration: Configuration,
 24     types: Types,
 25     attributes: Attributes,
 26     operations: Operations,
 27     diagnostics: Diagnostics,
 28     transient_bytes: usize,
 29 
 30     pub const Configuration = struct {
 31         table_bytes: usize,
 32         name_bytes: usize,
 33         interface_bytes: usize,
 34         transaction_bytes: usize,
 35     };
 36 
 37     pub const Types = struct {
 38         table_bytes: usize,
 39         key_bytes: usize,
 40         payload_bytes: usize,
 41     };
 42 
 43     pub const Attributes = struct {
 44         table_bytes: usize,
 45         payload_bytes: usize,
 46     };
 47 
 48     pub const Operations = struct {
 49         storage_bytes: usize,
 50         nested_bytes: usize,
 51     };
 52 
 53     pub const Diagnostics = struct {
 54         handler_bytes: usize,
 55         payload_bytes: usize,
 56     };
 57 
 58     /// The bytes `limits` reserves for `selected`.
 59     ///
 60     /// THE GROUPING AND THE NAMES ARE TWO VIEWS OF ONE SET OF FIGURES. The
 61     /// fields above are grouped by the table they size, because that is how
 62     /// the context is built. `Segment` names the same figures flat, because
 63     /// that is how a caller states one of them, how `Usage` reports it, and
 64     /// how an exhaustion names the one that filled. This map is the seam
 65     /// between the two views, and it lives here so that a segment added to
 66     /// the enum cannot compile until this switch answers for it.
 67     pub fn at(self: Limits, selected: Segment) usize {
 68         var held = self;
 69         return held.field(selected).*;
 70     }
 71 
 72     /// Replaces the bytes `self` reserves for `selected`, which is how a
 73     /// caller states one capacity and leaves the rest as they were.
 74     pub fn set(self: *Limits, selected: Segment, bytes: usize) void {
 75         self.field(selected).* = bytes;
 76     }
 77 
 78     fn field(self: *Limits, selected: Segment) *usize {
 79         return switch (selected) {
 80             .configuration_tables => &self.configuration.table_bytes,
 81             .configuration_names => &self.configuration.name_bytes,
 82             .configuration_interfaces => &self.configuration.interface_bytes,
 83             .configuration_transactions => &self.configuration.transaction_bytes,
 84             .type_tables => &self.types.table_bytes,
 85             .type_keys => &self.types.key_bytes,
 86             .type_payloads => &self.types.payload_bytes,
 87             .attribute_tables => &self.attributes.table_bytes,
 88             .attribute_payloads => &self.attributes.payload_bytes,
 89             .operation_storage => &self.operations.storage_bytes,
 90             .operation_nested => &self.operations.nested_bytes,
 91             .diagnostic_handlers => &self.diagnostics.handler_bytes,
 92             .diagnostic_payloads => &self.diagnostics.payload_bytes,
 93             .transient => &self.transient_bytes,
 94         };
 95     }
 96 
 97     pub const testing: Limits = .{
 98         .maximum_alignment = .@"64",
 99         .configuration = .{
100             .table_bytes = 512 * 1024,
101             .name_bytes = 128 * 1024,
102             .interface_bytes = 512 * 1024,
103             .transaction_bytes = 128 * 1024,
104         },
105         .types = .{
106             .table_bytes = 512 * 1024,
107             .key_bytes = 256 * 1024,
108             .payload_bytes = 512 * 1024,
109         },
110         .attributes = .{
111             .table_bytes = 1024 * 1024,
112             .payload_bytes = 1024 * 1024,
113         },
114         .operations = .{
115             .storage_bytes = 16 * 1024 * 1024,
116             .nested_bytes = 16 * 1024 * 1024,
117         },
118         .diagnostics = .{
119             .handler_bytes = 64 * 1024,
120             .payload_bytes = 256 * 1024,
121         },
122         .transient_bytes = 2 * 1024 * 1024,
123     };
124 
125     pub const standard: Limits = .{
126         .maximum_alignment = .@"64",
127         .configuration = .{
128             .table_bytes = 64 * 1024,
129             .name_bytes = 32 * 1024,
130             .interface_bytes = 512 * 1024,
131             .transaction_bytes = 64 * 1024,
132         },
133         .types = .{
134             .table_bytes = 128 * 1024,
135             .key_bytes = 128 * 1024,
136             .payload_bytes = 128 * 1024,
137         },
138         .attributes = .{
139             .table_bytes = 1024 * 1024,
140             .payload_bytes = 256 * 1024,
141         },
142         .operations = .{
143             .storage_bytes = 8 * 1024 * 1024,
144             .nested_bytes = 8 * 1024 * 1024,
145         },
146         .diagnostics = .{
147             .handler_bytes = 16 * 1024,
148             .payload_bytes = 128 * 1024,
149         },
150         .transient_bytes = 256 * 1024,
151     };
152 };
153 
154 /// The largest total a context's storage may come to.
155 ///
156 /// A JUDGEMENT WITH AN ARGUMENT, NOT A MEASUREMENT OF ONE ALLOCATOR. An
157 /// allocator may add a header to a request, round it to a page, and count the
158 /// request itself more than once before it answers. `ArenaAllocator` counts it
159 /// twice: its fast path has already advanced the node's end index by the
160 /// request by the time it reaches
161 /// `mem.alignForward(usize, @sizeOf(Node) + aligned_index + n, 2)`, so the sum
162 /// it forms there is the request twice over plus what the arena already holds.
163 /// A quarter of the address space leaves the other three quarters for that
164 /// second copy, for everything the allocator holds, and for its own header. A
165 /// half does not: half aborts on a warm arena where a page below half answers
166 /// null, and a page below half is a measurement of one arena at one warmth
167 /// rather than a figure, since the slack a page buys is spent once the arena
168 /// holds more than a page. Exhausting a quarter would take an allocator
169 /// already holding eight exbibytes, which no host grants.
170 ///
171 /// No host grants a total this large either. On a 64-bit `usize` it is more
172 /// than a hundred billion times the total `Limits.testing` states, which is
173 /// the larger of the two presets this file carries. On a 32-bit `usize` the
174 /// quarter comes to one byte under a gibibyte, which is still more than
175 /// twenty-six times that same total and is a quarter of everything such a
176 /// host can address at all. Refusing at or above the figure denies no caller
177 /// anything on either width. Past the edge an allocator's behaviour
178 /// is its own business and not something this library can read:
179 /// `std.mem.Allocator.rawAlloc` is documented to answer null when it cannot
180 /// allocate, and an allocator that aborts instead does so on state this
181 /// library cannot see, since the same figure a fresh `ArenaAllocator` refuses
182 /// cleanly aborts one that already holds a node.
183 ///
184 /// The figure is pinned from below rather than derived: a test places the
185 /// largest limits this admits into a warm arena and holds that `Context.init`
186 /// answers `error.OutOfMemory` and never aborts. That test is what fails the
187 /// day an allocator's growth changes, which is the only derivation that stays
188 /// true.
189 pub const maximum_storage_bytes: usize = std.math.maxInt(usize) / 4;
190 
191 /// Which stated figure `derive` cannot place, what was asked for, and the
192 /// largest that segment could have taken.
193 ///
194 /// `largest` IS READ IN `derive`'s PLACEMENT ORDER. It is the most this
195 /// segment could have taken GIVEN THE SEGMENTS PLACED BEFORE IT, so it is the
196 /// answer to "write this figure instead" and not a property of the segment on
197 /// its own. A caller that lowers an earlier segment raises this one.
198 pub const Refusal = struct {
199     segment: Segment,
200     stated: usize,
201     largest: usize,
202 };
203 
204 /// The first segment figure `derive` cannot place, or null when every one
205 /// fits.
206 ///
207 /// ALLOCATION FREE AND SIDE EFFECT FREE, so a caller asks before it builds
208 /// anything and says the sentence in its own words, with the segment, the
209 /// figure stated and the figure that would be admitted.
210 ///
211 /// IT ANSWERS ABOUT THE FIGURES AND NOT THE ALIGNMENT. `derive` also refuses
212 /// an alignment under `minimum_alignment`, which is public for exactly that
213 /// reason and which a caller checks against directly. Given an alignment
214 /// `derive` accepts, `derive` answers `error.CapacityOverflow` exactly when
215 /// this answers non-null: a partial sum that would wrap has already crossed
216 /// half the address space, so `maximum_storage_bytes` subsumes the arithmetic
217 /// every `place` performs and no sum inside `derive` can fail once this has
218 /// passed.
219 pub fn refusal(limits: Limits) ?Refusal {
220     const alignment = limits.maximum_alignment.toByteUnits();
221     if (alignment < @alignOf(Memory)) return null;
222     var cursor: usize = @sizeOf(Memory);
223     for (std.enums.values(Segment)) |selected| {
224         const stated = limits.at(selected);
225         const offset = alignedWithin(cursor, alignment) orelse
226             return .{ .segment = selected, .stated = stated, .largest = 0 };
227         const largest = maximum_storage_bytes - offset;
228         if (stated > largest) return .{ .segment = selected, .stated = stated, .largest = largest };
229         cursor = offset + stated;
230     }
231     return null;
232 }
233 
234 /// `cursor` rounded up to `alignment`, or null when that lands at or past
235 /// `maximum_storage_bytes`. `cursor` is at most the ceiling on entry, so the
236 /// rounding itself cannot wrap.
237 fn alignedWithin(cursor: usize, alignment: usize) ?usize {
238     std.debug.assert(cursor <= maximum_storage_bytes);
239     std.debug.assert(alignment != 0);
240     if (alignment > maximum_storage_bytes) return null;
241     const mask = alignment - 1;
242     if (cursor > maximum_storage_bytes - mask) return null;
243     const offset = (cursor + mask) & ~mask;
244     if (offset > maximum_storage_bytes) return null;
245     return offset;
246 }
247 
248 pub const Segment = enum {
249     configuration_tables,
250     configuration_names,
251     configuration_interfaces,
252     configuration_transactions,
253     type_tables,
254     type_keys,
255     type_payloads,
256     attribute_tables,
257     attribute_payloads,
258     operation_storage,
259     operation_nested,
260     diagnostic_handlers,
261     diagnostic_payloads,
262     transient,
263 };
264 
265 /// Which segment a request found full, and how many bytes it asked for.
266 ///
267 /// The limit and the bytes in use at that moment are the segment's own
268 /// `SegmentUsage`, so a caller that reports an exhaustion reads all four
269 /// figures without this type repeating two of them.
270 pub const Exhausted = struct {
271     segment: Segment,
272     requested_bytes: usize,
273 };
274 
275 pub const SegmentUsage = struct {
276     frontier_bytes: usize,
277     reserved_bytes: usize,
278 
279     pub fn remainingBytes(self: SegmentUsage) usize {
280         return self.reserved_bytes - self.frontier_bytes;
281     }
282 };
283 
284 pub const Usage = struct {
285     configuration_tables: SegmentUsage,
286     configuration_names: SegmentUsage,
287     configuration_interfaces: SegmentUsage,
288     configuration_transactions: SegmentUsage,
289     type_tables: SegmentUsage,
290     type_keys: SegmentUsage,
291     type_payloads: SegmentUsage,
292     attribute_tables: SegmentUsage,
293     attribute_payloads: SegmentUsage,
294     operation_storage: SegmentUsage,
295     operation_nested: SegmentUsage,
296     diagnostic_handlers: SegmentUsage,
297     diagnostic_payloads: SegmentUsage,
298     transient: SegmentUsage,
299 
300     pub fn get(self: Usage, selected: Segment) SegmentUsage {
301         return switch (selected) {
302             inline else => |segment_name| @field(self, @tagName(segment_name)),
303         };
304     }
305 };
306 
307 const SegmentAllocator = struct {
308     fixed: FixedBufferAllocator,
309     buffer: []u8,
310     exhausted_segment: *?Segment,
311     exhausted_request: *usize,
312     segment_name: Segment,
313 
314     fn init(buffer: []u8, memory: *Memory, segment_name: Segment) SegmentAllocator {
315         return .{
316             .fixed = FixedBufferAllocator.init(buffer),
317             .buffer = buffer,
318             .exhausted_segment = &memory.exhausted_segment,
319             .exhausted_request = &memory.exhausted_request,
320             .segment_name = segment_name,
321         };
322     }
323 
324     pub fn allocator(self: *SegmentAllocator) Allocator {
325         return .{
326             .ptr = self,
327             .vtable = &.{
328                 .alloc = alloc,
329                 .resize = resize,
330                 .remap = remap,
331                 .free = free,
332             },
333         };
334     }
335 
336     fn alloc(
337         context: *anyopaque,
338         len: usize,
339         alignment: Alignment,
340         return_address: usize,
341     ) ?[*]u8 {
342         const self: *SegmentAllocator = @ptrCast(@alignCast(context));
343         const result = self.fixed.allocator().rawAlloc(len, alignment, return_address);
344         if (result == null) self.recordExhaustion(len);
345         return result;
346     }
347 
348     fn resize(
349         context: *anyopaque,
350         memory: []u8,
351         alignment: Alignment,
352         new_len: usize,
353         return_address: usize,
354     ) bool {
355         const self: *SegmentAllocator = @ptrCast(@alignCast(context));
356         return self.fixed.allocator().rawResize(memory, alignment, new_len, return_address);
357     }
358 
359     fn remap(
360         context: *anyopaque,
361         memory: []u8,
362         alignment: Alignment,
363         new_len: usize,
364         return_address: usize,
365     ) ?[*]u8 {
366         const self: *SegmentAllocator = @ptrCast(@alignCast(context));
367         return self.fixed.allocator().rawRemap(memory, alignment, new_len, return_address);
368     }
369 
370     fn free(
371         context: *anyopaque,
372         memory: []u8,
373         alignment: Alignment,
374         return_address: usize,
375     ) void {
376         const self: *SegmentAllocator = @ptrCast(@alignCast(context));
377         self.fixed.allocator().rawFree(memory, alignment, return_address);
378     }
379 
380     fn recordExhaustion(self: *SegmentAllocator, requested_bytes: usize) void {
381         self.exhausted_segment.* = self.segment_name;
382         self.exhausted_request.* = requested_bytes;
383     }
384 };
385 
386 pub const Capacity = struct {
387     storage_bytes: usize,
388     storage_alignment: Alignment,
389     memory: Slice,
390     configuration_tables: Slice,
391     configuration_names: Slice,
392     configuration_interfaces: Slice,
393     configuration_transactions: Slice,
394     type_tables: Slice,
395     type_keys: Slice,
396     type_payloads: Slice,
397     attribute_tables: Slice,
398     attribute_payloads: Slice,
399     operation_storage: Slice,
400     operation_nested: Slice,
401     diagnostic_handlers: Slice,
402     diagnostic_payloads: Slice,
403     transient: Slice,
404 
405     pub const Slice = struct {
406         offset: usize,
407         bytes: usize,
408     };
409 
410     pub fn asLimits(self: Capacity) Limits {
411         return .{
412             .maximum_alignment = self.storage_alignment,
413             .configuration = .{
414                 .table_bytes = self.configuration_tables.bytes,
415                 .name_bytes = self.configuration_names.bytes,
416                 .interface_bytes = self.configuration_interfaces.bytes,
417                 .transaction_bytes = self.configuration_transactions.bytes,
418             },
419             .types = .{
420                 .table_bytes = self.type_tables.bytes,
421                 .key_bytes = self.type_keys.bytes,
422                 .payload_bytes = self.type_payloads.bytes,
423             },
424             .attributes = .{
425                 .table_bytes = self.attribute_tables.bytes,
426                 .payload_bytes = self.attribute_payloads.bytes,
427             },
428             .operations = .{
429                 .storage_bytes = self.operation_storage.bytes,
430                 .nested_bytes = self.operation_nested.bytes,
431             },
432             .diagnostics = .{
433                 .handler_bytes = self.diagnostic_handlers.bytes,
434                 .payload_bytes = self.diagnostic_payloads.bytes,
435             },
436             .transient_bytes = self.transient.bytes,
437         };
438     }
439 
440     pub fn derive(limits: Limits) error{CapacityOverflow}!Capacity {
441         const requested_alignment = limits.maximum_alignment.toByteUnits();
442         if (requested_alignment < @alignOf(Memory)) return error.CapacityOverflow;
443         if (refusal(limits) != null) return error.CapacityOverflow;
444         var cursor: usize = 0;
445         const memory = try place(@sizeOf(Memory), @alignOf(Memory), &cursor);
446         const configuration_tables = try place(limits.configuration.table_bytes, requested_alignment, &cursor);
447         const configuration_names = try place(limits.configuration.name_bytes, requested_alignment, &cursor);
448         const configuration_interfaces = try place(limits.configuration.interface_bytes, requested_alignment, &cursor);
449         const configuration_transactions = try place(limits.configuration.transaction_bytes, requested_alignment, &cursor);
450         const type_tables = try place(limits.types.table_bytes, requested_alignment, &cursor);
451         const type_keys = try place(limits.types.key_bytes, requested_alignment, &cursor);
452         const type_payloads = try place(limits.types.payload_bytes, requested_alignment, &cursor);
453         const attribute_tables = try place(limits.attributes.table_bytes, requested_alignment, &cursor);
454         const attribute_payloads = try place(limits.attributes.payload_bytes, requested_alignment, &cursor);
455         const operation_storage = try place(limits.operations.storage_bytes, requested_alignment, &cursor);
456         const operation_nested = try place(limits.operations.nested_bytes, requested_alignment, &cursor);
457         const diagnostic_handlers = try place(limits.diagnostics.handler_bytes, requested_alignment, &cursor);
458         const diagnostic_payloads = try place(limits.diagnostics.payload_bytes, requested_alignment, &cursor);
459         const transient = try place(limits.transient_bytes, requested_alignment, &cursor);
460         return .{
461             .storage_bytes = cursor,
462             .storage_alignment = limits.maximum_alignment,
463             .memory = memory,
464             .configuration_tables = configuration_tables,
465             .configuration_names = configuration_names,
466             .configuration_interfaces = configuration_interfaces,
467             .configuration_transactions = configuration_transactions,
468             .type_tables = type_tables,
469             .type_keys = type_keys,
470             .type_payloads = type_payloads,
471             .attribute_tables = attribute_tables,
472             .attribute_payloads = attribute_payloads,
473             .operation_storage = operation_storage,
474             .operation_nested = operation_nested,
475             .diagnostic_handlers = diagnostic_handlers,
476             .diagnostic_payloads = diagnostic_payloads,
477             .transient = transient,
478         };
479     }
480 };
481 
482 pub const Memory = struct {
483     exhausted_segment: ?Segment,
484     /// The size of the request that found `exhausted_segment` full. It is
485     /// read only beside that segment, through `exhaustion`.
486     exhausted_request: usize,
487     configuration_tables: SegmentAllocator,
488     configuration_names: SegmentAllocator,
489     configuration_interfaces: SegmentAllocator,
490     configuration_transactions: SegmentAllocator,
491     type_tables: SegmentAllocator,
492     type_keys: SegmentAllocator,
493     type_payloads: SegmentAllocator,
494     attribute_tables: SegmentAllocator,
495     attribute_payloads: SegmentAllocator,
496     operation_storage: SegmentAllocator,
497     operation_nested: SegmentAllocator,
498     diagnostic_handlers: SegmentAllocator,
499     diagnostic_payloads: SegmentAllocator,
500     transient: SegmentAllocator,
501 
502     pub fn init(storage: [*]u8, capacity: Capacity) *Memory {
503         const self: *Memory = @ptrCast(@alignCast(storage + capacity.memory.offset));
504         self.exhausted_segment = null;
505         self.exhausted_request = 0;
506         self.configuration_tables = SegmentAllocator.init(segment(storage, capacity.configuration_tables), self, .configuration_tables);
507         self.configuration_names = SegmentAllocator.init(segment(storage, capacity.configuration_names), self, .configuration_names);
508         self.configuration_interfaces = SegmentAllocator.init(segment(storage, capacity.configuration_interfaces), self, .configuration_interfaces);
509         self.configuration_transactions = SegmentAllocator.init(segment(storage, capacity.configuration_transactions), self, .configuration_transactions);
510         self.type_tables = SegmentAllocator.init(segment(storage, capacity.type_tables), self, .type_tables);
511         self.type_keys = SegmentAllocator.init(segment(storage, capacity.type_keys), self, .type_keys);
512         self.type_payloads = SegmentAllocator.init(segment(storage, capacity.type_payloads), self, .type_payloads);
513         self.attribute_tables = SegmentAllocator.init(segment(storage, capacity.attribute_tables), self, .attribute_tables);
514         self.attribute_payloads = SegmentAllocator.init(segment(storage, capacity.attribute_payloads), self, .attribute_payloads);
515         self.operation_storage = SegmentAllocator.init(segment(storage, capacity.operation_storage), self, .operation_storage);
516         self.operation_nested = SegmentAllocator.init(segment(storage, capacity.operation_nested), self, .operation_nested);
517         self.diagnostic_handlers = SegmentAllocator.init(segment(storage, capacity.diagnostic_handlers), self, .diagnostic_handlers);
518         self.diagnostic_payloads = SegmentAllocator.init(segment(storage, capacity.diagnostic_payloads), self, .diagnostic_payloads);
519         self.transient = SegmentAllocator.init(segment(storage, capacity.transient), self, .transient);
520         return self;
521     }
522 
523     pub fn usage(self: *const Memory) Usage {
524         return .{
525             .configuration_tables = segmentUsage(&self.configuration_tables),
526             .configuration_names = segmentUsage(&self.configuration_names),
527             .configuration_interfaces = segmentUsage(&self.configuration_interfaces),
528             .configuration_transactions = segmentUsage(&self.configuration_transactions),
529             .type_tables = segmentUsage(&self.type_tables),
530             .type_keys = segmentUsage(&self.type_keys),
531             .type_payloads = segmentUsage(&self.type_payloads),
532             .attribute_tables = segmentUsage(&self.attribute_tables),
533             .attribute_payloads = segmentUsage(&self.attribute_payloads),
534             .operation_storage = segmentUsage(&self.operation_storage),
535             .operation_nested = segmentUsage(&self.operation_nested),
536             .diagnostic_handlers = segmentUsage(&self.diagnostic_handlers),
537             .diagnostic_payloads = segmentUsage(&self.diagnostic_payloads),
538             .transient = segmentUsage(&self.transient),
539         };
540     }
541 
542     pub fn exhaustion(self: *const Memory) ?Exhausted {
543         const segment_name = self.exhausted_segment orelse return null;
544         return .{ .segment = segment_name, .requested_bytes = self.exhausted_request };
545     }
546 
547     pub fn hasCapacity(
548         self: *const Memory,
549         selected: Segment,
550         bytes: usize,
551         alignment: Alignment,
552     ) bool {
553         const allocator = switch (selected) {
554             inline else => |segment_name| &@field(self, @tagName(segment_name)),
555         };
556         const offset = std.mem.alignPointerOffset(
557             allocator.buffer.ptr + alloc_fixed.used(&allocator.fixed),
558             alignment.toByteUnits(),
559         ) orelse return false;
560         const aligned = std.math.add(usize, alloc_fixed.used(&allocator.fixed), offset) catch return false;
561         const end = std.math.add(usize, aligned, bytes) catch return false;
562         return end <= allocator.buffer.len;
563     }
564 };
565 
566 pub fn cast(memory_pointer: *anyopaque) *Memory {
567     return @ptrCast(@alignCast(memory_pointer));
568 }
569 
570 pub fn segmentAllocator(context: anytype, selected: Segment) Allocator {
571     if (comptime @hasField(@TypeOf(context.*), "memory")) {
572         const memory = cast(context.memory);
573         return switch (selected) {
574             inline else => |segment_name| @field(memory, @tagName(segment_name)).allocator(),
575         };
576     }
577     return context.allocator;
578 }
579 
580 pub fn configurationTableAllocator(memory_pointer: *anyopaque) Allocator {
581     return cast(memory_pointer).configuration_tables.allocator();
582 }
583 
584 pub fn configurationNameAllocator(memory_pointer: *anyopaque) Allocator {
585     return cast(memory_pointer).configuration_names.allocator();
586 }
587 
588 pub fn configurationInterfaceAllocator(memory_pointer: *anyopaque) Allocator {
589     return cast(memory_pointer).configuration_interfaces.allocator();
590 }
591 
592 pub fn configurationTransactionAllocator(memory_pointer: *anyopaque) Allocator {
593     return cast(memory_pointer).configuration_transactions.allocator();
594 }
595 
596 pub fn typeTableAllocator(memory_pointer: *anyopaque) Allocator {
597     return cast(memory_pointer).type_tables.allocator();
598 }
599 
600 pub fn typeKeyAllocator(memory_pointer: *anyopaque) Allocator {
601     return cast(memory_pointer).type_keys.allocator();
602 }
603 
604 pub fn typePayloadAllocator(memory_pointer: *anyopaque) Allocator {
605     return cast(memory_pointer).type_payloads.allocator();
606 }
607 
608 pub fn attributeTableAllocator(memory_pointer: *anyopaque) Allocator {
609     return cast(memory_pointer).attribute_tables.allocator();
610 }
611 
612 pub fn attributePayloadAllocator(memory_pointer: *anyopaque) Allocator {
613     return cast(memory_pointer).attribute_payloads.allocator();
614 }
615 
616 pub fn operationStorageAllocator(memory_pointer: *anyopaque) Allocator {
617     return cast(memory_pointer).operation_storage.allocator();
618 }
619 
620 pub fn operationNestedAllocator(memory_pointer: *anyopaque) Allocator {
621     return cast(memory_pointer).operation_nested.allocator();
622 }
623 
624 pub fn diagnosticHandlerAllocator(memory_pointer: *anyopaque) Allocator {
625     return cast(memory_pointer).diagnostic_handlers.allocator();
626 }
627 
628 pub fn diagnosticPayloadAllocator(memory_pointer: *anyopaque) Allocator {
629     return cast(memory_pointer).diagnostic_payloads.allocator();
630 }
631 
632 pub fn transientAllocator(memory_pointer: *anyopaque) Allocator {
633     return cast(memory_pointer).transient.allocator();
634 }
635 
636 fn place(bytes: usize, alignment: usize, cursor: *usize) error{CapacityOverflow}!Capacity.Slice {
637     const mask = std.math.sub(usize, alignment, 1) catch return error.CapacityOverflow;
638     const padded = std.math.add(usize, cursor.*, mask) catch return error.CapacityOverflow;
639     const offset = padded & ~mask;
640     cursor.* = std.math.add(usize, offset, bytes) catch return error.CapacityOverflow;
641     return .{ .offset = offset, .bytes = bytes };
642 }
643 
644 fn segment(storage: [*]u8, capacity: Capacity.Slice) []u8 {
645     return (storage + capacity.offset)[0..capacity.bytes];
646 }
647 
648 fn segmentUsage(allocator: *const SegmentAllocator) SegmentUsage {
649     return .{
650         .frontier_bytes = alloc_fixed.used(&allocator.fixed),
651         .reserved_bytes = allocator.buffer.len,
652     };
653 }
654 
655 test "Context capacity follows an independent aligned segment model" {
656     comptime {
657         @stardustClaim(
658             @import("alloc_phase").capacity.witness(@import("./root.zig").Context, "choir_context_capacity"),
659             null,
660             null,
661             null,
662             null,
663             null,
664             null,
665         );
666     }
667 
668     const limits = Limits{
669         .maximum_alignment = .@"64",
670         .configuration = .{ .table_bytes = 1, .name_bytes = 2, .interface_bytes = 3, .transaction_bytes = 4 },
671         .types = .{ .table_bytes = 5, .key_bytes = 6, .payload_bytes = 7 },
672         .attributes = .{ .table_bytes = 8, .payload_bytes = 9 },
673         .operations = .{ .storage_bytes = 10, .nested_bytes = 11 },
674         .diagnostics = .{ .handler_bytes = 12, .payload_bytes = 13 },
675         .transient_bytes = 14,
676     };
677     const capacity = try Capacity.derive(limits);
678     var cursor: usize = 0;
679     cursor = std.mem.alignForward(usize, cursor, @alignOf(Memory)) + @sizeOf(Memory);
680     inline for (.{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }) |bytes| {
681         cursor = std.mem.alignForward(usize, cursor, 64) + bytes;
682     }
683     try std.testing.expectEqual(cursor, capacity.storage_bytes);
684     try std.testing.expectEqualDeep(limits, capacity.asLimits());
685     try std.testing.expectEqualDeep(capacity, try Capacity.derive(capacity.asLimits()));
686 }
687 
688 test "Context capacity rejects overflowing segments" {
689     comptime {
690         @stardustClaim(
691             @import("alloc_phase").capacity.witness(@import("./root.zig").Context, "choir_context_capacity_overflow"),
692             null,
693             null,
694             null,
695             null,
696             null,
697             null,
698         );
699     }
700 
701     var limits = Limits.testing;
702     limits.configuration.table_bytes = std.math.maxInt(usize);
703     try std.testing.expectError(error.CapacityOverflow, Capacity.derive(limits));
704 }
705 
706 test "Limits reads and writes every segment through its own field" {
707     var limits = Limits.testing;
708     for (std.enums.values(Segment), 0..) |named, index| {
709         limits.set(named, (index + 1) * 4096);
710     }
711     for (std.enums.values(Segment), 0..) |named, index| {
712         try std.testing.expectEqual((index + 1) * 4096, limits.at(named));
713     }
714     try std.testing.expectEqual(limits.operations.storage_bytes, limits.at(.operation_storage));
715     try std.testing.expectEqual(limits.operations.nested_bytes, limits.at(.operation_nested));
716     try std.testing.expectEqual(limits.transient_bytes, limits.at(.transient));
717 }
718 
719 test "Limits names one segment for each figure it reserves" {
720     const counted = comptime blk: {
721         var total: usize = 0;
722         for (@typeInfo(Limits).@"struct".field_types) |outer| {
723             if (outer == usize) {
724                 total += 1;
725                 continue;
726             }
727             if (@typeInfo(outer) != .@"struct") continue;
728             for (@typeInfo(outer).@"struct".field_types) |inner| {
729                 if (inner == usize) total += 1;
730             }
731         }
732         break :blk total;
733     };
734     try std.testing.expectEqual(counted, std.enums.values(Segment).len);
735 }
736 
737 test "Limits leaves every other segment where it was" {
738     var limits = Limits.testing;
739     const before = limits;
740     limits.set(.type_keys, 1234);
741     for (std.enums.values(Segment)) |named| {
742         if (named == .type_keys) continue;
743         try std.testing.expectEqual(before.at(named), limits.at(named));
744     }
745     try std.testing.expectEqual(@as(usize, 1234), limits.at(.type_keys));
746 }
747 
748 /// Limits whose every segment states `bytes`, under the testing alignment, so
749 /// a test can move one figure and leave the rest flat.
750 fn flatLimits(bytes: usize) Limits {
751     var limits = Limits.testing;
752     for (std.enums.values(Segment)) |selected| limits.set(selected, bytes);
753     return limits;
754 }
755 
756 test "derive refuses exactly the limits refusal names" {
757     const testing = std.testing;
758     const cases = [_]Limits{
759         Limits.testing,
760         Limits.standard,
761         flatLimits(0),
762         flatLimits(4096),
763         flatLimits(maximum_storage_bytes),
764         flatLimits(maximum_storage_bytes / 14),
765         flatLimits(std.math.maxInt(usize)),
766     };
767     for (cases) |limits| {
768         const named = refusal(limits);
769         if (Capacity.derive(limits)) |_| {
770             try testing.expect(named == null);
771         } else |err| {
772             try testing.expectEqual(error.CapacityOverflow, err);
773             try testing.expect(named != null);
774         }
775     }
776 }
777 
778 test "a total that crosses only at the last segment placed is refused at that segment" {
779     const testing = std.testing;
780     var limits = Limits.testing;
781     for (std.enums.values(Segment)) |selected| limits.set(selected, 0);
782 
783     const last = std.enums.values(Segment)[std.enums.values(Segment).len - 1];
784     try testing.expectEqual(Segment.transient, last);
785 
786     limits.set(last, maximum_storage_bytes);
787     const named = refusal(limits) orelse return error.TestExpectedRefusal;
788     try testing.expectEqual(last, named.segment);
789     try testing.expectEqual(maximum_storage_bytes, named.stated);
790     try testing.expect(named.largest < maximum_storage_bytes);
791     try testing.expectError(error.CapacityOverflow, Capacity.derive(limits));
792 
793     limits.set(last, named.largest);
794     try testing.expect(refusal(limits) == null);
795     const capacity = try Capacity.derive(limits);
796     try testing.expect(capacity.storage_bytes <= maximum_storage_bytes);
797 }
798 
799 test "refusal names the first segment in derive's own placement order" {
800     const testing = std.testing;
801     var limits = flatLimits(0);
802     limits.set(.type_keys, maximum_storage_bytes);
803     limits.set(.transient, maximum_storage_bytes);
804     const named = refusal(limits) orelse return error.TestExpectedRefusal;
805     try testing.expectEqual(Segment.type_keys, named.segment);
806 
807     var later = flatLimits(0);
808     later.set(.transient, maximum_storage_bytes);
809     const only_last = refusal(later) orelse return error.TestExpectedRefusal;
810     try testing.expectEqual(Segment.transient, only_last.segment);
811 }
812 
813 test "the largest figure a segment may take falls as the segments before it rise" {
814     const testing = std.testing;
815     var lean = flatLimits(0);
816     lean.set(.transient, maximum_storage_bytes);
817     const roomy = (refusal(lean) orelse return error.TestExpectedRefusal).largest;
818 
819     var heavy = flatLimits(1024 * 1024);
820     heavy.set(.transient, maximum_storage_bytes);
821     const cramped = (refusal(heavy) orelse return error.TestExpectedRefusal).largest;
822 
823     try testing.expect(cramped < roomy);
824 }