lib/choir/src/dialects/fixture/dialect.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const alloc_arena = @import("alloc_arena");
  3 const ir = @import("../../core/root.zig");
  4 const interfaces = @import("../../core/root.zig").interfaces;
  5 
  6 const effects = ir.interfaces.effects;
  7 
  8 const test_dialect_name = "test";
  9 const test_unit_attr_name = "test.unit";
 10 const test_bool_attr_name = "test.bool";
 11 const test_int_attr_name = "test.int";
 12 const test_string_attr_name = "test.string";
 13 const test_type_attr_name = "test.type";
 14 
 15 const test_i32_type_name = "test.i32";
 16 const test_i64_type_name = "test.i64";
 17 const test_f64_type_name = "test.f64";
 18 
 19 fn attrPayload(attr: ir.Attribute, name: []const u8) ?[]const u8 {
 20     if (!std.mem.eql(u8, attr.abstract.name, name)) return null;
 21     const dialect_attr = attr.cast(ir.Attribute.DialectAttr) orelse return null;
 22     return dialect_attr.payload;
 23 }
 24 
 25 pub const TestDialect = struct {
 26     pub const name = test_dialect_name;
 27     const op_specs = ir.dialects.opSpec.dialect(@This());
 28     pub const spec = ir.dialects.dialectSpec(@This(), .{
 29         .types = &.{
 30             ir.dialects.typeName(test_i32_type_name),
 31             ir.dialects.typeName(test_i64_type_name),
 32             ir.dialects.typeName(test_f64_type_name),
 33         },
 34     });
 35 
 36     const func_symbol_vtable = interfaces.SymbolOpInterface.VTable{
 37         .getSymbolName = getFuncSymbolName,
 38         .setSymbolName = setFuncSymbolName,
 39         .isDeclaration = isFuncDeclaration,
 40     };
 41 
 42     pub const ModuleOp = struct {
 43         op: *ir.Operation,
 44 
 45         pub const operation_spec = op_specs.define(.{
 46             .mnemonic = "module",
 47             .operands = 0,
 48             .results = 0,
 49             .regions = 1,
 50             .successors = 0,
 51             .dynamic_traits = .{ ir.traits.SymbolTable, ir.traits.IsolatedFromAbove, ir.traits.NoTerminator, ir.traits.SingleBlock },
 52         });
 53         pub const operation_name = operation_spec.name;
 54 
 55         pub fn create(ctx: *ir.Context, loc: ir.Location) !ModuleOp {
 56             try registerTestDialect(ctx);
 57             var builder = ir.OperationBuilder.init(ctx);
 58             var state = op_specs.state(@This(), loc);
 59             var body = ir.context.initRegion(ctx);
 60             defer body.deinit();
 61             var body_builder = ir.OperationBuilder.init(ctx);
 62             _ = try body_builder.createBlock(&body, &.{}, &.{});
 63             var regions = [_]*ir.Region{&body};
 64             state.addRegionBodies(&regions);
 65 
 66             const op = try builder.create(state);
 67 
 68             return .{ .op = op };
 69         }
 70 
 71         pub fn getBody(self: ModuleOp) *ir.Region {
 72             return self.op.getRegion(0).?;
 73         }
 74 
 75         pub fn getBodyBlock(self: ModuleOp) *ir.Block {
 76             return self.getBody().getEntryBlock().?;
 77         }
 78     };
 79 
 80     pub const FuncOp = struct {
 81         op: *ir.Operation,
 82 
 83         pub const operation_spec = op_specs.define(.{
 84             .mnemonic = "func",
 85             .operands = 0,
 86             .results = 0,
 87             .regions = 1,
 88             .successors = 0,
 89             .attrs = &.{ir.SymbolTable.symbol_attr_names.sym_visibility},
 90             .required_attrs = &.{"sym_name"},
 91             .interfaces = &.{
 92                 interfaces.SymbolOpInterface.entry(&func_symbol_vtable),
 93                 effects.EffectOpInterface.entryFor(.{
 94                     .facts = &.{.{ .region = .{
 95                         .index = 0,
 96                         .execution = .latent,
 97                         .may_diverge = false,
 98                         .captures = false,
 99                     } }},
100                 }),
101             },
102             .dynamic_traits = .{ir.traits.IsolatedFromAbove},
103         });
104         pub const operation_name = operation_spec.name;
105 
106         pub fn create(
107             ctx: *ir.Context,
108             loc: ir.Location,
109             func_name: []const u8,
110             input_types: []const ir.Type,
111         ) !FuncOp {
112             try registerTestDialect(ctx);
113             var builder = ir.OperationBuilder.init(ctx);
114             var state = op_specs.state(@This(), loc);
115             var body = ir.context.initRegion(ctx);
116             defer body.deinit();
117             var body_builder = ir.OperationBuilder.init(ctx);
118             _ = try body_builder.createBlockWithLoc(&body, input_types, loc);
119             var regions = [_]*ir.Region{&body};
120             state.addRegionBodies(&regions);
121 
122             const op = try builder.create(state);
123 
124             const name_attr = try getStringAttr(ctx, func_name);
125             try op.setAttr("sym_name", name_attr);
126 
127             return .{ .op = op };
128         }
129 
130         pub fn getName(self: FuncOp) ?[]const u8 {
131             return ir.SymbolTable.getSymbolName(self.op);
132         }
133 
134         pub fn getBody(self: FuncOp) *ir.Region {
135             return self.op.getRegion(0).?;
136         }
137 
138         pub fn getEntryBlock(self: FuncOp) *ir.Block {
139             return self.getBody().getEntryBlock().?;
140         }
141 
142         pub fn getArguments(self: FuncOp) []*ir.Value {
143             return self.getEntryBlock().arguments.items;
144         }
145     };
146 
147     pub const ConstantOp = struct {
148         op: *ir.Operation,
149 
150         pub const operation_spec = op_specs.leaf(.{
151             .mnemonic = "constant",
152             .interfaces = &.{effects.EffectOpInterface.entryFor(.{
153                 .capacity = .{ .per_result = 1 },
154                 .enumerate = constantEffects,
155             })},
156             .operands = 0,
157             .results = .{"result"},
158             .required_attrs = &.{"value"},
159         });
160         pub const operation_name = operation_spec.name;
161 
162         pub fn create(
163             ctx: *ir.Context,
164             loc: ir.Location,
165             result_type: ir.Type,
166             int_value: i64,
167         ) !ConstantOp {
168             var builder = ir.OperationBuilder.init(ctx);
169             var state = op_specs.state(@This(), loc);
170             state.addTypes(&.{result_type});
171 
172             const op = try builder.create(state);
173             const value_attr = try getIntegerAttr(ctx, int_value);
174             try op.setAttr("value", value_attr);
175 
176             return .{ .op = op };
177         }
178 
179         pub fn getResult(self: *const ConstantOp) *ir.Value {
180             return ir.dialects.result(operation_spec, self.op, "result");
181         }
182 
183         pub fn getValue(self: ConstantOp) ?i64 {
184             if (self.op.getAttr("value")) |attr| {
185                 return getIntegerValue(attr);
186             }
187             return null;
188         }
189     };
190 
191     /// Consumes the next value of a global counter; every execution is distinct.
192     pub const DrawOp = struct {
193         op: *ir.Operation,
194         pub const operation_spec = op_specs.leaf(.{
195             .mnemonic = "draw",
196             .operands = 0,
197             .results = 1,
198             .interfaces = &.{effects.EffectOpInterface.entryFor(.{
199                 .complete = true,
200                 .facts = &.{
201                     .{ .event = .{ .kind = .state_draw, .resource = .{
202                         .subject = .{ .global = "fixture.draws" },
203                         .state_key = "next",
204                     } } },
205                     .{ .result = .{ .index = 0, .ownership = .none } },
206                 },
207             })},
208         });
209         pub const operation_name = operation_spec.name;
210     };
211 
212     /// Consumes the next counter value at its explicit address.
213     pub const AddressedDrawOp = struct {
214         op: *ir.Operation,
215         pub const operation_spec = op_specs.leaf(.{
216             .mnemonic = "addressed_draw",
217             .operands = 1,
218             .results = 1,
219             .interfaces = &.{effects.EffectOpInterface.entryFor(.{
220                 .complete = true,
221                 .facts = &.{
222                     .{ .event = .{ .kind = .state_draw, .resource = .{
223                         .subject = .{ .operand = 0 },
224                         .state_key = "next",
225                     } } },
226                     .{ .result = .{ .index = 0, .ownership = .none } },
227                 },
228             })},
229         });
230         pub const operation_name = operation_spec.name;
231     };
232 
233     /// Reads a permanent initialized i64 cell. The fixture cell contains seven;
234     /// reading it cannot fail, consume state, acquire ownership or allocate.
235     pub const SafeReadOp = struct {
236         op: *ir.Operation,
237         pub const operation_spec = op_specs.leaf(.{
238             .mnemonic = "safe_read",
239             .operands = 0,
240             .results = 1,
241             .result_types = &.{ir.dialects.typeConstraint.exact(0, test_i64_type_name)},
242             .interfaces = &.{effects.EffectOpInterface.entryFor(.{
243                 .complete = true,
244                 .facts = &.{
245                     .{ .event = .{ .kind = .read, .resource = .{
246                         .subject = .{ .global = "fixture.permanent_cell" },
247                         .lifetime = .{ .named = "process" },
248                     } } },
249                     .{ .result = .{ .index = 0, .ownership = .none } },
250                 },
251             })},
252         });
253         pub const operation_name = operation_spec.name;
254     };
255 
256     pub const IdentityOp = struct {
257         op: *ir.Operation,
258 
259         pub const operation_spec = op_specs.leaf(.{
260             .mnemonic = "identity",
261             .operands = .{"input"},
262             .results = .{"result"},
263         });
264         pub const operation_name = operation_spec.name;
265     };
266 
267     pub const UserOp = struct {
268         op: *ir.Operation,
269 
270         pub const operation_spec = op_specs.leaf(.{
271             .mnemonic = "user",
272             .operands = .{"input"},
273             .results = 0,
274         });
275         pub const operation_name = operation_spec.name;
276     };
277 
278     pub const BinaryOp = struct {
279         op: *ir.Operation,
280 
281         pub const operation_spec = op_specs.leaf(.{
282             .mnemonic = "binary",
283             .operands = .{ "lhs", "rhs" },
284             .results = .{"result"},
285             .interfaces = &.{
286                 interfaces.InferTypeOpInterface.entryFor(inferBinaryResultTypes),
287             },
288         });
289         pub const operation_name = operation_spec.name;
290 
291         pub fn create(
292             ctx: *ir.Context,
293             loc: ir.Location,
294             lhs: *ir.Value,
295             rhs: *ir.Value,
296         ) !BinaryOp {
297             var builder = ir.OperationBuilder.init(ctx);
298             var state = op_specs.state(@This(), loc);
299             state.addOperands(&.{ lhs, rhs });
300             state.addTypes(&.{lhs.type});
301 
302             const op = try builder.create(state);
303             return .{ .op = op };
304         }
305 
306         pub fn getResult(self: *const BinaryOp) *ir.Value {
307             return ir.dialects.result(operation_spec, self.op, "result");
308         }
309 
310         pub fn getLhs(self: BinaryOp) *ir.Value {
311             return ir.dialects.operand(operation_spec, self.op, "lhs");
312         }
313 
314         pub fn getRhs(self: BinaryOp) *ir.Value {
315             return ir.dialects.operand(operation_spec, self.op, "rhs");
316         }
317     };
318 
319     pub const BranchOp = struct {
320         op: *ir.Operation,
321 
322         pub const operation_spec = op_specs.define(.{
323             .mnemonic = "br",
324             .operands = 0,
325             .results = 0,
326             .regions = 0,
327             .successors = .{"target"},
328             .interfaces = &.{
329                 interfaces.ControlFlowInterface.entryFor(getBranchSuccessorCount, getBranchSuccessor),
330             },
331         });
332         pub const operation_name = operation_spec.name;
333 
334         pub fn create(ctx: *ir.Context, loc: ir.Location, target: *ir.Block) !BranchOp {
335             var builder = ir.OperationBuilder.init(ctx);
336             var state = op_specs.state(@This(), loc);
337             state.addSuccessors(&.{target});
338             const op = try builder.create(state);
339             return .{ .op = op };
340         }
341 
342         pub fn getTarget(self: BranchOp) *ir.Block {
343             return ir.dialects.successor(operation_spec, self.op, "target");
344         }
345     };
346 
347     pub const StoreOp = struct {
348         op: *ir.Operation,
349 
350         pub const operation_spec = op_specs.leaf(.{
351             .mnemonic = "store",
352             .operands = .{ "target", "value" },
353             .results = 0,
354             .interfaces = &.{
355                 effects.EffectOpInterface.entryFor(.{ .facts = &.{.{ .event = .{
356                     .kind = .write,
357                     .resource = .{ .subject = .{ .operand = 1 } },
358                 } }} }),
359             },
360         });
361         pub const operation_name = operation_spec.name;
362 
363         pub fn create(ctx: *ir.Context, loc: ir.Location, target: *ir.Value, value: *ir.Value) !StoreOp {
364             var builder = ir.OperationBuilder.init(ctx);
365             var state = op_specs.state(@This(), loc);
366             state.addOperands(&.{ target, value });
367             const op = try builder.create(state);
368             return .{ .op = op };
369         }
370 
371         pub fn getTarget(self: StoreOp) *ir.Value {
372             return ir.dialects.operand(operation_spec, self.op, "target");
373         }
374 
375         pub fn getValue(self: StoreOp) *ir.Value {
376             return ir.dialects.operand(operation_spec, self.op, "value");
377         }
378     };
379 
380     pub const ReturnOp = struct {
381         op: *ir.Operation,
382 
383         pub const operation_spec = op_specs.leaf(.{
384             .mnemonic = "return",
385             .results = 0,
386         });
387         pub const operation_name = operation_spec.name;
388 
389         pub fn create(
390             ctx: *ir.Context,
391             loc: ir.Location,
392             operands: []const *ir.Value,
393         ) !ReturnOp {
394             var builder = ir.OperationBuilder.init(ctx);
395             var state = op_specs.state(@This(), loc);
396             state.addOperands(operands);
397             const op = try builder.create(state);
398             return .{ .op = op };
399         }
400     };
401 
402     pub fn getI32Type(ctx: *ir.Context) !ir.Type {
403         try registerTestDialect(ctx);
404         return ctx.getDialectTypeFromName(test_i32_type_name);
405     }
406 
407     pub fn getI64Type(ctx: *ir.Context) !ir.Type {
408         try registerTestDialect(ctx);
409         return ctx.getDialectTypeFromName(test_i64_type_name);
410     }
411 
412     pub fn getF64Type(ctx: *ir.Context) !ir.Type {
413         try registerTestDialect(ctx);
414         return ctx.getDialectTypeFromName(test_f64_type_name);
415     }
416 
417     pub fn getUnitAttr(ctx: *ir.Context) !ir.Attribute {
418         return ctx.getDialectAttr(test_unit_attr_name, "");
419     }
420 
421     pub fn getBoolAttr(ctx: *ir.Context, value: bool) !ir.Attribute {
422         const payload = [_]u8{if (value) 1 else 0};
423         return ctx.getDialectAttr(test_bool_attr_name, &payload);
424     }
425 
426     pub fn getBoolValue(attr: ir.Attribute) ?bool {
427         const payload = attrPayload(attr, test_bool_attr_name) orelse return null;
428         if (payload.len != 1) return null;
429         return payload[0] != 0;
430     }
431 
432     pub fn getIntegerAttr(ctx: *ir.Context, value: i64) !ir.Attribute {
433         var buf: [32]u8 = undefined;
434         const payload = try ir.format.intPayload(buf[0..], value);
435         return ctx.getDialectAttr(test_int_attr_name, payload);
436     }
437 
438     pub fn getIntegerValue(attr: ir.Attribute) ?i64 {
439         const payload = attrPayload(attr, test_int_attr_name) orelse return null;
440         return std.fmt.parseInt(i64, payload, 10) catch null;
441     }
442 
443     pub fn getStringAttr(ctx: *ir.Context, value: []const u8) !ir.Attribute {
444         return ctx.getDialectAttr(test_string_attr_name, value);
445     }
446 
447     pub fn getStringValue(attr: ir.Attribute) ?[]const u8 {
448         return attrPayload(attr, test_string_attr_name);
449     }
450 
451     pub fn getTypeAttr(ctx: *ir.Context, typ: ir.Type) !ir.Attribute {
452         var buf: [128]u8 = undefined;
453         const payload = try std.fmt.bufPrint(&buf, "{f}", .{typ});
454         return ctx.getDialectAttr(test_type_attr_name, payload);
455     }
456 
457     fn getFuncSymbolName(op_ptr: *const anyopaque) ?[]const u8 {
458         const op: *const ir.Operation = @ptrCast(@alignCast(op_ptr));
459         if (op.getAttr("sym_name")) |attr| {
460             return getStringValue(attr);
461         }
462         return null;
463     }
464 
465     fn setFuncSymbolName(op_ptr: *const anyopaque, symbol_name: []const u8) anyerror!void {
466         const op: *ir.Operation = @ptrCast(@alignCast(@constCast(op_ptr)));
467         try op.setAttr("sym_name", try getStringAttr(op.getContext(), symbol_name));
468     }
469 
470     fn isFuncDeclaration(_: *const anyopaque) bool {
471         return false;
472     }
473 
474     fn getBranchSuccessorCount(op_ptr: *const anyopaque) usize {
475         const op: *const ir.Operation = @ptrCast(@alignCast(op_ptr));
476         return op.getNumSuccessors();
477     }
478 
479     fn getBranchSuccessor(op_ptr: *const anyopaque, index: usize) ?*ir.Block {
480         const op: *const ir.Operation = @ptrCast(@alignCast(op_ptr));
481         return op.getSuccessor(index);
482     }
483 
484     fn inferBinaryResultTypes(
485         op_ptr: *const anyopaque,
486         allocator: std.mem.Allocator,
487         out_types: *std.ArrayListUnmanaged(ir.Type),
488     ) anyerror!void {
489         const op: *const ir.Operation = @ptrCast(@alignCast(op_ptr));
490         if (op.operands.items.len != 2) return error.InvalidOperandCount;
491         const lhs_type = op.operands.items[0].value.type;
492         const rhs_type = op.operands.items[1].value.type;
493         if (!lhs_type.eql(rhs_type)) return error.OperandTypeMismatch;
494         try out_types.append(allocator, lhs_type);
495     }
496 };
497 
498 fn loadTestDialect(ctx: *ir.Context) !void {
499     try ir.dialects.loadDialectSpec(ctx, TestDialect.spec);
500 }
501 
502 pub fn registerTestDialect(ctx: *ir.Context) !void {
503     ctx.registerDialectLoader(TestDialect.name, loadTestDialect) catch |err| switch (err) {
504         error.DuplicateDialectLoader => {},
505         else => return err,
506     };
507     _ = try ctx.getOrLoadDialect(TestDialect.name);
508 }
509 
510 test "TestDialect spec owns operation interfaces" {
511     const testing = std.testing;
512 
513     var func_symbol = false;
514     var branch_control_flow = false;
515     var binary_side_effect = false;
516     var binary_infer_type = false;
517     var store_side_effect = false;
518     var binary_shape = false;
519     var module_shape = false;
520     var module_isolated = false;
521     var module_no_terminator = false;
522     var module_single_block = false;
523     var func_isolated = false;
524     var func_sym_name_required = false;
525     var constant_value_required = false;
526 
527     for (TestDialect.spec.operations) |op_spec| {
528         if (std.mem.eql(u8, op_spec.name, TestDialect.BinaryOp.operation_name)) {
529             binary_shape = op_spec.shape.operands.allows(2) and
530                 !op_spec.shape.operands.allows(1) and
531                 op_spec.shape.results.allows(1) and
532                 !op_spec.shape.results.allows(0);
533         }
534         if (std.mem.eql(u8, op_spec.name, TestDialect.ModuleOp.operation_name)) {
535             module_shape = op_spec.shape.regions.allows(1) and
536                 !op_spec.shape.regions.allows(0);
537         }
538         for (op_spec.required_attribute_names) |attr_name| {
539             if (std.mem.eql(u8, op_spec.name, TestDialect.FuncOp.operation_name) and std.mem.eql(u8, attr_name, "sym_name")) {
540                 func_sym_name_required = true;
541             }
542             if (std.mem.eql(u8, op_spec.name, TestDialect.ConstantOp.operation_name) and std.mem.eql(u8, attr_name, "value")) {
543                 constant_value_required = true;
544             }
545         }
546         for (op_spec.dynamic_traits) |trait_spec| {
547             if (std.mem.eql(u8, op_spec.name, TestDialect.ModuleOp.operation_name) and trait_spec.id == ir.traits.IsolatedFromAbove.id) {
548                 module_isolated = true;
549             }
550             if (std.mem.eql(u8, op_spec.name, TestDialect.ModuleOp.operation_name) and trait_spec.id == ir.traits.NoTerminator.id) {
551                 module_no_terminator = true;
552             }
553             if (std.mem.eql(u8, op_spec.name, TestDialect.ModuleOp.operation_name) and trait_spec.id == ir.traits.SingleBlock.id) {
554                 module_single_block = true;
555             }
556             if (std.mem.eql(u8, op_spec.name, TestDialect.FuncOp.operation_name) and trait_spec.id == ir.traits.IsolatedFromAbove.id) {
557                 func_isolated = true;
558             }
559         }
560         for (op_spec.interfaces) |entry| {
561             if (std.mem.eql(u8, op_spec.name, TestDialect.FuncOp.operation_name) and entry.id == interfaces.SymbolOpInterface.id) {
562                 func_symbol = true;
563             }
564             if (std.mem.eql(u8, op_spec.name, TestDialect.BranchOp.operation_name) and entry.id == interfaces.ControlFlowInterface.id) {
565                 branch_control_flow = true;
566             }
567             if (std.mem.eql(
568                 u8,
569                 op_spec.name,
570                 TestDialect.BinaryOp.operation_name,
571             ) and entry.id == interfaces.EffectOpInterface.id) {
572                 binary_side_effect = true;
573             }
574             if (std.mem.eql(u8, op_spec.name, TestDialect.BinaryOp.operation_name) and entry.id == interfaces.InferTypeOpInterface.id) {
575                 binary_infer_type = true;
576             }
577             if (std.mem.eql(
578                 u8,
579                 op_spec.name,
580                 TestDialect.StoreOp.operation_name,
581             ) and entry.id == interfaces.EffectOpInterface.id) {
582                 store_side_effect = true;
583             }
584         }
585     }
586 
587     try testing.expect(func_symbol);
588     try testing.expect(branch_control_flow);
589     try testing.expect(!binary_side_effect);
590     try testing.expect(binary_infer_type);
591     try testing.expect(store_side_effect);
592     try testing.expect(binary_shape);
593     try testing.expect(module_shape);
594     try testing.expect(module_isolated);
595     try testing.expect(module_no_terminator);
596     try testing.expect(module_single_block);
597     try testing.expect(func_isolated);
598     try testing.expect(func_sym_name_required);
599     try testing.expect(constant_value_required);
600 }
601 
602 test "TestDialect.ModuleOp creates container with region" {
603     const testing = std.testing;
604     var arena = alloc_arena.Arena.init(std.testing.allocator);
605     defer arena.deinit();
606     const allocator = arena.allocator();
607 
608     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
609     defer ctx.deinit(allocator);
610 
611     const loc = ir.Location.getUnknown();
612     const module = try TestDialect.ModuleOp.create(&ctx, loc);
613 
614     try testing.expectEqualStrings("test.module", module.op.name.name);
615     _ = module.getBody();
616     _ = module.getBodyBlock();
617 }
618 
619 test "TestDialect.FuncOp creates function with symbol name" {
620     const testing = std.testing;
621     var arena = alloc_arena.Arena.init(std.testing.allocator);
622     defer arena.deinit();
623     const allocator = arena.allocator();
624 
625     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
626     defer ctx.deinit(allocator);
627     try registerTestDialect(&ctx);
628 
629     const loc = ir.Location.getUnknown();
630     const i32_type = try TestDialect.getI32Type(&ctx);
631     const func_op = try TestDialect.FuncOp.create(&ctx, loc, "my_func", &.{ i32_type, i32_type });
632 
633     try testing.expectEqualStrings("test.func", func_op.op.name.name);
634     try testing.expectEqualStrings("my_func", func_op.getName().?);
635     try testing.expectEqual(@as(usize, 2), func_op.getArguments().len);
636 
637     const iface = func_op.op.interface(interfaces.SymbolOpInterface).?;
638     try testing.expectEqualStrings("my_func", iface.call(.getSymbolName, .{}).?);
639 }
640 
641 test "TestDialect.ConstantOp creates constant value" {
642     const testing = std.testing;
643     var arena = alloc_arena.Arena.init(std.testing.allocator);
644     defer arena.deinit();
645     const allocator = arena.allocator();
646 
647     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
648     defer ctx.deinit(allocator);
649 
650     const loc = ir.Location.getUnknown();
651     const i64_type = try TestDialect.getI64Type(&ctx);
652     var const_op = try TestDialect.ConstantOp.create(&ctx, loc, i64_type, 42);
653 
654     try testing.expectEqualStrings("test.constant", const_op.op.name.name);
655     try testing.expectEqual(@as(i64, 42), const_op.getValue().?);
656     _ = const_op.getResult();
657 }
658 
659 test "TestDialect.BinaryOp creates operation with operands" {
660     const testing = std.testing;
661     var arena = alloc_arena.Arena.init(std.testing.allocator);
662     defer arena.deinit();
663     const allocator = arena.allocator();
664 
665     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
666     defer ctx.deinit(allocator);
667 
668     const loc = ir.Location.getUnknown();
669     const i64_type = try TestDialect.getI64Type(&ctx);
670 
671     var c1 = try TestDialect.ConstantOp.create(&ctx, loc, i64_type, 10);
672     var c2 = try TestDialect.ConstantOp.create(&ctx, loc, i64_type, 20);
673     const binary = try TestDialect.BinaryOp.create(&ctx, loc, c1.getResult(), c2.getResult());
674 
675     try testing.expectEqualStrings("test.binary", binary.op.name.name);
676     try testing.expect(binary.getLhs() == c1.getResult());
677     try testing.expect(binary.getRhs() == c2.getResult());
678     _ = binary.getResult();
679 }
680 
681 test "TestDialect.BranchOp ControlFlowInterface exposes successors" {
682     const testing = std.testing;
683     var arena = alloc_arena.Arena.init(std.testing.allocator);
684     defer arena.deinit();
685     const allocator = arena.allocator();
686 
687     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
688     defer ctx.deinit(allocator);
689     try registerTestDialect(&ctx);
690 
691     const loc = ir.Location.getUnknown();
692     const module = try TestDialect.ModuleOp.create(&ctx, loc);
693     const region = module.getBody();
694     const entry = module.getBodyBlock();
695     const target = try region.addBlock();
696 
697     var br = try TestDialect.BranchOp.create(&ctx, loc, target);
698     try entry.addOperation(br.op);
699 
700     const iface = br.op.interface(interfaces.ControlFlowInterface).?;
701     try testing.expectEqual(@as(usize, 1), iface.call(.getSuccessorCount, .{}));
702     try testing.expect(iface.call(.getSuccessor, .{0}) == target);
703 }
704 
705 test "TestDialect effects leave binary unknown and report store writes" {
706     const testing = std.testing;
707     var arena = alloc_arena.Arena.init(std.testing.allocator);
708     defer arena.deinit();
709     const allocator = arena.allocator();
710 
711     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
712     defer ctx.deinit(allocator);
713     try registerTestDialect(&ctx);
714 
715     const loc = ir.Location.getUnknown();
716     const i64_type = try TestDialect.getI64Type(&ctx);
717 
718     var c1 = try TestDialect.ConstantOp.create(&ctx, loc, i64_type, 10);
719     var c2 = try TestDialect.ConstantOp.create(&ctx, loc, i64_type, 20);
720     const binary = try TestDialect.BinaryOp.create(&ctx, loc, c1.getResult(), c2.getResult());
721     const store = try TestDialect.StoreOp.create(&ctx, loc, c1.getResult(), c2.getResult());
722 
723     var binary_facts = try effects.inspect(allocator, binary.op);
724     defer binary_facts.deinit(allocator);
725     try testing.expect(!effects.discard(binary_facts.facts));
726     var store_facts = try effects.inspect(allocator, store.op);
727     defer store_facts.deinit(allocator);
728     try testing.expectEqual(effects.EventKind.write, store_facts.facts.records[0].event.kind);
729     try testing.expect(!effects.discard(store_facts.facts));
730 }
731 
732 test "TestDialect.InferTypeOpInterface infers binary result type" {
733     const testing = std.testing;
734     var arena = alloc_arena.Arena.init(std.testing.allocator);
735     defer arena.deinit();
736     const allocator = arena.allocator();
737 
738     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
739     defer ctx.deinit(allocator);
740     try registerTestDialect(&ctx);
741 
742     const loc = ir.Location.getUnknown();
743     const i64_type = try TestDialect.getI64Type(&ctx);
744 
745     var c1 = try TestDialect.ConstantOp.create(&ctx, loc, i64_type, 1);
746     var c2 = try TestDialect.ConstantOp.create(&ctx, loc, i64_type, 2);
747     const binary = try TestDialect.BinaryOp.create(&ctx, loc, c1.getResult(), c2.getResult());
748 
749     const iface = binary.op.interface(interfaces.InferTypeOpInterface).?;
750     var inferred: std.ArrayListUnmanaged(ir.Type) = .empty;
751     defer inferred.deinit(allocator);
752 
753     try iface.call(.inferResultTypes, .{ allocator, &inferred });
754     try testing.expectEqual(@as(usize, 1), inferred.items.len);
755     try testing.expect(inferred.items[0].eql(i64_type));
756 }
757 
758 fn constantEffects(op: *const ir.Operation, collector: *effects.Collector) void {
759     collector.valueResults(op);
760     if (op.getNumResults() != 1 or op.getNumOperands() != 0) return;
761     const value = op.getAttr("value") orelse return;
762     collector.complete = TestDialect.getIntegerValue(value) != null;
763 }