lib/accy/src/choir/contract.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const choir = @import("choir");
3 const semantic = @import("semantic.zig");
4
5 const ir = choir.ir;
6
7 pub const product_name = "accy.contract";
8
9 pub const allowed_dialects = [_][]const u8{
10 "builtin",
11 "func",
12 "accy",
13 };
14
15 pub const ContractError = error{
16 UnregisteredOperation,
17 UnsupportedDialect,
18 };
19
20 pub const ContractModule = @import("root.zig").publication.Module(.contract);
21
22 pub const ContractJob = struct {
23 allocator: std.mem.Allocator,
24 semantic_module: *semantic.SemanticModule,
25 choir_module: *ir.Operation,
26 observed_fingerprint: u64,
27
28 pub fn init(
29 allocator: std.mem.Allocator,
30 semantic_module: *semantic.SemanticModule,
31 ) !*ContractJob {
32 const module = try allocator.create(ContractJob);
33 errdefer allocator.destroy(module);
34 module.* = .{
35 .allocator = allocator,
36 .semantic_module = semantic_module,
37 .choir_module = semantic_module.choir_module,
38 .observed_fingerprint = 0,
39 };
40 try module.verify();
41 module.observed_fingerprint = try choir.operationFingerprint(allocator, module.choir_module);
42 return module;
43 }
44
45 pub fn context(self: *ContractJob) *ir.Context {
46 return self.semantic_module.context();
47 }
48
49 pub fn deinit(self: *ContractJob) void {
50 self.semantic_module.deinit();
51 const allocator = self.allocator;
52 allocator.destroy(self);
53 }
54
55 pub fn verify(self: *ContractJob) !void {
56 try self.semantic_module.verify();
57 try verifyAllowedDialects(self.choir_module);
58 }
59
60 pub fn fingerprint(self: *const ContractJob) u64 {
61 return self.observed_fingerprint;
62 }
63 };
64
65 pub fn verifyAllowedDialects(module: *ir.Operation) !void {
66 var verifier = AllowedDialectVerifier{};
67 const result = try module.walk(.{ .order = .pre_order }, &verifier, AllowedDialectVerifier.visit);
68 if (result.wasInterrupted()) return error.UnsupportedDialect;
69 }
70
71 const AllowedDialectVerifier = struct {
72 fn visit(_: *AllowedDialectVerifier, op: *ir.Operation) !ir.WalkResult {
73 _ = op.name.resolveRegisteredInfo(op.context) orelse return error.UnregisteredOperation;
74 if (!isAllowedDialect(op.name.getDialectNamespace())) return error.UnsupportedDialect;
75 return .advance;
76 }
77 };
78
79 fn isAllowedDialect(name: []const u8) bool {
80 inline for (allowed_dialects) |allowed| {
81 if (std.mem.eql(u8, name, allowed)) return true;
82 }
83 return false;
84 }
85
86 fn bodyBlock(module: *ir.Operation) *ir.Block {
87 return module.getRegion(0).?.getEntryBlock().?;
88 }
89
90 test "contract module verifies semantic Choir and exposes a stable fingerprint" {
91 const allocator = std.testing.allocator;
92
93 var builder = try semantic.Builder.init(allocator, semantic.Builder.ContextLimits.standard);
94 defer builder.deinit();
95
96 const ty = try builder.tensor(.f32, &.{4});
97 var function = try builder.beginFunction("contract_add", &.{ ty, ty }, &.{ty});
98 const sum = try function.add(function.parameter(0), function.parameter(1));
99 try function.return_(&.{sum});
100 try function.finish();
101
102 const semantic_module = try builder.finish();
103 var contract_module = try ContractJob.init(allocator, semantic_module);
104 defer contract_module.deinit();
105
106 try std.testing.expectEqualStrings(product_name, "accy.contract");
107 try contract_module.verify();
108 try std.testing.expect(contract_module.fingerprint() != 0);
109 try std.testing.expectEqual(contract_module.fingerprint(), try choir.operationFingerprint(allocator, contract_module.choir_module));
110 }
111
112 test "contract module rejects non-contract dialect operations" {
113 const allocator = std.testing.allocator;
114
115 var builder = try semantic.Builder.init(allocator, semantic.Builder.ContextLimits.standard);
116 defer builder.deinit();
117
118 const ty = try builder.tensor(.f32, &.{4});
119 var function = try builder.beginFunction("bad_contract", &.{ty}, &.{ty});
120 try function.return_(&.{function.parameter(0)});
121 try function.finish();
122
123 const semantic_module = try builder.finish();
124 var semantic_owned = true;
125 errdefer if (semantic_owned) semantic_module.deinit();
126
127 const f32_type = try choir.dialects.ArithDialect.getScalarType(semantic_module.context(), .f32);
128 const memref_type = try choir.dialects.MemrefDialect.getMemrefType1D(semantic_module.context(), 4, f32_type, .device);
129 const alloc = try choir.dialects.MemrefDialect.AllocOp.createStatic(semantic_module.context(), ir.Location.getUnknown(), memref_type);
130 try bodyBlock(semantic_module.choir_module).addOperation(alloc.op);
131
132 semantic_owned = false;
133 const result = ContractJob.init(allocator, semantic_module);
134 try std.testing.expectError(error.UnsupportedDialect, result);
135 semantic_module.deinit();
136 }