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 }