lib/choir/src/product/storage.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const alloc_phase = @import("alloc_phase");
  3 
  4 const Allocator = std.mem.Allocator;
  5 
  6 pub const segment_count: usize = 4;
  7 
  8 pub const Segment = struct {
  9     count: usize,
 10     element_bytes: usize,
 11     alignment: std.mem.Alignment,
 12 };
 13 
 14 pub fn segment(comptime T: type, count: usize) Segment {
 15     return .{
 16         .count = count,
 17         .element_bytes = @sizeOf(T),
 18         .alignment = .fromByteUnits(@alignOf(T)),
 19     };
 20 }
 21 
 22 pub const Storage = struct {
 23     pub const Limits = struct {
 24         segments: [segment_count]Segment,
 25     };
 26 
 27     pub const Capacity = struct {
 28         offsets: [segment_count]usize,
 29         byte_lengths: [segment_count]usize,
 30         alignments: [segment_count]std.mem.Alignment,
 31         total_bytes: usize,
 32         allocation_alignment: std.mem.Alignment,
 33 
 34         pub fn derive(limits: Limits) error{CapacityOverflow}!Capacity {
 35             var offsets: [segment_count]usize = undefined;
 36             var byte_lengths: [segment_count]usize = undefined;
 37             var alignments: [segment_count]std.mem.Alignment = undefined;
 38             var cursor: usize = 0;
 39             var allocation_alignment: usize = 1;
 40             for (limits.segments, &offsets, &byte_lengths, &alignments) |segment_value, *offset, *byte_length, *alignment| {
 41                 byte_length.* = std.math.mul(
 42                     usize,
 43                     segment_value.count,
 44                     segment_value.element_bytes,
 45                 ) catch return error.CapacityOverflow;
 46                 offset.* = try placeBytes(
 47                     byte_length.*,
 48                     segment_value.alignment.toByteUnits(),
 49                     &cursor,
 50                     &allocation_alignment,
 51                 );
 52                 alignment.* = segment_value.alignment;
 53             }
 54             return .{
 55                 .offsets = offsets,
 56                 .byte_lengths = byte_lengths,
 57                 .alignments = alignments,
 58                 .total_bytes = cursor,
 59                 .allocation_alignment = .fromByteUnits(allocation_alignment),
 60             };
 61         }
 62     };
 63 
 64     pub const InitError = Allocator.Error || error{CapacityOverflow};
 65 
 66     pub const claim: alloc_phase.capacity.Declaration = .{
 67         .source = .{
 68             .id = "choir.product_metadata_storage",
 69             .kind = .phase_static,
 70             .limit_source = .caller,
 71             .storage = .{
 72                 .covered = &.{
 73                     .{
 74                         .id = "durable_product_graph_records_and_names",
 75                         .lifetime = .steady,
 76                         .detail = "durable product graph records and names",
 77                     },
 78                     .{
 79                         .id = "durable_refresh_plan_records_and_names",
 80                         .lifetime = .steady,
 81                         .detail = "durable refresh plan records and names",
 82                     },
 83                     .{
 84                         .id = "durable_refresh_report_projections",
 85                         .lifetime = .steady,
 86                         .detail = "durable refresh report projections",
 87                     },
 88                 },
 89                 .excluded = &.{
 90                     "borrowing product graph builders and closure workspaces",
 91                     "caller-owned source metadata and allocator implementation state",
 92                     "retained exact records in separately bounded revision stores",
 93                 },
 94             },
 95             .capacity = .{
 96                 .inputs = &.{},
 97                 .type_selectors = &.{},
 98                 .nodes = &.{
 99                     .{ .constant = 0 },
100                 },
101                 .assertions = &.{.{
102                     .scope = .closure_total,
103                     .measure = .retained,
104                     .relation = .exact,
105                     .expression = 0,
106                 }},
107             },
108             .overload = .{
109                 .kind = .reject_before_seal,
110                 .detail = "count, name-byte, layout, or allocation failure returns before a durable product value is published",
111             },
112             .risks = .{
113                 .transitive = .{
114                     .status = .witnessed,
115                     .detail = "graph queries read retained immutable revision records; teardown releases those handles after the local region",
116                 },
117                 .foreign = .{
118                     .status = .excluded,
119                     .detail = "product metadata construction and queries cross no operating-system or foreign callback boundary",
120                 },
121             },
122             .obligations = &.{
123                 .{ .key = "choir_product_metadata_capacity", .role = .capacity_model },
124                 .{ .key = "choir_product_metadata_acquisition", .role = .custom },
125                 .{ .key = "choir_product_metadata_boundary", .role = .overload },
126                 .{ .key = "choir_product_metadata_oom", .role = .overload },
127                 .{ .key = "choir_product_graph_lifetime_transitive_risk", .role = .transitive_risk },
128                 .{ .key = "choir_product_graph_lifetime_foreign_risk", .role = .foreign_risk },
129                 .{ .key = "choir_product_refresh_lifetime", .role = .transitive_risk },
130                 .{ .key = "choir_product_report_composition_lifetime", .role = .transitive_risk },
131                 .{ .key = "choir_product_metadata_integration", .role = .custom },
132             },
133         },
134         .bindings = .{
135             .owner = @This(),
136             .seal = .{
137                 .family = alloc_phase.capacity.selector(@This().activate),
138                 .premise = .{
139                     .class = .checked_semantic_fact,
140                     .authority = .checker,
141                 },
142             },
143             .teardown = .{
144                 .family = alloc_phase.capacity.selector(@This().deinit),
145                 .premise = .{
146                     .class = .checked_semantic_fact,
147                     .authority = .checker,
148                 },
149             },
150         },
151     };
152 
153     phase: alloc_phase.capacity.Phase,
154     capacity: Capacity,
155     bytes: [*]u8,
156 
157     pub fn init(allocator: Allocator, limits: Limits) InitError!Storage {
158         const capacity = try Capacity.derive(limits);
159         std.debug.assert(capacity.total_bytes > 0);
160         const bytes = allocator.rawAlloc(
161             capacity.total_bytes,
162             capacity.allocation_alignment,
163             @returnAddress(),
164         ) orelse return error.OutOfMemory;
165         return .{
166             .phase = .initialization,
167             .capacity = capacity,
168             .bytes = bytes,
169         };
170     }
171 
172     pub fn region(self: *Storage, comptime T: type, comptime index: usize, count: usize) []T {
173         comptime std.debug.assert(index < segment_count);
174         std.debug.assert(self.phase == .initialization);
175         const byte_length = std.math.mul(usize, count, @sizeOf(T)) catch unreachable;
176         std.debug.assert(byte_length == self.capacity.byte_lengths[index]);
177         std.debug.assert(@alignOf(T) == self.capacity.alignments[index].toByteUnits());
178         const offset = self.capacity.offsets[index];
179         const address = std.math.add(usize, @intFromPtr(self.bytes), offset) catch unreachable;
180         const raw: [*]u8 = @ptrFromInt(address);
181         const items: [*]T = @ptrCast(@alignCast(raw));
182         return items[0..count];
183     }
184 
185     pub fn activate(self: *Storage) void {
186         std.debug.assert(self.phase == .initialization);
187         self.phase = .steady;
188     }
189 
190     pub fn status(self: *const Storage) alloc_phase.capacity.Phase {
191         return self.phase;
192     }
193 
194     pub fn deinit(self: *Storage, allocator: Allocator) void {
195         std.debug.assert(self.phase != .teardown);
196         self.phase = .teardown;
197         allocator.rawFree(
198             self.bytes[0..self.capacity.total_bytes],
199             self.capacity.allocation_alignment,
200             @returnAddress(),
201         );
202         self.* = undefined;
203     }
204 };
205 
206 comptime {
207     alloc_phase.capacity.requireAllocatorExactOwnerShape(Storage);
208 }
209 
210 fn placeBytes(
211     bytes: usize,
212     alignment: usize,
213     cursor: *usize,
214     allocation_alignment: *usize,
215 ) error{CapacityOverflow}!usize {
216     std.debug.assert(std.math.isPowerOfTwo(alignment));
217     const mask = std.math.sub(usize, alignment, 1) catch unreachable;
218     const padded = std.math.add(usize, cursor.*, mask) catch return error.CapacityOverflow;
219     const offset = padded & ~mask;
220     cursor.* = std.math.add(usize, offset, bytes) catch return error.CapacityOverflow;
221     allocation_alignment.* = @max(allocation_alignment.*, alignment);
222     return offset;
223 }
224 
225 test "product metadata capacity matches an independent aligned byte model" {
226     comptime {
227         @stardustClaim(
228             @import("alloc_phase").capacity.witness(Storage, "choir_product_metadata_capacity"),
229             null,
230             null,
231             null,
232             null,
233             null,
234             null,
235         );
236     }
237 
238     const limits = Storage.Limits{ .segments = .{
239         segment(u64, 3),
240         segment(u8, 7),
241         segment(u128, 2),
242         segment(u16, 5),
243     } };
244     const capacity = try Storage.Capacity.derive(limits);
245 
246     var expected: usize = 0;
247     inline for (.{ u64, u8, u128, u16 }, .{ 3, 7, 2, 5 }) |T, count| {
248         expected = std.mem.alignForward(usize, expected, @alignOf(T));
249         expected = try std.math.add(usize, expected, try std.math.mul(usize, count, @sizeOf(T)));
250     }
251 
252     try std.testing.expectEqual(expected, capacity.total_bytes);
253     try std.testing.expectEqual(@as(usize, @alignOf(u128)), capacity.allocation_alignment.toByteUnits());
254 }
255 
256 test "product metadata storage acquires one exact aligned region" {
257     comptime {
258         @stardustClaim(
259             @import("alloc_phase").capacity.witness(Storage, "choir_product_metadata_acquisition"),
260             null,
261             null,
262             null,
263             null,
264             null,
265             null,
266         );
267     }
268 
269     const limits = Storage.Limits{ .segments = .{
270         segment(u64, 3),
271         segment(u8, 7),
272         segment(u128, 2),
273         segment(u16, 5),
274     } };
275     const capacity = try Storage.Capacity.derive(limits);
276     var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{});
277     var storage = try Storage.init(failing.allocator(), limits);
278     defer storage.deinit(failing.allocator());
279 
280     try std.testing.expectEqual(@as(usize, 1), failing.alloc_index);
281     try std.testing.expectEqual(capacity.total_bytes, failing.allocated_bytes);
282     const allocation_alignment = capacity.allocation_alignment.toByteUnits();
283     std.debug.assert(allocation_alignment > 0);
284     try std.testing.expect(@intFromPtr(storage.bytes) % allocation_alignment == 0);
285     try std.testing.expectEqual(@as(usize, 3), storage.region(u64, 0, 3).len);
286     try std.testing.expectEqual(@as(usize, 7), storage.region(u8, 1, 7).len);
287     try std.testing.expect(!alloc_phase.capacity.typeHasAllocatorCapability(Storage));
288     try std.testing.expectEqual(alloc_phase.capacity.Phase.initialization, storage.status());
289     storage.activate();
290     try std.testing.expectEqual(alloc_phase.capacity.Phase.steady, storage.status());
291 }
292 
293 test "product metadata storage rejects max plus one arithmetic" {
294     comptime {
295         @stardustClaim(
296             @import("alloc_phase").capacity.witness(Storage, "choir_product_metadata_boundary"),
297             null,
298             null,
299             null,
300             null,
301             null,
302             null,
303         );
304     }
305 
306     try std.testing.expectError(error.CapacityOverflow, Storage.Capacity.derive(.{ .segments = .{
307         segment(usize, std.math.maxInt(usize)),
308         segment(u8, 0),
309         segment(u8, 0),
310         segment(u8, 0),
311     } }));
312     try std.testing.expectError(error.CapacityOverflow, Storage.Capacity.derive(.{ .segments = .{
313         segment(u8, std.math.maxInt(usize)),
314         segment(u128, 1),
315         segment(u8, 0),
316         segment(u8, 0),
317     } }));
318 }
319 
320 test "product metadata storage retries after allocation failure" {
321     comptime {
322         @stardustClaim(
323             @import("alloc_phase").capacity.witness(Storage, "choir_product_metadata_oom"),
324             null,
325             null,
326             null,
327             null,
328             null,
329             null,
330         );
331     }
332 
333     const limits = Storage.Limits{ .segments = .{
334         segment(usize, 1),
335         segment(u8, 1),
336         segment(u8, 0),
337         segment(u8, 0),
338     } };
339     var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 });
340     try std.testing.expectError(error.OutOfMemory, Storage.init(failing.allocator(), limits));
341 
342     failing.fail_index = std.math.maxInt(usize);
343     var storage = try Storage.init(failing.allocator(), limits);
344     storage.deinit(failing.allocator());
345 }