lib/choir/src/backends/contract.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const alloc_arena = @import("alloc_arena");
  3 const ir = @import("../core/root.zig");
  4 const diagnostics = @import("../root.zig").diagnostics;
  5 const interface = @import("interface.zig");
  6 
  7 const BackendError = interface.BackendError;
  8 
  9 pub fn verifyModule(stage: []const u8, module: *ir.Operation) BackendError!void {
 10     return verifyModuleWithOptions(stage, module, ir.verify.default_options);
 11 }
 12 
 13 pub const VerificationDiagnostic = struct {
 14     buffer: [256]u8 = undefined,
 15     len: usize = 0,
 16     failure: ?diagnostics.VerifierFailure = null,
 17 
 18     pub fn set(self: *VerificationDiagnostic, message: []const u8) void {
 19         self.len = @min(message.len, self.buffer.len);
 20         @memcpy(self.buffer[0..self.len], message[0..self.len]);
 21         self.failure = null;
 22     }
 23 
 24     pub fn capture(
 25         self: *VerificationDiagnostic,
 26         stage: []const u8,
 27         module: *ir.Operation,
 28         options: ir.VerifyOptions,
 29         err: anyerror,
 30     ) void {
 31         self.setFailure(diagnostics.captureVerifierFailure(stage, module, options, err));
 32     }
 33 
 34     pub fn setFailure(self: *VerificationDiagnostic, failure: diagnostics.VerifierFailure) void {
 35         self.failure = failure;
 36         const message = self.failure.?.format(&self.buffer);
 37         if (message.ptr == self.buffer[0..].ptr) {
 38             self.len = message.len;
 39         } else {
 40             self.set(message);
 41         }
 42     }
 43 
 44     pub fn text(self: *const VerificationDiagnostic) []const u8 {
 45         return self.buffer[0..self.len];
 46     }
 47 
 48     pub fn hasText(self: *const VerificationDiagnostic) bool {
 49         return self.len != 0;
 50     }
 51 
 52     pub fn diagnostic(self: *const VerificationDiagnostic) ?diagnostics.Diagnostic {
 53         const failure = self.failure orelse return null;
 54         return failure.diagnostic();
 55     }
 56 };
 57 
 58 pub fn verifyModuleWithOptions(
 59     stage: []const u8,
 60     module: *ir.Operation,
 61     options: ir.VerifyOptions,
 62 ) BackendError!void {
 63     return verifyModuleWithOptionsAndDiagnostic(stage, module, options, null);
 64 }
 65 
 66 pub fn verifyModuleWithDiagnostic(
 67     stage: []const u8,
 68     module: *ir.Operation,
 69     diagnostic: ?*VerificationDiagnostic,
 70 ) BackendError!void {
 71     return verifyModuleWithOptionsAndDiagnostic(stage, module, ir.verify.default_options, diagnostic);
 72 }
 73 
 74 pub fn verifyModuleWithOptionsAndDiagnostic(
 75     stage: []const u8,
 76     module: *ir.Operation,
 77     options: ir.VerifyOptions,
 78     diagnostic: ?*VerificationDiagnostic,
 79 ) BackendError!void {
 80     ir.verifyOperation(module, options) catch |err| {
 81         const failure = diagnostics.captureVerifierFailure(stage, module, options, err);
 82         if (diagnostic) |captured| captured.setFailure(failure);
 83         var in_flight = module.getContext().emitDiagnostic(failure.diagnostic());
 84         defer in_flight.deinit();
 85         in_flight.attachNoteAt(failure.root.getLoc(), "verifier root operation") catch {};
 86         in_flight.addMetadata("choir.stage", failure.stage) catch {};
 87         in_flight.addMetadata("choir.error", @errorName(failure.err)) catch {};
 88         _ = in_flight.emit() catch {};
 89         return BackendError.VerificationFailed;
 90     };
 91 }
 92 
 93 /// Loads the arith dialect that backend lowering builds operations from.
 94 /// An activated context that lacks it stays usable, and lowering fails later if it needs arith.
 95 /// Fails with `error.OutOfMemory` when the load cannot allocate.
 96 pub fn loadArithDialect(ctx: *ir.Context) std.mem.Allocator.Error!void {
 97     const dialects = @import("../dialects/root.zig");
 98     ir.dialects.loadDialectSpec(ctx, dialects.arith.spec) catch |err| switch (err) {
 99         error.ContextFrozen => {},
100         error.OutOfMemory => return error.OutOfMemory,
101         else => std.debug.panic("loading the arith dialect failed: {s}", .{@errorName(err)}),
102     };
103 }
104 
105 test "verifyModuleWithOptionsAndDiagnostic captures verifier detail" {
106     const testing = std.testing;
107     const dialects = @import("../dialects/root.zig");
108     const ArithDialect = dialects.ArithDialect;
109     const BuiltinDialect = dialects.BuiltinDialect;
110     const FuncDialect = dialects.FuncDialect;
111 
112     var arena = alloc_arena.Arena.init(std.testing.allocator);
113     defer arena.deinit();
114     const allocator = arena.allocator();
115 
116     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
117     defer ctx.deinit(allocator);
118     try @import("../dialects/root.zig").registerAllDialects(&ctx);
119     try ir.dialects.loadDialectSpec(&ctx, dialects.arith.spec);
120 
121     const loc = ir.Location.getUnknown();
122     const i32_type = try ArithDialect.getI32Type(&ctx);
123     const i64_type = try ArithDialect.getScalarType(&ctx, .i64);
124     const bool_type = try ArithDialect.getScalarType(&ctx, .bool);
125 
126     const module = try BuiltinDialect.ModuleOp.create(&ctx, loc);
127     const module_block = module.getBodyBlock();
128 
129     var func = try FuncDialect.FuncOp.create(&ctx, loc, "bad_cmp", &.{}, &.{bool_type});
130     try module_block.addOperation(func.op);
131 
132     const entry_block = func.getEntryBlock();
133     var left = try ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 1);
134     try entry_block.addOperation(left.op);
135     var right = try ArithDialect.ConstantOp.createInt(&ctx, loc, i64_type, 2);
136     try entry_block.addOperation(right.op);
137     const bad_cmp = try ArithDialect.CmpOp.create(&ctx, loc, .eq, left.getResult(), right.getResult());
138     try entry_block.addOperation(bad_cmp.op);
139     const ret = try FuncDialect.ReturnOp.create(&ctx, loc, &.{bad_cmp.getResult()});
140     try entry_block.addOperation(ret.op);
141 
142     const Recorder = struct {
143         seen: usize = 0,
144         op_name: []const u8 = "",
145         error_name: []const u8 = "",
146         stage: []const u8 = "",
147         note_count: usize = 0,
148 
149         fn handle(context: ?*anyopaque, diagnostic: *const diagnostics.Diagnostic) !diagnostics.HandlerResult {
150             const self: *@This() = @ptrCast(@alignCast(context.?));
151             self.seen += 1;
152             self.op_name = diagnostic.operationName().?;
153             self.error_name = diagnostic.error_name.?;
154             self.note_count = diagnostic.notes.len;
155             for (diagnostic.metadata) |metadata| {
156                 if (std.mem.eql(u8, metadata.name, "choir.stage")) {
157                     self.stage = metadata.value;
158                 }
159             }
160             return .consumed;
161         }
162     };
163 
164     var recorder = Recorder{};
165     _ = try ctx.registerDiagnosticHandler(.{
166         .context = &recorder,
167         .handle = Recorder.handle,
168     });
169 
170     var captured: VerificationDiagnostic = .{};
171     try testing.expectError(
172         BackendError.VerificationFailed,
173         verifyModuleWithOptionsAndDiagnostic(
174             "backend/test/verify",
175             module.op,
176             ir.verify.default_options,
177             &captured,
178         ),
179     );
180     try testing.expect(captured.hasText());
181     try testing.expect(std.mem.indexOf(u8, captured.text(), "backend/test/verify") != null);
182     try testing.expect(std.mem.indexOf(u8, captured.text(), "arith.cmp") != null);
183     const diagnostic = captured.diagnostic() orelse return error.TestExpectedResult;
184     try testing.expectEqual(diagnostics.Severity.err, diagnostic.severity);
185     try testing.expectEqual(bad_cmp.op, diagnostic.operation.?);
186     try testing.expectEqualStrings("SameTypeOperandsMismatch", diagnostic.error_name.?);
187     try testing.expectEqual(@as(usize, 1), recorder.seen);
188     try testing.expectEqualStrings("arith.cmp", recorder.op_name);
189     try testing.expectEqualStrings("SameTypeOperandsMismatch", recorder.error_name);
190     try testing.expectEqualStrings("backend/test/verify", recorder.stage);
191     try testing.expectEqual(@as(usize, 1), recorder.note_count);
192 }