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 }