lib/zen/src/document/storage.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const alloc_phase = @import("alloc_phase");
  3 const capacity_mod = @import("capacity.zig");
  4 const model = @import("model.zig");
  5 const plan_mod = @import("plan.zig");
  6 
  7 /// One acquired view into heading storage.
  8 pub const Regions = struct {
  9     plan: plan_mod.Plan,
 10     headings: []model.Heading,
 11     text: []u8,
 12     ids: []u8,
 13 };
 14 
 15 /// Observable capacity, current demand, high-water demand, and rejections.
 16 pub const Status = struct {
 17     /// Initialization, steady, or teardown lifecycle phase.
 18     phase: alloc_phase.capacity.Phase,
 19     /// True while a returned document still borrows this storage.
 20     in_use: bool,
 21     /// Total bytes acquired during initialization.
 22     storage_bytes: usize,
 23     max_headings: usize,
 24     max_text_bytes: usize,
 25     max_id_bytes: usize,
 26     heading_count: usize,
 27     text_bytes: usize,
 28     id_bytes: usize,
 29     /// Largest heading count admitted since activation.
 30     high_water_headings: usize,
 31     /// Largest visible-text demand admitted since activation.
 32     high_water_text_bytes: usize,
 33     /// Largest ID-byte demand admitted since activation.
 34     high_water_id_bytes: usize,
 35     /// Saturating count of capacity and in-use rejections.
 36     rejected_source_count: u64,
 37 };
 38 
 39 /// One reusable aligned region for an inspected Markdown document.
 40 ///
 41 /// Call `init`, then `activate`. A document borrows the region until `reset`.
 42 /// Call `deinit` with the allocator passed to `init` after the last reset.
 43 pub const Storage = struct {
 44     phase: alloc_phase.capacity.Phase,
 45     capacity: capacity_mod.Capacity,
 46     bytes: []align(capacity_mod.storage_alignment) u8,
 47     headings: []model.Heading,
 48     text: []u8,
 49     ids: []u8,
 50     in_use: bool = false,
 51     heading_count: usize = 0,
 52     text_bytes: usize = 0,
 53     id_bytes: usize = 0,
 54     high_water_headings: usize = 0,
 55     high_water_text_bytes: usize = 0,
 56     high_water_id_bytes: usize = 0,
 57     rejected_source_count: u64 = 0,
 58 
 59     pub const Limits: type = capacity_mod.Limits;
 60     pub const Capacity: type = capacity_mod.Capacity;
 61     pub const Exhaustion: type = model.Exhaustion;
 62     pub const InitError = std.mem.Allocator.Error || capacity_mod.DeriveError;
 63     pub const AcquireError: type = model.Exhaustion;
 64 
 65     pub const claim: alloc_phase.capacity.Declaration = .{
 66         .source = .{
 67             .id = "zen.heading_storage",
 68             .kind = .phase_static,
 69             .limit_source = .caller,
 70             .storage = .{
 71                 .covered = &.{
 72                     .{
 73                         .id = "markdown_heading_descriptors",
 74                         .lifetime = .steady,
 75                         .detail = "Markdown heading descriptors",
 76                     },
 77                     .{
 78                         .id = "source_independent_heading_text",
 79                         .lifetime = .steady,
 80                         .detail = "source-independent heading text",
 81                     },
 82                     .{
 83                         .id = "stable_heading_identifiers",
 84                         .lifetime = .steady,
 85                         .detail = "stable heading identifiers",
 86                     },
 87                 },
 88                 .excluded = &.{
 89                     "caller-owned Markdown source",
 90                     "rendered HTML and inline-rendering owners",
 91                     "site catalog, theme, and filesystem output owners",
 92                 },
 93             },
 94             .capacity = .{
 95                 .inputs = &.{
 96                     alloc_phase.capacity.bindInput(Limits, "max_headings", "max_headings"),
 97                     alloc_phase.capacity.bindInput(Limits, "max_text_bytes", "max_text_bytes"),
 98                     alloc_phase.capacity.bindInput(Limits, "max_id_bytes", "max_id_bytes"),
 99                 },
100                 .type_selectors = &.{
101                     alloc_phase.capacity.bindType(model.Heading, "heading"),
102                 },
103                 .nodes = &.{
104                     .{ .input = 0 },
105                     .{ .scale = .{ .node = 0, .coefficient = .{ .size_of_concrete_type = 0 } } },
106                     .{ .input = 1 },
107                     .{ .input = 2 },
108                     .{ .add = .{ .left = 1, .right = 2 } },
109                     .{ .add = .{ .left = 4, .right = 3 } },
110                 },
111                 .assertions = &.{.{
112                     .scope = .closure_total,
113                     .measure = .retained,
114                     .relation = .exact,
115                     .expression = 5,
116                 }},
117             },
118             .overload = .{
119                 .kind = .reject_before_mutation,
120                 .detail = "max plus one and in-use acquisition reject before the backing region or active result changes; only rejection telemetry advances",
121             },
122             .risks = .{
123                 .transitive = .{
124                     .status = .witnessed,
125                     .detail = "heading scanning, planning, copying, and identifier resolution use borrowed input and acquired slices only",
126                 },
127                 .foreign = .{
128                     .status = .excluded,
129                     .detail = "heading inspection crosses no operating-system or foreign callback boundary",
130                 },
131             },
132             .obligations = &.{
133                 .{ .key = "zen_heading_capacity", .role = .capacity_model },
134                 .{ .key = "zen_heading_acquisition", .role = .custom },
135                 .{ .key = "zen_heading_oom", .role = .custom },
136                 .{ .key = "zen_heading_boundaries", .role = .overload },
137                 .{ .key = "zen_heading_reuse", .role = .overload },
138                 .{ .key = "zen_heading_sealed", .role = .transitive_risk },
139                 .{ .key = "zen_heading_root", .role = .custom },
140                 .{ .key = "zen_heading_consumer", .role = .foreign_risk },
141             },
142         },
143         .bindings = .{
144             .owner = @This(),
145             .seal = .{
146                 .family = alloc_phase.capacity.selector(@This().activate),
147                 .premise = .{
148                     .class = .checked_semantic_fact,
149                     .authority = .checker,
150                 },
151             },
152             .teardown = .{
153                 .family = alloc_phase.capacity.selector(@This().deinit),
154                 .premise = .{
155                     .class = .checked_semantic_fact,
156                     .authority = .checker,
157                 },
158             },
159         },
160     };
161 
162     pub fn init(allocator: std.mem.Allocator, limits: Limits) InitError!Storage {
163         const capacity = try Capacity.derive(limits);
164         const bytes = if (capacity.storage_bytes == 0)
165             @as([]align(capacity_mod.storage_alignment) u8, &.{})
166         else
167             try allocator.alignedAlloc(
168                 u8,
169                 .fromByteUnits(capacity_mod.storage_alignment),
170                 capacity.storage_bytes,
171             );
172         return .{
173             .phase = .initialization,
174             .capacity = capacity,
175             .bytes = bytes,
176             .headings = typedSlice(
177                 model.Heading,
178                 bytes,
179                 capacity.headings_offset,
180                 limits.max_headings,
181             ),
182             .text = bytes[capacity.text_offset..][0..limits.max_text_bytes],
183             .ids = bytes[capacity.id_offset..][0..limits.max_id_bytes],
184         };
185     }
186 
187     pub fn activate(self: *Storage) void {
188         std.debug.assert(self.phase == .initialization);
189         self.assertStorage();
190         self.phase = .steady;
191     }
192 
193     /// Acquires exact plan regions or rejects before backing bytes change.
194     pub fn acquire(self: *Storage, plan: plan_mod.Plan) AcquireError!Regions {
195         std.debug.assert(self.phase == .steady);
196         if (self.in_use) return self.reject(error.DocumentStorageInUse);
197         plan.require(self.capacity.limits) catch |err| return self.reject(err);
198         self.in_use = true;
199         self.heading_count = plan.headings;
200         self.text_bytes = plan.text_bytes;
201         self.id_bytes = plan.id_bytes;
202         self.high_water_headings = @max(self.high_water_headings, plan.headings);
203         self.high_water_text_bytes = @max(self.high_water_text_bytes, plan.text_bytes);
204         self.high_water_id_bytes = @max(self.high_water_id_bytes, plan.id_bytes);
205         self.assertStorage();
206         return .{
207             .plan = plan,
208             .headings = self.headings[0..plan.headings],
209             .text = self.text[0..plan.text_bytes],
210             .ids = self.ids[0..plan.id_bytes],
211         };
212     }
213 
214     /// Ends the active borrow so the same region can inspect another document.
215     pub fn reset(self: *Storage) void {
216         std.debug.assert(self.phase == .steady);
217         std.debug.assert(self.in_use);
218         self.in_use = false;
219         self.heading_count = 0;
220         self.text_bytes = 0;
221         self.id_bytes = 0;
222         self.assertStorage();
223     }
224 
225     /// Returns current, high-water, and rejected demand without mutation.
226     pub fn status(self: *const Storage) Status {
227         return .{
228             .phase = self.phase,
229             .in_use = self.in_use,
230             .storage_bytes = self.capacity.storage_bytes,
231             .max_headings = self.capacity.limits.max_headings,
232             .max_text_bytes = self.capacity.limits.max_text_bytes,
233             .max_id_bytes = self.capacity.limits.max_id_bytes,
234             .heading_count = self.heading_count,
235             .text_bytes = self.text_bytes,
236             .id_bytes = self.id_bytes,
237             .high_water_headings = self.high_water_headings,
238             .high_water_text_bytes = self.high_water_text_bytes,
239             .high_water_id_bytes = self.high_water_id_bytes,
240             .rejected_source_count = self.rejected_source_count,
241         };
242     }
243 
244     pub fn deinit(self: *Storage, allocator: std.mem.Allocator) void {
245         std.debug.assert(self.phase != .teardown);
246         std.debug.assert(!self.in_use);
247         self.assertStorage();
248         self.phase = .teardown;
249         allocator.free(self.bytes);
250         self.bytes = &.{};
251         self.headings = &.{};
252         self.text = &.{};
253         self.ids = &.{};
254     }
255 
256     fn reject(self: *Storage, err: model.Exhaustion) model.Exhaustion {
257         self.rejected_source_count +|= 1;
258         return err;
259     }
260 
261     fn assertStorage(self: *const Storage) void {
262         std.debug.assert(self.bytes.len == self.capacity.storage_bytes);
263         std.debug.assert(self.headings.len == self.capacity.limits.max_headings);
264         std.debug.assert(self.text.len == self.capacity.limits.max_text_bytes);
265         std.debug.assert(self.ids.len == self.capacity.limits.max_id_bytes);
266         std.debug.assert(self.heading_count <= self.headings.len);
267         std.debug.assert(self.text_bytes <= self.text.len);
268         std.debug.assert(self.id_bytes <= self.ids.len);
269         std.debug.assert(self.high_water_headings <= self.headings.len);
270         std.debug.assert(self.high_water_text_bytes <= self.text.len);
271         std.debug.assert(self.high_water_id_bytes <= self.ids.len);
272         if (!self.in_use) {
273             std.debug.assert(self.heading_count == 0);
274             std.debug.assert(self.text_bytes == 0);
275             std.debug.assert(self.id_bytes == 0);
276         }
277     }
278 };
279 
280 fn typedSlice(
281     comptime T: type,
282     bytes: []align(capacity_mod.storage_alignment) u8,
283     offset: usize,
284     count: usize,
285 ) []T {
286     const byte_count = count * @sizeOf(T);
287     const region: []align(@alignOf(T)) u8 = @alignCast(bytes[offset..][0..byte_count]);
288     return std.mem.bytesAsSlice(T, region);
289 }
290 
291 fn checkInitFailures(allocator: std.mem.Allocator) !void {
292     var storage = try Storage.init(allocator, .{
293         .max_headings = 2,
294         .max_text_bytes = 11,
295         .max_id_bytes = 11,
296     });
297     storage.deinit(allocator);
298 }
299 
300 test "heading storage acquires one exact aligned region" {
301     comptime {
302         @stardustClaim(
303             @import("alloc_phase").capacity.witness(Storage, "zen_heading_acquisition"),
304             null,
305             null,
306             null,
307             null,
308             null,
309             null,
310         );
311     }
312 
313     var counting = std.testing.FailingAllocator.init(std.testing.allocator, .{});
314     const limits = capacity_mod.Limits{
315         .max_headings = 2,
316         .max_text_bytes = 11,
317         .max_id_bytes = 11,
318     };
319     const capacity = try capacity_mod.Capacity.derive(limits);
320     var storage = try Storage.init(counting.allocator(), limits);
321     defer storage.deinit(counting.allocator());
322 
323     try std.testing.expectEqual(@as(usize, 1), counting.alloc_index);
324     try std.testing.expectEqual(capacity.storage_bytes, counting.allocated_bytes);
325     try std.testing.expectEqual(alloc_phase.capacity.Phase.initialization, storage.status().phase);
326     storage.activate();
327     const regions = try storage.acquire(.{
328         .source_bytes = 20,
329         .headings = 2,
330         .text_bytes = 11,
331         .id_bytes = 11,
332     });
333     defer storage.reset();
334     const base = @intFromPtr(storage.bytes.ptr);
335     try std.testing.expectEqual(base + capacity.headings_offset, @intFromPtr(regions.headings.ptr));
336     try std.testing.expectEqual(base + capacity.text_offset, @intFromPtr(regions.text.ptr));
337     try std.testing.expectEqual(base + capacity.id_offset, @intFromPtr(regions.ids.ptr));
338 }
339 
340 test "heading storage retries after every allocation failure" {
341     comptime {
342         @stardustClaim(
343             @import("alloc_phase").capacity.witness(Storage, "zen_heading_oom"),
344             null,
345             null,
346             null,
347             null,
348             null,
349             null,
350         );
351     }
352 
353     try std.testing.checkAllAllocationFailures(std.testing.allocator, checkInitFailures, .{});
354 }
355 
356 comptime {
357     alloc_phase.capacity.requireAllocatorRejectingOwnerShape(Storage);
358 }