lib/zen/src/diagram/ascii/capacity.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 
  3 const diagram = @import("../root.zig");
  4 const plan = @import("plan.zig");
  5 
  6 pub const storage_alignment: usize = @max(
  7     @alignOf([]const u8),
  8     @alignOf(diagram.bounds.StackTotal),
  9 );
 10 
 11 pub const Limits = plan.Limits;
 12 pub const DeriveError = error{CapacityOverflow};
 13 
 14 pub const Capacity = struct {
 15     limits: Limits,
 16     categories_offset: usize,
 17     category_bytes: usize,
 18     stack_totals_offset: usize,
 19     stack_total_bytes: usize,
 20     canvas_offset: usize,
 21     output_offset: usize,
 22     storage_bytes: usize,
 23 
 24     pub fn derive(limits: Limits) DeriveError!Capacity {
 25         const category_bytes = try multiplied(limits.max_categories, @sizeOf([]const u8));
 26         const stack_totals_offset = try aligned(category_bytes, @alignOf(diagram.bounds.StackTotal));
 27         const stack_total_bytes = try multiplied(
 28             limits.max_stack_totals,
 29             @sizeOf(diagram.bounds.StackTotal),
 30         );
 31         const canvas_offset = try added(stack_totals_offset, stack_total_bytes);
 32         const output_offset = try added(canvas_offset, limits.max_canvas_bytes);
 33         return .{
 34             .limits = limits,
 35             .categories_offset = 0,
 36             .category_bytes = category_bytes,
 37             .stack_totals_offset = stack_totals_offset,
 38             .stack_total_bytes = stack_total_bytes,
 39             .canvas_offset = canvas_offset,
 40             .output_offset = output_offset,
 41             .storage_bytes = try added(output_offset, limits.max_output_bytes),
 42         };
 43     }
 44 };
 45 
 46 fn added(left: usize, right: usize) DeriveError!usize {
 47     return std.math.add(usize, left, right) catch error.CapacityOverflow;
 48 }
 49 
 50 fn multiplied(left: usize, right: usize) DeriveError!usize {
 51     return std.math.mul(usize, left, right) catch error.CapacityOverflow;
 52 }
 53 
 54 fn aligned(value: usize, alignment: usize) DeriveError!usize {
 55     const padding = (alignment - value % alignment) % alignment;
 56     return added(value, padding);
 57 }
 58 
 59 fn independent(limits: Limits) DeriveError!Capacity {
 60     const category_bytes = @as(u128, limits.max_categories) * @sizeOf([]const u8);
 61     const alignment = @alignOf(diagram.bounds.StackTotal);
 62     const stack_totals_offset = category_bytes + (alignment - category_bytes % alignment) % alignment;
 63     const stack_total_bytes = @as(u128, limits.max_stack_totals) * @sizeOf(diagram.bounds.StackTotal);
 64     const canvas_offset = stack_totals_offset + stack_total_bytes;
 65     const output_offset = canvas_offset + limits.max_canvas_bytes;
 66     const storage_bytes = output_offset + limits.max_output_bytes;
 67     if (category_bytes > std.math.maxInt(usize) or
 68         stack_totals_offset > std.math.maxInt(usize) or
 69         stack_total_bytes > std.math.maxInt(usize) or
 70         canvas_offset > std.math.maxInt(usize) or
 71         output_offset > std.math.maxInt(usize) or
 72         storage_bytes > std.math.maxInt(usize))
 73     {
 74         return error.CapacityOverflow;
 75     }
 76     return .{
 77         .limits = limits,
 78         .categories_offset = 0,
 79         .category_bytes = @intCast(category_bytes),
 80         .stack_totals_offset = @intCast(stack_totals_offset),
 81         .stack_total_bytes = @intCast(stack_total_bytes),
 82         .canvas_offset = @intCast(canvas_offset),
 83         .output_offset = @intCast(output_offset),
 84         .storage_bytes = @intCast(storage_bytes),
 85     };
 86 }
 87 
 88 test "ASCII render capacity matches an independent byte model" {
 89     comptime {
 90         @stardustClaim(
 91             @import("alloc_phase").capacity.witness(@import("./root.zig").RenderStorage, "zen_ascii_render_capacity"),
 92             null,
 93             null,
 94             null,
 95             null,
 96             null,
 97             null,
 98         );
 99     }
100 
101     const limits = Limits{
102         .max_categories = 3,
103         .max_stack_totals = 2,
104         .max_canvas_bytes = 72 * 22,
105         .max_output_bytes = 73 * 22,
106     };
107     const capacity = try Capacity.derive(limits);
108     try std.testing.expectEqual(try independent(limits), capacity);
109     try std.testing.expectEqual(
110         @as(usize, 3 * @sizeOf([]const u8) + 2 * @sizeOf(diagram.bounds.StackTotal) + 72 * 22 + 73 * 22),
111         capacity.storage_bytes,
112     );
113 }
114 
115 test "ASCII render capacity handles zero and rejects overflow" {
116     try std.testing.expectEqual(@as(usize, 0), (try Capacity.derive(.{
117         .max_categories = 0,
118         .max_stack_totals = 0,
119         .max_canvas_bytes = 0,
120         .max_output_bytes = 0,
121     })).storage_bytes);
122     try std.testing.expectError(error.CapacityOverflow, Capacity.derive(.{
123         .max_categories = std.math.maxInt(usize),
124         .max_stack_totals = 0,
125         .max_canvas_bytes = 0,
126         .max_output_bytes = 0,
127     }));
128     try std.testing.expectError(error.CapacityOverflow, Capacity.derive(.{
129         .max_categories = 0,
130         .max_stack_totals = 0,
131         .max_canvas_bytes = std.math.maxInt(usize),
132         .max_output_bytes = 1,
133     }));
134 }