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 }