lib/choir/src/core/test.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const alloc_phase = @import("alloc_phase");
2 const alloc_arena = @import("alloc_arena");
3 const std = @import("std");
4 const core = @import("root.zig");
5 const dialects = @import("../dialects/root.zig");
6 const context_test = @import("context/test.zig");
7 const dialects_test = @import("dialects/test.zig");
8 const interfaces_test = @import("interfaces/test.zig");
9
10 const Attribute = core.Attribute;
11 const Block = core.Block;
12 const Context = core.Context;
13 const interfaces = core.interfaces;
14 const Location = core.Location;
15 const Mapping = core.Mapping;
16 const NamedAttribute = core.NamedAttribute;
17 const Operation = core.Operation;
18 const Region = core.Region;
19 const Type = core.Type;
20 const Value = core.Value;
21
22 test {
23 _ = @import("rewrite/test.zig");
24 std.testing.refAllDecls(core);
25 _ = context_test;
26 _ = dialects_test;
27 _ = interfaces_test;
28 }
29
30 test "Choir parse round-trips dumped module operations" {
31 const testing = std.testing;
32
33 var arena = alloc_arena.Arena.init(testing.allocator);
34 defer arena.deinit();
35 const allocator = arena.allocator();
36
37 var ctx = try core.Context.init(allocator, core.Context.Limits.testing);
38 defer ctx.deinit(allocator);
39 try dialects.registerChoirDialect(&ctx);
40 _ = try ctx.getOrLoadDialect("builtin");
41 _ = try ctx.getOrLoadDialect("arith");
42 _ = try ctx.getOrLoadDialect("func");
43
44 const loc = core.Location.getUnknown();
45 const module = try dialects.BuiltinDialect.ModuleOp.create(&ctx, loc);
46 const body = module.getBodyBlock();
47 const i32_type = try dialects.ArithDialect.getScalarType(&ctx, .i32);
48 var func = try dialects.FuncDialect.FuncOp.create(
49 &ctx,
50 loc,
51 "parse_round_trip",
52 &.{i32_type},
53 &.{i32_type},
54 );
55 try body.addOperation(func.op);
56 const entry = func.getEntryBlock();
57 const ret = try dialects.FuncDialect.ReturnOp.create(
58 &ctx,
59 loc,
60 &.{func.getArgument(0)},
61 );
62 try entry.addOperation(ret.op);
63
64 const text = try core.dump.operationAlloc(allocator, module.op);
65 const parsed = try core.parse.operation(&ctx, text);
66 defer parsed.erase();
67
68 const reparsed = try core.dump.operationAlloc(allocator, parsed);
69 try testing.expectEqualStrings(text, reparsed);
70 }
71
72 test "Choir parse round-trips atomic memory operations and their orderings" {
73 const testing = std.testing;
74 const memref = dialects.MemrefDialect;
75
76 var arena = alloc_arena.Arena.init(testing.allocator);
77 defer arena.deinit();
78 const allocator = arena.allocator();
79
80 var ctx = try core.Context.init(allocator, core.Context.Limits.testing);
81 defer ctx.deinit(allocator);
82 try dialects.registerChoirDialect(&ctx);
83 _ = try ctx.getOrLoadDialect("builtin");
84 _ = try ctx.getOrLoadDialect("arith");
85 _ = try ctx.getOrLoadDialect("func");
86 _ = try ctx.getOrLoadDialect("memref");
87
88 const loc = core.Location.getUnknown();
89 const module = try dialects.BuiltinDialect.ModuleOp.create(&ctx, loc);
90 const body = module.getBodyBlock();
91 const i64_type = try dialects.ArithDialect.getScalarType(&ctx, .i64);
92 const i32_type = try dialects.ArithDialect.getScalarType(&ctx, .i32);
93 const index_type = try dialects.ArithDialect.getIndexType(&ctx);
94 const words = try memref.getMemrefType1D(&ctx, 4, i64_type, .host);
95 const halves = try memref.getMemrefType1D(&ctx, 4, i32_type, .host);
96 var func = try dialects.FuncDialect.FuncOp.create(
97 &ctx,
98 loc,
99 "atomic_round_trip",
100 &.{ words, halves, index_type, i64_type, i32_type },
101 &.{i64_type},
102 );
103 try body.addOperation(func.op);
104 const entry = func.getEntryBlock();
105 const word_buffer = func.getArgument(0);
106 const half_buffer = func.getArgument(1);
107 const slot = func.getArgument(2);
108 const word = func.getArgument(3);
109 const half = func.getArgument(4);
110
111 const loaded = try memref.AtomicLoadOp.create(&ctx, loc, word_buffer, slot, i64_type, .acquire);
112 try entry.addOperation(loaded.op);
113 const released = try memref.AtomicStoreOp.create(&ctx, loc, word, word_buffer, slot, .release);
114 try entry.addOperation(released.op);
115 const published = try memref.AtomicStoreOp.create(&ctx, loc, half, half_buffer, slot, .seq_cst);
116 try entry.addOperation(published.op);
117 const exchanged = try memref.AtomicCasOp.createOrdered(
118 &ctx,
119 loc,
120 loaded.getResult(),
121 word,
122 word_buffer,
123 slot,
124 i64_type,
125 .acq_rel,
126 );
127 try entry.addOperation(exchanged.op);
128 const unordered = try memref.AtomicCasOp.create(&ctx, loc, half, half, half_buffer, slot, i32_type);
129 try entry.addOperation(unordered.op);
130 const fence = try memref.FenceOp.create(&ctx, loc, .system, .seq_cst);
131 try entry.addOperation(fence.op);
132 const ret = try dialects.FuncDialect.ReturnOp.create(&ctx, loc, &.{exchanged.getResult()});
133 try entry.addOperation(ret.op);
134
135 const text = try core.dump.operationAlloc(allocator, module.op);
136 try testing.expect(std.mem.indexOf(u8, text, "memref.atomic_load") != null);
137 try testing.expect(std.mem.indexOf(u8, text, "memref.atomic_store") != null);
138 try testing.expect(std.mem.indexOf(u8, text, "memref.atomic_cas") != null);
139 try testing.expect(std.mem.indexOf(u8, text, "memref.fence") != null);
140 try testing.expect(std.mem.indexOf(u8, text, "\"acq_rel\"") != null);
141
142 const parsed = try core.parse.operation(&ctx, text);
143 defer parsed.erase();
144 const reparsed = try core.dump.operationAlloc(allocator, parsed);
145 try testing.expectEqualStrings(text, reparsed);
146
147 var module_ops = parsed.getRegion(0).?.getEntryBlock().?.getOperations();
148 const parsed_func = module_ops.next().?;
149 var func_ops = parsed_func.getRegion(0).?.getEntryBlock().?.getOperations();
150 var orderings: [4]dialects.FenceOrdering = undefined;
151 var ordering_count: usize = 0;
152 while (func_ops.next()) |op| {
153 if (std.mem.eql(u8, op.name.name, "memref.atomic_load")) {
154 orderings[ordering_count] = (memref.AtomicLoadOp{ .op = op }).getOrdering().?;
155 ordering_count += 1;
156 }
157 if (std.mem.eql(u8, op.name.name, "memref.atomic_store")) {
158 orderings[ordering_count] = (memref.AtomicStoreOp{ .op = op }).getOrdering().?;
159 ordering_count += 1;
160 }
161 if (std.mem.eql(u8, op.name.name, "memref.atomic_cas")) {
162 if (ordering_count == 3) {
163 orderings[ordering_count] = (memref.AtomicCasOp{ .op = op }).getOrdering();
164 ordering_count += 1;
165 } else {
166 try testing.expectEqual(dialects.FenceOrdering.seq_cst, (memref.AtomicCasOp{ .op = op }).getOrdering());
167 }
168 }
169 }
170 try testing.expectEqual(@as(usize, 4), ordering_count);
171 try testing.expectEqualSlices(
172 dialects.FenceOrdering,
173 &.{ .acquire, .release, .seq_cst, .acq_rel },
174 &orderings,
175 );
176 }
177
178 test "Choir parse round-trips list attributes" {
179 const testing = std.testing;
180
181 var arena = alloc_arena.Arena.init(testing.allocator);
182 defer arena.deinit();
183 const allocator = arena.allocator();
184
185 var ctx = try core.Context.init(allocator, core.Context.Limits.testing);
186 defer ctx.deinit(allocator);
187 try ctx.allowUnregistered();
188 _ = try dialects.ArithDialect.getScalarType(&ctx, .i32);
189 _ = try dialects.ArithDialect.getScalarType(&ctx, .f32);
190
191 const text =
192 \\test.attrs() {array = [true, [false, 4:i32], "tail"], empty = [], strings = ["alpha", "beta"], types = [!arith.i32, !arith.f32]}
193 \\
194 ;
195
196 const parsed = try core.parse.operation(&ctx, text);
197 defer parsed.erase();
198
199 const reparsed = try core.dump.operationAlloc(allocator, parsed);
200 try testing.expectEqualStrings(text, reparsed);
201 }
202
203 const WalkNameRecorder = struct {
204 allocator: std.mem.Allocator,
205 names: std.ArrayList([]const u8) = .empty,
206 skip_name: ?[]const u8 = null,
207 interrupt_name: ?[]const u8 = null,
208
209 fn deinit(self: *WalkNameRecorder) void {
210 self.names.deinit(self.allocator);
211 }
212
213 fn record(self: *WalkNameRecorder, op: *Operation) !Operation.WalkResult {
214 try self.names.append(self.allocator, op.name.name);
215 if (self.interrupt_name) |name| {
216 if (std.mem.eql(u8, name, op.name.name)) return .interrupt;
217 }
218 if (self.skip_name) |name| {
219 if (std.mem.eql(u8, name, op.name.name)) return .skip;
220 }
221 return .advance;
222 }
223 };
224
225 fn expectWalkNames(actual: []const []const u8, expected: []const []const u8) !void {
226 try std.testing.expectEqual(expected.len, actual.len);
227 for (expected, actual) |expected_name, actual_name| {
228 try std.testing.expectEqualStrings(expected_name, actual_name);
229 }
230 }
231
232 test "use-def chain basic operations" {
233 const testing = std.testing;
234
235 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
236 defer ctx.deinit(testing.allocator);
237 try ctx.allowUnregistered();
238
239 const unknown_type = try ctx.getDialectTypeFromName("test.ty");
240 var producer_state = Operation.State.init("test.producer", .unknown);
241 producer_state.addTypes(&.{unknown_type});
242 const producer = try ctx.createOperation(producer_state);
243
244 const result = producer.getResult(0).?;
245 try testing.expect(result.hasNoUses());
246 try testing.expect(producer.hasNoUses());
247 try testing.expectEqual(@as(usize, 0), result.getNumUses());
248
249 var consumer_state = Operation.State.init("test.consumer", .unknown);
250 consumer_state.addOperands(&.{result});
251 _ = try ctx.createOperation(consumer_state);
252
253 try testing.expect(!result.hasNoUses());
254 try testing.expect(result.hasOneUse());
255 try testing.expect(!producer.hasNoUses());
256 try testing.expectEqual(@as(usize, 1), result.getNumUses());
257
258 var consumer2_state = Operation.State.init("test.consumer2", .unknown);
259 consumer2_state.addOperands(&.{result});
260 _ = try ctx.createOperation(consumer2_state);
261
262 try testing.expect(!result.hasNoUses());
263 try testing.expect(!result.hasOneUse());
264 try testing.expectEqual(@as(usize, 2), result.getNumUses());
265 }
266
267 test "use-def chain drop uses on erase" {
268 const testing = std.testing;
269
270 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
271 defer ctx.deinit(testing.allocator);
272 try ctx.allowUnregistered();
273
274 const unknown_type = try ctx.getDialectTypeFromName("test.ty");
275 var producer_state = Operation.State.init("test.producer", .unknown);
276 producer_state.addTypes(&.{unknown_type});
277 const producer = try ctx.createOperation(producer_state);
278
279 const result = producer.getResult(0).?;
280
281 {
282 var consumer_state = Operation.State.init("test.consumer", .unknown);
283 consumer_state.addOperands(&.{result});
284 const consumer = try ctx.createOperation(consumer_state);
285
286 try testing.expectEqual(@as(usize, 1), result.getNumUses());
287
288 consumer.erase();
289 }
290
291 try testing.expect(result.hasNoUses());
292 try testing.expectEqual(@as(usize, 0), result.getNumUses());
293 }
294
295 test "operation hasNoUses with zero results" {
296 const testing = std.testing;
297
298 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
299 defer ctx.deinit(testing.allocator);
300 try ctx.allowUnregistered();
301
302 const state = Operation.State.init("test.void_op", .unknown);
303 const op = try ctx.createOperation(state);
304
305 try testing.expect(op.hasNoUses());
306 }
307
308 test "operation walk orders and controls nested traversal" {
309 const testing = std.testing;
310
311 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
312 defer ctx.deinit(testing.allocator);
313 try ctx.allowUnregistered();
314
315 var child_region = core.context.initRegion(&ctx);
316 defer child_region.deinit();
317 const child_block = try child_region.addBlock();
318 const grandchild = try ctx.createOperation(Operation.State.init("test.walk.grandchild", .unknown));
319 try child_block.addOperation(grandchild);
320
321 var parent_state = Operation.State.init("test.walk.parent", .unknown);
322 parent_state.addRegionBodies(&.{&child_region});
323 const parent = try ctx.createOperation(parent_state);
324
325 var root_region = core.context.initRegion(&ctx);
326 defer root_region.deinit();
327 const root_block = try root_region.addBlock();
328 const first = try ctx.createOperation(Operation.State.init("test.walk.first", .unknown));
329 const last = try ctx.createOperation(Operation.State.init("test.walk.last", .unknown));
330 try root_block.addOperation(first);
331 try root_block.addOperation(parent);
332 try root_block.addOperation(last);
333
334 var root_state = Operation.State.init("test.walk.root", .unknown);
335 root_state.addRegionBodies(&.{&root_region});
336 const root = try ctx.createOperation(root_state);
337
338 var pre_order = WalkNameRecorder{ .allocator = testing.allocator };
339 defer pre_order.deinit();
340 try testing.expectEqual(Operation.WalkResult.advance, try root.walk(.{ .order = .pre_order }, &pre_order, WalkNameRecorder.record));
341 try expectWalkNames(pre_order.names.items, &.{
342 "test.walk.root",
343 "test.walk.first",
344 "test.walk.parent",
345 "test.walk.grandchild",
346 "test.walk.last",
347 });
348
349 var post_order = WalkNameRecorder{ .allocator = testing.allocator };
350 defer post_order.deinit();
351 try testing.expectEqual(Operation.WalkResult.advance, try root.walk(.{ .order = .post_order }, &post_order, WalkNameRecorder.record));
352 try expectWalkNames(post_order.names.items, &.{
353 "test.walk.first",
354 "test.walk.grandchild",
355 "test.walk.parent",
356 "test.walk.last",
357 "test.walk.root",
358 });
359
360 var skip_parent = WalkNameRecorder{
361 .allocator = testing.allocator,
362 .skip_name = "test.walk.parent",
363 };
364 defer skip_parent.deinit();
365 try testing.expectEqual(Operation.WalkResult.advance, try root.walk(.{ .order = .pre_order }, &skip_parent, WalkNameRecorder.record));
366 try expectWalkNames(skip_parent.names.items, &.{
367 "test.walk.root",
368 "test.walk.first",
369 "test.walk.parent",
370 "test.walk.last",
371 });
372
373 var interrupt_parent = WalkNameRecorder{
374 .allocator = testing.allocator,
375 .interrupt_name = "test.walk.parent",
376 };
377 defer interrupt_parent.deinit();
378 try testing.expectEqual(Operation.WalkResult.interrupt, try root.walk(.{ .order = .pre_order }, &interrupt_parent, WalkNameRecorder.record));
379 try expectWalkNames(interrupt_parent.names.items, &.{
380 "test.walk.root",
381 "test.walk.first",
382 "test.walk.parent",
383 });
384 }
385
386 test "operation walk accepts void callbacks" {
387 const testing = std.testing;
388
389 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
390 defer ctx.deinit(testing.allocator);
391 try ctx.allowUnregistered();
392
393 var body = core.context.initRegion(&ctx);
394 defer body.deinit();
395 const block = try body.addBlock();
396 const child = try ctx.createOperation(Operation.State.init("test.walk.child", .unknown));
397 try block.addOperation(child);
398
399 var state = Operation.State.init("test.walk.void_root", .unknown);
400 state.addRegionBodies(&.{&body});
401 const root = try ctx.createOperation(state);
402
403 const Counter = struct {
404 count: usize = 0,
405
406 fn visit(self: *@This(), op: *Operation) void {
407 _ = op;
408 self.count += 1;
409 }
410 };
411
412 var counter = Counter{};
413 try testing.expectEqual(Operation.WalkResult.advance, try root.walk(.{ .order = .pre_order }, &counter, Counter.visit));
414 try testing.expectEqual(@as(usize, 2), counter.count);
415 }
416
417 test "operation exposes parent and ancestry navigation" {
418 const testing = std.testing;
419
420 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
421 defer ctx.deinit(testing.allocator);
422 try ctx.allowUnregistered();
423
424 var child_body = core.context.initRegion(&ctx);
425 defer child_body.deinit();
426 const child_block = try child_body.addBlock();
427 const first = try ctx.createOperation(Operation.State.init("test.nav.first", .unknown));
428 const second = try ctx.createOperation(Operation.State.init("test.nav.second", .unknown));
429 try child_block.addOperation(first);
430 try child_block.addOperation(second);
431
432 var parent_state = Operation.State.init("test.nav.parent", .unknown);
433 parent_state.addRegionBodies(&.{&child_body});
434 const parent = try ctx.createOperation(parent_state);
435 const parent_region = parent.getRegion(0).?;
436 const parent_block = parent_region.getEntryBlock().?;
437
438 var root_body = core.context.initRegion(&ctx);
439 defer root_body.deinit();
440 const root_block = try root_body.addBlock();
441 const sibling = try ctx.createOperation(Operation.State.init("test.nav.sibling", .unknown));
442 try root_block.addOperation(parent);
443 try root_block.addOperation(sibling);
444
445 var root_state = Operation.State.init("test.nav.root", .unknown);
446 root_state.addRegionBodies(&.{&root_body});
447 const root = try ctx.createOperation(root_state);
448 const root_region = root.getRegion(0).?;
449
450 try testing.expect(parent.getParentRegion() == root_region);
451 try testing.expect(parent.getParentOp() == root);
452 try testing.expect(root_block.getParentRegion() == root_region);
453 try testing.expect(root_block.getParentOperation() == root);
454 try testing.expect(first.getParentRegion() == parent_region);
455 try testing.expect(first.getParentOp() == parent);
456 try testing.expect(parent_block.getParentOperation() == parent);
457
458 try testing.expect(root.isAncestor(root));
459 try testing.expect(root.isProperAncestor(parent));
460 try testing.expect(root.isProperAncestor(first));
461 try testing.expect(parent.isProperAncestor(first));
462 try testing.expect(!parent.isProperAncestor(parent));
463 try testing.expect(!first.isProperAncestor(parent));
464
465 try testing.expect(parent.isBeforeInBlock(sibling));
466 try testing.expect(first.isBeforeInBlock(second));
467 try testing.expect(!second.isBeforeInBlock(first));
468 try testing.expect(!first.isBeforeInBlock(parent));
469 }
470
471 test "operation defined value use query includes nested regions" {
472 const testing = std.testing;
473
474 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
475 defer ctx.deinit(testing.allocator);
476 try ctx.allowUnregistered();
477
478 const value_type = try ctx.getDialectTypeFromName("test.value");
479
480 var body = core.context.initRegion(&ctx);
481 defer body.deinit();
482 const body_block = try body.addBlock();
483 const body_arg = try body_block.addArgument(value_type, .unknown);
484
485 var parent_state = Operation.State.init("test.parent", .unknown);
486 parent_state.addRegionBodies(&.{&body});
487 const parent = try ctx.createOperation(parent_state);
488 const entry = parent.getRegion(0).?.getEntryBlock().?;
489
490 var nested_state = Operation.State.init("test.nested_producer", .unknown);
491 nested_state.addTypes(&.{value_type});
492 const nested = try ctx.createOperation(nested_state);
493 try entry.addOperation(nested);
494
495 var user_state = Operation.State.init("test.user", .unknown);
496 user_state.addOperands(&.{ body_arg, nested.getResult(0).? });
497 const user = try ctx.createOperation(user_state);
498
499 try testing.expect(!parent.hasNoDefinedValueUses());
500 try testing.expect(!parent.getRegion(0).?.hasNoDefinedValueUses());
501 try testing.expect(!entry.hasNoDefinedValueUses());
502
503 user.dropAllReferences();
504
505 try testing.expect(parent.hasNoDefinedValueUses());
506 try testing.expect(parent.getRegion(0).?.hasNoDefinedValueUses());
507 try testing.expect(entry.hasNoDefinedValueUses());
508 }
509
510 test "operation dropAllDefinedValueUses includes nested regions" {
511 const testing = std.testing;
512
513 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
514 defer ctx.deinit(testing.allocator);
515 try ctx.allowUnregistered();
516
517 const value_type = try ctx.getDialectTypeFromName("test.value");
518
519 var body = core.context.initRegion(&ctx);
520 defer body.deinit();
521 const body_block = try body.addBlock();
522 const body_arg = try body_block.addArgument(value_type, .unknown);
523
524 var parent_state = Operation.State.init("test.parent", .unknown);
525 parent_state.addRegionBodies(&.{&body});
526 const parent = try ctx.createOperation(parent_state);
527 const entry = parent.getRegion(0).?.getEntryBlock().?;
528
529 var nested_state = Operation.State.init("test.nested_producer", .unknown);
530 nested_state.addTypes(&.{value_type});
531 const nested = try ctx.createOperation(nested_state);
532 try entry.addOperation(nested);
533 const nested_result = nested.getResult(0).?;
534
535 var user_state = Operation.State.init("test.user", .unknown);
536 user_state.addOperands(&.{ body_arg, nested_result });
537 const user = try ctx.createOperation(user_state);
538
539 try testing.expectEqual(@as(usize, 1), body_arg.getNumUses());
540 try testing.expectEqual(@as(usize, 1), nested_result.getNumUses());
541
542 parent.dropAllDefinedValueUses();
543
544 try testing.expect(parent.hasNoDefinedValueUses());
545 try testing.expect(body_arg.hasNoUses());
546 try testing.expect(nested_result.hasNoUses());
547 try testing.expectEqual(body_arg, user.getOperand(0).?);
548 try testing.expectEqual(nested_result, user.getOperand(1).?);
549
550 user.erase();
551 }
552
553 test "operation dropAllReferences recurses through nested operations" {
554 const testing = std.testing;
555
556 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
557 defer ctx.deinit(testing.allocator);
558 try ctx.allowUnregistered();
559
560 const value_type = try ctx.getDialectTypeFromName("test.value");
561 var external_state = Operation.State.init("test.external_producer", .unknown);
562 external_state.addTypes(&.{value_type});
563 const external = try ctx.createOperation(external_state);
564 const external_result = external.getResult(0).?;
565
566 var body = core.context.initRegion(&ctx);
567 defer body.deinit();
568 _ = try body.addBlock();
569
570 var parent_state = Operation.State.init("test.parent", .unknown);
571 parent_state.addRegionBodies(&.{&body});
572 const parent = try ctx.createOperation(parent_state);
573 const entry = parent.getRegion(0).?.getEntryBlock().?;
574
575 var nested_state = Operation.State.init("test.nested_consumer", .unknown);
576 nested_state.addOperands(&.{external_result});
577 const nested = try ctx.createOperation(nested_state);
578 try entry.addOperation(nested);
579
580 try testing.expectEqual(@as(usize, 1), external_result.getNumUses());
581
582 parent.dropAllReferences();
583
584 try testing.expect(external_result.hasNoUses());
585 try testing.expectEqual(@as(usize, 0), external_result.getNumUses());
586 }
587
588 test "operation dropAllReferences clears successor predecessors" {
589 const testing = std.testing;
590
591 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
592 defer ctx.deinit(testing.allocator);
593 try ctx.allowUnregistered();
594
595 var pred = Block.init(testing.allocator);
596 defer pred.deinit();
597 var succ = Block.init(testing.allocator);
598 defer succ.deinit();
599
600 var state = Operation.State.init("test.branch", .unknown);
601 state.addSuccessors(&.{&succ});
602 const op = try ctx.createOperation(state);
603 try pred.addOperation(op);
604
605 try testing.expectEqual(@as(usize, 1), op.getNumSuccessors());
606 try testing.expect(succ.hasPredecessor(&pred));
607
608 op.dropAllReferences();
609
610 try testing.expectEqual(@as(usize, 0), op.getNumSuccessors());
611 try testing.expect(!succ.hasPredecessor(&pred));
612 }
613
614 test "operation erase owns nested context operations" {
615 const testing = std.testing;
616
617 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
618 defer ctx.deinit(testing.allocator);
619 try ctx.allowUnregistered();
620
621 const value_type = try ctx.getDialectTypeFromName("test.value");
622
623 var body = core.context.initRegion(&ctx);
624 defer body.deinit();
625 _ = try body.addBlock();
626
627 var parent_state = Operation.State.init("test.parent", .unknown);
628 parent_state.addRegionBodies(&.{&body});
629 const parent = try ctx.createOperation(parent_state);
630 const entry = parent.getRegion(0).?.getEntryBlock().?;
631
632 var producer_state = Operation.State.init("test.producer", .unknown);
633 producer_state.addTypes(&.{value_type});
634 const producer = try ctx.createOperation(producer_state);
635 try entry.addOperation(producer);
636
637 var consumer_state = Operation.State.init("test.consumer", .unknown);
638 consumer_state.addOperands(&.{producer.getResult(0).?});
639 const consumer = try ctx.createOperation(consumer_state);
640 try entry.addOperation(consumer);
641
642 try testing.expectEqual(@as(usize, 3), ctx.operationCount());
643
644 parent.erase();
645
646 try testing.expectEqual(@as(usize, 0), ctx.operationCount());
647 }
648
649 test "region eraseBlock removes predecessor-free blocks in reverse operation order" {
650 const testing = std.testing;
651
652 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
653 var region = core.context.initRegion(&ctx);
654 defer {
655 region.deinit();
656 ctx.deinit(testing.allocator);
657 }
658 try ctx.allowUnregistered();
659
660 const entry = try region.addBlock();
661 const with_predecessor = try region.addBlock();
662 const with_defined_uses = try region.addBlock();
663 const removable = try region.addBlock();
664 var detached = Block.init(testing.allocator);
665 defer detached.deinit();
666
667 try testing.expectEqual(@as(usize, 4), region.blocks.size);
668 const value_type = try ctx.getDialectTypeFromName("test.value");
669 const block_arg = try with_defined_uses.addArgument(value_type, .unknown);
670 var producer_state = Operation.State.init("test.producer", .unknown);
671 producer_state.addTypes(&.{value_type});
672 const producer = try ctx.createOperation(producer_state);
673 try with_defined_uses.addOperation(producer);
674 var consumer_state = Operation.State.init("test.consumer", .unknown);
675 consumer_state.addOperands(&.{ block_arg, producer.getResult(0).? });
676 const consumer = try ctx.createOperation(consumer_state);
677 try with_defined_uses.addOperation(consumer);
678 var branch_state = Operation.State.init("test.branch", .unknown);
679 branch_state.addSuccessors(&.{with_predecessor});
680 const branch = try ctx.createOperation(branch_state);
681 try entry.addOperation(branch);
682
683 try testing.expect(!region.eraseBlock(&detached));
684 try testing.expect(!region.eraseBlock(with_predecessor));
685 try testing.expect(region.eraseBlock(with_defined_uses));
686 try testing.expect(region.eraseBlock(removable));
687
688 try testing.expectEqual(@as(usize, 2), region.blocks.size);
689 try testing.expect(region.blocks.head == entry);
690 try testing.expect(entry.next == with_predecessor);
691 try testing.expect(with_predecessor.prev == entry);
692 try testing.expect(region.blocks.tail == with_predecessor);
693 branch.erase();
694 try testing.expectEqual(@as(usize, 0), ctx.operationCount());
695 }
696
697 test "use-def chain multiple operands same value" {
698 const testing = std.testing;
699
700 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
701 defer ctx.deinit(testing.allocator);
702 try ctx.allowUnregistered();
703
704 const unknown_type = try ctx.getDialectTypeFromName("test.ty");
705 var producer_state = Operation.State.init("test.producer", .unknown);
706 producer_state.addTypes(&.{unknown_type});
707 const producer = try ctx.createOperation(producer_state);
708
709 const result = producer.getResult(0).?;
710
711 var consumer_state = Operation.State.init("test.binary_op", .unknown);
712 consumer_state.addOperands(&.{ result, result });
713 _ = try ctx.createOperation(consumer_state);
714
715 try testing.expectEqual(@as(usize, 2), result.getNumUses());
716 }
717
718 test "operation caches operand values and result types" {
719 const testing = std.testing;
720
721 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
722 defer ctx.deinit(testing.allocator);
723 try ctx.allowUnregistered();
724
725 const unknown_type = try ctx.getDialectTypeFromName("test.ty");
726
727 var producer_a_state = Operation.State.init("test.producer_a", .unknown);
728 producer_a_state.addTypes(&.{unknown_type});
729 const producer_a = try ctx.createOperation(producer_a_state);
730 const a_result = producer_a.getResult(0).?;
731
732 var producer_b_state = Operation.State.init("test.producer_b", .unknown);
733 producer_b_state.addTypes(&.{unknown_type});
734 const producer_b = try ctx.createOperation(producer_b_state);
735 const b_result = producer_b.getResult(0).?;
736
737 var consumer_state = Operation.State.init("test.consumer", .unknown);
738 consumer_state.addOperands(&.{ a_result, b_result });
739 consumer_state.addTypes(&.{unknown_type});
740 const consumer = try ctx.createOperation(consumer_state);
741
742 const cached_operands = consumer.getOperandValues();
743 try testing.expectEqual(@as(usize, 2), cached_operands.len);
744 try testing.expectEqual(a_result, cached_operands[0]);
745 try testing.expectEqual(b_result, cached_operands[1]);
746 try testing.expectEqual(a_result, consumer.getOperand(0).?);
747 try testing.expectEqual(b_result, consumer.getOperand(1).?);
748
749 const cached_result_types = consumer.getResultTypes();
750 try testing.expectEqual(@as(usize, 1), cached_result_types.len);
751 try testing.expectEqual(unknown_type, cached_result_types[0]);
752 try testing.expectEqual(@as(usize, 1), consumer.getNumResults());
753 }
754
755 test "operation creation consumes only Context-reserved storage" {
756 comptime {
757 @stardustClaim(
758 @import("alloc_phase").capacity.witness(@import("./operation/root.zig").OperationFixedStorage, "choir_operation_storage_integration"),
759 null,
760 null,
761 null,
762 null,
763 null,
764 null,
765 );
766 }
767
768 const testing = std.testing;
769
770 var failing = testing.FailingAllocator.init(testing.allocator, .{});
771 var ctx = try Context.init(failing.allocator(), Context.Limits.testing);
772 defer ctx.deinit(failing.allocator());
773 try ctx.allowUnregistered();
774 _ = try ctx.registerOperation("test.operation.storage", .{});
775 try ctx.registerOperationPropertiesModel(
776 "test.operation.storage",
777 interfaces.singleAttributePropertiesModel("test.operation.storage.properties", "value"),
778 );
779
780 const storage = Type.DialectTypeStorage{
781 .name = "test.operation.storage",
782 .param_key = "",
783 .type_info = null,
784 .print_fn = null,
785 .unique_id = 1,
786 };
787 const ty = Type{ .type_id = .dialect_type, .impl = &storage };
788 var owner: u8 = 0;
789 var operand = Value{
790 .kind = .{ .block_argument = .{ .owner = &owner, .arg_number = 0 } },
791 .type = ty,
792 .id = 1,
793 };
794 var successor = Block.init(failing.allocator());
795 defer successor.deinit();
796
797 var state = Operation.State.init("test.operation.storage", .unknown);
798 state.addOperands(&.{&operand});
799 state.addTypes(&.{ty});
800 state.addRegion();
801 state.addSuccessors(&.{&successor});
802
803 const before = failing.alloc_index;
804 const op = try Operation.create(&ctx, state);
805 try testing.expectEqual(before, failing.alloc_index);
806 try testing.expectEqual(alloc_phase.capacity.Phase.steady, op.storage.status());
807 try testing.expectEqual(@as(usize, 1), op.getNumOperands());
808 try testing.expectEqual(@as(usize, 1), op.getNumResults());
809 try testing.expectEqual(@as(usize, 1), op.getNumRegions());
810 try testing.expectEqual(@as(usize, 1), op.getNumSuccessors());
811 try testing.expect(op.getPropertiesRef() != null);
812 op.destroy();
813 }
814
815 test "common operation storage allocator retries and reuses exact classes" {
816 const testing = std.testing;
817 var failing = testing.FailingAllocator.init(testing.allocator, .{ .fail_index = 0 });
818 var pool = Operation.StorageAllocator.init(failing.allocator());
819 defer pool.deinit();
820 const allocator = pool.allocator();
821
822 try testing.expectEqual(
823 @as(?[*]u8, null),
824 allocator.rawAlloc(672, .@"8", @returnAddress()),
825 );
826 failing.fail_index = std.math.maxInt(usize);
827 const first = allocator.rawAlloc(
828 672,
829 .@"8",
830 @returnAddress(),
831 ) orelse return error.OutOfMemory;
832 allocator.rawFree(first[0..672], .@"8", @returnAddress());
833
834 const first_allocations = failing.alloc_index;
835 failing.fail_index = first_allocations;
836 var first_chunk: [Operation.StorageAllocator.items_per_chunk][*]u8 = undefined;
837 for (&first_chunk) |*slot| {
838 slot.* = allocator.rawAlloc(
839 672,
840 .@"8",
841 @returnAddress(),
842 ) orelse return error.OutOfMemory;
843 }
844 try testing.expectEqual(first_allocations, failing.alloc_index);
845 try testing.expectEqual(@intFromPtr(first), @intFromPtr(first_chunk[0]));
846 try testing.expectEqual(
847 @as(?[*]u8, null),
848 allocator.rawAlloc(672, .@"8", @returnAddress()),
849 );
850
851 failing.fail_index = std.math.maxInt(usize);
852 const first_overflow = allocator.rawAlloc(
853 672,
854 .@"8",
855 @returnAddress(),
856 ) orelse return error.OutOfMemory;
857 for (first_chunk) |slot| allocator.rawFree(slot[0..672], .@"8", @returnAddress());
858 allocator.rawFree(first_overflow[0..672], .@"8", @returnAddress());
859
860 failing.fail_index = failing.alloc_index;
861 try testing.expectEqual(
862 @as(?[*]u8, null),
863 allocator.rawAlloc(752, .@"8", @returnAddress()),
864 );
865 failing.fail_index = std.math.maxInt(usize);
866 const second = allocator.rawAlloc(
867 752,
868 .@"8",
869 @returnAddress(),
870 ) orelse return error.OutOfMemory;
871 allocator.rawFree(second[0..752], .@"8", @returnAddress());
872
873 const second_allocations = failing.alloc_index;
874 failing.fail_index = second_allocations;
875 const second_reused = allocator.rawAlloc(
876 752,
877 .@"8",
878 @returnAddress(),
879 ) orelse return error.OutOfMemory;
880 try testing.expectEqual(second_allocations, failing.alloc_index);
881 try testing.expectEqual(@intFromPtr(second), @intFromPtr(second_reused));
882 allocator.rawFree(second_reused[0..752], .@"8", @returnAddress());
883
884 failing.fail_index = failing.alloc_index;
885 try testing.expectEqual(
886 @as(?[*]u8, null),
887 allocator.rawAlloc(640, .@"8", @returnAddress()),
888 );
889 failing.fail_index = std.math.maxInt(usize);
890 const third = allocator.rawAlloc(
891 640,
892 .@"8",
893 @returnAddress(),
894 ) orelse return error.OutOfMemory;
895 allocator.rawFree(third[0..640], .@"8", @returnAddress());
896
897 const third_allocations = failing.alloc_index;
898 failing.fail_index = third_allocations;
899 const third_reused = allocator.rawAlloc(
900 640,
901 .@"8",
902 @returnAddress(),
903 ) orelse return error.OutOfMemory;
904 try testing.expectEqual(third_allocations, failing.alloc_index);
905 try testing.expectEqual(@intFromPtr(third), @intFromPtr(third_reused));
906 allocator.rawFree(third_reused[0..640], .@"8", @returnAddress());
907
908 failing.fail_index = failing.alloc_index;
909 try testing.expectEqual(
910 @as(?[*]u8, null),
911 allocator.rawAlloc(696, .@"8", @returnAddress()),
912 );
913 failing.fail_index = std.math.maxInt(usize);
914 const fourth = allocator.rawAlloc(
915 696,
916 .@"8",
917 @returnAddress(),
918 ) orelse return error.OutOfMemory;
919 allocator.rawFree(fourth[0..696], .@"8", @returnAddress());
920
921 const fourth_allocations = failing.alloc_index;
922 failing.fail_index = fourth_allocations;
923 const fourth_reused = allocator.rawAlloc(
924 696,
925 .@"8",
926 @returnAddress(),
927 ) orelse return error.OutOfMemory;
928 try testing.expectEqual(fourth_allocations, failing.alloc_index);
929 try testing.expectEqual(@intFromPtr(fourth), @intFromPtr(fourth_reused));
930 allocator.rawFree(fourth_reused[0..696], .@"8", @returnAddress());
931 }
932
933 test "operation replaceOperands updates caches and uses" {
934 const testing = std.testing;
935
936 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
937 defer ctx.deinit(testing.allocator);
938 try ctx.allowUnregistered();
939
940 const unknown_type = try ctx.getDialectTypeFromName("test.ty");
941
942 var producer_1_state = Operation.State.init("test.producer_1", .unknown);
943 producer_1_state.addTypes(&.{unknown_type});
944 const producer_1 = try ctx.createOperation(producer_1_state);
945 const r1 = producer_1.getResult(0).?;
946
947 var producer_2_state = Operation.State.init("test.producer_2", .unknown);
948 producer_2_state.addTypes(&.{unknown_type});
949 const producer_2 = try ctx.createOperation(producer_2_state);
950 const r2 = producer_2.getResult(0).?;
951
952 var producer_3_state = Operation.State.init("test.producer_3", .unknown);
953 producer_3_state.addTypes(&.{unknown_type});
954 const producer_3 = try ctx.createOperation(producer_3_state);
955 const r3 = producer_3.getResult(0).?;
956
957 var consumer_state = Operation.State.init("test.consumer_replace", .unknown);
958 consumer_state.addOperands(&.{r1});
959 const consumer = try ctx.createOperation(consumer_state);
960
961 try testing.expectEqual(@as(usize, 1), r1.getNumUses());
962
963 try consumer.replaceOperands(&.{ r2, r3 });
964
965 try testing.expectEqual(@as(usize, 0), r1.getNumUses());
966 try testing.expectEqual(@as(usize, 1), r2.getNumUses());
967 try testing.expectEqual(@as(usize, 1), r3.getNumUses());
968
969 const cached_operands = consumer.getOperandValues();
970 try testing.expectEqual(@as(usize, 2), cached_operands.len);
971 try testing.expectEqual(r2, cached_operands[0]);
972 try testing.expectEqual(r3, cached_operands[1]);
973
974 try consumer.replaceOperands(consumer.getOperandValues()[1..]);
975 try testing.expectEqual(@as(usize, 0), r2.getNumUses());
976 try testing.expectEqual(@as(usize, 1), r3.getNumUses());
977 try testing.expectEqual(@as(usize, 1), consumer.getNumOperands());
978 try testing.expectEqual(r3, consumer.getOperand(0).?);
979 }
980
981 test "operation replaceOperands rejects exhausted Context spill capacity before mutation" {
982 comptime {
983 @stardustClaim(
984 @import("alloc_phase").capacity.witness(@import("./context/root.zig").Context, "choir_context_spill_exhaustion"),
985 null,
986 null,
987 null,
988 null,
989 null,
990 null,
991 );
992 }
993 comptime {
994 @stardustClaim(
995 @import("alloc_phase").capacity.witness(@import("./operation/root.zig").OperationFixedStorage, "choir_operation_storage_spill"),
996 null,
997 null,
998 null,
999 null,
1000 null,
1001 null,
1002 );
1003 }
1004
1005 const testing = std.testing;
1006
1007 var limits = Context.Limits.testing;
1008 limits.operations.nested_bytes = 0;
1009 var ctx = try Context.init(testing.allocator, limits);
1010 defer ctx.deinit(testing.allocator);
1011 try ctx.allowUnregistered();
1012
1013 const unknown_type = try ctx.getDialectTypeFromName("test.ty");
1014 var producer_state = Operation.State.init("test.producer", .unknown);
1015 producer_state.addTypes(&.{ unknown_type, unknown_type, unknown_type });
1016 const producer = try ctx.createOperation(producer_state);
1017 const first = producer.getResult(0).?;
1018 const second = producer.getResult(1).?;
1019 const third = producer.getResult(2).?;
1020
1021 var consumer_state = Operation.State.init("test.consumer", .unknown);
1022 consumer_state.addOperands(&.{first});
1023 const consumer = try ctx.createOperation(consumer_state);
1024
1025 try testing.expectError(error.OutOfMemory, consumer.replaceOperands(&.{ second, third }));
1026 try testing.expectEqual(@as(usize, 1), consumer.getNumOperands());
1027 try testing.expectEqual(first, consumer.getOperand(0).?);
1028 try testing.expectEqual(@as(usize, 1), first.getNumUses());
1029 try testing.expectEqual(@as(usize, 0), second.getNumUses());
1030 try testing.expectEqual(@as(usize, 0), third.getNumUses());
1031 }
1032
1033 test "operation setOperandValue updates caches and uses" {
1034 const testing = std.testing;
1035
1036 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1037 defer ctx.deinit(testing.allocator);
1038 try ctx.allowUnregistered();
1039
1040 const unknown_type = try ctx.getDialectTypeFromName("test.ty");
1041
1042 var producer_1_state = Operation.State.init("test.producer_1", .unknown);
1043 producer_1_state.addTypes(&.{unknown_type});
1044 const producer_1 = try ctx.createOperation(producer_1_state);
1045 const r1 = producer_1.getResult(0).?;
1046
1047 var producer_2_state = Operation.State.init("test.producer_2", .unknown);
1048 producer_2_state.addTypes(&.{unknown_type});
1049 const producer_2 = try ctx.createOperation(producer_2_state);
1050 const r2 = producer_2.getResult(0).?;
1051
1052 var consumer_state = Operation.State.init("test.consumer_set_operand", .unknown);
1053 consumer_state.addOperands(&.{r1});
1054 const consumer = try ctx.createOperation(consumer_state);
1055
1056 try testing.expectEqual(@as(usize, 1), r1.getNumUses());
1057 try testing.expectEqual(@as(usize, 0), r2.getNumUses());
1058
1059 consumer.setOperandValue(0, r2);
1060
1061 try testing.expectEqual(@as(usize, 0), r1.getNumUses());
1062 try testing.expectEqual(@as(usize, 1), r2.getNumUses());
1063 try testing.expectEqual(r2, consumer.getOperand(0).?);
1064 try testing.expectEqual(r2, consumer.getOperandValues()[0]);
1065 }
1066
1067 test "value replaceAllUsesWith updates operand caches" {
1068 const testing = std.testing;
1069
1070 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1071 defer ctx.deinit(testing.allocator);
1072 try ctx.allowUnregistered();
1073
1074 const unknown_type = try ctx.getDialectTypeFromName("test.ty");
1075
1076 var producer_1_state = Operation.State.init("test.producer_1", .unknown);
1077 producer_1_state.addTypes(&.{unknown_type});
1078 const producer_1 = try ctx.createOperation(producer_1_state);
1079 const r1 = producer_1.getResult(0).?;
1080
1081 var producer_2_state = Operation.State.init("test.producer_2", .unknown);
1082 producer_2_state.addTypes(&.{unknown_type});
1083 const producer_2 = try ctx.createOperation(producer_2_state);
1084 const r2 = producer_2.getResult(0).?;
1085
1086 var consumer_state = Operation.State.init("test.consumer_replace_all_uses", .unknown);
1087 consumer_state.addOperands(&.{r1});
1088 const consumer = try ctx.createOperation(consumer_state);
1089
1090 try testing.expectEqual(@as(usize, 1), r1.getNumUses());
1091 try testing.expectEqual(@as(usize, 0), r2.getNumUses());
1092
1093 r1.replaceAllUsesWith(r2);
1094
1095 try testing.expectEqual(@as(usize, 0), r1.getNumUses());
1096 try testing.expectEqual(@as(usize, 1), r2.getNumUses());
1097 try testing.expectEqual(r2, consumer.getOpOperand(0).?.value);
1098 try testing.expectEqual(r2, consumer.getOperand(0).?);
1099 try testing.expectEqual(r2, consumer.getOperandValues()[0]);
1100 }
1101
1102 test "operation attribute lookup uses sorted storage" {
1103 const testing = std.testing;
1104
1105 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1106 defer ctx.deinit(testing.allocator);
1107 try ctx.allowUnregistered();
1108
1109 const state = Operation.State.init("test.attr_op", .unknown);
1110 const op = try ctx.createOperation(state);
1111
1112 const unit_attr = try ctx.getDialectAttr("test.unit", "");
1113 try op.setAttr("zeta", unit_attr);
1114 try op.setAttr("alpha", unit_attr);
1115 try op.setAttr("mid", unit_attr);
1116
1117 try testing.expect(op.getAttr("alpha") != null);
1118 try testing.expect(op.getAttr("mid") != null);
1119 try testing.expect(op.getAttr("zeta") != null);
1120 try testing.expect(op.removeAttr("mid"));
1121 try testing.expect(op.getAttr("mid") == null);
1122 try testing.expect(!op.removeAttr("mid"));
1123
1124 const attrs = op.getRawDictionaryAttrs();
1125 try testing.expect(std.mem.order(u8, attrs[0].name, attrs[1].name) != .gt);
1126 try testing.expectEqual(attrs.ptr, op.getRawDictionaryAttrs().ptr);
1127 try testing.expectEqual(attrs.len, op.countDiscardableAttrs());
1128 try testing.expect(op.getDiscardableAttr("alpha") != null);
1129 try testing.expect(op.removeDiscardableAttr("zeta"));
1130 try testing.expect(op.getAttr("zeta") == null);
1131 }
1132
1133 test "inherent attribute names are not discardable attributes" {
1134 const testing = std.testing;
1135
1136 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1137 defer ctx.deinit(testing.allocator);
1138 try ctx.allowUnregistered();
1139
1140 _ = try ctx.registerOperation("test.inherent_attr_op", .{});
1141 try ctx.registerOperationInherentAttributeName("test.inherent_attr_op", "value");
1142
1143 const state = Operation.State.init("test.inherent_attr_op", .unknown);
1144 const op = try ctx.createOperation(state);
1145
1146 const value_attr = try ctx.getI64Attr(1);
1147 const note_attr = try ctx.getI64Attr(2);
1148 try op.setAttr("value", value_attr);
1149 try op.setAttr("debug.note", note_attr);
1150
1151 const raw_attrs = op.getRawDictionaryAttrs();
1152 try testing.expectEqual(@as(usize, 2), raw_attrs.len);
1153 try testing.expectEqualStrings("debug.note", raw_attrs[0].name);
1154 try testing.expectEqualStrings("value", raw_attrs[1].name);
1155 try testing.expect(op.hasInherentAttributeName("value"));
1156 try testing.expect(!op.isDiscardableAttrName("value"));
1157 try testing.expect(op.isDiscardableAttrName("debug.note"));
1158 try testing.expect(op.getAttr("value") != null);
1159 try testing.expect(op.getDiscardableAttr("value") == null);
1160 try testing.expect(op.getDiscardableAttr("debug.note") != null);
1161 try testing.expectEqual(@as(usize, 1), op.countDiscardableAttrs());
1162
1163 var iter = op.getDiscardableAttrs();
1164 const only_attr = iter.next() orelse return error.TestExpectedDiscardableAttr;
1165 try testing.expectEqualStrings("debug.note", only_attr.name);
1166 try testing.expect(iter.next() == null);
1167
1168 try testing.expectError(error.InherentAttributeName, op.setDiscardableAttr("value", value_attr));
1169 try testing.expect(!op.removeDiscardableAttr("value"));
1170 try testing.expect(op.getAttr("value") != null);
1171 try testing.expect(op.removeAttr("value"));
1172 try testing.expect(op.getAttr("value") == null);
1173 }
1174
1175 test "operation typed attribute lookup checks inherent and discardable storage" {
1176 const testing = std.testing;
1177
1178 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1179 defer ctx.deinit(testing.allocator);
1180 try ctx.allowUnregistered();
1181
1182 _ = try ctx.registerOperation("test.typed_attr_op", .{});
1183 try ctx.registerOperationInherentAttributeName("test.typed_attr_op", "value");
1184 try ctx.registerOperationPropertiesModel(
1185 "test.typed_attr_op",
1186 interfaces.singleAttributePropertiesModel("test.typed_attr_op.properties", "value"),
1187 );
1188
1189 const state = Operation.State.init("test.typed_attr_op", .unknown);
1190 const op = try ctx.createOperation(state);
1191
1192 try op.setAttr("value", try ctx.getI64Attr(7));
1193 try op.setAttr("debug.note", try ctx.getStringAttr("note"));
1194
1195 try testing.expectEqual(
1196 @as(i64, 7),
1197 op.getAttrAs(Attribute.IntegerAttr, "value").?.getValue(),
1198 );
1199 try testing.expect(op.getAttrAs(Attribute.StringAttr, "value") == null);
1200
1201 try testing.expectEqualStrings(
1202 "note",
1203 op.getAttrAs(Attribute.StringAttr, "debug.note").?.getValue(),
1204 );
1205 try testing.expectEqualStrings(
1206 "note",
1207 op.getDiscardableAttrAs(Attribute.StringAttr, "debug.note").?.getValue(),
1208 );
1209 try testing.expect(op.getDiscardableAttrAs(Attribute.IntegerAttr, "debug.note") == null);
1210 try testing.expect(op.getDiscardableAttrAs(Attribute.IntegerAttr, "value") == null);
1211 }
1212
1213 test "operation attribute iterator merges raw and inherent attributes" {
1214 const testing = std.testing;
1215
1216 const TestProperties = struct {
1217 value: ?Attribute = null,
1218 predicate: ?Attribute = null,
1219
1220 fn from(storage: *anyopaque) *@This() {
1221 return @ptrCast(@alignCast(storage));
1222 }
1223
1224 fn fromConst(storage: *const anyopaque) *const @This() {
1225 return @ptrCast(@alignCast(storage));
1226 }
1227
1228 fn init(storage: *anyopaque, _: std.mem.Allocator) anyerror!void {
1229 from(storage).* = .{};
1230 }
1231
1232 fn deinit(_: *anyopaque, _: std.mem.Allocator) void {}
1233
1234 fn get(_: *const Operation, storage: *const anyopaque, name: []const u8) ?Attribute {
1235 const self = fromConst(storage);
1236 if (std.mem.eql(u8, name, "value")) return self.value;
1237 if (std.mem.eql(u8, name, "predicate")) return self.predicate;
1238 return null;
1239 }
1240
1241 fn set(_: *Operation, storage: *anyopaque, name: []const u8, attr: Attribute) anyerror!bool {
1242 const self = from(storage);
1243 if (std.mem.eql(u8, name, "value")) {
1244 self.value = attr;
1245 return true;
1246 }
1247 if (std.mem.eql(u8, name, "predicate")) {
1248 self.predicate = attr;
1249 return true;
1250 }
1251 return false;
1252 }
1253
1254 fn remove(_: *Operation, storage: *anyopaque, name: []const u8) bool {
1255 const self = from(storage);
1256 if (std.mem.eql(u8, name, "value")) {
1257 const existed = self.value != null;
1258 self.value = null;
1259 return existed;
1260 }
1261 if (std.mem.eql(u8, name, "predicate")) {
1262 const existed = self.predicate != null;
1263 self.predicate = null;
1264 return existed;
1265 }
1266 return false;
1267 }
1268
1269 fn copyProperties(dest: *anyopaque, source: *const anyopaque) anyerror!void {
1270 from(dest).* = fromConst(source).*;
1271 }
1272
1273 const model = interfaces.OperationPropertiesModel{
1274 .name = "test.properties",
1275 .size = @sizeOf(@This()),
1276 .alignment = std.mem.Alignment.fromByteUnits(@alignOf(@This())),
1277 .init = init,
1278 .deinit = deinit,
1279 .getInherentAttr = get,
1280 .setInherentAttr = set,
1281 .removeInherentAttr = remove,
1282 .copyProperties = copyProperties,
1283 };
1284 };
1285
1286 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1287 defer ctx.deinit(testing.allocator);
1288 try ctx.allowUnregistered();
1289
1290 _ = try ctx.registerOperation("test.property_attr_op", .{});
1291 try ctx.registerOperationInherentAttributeNames(
1292 "test.property_attr_op",
1293 &.{ "unused", "value", "missing", "predicate" },
1294 );
1295 try ctx.registerOperationPropertiesModel("test.property_attr_op", TestProperties.model);
1296
1297 const value_attr = try ctx.getI64Attr(11);
1298 const note_attr = try ctx.getI64Attr(12);
1299 const predicate_attr = try ctx.getBoolAttr(true);
1300 const alpha_attr = try ctx.getI64Attr(13);
1301 const missing_attr = try ctx.getI64Attr(14);
1302 const omega_attr = try ctx.getI64Attr(15);
1303 const zeta_attr = try ctx.getI64Attr(16);
1304 const collision_attr = try ctx.getI64Attr(17);
1305 const attrs = [_]NamedAttribute{
1306 .{ .name = "value", .value = value_attr },
1307 .{ .name = "debug.note", .value = note_attr },
1308 .{ .name = "zeta", .value = zeta_attr },
1309 .{ .name = "alpha", .value = alpha_attr },
1310 .{ .name = "missing", .value = missing_attr },
1311 .{ .name = "omega", .value = omega_attr },
1312 };
1313 var state = Operation.State.init("test.property_attr_op", .unknown);
1314 state.addAttributes(&attrs);
1315 const op = try ctx.createOperation(state);
1316
1317 try testing.expect(op.getAttr("value").?.eql(value_attr));
1318 try testing.expect(op.getAttr("debug.note").?.eql(note_attr));
1319 try testing.expectEqual(@as(usize, 5), op.getRawDictionaryAttrs().len);
1320
1321 try op.setAttr("predicate", predicate_attr);
1322 _ = try op.raw_dictionary_attrs.set(testing.allocator, "value", collision_attr);
1323 try testing.expect(op.getAttr("predicate").?.eql(predicate_attr));
1324 try testing.expect(op.getAttr("value").?.eql(value_attr));
1325 try testing.expect(op.raw_dictionary_attrs.get("value").?.eql(collision_attr));
1326
1327 const raw_ptr = op.getRawDictionaryAttrs().ptr;
1328 const raw_len = op.getRawDictionaryAttrs().len;
1329 const raw_capacity = op.raw_dictionary_attrs.capacity();
1330 var raw_snapshot: [6]NamedAttribute = undefined;
1331 @memcpy(&raw_snapshot, op.getRawDictionaryAttrs());
1332
1333 const expected_names = [_][]const u8{
1334 "alpha",
1335 "debug.note",
1336 "missing",
1337 "omega",
1338 "predicate",
1339 "value",
1340 "zeta",
1341 };
1342 const expected_values = [_]Attribute{
1343 alpha_attr,
1344 note_attr,
1345 missing_attr,
1346 omega_attr,
1347 predicate_attr,
1348 value_attr,
1349 zeta_attr,
1350 };
1351 op.name.registered_info = null;
1352 try testing.expect(op.name.getRegisteredInfo() == null);
1353 try testing.expectEqual(expected_names.len, op.getNumAttrs());
1354 var logical_attrs = op.getAttrs();
1355 for (expected_names, expected_values) |name, value| {
1356 const attr = logical_attrs.next() orelse return error.TestExpectedAttribute;
1357 try testing.expectEqualStrings(name, attr.name);
1358 try testing.expect(attr.value.eql(value));
1359 }
1360 try testing.expect(logical_attrs.next() == null);
1361 try testing.expect(op.name.getRegisteredInfo() == null);
1362 try testing.expectEqual(raw_ptr, op.getRawDictionaryAttrs().ptr);
1363 try testing.expectEqual(raw_len, op.getRawDictionaryAttrs().len);
1364 try testing.expectEqual(raw_capacity, op.raw_dictionary_attrs.capacity());
1365 for (raw_snapshot, op.getRawDictionaryAttrs()) |before, after| {
1366 try testing.expectEqualStrings(before.name, after.name);
1367 try testing.expect(before.value.eql(after.value));
1368 }
1369
1370 const printed = try std.fmt.allocPrint(testing.allocator, "{f}", .{op.*});
1371 defer testing.allocator.free(printed);
1372 try testing.expect(std.mem.indexOf(u8, printed, "debug.note") != null);
1373 try testing.expect(std.mem.indexOf(u8, printed, "predicate") != null);
1374 try testing.expect(std.mem.indexOf(u8, printed, "value") != null);
1375
1376 try testing.expect(op.removeAttr("value"));
1377 try testing.expect(op.getAttr("value").?.eql(collision_attr));
1378 try testing.expectEqual(expected_names.len, op.getNumAttrs());
1379 var after_remove = op.getAttrs();
1380 for (expected_names) |name| {
1381 const attr = after_remove.next() orelse return error.TestExpectedAttribute;
1382 try testing.expectEqualStrings(name, attr.name);
1383 if (std.mem.eql(u8, name, "value")) {
1384 try testing.expect(attr.value.eql(collision_attr));
1385 }
1386 }
1387 try testing.expect(after_remove.next() == null);
1388 }
1389
1390 test "operation state property attribute converts after initial attributes" {
1391 const testing = std.testing;
1392
1393 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1394 defer ctx.deinit(testing.allocator);
1395 try ctx.allowUnregistered();
1396
1397 _ = try ctx.registerOperation("test.state_property_attr_op", .{});
1398 try ctx.registerOperationInherentAttributeName("test.state_property_attr_op", "value");
1399 try ctx.registerOperationPropertiesModel(
1400 "test.state_property_attr_op",
1401 interfaces.singleAttributePropertiesModel("test.state_property_attr.properties", "value"),
1402 );
1403
1404 const initial_attr = try ctx.getI64Attr(1);
1405 const properties_attr = try ctx.getI64Attr(2);
1406 const note_attr = try ctx.getI64Attr(3);
1407 const attrs = [_]NamedAttribute{
1408 .{ .name = "value", .value = initial_attr },
1409 .{ .name = "debug.note", .value = note_attr },
1410 };
1411 var state = Operation.State.init("test.state_property_attr_op", .unknown);
1412 state.addAttributes(&attrs);
1413 try state.setPropertiesAttr(properties_attr);
1414 const op = try ctx.createOperation(state);
1415
1416 try testing.expect(op.getAttr("value").?.eql(properties_attr));
1417 try testing.expect((try op.getPropertiesAsAttr()).?.eql(properties_attr));
1418 try testing.expectEqual(@as(usize, 1), op.getRawDictionaryAttrs().len);
1419 try testing.expectEqualStrings("debug.note", op.getRawDictionaryAttrs()[0].name);
1420
1421 try testing.expectEqual(@as(usize, 2), op.getNumAttrs());
1422 var collected = op.getAttrs();
1423 try testing.expectEqualStrings("debug.note", collected.next().?.name);
1424 const value = collected.next().?;
1425 try testing.expectEqualStrings("value", value.name);
1426 try testing.expect(value.value.eql(properties_attr));
1427 try testing.expect(collected.next() == null);
1428 }
1429
1430 test "operation state property ref copies typed storage after initial attributes" {
1431 const testing = std.testing;
1432
1433 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1434 defer ctx.deinit(testing.allocator);
1435 try ctx.allowUnregistered();
1436
1437 _ = try ctx.registerOperation("test.state_property_ref_op", .{});
1438 try ctx.registerOperationInherentAttributeName("test.state_property_ref_op", "value");
1439 try ctx.registerOperationPropertiesModel(
1440 "test.state_property_ref_op",
1441 interfaces.singleAttributePropertiesModel("test.state_property_ref.properties", "value"),
1442 );
1443
1444 const source_attr = try ctx.getI64Attr(41);
1445 const changed_source_attr = try ctx.getI64Attr(42);
1446 const initial_attr = try ctx.getI64Attr(1);
1447 const note_attr = try ctx.getI64Attr(7);
1448
1449 const source = try ctx.createOperation(Operation.State.init("test.state_property_ref_op", .unknown));
1450 try source.setAttr("value", source_attr);
1451
1452 const attrs = [_]NamedAttribute{
1453 .{ .name = "value", .value = initial_attr },
1454 .{ .name = "debug.note", .value = note_attr },
1455 };
1456 var state = Operation.State.init("test.state_property_ref_op", .unknown);
1457 state.addAttributes(&attrs);
1458 try state.setPropertiesFromOperation(source);
1459 const target = try ctx.createOperation(state);
1460
1461 try testing.expect(target.getAttr("value").?.eql(source_attr));
1462 try testing.expect((try target.getPropertiesAsAttr()).?.eql(source_attr));
1463 try testing.expectEqual(@as(usize, 1), target.getRawDictionaryAttrs().len);
1464 try testing.expectEqualStrings("debug.note", target.getRawDictionaryAttrs()[0].name);
1465
1466 try source.setAttr("value", changed_source_attr);
1467 try testing.expect(source.getAttr("value").?.eql(changed_source_attr));
1468 try testing.expect(target.getAttr("value").?.eql(source_attr));
1469 }
1470
1471 test "operation state property ref rejects model mismatch" {
1472 const testing = std.testing;
1473
1474 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1475 defer ctx.deinit(testing.allocator);
1476 try ctx.allowUnregistered();
1477
1478 _ = try ctx.registerOperation("test.source_property_ref_op", .{});
1479 try ctx.registerOperationInherentAttributeName("test.source_property_ref_op", "value");
1480 try ctx.registerOperationPropertiesModel(
1481 "test.source_property_ref_op",
1482 interfaces.singleAttributePropertiesModel("test.source_property_ref.properties", "value"),
1483 );
1484
1485 _ = try ctx.registerOperation("test.target_property_ref_op", .{});
1486 try ctx.registerOperationInherentAttributeName("test.target_property_ref_op", "value");
1487 try ctx.registerOperationPropertiesModel(
1488 "test.target_property_ref_op",
1489 interfaces.singleAttributePropertiesModel("test.target_property_ref.properties", "value"),
1490 );
1491
1492 const source = try ctx.createOperation(Operation.State.init("test.source_property_ref_op", .unknown));
1493 try source.setAttr("value", try ctx.getI64Attr(11));
1494
1495 var state = Operation.State.init("test.target_property_ref_op", .unknown);
1496 try state.setPropertiesFromOperation(source);
1497 try testing.expectError(error.OperationPropertiesMismatch, ctx.createOperation(state));
1498 }
1499
1500 test "operation state rejects mixed property payloads" {
1501 const testing = std.testing;
1502
1503 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1504 defer ctx.deinit(testing.allocator);
1505 try ctx.allowUnregistered();
1506
1507 _ = try ctx.registerOperation("test.mixed_property_payload_op", .{});
1508 try ctx.registerOperationInherentAttributeName("test.mixed_property_payload_op", "value");
1509 try ctx.registerOperationPropertiesModel(
1510 "test.mixed_property_payload_op",
1511 interfaces.singleAttributePropertiesModel("test.mixed_property_payload.properties", "value"),
1512 );
1513
1514 const source = try ctx.createOperation(Operation.State.init("test.mixed_property_payload_op", .unknown));
1515 const attr = try ctx.getI64Attr(19);
1516 try source.setAttr("value", attr);
1517
1518 var ref_first = Operation.State.init("test.mixed_property_payload_op", .unknown);
1519 try ref_first.setPropertiesFromOperation(source);
1520 try testing.expectError(error.DuplicateOperationPropertiesPayload, ref_first.setPropertiesAttr(attr));
1521
1522 var attr_first = Operation.State.init("test.mixed_property_payload_op", .unknown);
1523 try attr_first.setPropertiesAttr(attr);
1524 try testing.expectError(error.DuplicateOperationPropertiesPayload, attr_first.setPropertiesFromOperation(source));
1525 }
1526
1527 test "operation cloneWithoutRegions copies header and leaves regions empty" {
1528 const testing = std.testing;
1529
1530 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1531 defer ctx.deinit(testing.allocator);
1532 try ctx.allowUnregistered();
1533
1534 const value_type = try ctx.getDialectTypeFromName("test.value");
1535 var producer_state = Operation.State.init("test.clone_producer", .unknown);
1536 producer_state.addTypes(&.{value_type});
1537 const producer = try ctx.createOperation(producer_state);
1538 const operand = producer.getResult(0).?;
1539
1540 var successor = Block.init(testing.allocator);
1541 defer successor.deinit();
1542
1543 var body = core.context.initRegion(&ctx);
1544 defer body.deinit();
1545 _ = try body.addBlock();
1546
1547 const note_attr = try ctx.getI64Attr(9);
1548 const attrs = [_]NamedAttribute{.{ .name = "debug.note", .value = note_attr }};
1549
1550 var state = Operation.State.init("test.clone_source", .unknown);
1551 state.addOperands(&.{operand});
1552 state.addTypes(&.{value_type});
1553 state.addAttributes(&attrs);
1554 state.addRegionBodies(&.{&body});
1555 state.addSuccessors(&.{&successor});
1556 const source = try ctx.createOperation(state);
1557
1558 const clone = try source.cloneWithoutRegions();
1559
1560 try testing.expect(clone != source);
1561 try testing.expectEqualStrings(source.name.name, clone.name.name);
1562 try testing.expectEqual(@as(usize, 1), clone.getNumOperands());
1563 try testing.expect(clone.getOperand(0).? == operand);
1564 try testing.expectEqual(@as(usize, 1), clone.getNumResults());
1565 try testing.expect(clone.getResult(0).? != source.getResult(0).?);
1566 try testing.expect(clone.getResult(0).?.type.eql(value_type));
1567 try testing.expect(clone.getAttr("debug.note").?.eql(note_attr));
1568 try testing.expectEqual(@as(usize, 1), clone.getNumSuccessors());
1569 try testing.expect(clone.getSuccessor(0).? == &successor);
1570 try testing.expectEqual(@as(usize, 1), clone.getNumRegions());
1571 try testing.expect(!source.getRegion(0).?.empty());
1572 try testing.expect(clone.getRegion(0).?.empty());
1573 try testing.expect(clone.parent_block == null);
1574 }
1575
1576 test "operation cloneWithoutRegions copies typed properties independently" {
1577 const testing = std.testing;
1578
1579 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1580 defer ctx.deinit(testing.allocator);
1581 try ctx.allowUnregistered();
1582
1583 _ = try ctx.registerOperation("test.clone_property_op", .{});
1584 try ctx.registerOperationInherentAttributeName("test.clone_property_op", "value");
1585 try ctx.registerOperationPropertiesModel(
1586 "test.clone_property_op",
1587 interfaces.singleAttributePropertiesModel("test.clone_property.properties", "value"),
1588 );
1589
1590 const value_attr = try ctx.getI64Attr(11);
1591 const changed_attr = try ctx.getI64Attr(12);
1592 const note_attr = try ctx.getI64Attr(13);
1593
1594 const source = try ctx.createOperation(Operation.State.init("test.clone_property_op", .unknown));
1595 try source.setAttr("value", value_attr);
1596 try source.setAttr("debug.note", note_attr);
1597
1598 const clone = try source.cloneWithoutRegions();
1599 try source.setAttr("value", changed_attr);
1600
1601 try testing.expect(source.getAttr("value").?.eql(changed_attr));
1602 try testing.expect(clone.getAttr("value").?.eql(value_attr));
1603 try testing.expect((try clone.getPropertiesAsAttr()).?.eql(value_attr));
1604 try testing.expect(clone.getAttr("debug.note").?.eql(note_attr));
1605 try testing.expectEqual(@as(usize, 1), clone.getRawDictionaryAttrs().len);
1606 try testing.expectEqualStrings("debug.note", clone.getRawDictionaryAttrs()[0].name);
1607 }
1608
1609 test "operation clone copies nested regions through mapped values" {
1610 const testing = std.testing;
1611
1612 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1613 defer ctx.deinit(testing.allocator);
1614 try ctx.allowUnregistered();
1615
1616 const value_type = try ctx.getDialectTypeFromName("test.value");
1617
1618 var body = core.context.initRegion(&ctx);
1619 defer body.deinit();
1620 _ = try body.addBlock();
1621
1622 var parent_state = Operation.State.init("test.clone_parent", .unknown);
1623 parent_state.addTypes(&.{value_type});
1624 parent_state.addRegionBodies(&.{&body});
1625 const parent = try ctx.createOperation(parent_state);
1626
1627 const entry = parent.getRegion(0).?.getEntryBlock().?;
1628 var child_state = Operation.State.init("test.clone_child", .unknown);
1629 child_state.addOperands(&.{parent.getResult(0).?});
1630 const child = try ctx.createOperation(child_state);
1631 try entry.addOperation(child);
1632
1633 const cloned_parent = try parent.clone();
1634
1635 try testing.expect(cloned_parent != parent);
1636 try testing.expectEqual(@as(usize, 1), cloned_parent.getNumRegions());
1637 try testing.expect(!cloned_parent.getRegion(0).?.empty());
1638 try testing.expect(cloned_parent.getResult(0).? != parent.getResult(0).?);
1639
1640 const cloned_entry = cloned_parent.getRegion(0).?.getEntryBlock().?;
1641 try testing.expect(cloned_entry != entry);
1642
1643 var cloned_ops = cloned_entry.getOperations();
1644 const cloned_child = cloned_ops.next().?;
1645 try testing.expect(cloned_ops.next() == null);
1646 try testing.expect(cloned_child != child);
1647 try testing.expect(cloned_child.getOperand(0).? == cloned_parent.getResult(0).?);
1648 try testing.expect(parent.getResult(0).?.getNumUses() == 1);
1649 try testing.expect(cloned_parent.getResult(0).?.getNumUses() == 1);
1650 }
1651
1652 test "operation clone records source to clone operation mappings" {
1653 const testing = std.testing;
1654
1655 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1656 defer ctx.deinit(testing.allocator);
1657 try ctx.allowUnregistered();
1658
1659 const value_type = try ctx.getDialectTypeFromName("test.value");
1660
1661 var body = core.context.initRegion(&ctx);
1662 defer body.deinit();
1663 _ = try body.addBlock();
1664
1665 var parent_state = Operation.State.init("test.clone_parent", .unknown);
1666 parent_state.addTypes(&.{value_type});
1667 parent_state.addRegionBodies(&.{&body});
1668 const parent = try ctx.createOperation(parent_state);
1669
1670 const entry = parent.getRegion(0).?.getEntryBlock().?;
1671 var child_state = Operation.State.init("test.clone_child", .unknown);
1672 child_state.addOperands(&.{parent.getResult(0).?});
1673 const child = try ctx.createOperation(child_state);
1674 try entry.addOperation(child);
1675
1676 var mapping = Mapping.init(testing.allocator);
1677 defer mapping.deinit();
1678
1679 const cloned_parent = try parent.cloneWithoutRegionsMapped(&mapping, .{});
1680 for (parent.regions.items, 0..) |*region, i| {
1681 try region.cloneInto(&cloned_parent.regions.items[i], &mapping);
1682 }
1683
1684 const cloned_entry = cloned_parent.getRegion(0).?.getEntryBlock().?;
1685 var cloned_ops = cloned_entry.getOperations();
1686 const cloned_child = cloned_ops.next().?;
1687
1688 try testing.expect(mapping.lookupOperation(parent).? == cloned_parent);
1689 try testing.expect(mapping.lookupOperation(child).? == cloned_child);
1690 try testing.expect(mapping.lookupOrDefaultOperation(parent) == cloned_parent);
1691 try testing.expect(cloned_child.getOperand(0).? == cloned_parent.getResult(0).?);
1692 }
1693
1694 test "region cloneInto remaps blocks arguments results operands and successors" {
1695 const testing = std.testing;
1696
1697 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1698 try ctx.allowUnregistered();
1699
1700 const value_type = try ctx.getDialectTypeFromName("test.value");
1701
1702 const boundary = ctx.operationCreationBoundary();
1703 var source = core.context.initRegion(&ctx);
1704 var dest = core.context.initRegion(&ctx);
1705 defer {
1706 ctx.eraseOperationsCreatedSince(boundary);
1707 dest.deinit();
1708 source.deinit();
1709 ctx.deinit(testing.allocator);
1710 }
1711
1712 const entry = try source.addBlock();
1713 const exit = try source.addBlock();
1714 const argument = try entry.addArgument(value_type, .unknown);
1715
1716 var producer_state = Operation.State.init("test.clone_producer", .unknown);
1717 producer_state.addTypes(&.{value_type});
1718 const producer = try ctx.createOperation(producer_state);
1719 try entry.addOperation(producer);
1720
1721 var consumer_state = Operation.State.init("test.clone_consumer", .unknown);
1722 consumer_state.addOperands(&.{ producer.getResult(0).?, argument });
1723 consumer_state.addSuccessors(&.{exit});
1724 const consumer = try ctx.createOperation(consumer_state);
1725 try entry.addOperation(consumer);
1726
1727 var mapping = Mapping.init(testing.allocator);
1728 defer mapping.deinit();
1729 try source.cloneInto(&dest, &mapping);
1730
1731 try testing.expectEqual(@as(usize, 2), source.blocks.size);
1732 try testing.expectEqual(@as(usize, 2), dest.blocks.size);
1733
1734 const cloned_entry = mapping.lookupBlock(entry).?;
1735 const cloned_exit = mapping.lookupBlock(exit).?;
1736 try testing.expect(cloned_entry != entry);
1737 try testing.expect(cloned_exit != exit);
1738 try testing.expect(dest.blocks.head == cloned_entry);
1739 try testing.expect(dest.blocks.tail == cloned_exit);
1740 try testing.expect(cloned_entry.parent == @as(*anyopaque, @ptrCast(&dest)));
1741 try testing.expect(cloned_exit.parent == @as(*anyopaque, @ptrCast(&dest)));
1742
1743 const cloned_argument = mapping.lookupValue(argument).?;
1744 try testing.expect(cloned_argument != argument);
1745 try testing.expect(cloned_argument.type.eql(value_type));
1746 try testing.expect(cloned_argument.kind.block_argument.owner == @as(*anyopaque, @ptrCast(cloned_entry)));
1747
1748 var cloned_ops = cloned_entry.getOperations();
1749 const cloned_producer = cloned_ops.next().?;
1750 const cloned_consumer = cloned_ops.next().?;
1751 try testing.expect(cloned_ops.next() == null);
1752 try testing.expect(cloned_producer != producer);
1753 try testing.expect(cloned_consumer != consumer);
1754 try testing.expect(mapping.lookupOperation(producer).? == cloned_producer);
1755 try testing.expect(mapping.lookupOperation(consumer).? == cloned_consumer);
1756 try testing.expect(mapping.lookupValue(producer.getResult(0).?).? == cloned_producer.getResult(0).?);
1757 try testing.expect(cloned_consumer.getOperand(0).? == cloned_producer.getResult(0).?);
1758 try testing.expect(cloned_consumer.getOperand(1).? == cloned_argument);
1759 try testing.expect(cloned_consumer.getSuccessor(0).? == cloned_exit);
1760 try testing.expect(cloned_exit.hasPredecessor(cloned_entry));
1761 try testing.expect(!exit.hasPredecessor(cloned_entry));
1762 try testing.expect(consumer.getOperand(0).? == producer.getResult(0).?);
1763 try testing.expect(consumer.getSuccessor(0).? == exit);
1764 }
1765
1766 test "operation state region bodies transfer into created operation" {
1767 const testing = std.testing;
1768
1769 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1770 defer ctx.deinit(testing.allocator);
1771 try ctx.allowUnregistered();
1772
1773 var body = core.context.initRegion(&ctx);
1774 defer body.deinit();
1775
1776 const moved_block = try body.addBlock();
1777 var state = Operation.State.init("test.region_body_op", .unknown);
1778 state.addRegion();
1779 state.addRegionBodies(&.{&body});
1780
1781 const op = try ctx.createOperation(state);
1782
1783 try testing.expectEqual(@as(usize, 2), op.getNumRegions());
1784 const empty_region = op.getRegion(0).?;
1785 try testing.expect(empty_region.empty());
1786
1787 const moved_region = op.getRegion(1).?;
1788 try testing.expect(!moved_region.empty());
1789 try testing.expect(moved_region.getEntryBlock().? == moved_block);
1790 try testing.expect(moved_block.parent == @as(*anyopaque, @ptrCast(moved_region)));
1791 try testing.expect(moved_region.parent == @as(*anyopaque, @ptrCast(op)));
1792 try testing.expect(body.empty());
1793 }
1794
1795 test "operation creation canonicalizes duplicate initial attributes" {
1796 const testing = std.testing;
1797
1798 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1799 defer ctx.deinit(testing.allocator);
1800 try ctx.allowUnregistered();
1801
1802 const first = try ctx.getI64Attr(1);
1803 const second = try ctx.getI64Attr(2);
1804 const beta = try ctx.getI64Attr(3);
1805
1806 const attrs = [_]NamedAttribute{
1807 .{ .name = "beta", .value = beta },
1808 .{ .name = "alpha", .value = first },
1809 .{ .name = "alpha", .value = second },
1810 };
1811 var state = Operation.State.init("test.attr_op", .unknown);
1812 state.addAttributes(&attrs);
1813 const op = try ctx.createOperation(state);
1814
1815 const stored_attrs = op.getRawDictionaryAttrs();
1816 try testing.expectEqual(@as(usize, 2), stored_attrs.len);
1817 try testing.expectEqualStrings("alpha", stored_attrs[0].name);
1818 try testing.expectEqualStrings("beta", stored_attrs[1].name);
1819 try testing.expect(op.getAttr("alpha").?.eql(second));
1820 try testing.expect(op.getAttr("beta").?.eql(beta));
1821 }
1822
1823 test "operation successors update block predecessors" {
1824 const testing = std.testing;
1825 const allocator = testing.allocator;
1826
1827 var ctx = try Context.init(allocator, Context.Limits.testing);
1828 defer ctx.deinit(allocator);
1829 try ctx.allowUnregistered();
1830
1831 var pred = Block.init(allocator);
1832 defer pred.deinit();
1833 var succ = Block.init(allocator);
1834 defer succ.deinit();
1835
1836 var state = Operation.State.init("test.br", Location.getUnknown());
1837 state.addSuccessors(&.{&succ});
1838
1839 const op = try ctx.createOperation(state);
1840 try pred.addOperation(op);
1841
1842 try testing.expect(succ.hasPredecessor(&pred));
1843
1844 op.erase();
1845
1846 try testing.expect(!succ.hasPredecessor(&pred));
1847 }
1848
1849 test "setSuccessors preserves the old relation on allocation failure" {
1850 const testing = std.testing;
1851
1852 var failing = testing.FailingAllocator.init(testing.allocator, .{});
1853 const allocator = failing.allocator();
1854 var ctx = try Context.init(allocator, Context.Limits.testing);
1855 var source = Block.init(allocator);
1856 var old_target = Block.init(allocator);
1857 var new_target = Block.init(allocator);
1858 defer {
1859 ctx.deinit(allocator);
1860 source.deinit();
1861 old_target.deinit();
1862 new_target.deinit();
1863 }
1864 try ctx.allowUnregistered();
1865
1866 var state = Operation.State.init("test.branch", .unknown);
1867 state.addSuccessors(&.{&old_target});
1868 const branch = try ctx.createOperation(state);
1869 try source.addOperation(branch);
1870
1871 failing.fail_index = failing.alloc_index;
1872 failing.resize_fail_index = failing.resize_index;
1873 const result = branch.setSuccessors(&.{&new_target});
1874 failing.fail_index = std.math.maxInt(usize);
1875 failing.resize_fail_index = std.math.maxInt(usize);
1876
1877 try testing.expectError(error.OutOfMemory, result);
1878 try testing.expect(branch.getSuccessor(0).? == &old_target);
1879 try testing.expect(old_target.hasPredecessor(&source));
1880 try testing.expect(new_target.hasNoPredecessors());
1881 }
1882
1883 test "setSuccessors accepts an overlapping successor slice" {
1884 const testing = std.testing;
1885
1886 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1887 var source = Block.init(testing.allocator);
1888 var first_target = Block.init(testing.allocator);
1889 var second_target = Block.init(testing.allocator);
1890 defer {
1891 ctx.deinit(testing.allocator);
1892 source.deinit();
1893 first_target.deinit();
1894 second_target.deinit();
1895 }
1896 try ctx.allowUnregistered();
1897
1898 var state = Operation.State.init("test.branch", .unknown);
1899 state.addSuccessors(&.{ &first_target, &second_target });
1900 const branch = try ctx.createOperation(state);
1901 try source.addOperation(branch);
1902
1903 try branch.setSuccessors(branch.successors.items[1..]);
1904
1905 try testing.expectEqual(@as(usize, 1), branch.getNumSuccessors());
1906 try testing.expect(branch.getSuccessor(0).? == &second_target);
1907 try testing.expect(first_target.hasNoPredecessors());
1908 try testing.expect(second_target.hasPredecessor(&source));
1909 }
1910
1911 test "setSuccessors stabilizes a borrowed predecessor slice" {
1912 const testing = std.testing;
1913 var limits = Context.Limits.testing;
1914 limits.operations.nested_bytes = 0;
1915 var ctx = try Context.init(testing.allocator, limits);
1916 var source = Block.init(testing.allocator);
1917 var alias_owner = Block.init(testing.allocator);
1918 var old_target = Block.init(testing.allocator);
1919 defer {
1920 ctx.deinit(testing.allocator);
1921 source.deinit();
1922 alias_owner.deinit();
1923 old_target.deinit();
1924 }
1925 try ctx.allowUnregistered();
1926
1927 var self_state = Operation.State.init("test.self_branch", .unknown);
1928 self_state.addSuccessors(&.{&alias_owner});
1929 try alias_owner.addOperation(try ctx.createOperation(self_state));
1930
1931 var branch_state = Operation.State.init("test.branch", .unknown);
1932 branch_state.addSuccessors(&.{&old_target});
1933 const branch = try ctx.createOperation(branch_state);
1934 try source.addOperation(branch);
1935
1936 try alias_owner.predecessors.shrinkToLen(testing.allocator);
1937 const borrowed = alias_owner.getPredecessors();
1938 try testing.expectError(error.OutOfMemory, branch.setSuccessors(borrowed));
1939 try testing.expect(branch.getSuccessor(0).? == &old_target);
1940 try testing.expect(old_target.hasPredecessor(&source));
1941 try testing.expect(!alias_owner.hasPredecessor(&source));
1942
1943 try branch.setSuccessors(&.{&alias_owner});
1944
1945 try testing.expectEqual(@as(usize, 1), branch.getNumSuccessors());
1946 try testing.expect(branch.getSuccessor(0).? == &alias_owner);
1947 try testing.expectEqual(@as(usize, 2), alias_owner.getNumPredecessors());
1948 try testing.expect(alias_owner.hasPredecessor(&source));
1949 try testing.expect(old_target.hasNoPredecessors());
1950 }
1951
1952 test "setSuccessors preserves another operation edge" {
1953 const testing = std.testing;
1954
1955 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1956 var source = Block.init(testing.allocator);
1957 var shared_target = Block.init(testing.allocator);
1958 var replacement_target = Block.init(testing.allocator);
1959 defer {
1960 ctx.deinit(testing.allocator);
1961 source.deinit();
1962 shared_target.deinit();
1963 replacement_target.deinit();
1964 }
1965 try ctx.allowUnregistered();
1966
1967 var first_state = Operation.State.init("test.first_branch", .unknown);
1968 first_state.addSuccessors(&.{&shared_target});
1969 const first = try ctx.createOperation(first_state);
1970 try source.addOperation(first);
1971 var second_state = Operation.State.init("test.second_branch", .unknown);
1972 second_state.addSuccessors(&.{&shared_target});
1973 const second = try ctx.createOperation(second_state);
1974 try source.addOperation(second);
1975
1976 try first.setSuccessors(&.{&replacement_target});
1977
1978 try testing.expect(shared_target.hasPredecessor(&source));
1979 try testing.expect(replacement_target.hasPredecessor(&source));
1980 source.removeOperation(second);
1981 try testing.expect(shared_target.hasNoPredecessors());
1982 try testing.expect(replacement_target.hasPredecessor(&source));
1983 }
1984
1985 test "moveToEnd preserves placement and CFG on allocation failure" {
1986 const testing = std.testing;
1987
1988 var failing = testing.FailingAllocator.init(testing.allocator, .{});
1989 const allocator = failing.allocator();
1990 var ctx = try Context.init(allocator, Context.Limits.testing);
1991 var source = Block.init(allocator);
1992 var destination = Block.init(allocator);
1993 var target = Block.init(allocator);
1994 defer {
1995 ctx.deinit(allocator);
1996 source.deinit();
1997 destination.deinit();
1998 target.deinit();
1999 }
2000 try ctx.allowUnregistered();
2001
2002 var state = Operation.State.init("test.branch", .unknown);
2003 state.addSuccessors(&.{&target});
2004 const branch = try ctx.createOperation(state);
2005 try source.addOperation(branch);
2006 try target.predecessors.shrinkToLen(allocator);
2007
2008 failing.fail_index = failing.alloc_index;
2009 failing.resize_fail_index = failing.resize_index;
2010 const result = branch.moveToEnd(&destination);
2011 failing.fail_index = std.math.maxInt(usize);
2012 failing.resize_fail_index = std.math.maxInt(usize);
2013
2014 try testing.expectError(error.OutOfMemory, result);
2015 try testing.expect(branch.parent_block == &source);
2016 const source_head: *Operation = @ptrCast(@alignCast(source.operations.head.?));
2017 const source_tail: *Operation = @ptrCast(@alignCast(source.operations.tail.?));
2018 try testing.expect(source_head == branch);
2019 try testing.expect(source_tail == branch);
2020 try testing.expect(destination.operations.isEmpty());
2021 try testing.expect(target.hasPredecessor(&source));
2022 try testing.expect(!target.hasPredecessor(&destination));
2023 }
2024
2025 test "eraseBlock rejects a target with a remaining operation edge" {
2026 const testing = std.testing;
2027
2028 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
2029 var region = core.context.initRegion(&ctx);
2030 const boundary = ctx.operationCreationBoundary();
2031 defer {
2032 ctx.eraseOperationsCreatedSince(boundary);
2033 region.deinit();
2034 ctx.deinit(testing.allocator);
2035 }
2036 try ctx.allowUnregistered();
2037
2038 const source = try region.addBlock();
2039 const target = try region.addBlock();
2040 var first_state = Operation.State.init("test.first_branch", .unknown);
2041 first_state.addSuccessors(&.{target});
2042 const first = try ctx.createOperation(first_state);
2043 try source.addOperation(first);
2044 var second_state = Operation.State.init("test.second_branch", .unknown);
2045 second_state.addSuccessors(&.{target});
2046 try source.addOperation(try ctx.createOperation(second_state));
2047
2048 source.removeOperation(first);
2049
2050 try testing.expect(target.hasPredecessor(source));
2051 try testing.expect(!region.eraseBlock(target));
2052 }
2053
2054 var materialize_called: bool = false;
2055
2056 fn materializeIdentity(_: *core.rewrite.PatternRewriter, value: *core.Value, _: core.Type) ?*core.Value {
2057 materialize_called = true;
2058 return value;
2059 }
2060
2061 test "pattern rewriter materialize uses type converter" {
2062 const testing = std.testing;
2063 const test_dialect = @import("../dialects/fixture/root.zig");
2064
2065 var arena = alloc_arena.Arena.init(std.testing.allocator);
2066 defer arena.deinit();
2067 const allocator = arena.allocator();
2068
2069 var ctx = try core.Context.init(allocator, core.Context.Limits.testing);
2070 defer ctx.deinit(allocator);
2071 try ctx.allowUnregistered();
2072
2073 const i32_type = try test_dialect.TestDialect.getI32Type(&ctx);
2074 var converter = core.rewrite.TypeConverter.init(allocator);
2075 defer converter.deinit();
2076 converter.setMaterialization(materializeIdentity);
2077
2078 var rewriter = core.rewrite.PatternRewriter.initWithTypeConverter(allocator, &ctx, &converter);
2079 defer rewriter.deinit();
2080
2081 var state = core.Operation.State.init("test.constant", core.Location.getUnknown());
2082 state.addTypes(&.{i32_type});
2083 const op = try ctx.createOperation(state);
2084 const value = op.getResult(0).?;
2085
2086 materialize_called = false;
2087 const result = rewriter.materializeConversion(value, i32_type);
2088 try testing.expect(materialize_called);
2089 try testing.expect(result == value);
2090 }
2091
2092 test "type converter addConversion overwrites existing mapping" {
2093 const testing = std.testing;
2094 const test_dialect = @import("../dialects/fixture/root.zig");
2095
2096 var arena = alloc_arena.Arena.init(std.testing.allocator);
2097 defer arena.deinit();
2098 const allocator = arena.allocator();
2099
2100 var ctx = try core.Context.init(allocator, core.Context.Limits.testing);
2101 defer ctx.deinit(allocator);
2102 try ctx.allowUnregistered();
2103
2104 const i32_type = try test_dialect.TestDialect.getI32Type(&ctx);
2105 const i64_type = try test_dialect.TestDialect.getI64Type(&ctx);
2106
2107 var converter = core.rewrite.TypeConverter.init(allocator);
2108 defer converter.deinit();
2109
2110 try converter.addConversion(i32_type, &.{i64_type});
2111 try testing.expect(converter.convertType(i32_type) != null);
2112 var converted = converter.convertType(i32_type).?;
2113 try testing.expectEqual(@as(usize, 1), converted.len);
2114 try testing.expect(converted[0].eql(i64_type));
2115
2116 try converter.addConversion(i32_type, &.{i32_type});
2117 try testing.expect(converter.convertType(i32_type) != null);
2118 converted = converter.convertType(i32_type).?;
2119 try testing.expectEqual(@as(usize, 1), converted.len);
2120 try testing.expect(converted[0].eql(i32_type));
2121 }
2122
2123 test "Choir parse preserves block argument identity in an scf loop" {
2124 const testing = std.testing;
2125 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
2126 defer ctx.deinit(testing.allocator);
2127 try core.dialects.loadDialectSpec(&ctx, dialects.BuiltinDialect.spec);
2128 try core.dialects.loadDialectSpec(&ctx, dialects.arith.spec);
2129 try core.dialects.loadDialectSpec(&ctx, dialects.ScfDialect.spec);
2130
2131 const text =
2132 \\builtin.module() {
2133 \\ ^bb0(%0: !arith.index, %1: !arith.index, %2: !arith.index, %3: !arith.f64):
2134 \\ %7 = scf.for(%0, %1, %2, %3): !arith.f64 {
2135 \\ ^bb1(%4: !arith.index, %5: !arith.f64):
2136 \\ scf.yield(%4)
2137 \\ }
2138 \\}
2139 \\
2140 ;
2141 const parsed = try core.parse.operation(&ctx, text);
2142 defer parsed.erase();
2143 try testing.expectError(
2144 error.ScfForYieldTypeMismatch,
2145 core.verify.verifyOperation(parsed, core.verify.default_options),
2146 );
2147 const dumped = try core.dump.operationAlloc(testing.allocator, parsed);
2148 defer testing.allocator.free(dumped);
2149 const expected =
2150 \\builtin.module() {
2151 \\ ^bb0(%0: !arith.index, %1: !arith.index, %2: !arith.index, %3: !arith.f64):
2152 \\ %4 = scf.for(%0, %1, %2, %3) : !arith.f64 {
2153 \\ ^bb0(%5: !arith.index, %6: !arith.f64):
2154 \\ scf.yield(%5)
2155 \\ }
2156 \\}
2157 \\
2158 ;
2159 try testing.expectEqualStrings(expected, dumped);
2160 }