lib/choir/src/core/test.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

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