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 }