lib/choir/src/backends/wasm/emission/capacity.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const ir = @import("../../../core/root.zig");
  3 const emission = @import("root.zig");
  4 
  5 pub const Limits = struct {
  6     module: *ir.Operation,
  7     options: emission.Options,
  8     facts: emission.Facts,
  9 
 10     pub fn inspect(module: *ir.Operation, options: emission.Options) emission.Error!Limits {
 11         return .{
 12             .module = module,
 13             .options = options,
 14             .facts = try emission.inspection.inspect(module, options),
 15         };
 16     }
 17 };
 18 
 19 pub const Capacity = struct {
 20     facts: emission.Facts,
 21     frame_count: usize,
 22     function_index_slots: usize,
 23     value_index_slots: usize,
 24     function_plan_bytes: usize,
 25     function_index_bytes: usize,
 26     value_plan_bytes: usize,
 27     value_index_bytes: usize,
 28     frame_bytes: usize,
 29     working_bytes: usize,
 30 
 31     pub fn derive(limits: Limits) error{CapacityOverflow}!Capacity {
 32         const frame_count = if (limits.facts.definition_count == 0)
 33             0
 34         else frame_count: {
 35             const controls = std.math.mul(
 36                 usize,
 37                 limits.facts.nesting_depth,
 38                 2,
 39             ) catch return error.CapacityOverflow;
 40             break :frame_count std.math.add(usize, controls, 1) catch
 41                 return error.CapacityOverflow;
 42         };
 43         const function_plan_bytes = try bytesFor(
 44             emission.FunctionPlan,
 45             limits.facts.function_count,
 46         );
 47         const function_index_slots = try emission.plan.index.slotsFor(
 48             limits.facts.function_count,
 49         );
 50         const function_index_bytes = try bytesFor(u32, function_index_slots);
 51         const value_plan_bytes = try bytesFor(emission.ValuePlan, limits.facts.value_count);
 52         const value_index_slots = try emission.plan.index.slotsFor(limits.facts.value_count);
 53         const value_index_bytes = try bytesFor(u32, value_index_slots);
 54         const frame_bytes = try bytesFor(emission.function.Frame, frame_count);
 55         var working_bytes = try add(function_plan_bytes, function_index_bytes);
 56         working_bytes = try add(working_bytes, value_plan_bytes);
 57         working_bytes = try add(working_bytes, value_index_bytes);
 58         working_bytes = try add(working_bytes, frame_bytes);
 59         return .{
 60             .facts = limits.facts,
 61             .frame_count = frame_count,
 62             .function_index_slots = function_index_slots,
 63             .value_index_slots = value_index_slots,
 64             .function_plan_bytes = function_plan_bytes,
 65             .function_index_bytes = function_index_bytes,
 66             .value_plan_bytes = value_plan_bytes,
 67             .value_index_bytes = value_index_bytes,
 68             .frame_bytes = frame_bytes,
 69             .working_bytes = working_bytes,
 70         };
 71     }
 72 };
 73 
 74 pub const OutputCapacity = struct {
 75     output_bytes: usize,
 76     initialization_bytes: usize,
 77     steady_bytes: usize,
 78 
 79     pub fn derive(
 80         capacity: Capacity,
 81         output_bytes: usize,
 82     ) error{CapacityOverflow}!OutputCapacity {
 83         return .{
 84             .output_bytes = output_bytes,
 85             .initialization_bytes = try add(capacity.working_bytes, output_bytes),
 86             .steady_bytes = output_bytes,
 87         };
 88     }
 89 };
 90 
 91 fn bytesFor(comptime T: type, count: usize) error{CapacityOverflow}!usize {
 92     return std.math.mul(usize, @sizeOf(T), count) catch error.CapacityOverflow;
 93 }
 94 
 95 fn add(lhs: usize, rhs: usize) error{CapacityOverflow}!usize {
 96     return std.math.add(usize, lhs, rhs) catch error.CapacityOverflow;
 97 }
 98 
 99 fn testLimits(facts: emission.Facts) Limits {
100     return .{ .module = undefined, .options = .{}, .facts = facts };
101 }
102 
103 fn testFacts() emission.Facts {
104     return .{
105         .function_count = 0,
106         .import_count = 0,
107         .definition_count = 0,
108         .value_count = 0,
109         .local_count = 0,
110         .nesting_depth = 0,
111         .needs_memory = false,
112     };
113 }
114 
115 test "wasm index capacity preserves a strict three-quarter load bound" {
116     var facts = testFacts();
117     facts.function_count = 1;
118     facts.value_count = 3;
119     var capacity = try Capacity.derive(testLimits(facts));
120     try std.testing.expectEqual(@as(usize, 4), capacity.function_index_slots);
121     try std.testing.expectEqual(@as(usize, 4), capacity.value_index_slots);
122 
123     facts.function_count = 4;
124     facts.value_count = 4;
125     capacity = try Capacity.derive(testLimits(facts));
126     try std.testing.expectEqual(@as(usize, 8), capacity.function_index_slots);
127     try std.testing.expectEqual(@as(usize, 8), capacity.value_index_slots);
128 }
129 
130 test "wasm zero capacity owns no index or control workspace" {
131     const capacity = try Capacity.derive(testLimits(testFacts()));
132     try std.testing.expectEqual(@as(usize, 0), capacity.function_index_slots);
133     try std.testing.expectEqual(@as(usize, 0), capacity.value_index_slots);
134     try std.testing.expectEqual(@as(usize, 0), capacity.frame_count);
135     try std.testing.expectEqual(@as(usize, 0), capacity.working_bytes);
136 }
137 
138 test "wasm capacity arithmetic rejects unrepresentable workspace" {
139     var facts = testFacts();
140     facts.function_count = std.math.maxInt(usize);
141     try std.testing.expectError(
142         error.CapacityOverflow,
143         Capacity.derive(testLimits(facts)),
144     );
145 }