lib/zen/src/quiz/capacity.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const model = @import("model.zig");
  3 const plan_mod = @import("plan.zig");
  4 
  5 pub const storage_alignment: usize = @max(
  6     @alignOf(model.Question),
  7     @alignOf(model.Option),
  8 );
  9 
 10 pub const Limits = plan_mod.Limits;
 11 pub const DeriveError = error{CapacityOverflow};
 12 
 13 pub const Capacity = struct {
 14     limits: Limits,
 15     questions_offset: usize,
 16     question_bytes: usize,
 17     options_offset: usize,
 18     option_bytes: usize,
 19     joined_text_offset: usize,
 20     storage_bytes: usize,
 21 
 22     pub fn derive(limits: Limits) DeriveError!Capacity {
 23         const questions = try placed(model.Question, 0, limits.max_questions);
 24         const options = try placed(model.Option, questions.end, limits.max_options);
 25         return .{
 26             .limits = limits,
 27             .questions_offset = questions.start,
 28             .question_bytes = questions.bytes,
 29             .options_offset = options.start,
 30             .option_bytes = options.bytes,
 31             .joined_text_offset = options.end,
 32             .storage_bytes = try added(options.end, limits.max_joined_text_bytes),
 33         };
 34     }
 35 };
 36 
 37 const Region = struct {
 38     start: usize,
 39     bytes: usize,
 40     end: usize,
 41 };
 42 
 43 fn placed(comptime T: type, offset: usize, count: usize) DeriveError!Region {
 44     const mask: usize = @alignOf(T) - 1;
 45     const start = (try added(offset, mask)) & ~mask;
 46     const bytes = std.math.mul(usize, count, @sizeOf(T)) catch {
 47         return error.CapacityOverflow;
 48     };
 49     return .{ .start = start, .bytes = bytes, .end = try added(start, bytes) };
 50 }
 51 
 52 fn added(left: usize, right: usize) DeriveError!usize {
 53     return std.math.add(usize, left, right) catch error.CapacityOverflow;
 54 }
 55 
 56 fn independent(limits: Limits) DeriveError!Capacity {
 57     const question_bytes = @as(u128, limits.max_questions) * @sizeOf(model.Question);
 58     const options_offset = alignForward(question_bytes, @alignOf(model.Option));
 59     const option_bytes = @as(u128, limits.max_options) * @sizeOf(model.Option);
 60     const joined_text_offset = options_offset + option_bytes;
 61     const storage_bytes = joined_text_offset + limits.max_joined_text_bytes;
 62     if (question_bytes > std.math.maxInt(usize) or
 63         options_offset > std.math.maxInt(usize) or
 64         option_bytes > std.math.maxInt(usize) or
 65         joined_text_offset > std.math.maxInt(usize) or
 66         storage_bytes > std.math.maxInt(usize))
 67     {
 68         return error.CapacityOverflow;
 69     }
 70     return .{
 71         .limits = limits,
 72         .questions_offset = 0,
 73         .question_bytes = @intCast(question_bytes),
 74         .options_offset = @intCast(options_offset),
 75         .option_bytes = @intCast(option_bytes),
 76         .joined_text_offset = @intCast(joined_text_offset),
 77         .storage_bytes = @intCast(storage_bytes),
 78     };
 79 }
 80 
 81 fn alignForward(value: u128, alignment: u128) u128 {
 82     const mask = alignment - 1;
 83     return (value + mask) & ~mask;
 84 }
 85 
 86 test "quiz capacity matches an independent byte model" {
 87     comptime {
 88         @stardustClaim(
 89             @import("alloc_phase").capacity.witness(@import("./root.zig").Storage, "zen_quiz_capacity"),
 90             null,
 91             null,
 92             null,
 93             null,
 94             null,
 95             null,
 96         );
 97     }
 98 
 99     const limits = Limits{
100         .max_questions = 2,
101         .max_options = 3,
102         .max_joined_text_bytes = 65,
103     };
104     const capacity = try Capacity.derive(limits);
105     try std.testing.expectEqual(try independent(limits), capacity);
106     try std.testing.expectEqual(@as(usize, 233), capacity.storage_bytes);
107     try std.testing.expectEqual(
108         @as(usize, 83_968),
109         (try Capacity.derive(plan_mod.default_limits)).storage_bytes,
110     );
111 }
112 
113 test "quiz capacity handles zero and rejects overflow" {
114     try std.testing.expectEqual(@as(usize, 0), (try Capacity.derive(.{
115         .max_questions = 0,
116         .max_options = 0,
117         .max_joined_text_bytes = 0,
118     })).storage_bytes);
119     try std.testing.expectError(error.CapacityOverflow, Capacity.derive(.{
120         .max_questions = std.math.maxInt(usize),
121         .max_options = 0,
122         .max_joined_text_bytes = 0,
123     }));
124     try std.testing.expectError(error.CapacityOverflow, Capacity.derive(.{
125         .max_questions = 0,
126         .max_options = std.math.maxInt(usize),
127         .max_joined_text_bytes = 0,
128     }));
129 }