lib/zen/src/site/catalog/capacity.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

 1 const std = @import("std");
 2 
 3 const model = @import("model.zig");
 4 const plan = @import("plan.zig");
 5 
 6 pub const storage_alignment: usize = @alignOf(model.Entry);
 7 pub const Limits = plan.Limits;
 8 pub const DeriveError = error{CapacityOverflow};
 9 
10 pub const Capacity = struct {
11     limits: Limits,
12     entries_offset: usize,
13     entry_bytes: usize,
14     text_offset: usize,
15     storage_bytes: usize,
16 
17     pub fn derive(limits: Limits) DeriveError!Capacity {
18         const entries = try placed(model.Entry, 0, limits.max_pages);
19         const capacity = Capacity{
20             .limits = limits,
21             .entries_offset = entries.start,
22             .entry_bytes = entries.bytes,
23             .text_offset = entries.end,
24             .storage_bytes = try added(entries.end, limits.max_text_bytes),
25         };
26         std.debug.assert(capacity.entries_offset == 0);
27         std.debug.assert(capacity.entry_bytes == entries.bytes);
28         std.debug.assert(capacity.text_offset == entries.end);
29         std.debug.assert(capacity.storage_bytes >= capacity.text_offset);
30         std.debug.assert(capacity.storage_bytes - capacity.text_offset == limits.max_text_bytes);
31         return capacity;
32     }
33 };
34 
35 const Region = struct {
36     start: usize,
37     bytes: usize,
38     end: usize,
39 };
40 
41 fn placed(comptime T: type, offset: usize, count: usize) DeriveError!Region {
42     std.debug.assert(std.math.isPowerOfTwo(@alignOf(T)));
43     const mask: usize = @alignOf(T) - 1;
44     const start = (try added(offset, mask)) & ~mask;
45     const bytes = std.math.mul(usize, count, @sizeOf(T)) catch
46         return error.CapacityOverflow;
47     const region = Region{ .start = start, .bytes = bytes, .end = try added(start, bytes) };
48     std.debug.assert(region.start % @alignOf(T) == 0);
49     std.debug.assert(region.end >= region.start);
50     return region;
51 }
52 
53 fn added(left: usize, right: usize) DeriveError!usize {
54     return std.math.add(usize, left, right) catch error.CapacityOverflow;
55 }
56 
57 fn independent(limits: Limits) DeriveError!Capacity {
58     const entries = @as(u128, limits.max_pages) * @sizeOf(model.Entry);
59     const total = entries + limits.max_text_bytes;
60     if (entries > std.math.maxInt(usize) or total > std.math.maxInt(usize)) {
61         return error.CapacityOverflow;
62     }
63     return .{
64         .limits = limits,
65         .entries_offset = 0,
66         .entry_bytes = @intCast(entries),
67         .text_offset = @intCast(entries),
68         .storage_bytes = @intCast(total),
69     };
70 }
71 
72 test "site catalog capacity matches an independent byte model" {
73     comptime {
74         @stardustClaim(
75             @import("alloc_phase").capacity.witness(@import("./root.zig").Storage, "zen_site_catalog_capacity"),
76             null,
77             null,
78             null,
79             null,
80             null,
81             null,
82         );
83     }
84 
85     const limits = Limits{ .max_pages = 32, .max_text_bytes = 5041 };
86     try std.testing.expectEqual(try independent(limits), try Capacity.derive(limits));
87 }
88 
89 test "site catalog capacity handles empty input and rejects overflow" {
90     try std.testing.expectEqual(@as(usize, 0), (try Capacity.derive(.{
91         .max_pages = 0,
92         .max_text_bytes = 0,
93     })).storage_bytes);
94     try std.testing.expectError(error.CapacityOverflow, Capacity.derive(.{
95         .max_pages = std.math.maxInt(usize),
96         .max_text_bytes = 1,
97     }));
98 }