lib/xkb/src/compose/workspace/capacity.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 pub const max_include_depth: usize = 5;
4 pub const max_file_bytes: usize = 16 * 1024 * 1024;
5 pub const max_registry_bytes: usize = 8 * 1024 * 1024;
6 pub const max_path_bytes: usize = 4096;
7 pub const file_line_slot_count: usize = max_include_depth + 1;
8 pub const registry_line_slot_index: usize = file_line_slot_count;
9 pub const line_slot_count: usize = file_line_slot_count + 1;
10 pub const file_path_slot_count: usize = max_include_depth + 1;
11 pub const auxiliary_path_slot_count: usize = 2;
12 pub const path_slot_count: usize = file_path_slot_count + auxiliary_path_slot_count;
13
14 pub const Limits = struct {
15 line_bytes: usize,
16 path_bytes: usize,
17 };
18
19 pub const default_limits = Limits{
20 .line_bytes = 256,
21 .path_bytes = max_path_bytes,
22 };
23
24 pub const DeriveError = error{
25 LineByteLimitZero,
26 LineByteLimitExceeded,
27 PathByteLimitZero,
28 PathByteLimitExceeded,
29 CapacityOverflow,
30 };
31
32 pub const Capacity = struct {
33 limits: Limits,
34 line_bytes_per_slot: usize,
35 line_slot_count: usize,
36 line_offset: usize,
37 line_storage_bytes: usize,
38 path_slot_count: usize,
39 path_offset: usize,
40 path_storage_bytes: usize,
41 storage_bytes: usize,
42
43 pub fn derive(limits: Limits) DeriveError!Capacity {
44 if (limits.line_bytes == 0) return error.LineByteLimitZero;
45 if (limits.line_bytes > max_file_bytes) return error.LineByteLimitExceeded;
46 if (limits.path_bytes == 0) return error.PathByteLimitZero;
47 if (limits.path_bytes > max_path_bytes) return error.PathByteLimitExceeded;
48 const line_bytes_per_slot = try added(limits.line_bytes, 1);
49 const line_storage_bytes = try multiplied(line_slot_count, line_bytes_per_slot);
50 const path_storage_bytes = try multiplied(path_slot_count, limits.path_bytes);
51 return .{
52 .limits = limits,
53 .line_bytes_per_slot = line_bytes_per_slot,
54 .line_slot_count = line_slot_count,
55 .line_offset = 0,
56 .line_storage_bytes = line_storage_bytes,
57 .path_slot_count = path_slot_count,
58 .path_offset = line_storage_bytes,
59 .path_storage_bytes = path_storage_bytes,
60 .storage_bytes = try added(line_storage_bytes, path_storage_bytes),
61 };
62 }
63 };
64
65 fn added(left: usize, right: usize) DeriveError!usize {
66 return std.math.add(usize, left, right) catch error.CapacityOverflow;
67 }
68
69 fn multiplied(left: usize, right: usize) DeriveError!usize {
70 return std.math.mul(usize, left, right) catch error.CapacityOverflow;
71 }
72
73 fn modelCapacity(limits: Limits) DeriveError!Capacity {
74 if (limits.line_bytes == 0) return error.LineByteLimitZero;
75 if (limits.line_bytes > max_file_bytes) return error.LineByteLimitExceeded;
76 if (limits.path_bytes == 0) return error.PathByteLimitZero;
77 if (limits.path_bytes > max_path_bytes) return error.PathByteLimitExceeded;
78 const line_bytes_per_slot = @as(u128, limits.line_bytes) + 1;
79 const line_storage_bytes = @as(u128, line_slot_count) * line_bytes_per_slot;
80 const path_storage_bytes = @as(u128, path_slot_count) * limits.path_bytes;
81 const storage_bytes = line_storage_bytes + path_storage_bytes;
82 const values = [_]u128{ line_bytes_per_slot, line_storage_bytes, path_storage_bytes, storage_bytes };
83 for (values) |value| {
84 if (value > std.math.maxInt(usize)) return error.CapacityOverflow;
85 }
86 return .{
87 .limits = limits,
88 .line_bytes_per_slot = @intCast(line_bytes_per_slot),
89 .line_slot_count = line_slot_count,
90 .line_offset = 0,
91 .line_storage_bytes = @intCast(line_storage_bytes),
92 .path_slot_count = path_slot_count,
93 .path_offset = @intCast(line_storage_bytes),
94 .path_storage_bytes = @intCast(path_storage_bytes),
95 .storage_bytes = @intCast(storage_bytes),
96 };
97 }
98
99 test "Compose scratch capacity matches an independent slot model" {
100 comptime {
101 @stardustClaim(
102 @import("alloc_phase").capacity.witness(@import("./root.zig").Storage, "xkb_compose_scratch_capacity"),
103 null,
104 null,
105 null,
106 null,
107 null,
108 null,
109 );
110 }
111
112 const capacity = try Capacity.derive(default_limits);
113 try std.testing.expectEqual(try modelCapacity(default_limits), capacity);
114 try std.testing.expectEqual(@as(usize, 34_567), capacity.storage_bytes);
115 try std.testing.expectEqual(@as(usize, 7), capacity.line_slot_count);
116 try std.testing.expectEqual(@as(usize, 8), capacity.path_slot_count);
117 }
118
119 test "Compose scratch capacity rejects invalid semantic limits" {
120 try std.testing.expectError(
121 error.LineByteLimitZero,
122 Capacity.derive(.{ .line_bytes = 0, .path_bytes = 1 }),
123 );
124 try std.testing.expectError(
125 error.LineByteLimitExceeded,
126 Capacity.derive(.{ .line_bytes = max_file_bytes + 1, .path_bytes = 1 }),
127 );
128 try std.testing.expectError(
129 error.PathByteLimitZero,
130 Capacity.derive(.{ .line_bytes = 1, .path_bytes = 0 }),
131 );
132 try std.testing.expectError(
133 error.PathByteLimitExceeded,
134 Capacity.derive(.{ .line_bytes = 1, .path_bytes = max_path_bytes + 1 }),
135 );
136 }