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(®ions);
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(®ions);
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 }