lib/choir/src/product/recipe.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const ir = @import("../core/root.zig");
3 const revision = @import("root.zig").revision;
4
5 pub const version: u32 = 1;
6 pub const ArithmeticPolicy = ir.interfaces.effects.ArithmeticPolicy;
7
8 /// The effective Context policy is an input, not a derived permission bit.
9 pub const Recipe = struct {
10 arithmetic: ArithmeticPolicy,
11 policy: []const u8,
12
13 pub fn requireContext(self: Recipe, context: *const ir.Context) !void {
14 if (!std.meta.eql(self.arithmetic, context.arithmetic_policy)) {
15 return error.UnrecordedArithmeticPolicy;
16 }
17 }
18
19 pub fn restore(self: Recipe, context: *ir.Context) void {
20 context.arithmetic_policy = self.arithmetic;
21 }
22 };
23
24 pub fn encode(allocator: std.mem.Allocator, recipe: Recipe) ![]u8 {
25 comptime classify();
26 var writer = revision.record.Writer.init(allocator);
27 defer writer.deinit();
28 try writer.writeInt(u32, version);
29 inline for (@typeInfo(ArithmeticPolicy).@"struct".field_names) |field| {
30 try writer.writeBool(@field(recipe.arithmetic, field));
31 }
32 try writer.writeBlob(recipe.policy);
33 return writer.finish();
34 }
35
36 pub fn decode(bytes: []const u8) !Recipe {
37 comptime classify();
38 var reader = try revision.record.Reader.init(bytes);
39 if (try reader.readInt(u32) != version) return error.UnknownRecipeSchema;
40 var arithmetic: ArithmeticPolicy = undefined;
41 inline for (@typeInfo(ArithmeticPolicy).@"struct".field_names) |field| {
42 @field(arithmetic, field) = try reader.readBool();
43 }
44 const policy = try reader.readBlob();
45 if (!reader.atEnd()) return error.InvalidRecipe;
46 return .{ .arithmetic = arithmetic, .policy = policy };
47 }
48
49 fn classify() void {
50 revision.record.requireFields(Recipe, &.{ "arithmetic", "policy" });
51 revision.record.requireFields(ArithmeticPolicy, &.{
52 "exceptions_masked", "default_rounding", "environment_observable",
53 });
54 }
55
56 test "operation recipe distinguishes all three effective arithmetic policy inputs" {
57 const allocator = std.testing.allocator;
58 const original = try encode(allocator, .{ .arithmetic = .{}, .policy = "strict" });
59 defer allocator.free(original);
60 inline for (@typeInfo(ArithmeticPolicy).@"struct".field_names) |field| {
61 var changed: ArithmeticPolicy = .{};
62 @field(changed, field) = !@field(changed, field);
63 const bytes = try encode(allocator, .{ .arithmetic = changed, .policy = "strict" });
64 defer allocator.free(bytes);
65 try std.testing.expect(!std.mem.eql(u8, original, bytes));
66 const decoded = try decode(bytes);
67 try std.testing.expectEqualDeep(changed, decoded.arithmetic);
68 try std.testing.expectEqualStrings("strict", decoded.policy);
69 }
70 }