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(&region, 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(&region, 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(&region, 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 }