lib/choir/src/core/verify.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const alloc_arena = @import("alloc_arena");
3 const Operation = @import("operation/root.zig").Operation;
4 const Block = @import("block.zig").Block;
5 const Region = @import("region.zig").Region;
6 const Location = @import("location.zig").Location;
7 const Value = @import("value.zig").Value;
8 const OpOperand = @import("value.zig").OpOperand;
9 const Attribute = @import("attribute.zig").Attribute;
10 const cfg = @import("cfg.zig");
11 const interfaces = @import("interfaces/root.zig");
12 const trait_definitions = @import("traits.zig");
13 const context_mod = @import("context/root.zig");
14 const structure_item_limit = 1_000_000;
15
16 pub const VerifyError = error{
17 OutOfMemory,
18 ParentBlockMismatch,
19 OperationListCorrupted,
20 SuccessorPredecessorMismatch,
21 SuccessorRegionMismatch,
22 EntryBlockSuccessor,
23 RegionParentMismatch,
24 UseDefChainBroken,
25 BlockParentMismatch,
26 BlockListCorrupted,
27 BlockArgumentOwnerMismatch,
28 RegionParentOpMismatch,
29 RegionSizeMismatch,
30 MissingTerminator,
31 OperationAfterTerminator,
32 InvalidPredecessor,
33 UseChainValueMismatch,
34 UseChainCycle,
35 InvalidLocalDominance,
36 OperandCountMismatch,
37 ResultCountMismatch,
38 RegionCountMismatch,
39 SuccessorCountMismatch,
40 InvalidOperandSegmentSizeAttribute,
41 InvalidResultSegmentSizeAttribute,
42 OperandSegmentSizeMismatch,
43 ResultSegmentSizeMismatch,
44 MissingRequiredAttribute,
45 OperandTypeConstraintMismatch,
46 ResultTypeConstraintMismatch,
47 GraphRegionMultipleBlocks,
48 };
49
50 pub const VerifyOptions = struct {
51 check_terminators: bool = false,
52
53 require_terminators: bool = false,
54
55 recursive: bool = true,
56
57 check_use_def: bool = true,
58
59 check_local_dominance: bool = true,
60
61 check_cfg: bool = true,
62
63 max_depth: usize = 0,
64 };
65
66 pub const default_options = VerifyOptions{
67 .check_terminators = true,
68 .recursive = true,
69 .check_use_def = true,
70 .check_local_dominance = true,
71 .check_cfg = true,
72 };
73
74 pub fn verifyOperationStructure(op: *Operation, options: VerifyOptions) VerifyError!void {
75 return verifyStructureWithDepth(.{ .operation = op }, options, 0, null);
76 }
77
78 const StructureTarget = union(enum) {
79 operation: *Operation,
80 region: *Region,
81 block: *Block,
82 };
83
84 fn verifyStructureWithDepth(
85 target: StructureTarget,
86 options: VerifyOptions,
87 depth: usize,
88 known_region_ssa_dominance: ?bool,
89 ) VerifyError!void {
90 if (options.max_depth > 0 and depth >= options.max_depth) {
91 return;
92 }
93
94 switch (target) {
95 .operation => |op| {
96 if (options.check_use_def) {
97 for (op.operands.items) |*operand| {
98 try verifyOperandUse(operand);
99 }
100 }
101
102 if (options.check_local_dominance) {
103 const may_have_ssa_dominance = known_region_ssa_dominance orelse block: {
104 const parent_block = op.parent_block orelse break :block true;
105 break :block blockMayHaveSSADominance(parent_block);
106 };
107 for (op.operands.items) |operand| {
108 try verifyOperandLocalDominance(op, operand.value, may_have_ssa_dominance);
109 }
110 }
111
112 if (options.check_cfg) {
113 try verifyOperationSuccessors(op);
114 }
115
116 for (op.regions.items) |*region| {
117 const expected_parent: *anyopaque = @ptrCast(op);
118 if (region.parent != expected_parent) {
119 return error.RegionParentMismatch;
120 }
121
122 if (options.recursive) {
123 try verifyStructureWithDepth(.{ .region = region }, options, depth + 1, null);
124 }
125 }
126 },
127 .region => |region| {
128 const may_have_ssa_dominance = if (options.check_local_dominance)
129 regionMayHaveSSADominance(region)
130 else
131 null;
132 var actual_count: usize = 0;
133 var current = region.blocks.head;
134 var prev_block: ?*Block = null;
135
136 while (current) |block| {
137 actual_count += 1;
138
139 if (actual_count > structure_item_limit) {
140 return error.BlockListCorrupted;
141 }
142
143 if (block.parent != @as(*anyopaque, @ptrCast(region))) {
144 return error.BlockParentMismatch;
145 }
146
147 if (block.prev != prev_block) {
148 return error.BlockListCorrupted;
149 }
150
151 if (options.recursive) {
152 try verifyStructureWithDepth(.{ .block = block }, options, depth, may_have_ssa_dominance);
153 }
154
155 prev_block = block;
156 current = block.next;
157 }
158
159 if (actual_count != region.blocks.size) {
160 return error.RegionSizeMismatch;
161 }
162
163 if (actual_count > 1 and regionKind(region) == .graph) {
164 return error.GraphRegionMultipleBlocks;
165 }
166
167 if (options.check_cfg and regionKind(region) == .ssacfg) {
168 if (region.getEntryBlock()) |entry| {
169 if (entry.predecessors.items.len != 0) return error.EntryBlockSuccessor;
170 }
171 }
172
173 if (region.blocks.tail != prev_block) {
174 return error.BlockListCorrupted;
175 }
176 },
177 .block => |block| {
178 if (options.check_local_dominance) _ = block.sealOperationOrder();
179 const may_have_ssa_dominance = if (options.check_local_dominance)
180 known_region_ssa_dominance orelse blockMayHaveSSADominance(block)
181 else
182 null;
183 for (block.arguments.items) |arg| {
184 if (arg.kind == .block_argument) {
185 const owner: *Block = @ptrCast(@alignCast(arg.kind.block_argument.owner));
186 if (owner != block) {
187 return error.BlockArgumentOwnerMismatch;
188 }
189 }
190 }
191
192 if (!block.operations.isEmpty()) {
193 const head: *Operation = @ptrCast(@alignCast(block.operations.head.?));
194 const tail: *Operation = @ptrCast(@alignCast(block.operations.tail.?));
195
196 if (head.prev_op != null) {
197 return error.OperationListCorrupted;
198 }
199
200 if (tail.next_op != null) {
201 return error.OperationListCorrupted;
202 }
203
204 var op: ?*Operation = head;
205 var prev: ?*Operation = null;
206 var saw_terminator = false;
207 var count: usize = 0;
208
209 while (op) |current| {
210 count += 1;
211
212 if (count > structure_item_limit) {
213 return error.OperationListCorrupted;
214 }
215
216 if (current.parent_block != block) {
217 return error.ParentBlockMismatch;
218 }
219
220 if (current.prev_op != prev) {
221 return error.OperationListCorrupted;
222 }
223
224 if (options.check_terminators) {
225 if (saw_terminator) {
226 return error.OperationAfterTerminator;
227 }
228 if (current.getTraits().is_terminator) {
229 saw_terminator = true;
230 }
231 }
232
233 if (options.recursive) {
234 try verifyStructureWithDepth(.{ .operation = current }, options, depth, may_have_ssa_dominance);
235 }
236
237 prev = current;
238 op = current.next_op;
239 }
240
241 if (prev != tail) {
242 return error.OperationListCorrupted;
243 }
244
245 if (options.check_terminators and
246 options.require_terminators and
247 count > 0 and
248 !saw_terminator and
249 !blockAllowsMissingTerminator(block))
250 {
251 return error.MissingTerminator;
252 }
253 }
254
255 if (options.check_cfg) {
256 try verifyPredecessors(block);
257 }
258 },
259 }
260 }
261
262 fn verifyOperationSuccessors(op: *Operation) VerifyError!void {
263 const parent = op.parent_block orelse return;
264 const parent_region = blockParentRegion(parent);
265
266 for (op.successors.items) |successor| {
267 if (!successor.hasPredecessor(parent)) {
268 return error.SuccessorPredecessorMismatch;
269 }
270
271 const region = parent_region orelse continue;
272 const successor_region = blockParentRegion(successor) orelse return error.SuccessorRegionMismatch;
273 if (successor_region != region) {
274 return error.SuccessorRegionMismatch;
275 }
276 if (regionKind(region) == .ssacfg and region.getEntryBlock() == successor) {
277 return error.EntryBlockSuccessor;
278 }
279 }
280 }
281
282 fn verifyOperandUse(operand: *const OpOperand) VerifyError!void {
283 const back = operand.back orelse return error.UseDefChainBroken;
284 const linked = back.* orelse return error.UseDefChainBroken;
285 if (linked != operand) {
286 return error.UseDefChainBroken;
287 }
288 if (operand.next_use) |next| {
289 const next_back = next.back orelse return error.UseDefChainBroken;
290 if (next_back != &linked.next_use) {
291 return error.UseDefChainBroken;
292 }
293 if (next.value != operand.value) {
294 return error.UseChainValueMismatch;
295 }
296 }
297 }
298
299 fn verifyOperandLocalDominance(user: *const Operation, value: *const Value, may_have_ssa_dominance: bool) VerifyError!void {
300 const user_block = user.parent_block orelse return;
301 return switch (value.kind) {
302 .block_argument => |info| {
303 const defining_block: *const Block = @ptrCast(@alignCast(info.owner));
304 try verifyBlockDominatesUse(defining_block, user_block, may_have_ssa_dominance);
305 },
306 .op_result => |info| {
307 const defining_op: *const Operation = @ptrCast(@alignCast(info.owner));
308 const defining_block = defining_op.parent_block orelse return;
309 if (user_block != defining_block) {
310 return verifyBlockDominatesUse(defining_block, user_block, may_have_ssa_dominance);
311 }
312 if (defining_op == user) return error.InvalidLocalDominance;
313 if (!may_have_ssa_dominance) return;
314
315 if (!defining_op.isBeforeInBlock(user)) {
316 return error.InvalidLocalDominance;
317 }
318 return;
319 },
320 };
321 }
322
323 fn verifyBlockDominatesUse(defining_block: *const Block, user_block: *const Block, may_have_ssa_dominance: bool) VerifyError!void {
324 if (defining_block == user_block) return;
325 const defining_region = blockParentRegion(defining_block) orelse return;
326 const user_region = blockParentRegion(user_block) orelse return;
327 if (defining_region != user_region) return;
328 if (!may_have_ssa_dominance) return;
329 if (!try blockDominates(defining_region, defining_block, user_block)) {
330 return error.InvalidLocalDominance;
331 }
332 }
333
334 fn blockMayHaveSSADominance(block: *const Block) bool {
335 const region = block.getParentRegion() orelse return true;
336 return regionMayHaveSSADominance(region);
337 }
338
339 fn blockParentRegion(block: *const Block) ?*const Region {
340 return block.getParentRegion();
341 }
342
343 pub fn regionMayHaveSSADominance(region: *const Region) bool {
344 const parent_op = regionParentOperation(region) orelse return true;
345 const index = regionIndex(parent_op, region) orelse return true;
346 if (parent_op.getInterface(interfaces.RegionKindInterface)) |vtable| {
347 return vtable.hasSSADominance(@ptrCast(parent_op), index);
348 }
349 return !parent_op.getTraits().has_only_graph_regions;
350 }
351
352 pub fn regionKind(region: *const Region) interfaces.RegionKind {
353 const parent_op = regionParentOperation(region) orelse return .ssacfg;
354 const index = regionIndex(parent_op, region) orelse return .ssacfg;
355 if (parent_op.getInterface(interfaces.RegionKindInterface)) |vtable| {
356 return vtable.getRegionKind(@ptrCast(parent_op), index);
357 }
358 if (parent_op.getTraits().has_only_graph_regions) return .graph;
359 return .ssacfg;
360 }
361
362 fn regionParentOperation(region: *const Region) ?*Operation {
363 return region.getParentOperation();
364 }
365
366 fn regionIndex(parent_op: *Operation, region: *const Region) ?usize {
367 for (parent_op.regions.items, 0..) |*candidate, index| {
368 if (candidate == region) return index;
369 }
370 return null;
371 }
372
373 fn blockDominates(region: *const Region, defining_block: *const Block, user_block: *const Block) VerifyError!bool {
374 const entry = region.getEntryBlock() orelse return false;
375 if (entry == defining_block) return true;
376 if (entry == user_block) return false;
377
378 var visited: std.AutoHashMap(*const Block, void) = .init(region.allocator);
379 defer visited.deinit();
380 var stack: std.ArrayList(*const Block) = .empty;
381 defer stack.deinit(region.allocator);
382
383 try visited.put(entry, {});
384 try stack.append(region.allocator, entry);
385
386 while (stack.pop()) |block| {
387 const terminator_any = block.getTerminator() orelse continue;
388 const terminator: *const Operation = @ptrCast(@alignCast(terminator_any));
389 for (terminator.successors.items) |successor| {
390 if (successor == defining_block) continue;
391 if (successor == user_block) return false;
392 if (blockParentRegion(successor) != region) continue;
393 const gop = try visited.getOrPut(successor);
394 if (!gop.found_existing) try stack.append(region.allocator, successor);
395 }
396 }
397
398 return true;
399 }
400
401 pub fn verifyBlockStructure(block: *Block, options: VerifyOptions) VerifyError!void {
402 return verifyStructureWithDepth(.{ .block = block }, options, 0, null);
403 }
404
405 fn blockAllowsMissingTerminator(block: *const Block) bool {
406 const region = block.getParentRegion() orelse return false;
407 if (!region.hasOneBlock()) return false;
408 const parent_op = region.getParentOperation() orelse return false;
409 return parent_op.getTraits().has_no_terminator;
410 }
411
412 fn verifyPredecessors(block: *Block) VerifyError!void {
413 const region = blockParentRegion(block);
414 for (block.predecessors.items, 0..) |pred, index| {
415 for (block.predecessors.items[0..index]) |prior| {
416 if (prior == pred) return error.InvalidPredecessor;
417 }
418 if (blockParentRegion(pred) != region) {
419 return error.SuccessorRegionMismatch;
420 }
421 if (!try cfg.containsBounded(
422 pred,
423 block,
424 structure_item_limit,
425 )) return error.InvalidPredecessor;
426 }
427 }
428
429 pub fn verifyRegionStructure(region: *Region, options: VerifyOptions) VerifyError!void {
430 return verifyStructureWithDepth(.{ .region = region }, options, 0, null);
431 }
432
433 pub fn verifyOperation(op: *Operation, options: VerifyOptions) !void {
434 try verifyOperationStructure(op, options);
435 try runOperationVerifiers(op, options);
436 }
437
438 pub fn verifyBlock(block: *Block, options: VerifyOptions) VerifyError!void {
439 return verifyBlockStructure(block, options);
440 }
441
442 pub fn verifyRegion(region: *Region, options: VerifyOptions) VerifyError!void {
443 return verifyRegionStructure(region, options);
444 }
445
446 pub fn verify(op: *Operation, options: VerifyOptions) !void {
447 return verifyOperation(op, options);
448 }
449
450 pub fn verifyValue(value: *const Value) VerifyError!void {
451 var slow = value.first_use;
452 var fast = value.first_use;
453 var count: usize = 0;
454
455 while (slow) |use| {
456 count += 1;
457
458 if (use.value != value) {
459 return error.UseChainValueMismatch;
460 }
461
462 slow = use.next_use;
463
464 if (fast) |f| {
465 fast = f.next_use;
466 if (fast) |ff| fast = ff.next_use;
467 }
468 if (slow != null and slow == fast) {
469 return error.UseChainCycle;
470 }
471
472 if (count > structure_item_limit) {
473 return error.UseChainCycle;
474 }
475 }
476 }
477
478 pub const VerifyOpInterface = struct {
479 pub const interface_name = "ir.interface.verify_op";
480 pub const id: interfaces.InterfaceId = interfaces.interfaceId(interface_name);
481
482 pub const VTable = struct {
483 verify: *const fn (op_ptr: *const anyopaque) anyerror!void,
484 };
485
486 pub fn entry(vtable: *const VTable) interfaces.InterfaceEntry {
487 return .{ .id = id, .vtable = vtable };
488 }
489
490 pub fn vtableFor(comptime verify_fn: *const fn (op_ptr: *const anyopaque) anyerror!void) *const VTable {
491 return &.{ .verify = verify_fn };
492 }
493
494 pub fn entryFor(comptime verify_fn: *const fn (op_ptr: *const anyopaque) anyerror!void) interfaces.InterfaceEntry {
495 return entry(vtableFor(verify_fn));
496 }
497 };
498
499 pub const VerifyRegionOpInterface = struct {
500 pub const interface_name = "ir.interface.verify_region_op";
501 pub const id: interfaces.InterfaceId = interfaces.interfaceId(interface_name);
502
503 pub const VTable = struct {
504 verify: *const fn (op_ptr: *const anyopaque) anyerror!void,
505 };
506
507 pub fn entry(vtable: *const VTable) interfaces.InterfaceEntry {
508 return .{ .id = id, .vtable = vtable };
509 }
510
511 pub fn vtableFor(comptime verify_fn: *const fn (op_ptr: *const anyopaque) anyerror!void) *const VTable {
512 return &.{ .verify = verify_fn };
513 }
514
515 pub fn entryFor(comptime verify_fn: *const fn (op_ptr: *const anyopaque) anyerror!void) interfaces.InterfaceEntry {
516 return entry(vtableFor(verify_fn));
517 }
518 };
519
520 fn resolveOpInfo(op: *Operation) ?*const interfaces.OperationInfo {
521 return op.getRegisteredInfo();
522 }
523
524 fn test_segment_attribute(context: anytype, comptime values: []const i64) !Attribute {
525 var attributes: [values.len]Attribute = undefined;
526 for (values, 0..) |value, index| {
527 attributes[index] = try context.getI64Attr(value);
528 }
529 return context.getArrayAttr(attributes[0..]);
530 }
531
532 pub fn runTraitVerifiers(op: *Operation) !void {
533 const info = resolveOpInfo(op) orelse return;
534 for (info.getDynamicTraitIds()) |trait_id| {
535 const vtable = op.context.lookupTrait(trait_id) orelse continue;
536 if (vtable.verify) |verify_fn| {
537 try verify_fn(@ptrCast(op));
538 }
539 }
540 }
541
542 pub fn runRegionTraitVerifiers(op: *Operation) !void {
543 return runRegionTraitVerifiersWithIsolation(op, null);
544 }
545
546 const IsolationFrame = struct {
547 root: *const Operation,
548 invalid: bool = false,
549 };
550
551 fn runRegionTraitVerifiersWithIsolation(op: *Operation, isolation: ?*const IsolationFrame) !void {
552 const info = resolveOpInfo(op) orelse return;
553 for (info.getDynamicTraitIds()) |trait_id| {
554 const vtable = op.context.lookupTrait(trait_id) orelse continue;
555 if (trait_id == trait_definitions.IsolatedFromAbove.id and vtable == &trait_definitions.IsolatedFromAbove.vtable) {
556 if (isolation) |frame| {
557 if (frame.invalid) return trait_definitions.TraitError.IsolatedFromAbove;
558 continue;
559 }
560 }
561 if (vtable.verify_regions) |verify_fn| {
562 try verify_fn(@ptrCast(op));
563 }
564 }
565 }
566
567 pub fn verifyRegisteredOperationShape(op: *Operation) VerifyError!void {
568 const info = resolveOpInfo(op) orelse return;
569 const shape = info.shape;
570 if (!shape.hasConstraints()) return;
571
572 if (!shape.operands.allows(op.operands.items.len)) {
573 return error.OperandCountMismatch;
574 }
575 if (!shape.results.allows(op.results.items.len)) {
576 return error.ResultCountMismatch;
577 }
578 if (!shape.regions.allows(op.regions.items.len)) {
579 return error.RegionCountMismatch;
580 }
581 if (!shape.successors.allows(op.successors.items.len)) {
582 return error.SuccessorCountMismatch;
583 }
584 }
585
586 pub fn verifyRegisteredOperationRequiredAttributes(op: *Operation) VerifyError!void {
587 const info = resolveOpInfo(op) orelse return;
588 for (info.getRequiredAttributeNames()) |attr_name| {
589 if (op.getAttr(attr_name) == null) return error.MissingRequiredAttribute;
590 }
591 }
592
593 pub fn verifyRegisteredOperationSegments(op: *Operation) VerifyError!void {
594 const info = resolveOpInfo(op) orelse return;
595 if (info.getOperandSegments()) |segment_spec| {
596 try verifyRegisteredOperationSegmentSpec(
597 op,
598 segment_spec,
599 op.operands.items.len,
600 error.InvalidOperandSegmentSizeAttribute,
601 error.OperandSegmentSizeMismatch,
602 );
603 }
604 if (info.getResultSegments()) |segment_spec| {
605 try verifyRegisteredOperationSegmentSpec(
606 op,
607 segment_spec,
608 op.results.items.len,
609 error.InvalidResultSegmentSizeAttribute,
610 error.ResultSegmentSizeMismatch,
611 );
612 }
613 }
614
615 fn verifyRegisteredOperationSegmentSpec(
616 op: *Operation,
617 segment_spec: interfaces.OperationSegmentSpec,
618 actual_count: usize,
619 invalid_error: VerifyError,
620 mismatch_error: VerifyError,
621 ) VerifyError!void {
622 const attr = op.getAttrAs(Attribute.ArrayAttr, segment_spec.attribute_name) orelse return invalid_error;
623 const values = attr.getValues();
624 if (values.len != segment_spec.segments.len) return invalid_error;
625
626 var total: usize = 0;
627 for (values, segment_spec.segments) |value, range| {
628 const int_attr = value.cast(Attribute.IntegerAttr) orelse return invalid_error;
629 const size = std.math.cast(usize, int_attr.getValue()) orelse return invalid_error;
630 if (!range.allows(size)) return mismatch_error;
631 total = std.math.add(usize, total, size) catch return mismatch_error;
632 }
633
634 if (total != actual_count) return mismatch_error;
635 }
636
637 pub fn verifyRegisteredOperationTypeConstraints(op: *Operation) VerifyError!void {
638 const info = resolveOpInfo(op) orelse return;
639 for (info.getOperandTypeConstraints()) |constraint| {
640 if (constraint.index >= op.operands.items.len) return error.OperandTypeConstraintMismatch;
641 if (!operationTypeConstraintAllows(constraint, op.operands.items[constraint.index].value.type)) {
642 return error.OperandTypeConstraintMismatch;
643 }
644 }
645 for (info.getResultTypeConstraints()) |constraint| {
646 if (constraint.index >= op.results.items.len) return error.ResultTypeConstraintMismatch;
647 if (!operationTypeConstraintAllows(constraint, op.results.items[constraint.index].type)) {
648 return error.ResultTypeConstraintMismatch;
649 }
650 }
651 }
652
653 fn operationTypeConstraintAllows(constraint: interfaces.OperationTypeConstraint, typ: @import("type.zig").Type) bool {
654 const type_name = typ.getDialectTypeName() orelse return false;
655 if (!std.mem.eql(u8, type_name, constraint.type_name)) return false;
656 return constraint.allow_parameterized or typ.getDialectParamKey() == null;
657 }
658
659 fn runOperationVerifiers(op: *Operation, options: VerifyOptions) !void {
660 return runOperationVerifiersWithinIsolation(op, options, null);
661 }
662
663 fn runOperationVerifiersWithinIsolation(op: *Operation, options: VerifyOptions, active_isolation: ?*IsolationFrame) !void {
664 if (active_isolation) |frame| {
665 trait_definitions.verifyOperandsWithin(frame.root, op) catch |err| switch (err) {
666 trait_definitions.TraitError.IsolatedFromAbove => frame.invalid = true,
667 else => return err,
668 };
669 }
670
671 try verifyRegisteredOperationShape(op);
672 try verifyRegisteredOperationSegments(op);
673 try verifyRegisteredOperationRequiredAttributes(op);
674 try verifyRegisteredOperationTypeConstraints(op);
675 try interfaces.effects.verify(op);
676 try runTraitVerifiers(op);
677
678 if (op.getInterface(VerifyOpInterface)) |vtable| {
679 try vtable.verify(@ptrCast(op));
680 }
681
682 const info = resolveOpInfo(op);
683 const starts_isolation = if (info) |registered|
684 registered.hasTraitId(trait_definitions.IsolatedFromAbove.id) and
685 op.context.lookupTrait(trait_definitions.IsolatedFromAbove.id) == &trait_definitions.IsolatedFromAbove.vtable
686 else
687 false;
688 var isolation = IsolationFrame{ .root = op };
689 const nested_isolation = if (starts_isolation) &isolation else active_isolation;
690
691 if (options.recursive) {
692 for (op.regions.items) |*region| {
693 var block_iter = region.getBlocks();
694 while (block_iter.next()) |block| {
695 var op_node: ?*anyopaque = block.operations.head;
696 while (op_node) |node| {
697 const nested_op: *Operation = @ptrCast(@alignCast(node));
698 try runOperationVerifiersWithinIsolation(nested_op, options, nested_isolation);
699 op_node = nested_op.next_op;
700 }
701 }
702 }
703 }
704
705 if (options.recursive) {
706 try runRegionTraitVerifiersWithIsolation(op, if (starts_isolation) &isolation else null);
707
708 if (op.getInterface(VerifyRegionOpInterface)) |vtable| {
709 try vtable.verify(@ptrCast(op));
710 }
711 }
712 }
713
714 test "verify empty operation" {
715 const testing = std.testing;
716 const Context = @import("context/root.zig").Context;
717
718 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
719 defer ctx.deinit(testing.allocator);
720 try ctx.allowUnregistered();
721
722 const state = Operation.State.init("test.empty", .unknown);
723 const op = try ctx.createOperation(state);
724
725 try verifyOperation(op, default_options);
726 }
727
728 test "verify operation with result" {
729 const testing = std.testing;
730 const Context = @import("context/root.zig").Context;
731
732 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
733 defer ctx.deinit(testing.allocator);
734 try ctx.allowUnregistered();
735
736 const ty = try ctx.getDialectTypeFromName("test.i32");
737 var state = Operation.State.init("test.const", .unknown);
738 state.addTypes(&.{ty});
739
740 const op = try ctx.createOperation(state);
741 try verifyOperation(op, default_options);
742 }
743
744 test "verify operation with operand use-def chain" {
745 const testing = std.testing;
746 const Context = @import("context/root.zig").Context;
747
748 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
749 defer ctx.deinit(testing.allocator);
750 try ctx.allowUnregistered();
751
752 const ty = try ctx.getDialectTypeFromName("test.i32");
753
754 var producer_state = Operation.State.init("test.producer", .unknown);
755 producer_state.addTypes(&.{ty});
756 const producer = try ctx.createOperation(producer_state);
757
758 const result = producer.getResult(0).?;
759 var consumer_state = Operation.State.init("test.consumer", .unknown);
760 consumer_state.addOperands(&.{result});
761 const consumer = try ctx.createOperation(consumer_state);
762
763 try verifyOperation(producer, default_options);
764 try verifyOperation(consumer, default_options);
765 }
766
767 test "verify block with operations" {
768 const testing = std.testing;
769 const Context = @import("context/root.zig").Context;
770
771 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
772 defer ctx.deinit(testing.allocator);
773 try ctx.allowUnregistered();
774
775 var block = Block.init(testing.allocator);
776 defer block.deinit();
777
778 const state1 = Operation.State.init("test.op1", .unknown);
779 const op1 = try ctx.createOperation(state1);
780 try block.addOperation(op1);
781
782 const state2 = Operation.State.init("test.op2", .unknown);
783 const op2 = try ctx.createOperation(state2);
784 try block.addOperation(op2);
785
786 var options = default_options;
787 options.check_terminators = false;
788 try verifyBlock(&block, options);
789 }
790
791 test "verify region with blocks" {
792 const testing = std.testing;
793 const Context = @import("context/root.zig").Context;
794
795 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
796 defer ctx.deinit(testing.allocator);
797 try ctx.allowUnregistered();
798
799 var region = context_mod.initRegion(&ctx);
800 defer region.deinit();
801
802 _ = try region.addBlock();
803 _ = try region.addBlock();
804
805 var options = default_options;
806 options.check_terminators = false;
807 try verifyRegion(®ion, options);
808
809 try testing.expectEqual(@as(usize, 2), region.blocks.size);
810 }
811
812 test "verify use-def chain consistency" {
813 const testing = std.testing;
814 const Context = @import("context/root.zig").Context;
815
816 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
817 defer ctx.deinit(testing.allocator);
818 try ctx.allowUnregistered();
819
820 const ty = try ctx.getDialectTypeFromName("test.i32");
821
822 var producer_state = Operation.State.init("test.producer", .unknown);
823 producer_state.addTypes(&.{ty});
824 const producer = try ctx.createOperation(producer_state);
825
826 const result = producer.getResult(0).?;
827
828 var consumer1_state = Operation.State.init("test.consumer1", .unknown);
829 consumer1_state.addOperands(&.{result});
830 const consumer1 = try ctx.createOperation(consumer1_state);
831
832 var consumer2_state = Operation.State.init("test.consumer2", .unknown);
833 consumer2_state.addOperands(&.{result});
834 _ = try ctx.createOperation(consumer2_state);
835
836 try verifyValue(result);
837
838 try testing.expectEqual(@as(usize, 2), result.getNumUses());
839
840 try verifyOperandUse(consumer1.getOpOperand(0).?);
841 }
842
843 test "verify accepts same-block use after definition" {
844 const testing = std.testing;
845 const Context = @import("context/root.zig").Context;
846
847 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
848 defer ctx.deinit(testing.allocator);
849 try ctx.allowUnregistered();
850
851 var block = Block.init(testing.allocator);
852 defer block.deinit();
853
854 const ty = try ctx.getDialectTypeFromName("test.i32");
855 var producer_state = Operation.State.init("test.producer", .unknown);
856 producer_state.addTypes(&.{ty});
857 const producer = try ctx.createOperation(producer_state);
858 try block.addOperation(producer);
859
860 var consumer_state = Operation.State.init("test.consumer", .unknown);
861 consumer_state.addOperands(&.{producer.getResult(0).?});
862 const consumer = try ctx.createOperation(consumer_state);
863 try block.addOperation(consumer);
864
865 var options = default_options;
866 options.check_terminators = false;
867 try verifyBlock(&block, options);
868 }
869
870 test "verify detects same-block use before definition" {
871 const testing = std.testing;
872 const Context = @import("context/root.zig").Context;
873
874 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
875 defer ctx.deinit(testing.allocator);
876 try ctx.allowUnregistered();
877
878 var block = Block.init(testing.allocator);
879 defer block.deinit();
880
881 const ty = try ctx.getDialectTypeFromName("test.i32");
882 var producer_state = Operation.State.init("test.producer", .unknown);
883 producer_state.addTypes(&.{ty});
884 const producer = try ctx.createOperation(producer_state);
885
886 var consumer_state = Operation.State.init("test.consumer", .unknown);
887 consumer_state.addOperands(&.{producer.getResult(0).?});
888 const consumer = try ctx.createOperation(consumer_state);
889
890 try block.addOperation(consumer);
891 try block.addOperation(producer);
892
893 var options = default_options;
894 options.check_terminators = false;
895 try testing.expectError(error.InvalidLocalDominance, verifyBlock(&block, options));
896 }
897
898 test "verify can skip local dominance while preserving use-def checks" {
899 const testing = std.testing;
900 const Context = @import("context/root.zig").Context;
901
902 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
903 defer ctx.deinit(testing.allocator);
904 try ctx.allowUnregistered();
905
906 var block = Block.init(testing.allocator);
907 defer block.deinit();
908
909 const ty = try ctx.getDialectTypeFromName("test.i32");
910 var producer_state = Operation.State.init("test.producer", .unknown);
911 producer_state.addTypes(&.{ty});
912 const producer = try ctx.createOperation(producer_state);
913
914 var consumer_state = Operation.State.init("test.consumer", .unknown);
915 consumer_state.addOperands(&.{producer.getResult(0).?});
916 const consumer = try ctx.createOperation(consumer_state);
917
918 try block.addOperation(consumer);
919 try block.addOperation(producer);
920
921 var options = default_options;
922 options.check_terminators = false;
923 options.check_local_dominance = false;
924 try verifyBlock(&block, options);
925 }
926
927 test "graph region accepts same-block use before definition" {
928 const testing = std.testing;
929 const Context = @import("context/root.zig").Context;
930 const core_traits = @import("traits.zig");
931
932 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
933 defer ctx.deinit(testing.allocator);
934 try ctx.allowUnregistered();
935
936 const owner_name = "region_kind.graph_owner";
937 try core_traits.registerOperationTrait(&ctx, owner_name, core_traits.HasOnlyGraphRegion);
938
939 var owner_state = Operation.State.init(owner_name, .unknown);
940 owner_state.addRegion();
941 const owner = try ctx.createOperation(owner_state);
942 const region = owner.getRegion(0).?;
943 const block = try region.addBlock();
944
945 const ty = try ctx.getDialectTypeFromName("test.i32");
946 var producer_state = Operation.State.init("region_kind.producer", .unknown);
947 producer_state.addTypes(&.{ty});
948 const producer = try ctx.createOperation(producer_state);
949
950 var consumer_state = Operation.State.init("region_kind.consumer", .unknown);
951 consumer_state.addOperands(&.{producer.getResult(0).?});
952 const consumer = try ctx.createOperation(consumer_state);
953
954 try block.addOperation(consumer);
955 try block.addOperation(producer);
956
957 try testing.expectEqual(interfaces.RegionKind.graph, regionKind(region));
958 try testing.expect(!regionMayHaveSSADominance(region));
959
960 var options = default_options;
961 options.check_terminators = false;
962 try verifyOperation(owner, options);
963 }
964
965 test "ssacfg region keeps same-block dominance requirement" {
966 const testing = std.testing;
967 const Context = @import("context/root.zig").Context;
968
969 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
970 defer ctx.deinit(testing.allocator);
971 try ctx.allowUnregistered();
972
973 var owner_state = Operation.State.init("region_kind.ssacfg_owner", .unknown);
974 owner_state.addRegion();
975 const owner = try ctx.createOperation(owner_state);
976 const region = owner.getRegion(0).?;
977 const block = try region.addBlock();
978
979 const ty = try ctx.getDialectTypeFromName("test.i32");
980 var producer_state = Operation.State.init("region_kind.producer", .unknown);
981 producer_state.addTypes(&.{ty});
982 const producer = try ctx.createOperation(producer_state);
983
984 var consumer_state = Operation.State.init("region_kind.consumer", .unknown);
985 consumer_state.addOperands(&.{producer.getResult(0).?});
986 const consumer = try ctx.createOperation(consumer_state);
987
988 try block.addOperation(consumer);
989 try block.addOperation(producer);
990
991 try testing.expectEqual(interfaces.RegionKind.ssacfg, regionKind(region));
992 try testing.expect(regionMayHaveSSADominance(region));
993
994 var options = default_options;
995 options.check_terminators = false;
996 try testing.expectError(error.InvalidLocalDominance, verifyOperation(owner, options));
997 }
998
999 test "ssacfg region accepts cross-block use dominated by entry definition" {
1000 const testing = std.testing;
1001 const Context = @import("context/root.zig").Context;
1002
1003 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1004 defer ctx.deinit(testing.allocator);
1005 try ctx.allowUnregistered();
1006
1007 var owner_state = Operation.State.init("dominance.owner", .unknown);
1008 owner_state.addRegion();
1009 const owner = try ctx.createOperation(owner_state);
1010 const region = owner.getRegion(0).?;
1011 const entry = try region.addBlock();
1012 const then_block = try region.addBlock();
1013 const else_block = try region.addBlock();
1014 const merge = try region.addBlock();
1015
1016 const ty = try ctx.getDialectTypeFromName("dominance.i32");
1017 var producer_state = Operation.State.init("dominance.producer", .unknown);
1018 producer_state.addTypes(&.{ty});
1019 const producer = try ctx.createOperation(producer_state);
1020 try entry.addOperation(producer);
1021
1022 var entry_branch_state = Operation.State.init("dominance.entry_branch", .unknown);
1023 entry_branch_state.addSuccessors(&.{ then_block, else_block });
1024 try entry.addOperation(try ctx.createOperation(entry_branch_state));
1025
1026 var then_branch_state = Operation.State.init("dominance.then_branch", .unknown);
1027 then_branch_state.addSuccessors(&.{merge});
1028 try then_block.addOperation(try ctx.createOperation(then_branch_state));
1029
1030 var else_branch_state = Operation.State.init("dominance.else_branch", .unknown);
1031 else_branch_state.addSuccessors(&.{merge});
1032 try else_block.addOperation(try ctx.createOperation(else_branch_state));
1033
1034 var consumer_state = Operation.State.init("dominance.consumer", .unknown);
1035 consumer_state.addOperands(&.{producer.getResult(0).?});
1036 try merge.addOperation(try ctx.createOperation(consumer_state));
1037
1038 var options = default_options;
1039 options.check_terminators = false;
1040 try verifyOperation(owner, options);
1041 }
1042
1043 test "ssacfg region rejects cross-block use without dominance" {
1044 const testing = std.testing;
1045 const Context = @import("context/root.zig").Context;
1046
1047 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1048 defer ctx.deinit(testing.allocator);
1049 try ctx.allowUnregistered();
1050
1051 var owner_state = Operation.State.init("dominance.owner", .unknown);
1052 owner_state.addRegion();
1053 const owner = try ctx.createOperation(owner_state);
1054 const region = owner.getRegion(0).?;
1055 const entry = try region.addBlock();
1056 const then_block = try region.addBlock();
1057 const else_block = try region.addBlock();
1058 const merge = try region.addBlock();
1059
1060 var entry_branch_state = Operation.State.init("dominance.entry_branch", .unknown);
1061 entry_branch_state.addSuccessors(&.{ then_block, else_block });
1062 try entry.addOperation(try ctx.createOperation(entry_branch_state));
1063
1064 const ty = try ctx.getDialectTypeFromName("dominance.i32");
1065 var producer_state = Operation.State.init("dominance.producer", .unknown);
1066 producer_state.addTypes(&.{ty});
1067 const producer = try ctx.createOperation(producer_state);
1068 try then_block.addOperation(producer);
1069
1070 var then_branch_state = Operation.State.init("dominance.then_branch", .unknown);
1071 then_branch_state.addSuccessors(&.{merge});
1072 try then_block.addOperation(try ctx.createOperation(then_branch_state));
1073
1074 var else_branch_state = Operation.State.init("dominance.else_branch", .unknown);
1075 else_branch_state.addSuccessors(&.{merge});
1076 try else_block.addOperation(try ctx.createOperation(else_branch_state));
1077
1078 var consumer_state = Operation.State.init("dominance.consumer", .unknown);
1079 consumer_state.addOperands(&.{producer.getResult(0).?});
1080 try merge.addOperation(try ctx.createOperation(consumer_state));
1081
1082 var options = default_options;
1083 options.check_terminators = false;
1084 try testing.expectError(error.InvalidLocalDominance, verifyOperation(owner, options));
1085 }
1086
1087 test "ssacfg region rejects cross-block block argument use without dominance" {
1088 const testing = std.testing;
1089 const Context = @import("context/root.zig").Context;
1090
1091 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1092 defer ctx.deinit(testing.allocator);
1093 try ctx.allowUnregistered();
1094
1095 var owner_state = Operation.State.init("dominance.owner", .unknown);
1096 owner_state.addRegion();
1097 const owner = try ctx.createOperation(owner_state);
1098 const region = owner.getRegion(0).?;
1099 const entry = try region.addBlock();
1100 const then_block = try region.addBlock();
1101 const else_block = try region.addBlock();
1102 const merge = try region.addBlock();
1103
1104 var entry_branch_state = Operation.State.init("dominance.entry_branch", .unknown);
1105 entry_branch_state.addSuccessors(&.{ then_block, else_block });
1106 try entry.addOperation(try ctx.createOperation(entry_branch_state));
1107
1108 const ty = try ctx.getDialectTypeFromName("dominance.i32");
1109 const then_arg = try then_block.addArgument(ty, .unknown);
1110
1111 var then_branch_state = Operation.State.init("dominance.then_branch", .unknown);
1112 then_branch_state.addSuccessors(&.{merge});
1113 try then_block.addOperation(try ctx.createOperation(then_branch_state));
1114
1115 var else_branch_state = Operation.State.init("dominance.else_branch", .unknown);
1116 else_branch_state.addSuccessors(&.{merge});
1117 try else_block.addOperation(try ctx.createOperation(else_branch_state));
1118
1119 var consumer_state = Operation.State.init("dominance.consumer", .unknown);
1120 consumer_state.addOperands(&.{then_arg});
1121 try merge.addOperation(try ctx.createOperation(consumer_state));
1122
1123 var options = default_options;
1124 options.check_terminators = false;
1125 try testing.expectError(error.InvalidLocalDominance, verifyOperation(owner, options));
1126 }
1127
1128 test "graph region rejects multiple blocks" {
1129 const testing = std.testing;
1130 const Context = @import("context/root.zig").Context;
1131 const core_traits = @import("traits.zig");
1132
1133 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1134 defer ctx.deinit(testing.allocator);
1135 try ctx.allowUnregistered();
1136
1137 const owner_name = "region_kind.multi_block_graph_owner";
1138 try core_traits.registerOperationTrait(&ctx, owner_name, core_traits.HasOnlyGraphRegion);
1139
1140 var owner_state = Operation.State.init(owner_name, .unknown);
1141 owner_state.addRegion();
1142 const owner = try ctx.createOperation(owner_state);
1143 const region = owner.getRegion(0).?;
1144 _ = try region.addBlock();
1145 _ = try region.addBlock();
1146
1147 var options = default_options;
1148 options.check_terminators = false;
1149 try testing.expectError(error.GraphRegionMultipleBlocks, verifyOperation(owner, options));
1150 }
1151
1152 test "verify successor/predecessor consistency" {
1153 const testing = std.testing;
1154 const Context = @import("context/root.zig").Context;
1155
1156 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1157
1158 var pred = Block.init(testing.allocator);
1159 defer pred.deinit();
1160 var succ = Block.init(testing.allocator);
1161 defer succ.deinit();
1162 defer ctx.deinit(testing.allocator);
1163 try ctx.allowUnregistered();
1164
1165 var br_state = Operation.State.init("test.br", Location.getUnknown());
1166 br_state.addSuccessors(&.{&succ});
1167 const br_op = try ctx.createOperation(br_state);
1168 try pred.addOperation(br_op);
1169
1170 var options = default_options;
1171 options.check_terminators = false;
1172 try verifyBlock(&pred, options);
1173 try verifyBlock(&succ, options);
1174
1175 try verifyOperation(br_op, options);
1176 }
1177
1178 test "verify rejects reverse-only predecessor entries" {
1179 const testing = std.testing;
1180 const Context = @import("context/root.zig").Context;
1181
1182 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1183 var source = Block.init(testing.allocator);
1184 var target = Block.init(testing.allocator);
1185 defer {
1186 ctx.deinit(testing.allocator);
1187 source.deinit();
1188 target.deinit();
1189 }
1190 try ctx.allowUnregistered();
1191
1192 try target.predecessors.append(target.allocator, &source);
1193
1194 var options = default_options;
1195 options.check_terminators = false;
1196 try testing.expectError(error.InvalidPredecessor, verifyBlock(&target, options));
1197
1198 try source.addOperation(try ctx.createOperation(
1199 Operation.State.init("test.zero_successor_tail", .unknown),
1200 ));
1201 try testing.expectError(error.InvalidPredecessor, verifyBlock(&target, options));
1202 }
1203
1204 test "verify rejects duplicate predecessor entries" {
1205 const testing = std.testing;
1206 const Context = @import("context/root.zig").Context;
1207
1208 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1209 var source = Block.init(testing.allocator);
1210 var target = Block.init(testing.allocator);
1211 defer {
1212 ctx.deinit(testing.allocator);
1213 source.deinit();
1214 target.deinit();
1215 }
1216 try ctx.allowUnregistered();
1217
1218 var state = Operation.State.init("test.duplicate_predecessor", .unknown);
1219 state.addSuccessors(&.{&target});
1220 const operation = try ctx.createOperation(state);
1221 try source.addOperation(operation);
1222 try target.predecessors.append(target.allocator, &source);
1223
1224 var options = default_options;
1225 options.check_terminators = false;
1226 try testing.expectError(error.InvalidPredecessor, verifyBlock(&target, options));
1227 source.removeOperation(operation);
1228 try testing.expect(target.hasNoPredecessors());
1229 }
1230
1231 test "verify rejects predecessor outside the target region" {
1232 const testing = std.testing;
1233 const Context = @import("context/root.zig").Context;
1234
1235 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1236 var source = Block.init(testing.allocator);
1237 defer {
1238 ctx.deinit(testing.allocator);
1239 source.deinit();
1240 }
1241 try ctx.allowUnregistered();
1242
1243 var owner_state = Operation.State.init("test.region_owner", .unknown);
1244 owner_state.addRegion();
1245 const owner = try ctx.createOperation(owner_state);
1246 const region = owner.getRegion(0).?;
1247 _ = try region.addBlock();
1248 const target = try region.addBlock();
1249
1250 var state = Operation.State.init("test.external_branch", .unknown);
1251 state.addSuccessors(&.{target});
1252 try source.addOperation(try ctx.createOperation(state));
1253
1254 var options = default_options;
1255 options.check_terminators = false;
1256 try testing.expectError(
1257 error.SuccessorRegionMismatch,
1258 verifyRegionStructure(region, options),
1259 );
1260 }
1261
1262 test "verify bounds corrupt predecessor operation traversal" {
1263 const testing = std.testing;
1264 const Context = @import("context/root.zig").Context;
1265
1266 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1267 var source = Block.init(testing.allocator);
1268 var target = Block.init(testing.allocator);
1269 defer {
1270 ctx.deinit(testing.allocator);
1271 source.deinit();
1272 target.deinit();
1273 }
1274 try ctx.allowUnregistered();
1275
1276 const operation = try ctx.createOperation(
1277 Operation.State.init("test.cyclic_predecessor_source", .unknown),
1278 );
1279 try source.addOperation(operation);
1280 operation.next_op = operation;
1281 defer operation.next_op = null;
1282 try target.predecessors.append(target.allocator, &source);
1283
1284 var options = default_options;
1285 options.check_terminators = false;
1286 try testing.expectError(
1287 error.OperationListCorrupted,
1288 verifyBlock(&target, options),
1289 );
1290 }
1291
1292 test "verify accepts predecessors justified by non-tail operations" {
1293 const testing = std.testing;
1294 const Context = @import("context/root.zig").Context;
1295
1296 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1297 var source = Block.init(testing.allocator);
1298 var first_target = Block.init(testing.allocator);
1299 var tail_target = Block.init(testing.allocator);
1300 defer {
1301 ctx.deinit(testing.allocator);
1302 source.deinit();
1303 first_target.deinit();
1304 tail_target.deinit();
1305 }
1306 try ctx.allowUnregistered();
1307
1308 var first_state = Operation.State.init("test.first_branch", .unknown);
1309 first_state.addSuccessors(&.{&first_target});
1310 try source.addOperation(try ctx.createOperation(first_state));
1311 var tail_state = Operation.State.init("test.tail_branch", .unknown);
1312 tail_state.addSuccessors(&.{&tail_target});
1313 try source.addOperation(try ctx.createOperation(tail_state));
1314
1315 var options = default_options;
1316 options.check_terminators = false;
1317 try verifyBlock(&first_target, options);
1318 }
1319
1320 test "ssacfg region rejects successor to entry block" {
1321 const testing = std.testing;
1322 const Context = @import("context/root.zig").Context;
1323
1324 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1325 defer ctx.deinit(testing.allocator);
1326 try ctx.allowUnregistered();
1327
1328 var owner_state = Operation.State.init("cfg.entry_successor_owner", .unknown);
1329 owner_state.addRegion();
1330 const owner = try ctx.createOperation(owner_state);
1331 const region = owner.getRegion(0).?;
1332 const entry = try region.addBlock();
1333 const body = try region.addBlock();
1334
1335 var entry_branch_state = Operation.State.init("cfg.entry_branch", .unknown);
1336 entry_branch_state.addSuccessors(&.{body});
1337 try entry.addOperation(try ctx.createOperation(entry_branch_state));
1338
1339 var backedge_state = Operation.State.init("cfg.backedge", .unknown);
1340 backedge_state.addSuccessors(&.{entry});
1341 try body.addOperation(try ctx.createOperation(backedge_state));
1342
1343 var options = default_options;
1344 options.check_terminators = false;
1345 try testing.expectError(error.EntryBlockSuccessor, verifyOperation(owner, options));
1346 }
1347
1348 test "ssacfg region rejects successor outside containing region" {
1349 const testing = std.testing;
1350 const Context = @import("context/root.zig").Context;
1351
1352 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1353 defer ctx.deinit(testing.allocator);
1354 try ctx.allowUnregistered();
1355
1356 var source_owner_state = Operation.State.init("cfg.source_owner", .unknown);
1357 source_owner_state.addRegion();
1358 const source_owner = try ctx.createOperation(source_owner_state);
1359 const source_block = try source_owner.getRegion(0).?.addBlock();
1360
1361 var target_owner_state = Operation.State.init("cfg.target_owner", .unknown);
1362 target_owner_state.addRegion();
1363 const target_owner = try ctx.createOperation(target_owner_state);
1364 const target_block = try target_owner.getRegion(0).?.addBlock();
1365
1366 var branch_state = Operation.State.init("cfg.cross_region_branch", .unknown);
1367 branch_state.addSuccessors(&.{target_block});
1368 try source_block.addOperation(try ctx.createOperation(branch_state));
1369
1370 var options = default_options;
1371 options.check_terminators = false;
1372 try testing.expectError(error.SuccessorRegionMismatch, verifyOperation(source_owner, options));
1373 }
1374
1375 test "verify nested regions" {
1376 const testing = std.testing;
1377 const Context = @import("context/root.zig").Context;
1378
1379 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1380 defer ctx.deinit(testing.allocator);
1381 try ctx.allowUnregistered();
1382
1383 var state = Operation.State.init("test.module", .unknown);
1384 state.addRegion();
1385 const module_op = try ctx.createOperation(state);
1386
1387 const region = module_op.getRegion(0).?;
1388 _ = try region.addBlock();
1389 try testing.expect(region.parent == @as(*anyopaque, @ptrCast(module_op)));
1390
1391 var options = default_options;
1392 options.check_terminators = false;
1393 try verifyOperation(module_op, options);
1394 }
1395
1396 test "verify detects parent block mismatch" {
1397 const testing = std.testing;
1398 const Context = @import("context/root.zig").Context;
1399
1400 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1401 defer ctx.deinit(testing.allocator);
1402 try ctx.allowUnregistered();
1403
1404 var block1 = Block.init(testing.allocator);
1405 defer block1.deinit();
1406 var block2 = Block.init(testing.allocator);
1407 defer block2.deinit();
1408
1409 const state = Operation.State.init("test.op", .unknown);
1410 const op = try ctx.createOperation(state);
1411
1412 try block1.addOperation(op);
1413 op.parent_block = &block2;
1414
1415 var options = default_options;
1416 options.check_terminators = false;
1417 const result = verifyBlock(&block1, options);
1418 try testing.expectError(error.ParentBlockMismatch, result);
1419
1420 op.parent_block = &block1;
1421 }
1422
1423 test "verify detects use-def chain corruption" {
1424 const testing = std.testing;
1425 const Context = @import("context/root.zig").Context;
1426
1427 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1428 defer ctx.deinit(testing.allocator);
1429 try ctx.allowUnregistered();
1430
1431 const ty = try ctx.getDialectTypeFromName("test.i32");
1432
1433 var producer1_state = Operation.State.init("test.producer1", .unknown);
1434 producer1_state.addTypes(&.{ty});
1435 const producer1 = try ctx.createOperation(producer1_state);
1436
1437 var producer2_state = Operation.State.init("test.producer2", .unknown);
1438 producer2_state.addTypes(&.{ty});
1439 const producer2 = try ctx.createOperation(producer2_state);
1440
1441 const result1 = producer1.getResult(0).?;
1442 const result2 = producer2.getResult(0).?;
1443
1444 var consumer_state = Operation.State.init("test.consumer", .unknown);
1445 consumer_state.addOperands(&.{result1});
1446 const consumer = try ctx.createOperation(consumer_state);
1447
1448 consumer.operands.items[0].value = result2;
1449
1450 const verify_result = verifyValue(result1);
1451 try testing.expectError(error.UseChainValueMismatch, verify_result);
1452
1453 consumer.operands.items[0].value = result1;
1454 }
1455
1456 test "verify empty region" {
1457 const testing = std.testing;
1458
1459 var region = Region.init(testing.allocator);
1460 defer region.deinit();
1461
1462 var options = default_options;
1463 options.check_terminators = false;
1464 try verifyRegion(®ion, options);
1465
1466 try testing.expectEqual(@as(usize, 0), region.blocks.size);
1467 }
1468
1469 test "verifyValue on unused value" {
1470 const testing = std.testing;
1471 const Context = @import("context/root.zig").Context;
1472
1473 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1474 defer ctx.deinit(testing.allocator);
1475 try ctx.allowUnregistered();
1476
1477 const ty = try ctx.getDialectTypeFromName("test.i32");
1478
1479 var state = Operation.State.init("test.producer", .unknown);
1480 state.addTypes(&.{ty});
1481 const producer = try ctx.createOperation(state);
1482
1483 const result = producer.getResult(0).?;
1484
1485 try verifyValue(result);
1486 try testing.expect(result.hasNoUses());
1487 }
1488
1489 test "verify region size consistency" {
1490 const testing = std.testing;
1491
1492 var region = Region.init(testing.allocator);
1493 defer region.deinit();
1494
1495 _ = try region.addBlock();
1496 _ = try region.addBlock();
1497 _ = try region.addBlock();
1498
1499 const correct_size = region.blocks.size;
1500 region.blocks.size = 999;
1501
1502 var options = default_options;
1503 options.check_terminators = false;
1504 const result = verifyRegion(®ion, options);
1505 try testing.expectError(error.RegionSizeMismatch, result);
1506
1507 region.blocks.size = correct_size;
1508 }
1509
1510 test "verify operation list forward/backward consistency" {
1511 const testing = std.testing;
1512 const Context = @import("context/root.zig").Context;
1513
1514 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1515 defer ctx.deinit(testing.allocator);
1516 try ctx.allowUnregistered();
1517
1518 var block = Block.init(testing.allocator);
1519 defer block.deinit();
1520
1521 const state1 = Operation.State.init("test.op1", .unknown);
1522 const op1 = try ctx.createOperation(state1);
1523 try block.addOperation(op1);
1524
1525 const state2 = Operation.State.init("test.op2", .unknown);
1526 const op2 = try ctx.createOperation(state2);
1527 try block.addOperation(op2);
1528
1529 const state3 = Operation.State.init("test.op3", .unknown);
1530 const op3 = try ctx.createOperation(state3);
1531 try block.addOperation(op3);
1532
1533 try testing.expectEqual(op1.next_op, op2);
1534 try testing.expectEqual(op2.prev_op, op1);
1535 try testing.expectEqual(op2.next_op, op3);
1536 try testing.expectEqual(op3.prev_op, op2);
1537
1538 var options = default_options;
1539 options.check_terminators = false;
1540 try verifyBlock(&block, options);
1541 }
1542
1543 test "verify MissingTerminator error when require_terminators enabled" {
1544 const testing = std.testing;
1545 const Context = @import("context/root.zig").Context;
1546
1547 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1548 defer ctx.deinit(testing.allocator);
1549 try ctx.allowUnregistered();
1550
1551 var block = Block.init(testing.allocator);
1552 defer block.deinit();
1553
1554 const state = Operation.State.init("test.noop", .unknown);
1555 const op = try ctx.createOperation(state);
1556 try block.addOperation(op);
1557
1558 var options = default_options;
1559 options.check_terminators = true;
1560 options.require_terminators = false;
1561 try verifyBlock(&block, options);
1562
1563 options.require_terminators = true;
1564 const result = verifyBlock(&block, options);
1565 try testing.expectError(error.MissingTerminator, result);
1566 }
1567
1568 test "NoTerminator trait allows single-block region without terminator" {
1569 const Context = @import("context/root.zig").Context;
1570 const test_dialect = @import("../dialects/fixture/root.zig");
1571
1572 var arena = alloc_arena.Arena.init(std.testing.allocator);
1573 defer arena.deinit();
1574 const allocator = arena.allocator();
1575
1576 var ctx = try Context.init(allocator, Context.Limits.testing);
1577 defer ctx.deinit(allocator);
1578 try ctx.allowUnregistered();
1579
1580 const loc = Location.getUnknown();
1581 const module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1582 const block = module.getBodyBlock();
1583 const i64_type = try test_dialect.TestDialect.getI64Type(&ctx);
1584 const constant = try test_dialect.TestDialect.ConstantOp.create(&ctx, loc, i64_type, 1);
1585 try block.addOperation(constant.op);
1586
1587 var options = default_options;
1588 options.require_terminators = true;
1589 try verifyOperation(module.op, options);
1590 }
1591
1592 test "NoTerminator trait does not exempt multi-block regions" {
1593 const testing = std.testing;
1594 const Context = @import("context/root.zig").Context;
1595 const core_traits = @import("traits.zig");
1596
1597 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1598 defer ctx.deinit(testing.allocator);
1599 try ctx.allowUnregistered();
1600
1601 const owner_name = "terminator.no_terminator_owner";
1602 try core_traits.registerOperationTrait(&ctx, owner_name, core_traits.NoTerminator);
1603
1604 const loc = Location.getUnknown();
1605 var owner_state = Operation.State.init(owner_name, loc);
1606 owner_state.addRegion();
1607 const owner = try ctx.createOperation(owner_state);
1608
1609 const region = owner.getRegion(0).?;
1610 const first = try region.addBlock();
1611 _ = try region.addBlock();
1612 const child = try ctx.createOperation(Operation.State.init("terminator.child", loc));
1613 try first.addOperation(child);
1614
1615 var options = default_options;
1616 options.require_terminators = true;
1617 try testing.expectError(error.MissingTerminator, verifyOperation(owner, options));
1618 }
1619
1620 test "NoTerminator trait rejects multi-block regions with terminators" {
1621 const testing = std.testing;
1622 const Context = @import("context/root.zig").Context;
1623 const core_traits = @import("traits.zig");
1624
1625 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1626 defer ctx.deinit(testing.allocator);
1627 try ctx.allowUnregistered();
1628
1629 const owner_name = "terminator.no_terminator_multi_block_owner";
1630 const terminator_name = "terminator.no_terminator_multi_block_term";
1631 try core_traits.registerOperationTrait(&ctx, owner_name, core_traits.NoTerminator);
1632 try core_traits.registerOperationTrait(&ctx, terminator_name, core_traits.Terminator);
1633
1634 const loc = Location.getUnknown();
1635 var owner_state = Operation.State.init(owner_name, loc);
1636 owner_state.addRegion();
1637 const owner = try ctx.createOperation(owner_state);
1638
1639 const region = owner.getRegion(0).?;
1640 const first = try region.addBlock();
1641 const second = try region.addBlock();
1642 try first.addOperation(try ctx.createOperation(Operation.State.init(terminator_name, loc)));
1643 try second.addOperation(try ctx.createOperation(Operation.State.init(terminator_name, loc)));
1644
1645 try testing.expectError(core_traits.TraitError.SingleBlockRegionMismatch, verifyOperation(owner, default_options));
1646 }
1647
1648 test "trait verification enforces operand counts" {
1649 const testing = std.testing;
1650 const Context = @import("context/root.zig").Context;
1651 const test_dialect = @import("../dialects/fixture/root.zig");
1652 const core_traits = @import("traits.zig");
1653
1654 var arena = alloc_arena.Arena.init(std.testing.allocator);
1655 defer arena.deinit();
1656 const allocator = arena.allocator();
1657
1658 var ctx = try Context.init(allocator, Context.Limits.testing);
1659 defer ctx.deinit(allocator);
1660 try ctx.allowUnregistered();
1661
1662 try core_traits.registerOperationTrait(&ctx, test_dialect.TestDialect.BinaryOp.operation_name, core_traits.NOperands(2));
1663 try core_traits.registerOperationTrait(&ctx, test_dialect.TestDialect.BinaryOp.operation_name, core_traits.NResults(1));
1664
1665 const loc = Location.getUnknown();
1666 const module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1667 const block = module.getBodyBlock();
1668
1669 const i64_type = try test_dialect.TestDialect.getI64Type(&ctx);
1670 _ = try block.addArgument(i64_type, loc);
1671 _ = try block.addArgument(i64_type, loc);
1672 const arg0 = block.arguments.items[0];
1673 const arg1 = block.arguments.items[1];
1674
1675 const good_op = try test_dialect.TestDialect.BinaryOp.create(&ctx, loc, arg0, arg1);
1676 try block.addOperation(good_op.op);
1677
1678 try verifyOperation(module.op, default_options);
1679
1680 var bad_state = Operation.State.init(test_dialect.TestDialect.BinaryOp.operation_name, loc);
1681 bad_state.addOperands(&.{arg0});
1682 bad_state.addTypes(&.{i64_type});
1683 const bad_op = try ctx.createOperation(bad_state);
1684 try block.addOperation(bad_op);
1685
1686 const result = verifyOperation(module.op, default_options);
1687 try testing.expectError(core_traits.TraitError.OperandCountMismatch, result);
1688 }
1689
1690 test "trait verification enforces result counts" {
1691 const testing = std.testing;
1692 const Context = @import("context/root.zig").Context;
1693 const test_dialect = @import("../dialects/fixture/root.zig");
1694 const core_traits = @import("traits.zig");
1695
1696 var arena = alloc_arena.Arena.init(std.testing.allocator);
1697 defer arena.deinit();
1698 const allocator = arena.allocator();
1699
1700 var ctx = try Context.init(allocator, Context.Limits.testing);
1701 defer ctx.deinit(allocator);
1702 try ctx.allowUnregistered();
1703
1704 try core_traits.registerOperationTrait(&ctx, test_dialect.TestDialect.BinaryOp.operation_name, core_traits.NResults(1));
1705
1706 const loc = Location.getUnknown();
1707 const module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1708 const block = module.getBodyBlock();
1709
1710 const i64_type = try test_dialect.TestDialect.getI64Type(&ctx);
1711 _ = try block.addArgument(i64_type, loc);
1712 _ = try block.addArgument(i64_type, loc);
1713 const arg0 = block.arguments.items[0];
1714 const arg1 = block.arguments.items[1];
1715
1716 var bad_state = Operation.State.init(test_dialect.TestDialect.BinaryOp.operation_name, loc);
1717 bad_state.addOperands(&.{ arg0, arg1 });
1718 const bad_op = try ctx.createOperation(bad_state);
1719 try block.addOperation(bad_op);
1720
1721 const result = verifyOperation(module.op, default_options);
1722 try testing.expectError(core_traits.TraitError.ResultCountMismatch, result);
1723 }
1724
1725 test "structural verification does not run operation trait verifiers" {
1726 const testing = std.testing;
1727 const Context = @import("context/root.zig").Context;
1728 const test_dialect = @import("../dialects/fixture/root.zig");
1729 const core_traits = @import("traits.zig");
1730
1731 var arena = alloc_arena.Arena.init(std.testing.allocator);
1732 defer arena.deinit();
1733 const allocator = arena.allocator();
1734
1735 var ctx = try Context.init(allocator, Context.Limits.testing);
1736 defer ctx.deinit(allocator);
1737 try ctx.allowUnregistered();
1738
1739 try core_traits.registerOperationTrait(&ctx, test_dialect.TestDialect.BinaryOp.operation_name, core_traits.NOperands(2));
1740
1741 const loc = Location.getUnknown();
1742 const i64_type = try test_dialect.TestDialect.getI64Type(&ctx);
1743 var producer_state = Operation.State.init(test_dialect.TestDialect.ConstantOp.operation_name, loc);
1744 producer_state.addTypes(&.{i64_type});
1745 const producer = try ctx.createOperation(producer_state);
1746
1747 var bad_state = Operation.State.init(test_dialect.TestDialect.BinaryOp.operation_name, loc);
1748 bad_state.addOperands(&.{producer.getResult(0).?});
1749 bad_state.addTypes(&.{i64_type});
1750 const bad_op = try ctx.createOperation(bad_state);
1751
1752 try verifyOperationStructure(bad_op, .{ .recursive = false });
1753 try testing.expectError(core_traits.TraitError.OperandCountMismatch, verifyOperation(bad_op, .{ .recursive = false }));
1754 }
1755
1756 test "registered operation shape runs before custom verifiers" {
1757 const testing = std.testing;
1758 const Context = @import("context/root.zig").Context;
1759
1760 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1761 defer ctx.deinit(testing.allocator);
1762 try ctx.allowUnregistered();
1763
1764 _ = try ctx.registerOperation("shape.checked", .{});
1765 try ctx.registerOperationShape("shape.checked", .{
1766 .operands = interfaces.CountRange.exactly(2),
1767 .results = interfaces.CountRange.exactly(1),
1768 .regions = interfaces.CountRange.exactly(0),
1769 .successors = interfaces.CountRange.exactly(0),
1770 });
1771
1772 const hooks = struct {
1773 fn verify(_: *const anyopaque) anyerror!void {
1774 return error.CustomVerifierRan;
1775 }
1776 };
1777 try ctx.registerOperationInterface("shape.checked", VerifyOpInterface.entryFor(hooks.verify));
1778
1779 const ty = try ctx.getDialectTypeFromName("shape.i32");
1780 var producer_state = Operation.State.init("shape.producer", .unknown);
1781 producer_state.addTypes(&.{ty});
1782 const producer = try ctx.createOperation(producer_state);
1783
1784 var bad_state = Operation.State.init("shape.checked", .unknown);
1785 bad_state.addOperands(&.{producer.getResult(0).?});
1786 bad_state.addTypes(&.{ty});
1787 const bad_op = try ctx.createOperation(bad_state);
1788
1789 try verifyOperationStructure(bad_op, .{ .recursive = false });
1790 try testing.expectError(error.OperandCountMismatch, verifyOperation(bad_op, .{ .recursive = false }));
1791
1792 var good_state = Operation.State.init("shape.checked", .unknown);
1793 good_state.addOperands(&.{ producer.getResult(0).?, producer.getResult(0).? });
1794 good_state.addTypes(&.{ty});
1795 const good_op = try ctx.createOperation(good_state);
1796 try testing.expectError(error.CustomVerifierRan, verifyOperation(good_op, .{ .recursive = false }));
1797 }
1798
1799 test "registered operation segments verify before custom verifiers" {
1800 const testing = std.testing;
1801 const Context = @import("context/root.zig").Context;
1802
1803 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1804 defer ctx.deinit(testing.allocator);
1805 try ctx.allowUnregistered();
1806
1807 _ = try ctx.registerOperation("segments.checked", .{});
1808 try ctx.registerOperationShape("segments.checked", .{
1809 .operands = interfaces.CountRange.between(1, 4),
1810 .results = interfaces.CountRange.between(1, 2),
1811 });
1812 try ctx.registerOperationOperandSegments("segments.checked", .{
1813 .attribute_name = "operand_segment_sizes",
1814 .segments = &.{ interfaces.CountRange.exactly(1), interfaces.CountRange.atMost(1), interfaces.CountRange.atMost(2) },
1815 });
1816 try ctx.registerOperationResultSegments("segments.checked", .{
1817 .attribute_name = "result_segment_sizes",
1818 .segments = &.{ interfaces.CountRange.exactly(1), interfaces.CountRange.atMost(1) },
1819 });
1820
1821 const hooks = struct {
1822 fn verify(_: *const anyopaque) anyerror!void {
1823 return error.CustomVerifierRan;
1824 }
1825 };
1826 try ctx.registerOperationInterface("segments.checked", VerifyOpInterface.entryFor(hooks.verify));
1827
1828 const ty = try ctx.getDialectTypeFromName("segments.i32");
1829 var producer_state = Operation.State.init("segments.producer", .unknown);
1830 producer_state.addTypes(&.{ty});
1831 const producer = try ctx.createOperation(producer_state);
1832 const value = producer.getResult(0).?;
1833
1834 var bad_operand_sum_state = Operation.State.init("segments.checked", .unknown);
1835 bad_operand_sum_state.addOperands(&.{ value, value });
1836 bad_operand_sum_state.addTypes(&.{ty});
1837 const bad_operand_sum = try ctx.createOperation(bad_operand_sum_state);
1838 try bad_operand_sum.setAttr(
1839 "operand_segment_sizes",
1840 try test_segment_attribute(&ctx, &.{ 1, 0, 0 }),
1841 );
1842 try bad_operand_sum.setAttr(
1843 "result_segment_sizes",
1844 try test_segment_attribute(&ctx, &.{ 1, 0 }),
1845 );
1846 try testing.expectError(error.OperandSegmentSizeMismatch, verifyOperation(bad_operand_sum, .{ .recursive = false }));
1847
1848 var bad_operand_attr_state = Operation.State.init("segments.checked", .unknown);
1849 bad_operand_attr_state.addOperands(&.{value});
1850 bad_operand_attr_state.addTypes(&.{ty});
1851 const bad_operand_attr = try ctx.createOperation(bad_operand_attr_state);
1852 try bad_operand_attr.setAttr(
1853 "operand_segment_sizes",
1854 try test_segment_attribute(&ctx, &.{ 1, 0 }),
1855 );
1856 try bad_operand_attr.setAttr(
1857 "result_segment_sizes",
1858 try test_segment_attribute(&ctx, &.{ 1, 0 }),
1859 );
1860 try testing.expectError(error.InvalidOperandSegmentSizeAttribute, verifyOperation(bad_operand_attr, .{ .recursive = false }));
1861
1862 var bad_result_sum_state = Operation.State.init("segments.checked", .unknown);
1863 bad_result_sum_state.addOperands(&.{value});
1864 bad_result_sum_state.addTypes(&.{ty});
1865 const bad_result_sum = try ctx.createOperation(bad_result_sum_state);
1866 try bad_result_sum.setAttr(
1867 "operand_segment_sizes",
1868 try test_segment_attribute(&ctx, &.{ 1, 0, 0 }),
1869 );
1870 try bad_result_sum.setAttr(
1871 "result_segment_sizes",
1872 try test_segment_attribute(&ctx, &.{ 1, 1 }),
1873 );
1874 try testing.expectError(error.ResultSegmentSizeMismatch, verifyOperation(bad_result_sum, .{ .recursive = false }));
1875
1876 var good_state = Operation.State.init("segments.checked", .unknown);
1877 good_state.addOperands(&.{ value, value, value });
1878 good_state.addTypes(&.{ ty, ty });
1879 const good_op = try ctx.createOperation(good_state);
1880 try good_op.setAttr("operand_segment_sizes", try test_segment_attribute(&ctx, &.{ 1, 1, 1 }));
1881 try good_op.setAttr("result_segment_sizes", try test_segment_attribute(&ctx, &.{ 1, 1 }));
1882 try testing.expectError(error.CustomVerifierRan, verifyOperation(good_op, .{ .recursive = false }));
1883 }
1884
1885 test "registered required attributes verify before custom verifiers" {
1886 const testing = std.testing;
1887 const Context = @import("context/root.zig").Context;
1888
1889 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1890 defer ctx.deinit(testing.allocator);
1891 try ctx.allowUnregistered();
1892
1893 _ = try ctx.registerOperation("required.checked", .{});
1894 try ctx.registerOperationRequiredAttributeName("required.checked", "value");
1895
1896 const hooks = struct {
1897 fn verify(_: *const anyopaque) anyerror!void {
1898 return error.CustomVerifierRan;
1899 }
1900 };
1901 try ctx.registerOperationInterface("required.checked", VerifyOpInterface.entryFor(hooks.verify));
1902
1903 const missing_op = try ctx.createOperation(Operation.State.init("required.checked", .unknown));
1904 try testing.expectError(error.MissingRequiredAttribute, verifyOperation(missing_op, .{ .recursive = false }));
1905
1906 const present_op = try ctx.createOperation(Operation.State.init("required.checked", .unknown));
1907 try present_op.setAttr("value", try ctx.getI64Attr(42));
1908 try testing.expectError(error.CustomVerifierRan, verifyOperation(present_op, .{ .recursive = false }));
1909 }
1910
1911 test "registered required attributes accept property-backed inherent storage" {
1912 const testing = std.testing;
1913 const Context = @import("context/root.zig").Context;
1914
1915 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1916 defer ctx.deinit(testing.allocator);
1917 try ctx.allowUnregistered();
1918
1919 _ = try ctx.registerOperation("required.properties", .{});
1920 try ctx.registerOperationRequiredAttributeName("required.properties", "value");
1921 try ctx.registerOperationPropertiesModel(
1922 "required.properties",
1923 interfaces.singleAttributePropertiesModel("required.properties.model", "value"),
1924 );
1925
1926 var state = Operation.State.init("required.properties", .unknown);
1927 try state.setPropertiesAttr(try ctx.getI64Attr(7));
1928 const op = try ctx.createOperation(state);
1929
1930 try verifyOperation(op, .{ .recursive = false });
1931 }
1932
1933 test "registered type constraints verify before custom verifiers" {
1934 const testing = std.testing;
1935 const Context = @import("context/root.zig").Context;
1936
1937 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1938 defer ctx.deinit(testing.allocator);
1939 try ctx.allowUnregistered();
1940
1941 _ = try ctx.registerOperation("types.checked", .{});
1942 try ctx.registerOperationShape("types.checked", .{
1943 .operands = interfaces.CountRange.exactly(1),
1944 .results = interfaces.CountRange.exactly(1),
1945 });
1946 try ctx.registerOperationOperandTypeConstraint("types.checked", .{
1947 .index = 0,
1948 .type_name = "types.i32",
1949 });
1950 try ctx.registerOperationResultTypeConstraint("types.checked", .{
1951 .index = 0,
1952 .type_name = "types.bool",
1953 });
1954
1955 const hooks = struct {
1956 fn verify(_: *const anyopaque) anyerror!void {
1957 return error.CustomVerifierRan;
1958 }
1959 };
1960 try ctx.registerOperationInterface("types.checked", VerifyOpInterface.entryFor(hooks.verify));
1961
1962 const i32_type = try ctx.getDialectTypeFromName("types.i32");
1963 const f32_type = try ctx.getDialectTypeFromName("types.f32");
1964 const bool_type = try ctx.getDialectTypeFromName("types.bool");
1965
1966 var good_operand_state = Operation.State.init("types.good_operand", .unknown);
1967 good_operand_state.addTypes(&.{i32_type});
1968 const good_operand = try ctx.createOperation(good_operand_state);
1969
1970 var bad_operand_state = Operation.State.init("types.bad_operand", .unknown);
1971 bad_operand_state.addTypes(&.{f32_type});
1972 const bad_operand = try ctx.createOperation(bad_operand_state);
1973
1974 var bad_operand_use_state = Operation.State.init("types.checked", .unknown);
1975 bad_operand_use_state.addOperands(&.{bad_operand.getResult(0).?});
1976 bad_operand_use_state.addTypes(&.{bool_type});
1977 const bad_operand_use = try ctx.createOperation(bad_operand_use_state);
1978 try testing.expectError(error.OperandTypeConstraintMismatch, verifyOperation(bad_operand_use, .{ .recursive = false }));
1979
1980 var bad_result_state = Operation.State.init("types.checked", .unknown);
1981 bad_result_state.addOperands(&.{good_operand.getResult(0).?});
1982 bad_result_state.addTypes(&.{f32_type});
1983 const bad_result = try ctx.createOperation(bad_result_state);
1984 try testing.expectError(error.ResultTypeConstraintMismatch, verifyOperation(bad_result, .{ .recursive = false }));
1985
1986 var good_state = Operation.State.init("types.checked", .unknown);
1987 good_state.addOperands(&.{good_operand.getResult(0).?});
1988 good_state.addTypes(&.{bool_type});
1989 const good_op = try ctx.createOperation(good_state);
1990 try testing.expectError(error.CustomVerifierRan, verifyOperation(good_op, .{ .recursive = false }));
1991 }
1992
1993 test "registered type constraints can allow parameterized dialect types" {
1994 const Context = @import("context/root.zig").Context;
1995
1996 var ctx = try Context.init(std.testing.allocator, Context.Limits.testing);
1997 defer ctx.deinit(std.testing.allocator);
1998 try ctx.allowUnregistered();
1999
2000 _ = try ctx.registerOperation("types.parameterized", .{});
2001 try ctx.registerOperationOperandTypeConstraint("types.parameterized", .{
2002 .index = 0,
2003 .type_name = "types.param",
2004 .allow_parameterized = true,
2005 });
2006 try ctx.registerOperationResultTypeConstraint("types.parameterized", .{
2007 .index = 0,
2008 .type_name = "types.param",
2009 .allow_parameterized = true,
2010 });
2011
2012 const param_type = try ctx.getDialectTypeFromNameWithKey("types.param", "64");
2013 var producer_state = Operation.State.init("types.producer", .unknown);
2014 producer_state.addTypes(&.{param_type});
2015 const producer = try ctx.createOperation(producer_state);
2016
2017 var consumer_state = Operation.State.init("types.parameterized", .unknown);
2018 consumer_state.addOperands(&.{producer.getResult(0).?});
2019 consumer_state.addTypes(&.{param_type});
2020 const consumer = try ctx.createOperation(consumer_state);
2021
2022 try verifyOperation(consumer, .{ .recursive = false });
2023 }
2024
2025 test "trait verification enforces region counts" {
2026 const testing = std.testing;
2027 const Context = @import("context/root.zig").Context;
2028 const core_traits = @import("traits.zig");
2029
2030 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
2031 defer ctx.deinit(testing.allocator);
2032 try ctx.allowUnregistered();
2033
2034 const op_name = "test.region_owner";
2035 try core_traits.registerOperationTrait(&ctx, op_name, core_traits.OneRegion);
2036
2037 const bad_state = Operation.State.init(op_name, Location.getUnknown());
2038 const bad_op = try ctx.createOperation(bad_state);
2039
2040 const result = verifyOperation(bad_op, default_options);
2041 try testing.expectError(core_traits.TraitError.RegionCountMismatch, result);
2042 }
2043
2044 test "trait verification enforces single-block regions" {
2045 const testing = std.testing;
2046 const Context = @import("context/root.zig").Context;
2047 const core_traits = @import("traits.zig");
2048
2049 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
2050 defer ctx.deinit(testing.allocator);
2051 try ctx.allowUnregistered();
2052
2053 const op_name = "test.single_block_owner";
2054 try core_traits.registerOperationTrait(&ctx, op_name, core_traits.SingleBlock);
2055
2056 const loc = Location.getUnknown();
2057 var state = Operation.State.init(op_name, loc);
2058 state.addRegion();
2059 const op = try ctx.createOperation(state);
2060
2061 const region = op.getRegion(0).?;
2062 _ = try region.addBlock();
2063 _ = try region.addBlock();
2064
2065 var options = default_options;
2066 options.check_terminators = false;
2067 try testing.expectError(core_traits.TraitError.SingleBlockRegionMismatch, verifyOperation(op, options));
2068 }
2069
2070 test "trait verification enforces implicit terminator name" {
2071 const testing = std.testing;
2072 const Context = @import("context/root.zig").Context;
2073 const core_traits = @import("traits.zig");
2074
2075 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
2076 defer ctx.deinit(testing.allocator);
2077 try ctx.allowUnregistered();
2078
2079 const owner_name = "test.implicit_terminator_owner";
2080 const terminator_name = "test.implicit_terminator";
2081 try core_traits.registerOperationTrait(
2082 &ctx,
2083 owner_name,
2084 core_traits.SingleBlockImplicitTerminator(terminator_name),
2085 );
2086
2087 const loc = Location.getUnknown();
2088 var good_state = Operation.State.init(owner_name, loc);
2089 good_state.addRegion();
2090 const good = try ctx.createOperation(good_state);
2091 const good_block = try good.getRegion(0).?.addBlock();
2092 try good_block.addOperation(try ctx.createOperation(Operation.State.init(terminator_name, loc)));
2093 try verifyOperation(good, default_options);
2094
2095 var bad_state = Operation.State.init(owner_name, loc);
2096 bad_state.addRegion();
2097 const bad = try ctx.createOperation(bad_state);
2098 const bad_block = try bad.getRegion(0).?.addBlock();
2099 try bad_block.addOperation(try ctx.createOperation(Operation.State.init("test.wrong_terminator", loc)));
2100 try testing.expectError(
2101 core_traits.TraitError.ImplicitTerminatorMismatch,
2102 verifyOperation(bad, default_options),
2103 );
2104 }
2105
2106 test "trait verification enforces terminator placement" {
2107 const testing = std.testing;
2108 const Context = @import("context/root.zig").Context;
2109 const test_dialect = @import("../dialects/fixture/root.zig");
2110 const core_traits = @import("traits.zig");
2111
2112 var arena = alloc_arena.Arena.init(std.testing.allocator);
2113 defer arena.deinit();
2114 const allocator = arena.allocator();
2115
2116 var ctx = try Context.init(allocator, Context.Limits.testing);
2117 defer ctx.deinit(allocator);
2118 try ctx.allowUnregistered();
2119
2120 try core_traits.registerOperationTrait(&ctx, test_dialect.TestDialect.ReturnOp.operation_name, core_traits.Terminator);
2121
2122 const loc = Location.getUnknown();
2123 const module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
2124 const block = module.getBodyBlock();
2125
2126 const ret_op = try test_dialect.TestDialect.ReturnOp.create(&ctx, loc, &.{});
2127 try block.addOperation(ret_op.op);
2128
2129 const i64_type = try test_dialect.TestDialect.getI64Type(&ctx);
2130 const const_op = try test_dialect.TestDialect.ConstantOp.create(&ctx, loc, i64_type, 1);
2131 try block.addOperation(const_op.op);
2132
2133 var options = default_options;
2134 options.check_terminators = false;
2135
2136 const result = verifyOperation(module.op, options);
2137 try testing.expectError(core_traits.TraitError.TerminatorNotLast, result);
2138 }
2139
2140 test "trait verification enforces isolation from above" {
2141 const testing = std.testing;
2142 const Context = @import("context/root.zig").Context;
2143 const test_dialect = @import("../dialects/fixture/root.zig");
2144 const core_traits = @import("traits.zig");
2145
2146 var arena = alloc_arena.Arena.init(std.testing.allocator);
2147 defer arena.deinit();
2148 const allocator = arena.allocator();
2149
2150 var ctx = try Context.init(allocator, Context.Limits.testing);
2151 defer ctx.deinit(allocator);
2152 try ctx.allowUnregistered();
2153
2154 try core_traits.registerOperationTrait(&ctx, test_dialect.TestDialect.ModuleOp.operation_name, core_traits.IsolatedFromAbove);
2155
2156 const loc = Location.getUnknown();
2157 const i64_type = try test_dialect.TestDialect.getI64Type(&ctx);
2158
2159 const outer_module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
2160 const outer_block = outer_module.getBodyBlock();
2161
2162 const outer_const = try test_dialect.TestDialect.ConstantOp.create(&ctx, loc, i64_type, 7);
2163 try outer_block.addOperation(outer_const.op);
2164
2165 const inner_module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
2166 try outer_block.addOperation(inner_module.op);
2167
2168 const inner_block = inner_module.getBodyBlock();
2169 const inner_const = try test_dialect.TestDialect.ConstantOp.create(&ctx, loc, i64_type, 3);
2170 try inner_block.addOperation(inner_const.op);
2171
2172 const bad_binary = try test_dialect.TestDialect.BinaryOp.create(&ctx, loc, outer_const.getResult(), inner_const.getResult());
2173 try inner_block.addOperation(bad_binary.op);
2174
2175 const result = verifyOperation(outer_module.op, default_options);
2176 try testing.expectError(core_traits.TraitError.IsolatedFromAbove, result);
2177 }
2178
2179 test "trait verification enforces recursive isolation from above" {
2180 const testing = std.testing;
2181 const Context = @import("context/root.zig").Context;
2182 const core_traits = @import("traits.zig");
2183
2184 var arena = alloc_arena.Arena.init(std.testing.allocator);
2185 defer arena.deinit();
2186 const allocator = arena.allocator();
2187
2188 var ctx = try Context.init(allocator, Context.Limits.testing);
2189 defer ctx.deinit(allocator);
2190 try ctx.allowUnregistered();
2191
2192 const isolated_name = "isolation.recursive_root";
2193 try core_traits.registerOperationTrait(&ctx, isolated_name, core_traits.IsolatedFromAbove);
2194
2195 const loc = Location.getUnknown();
2196 const i64_type = try ctx.getDialectTypeFromName("isolation.i64");
2197
2198 var outer_state = Operation.State.init("isolation.outer", loc);
2199 outer_state.addRegion();
2200 const outer = try ctx.createOperation(outer_state);
2201 const outer_block = try outer.getRegion(0).?.addBlock();
2202
2203 var producer_state = Operation.State.init("isolation.producer", loc);
2204 producer_state.addTypes(&.{i64_type});
2205 const producer = try ctx.createOperation(producer_state);
2206 try outer_block.addOperation(producer);
2207
2208 var isolated_state = Operation.State.init(isolated_name, loc);
2209 isolated_state.addRegion();
2210 const isolated = try ctx.createOperation(isolated_state);
2211 try outer_block.addOperation(isolated);
2212 const isolated_block = try isolated.getRegion(0).?.addBlock();
2213
2214 var child_state = Operation.State.init("isolation.non_isolated_child", loc);
2215 child_state.addRegion();
2216 const child = try ctx.createOperation(child_state);
2217 try isolated_block.addOperation(child);
2218 const child_block = try child.getRegion(0).?.addBlock();
2219
2220 var consumer_state = Operation.State.init("isolation.consumer", loc);
2221 consumer_state.addOperands(&.{producer.getResult(0).?});
2222 const consumer = try ctx.createOperation(consumer_state);
2223 try child_block.addOperation(consumer);
2224
2225 var options = default_options;
2226 options.check_terminators = false;
2227 try testing.expectError(core_traits.TraitError.IsolatedFromAbove, runRegionTraitVerifiers(isolated));
2228 try testing.expectError(core_traits.TraitError.IsolatedFromAbove, verifyOperation(isolated, options));
2229 }
2230
2231 test "trait verification allows recursive uses inside isolated operation" {
2232 const Context = @import("context/root.zig").Context;
2233 const core_traits = @import("traits.zig");
2234
2235 var arena = alloc_arena.Arena.init(std.testing.allocator);
2236 defer arena.deinit();
2237 const allocator = arena.allocator();
2238
2239 var ctx = try Context.init(allocator, Context.Limits.testing);
2240 defer ctx.deinit(allocator);
2241 try ctx.allowUnregistered();
2242
2243 const isolated_name = "isolation.recursive_allowed_root";
2244 try core_traits.registerOperationTrait(&ctx, isolated_name, core_traits.IsolatedFromAbove);
2245
2246 const loc = Location.getUnknown();
2247 const i64_type = try ctx.getDialectTypeFromName("isolation.i64");
2248
2249 var isolated_state = Operation.State.init(isolated_name, loc);
2250 isolated_state.addRegion();
2251 const isolated = try ctx.createOperation(isolated_state);
2252 const isolated_block = try isolated.getRegion(0).?.addBlock();
2253
2254 var producer_state = Operation.State.init("isolation.local_producer", loc);
2255 producer_state.addTypes(&.{i64_type});
2256 const producer = try ctx.createOperation(producer_state);
2257 try isolated_block.addOperation(producer);
2258
2259 var child_state = Operation.State.init("isolation.local_non_isolated_child", loc);
2260 child_state.addRegion();
2261 const child = try ctx.createOperation(child_state);
2262 try isolated_block.addOperation(child);
2263 const child_block = try child.getRegion(0).?.addBlock();
2264
2265 var consumer_state = Operation.State.init("isolation.local_consumer", loc);
2266 consumer_state.addOperands(&.{producer.getResult(0).?});
2267 const consumer = try ctx.createOperation(consumer_state);
2268 try child_block.addOperation(consumer);
2269
2270 var options = default_options;
2271 options.check_terminators = false;
2272 try verifyOperation(isolated, options);
2273 }
2274
2275 test "isolation coordination preserves custom trait verifier" {
2276 const testing = std.testing;
2277 const Context = @import("context/root.zig").Context;
2278 const core_traits = @import("traits.zig");
2279
2280 var arena = alloc_arena.Arena.init(testing.allocator);
2281 defer arena.deinit();
2282 var ctx = try Context.init(arena.allocator(), Context.Limits.testing);
2283 defer ctx.deinit(arena.allocator());
2284 try ctx.allowUnregistered();
2285
2286 const Hooks = struct {
2287 fn fail(_: *const anyopaque) anyerror!void {
2288 return error.CustomIsolationVerifierRan;
2289 }
2290
2291 const vtable: interfaces.TraitVTable = .{ .verify_regions = fail };
2292 };
2293 try ctx.registerTraitDefinition(.{
2294 .id = core_traits.IsolatedFromAbove.id,
2295 .vtable = &Hooks.vtable,
2296 });
2297 const isolated_name = "isolation.custom_verifier";
2298 try ctx.registerOperationTraitId(isolated_name, core_traits.IsolatedFromAbove.id);
2299
2300 var state = Operation.State.init(isolated_name, .unknown);
2301 state.addRegion();
2302 const isolated = try ctx.createOperation(state);
2303 _ = try isolated.getRegion(0).?.addBlock();
2304
2305 var options = default_options;
2306 options.check_terminators = false;
2307 try testing.expectError(error.CustomIsolationVerifierRan, verifyOperation(isolated, options));
2308 }
2309
2310 test "isolation failure preserves nested operation verifier precedence" {
2311 const testing = std.testing;
2312 const Context = @import("context/root.zig").Context;
2313 const core_traits = @import("traits.zig");
2314
2315 var arena = alloc_arena.Arena.init(std.testing.allocator);
2316 defer arena.deinit();
2317 const allocator = arena.allocator();
2318 var ctx = try Context.init(allocator, Context.Limits.testing);
2319 defer ctx.deinit(allocator);
2320 try ctx.allowUnregistered();
2321
2322 const isolated_name = "isolation.precedence_root";
2323 const failing_name = "isolation.precedence_failing";
2324 try core_traits.registerOperationTrait(&ctx, isolated_name, core_traits.IsolatedFromAbove);
2325 const Hooks = struct {
2326 fn fail(_: *const anyopaque) anyerror!void {
2327 return error.NestedOperationVerifierRan;
2328 }
2329 };
2330 try ctx.registerOperationInterface(failing_name, VerifyOpInterface.entryFor(Hooks.fail));
2331
2332 const value_type = try ctx.getDialectTypeFromName("isolation.i64");
2333 var outer_state = Operation.State.init("isolation.precedence_outer", .unknown);
2334 outer_state.addRegion();
2335 const outer = try ctx.createOperation(outer_state);
2336 const outer_block = try outer.getRegion(0).?.addBlock();
2337 var producer_state = Operation.State.init("isolation.precedence_producer", .unknown);
2338 producer_state.addTypes(&.{value_type});
2339 const producer = try ctx.createOperation(producer_state);
2340 try outer_block.addOperation(producer);
2341
2342 var isolated_state = Operation.State.init(isolated_name, .unknown);
2343 isolated_state.addRegion();
2344 const isolated = try ctx.createOperation(isolated_state);
2345 try outer_block.addOperation(isolated);
2346 const isolated_block = try isolated.getRegion(0).?.addBlock();
2347 var consumer_state = Operation.State.init("isolation.precedence_consumer", .unknown);
2348 consumer_state.addOperands(&.{producer.getResult(0).?});
2349 try isolated_block.addOperation(try ctx.createOperation(consumer_state));
2350 try isolated_block.addOperation(try ctx.createOperation(Operation.State.init(failing_name, .unknown)));
2351
2352 const options = VerifyOptions{
2353 .recursive = true,
2354 .check_use_def = true,
2355 .check_local_dominance = false,
2356 .check_cfg = false,
2357 };
2358 try testing.expectError(error.NestedOperationVerifierRan, verifyOperation(isolated, options));
2359 }
2360
2361 test "dialect region verifier runs after nested operation verifier" {
2362 const testing = std.testing;
2363 const dialects = @import("root.zig").dialects;
2364 const Context = @import("context/root.zig").Context;
2365
2366 var ctx = try Context.init(testing.allocator, Context.Limits.testing);
2367 defer ctx.deinit(testing.allocator);
2368 try ctx.allowUnregistered();
2369
2370 const Hooks = struct {
2371 fn verifyChild(_: *const anyopaque) anyerror!void {
2372 return error.ChildVerifierRan;
2373 }
2374
2375 fn verifyParentRegions(_: *const anyopaque) anyerror!void {
2376 return error.ParentRegionVerifierRan;
2377 }
2378 };
2379
2380 const DialectForTest = struct {
2381 pub const name = "phase";
2382 const op_specs = dialects.opSpec.dialect(@This());
2383
2384 pub const ParentOp = struct {
2385 pub const operation_spec = op_specs.define(.{ .mnemonic = "parent" });
2386 pub const operation_name = operation_spec.name;
2387 pub const verifyRegions = Hooks.verifyParentRegions;
2388 };
2389
2390 pub const ChildOp = struct {
2391 pub const operation_spec = op_specs.define(.{ .mnemonic = "child" });
2392 pub const operation_name = operation_spec.name;
2393 pub const verify = Hooks.verifyChild;
2394 };
2395
2396 pub const spec = dialects.dialectSpec(@This(), .{});
2397 };
2398
2399 try dialects.loadDialectSpec(&ctx, DialectForTest.spec);
2400
2401 const loc = Location.getUnknown();
2402 var parent_state = Operation.State.init(DialectForTest.ParentOp.operation_name, loc);
2403 parent_state.addRegion();
2404 const parent = try ctx.createOperation(parent_state);
2405 const block = try parent.getRegion(0).?.addBlock();
2406 const child_state = Operation.State.init(DialectForTest.ChildOp.operation_name, loc);
2407 const child = try ctx.createOperation(child_state);
2408 try block.addOperation(child);
2409
2410 const result = verifyOperation(parent, default_options);
2411 try testing.expectError(error.ChildVerifierRan, result);
2412 }