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 }