lib/choir/src/passes/pass/test.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

   1 const std = @import("std");
   2 const alloc_arena = @import("alloc_arena");
   3 const alloc_observe = @import("alloc_observe");
   4 const sys = @import("sys");
   5 const choir = @import("../../root.zig");
   6 const ir = @import("../../core/root.zig");
   7 const diagnostics = choir.diagnostics;
   8 const passes = @import("../root.zig");
   9 const subject = @import("root.zig");
  10 const instrumentation = passes.instrumentation;
  11 
  12 const PassInstrumentor = instrumentation.PassInstrumentor;
  13 const PassInfo = instrumentation.PassInfo;
  14 const TimingInstrumentation = instrumentation.TimingInstrumentation;
  15 const CountingInstrumentation = instrumentation.CountingInstrumentation;
  16 const PassStatisticsInstrumentation = instrumentation.PassStatisticsInstrumentation;
  17 const PassMutationScope = subject.PassMutationScope;
  18 const dialectDependencies = subject.dialectDependencies;
  19 const Pass = subject.Pass;
  20 const analysisId = subject.analysisId;
  21 const AnalysisDescriptor = subject.AnalysisDescriptor;
  22 const Analysis = subject.Analysis;
  23 const PassManagerStats = subject.PassManagerStats;
  24 const PassFailureKind = subject.PassFailureKind;
  25 const PreservedAnalyses = subject.PreservedAnalyses;
  26 const AnalysisCache = subject.AnalysisCache;
  27 const PassContext = subject.PassContext;
  28 const PassResult = subject.PassResult;
  29 const PipelineEntry = subject.PipelineEntry;
  30 const OpPassManagerTargetKind = subject.OpPassManagerTargetKind;
  31 const OpPassManager = subject.OpPassManager;
  32 const PassManager = subject.PassManager;
  33 const OperationPass = subject.OperationPass;
  34 
  35 test {
  36     std.testing.refAllDecls(subject);
  37 }
  38 
  39 fn test_pass_success(_: *PassContext) PassResult {
  40     return .success;
  41 }
  42 
  43 fn test_pass_failure(_: *PassContext) PassResult {
  44     return .failure;
  45 }
  46 
  47 test "IR context census counts each PassManager pass run once" {
  48     const testing = std.testing;
  49     const test_dialect = @import("../../dialects/fixture/root.zig");
  50 
  51     var arena = alloc_arena.Arena.init(testing.allocator);
  52     defer arena.deinit();
  53     const allocator = arena.allocator();
  54 
  55     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
  56     defer ctx.deinit(allocator);
  57     try ir.dialects.loadDialectSpec(&ctx, test_dialect.TestDialect.spec);
  58     const module_op = try test_dialect.TestDialect.ModuleOp.create(
  59         &ctx,
  60         ir.Location.getUnknown(),
  61     );
  62 
  63     var normal = PassManager.init(allocator);
  64     defer normal.deinit();
  65     try normal.addPass(.{
  66         .name = "context-census-success",
  67         .description = "",
  68         .run_fn = test_pass_success,
  69     });
  70     try testing.expectEqual(PassResult.success, normal.run(module_op.op, &ctx));
  71     try testing.expectEqual(@as(u64, 1), ctx.passRunCount());
  72     try testing.expectEqual(
  73         PassResult.success,
  74         normal.runWithOptions(module_op.op, &ctx, .{}),
  75     );
  76     try testing.expectEqual(@as(u64, 2), ctx.passRunCount());
  77 
  78     var failing = PassManager.init(allocator);
  79     defer failing.deinit();
  80     try failing.addPass(.{
  81         .name = "context-census-failure",
  82         .description = "",
  83         .run_fn = test_pass_failure,
  84     });
  85     try testing.expectEqual(PassResult.failure, failing.run(module_op.op, &ctx));
  86     try testing.expectEqual(@as(u64, 3), ctx.passRunCount());
  87 
  88     var fixed = PassManager.init(allocator);
  89     defer fixed.deinit();
  90     try fixed.addPass(.{
  91         .name = "context-census-fixed-point",
  92         .description = "",
  93         .run_fn = test_pass_success,
  94     });
  95     const fixed_result = fixed.runToFixedPoint(module_op.op, &ctx, 8);
  96     try testing.expectEqual(PassResult.success, fixed_result.result);
  97     try testing.expectEqual(@as(usize, 1), fixed_result.iterations);
  98     try testing.expectEqual(@as(u64, 4), ctx.passRunCount());
  99 }
 100 
 101 const OperationPassTestTargetOp = struct {
 102     op: *ir.Operation,
 103     pub const operation_name = ir.dialects.operationName("test", "target_op");
 104 };
 105 
 106 const OperationPassCounter = struct {
 107     var count: usize = 0;
 108 
 109     fn run_on_op(_: *OperationPassTestTargetOp, _: *PassContext) PassResult {
 110         count += 1;
 111         return .success;
 112     }
 113 };
 114 
 115 const OperationPassTestFailOp = struct {
 116     op: *ir.Operation,
 117     pub const operation_name = ir.dialects.operationName("test", "fail_op");
 118 };
 119 
 120 const OperationPassFailCounter = struct {
 121     var count: usize = 0;
 122 
 123     fn run_on_op(_: *OperationPassTestFailOp, _: *PassContext) PassResult {
 124         count += 1;
 125         return .failure;
 126     }
 127 };
 128 
 129 test "OperationPass walks and matches operations" {
 130     const testing = std.testing;
 131     const test_dialect = @import("../../dialects/fixture/root.zig");
 132 
 133     var arena = alloc_arena.Arena.init(std.testing.allocator);
 134     defer arena.deinit();
 135     const allocator = arena.allocator();
 136 
 137     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
 138     defer ctx.deinit(allocator);
 139     try ctx.allowUnregistered();
 140 
 141     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
 142     const module_block = module_op.getBodyBlock();
 143 
 144     var builder = ir.OperationBuilder.init(&ctx);
 145     const target_name = ir.dialects.operationName("test", "target_op");
 146     const target1_state = ir.Operation.State.init(target_name, ir.Location.getUnknown());
 147     const target1_op = try builder.create(target1_state);
 148     try module_block.addOperation(target1_op);
 149 
 150     const func_op = try test_dialect.TestDialect.FuncOp.create(&ctx, ir.Location.getUnknown(), "test_func", &.{});
 151     try module_block.addOperation(func_op.op);
 152 
 153     const func_block = func_op.getEntryBlock();
 154 
 155     const target2_state = ir.Operation.State.init(target_name, ir.Location.getUnknown());
 156     const target2_op = try builder.create(target2_state);
 157     try func_block.addOperation(target2_op);
 158 
 159     const other_state = ir.Operation.State.init("test.other_op", ir.Location.getUnknown());
 160     const other_op = try builder.create(other_state);
 161     try func_block.addOperation(other_op);
 162 
 163     OperationPassCounter.count = 0;
 164 
 165     const TestPass = OperationPass(OperationPassTestTargetOp, OperationPassCounter.run_on_op);
 166     const pass = TestPass.init("test-pass", "Test pass for counting target ops");
 167 
 168     var analysis_cache = AnalysisCache.init(allocator, null);
 169     defer analysis_cache.deinit();
 170 
 171     var pass_ctx = PassContext.init(module_op.op, &ctx, allocator, &analysis_cache);
 172     defer pass_ctx.deinit();
 173     const result = pass.base.run(&pass_ctx);
 174 
 175     try testing.expectEqual(PassResult.success, result);
 176     try testing.expectEqual(@as(usize, 2), OperationPassCounter.count);
 177 }
 178 
 179 test "OperationPass stops on failure" {
 180     const testing = std.testing;
 181     const test_dialect = @import("../../dialects/fixture/root.zig");
 182 
 183     var arena = alloc_arena.Arena.init(std.testing.allocator);
 184     defer arena.deinit();
 185     const allocator = arena.allocator();
 186 
 187     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
 188     defer ctx.deinit(allocator);
 189     try ctx.allowUnregistered();
 190 
 191     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
 192     const module_block = module_op.getBodyBlock();
 193 
 194     var builder = ir.OperationBuilder.init(&ctx);
 195     const target_name = ir.dialects.operationName("test", "fail_op");
 196     const target1_state = ir.Operation.State.init(target_name, ir.Location.getUnknown());
 197     const target1_op = try builder.create(target1_state);
 198     try module_block.addOperation(target1_op);
 199 
 200     const target2_state = ir.Operation.State.init(target_name, ir.Location.getUnknown());
 201     const target2_op = try builder.create(target2_state);
 202     try module_block.addOperation(target2_op);
 203 
 204     OperationPassFailCounter.count = 0;
 205 
 206     const FailPass = OperationPass(OperationPassTestFailOp, OperationPassFailCounter.run_on_op);
 207     const pass = FailPass.init("fail-pass", "Pass that always fails");
 208 
 209     var analysis_cache = AnalysisCache.init(allocator, null);
 210     defer analysis_cache.deinit();
 211 
 212     var pass_ctx = PassContext.init(module_op.op, &ctx, allocator, &analysis_cache);
 213     defer pass_ctx.deinit();
 214     const result = pass.base.run(&pass_ctx);
 215 
 216     try testing.expectEqual(PassResult.failure, result);
 217     try testing.expectEqual(@as(usize, 1), OperationPassFailCounter.count);
 218 }
 219 
 220 fn preserveCompileTimeTestAnalysisSet(preserved: *PreservedAnalyses) void {
 221     preserved.preserveAnalysisSet(&.{
 222         analysisId("analysis.test.static.a"),
 223         analysisId("analysis.test.static.c"),
 224     });
 225 }
 226 
 227 test "PreservedAnalyses borrows compile-time analysis sets" {
 228     const testing = std.testing;
 229     const test_dialect = @import("../../dialects/fixture/root.zig");
 230     const desc_a = AnalysisDescriptor{
 231         .id = analysisId("analysis.test.static.a"),
 232         .name = "analysis.test.static.a",
 233     };
 234     const desc_b = AnalysisDescriptor{
 235         .id = analysisId("analysis.test.static.b"),
 236         .name = "analysis.test.static.b",
 237     };
 238     const desc_c = AnalysisDescriptor{
 239         .id = analysisId("analysis.test.static.c"),
 240         .name = "analysis.test.static.c",
 241     };
 242 
 243     var arena = alloc_arena.Arena.init(testing.allocator);
 244     defer arena.deinit();
 245     const allocator = arena.allocator();
 246 
 247     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
 248     defer ctx.deinit(allocator);
 249     const module_op = try test_dialect.TestDialect.ModuleOp.create(
 250         &ctx,
 251         ir.Location.getUnknown(),
 252     );
 253 
 254     var stats: PassManagerStats = .{};
 255     var analysis_cache = AnalysisCache.init(allocator, &stats);
 256     defer analysis_cache.deinit();
 257     var pass_ctx = PassContext.init(
 258         module_op.op,
 259         &ctx,
 260         allocator,
 261         &analysis_cache,
 262     );
 263     defer pass_ctx.deinit();
 264 
 265     analysis_counter = 0;
 266     _ = try pass_ctx.getAnalysis(module_op.op, &desc_a, computeCounter, cleanupCounter);
 267     _ = try pass_ctx.getAnalysis(module_op.op, &desc_b, computeCounter, cleanupCounter);
 268     _ = try pass_ctx.getAnalysis(module_op.op, &desc_c, computeCounter, cleanupCounter);
 269 
 270     var failing = testing.FailingAllocator.init(testing.allocator, .{ .fail_index = 0 });
 271     var preserved = PreservedAnalyses.init(failing.allocator());
 272     defer preserved.deinit();
 273 
 274     preserveCompileTimeTestAnalysisSet(&preserved);
 275 
 276     analysis_cache.invalidate(&preserved);
 277     const a = try pass_ctx.getAnalysis(module_op.op, &desc_a, computeCounter, cleanupCounter);
 278     const b = try pass_ctx.getAnalysis(module_op.op, &desc_b, computeCounter, cleanupCounter);
 279     const c = try pass_ctx.getAnalysis(module_op.op, &desc_c, computeCounter, cleanupCounter);
 280     try testing.expectEqual(@as(u32, 1), @as(*u32, @ptrCast(@alignCast(a))).*);
 281     try testing.expectEqual(@as(u32, 4), @as(*u32, @ptrCast(@alignCast(b))).*);
 282     try testing.expectEqual(@as(u32, 3), @as(*u32, @ptrCast(@alignCast(c))).*);
 283     try testing.expectEqual(@as(u64, 1), stats.analyses_invalidated);
 284     try preserved.preserveAnalysis(desc_a.id);
 285     try testing.expectEqual(@as(usize, 0), failing.alloc_index);
 286     try testing.expectError(error.OutOfMemory, preserved.preserveAnalysis(desc_b.id));
 287 }
 288 
 289 test "AnalysisCache invalidates based on preserved interfaces" {
 290     const testing = std.testing;
 291     const test_dialect = @import("../../dialects/fixture/root.zig");
 292 
 293     var arena = alloc_arena.Arena.init(std.testing.allocator);
 294     defer arena.deinit();
 295     const allocator = arena.allocator();
 296 
 297     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
 298     defer ctx.deinit(allocator);
 299 
 300     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
 301 
 302     var stats: PassManagerStats = .{};
 303     var analysis_cache = AnalysisCache.init(allocator, &stats);
 304     defer analysis_cache.deinit();
 305 
 306     var pass_ctx = PassContext.init(module_op.op, &ctx, allocator, &analysis_cache);
 307     defer pass_ctx.deinit();
 308 
 309     const desc_a = AnalysisDescriptor{
 310         .id = analysisId("analysis.test.a"),
 311         .name = "analysis.test.a",
 312     };
 313     const desc_b = AnalysisDescriptor{
 314         .id = analysisId("analysis.test.b"),
 315         .name = "analysis.test.b",
 316         .required_interfaces = &.{ir.interfaces.EffectOpInterface.id},
 317     };
 318     const desc_c = AnalysisDescriptor{
 319         .id = analysisId("analysis.test.c"),
 320         .name = "analysis.test.c",
 321     };
 322 
 323     analysis_counter = 0;
 324     const a_ptr1 = try pass_ctx.getAnalysis(module_op.op, &desc_a, computeCounter, cleanupCounter);
 325     const b_ptr1 = try pass_ctx.getAnalysis(module_op.op, &desc_b, computeCounter, cleanupCounter);
 326     const c_ptr1 = try pass_ctx.getAnalysis(module_op.op, &desc_c, computeCounter, cleanupCounter);
 327 
 328     try testing.expectEqual(@as(u32, 1), @as(*u32, @ptrCast(@alignCast(a_ptr1))).*);
 329     try testing.expectEqual(@as(u32, 2), @as(*u32, @ptrCast(@alignCast(b_ptr1))).*);
 330     try testing.expectEqual(@as(u32, 3), @as(*u32, @ptrCast(@alignCast(c_ptr1))).*);
 331 
 332     var preserved = PreservedAnalyses.init(allocator);
 333     defer preserved.deinit();
 334     try preserved.preserveInterface(ir.interfaces.EffectOpInterface.id);
 335 
 336     analysis_cache.invalidate(&preserved);
 337 
 338     const a_ptr2 = try pass_ctx.getAnalysis(module_op.op, &desc_a, computeCounter, cleanupCounter);
 339     const b_ptr2 = try pass_ctx.getAnalysis(module_op.op, &desc_b, computeCounter, cleanupCounter);
 340     const c_ptr2 = try pass_ctx.getAnalysis(module_op.op, &desc_c, computeCounter, cleanupCounter);
 341 
 342     try testing.expectEqual(@as(u32, 4), @as(*u32, @ptrCast(@alignCast(a_ptr2))).*);
 343     try testing.expectEqual(@as(u32, 2), @as(*u32, @ptrCast(@alignCast(b_ptr2))).*);
 344     try testing.expectEqual(@as(u32, 5), @as(*u32, @ptrCast(@alignCast(c_ptr2))).*);
 345     try testing.expectEqual(@as(u64, 2), stats.analyses_invalidated);
 346 }
 347 
 348 test "PassContext instruments analysis computation misses" {
 349     const testing = std.testing;
 350     const test_dialect = @import("../../dialects/fixture/root.zig");
 351 
 352     var arena = alloc_arena.Arena.init(std.testing.allocator);
 353     defer arena.deinit();
 354     const allocator = arena.allocator();
 355 
 356     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
 357     defer ctx.deinit(allocator);
 358 
 359     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
 360 
 361     var stats: PassManagerStats = .{};
 362     var analysis_cache = AnalysisCache.init(allocator, &stats);
 363     defer analysis_cache.deinit();
 364 
 365     var counter = CountingInstrumentation{};
 366     var timing = TimingInstrumentation.init(allocator);
 367     defer timing.deinit();
 368 
 369     var instrumentor = PassInstrumentor.init(allocator);
 370     defer instrumentor.deinit();
 371     try instrumentor.addInstrumentation(counter.instrumentation());
 372     try instrumentor.addInstrumentation(timing.instrumentation());
 373 
 374     var pass_ctx = PassContext.initWithInstrumentor(
 375         module_op.op,
 376         &ctx,
 377         allocator,
 378         &analysis_cache,
 379         &instrumentor,
 380     );
 381     defer pass_ctx.deinit();
 382 
 383     const desc = AnalysisDescriptor{
 384         .id = analysisId("analysis.test.instrumented"),
 385         .name = "analysis.test.instrumented",
 386     };
 387 
 388     analysis_counter = 0;
 389     const first = try pass_ctx.getAnalysis(module_op.op, &desc, computeCounter, cleanupCounter);
 390     const second = try pass_ctx.getAnalysis(module_op.op, &desc, computeCounter, cleanupCounter);
 391 
 392     try testing.expectEqual(first, second);
 393     try testing.expectEqual(@as(usize, 1), counter.analysis_count);
 394     try testing.expectEqual(@as(u64, 1), stats.analysis_misses);
 395     try testing.expectEqual(@as(u64, 1), stats.analysis_hits);
 396     try testing.expectEqual(@as(u64, 1), timing.getAnalysisCount(desc.name).?);
 397     try testing.expect(timing.getAnalysisTime(desc.name) != null);
 398 }
 399 
 400 test "Analysis wraps typed compute and cleanup callbacks" {
 401     const testing = std.testing;
 402     const test_dialect = @import("../../dialects/fixture/root.zig");
 403 
 404     var arena = alloc_arena.Arena.init(std.testing.allocator);
 405     defer arena.deinit();
 406     const allocator = arena.allocator();
 407 
 408     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
 409     defer ctx.deinit(allocator);
 410 
 411     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
 412 
 413     var stats: PassManagerStats = .{};
 414     typed_analysis_counter = 0;
 415     typed_analysis_cleanup_count = 0;
 416 
 417     {
 418         var analysis_cache = AnalysisCache.init(allocator, &stats);
 419         defer analysis_cache.deinit();
 420 
 421         var pass_ctx = PassContext.init(module_op.op, &ctx, allocator, &analysis_cache);
 422         defer pass_ctx.deinit();
 423 
 424         try testing.expectEqual(analysisId("analysis.test.typed"), TypedCounterAnalysis.id);
 425         try testing.expect(TypedCounterAnalysis.value_type == u32);
 426         try testing.expectEqualStrings("analysis.test.typed", TypedCounterAnalysis.descriptor.name);
 427 
 428         const first = try TypedCounterAnalysis.get(&pass_ctx, module_op.op);
 429         const second = try TypedCounterAnalysis.get(&pass_ctx, module_op.op);
 430 
 431         try testing.expectEqual(first, second);
 432         try testing.expectEqual(@as(u32, 1), first.*);
 433         try testing.expectEqual(@as(u64, 1), stats.analysis_misses);
 434         try testing.expectEqual(@as(u64, 1), stats.analysis_hits);
 435 
 436         var preserved = PreservedAnalyses.init(allocator);
 437         defer preserved.deinit();
 438         analysis_cache.invalidate(&preserved);
 439 
 440         try testing.expectEqual(@as(u32, 1), typed_analysis_cleanup_count);
 441 
 442         const third = try TypedCounterAnalysis.get(&pass_ctx, module_op.op);
 443         try testing.expectEqual(@as(u32, 2), third.*);
 444 
 445         try TypedCounterAnalysis.preserve(&pass_ctx);
 446         analysis_cache.invalidate(&pass_ctx.preserved);
 447         try testing.expectEqual(@as(u32, 1), typed_analysis_cleanup_count);
 448         try testing.expectEqual(third, try TypedCounterAnalysis.get(&pass_ctx, module_op.op));
 449     }
 450 
 451     try testing.expectEqual(@as(u32, 2), typed_analysis_cleanup_count);
 452 }
 453 
 454 test "AnalysisCache cleans computed values after insertion failure" {
 455     const testing = std.testing;
 456     const test_dialect = @import("../../dialects/fixture/root.zig");
 457     var ctx = try ir.Context.init(testing.allocator, ir.Context.Limits.testing);
 458     defer ctx.deinit(testing.allocator);
 459     const module = try test_dialect.TestDialect.ModuleOp.create(&ctx, .unknown);
 460     var failing = testing.FailingAllocator.init(testing.allocator, .{ .fail_index = 1 });
 461     var stats: PassManagerStats = .{};
 462     typed_analysis_counter = 0;
 463     typed_analysis_cleanup_count = 0;
 464     {
 465         var cache = AnalysisCache.init(failing.allocator(), &stats);
 466         defer cache.deinit();
 467         var pass_ctx = PassContext.init(module.op, &ctx, failing.allocator(), &cache);
 468         defer pass_ctx.deinit();
 469 
 470         try testing.expectError(error.OutOfMemory, TypedCounterAnalysis.get(&pass_ctx, module.op));
 471         try testing.expect(failing.has_induced_failure);
 472         try testing.expectEqual(@as(u32, 1), typed_analysis_counter);
 473         try testing.expectEqual(@as(u32, 1), typed_analysis_cleanup_count);
 474         try testing.expectEqual(@as(usize, 0), cache.entries.count());
 475         try testing.expectEqual(@as(u64, 0), stats.analysis_misses);
 476 
 477         failing.fail_index = std.math.maxInt(usize);
 478         const fresh = try TypedCounterAnalysis.get(&pass_ctx, module.op);
 479         try testing.expectEqual(@as(u32, 2), fresh.*);
 480         try testing.expectEqual(@as(u32, 2), typed_analysis_counter);
 481         try testing.expectEqual(@as(u32, 1), typed_analysis_cleanup_count);
 482         try testing.expectEqual(@as(usize, 1), cache.entries.count());
 483         try testing.expectEqual(@as(u64, 1), stats.analysis_misses);
 484         try testing.expectEqual(fresh, try TypedCounterAnalysis.get(&pass_ctx, module.op));
 485         try testing.expectEqual(@as(u32, 2), typed_analysis_counter);
 486         try testing.expectEqual(@as(u64, 1), stats.analysis_hits);
 487     }
 488     try testing.expectEqual(@as(u32, 2), typed_analysis_cleanup_count);
 489 }
 490 
 491 const OperationVersionAnalysis = Analysis(
 492     i64,
 493     "analysis.test.operation-version",
 494     &.{},
 495     computeOperationVersion,
 496     cleanupOperationVersion,
 497     null,
 498 );
 499 
 500 fn computeOperationVersion(ctx: *PassContext, op: *ir.Operation) anyerror!*i64 {
 501     const version = op.getAttr("analysis_version").?.cast(ir.Attribute.IntegerAttr).?.value;
 502     const value = try ctx.allocator.create(i64);
 503     value.* = version;
 504     return value;
 505 }
 506 
 507 fn cleanupOperationVersion(value: *i64, allocator: std.mem.Allocator) void {
 508     allocator.destroy(value);
 509 }
 510 
 511 const MutatingFailurePass = struct {
 512     fn run(ctx: *PassContext) PassResult {
 513         const version = ctx.ir_ctx.getI64Attr(2) catch return .failure;
 514         ctx.op.setAttr("analysis_version", version) catch return .failure;
 515         ctx.markModified();
 516         return .failure;
 517     }
 518 
 519     fn preserving(ctx: *PassContext) PassResult {
 520         ctx.preserveAllAnalyses();
 521         return run(ctx);
 522     }
 523 };
 524 
 525 test "PassManager failing mutations honor analysis preservation" {
 526     try expectFailedMutationCache(false);
 527     try expectFailedMutationCache(true);
 528 }
 529 
 530 fn expectFailedMutationCache(preserve_all: bool) !void {
 531     const testing = std.testing;
 532     const allocator = testing.allocator;
 533     const test_dialect = @import("../../dialects/fixture/root.zig");
 534     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
 535     defer ctx.deinit(allocator);
 536     try ir.dialects.loadDialectSpec(&ctx, test_dialect.TestDialect.spec);
 537     const module = try test_dialect.TestDialect.ModuleOp.create(&ctx, .unknown);
 538     try module.op.setAttr("analysis_version", try ctx.getI64Attr(1));
 539     _ = try ctx.getI64Attr(2);
 540     var stats: PassManagerStats = .{};
 541     var cache = AnalysisCache.init(allocator, &stats);
 542     defer cache.deinit();
 543     var pass_ctx = PassContext.init(module.op, &ctx, allocator, &cache);
 544     defer pass_ctx.deinit();
 545     const before = (try OperationVersionAnalysis.get(&pass_ctx, module.op)).*;
 546     try testing.expectEqual(@as(i64, 1), before);
 547     var pm = PassManager.init(allocator);
 548     defer pm.deinit();
 549     var counter = CountingInstrumentation{};
 550     try pm.addInstrumentation(counter.instrumentation());
 551     try pm.addPass(.{
 552         .name = "mutating-failure",
 553         .description = "",
 554         .run_fn = if (preserve_all) MutatingFailurePass.preserving else MutatingFailurePass.run,
 555     });
 556 
 557     try testing.expectEqual(.failure, pm.runWithAnalysisCache(module.op, &ctx, &cache, .{}));
 558     try testing.expectEqual(@as(u64, 1), pm.stats.pass_failures);
 559     try testing.expectEqual(@as(u64, 1), pm.stats.passes_modified);
 560     try testing.expectEqual(@as(usize, 1), counter.pass_failures);
 561     const invalidated: u64 = if (preserve_all) 0 else 1;
 562     try testing.expectEqual(invalidated, pm.stats.analyses_invalidated);
 563     try testing.expect(cache.stats == &stats);
 564     const observed = module.op.getAttr("analysis_version").?.cast(ir.Attribute.IntegerAttr).?.value;
 565     try testing.expectEqual(@as(i64, 2), observed);
 566     const after = (try OperationVersionAnalysis.get(&pass_ctx, module.op)).*;
 567     try testing.expectEqual(if (preserve_all) before else observed, after);
 568     const misses: u64 = if (preserve_all) 1 else 2;
 569     const hits: u64 = if (preserve_all) 1 else 0;
 570     try testing.expectEqual(misses, stats.analysis_misses);
 571     try testing.expectEqual(hits, stats.analysis_hits);
 572 }
 573 
 574 var analysis_counter: u32 = 0;
 575 
 576 fn computeCounter(ctx: *PassContext, _: *ir.Operation) anyerror!*anyopaque {
 577     analysis_counter += 1;
 578     const ptr = try ctx.allocator.create(u32);
 579     ptr.* = analysis_counter;
 580     return @ptrCast(ptr);
 581 }
 582 
 583 fn cleanupCounter(value: *anyopaque, allocator: std.mem.Allocator) void {
 584     const ptr: *u32 = @ptrCast(@alignCast(value));
 585     allocator.destroy(ptr);
 586 }
 587 
 588 const TypedCounterAnalysis = Analysis(
 589     u32,
 590     "analysis.test.typed",
 591     &.{},
 592     computeTypedCounter,
 593     cleanupTypedCounter,
 594     null,
 595 );
 596 
 597 var typed_analysis_counter: u32 = 0;
 598 var typed_analysis_cleanup_count: u32 = 0;
 599 
 600 fn computeTypedCounter(ctx: *PassContext, _: *ir.Operation) anyerror!*u32 {
 601     typed_analysis_counter += 1;
 602     const ptr = try ctx.allocator.create(u32);
 603     ptr.* = typed_analysis_counter;
 604     return ptr;
 605 }
 606 
 607 fn cleanupTypedCounter(value: *u32, allocator: std.mem.Allocator) void {
 608     typed_analysis_cleanup_count += 1;
 609     allocator.destroy(value);
 610 }
 611 
 612 const CallerCacheAnalysisRunner = struct {
 613     fn run(pass_ctx: *PassContext) PassResult {
 614         _ = TypedCounterAnalysis.get(pass_ctx, pass_ctx.op) catch return .failure;
 615         pass_ctx.preserveAllAnalyses();
 616         return .success;
 617     }
 618 };
 619 
 620 test "PassManager.runWithAnalysisCache leaves caller cache reusable" {
 621     const testing = std.testing;
 622     const test_dialect = @import("../../dialects/fixture/root.zig");
 623 
 624     const allocator = testing.allocator;
 625 
 626     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
 627     defer ctx.deinit(allocator);
 628     try ir.dialects.loadDialectSpec(&ctx, test_dialect.TestDialect.spec);
 629 
 630     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
 631 
 632     typed_analysis_counter = 0;
 633     typed_analysis_cleanup_count = 0;
 634 
 635     var cache = AnalysisCache.init(allocator, null);
 636     defer cache.deinit();
 637 
 638     var pm = PassManager.init(allocator);
 639     defer pm.deinit();
 640     try pm.addPass(.{
 641         .name = "caller-cache-analysis",
 642         .description = "",
 643         .run_fn = CallerCacheAnalysisRunner.run,
 644         .mutation_scope = .read_only,
 645     });
 646 
 647     try testing.expectEqual(PassResult.success, pm.runWithAnalysisCache(module_op.op, &ctx, &cache, .{}));
 648     try testing.expect(cache.stats == null);
 649     try testing.expectEqual(@as(u64, 1), pm.stats.analysis_misses);
 650     try testing.expectEqual(@as(u32, 1), typed_analysis_counter);
 651 
 652     var pass_ctx = PassContext.init(module_op.op, &ctx, allocator, &cache);
 653     defer pass_ctx.deinit();
 654     const value = try TypedCounterAnalysis.get(&pass_ctx, module_op.op);
 655 
 656     try testing.expectEqual(@as(u32, 1), value.*);
 657     try testing.expectEqual(@as(u32, 1), typed_analysis_counter);
 658     try testing.expectEqual(@as(u32, 0), typed_analysis_cleanup_count);
 659 }
 660 
 661 fn loadPassDependencyTestDialect(ctx: *ir.Context) !void {
 662     try ir.dialects.loadDialectSpec(ctx, .{ .name = "passdep" });
 663 }
 664 
 665 fn loadFixedPointDependencyTestDialect(ctx: *ir.Context) !void {
 666     try ir.dialects.loadDialectSpec(ctx, .{ .name = "fixeddep" });
 667 }
 668 
 669 fn appendInvalidTestConstant(ctx: *PassContext) PassResult {
 670     const test_dialect = @import("../../dialects/fixture/root.zig");
 671 
 672     const module = test_dialect.TestDialect.ModuleOp{ .op = ctx.op };
 673     appendInvalidTestConstantToBlock(ctx.ir_ctx, module.getBodyBlock()) catch return .failure;
 674     ctx.markModified();
 675     return .success;
 676 }
 677 
 678 fn appendInvalidTestConstantToBlock(ctx: *ir.Context, block: *ir.Block) !void {
 679     const test_dialect = @import("../../dialects/fixture/root.zig");
 680 
 681     const i32_type = try test_dialect.TestDialect.getI32Type(ctx);
 682     var builder = ir.OperationBuilder.init(ctx);
 683     var state = ir.Operation.State.init(test_dialect.TestDialect.ConstantOp.operation_name, ir.Location.getUnknown());
 684     state.addTypes(&.{i32_type});
 685     const bad_op = try builder.create(state);
 686     try block.addOperation(bad_op);
 687 }
 688 
 689 test "OpPassManager basic nesting" {
 690     const testing = std.testing;
 691     const allocator = testing.allocator;
 692 
 693     var pm = OpPassManager.init(allocator, null);
 694     defer pm.deinit();
 695 
 696     try testing.expectEqual(@as(?[]const u8, null), pm.target_op_name);
 697     try testing.expectEqual(OpPassManagerTargetKind.root, pm.target_kind);
 698     try testing.expectEqual(@as(usize, 0), pm.getNestingDepth());
 699 
 700     const func_pm = try pm.nest("func.func");
 701     try testing.expectEqualStrings("func.func", func_pm.target_op_name.?);
 702     try testing.expectEqual(OpPassManagerTargetKind.op, func_pm.target_kind);
 703     try testing.expectEqual(@as(usize, 1), func_pm.getNestingDepth());
 704     try testing.expectEqual(&pm, func_pm.parent.?);
 705 
 706     const loop_pm = try func_pm.nest("scf.for");
 707     try testing.expectEqualStrings("scf.for", loop_pm.target_op_name.?);
 708     try testing.expectEqual(@as(usize, 2), loop_pm.getNestingDepth());
 709     try testing.expectEqual(func_pm, loop_pm.parent.?);
 710 }
 711 
 712 test "OpPassManager supports op-agnostic nested manager" {
 713     const testing = std.testing;
 714     const allocator = testing.allocator;
 715 
 716     var pm = OpPassManager.init(allocator, null);
 717     defer pm.deinit();
 718 
 719     const any_pm = try pm.nestAny();
 720     try testing.expectEqual(@as(?[]const u8, null), any_pm.target_op_name);
 721     try testing.expectEqual(OpPassManagerTargetKind.any, any_pm.target_kind);
 722     try testing.expectEqual(@as(usize, 1), any_pm.getNestingDepth());
 723     try testing.expectEqual(&pm, any_pm.parent.?);
 724 
 725     try testing.expectEqual(@as(usize, 1), pm.pipeline.items.len);
 726     try testing.expectEqual(PipelineEntry{ .nested = any_pm }, pm.pipeline.items[0]);
 727 }
 728 
 729 test "PassManager.nest creates nested pipeline" {
 730     const testing = std.testing;
 731     const allocator = testing.allocator;
 732 
 733     var pm = PassManager.init(allocator);
 734     defer pm.deinit();
 735 
 736     const func_pm = try pm.nest("func.func");
 737     try testing.expectEqualStrings("func.func", func_pm.target_op_name.?);
 738 
 739     try testing.expectEqual(@as(usize, 1), pm.root.pipeline.items.len);
 740     try testing.expectEqual(PipelineEntry{ .nested = func_pm }, pm.root.pipeline.items[0]);
 741 }
 742 
 743 test "PassManager.nestAny creates op-agnostic nested pipeline" {
 744     const testing = std.testing;
 745     const allocator = testing.allocator;
 746 
 747     var pm = PassManager.init(allocator);
 748     defer pm.deinit();
 749 
 750     const any_pm = try pm.nestAny();
 751     try testing.expectEqual(OpPassManagerTargetKind.any, any_pm.target_kind);
 752     try testing.expectEqual(@as(?[]const u8, null), any_pm.target_op_name);
 753 
 754     try testing.expectEqual(@as(usize, 1), pm.root.pipeline.items.len);
 755     try testing.expectEqual(PipelineEntry{ .nested = any_pm }, pm.root.pipeline.items[0]);
 756 }
 757 
 758 const UnregisteredTargetCounter = struct {
 759     var count: usize = 0;
 760 
 761     fn run(_: *PassContext) PassResult {
 762         count += 1;
 763         return .success;
 764     }
 765 };
 766 
 767 test "PassManager rejects unregistered nested pass targets" {
 768     const testing = std.testing;
 769     const test_dialect = @import("../../dialects/fixture/root.zig");
 770 
 771     var arena = alloc_arena.Arena.init(std.testing.allocator);
 772     defer arena.deinit();
 773     const allocator = arena.allocator();
 774 
 775     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
 776     defer ctx.deinit(allocator);
 777     try ctx.allowUnregistered();
 778 
 779     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
 780     const module_block = module_op.getBodyBlock();
 781 
 782     var builder = ir.OperationBuilder.init(&ctx);
 783     const state = ir.Operation.State.init("test.unregistered_target", ir.Location.getUnknown());
 784     const target = try builder.create(state);
 785     try module_block.addOperation(target);
 786 
 787     UnregisteredTargetCounter.count = 0;
 788 
 789     var pm = PassManager.init(allocator);
 790     defer pm.deinit();
 791 
 792     const nested = try pm.nest("test.unregistered_target");
 793     try nested.addPass(.{
 794         .name = "must-not-run",
 795         .description = "",
 796         .run_fn = UnregisteredTargetCounter.run,
 797     });
 798 
 799     try testing.expectEqual(PassResult.failure, pm.run(module_op.op, &ctx));
 800     try testing.expectEqual(@as(usize, 0), UnregisteredTargetCounter.count);
 801 }
 802 
 803 const NonIsolatedTargetCounter = struct {
 804     var count: usize = 0;
 805 
 806     fn run(_: *PassContext) PassResult {
 807         count += 1;
 808         return .success;
 809     }
 810 };
 811 
 812 test "PassManager rejects non-isolated nested pass targets" {
 813     const testing = std.testing;
 814     const test_dialect = @import("../../dialects/fixture/root.zig");
 815 
 816     var arena = alloc_arena.Arena.init(std.testing.allocator);
 817     defer arena.deinit();
 818     const allocator = arena.allocator();
 819 
 820     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
 821     defer ctx.deinit(allocator);
 822 
 823     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
 824     const module_block = module_op.getBodyBlock();
 825 
 826     _ = try ctx.registerOperation("test.non_isolated_target", .{});
 827 
 828     var builder = ir.OperationBuilder.init(&ctx);
 829     const state = ir.Operation.State.init("test.non_isolated_target", ir.Location.getUnknown());
 830     const target = try builder.create(state);
 831     try module_block.addOperation(target);
 832 
 833     NonIsolatedTargetCounter.count = 0;
 834 
 835     var pm = PassManager.init(allocator);
 836     defer pm.deinit();
 837 
 838     const nested = try pm.nest("test.non_isolated_target");
 839     try nested.addPass(.{
 840         .name = "must-not-run",
 841         .description = "",
 842         .run_fn = NonIsolatedTargetCounter.run,
 843     });
 844 
 845     try testing.expectEqual(PassResult.failure, pm.run(module_op.op, &ctx));
 846     try testing.expectEqual(@as(usize, 0), NonIsolatedTargetCounter.count);
 847 }
 848 
 849 const AnyTargetCounter = struct {
 850     var count: usize = 0;
 851     var saw_func = false;
 852 
 853     fn run(ctx: *PassContext) PassResult {
 854         const dialect = @import("../../dialects/fixture/root.zig");
 855         count += 1;
 856         if (std.mem.eql(u8, ctx.op.name.name, dialect.TestDialect.FuncOp.operation_name)) {
 857             saw_func = true;
 858         }
 859         return .success;
 860     }
 861 };
 862 
 863 test "PassManager.nestAny runs registered isolated targets and skips non-viable operations" {
 864     const testing = std.testing;
 865     const test_dialect = @import("../../dialects/fixture/root.zig");
 866 
 867     var arena = alloc_arena.Arena.init(std.testing.allocator);
 868     defer arena.deinit();
 869     const allocator = arena.allocator();
 870 
 871     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
 872     defer ctx.deinit(allocator);
 873     try ctx.allowUnregistered();
 874 
 875     const loc = ir.Location.getUnknown();
 876     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
 877     const module_block = module_op.getBodyBlock();
 878 
 879     _ = try ctx.registerOperation("test.non_isolated_target", .{});
 880 
 881     var builder = ir.OperationBuilder.init(&ctx);
 882     const unregistered_state = ir.Operation.State.init("test.unregistered_target", loc);
 883     const unregistered = try builder.create(unregistered_state);
 884     try module_block.addOperation(unregistered);
 885 
 886     const non_isolated_state = ir.Operation.State.init("test.non_isolated_target", loc);
 887     const non_isolated = try builder.create(non_isolated_state);
 888     try module_block.addOperation(non_isolated);
 889 
 890     const func_op = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "any_target", &.{});
 891     try module_block.addOperation(func_op.op);
 892 
 893     AnyTargetCounter.count = 0;
 894     AnyTargetCounter.saw_func = false;
 895 
 896     var pm = PassManager.init(allocator);
 897     defer pm.deinit();
 898 
 899     const any_pm = try pm.nestAny();
 900     try any_pm.addPass(.{
 901         .name = "any-counter",
 902         .description = "counts any isolated target",
 903         .run_fn = AnyTargetCounter.run,
 904     });
 905 
 906     try testing.expectEqual(PassResult.success, pm.run(module_op.op, &ctx));
 907     try testing.expectEqual(@as(usize, 1), AnyTargetCounter.count);
 908     try testing.expect(AnyTargetCounter.saw_func);
 909 }
 910 
 911 test "OpPassManager collects dependent dialects from nested pipelines" {
 912     const testing = std.testing;
 913     const allocator = testing.allocator;
 914 
 915     var pm = PassManager.init(allocator);
 916     defer pm.deinit();
 917 
 918     try pm.addPass(.{
 919         .name = "root-dependent-pass",
 920         .description = "",
 921         .run_fn = test_pass_success,
 922         .dependent_dialects = dialectDependencies(&.{ "firstdep", "shareddep" }),
 923     });
 924 
 925     const nested = try pm.nest("test.func");
 926     try nested.addPass(.{
 927         .name = "nested-dependent-pass",
 928         .description = "",
 929         .run_fn = test_pass_success,
 930         .dependent_dialects = dialectDependencies(&.{ "shareddep", "nesteddep" }),
 931     });
 932 
 933     var names: std.ArrayListUnmanaged([]const u8) = .empty;
 934     defer names.deinit(allocator);
 935     try pm.root.collectDependentDialects(allocator, &names);
 936 
 937     try testing.expectEqual(@as(usize, 3), names.items.len);
 938     try testing.expectEqualStrings("firstdep", names.items[0]);
 939     try testing.expectEqualStrings("shareddep", names.items[1]);
 940     try testing.expectEqualStrings("nesteddep", names.items[2]);
 941 }
 942 
 943 const DependentDialectRunner = struct {
 944     var saw_loaded = false;
 945 
 946     fn run(ctx: *PassContext) PassResult {
 947         saw_loaded = ctx.ir_ctx.isDialectLoaded("passdep");
 948         ctx.preserveAllAnalyses();
 949         return if (saw_loaded) .success else .failure;
 950     }
 951 };
 952 
 953 test "PassManager preloads dependent dialects before running passes" {
 954     const testing = std.testing;
 955     const test_dialect = @import("../../dialects/fixture/root.zig");
 956 
 957     var arena = alloc_arena.Arena.init(std.testing.allocator);
 958     defer arena.deinit();
 959     const allocator = arena.allocator();
 960 
 961     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
 962     defer ctx.deinit(allocator);
 963     try ir.dialects.loadDialectSpec(&ctx, test_dialect.TestDialect.spec);
 964 
 965     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
 966     try ctx.requireRegistered();
 967     try ctx.registerDialectLoader("passdep", loadPassDependencyTestDialect);
 968 
 969     DependentDialectRunner.saw_loaded = false;
 970 
 971     var pm = PassManager.init(allocator);
 972     defer pm.deinit();
 973     try pm.addPass(.{
 974         .name = "dependent-dialect-runner",
 975         .description = "",
 976         .run_fn = DependentDialectRunner.run,
 977         .mutation_scope = .read_only,
 978         .dependent_dialects = dialectDependencies(&.{"passdep"}),
 979     });
 980 
 981     try testing.expect(!ctx.isDialectLoaded("passdep"));
 982     try testing.expectEqual(PassResult.success, pm.run(module_op.op, &ctx));
 983     try testing.expect(DependentDialectRunner.saw_loaded);
 984     try testing.expect(ctx.isDialectLoaded("passdep"));
 985 }
 986 
 987 const VerifierFailureFollower = struct {
 988     var runs: usize = 0;
 989 
 990     fn run(_: *PassContext) PassResult {
 991         runs += 1;
 992         return .success;
 993     }
 994 };
 995 
 996 test "PassManager verifier stops pipeline after invalid IR" {
 997     const testing = std.testing;
 998     const test_dialect = @import("../../dialects/fixture/root.zig");
 999 
1000     var arena = alloc_arena.Arena.init(std.testing.allocator);
1001     defer arena.deinit();
1002     const allocator = arena.allocator();
1003 
1004     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1005     defer ctx.deinit(allocator);
1006     try ir.dialects.loadDialectSpec(&ctx, test_dialect.TestDialect.spec);
1007 
1008     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
1009 
1010     VerifierFailureFollower.runs = 0;
1011 
1012     var pm = PassManager.init(allocator);
1013     defer pm.deinit();
1014     pm.enableVerifier();
1015     try pm.addPass(.{
1016         .name = "append-invalid-constant",
1017         .description = "",
1018         .run_fn = appendInvalidTestConstant,
1019     });
1020     try pm.addPass(.{
1021         .name = "must-not-run-after-verifier-failure",
1022         .description = "",
1023         .run_fn = VerifierFailureFollower.run,
1024     });
1025 
1026     try testing.expect(pm.verifierEnabled());
1027     try testing.expectEqual(PassResult.failure, pm.run(module_op.op, &ctx));
1028     try testing.expectEqual(@as(u64, 1), pm.stats.pass_runs);
1029     try testing.expectEqual(@as(u64, 0), pm.stats.pass_failures);
1030     try testing.expectEqual(@as(u64, 1), pm.stats.verifier_failures);
1031     try testing.expectEqual(@as(usize, 0), VerifierFailureFollower.runs);
1032 
1033     const failure = pm.getLastVerifierFailure() orelse return error.TestExpectedVerifierFailure;
1034     try testing.expectEqualStrings("append-invalid-constant", failure.pass_name);
1035     try testing.expectEqualStrings(test_dialect.TestDialect.ModuleOp.operation_name, failure.target_op_name);
1036     try testing.expectEqual(error.MissingRequiredAttribute, failure.err);
1037 
1038     const reproducer = pm.getLastFailureReproducer() orelse return error.TestExpectedReproducer;
1039     try testing.expectEqualStrings("append-invalid-constant,must-not-run-after-verifier-failure", reproducer.pipeline);
1040     try testing.expect(reproducer.verifier_enabled);
1041     try testing.expectEqual(PassFailureKind.verifier, reproducer.failure_kind.?);
1042     try testing.expectEqualStrings("append-invalid-constant", reproducer.pass_name.?);
1043     try testing.expectEqual(error.MissingRequiredAttribute, reproducer.verifier_error.?);
1044 }
1045 
1046 test "PassManager verifier is opt-in" {
1047     const testing = std.testing;
1048     const test_dialect = @import("../../dialects/fixture/root.zig");
1049 
1050     var arena = alloc_arena.Arena.init(std.testing.allocator);
1051     defer arena.deinit();
1052     const allocator = arena.allocator();
1053 
1054     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1055     defer ctx.deinit(allocator);
1056     try ir.dialects.loadDialectSpec(&ctx, test_dialect.TestDialect.spec);
1057 
1058     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
1059 
1060     var pm = PassManager.init(allocator);
1061     defer pm.deinit();
1062     try pm.addPass(.{
1063         .name = "append-invalid-constant",
1064         .description = "",
1065         .run_fn = appendInvalidTestConstant,
1066     });
1067 
1068     try testing.expect(!pm.verifierEnabled());
1069     try testing.expectEqual(PassResult.success, pm.run(module_op.op, &ctx));
1070     try testing.expectEqual(@as(u64, 1), pm.stats.pass_runs);
1071     try testing.expectEqual(@as(u64, 1), ctx.passRunCount());
1072     try testing.expectEqual(@as(u64, 0), pm.stats.verifier_failures);
1073     try testing.expect(pm.getLastVerifierFailure() == null);
1074 }
1075 
1076 const MissingDialectRunner = struct {
1077     var runs: usize = 0;
1078 
1079     fn run(_: *PassContext) PassResult {
1080         runs += 1;
1081         return .success;
1082     }
1083 };
1084 
1085 test "PassManager fails before running passes when dependent dialects are unavailable" {
1086     const testing = std.testing;
1087     const test_dialect = @import("../../dialects/fixture/root.zig");
1088 
1089     var arena = alloc_arena.Arena.init(std.testing.allocator);
1090     defer arena.deinit();
1091     const allocator = arena.allocator();
1092 
1093     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1094     defer ctx.deinit(allocator);
1095     try ir.dialects.loadDialectSpec(&ctx, test_dialect.TestDialect.spec);
1096 
1097     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
1098     try ctx.requireRegistered();
1099 
1100     MissingDialectRunner.runs = 0;
1101 
1102     var pm = PassManager.init(allocator);
1103     defer pm.deinit();
1104     try pm.addPass(.{
1105         .name = "missing-dependent-dialect-runner",
1106         .description = "",
1107         .run_fn = MissingDialectRunner.run,
1108         .dependent_dialects = dialectDependencies(&.{"missingdep"}),
1109     });
1110 
1111     try testing.expectEqual(PassResult.failure, pm.run(module_op.op, &ctx));
1112     try testing.expectEqual(@as(usize, 0), MissingDialectRunner.runs);
1113     try testing.expectEqual(@as(u64, 0), pm.stats.pass_runs);
1114 }
1115 
1116 test "PassManager.runToFixedPoint stops on verifier failure" {
1117     const testing = std.testing;
1118     const test_dialect = @import("../../dialects/fixture/root.zig");
1119 
1120     var arena = alloc_arena.Arena.init(std.testing.allocator);
1121     defer arena.deinit();
1122     const allocator = arena.allocator();
1123 
1124     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1125     defer ctx.deinit(allocator);
1126     try ir.dialects.loadDialectSpec(&ctx, test_dialect.TestDialect.spec);
1127 
1128     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
1129 
1130     var pm = PassManager.init(allocator);
1131     defer pm.deinit();
1132     pm.enableVerifier();
1133     try pm.addPass(.{
1134         .name = "append-invalid-constant",
1135         .description = "",
1136         .run_fn = appendInvalidTestConstant,
1137     });
1138 
1139     const result = pm.runToFixedPoint(module_op.op, &ctx, 8);
1140     try testing.expectEqual(PassResult.failure, result.result);
1141     try testing.expect(!result.changed);
1142     try testing.expectEqual(@as(usize, 1), result.iterations);
1143     try testing.expectEqual(@as(u64, 1), pm.stats.pass_runs);
1144     try testing.expectEqual(@as(u64, 1), ctx.passRunCount());
1145     try testing.expectEqual(@as(u64, 1), pm.stats.verifier_failures);
1146 
1147     const failure = pm.getLastVerifierFailure() orelse return error.TestExpectedVerifierFailure;
1148     try testing.expectEqualStrings("append-invalid-constant", failure.pass_name);
1149     try testing.expectEqual(error.MissingRequiredAttribute, failure.err);
1150 }
1151 
1152 const FixedPointDependencyRunner = struct {
1153     var runs: usize = 0;
1154 
1155     fn run(ctx: *PassContext) PassResult {
1156         if (!ctx.ir_ctx.isDialectLoaded("fixeddep")) return .failure;
1157         runs += 1;
1158         ctx.preserveAllAnalyses();
1159         return .success;
1160     }
1161 };
1162 
1163 test "PassManager.runToFixedPoint preloads dependent dialects before iteration" {
1164     const testing = std.testing;
1165     const test_dialect = @import("../../dialects/fixture/root.zig");
1166 
1167     var arena = alloc_arena.Arena.init(std.testing.allocator);
1168     defer arena.deinit();
1169     const allocator = arena.allocator();
1170 
1171     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1172     defer ctx.deinit(allocator);
1173     try ir.dialects.loadDialectSpec(&ctx, test_dialect.TestDialect.spec);
1174 
1175     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
1176     try ctx.requireRegistered();
1177     try ctx.registerDialectLoader("fixeddep", loadFixedPointDependencyTestDialect);
1178 
1179     FixedPointDependencyRunner.runs = 0;
1180 
1181     var pm = PassManager.init(allocator);
1182     defer pm.deinit();
1183     try pm.addPass(.{
1184         .name = "fixed-dependent-dialect-runner",
1185         .description = "",
1186         .run_fn = FixedPointDependencyRunner.run,
1187         .dependent_dialects = dialectDependencies(&.{"fixeddep"}),
1188     });
1189 
1190     const result = pm.runToFixedPoint(module_op.op, &ctx, 8);
1191     try testing.expectEqual(PassResult.success, result.result);
1192     try testing.expect(!result.changed);
1193     try testing.expectEqual(@as(usize, 1), result.iterations);
1194     try testing.expectEqual(@as(usize, 1), FixedPointDependencyRunner.runs);
1195     try testing.expect(ctx.isDialectLoaded("fixeddep"));
1196 }
1197 
1198 const MutationScopeCapture = struct {
1199     scope: ?PassMutationScope = null,
1200 
1201     fn before(raw: ?*anyopaque, info: PassInfo) void {
1202         const self: *@This() = @ptrCast(@alignCast(raw orelse return));
1203         self.scope = info.mutation_scope;
1204     }
1205 };
1206 
1207 const MutationScopeRunner = struct {
1208     fn run(ctx: *PassContext) PassResult {
1209         ctx.preserveAllAnalyses();
1210         return .success;
1211     }
1212 };
1213 
1214 const FixedPointRerunRunner = struct {
1215     var runs: usize = 0;
1216     var modify_first = false;
1217 
1218     fn run(ctx: *PassContext) PassResult {
1219         runs += 1;
1220         if (modify_first and runs == 1) {
1221             ctx.markModified();
1222         } else {
1223             ctx.preserveAllAnalyses();
1224         }
1225         return .success;
1226     }
1227 };
1228 
1229 const RerunIntervalRunner = struct {
1230     var runs: usize = 0;
1231 
1232     fn preserve(ctx: *PassContext) PassResult {
1233         runs += 1;
1234         ctx.preserveAllAnalyses();
1235         return .success;
1236     }
1237 
1238     fn modify(ctx: *PassContext) PassResult {
1239         runs += 1;
1240         ctx.markModified();
1241         return .success;
1242     }
1243 };
1244 
1245 const StatefulRerunRunner = struct {
1246     fn run(_: ?*anyopaque, _: *PassContext) PassResult {
1247         return .success;
1248     }
1249 };
1250 
1251 test "Pass mutation scope reaches instrumentation" {
1252     const testing = std.testing;
1253     const test_dialect = @import("../../dialects/fixture/root.zig");
1254 
1255     var arena = alloc_arena.Arena.init(std.testing.allocator);
1256     defer arena.deinit();
1257     const allocator = arena.allocator();
1258 
1259     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1260     defer ctx.deinit(allocator);
1261 
1262     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
1263 
1264     var capture = MutationScopeCapture{};
1265     var pm = PassManager.init(allocator);
1266     defer pm.deinit();
1267     try pm.addInstrumentation(.{
1268         .ctx = &capture,
1269         .runBeforePass = MutationScopeCapture.before,
1270     });
1271     try pm.addPass(.{
1272         .name = "read-only-test",
1273         .description = "records pass mutation metadata",
1274         .run_fn = MutationScopeRunner.run,
1275         .mutation_scope = .read_only,
1276     });
1277 
1278     try testing.expectEqual(PassResult.success, pm.run(module_op.op, &ctx));
1279     try testing.expectEqual(PassMutationScope.read_only, capture.scope.?);
1280 }
1281 
1282 test "PassManager skips a fixed-point rerun at the current revision" {
1283     const testing = std.testing;
1284     const test_dialect = @import("../../dialects/fixture/root.zig");
1285 
1286     const allocator = testing.allocator;
1287 
1288     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1289     defer ctx.deinit(allocator);
1290 
1291     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
1292 
1293     FixedPointRerunRunner.runs = 0;
1294     FixedPointRerunRunner.modify_first = true;
1295     RerunIntervalRunner.runs = 0;
1296 
1297     const fixed_point_pass = Pass{
1298         .name = "fixed-point-rerun",
1299         .description = "",
1300         .run_fn = FixedPointRerunRunner.run,
1301         .rerun_policy = .skip_if_unchanged,
1302     };
1303 
1304     var counter = CountingInstrumentation{};
1305     var pm = PassManager.init(allocator);
1306     defer pm.deinit();
1307     try pm.addInstrumentation(counter.instrumentation());
1308     try pm.addPass(fixed_point_pass);
1309     try pm.addPass(.{
1310         .name = "rerun-interval-preserver",
1311         .description = "",
1312         .run_fn = RerunIntervalRunner.preserve,
1313         .mutation_scope = .read_only,
1314     });
1315     try pm.addPass(fixed_point_pass);
1316 
1317     try testing.expectEqual(PassResult.success, pm.run(module_op.op, &ctx));
1318     try testing.expectEqual(@as(usize, 1), FixedPointRerunRunner.runs);
1319     try testing.expectEqual(@as(usize, 1), RerunIntervalRunner.runs);
1320     try testing.expectEqual(@as(u64, 2), pm.stats.pass_runs);
1321     try testing.expectEqual(@as(u64, 1), pm.stats.passes_skipped);
1322     try testing.expectEqual(@as(u64, 1), pm.stats.passes_modified);
1323     try testing.expectEqual(@as(usize, 2), counter.pass_count);
1324 }
1325 
1326 test "PassManager reruns a fixed-point pass after nested modification" {
1327     const testing = std.testing;
1328     const test_dialect = @import("../../dialects/fixture/root.zig");
1329 
1330     const allocator = testing.allocator;
1331 
1332     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1333     defer ctx.deinit(allocator);
1334     try ir.dialects.loadDialectSpec(&ctx, test_dialect.TestDialect.spec);
1335 
1336     const loc = ir.Location.getUnknown();
1337     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1338     const func_op = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "rerun_nested", &.{});
1339     try module_op.getBodyBlock().addOperation(func_op.op);
1340 
1341     FixedPointRerunRunner.runs = 0;
1342     FixedPointRerunRunner.modify_first = false;
1343     RerunIntervalRunner.runs = 0;
1344 
1345     const fixed_point_pass = Pass{
1346         .name = "fixed-point-rerun",
1347         .description = "",
1348         .run_fn = FixedPointRerunRunner.run,
1349         .rerun_policy = .skip_if_unchanged,
1350     };
1351 
1352     var pm = PassManager.init(allocator);
1353     defer pm.deinit();
1354     try pm.addPass(fixed_point_pass);
1355     const nested = try pm.nest(test_dialect.TestDialect.FuncOp.operation_name);
1356     try nested.addPass(.{
1357         .name = "nested-rerun-revision",
1358         .description = "",
1359         .run_fn = RerunIntervalRunner.modify,
1360     });
1361     try pm.addPass(fixed_point_pass);
1362 
1363     try testing.expectEqual(PassResult.success, pm.run(module_op.op, &ctx));
1364     try testing.expectEqual(@as(usize, 2), FixedPointRerunRunner.runs);
1365     try testing.expectEqual(@as(usize, 1), RerunIntervalRunner.runs);
1366     try testing.expectEqual(@as(u64, 3), pm.stats.pass_runs);
1367     try testing.expectEqual(@as(u64, 0), pm.stats.passes_skipped);
1368     try testing.expectEqual(@as(u64, 1), pm.stats.passes_modified);
1369 }
1370 
1371 test "Pass rerun identity is stateless and configuration-specific" {
1372     const testing = std.testing;
1373 
1374     const base = Pass{
1375         .name = "fixed-point-rerun",
1376         .description = "",
1377         .run_fn = FixedPointRerunRunner.run,
1378         .rerun_policy = .skip_if_unchanged,
1379         .textual_options = "mode=base",
1380     };
1381     var configured = base;
1382     configured.textual_options = "mode=other";
1383     var required = base;
1384     required.rerun_policy = .always;
1385 
1386     try testing.expect(base.validRerunContract());
1387     try testing.expect(base.sameRerunIdentity(base));
1388     try testing.expect(!base.sameRerunIdentity(configured));
1389     try testing.expect(!base.sameRerunIdentity(required));
1390 
1391     var state: u8 = 0;
1392     var pm = OpPassManager.init(testing.allocator, null);
1393     defer pm.deinit();
1394     try testing.expectError(error.InvalidPassRerunContract, pm.addPass(.{
1395         .name = "stateful-rerun",
1396         .description = "",
1397         .state = &state,
1398         .run_with_state_fn = StatefulRerunRunner.run,
1399         .rerun_policy = .skip_if_unchanged,
1400     }));
1401 }
1402 
1403 const StatisticsRunner = struct {
1404     fn run(ctx: *PassContext) PassResult {
1405         ctx.incrementStatistic("folds", "folds applied");
1406         ctx.addStatistic("rewrites", "rewrites applied", 2);
1407         ctx.preserveAllAnalyses();
1408         return .success;
1409     }
1410 };
1411 
1412 test "PassContext reports pass statistics through instrumentation" {
1413     const testing = std.testing;
1414     const test_dialect = @import("../../dialects/fixture/root.zig");
1415 
1416     const allocator = testing.allocator;
1417 
1418     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1419     defer ctx.deinit(allocator);
1420 
1421     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
1422 
1423     var statistics = PassStatisticsInstrumentation.init(allocator);
1424     defer statistics.deinit();
1425 
1426     var pm = PassManager.init(allocator);
1427     defer pm.deinit();
1428     try pm.addInstrumentation(statistics.instrumentation());
1429     try pm.addPass(.{
1430         .name = "stat-pass",
1431         .description = "reports pass statistics",
1432         .run_fn = StatisticsRunner.run,
1433         .mutation_scope = .read_only,
1434     });
1435 
1436     try testing.expectEqual(PassResult.success, pm.run(module_op.op, &ctx));
1437     try testing.expectEqual(@as(u64, 1), statistics.get("stat-pass", "folds").?);
1438     try testing.expectEqual(@as(u64, 2), statistics.get("stat-pass", "rewrites").?);
1439 
1440     const summaries = try statistics.summariesAlloc(allocator);
1441     defer allocator.free(summaries);
1442     try testing.expectEqual(@as(usize, 2), summaries.len);
1443     try testing.expectEqualStrings("stat-pass", summaries[0].pass_name);
1444     try testing.expectEqualStrings("folds", summaries[0].name);
1445     try testing.expectEqualStrings("folds applied", summaries[0].description);
1446     try testing.expectEqual(@as(u64, 1), summaries[0].value);
1447     try testing.expectEqualStrings("rewrites", summaries[1].name);
1448     try testing.expectEqual(@as(u64, 2), summaries[1].value);
1449 }
1450 
1451 const DuplicateStatisticsRunner = struct {
1452     fn run(ctx: *PassContext) PassResult {
1453         ctx.incrementStatistic("matches", "matched operations");
1454         ctx.preserveAllAnalyses();
1455         return .success;
1456     }
1457 };
1458 
1459 test "Pass statistics aggregate duplicate pass names" {
1460     const testing = std.testing;
1461     const test_dialect = @import("../../dialects/fixture/root.zig");
1462 
1463     const allocator = testing.allocator;
1464 
1465     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1466     defer ctx.deinit(allocator);
1467 
1468     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
1469 
1470     var statistics = PassStatisticsInstrumentation.init(allocator);
1471     defer statistics.deinit();
1472 
1473     var pm = PassManager.init(allocator);
1474     defer pm.deinit();
1475     try pm.addInstrumentation(statistics.instrumentation());
1476     try pm.addPass(.{
1477         .name = "repeat-pass",
1478         .description = "reports pass statistics",
1479         .run_fn = DuplicateStatisticsRunner.run,
1480         .mutation_scope = .read_only,
1481     });
1482     try pm.addPass(.{
1483         .name = "repeat-pass",
1484         .description = "reports pass statistics again",
1485         .run_fn = DuplicateStatisticsRunner.run,
1486         .mutation_scope = .read_only,
1487     });
1488 
1489     try testing.expectEqual(PassResult.success, pm.run(module_op.op, &ctx));
1490     try testing.expectEqual(@as(u64, 2), statistics.get("repeat-pass", "matches").?);
1491 
1492     const summaries = try statistics.summariesAlloc(allocator);
1493     defer allocator.free(summaries);
1494     try testing.expectEqual(@as(usize, 1), summaries.len);
1495     try testing.expectEqualStrings("repeat-pass", summaries[0].pass_name);
1496     try testing.expectEqualStrings("matches", summaries[0].name);
1497     try testing.expectEqual(@as(u64, 2), summaries[0].value);
1498 }
1499 
1500 const ConfiguredPassState = struct {
1501     runs: usize = 0,
1502 };
1503 
1504 const ConfiguredPassRunner = struct {
1505     fn run(raw: ?*anyopaque, ctx: *PassContext) PassResult {
1506         const state: *ConfiguredPassState = @ptrCast(@alignCast(raw orelse return .failure));
1507         state.runs += 1;
1508         ctx.preserveAllAnalyses();
1509         return .success;
1510     }
1511 };
1512 
1513 test "Pass state reaches configured run function" {
1514     const testing = std.testing;
1515     const test_dialect = @import("../../dialects/fixture/root.zig");
1516 
1517     var arena = alloc_arena.Arena.init(std.testing.allocator);
1518     defer arena.deinit();
1519     const allocator = arena.allocator();
1520 
1521     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1522     defer ctx.deinit(allocator);
1523 
1524     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
1525 
1526     var state = ConfiguredPassState{};
1527     var pm = PassManager.init(allocator);
1528     defer pm.deinit();
1529     try pm.addPass(.{
1530         .name = "stateful-test",
1531         .description = "records configured pass state",
1532         .state = &state,
1533         .run_with_state_fn = ConfiguredPassRunner.run,
1534         .mutation_scope = .read_only,
1535     });
1536 
1537     try testing.expectEqual(PassResult.success, pm.run(module_op.op, &ctx));
1538     try testing.expectEqual(@as(usize, 1), state.runs);
1539 }
1540 
1541 const StableFixedPointPass = struct {
1542     var runs: usize = 0;
1543     var changed_runs: usize = 0;
1544 
1545     fn run(ctx: *PassContext) PassResult {
1546         runs += 1;
1547         if (changed_runs < 2) {
1548             changed_runs += 1;
1549             ctx.markModified();
1550         } else {
1551             ctx.preserveAllAnalyses();
1552         }
1553         return .success;
1554     }
1555 };
1556 
1557 test "PassManager.runToFixedPoint stops after stable iteration" {
1558     const testing = std.testing;
1559     const test_dialect = @import("../../dialects/fixture/root.zig");
1560 
1561     var arena = alloc_arena.Arena.init(std.testing.allocator);
1562     defer arena.deinit();
1563     const allocator = arena.allocator();
1564 
1565     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1566     defer ctx.deinit(allocator);
1567 
1568     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
1569 
1570     StableFixedPointPass.runs = 0;
1571     StableFixedPointPass.changed_runs = 0;
1572 
1573     var pm = PassManager.init(allocator);
1574     defer pm.deinit();
1575     try pm.addPass(.{
1576         .name = "fixed-point-test",
1577         .description = "changes twice before stabilizing",
1578         .run_fn = StableFixedPointPass.run,
1579     });
1580 
1581     const result = pm.runToFixedPoint(module_op.op, &ctx, 8);
1582     try testing.expectEqual(PassResult.success, result.result);
1583     try testing.expect(result.changed);
1584     try testing.expectEqual(@as(usize, 3), result.iterations);
1585     try testing.expectEqual(@as(usize, 3), StableFixedPointPass.runs);
1586     try testing.expectEqual(@as(usize, 2), StableFixedPointPass.changed_runs);
1587     try testing.expectEqual(@as(u64, 3), pm.stats.pass_runs);
1588     try testing.expectEqual(@as(u64, 2), pm.stats.passes_modified);
1589 }
1590 
1591 test "PassManager.runToFixedPoint reports failure" {
1592     const testing = std.testing;
1593     const test_dialect = @import("../../dialects/fixture/root.zig");
1594 
1595     var arena = alloc_arena.Arena.init(std.testing.allocator);
1596     defer arena.deinit();
1597     const allocator = arena.allocator();
1598 
1599     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1600     defer ctx.deinit(allocator);
1601 
1602     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
1603 
1604     var pm = PassManager.init(allocator);
1605     defer pm.deinit();
1606     try pm.addPass(.{
1607         .name = "fixed-point-failure",
1608         .description = "fails fixed-point execution",
1609         .run_fn = test_pass_failure,
1610     });
1611 
1612     const result = pm.runToFixedPoint(module_op.op, &ctx, 8);
1613     try testing.expectEqual(PassResult.failure, result.result);
1614     try testing.expect(!result.changed);
1615     try testing.expectEqual(@as(usize, 1), result.iterations);
1616     try testing.expectEqual(@as(u64, 1), pm.stats.pass_runs);
1617     try testing.expectEqual(@as(u64, 1), pm.stats.pass_failures);
1618 }
1619 
1620 const HierarchicalFuncCounter = struct {
1621     var count: usize = 0;
1622 
1623     fn run(ctx: *PassContext) PassResult {
1624         if (std.mem.eql(u8, ctx.op.name.name, "test.func")) {
1625             count += 1;
1626         }
1627         return .success;
1628     }
1629 };
1630 
1631 test "Hierarchical pass manager runs only on matching ops" {
1632     const testing = std.testing;
1633     const test_dialect = @import("../../dialects/fixture/root.zig");
1634 
1635     var arena = alloc_arena.Arena.init(std.testing.allocator);
1636     defer arena.deinit();
1637     const allocator = arena.allocator();
1638 
1639     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1640     defer ctx.deinit(allocator);
1641     try ctx.allowUnregistered();
1642 
1643     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
1644     const module_block = module_op.getBodyBlock();
1645 
1646     var builder = ir.OperationBuilder.init(&ctx);
1647     const target1_state = ir.Operation.State.init("test.target_op", ir.Location.getUnknown());
1648     const target1_op = try builder.create(target1_state);
1649     try module_block.addOperation(target1_op);
1650 
1651     const func_op = try test_dialect.TestDialect.FuncOp.create(&ctx, ir.Location.getUnknown(), "nested_func", &.{});
1652     try module_block.addOperation(func_op.op);
1653 
1654     const func_block = func_op.getEntryBlock();
1655     const target2_state = ir.Operation.State.init("test.target_op", ir.Location.getUnknown());
1656     const target2_op = try builder.create(target2_state);
1657     try func_block.addOperation(target2_op);
1658 
1659     var pm = PassManager.init(allocator);
1660     defer pm.deinit();
1661 
1662     HierarchicalFuncCounter.count = 0;
1663 
1664     const func_pm = try pm.nest("test.func");
1665     try func_pm.addPass(Pass{
1666         .name = "func-counter",
1667         .description = "Counts func ops",
1668         .run_fn = HierarchicalFuncCounter.run,
1669     });
1670 
1671     const result = pm.run(module_op.op, &ctx);
1672     try testing.expectEqual(PassResult.success, result);
1673     try testing.expectEqual(@as(usize, 1), HierarchicalFuncCounter.count);
1674 }
1675 
1676 const DeepBinaryCounter = struct {
1677     var count: usize = 0;
1678 
1679     fn run(ctx: *PassContext) PassResult {
1680         if (std.mem.eql(u8, ctx.op.name.name, "test.binary")) {
1681             count += 1;
1682         }
1683         return .success;
1684     }
1685 };
1686 
1687 test "Hierarchical pass manager rejects non-isolated deeply nested targets" {
1688     const testing = std.testing;
1689     const test_dialect = @import("../../dialects/fixture/root.zig");
1690 
1691     var arena = alloc_arena.Arena.init(std.testing.allocator);
1692     defer arena.deinit();
1693     const allocator = arena.allocator();
1694 
1695     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1696     defer ctx.deinit(allocator);
1697 
1698     const loc = ir.Location.getUnknown();
1699     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1700     const module_block = module_op.getBodyBlock();
1701 
1702     const outer_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "outer", &.{});
1703     try module_block.addOperation(outer_func.op);
1704 
1705     const inner_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "inner", &.{});
1706     try outer_func.getEntryBlock().addOperation(inner_func.op);
1707 
1708     const i32_type = try test_dialect.TestDialect.getI32Type(&ctx);
1709     _ = try inner_func.getEntryBlock().addArgument(i32_type, loc);
1710     _ = try inner_func.getEntryBlock().addArgument(i32_type, loc);
1711     const inner_args = inner_func.getEntryBlock().arguments.items;
1712     const binary_op = try test_dialect.TestDialect.BinaryOp.create(&ctx, loc, inner_args[0], inner_args[1]);
1713     try inner_func.getEntryBlock().addOperation(binary_op.op);
1714 
1715     var pm = PassManager.init(allocator);
1716     defer pm.deinit();
1717 
1718     DeepBinaryCounter.count = 0;
1719 
1720     const func_pm = try pm.nest("test.func");
1721     const binary_pm = try func_pm.nest("test.binary");
1722     try binary_pm.addPass(Pass{
1723         .name = "binary-counter",
1724         .description = "Counts binary ops",
1725         .run_fn = DeepBinaryCounter.run,
1726     });
1727 
1728     const result = pm.run(module_op.op, &ctx);
1729     try testing.expectEqual(PassResult.failure, result);
1730     try testing.expectEqual(@as(usize, 0), DeepBinaryCounter.count);
1731 }
1732 
1733 test "Hierarchical pass manager propagates failure" {
1734     const testing = std.testing;
1735     const test_dialect = @import("../../dialects/fixture/root.zig");
1736 
1737     var arena = alloc_arena.Arena.init(std.testing.allocator);
1738     defer arena.deinit();
1739     const allocator = arena.allocator();
1740 
1741     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1742     defer ctx.deinit(allocator);
1743 
1744     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
1745     const module_block = module_op.getBodyBlock();
1746 
1747     const func_op = try test_dialect.TestDialect.FuncOp.create(&ctx, ir.Location.getUnknown(), "fail_func", &.{});
1748     try module_block.addOperation(func_op.op);
1749 
1750     var pm = PassManager.init(allocator);
1751     defer pm.deinit();
1752 
1753     const func_pm = try pm.nest("test.func");
1754     try func_pm.addPass(Pass{
1755         .name = "failing-pass",
1756         .description = "Always fails",
1757         .run_fn = test_pass_failure,
1758     });
1759 
1760     const result = pm.run(module_op.op, &ctx);
1761     try testing.expectEqual(PassResult.failure, result);
1762     try testing.expectEqual(@as(u64, 1), pm.stats.pass_failures);
1763 }
1764 
1765 test "PassManager captures serial pass failure reproducer" {
1766     const testing = std.testing;
1767     const test_dialect = @import("../../dialects/fixture/root.zig");
1768 
1769     var arena = alloc_arena.Arena.init(std.testing.allocator);
1770     defer arena.deinit();
1771     const allocator = arena.allocator();
1772 
1773     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1774     defer ctx.deinit(allocator);
1775 
1776     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
1777 
1778     var pm = PassManager.init(allocator);
1779     defer pm.deinit();
1780 
1781     try pm.addPass(Pass{
1782         .name = "serial-reproducer-fail",
1783         .description = "fails for reproducer capture",
1784         .run_fn = test_pass_failure,
1785     });
1786 
1787     const result = pm.run(module_op.op, &ctx);
1788     try testing.expectEqual(PassResult.failure, result);
1789 
1790     const reproducer = pm.getLastFailureReproducer() orelse return error.TestExpectedReproducer;
1791     try testing.expectEqualStrings("serial-reproducer-fail", reproducer.pipeline);
1792     try testing.expectEqual(@as(usize, 1), reproducer.max_threads);
1793     try testing.expectEqual(@as(usize, 1), reproducer.worker_count);
1794     try testing.expect(!reproducer.verifier_enabled);
1795     try testing.expectEqual(PassFailureKind.pass, reproducer.failure_kind.?);
1796     try testing.expectEqualStrings("serial-reproducer-fail", reproducer.pass_name.?);
1797     try testing.expectEqualStrings(module_op.op.name.name, reproducer.target_op_name.?);
1798     try testing.expect(std.mem.indexOf(u8, reproducer.ir, module_op.op.name.name) != null);
1799 }
1800 
1801 const NestedPassOrderTracker = struct {
1802     var order: [3]u8 = undefined;
1803     var idx: usize = 0;
1804 
1805     fn pass1(_: *PassContext) PassResult {
1806         order[idx] = '1';
1807         idx += 1;
1808         return .success;
1809     }
1810 
1811     fn pass2(_: *PassContext) PassResult {
1812         order[idx] = '2';
1813         idx += 1;
1814         return .success;
1815     }
1816 
1817     fn pass3(_: *PassContext) PassResult {
1818         order[idx] = '3';
1819         idx += 1;
1820         return .success;
1821     }
1822 };
1823 
1824 test "Multiple passes in nested manager run sequentially" {
1825     const testing = std.testing;
1826     const test_dialect = @import("../../dialects/fixture/root.zig");
1827 
1828     var arena = alloc_arena.Arena.init(std.testing.allocator);
1829     defer arena.deinit();
1830     const allocator = arena.allocator();
1831 
1832     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1833     defer ctx.deinit(allocator);
1834 
1835     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
1836     const module_block = module_op.getBodyBlock();
1837 
1838     const func_op = try test_dialect.TestDialect.FuncOp.create(&ctx, ir.Location.getUnknown(), "multi_pass", &.{});
1839     try module_block.addOperation(func_op.op);
1840 
1841     var pm = PassManager.init(allocator);
1842     defer pm.deinit();
1843 
1844     NestedPassOrderTracker.idx = 0;
1845 
1846     const func_pm = try pm.nest("test.func");
1847     try func_pm.addPass(Pass{
1848         .name = "pass1",
1849         .description = "",
1850         .run_fn = NestedPassOrderTracker.pass1,
1851     });
1852     try func_pm.addPass(Pass{
1853         .name = "pass2",
1854         .description = "",
1855         .run_fn = NestedPassOrderTracker.pass2,
1856     });
1857     try func_pm.addPass(Pass{
1858         .name = "pass3",
1859         .description = "",
1860         .run_fn = NestedPassOrderTracker.pass3,
1861     });
1862 
1863     const result = pm.run(module_op.op, &ctx);
1864     try testing.expectEqual(PassResult.success, result);
1865     try testing.expectEqual(@as(usize, 3), NestedPassOrderTracker.idx);
1866     try testing.expectEqualStrings("123", &NestedPassOrderTracker.order);
1867 }
1868 
1869 const ReadOnlyParallelBarrierPass = struct {
1870     var started = std.atomic.Value(usize).init(0);
1871     var release = std.atomic.Value(bool).init(false);
1872 
1873     fn run(ctx: *PassContext) PassResult {
1874         if (ctx.workerCount(8) != 2) return .failure;
1875         if (!ctx.ir_ctx.isMultithreadedExecution()) return .failure;
1876         ctx.preserveAllAnalyses();
1877         const arrived = started.fetchAdd(1, .acq_rel) + 1;
1878         if (arrived == 2) release.store(true, .release);
1879 
1880         var spins: usize = 0;
1881         while (!release.load(.acquire)) {
1882             spins += 1;
1883             if (spins > 1_000_000) return .failure;
1884             sys.thread.yield();
1885         }
1886         return .success;
1887     }
1888 };
1889 
1890 test "PassManager.runWithOptions runs read-only nested targets concurrently" {
1891     const testing = std.testing;
1892     const test_dialect = @import("../../dialects/fixture/root.zig");
1893 
1894     var arena = alloc_arena.Arena.init(std.testing.allocator);
1895     defer arena.deinit();
1896     const allocator = arena.allocator();
1897 
1898     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1899     defer ctx.deinit(allocator);
1900 
1901     const loc = ir.Location.getUnknown();
1902     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1903     const module_block = module_op.getBodyBlock();
1904 
1905     const first_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "first", &.{});
1906     try module_block.addOperation(first_func.op);
1907     const second_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "second", &.{});
1908     try module_block.addOperation(second_func.op);
1909 
1910     ReadOnlyParallelBarrierPass.started.store(0, .release);
1911     ReadOnlyParallelBarrierPass.release.store(false, .release);
1912 
1913     var pm = PassManager.init(allocator);
1914     defer pm.deinit();
1915 
1916     const func_pm = try pm.nest("test.func");
1917     try func_pm.addPass(Pass{
1918         .name = "barrier-read-only",
1919         .description = "waits for another read-only target",
1920         .run_fn = ReadOnlyParallelBarrierPass.run,
1921         .mutation_scope = .read_only,
1922     });
1923 
1924     const result = pm.runWithOptions(module_op.op, &ctx, .{ .max_threads = 2 });
1925     try testing.expectEqual(PassResult.success, result);
1926     try testing.expectEqual(@as(usize, 2), ReadOnlyParallelBarrierPass.started.load(.acquire));
1927     try testing.expectEqual(@as(u64, 2), pm.stats.pass_runs);
1928     try testing.expectEqual(@as(u64, 0), pm.stats.pass_failures);
1929 }
1930 
1931 const ParallelContextStoragePass = struct {
1932     var attr_value: i64 = 0;
1933 
1934     fn run(ctx: *PassContext) PassResult {
1935         if (!ctx.ir_ctx.isMultithreadedExecution()) return .failure;
1936         _ = ctx.ir_ctx.getI64Attr(attr_value) catch return .failure;
1937         ctx.preserveAllAnalyses();
1938         return .success;
1939     }
1940 };
1941 
1942 test "PassManager.runWithOptions blocks context storage misses in parallel read-only passes" {
1943     const testing = std.testing;
1944     const test_dialect = @import("../../dialects/fixture/root.zig");
1945 
1946     var arena = alloc_arena.Arena.init(std.testing.allocator);
1947     defer arena.deinit();
1948     const allocator = arena.allocator();
1949 
1950     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1951     defer ctx.deinit(allocator);
1952 
1953     const loc = ir.Location.getUnknown();
1954     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1955     const module_block = module_op.getBodyBlock();
1956 
1957     const first_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "first", &.{});
1958     try module_block.addOperation(first_func.op);
1959     const second_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "second", &.{});
1960     try module_block.addOperation(second_func.op);
1961 
1962     _ = try ctx.getI64Attr(7);
1963     ParallelContextStoragePass.attr_value = 7;
1964 
1965     var preload_pm = PassManager.init(allocator);
1966     defer preload_pm.deinit();
1967     const preload_func_pm = try preload_pm.nest("test.func");
1968     try preload_func_pm.addPass(Pass{
1969         .name = "preloaded-context-storage",
1970         .description = "reads preloaded context storage",
1971         .run_fn = ParallelContextStoragePass.run,
1972         .mutation_scope = .read_only,
1973     });
1974 
1975     const preload_result = preload_pm.runWithOptions(module_op.op, &ctx, .{ .max_threads = 2 });
1976     try testing.expectEqual(PassResult.success, preload_result);
1977     try testing.expectEqual(@as(u64, 2), preload_pm.stats.pass_runs);
1978 
1979     ParallelContextStoragePass.attr_value = 8;
1980 
1981     var miss_pm = PassManager.init(allocator);
1982     defer miss_pm.deinit();
1983     const miss_func_pm = try miss_pm.nest("test.func");
1984     try miss_func_pm.addPass(Pass{
1985         .name = "missing-context-storage",
1986         .description = "attempts context storage creation",
1987         .run_fn = ParallelContextStoragePass.run,
1988         .mutation_scope = .read_only,
1989     });
1990 
1991     const miss_result = miss_pm.runWithOptions(module_op.op, &ctx, .{ .max_threads = 2 });
1992     try testing.expectEqual(PassResult.failure, miss_result);
1993     try testing.expectEqual(@as(u64, 2), miss_pm.stats.pass_runs);
1994     try testing.expectEqual(@as(u64, 2), miss_pm.stats.pass_failures);
1995 }
1996 
1997 const ParallelReproducerFailingPass = struct {
1998     fn run(ctx: *PassContext) PassResult {
1999         if (!ctx.ir_ctx.isMultithreadedExecution()) return .failure;
2000         return .failure;
2001     }
2002 };
2003 
2004 test "PassManager.runWithOptions captures parallel pass failure reproducer in target order" {
2005     const testing = std.testing;
2006     const test_dialect = @import("../../dialects/fixture/root.zig");
2007 
2008     var arena = alloc_arena.Arena.init(std.testing.allocator);
2009     defer arena.deinit();
2010     const allocator = arena.allocator();
2011 
2012     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2013     defer ctx.deinit(allocator);
2014 
2015     const loc = ir.Location.getUnknown();
2016     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
2017     const module_block = module_op.getBodyBlock();
2018 
2019     const first_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "first", &.{});
2020     try module_block.addOperation(first_func.op);
2021     const second_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "second", &.{});
2022     try module_block.addOperation(second_func.op);
2023 
2024     var pm = PassManager.init(allocator);
2025     defer pm.deinit();
2026 
2027     const func_pm = try pm.nest("test.func");
2028     try func_pm.addPass(Pass{
2029         .name = "parallel-reproducer-fail",
2030         .description = "fails on each read-only target",
2031         .run_fn = ParallelReproducerFailingPass.run,
2032         .mutation_scope = .read_only,
2033     });
2034 
2035     const result = pm.runWithOptions(module_op.op, &ctx, .{ .max_threads = 2 });
2036     try testing.expectEqual(PassResult.failure, result);
2037 
2038     const reproducer = pm.getLastFailureReproducer() orelse return error.TestExpectedReproducer;
2039     try testing.expectEqualStrings("test.func(parallel-reproducer-fail)", reproducer.pipeline);
2040     try testing.expectEqual(@as(usize, 2), reproducer.max_threads);
2041     try testing.expectEqual(@as(usize, 2), reproducer.worker_count);
2042     try testing.expectEqual(PassFailureKind.pass, reproducer.failure_kind.?);
2043     try testing.expectEqualStrings("parallel-reproducer-fail", reproducer.pass_name.?);
2044     try testing.expectEqualStrings("test.func", reproducer.target_op_name.?);
2045     try testing.expectEqualStrings("first", reproducer.target_symbol_name.?);
2046     try testing.expect(std.mem.indexOf(u8, reproducer.ir, "second") != null);
2047 }
2048 
2049 const ParallelDiagnosticRecorder = struct {
2050     messages: [2][]const u8 = .{ "", "" },
2051     seen: usize = 0,
2052 
2053     fn handle(
2054         context: ?*anyopaque,
2055         diagnostic: *const diagnostics.Diagnostic,
2056     ) !diagnostics.HandlerResult {
2057         const self: *@This() = @ptrCast(@alignCast(context.?));
2058         self.messages[self.seen] = diagnostic.message;
2059         self.seen += 1;
2060         return .consumed;
2061     }
2062 };
2063 
2064 const ParallelDiagnosticPass = struct {
2065     var second_emitted = std.atomic.Value(bool).init(false);
2066 
2067     fn run(ctx: *PassContext) PassResult {
2068         const symbol_name = ir.SymbolTable.getSymbolName(ctx.op) orelse return .failure;
2069         const is_first = std.mem.eql(u8, symbol_name, "first");
2070         if (is_first) {
2071             var spins: usize = 0;
2072             while (!second_emitted.load(.acquire)) {
2073                 spins += 1;
2074                 if (spins > 1_000_000) return .failure;
2075                 sys.thread.yield();
2076             }
2077         }
2078 
2079         const message: []const u8 = if (is_first) "first diagnostic" else "second diagnostic";
2080         var diagnostic = ctx.op.emitWarning(message);
2081         defer diagnostic.deinit();
2082         _ = diagnostic.emit() catch return .failure;
2083 
2084         if (!is_first) second_emitted.store(true, .release);
2085         ctx.preserveAllAnalyses();
2086         return .success;
2087     }
2088 };
2089 
2090 test "PassManager.runWithOptions replays parallel diagnostics in target order" {
2091     const testing = std.testing;
2092     const test_dialect = @import("../../dialects/fixture/root.zig");
2093 
2094     var arena = alloc_arena.Arena.init(std.testing.allocator);
2095     defer arena.deinit();
2096     const allocator = arena.allocator();
2097 
2098     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2099     defer ctx.deinit(allocator);
2100 
2101     const loc = ir.Location.getUnknown();
2102     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
2103     const module_block = module_op.getBodyBlock();
2104 
2105     const first_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "first", &.{});
2106     try module_block.addOperation(first_func.op);
2107     const second_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "second", &.{});
2108     try module_block.addOperation(second_func.op);
2109 
2110     var recorder = ParallelDiagnosticRecorder{};
2111     _ = try ctx.registerDiagnosticHandler(.{
2112         .context = &recorder,
2113         .handle = ParallelDiagnosticRecorder.handle,
2114     });
2115     ParallelDiagnosticPass.second_emitted.store(false, .release);
2116 
2117     var pm = PassManager.init(allocator);
2118     defer pm.deinit();
2119 
2120     const func_pm = try pm.nest("test.func");
2121     try func_pm.addPass(Pass{
2122         .name = "diagnostic-order-check",
2123         .description = "emits diagnostics in scheduling-inverted order",
2124         .run_fn = ParallelDiagnosticPass.run,
2125         .mutation_scope = .read_only,
2126     });
2127 
2128     const result = pm.runWithOptions(module_op.op, &ctx, .{ .max_threads = 2 });
2129     try testing.expectEqual(PassResult.success, result);
2130     try testing.expectEqual(@as(usize, 2), recorder.seen);
2131     try testing.expectEqualStrings("first diagnostic", recorder.messages[0]);
2132     try testing.expectEqualStrings("second diagnostic", recorder.messages[1]);
2133 }
2134 
2135 const VerifierParallelBarrierPass = struct {
2136     var started = std.atomic.Value(usize).init(0);
2137     var release = std.atomic.Value(bool).init(false);
2138 
2139     fn run(ctx: *PassContext) PassResult {
2140         if (!ctx.ir_ctx.isMultithreadedExecution()) return .failure;
2141         ctx.preserveAllAnalyses();
2142         const arrived = started.fetchAdd(1, .acq_rel) + 1;
2143         if (arrived == 2) release.store(true, .release);
2144 
2145         var spins: usize = 0;
2146         while (!release.load(.acquire)) {
2147             spins += 1;
2148             if (spins > 1_000_000) return .failure;
2149             sys.thread.yield();
2150         }
2151         return .success;
2152     }
2153 };
2154 
2155 test "PassManager.runWithOptions keeps read-only targets parallel with verifier enabled" {
2156     const testing = std.testing;
2157     const test_dialect = @import("../../dialects/fixture/root.zig");
2158 
2159     var arena = alloc_arena.Arena.init(std.testing.allocator);
2160     defer arena.deinit();
2161     const allocator = arena.allocator();
2162 
2163     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2164     defer ctx.deinit(allocator);
2165     try ir.dialects.loadDialectSpec(&ctx, test_dialect.TestDialect.spec);
2166 
2167     const loc = ir.Location.getUnknown();
2168     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
2169     const module_block = module_op.getBodyBlock();
2170 
2171     const first_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "first", &.{});
2172     try module_block.addOperation(first_func.op);
2173     const second_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "second", &.{});
2174     try module_block.addOperation(second_func.op);
2175 
2176     VerifierParallelBarrierPass.started.store(0, .release);
2177     VerifierParallelBarrierPass.release.store(false, .release);
2178 
2179     var pm = PassManager.init(allocator);
2180     defer pm.deinit();
2181     pm.enableVerifier();
2182 
2183     const func_pm = try pm.nest("test.func");
2184     try func_pm.addPass(Pass{
2185         .name = "verifier-parallel-barrier",
2186         .description = "waits for another verified read-only target",
2187         .run_fn = VerifierParallelBarrierPass.run,
2188         .mutation_scope = .read_only,
2189     });
2190 
2191     const result = pm.runWithOptions(module_op.op, &ctx, .{ .max_threads = 2 });
2192     try testing.expectEqual(PassResult.success, result);
2193     try testing.expectEqual(@as(usize, 2), VerifierParallelBarrierPass.started.load(.acquire));
2194     try testing.expectEqual(@as(u64, 2), pm.stats.pass_runs);
2195     try testing.expectEqual(@as(u64, 0), pm.stats.verifier_failures);
2196     try testing.expect(pm.getLastVerifierFailure() == null);
2197 }
2198 
2199 const ExclusivePassState = struct {
2200     runs: usize = 0,
2201 };
2202 
2203 const ExclusiveStateSerialPass = struct {
2204     fn run(raw: ?*anyopaque, ctx: *PassContext) PassResult {
2205         if (ctx.ir_ctx.isMultithreadedExecution()) return .failure;
2206         const state: *ExclusivePassState = @ptrCast(@alignCast(raw orelse return .failure));
2207         state.runs += 1;
2208         ctx.preserveAllAnalyses();
2209         return .success;
2210     }
2211 };
2212 
2213 test "PassManager.runWithOptions keeps exclusive stateful read-only passes serial" {
2214     const testing = std.testing;
2215     const test_dialect = @import("../../dialects/fixture/root.zig");
2216 
2217     var arena = alloc_arena.Arena.init(std.testing.allocator);
2218     defer arena.deinit();
2219     const allocator = arena.allocator();
2220 
2221     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2222     defer ctx.deinit(allocator);
2223 
2224     const loc = ir.Location.getUnknown();
2225     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
2226     const module_block = module_op.getBodyBlock();
2227 
2228     const first_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "first", &.{});
2229     try module_block.addOperation(first_func.op);
2230     const second_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "second", &.{});
2231     try module_block.addOperation(second_func.op);
2232 
2233     var state = ExclusivePassState{};
2234     var pm = PassManager.init(allocator);
2235     defer pm.deinit();
2236 
2237     const func_pm = try pm.nest("test.func");
2238     try func_pm.addPass(Pass{
2239         .name = "exclusive-state-serial-check",
2240         .description = "records serial exclusive state",
2241         .state = &state,
2242         .run_with_state_fn = ExclusiveStateSerialPass.run,
2243         .mutation_scope = .read_only,
2244     });
2245 
2246     const result = pm.runWithOptions(module_op.op, &ctx, .{ .max_threads = 2 });
2247     try testing.expectEqual(PassResult.success, result);
2248     try testing.expectEqual(@as(usize, 2), state.runs);
2249 }
2250 
2251 const SharedPassState = struct {
2252     started: std.atomic.Value(usize) = std.atomic.Value(usize).init(0),
2253     release: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
2254 };
2255 
2256 const SharedStateParallelPass = struct {
2257     fn run(raw: ?*anyopaque, ctx: *PassContext) PassResult {
2258         if (!ctx.ir_ctx.isMultithreadedExecution()) return .failure;
2259         const state: *SharedPassState = @ptrCast(@alignCast(raw orelse return .failure));
2260         const arrived = state.started.fetchAdd(1, .acq_rel) + 1;
2261         if (arrived == 2) state.release.store(true, .release);
2262 
2263         var spins: usize = 0;
2264         while (!state.release.load(.acquire)) {
2265             spins += 1;
2266             if (spins > 1_000_000) return .failure;
2267             sys.thread.yield();
2268         }
2269         ctx.preserveAllAnalyses();
2270         return .success;
2271     }
2272 };
2273 
2274 test "PassManager.runWithOptions runs shared stateful read-only passes in parallel" {
2275     const testing = std.testing;
2276     const test_dialect = @import("../../dialects/fixture/root.zig");
2277 
2278     var arena = alloc_arena.Arena.init(std.testing.allocator);
2279     defer arena.deinit();
2280     const allocator = arena.allocator();
2281 
2282     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2283     defer ctx.deinit(allocator);
2284 
2285     const loc = ir.Location.getUnknown();
2286     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
2287     const module_block = module_op.getBodyBlock();
2288 
2289     const first_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "first", &.{});
2290     try module_block.addOperation(first_func.op);
2291     const second_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "second", &.{});
2292     try module_block.addOperation(second_func.op);
2293 
2294     var state = SharedPassState{};
2295     var pm = PassManager.init(allocator);
2296     defer pm.deinit();
2297 
2298     const func_pm = try pm.nest("test.func");
2299     try func_pm.addPass(Pass{
2300         .name = "shared-state-parallel-check",
2301         .description = "coordinates through explicit shared state",
2302         .state = &state,
2303         .run_with_state_fn = SharedStateParallelPass.run,
2304         .state_concurrency = .shared,
2305         .mutation_scope = .read_only,
2306     });
2307 
2308     const result = pm.runWithOptions(module_op.op, &ctx, .{ .max_threads = 2 });
2309     try testing.expectEqual(PassResult.success, result);
2310     try testing.expectEqual(@as(usize, 2), state.started.load(.acquire));
2311 }
2312 
2313 const ClonedPassStats = struct {
2314     clones: std.atomic.Value(usize) = std.atomic.Value(usize).init(0),
2315     deinit_count: std.atomic.Value(usize) = std.atomic.Value(usize).init(0),
2316     runs: std.atomic.Value(usize) = std.atomic.Value(usize).init(0),
2317     started: std.atomic.Value(usize) = std.atomic.Value(usize).init(0),
2318     release: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
2319 };
2320 
2321 const ClonedPassState = struct {
2322     stats: *ClonedPassStats,
2323     original: bool,
2324     runs: usize = 0,
2325 };
2326 
2327 const ClonedStateParallelPass = struct {
2328     fn clone(raw: ?*anyopaque, clone_allocator: std.mem.Allocator) anyerror!?*anyopaque {
2329         const state = raw orelse return error.TestMissingState;
2330         const original: *ClonedPassState = @ptrCast(@alignCast(state));
2331         const cloned = try clone_allocator.create(ClonedPassState);
2332         cloned.* = .{
2333             .stats = original.stats,
2334             .original = false,
2335         };
2336         _ = original.stats.clones.fetchAdd(1, .acq_rel);
2337         return cloned;
2338     }
2339 
2340     fn deinit(raw: ?*anyopaque, deinit_allocator: std.mem.Allocator) void {
2341         const state: *ClonedPassState = @ptrCast(@alignCast(raw orelse return));
2342         _ = state.stats.deinit_count.fetchAdd(1, .acq_rel);
2343         deinit_allocator.destroy(state);
2344     }
2345 
2346     fn run(raw: ?*anyopaque, ctx: *PassContext) PassResult {
2347         if (!ctx.ir_ctx.isMultithreadedExecution()) return .failure;
2348         const state: *ClonedPassState = @ptrCast(@alignCast(raw orelse return .failure));
2349         if (state.original) return .failure;
2350         if (state.runs != 0) return .failure;
2351         state.runs += 1;
2352         _ = state.stats.runs.fetchAdd(1, .acq_rel);
2353 
2354         const arrived = state.stats.started.fetchAdd(1, .acq_rel) + 1;
2355         if (arrived == 2) state.stats.release.store(true, .release);
2356 
2357         var spins: usize = 0;
2358         while (!state.stats.release.load(.acquire)) {
2359             spins += 1;
2360             if (spins > 1_000_000) return .failure;
2361             sys.thread.yield();
2362         }
2363         ctx.preserveAllAnalyses();
2364         return .success;
2365     }
2366 };
2367 
2368 test "PassManager.runWithOptions clones stateful read-only passes for parallel targets" {
2369     const testing = std.testing;
2370     const test_dialect = @import("../../dialects/fixture/root.zig");
2371 
2372     var arena = alloc_arena.Arena.init(std.testing.allocator);
2373     defer arena.deinit();
2374     const allocator = arena.allocator();
2375 
2376     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2377     defer ctx.deinit(allocator);
2378 
2379     const loc = ir.Location.getUnknown();
2380     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
2381     const module_block = module_op.getBodyBlock();
2382 
2383     const first_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "first", &.{});
2384     try module_block.addOperation(first_func.op);
2385     const second_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "second", &.{});
2386     try module_block.addOperation(second_func.op);
2387 
2388     var stats = ClonedPassStats{};
2389     const original = try allocator.create(ClonedPassState);
2390     original.* = .{
2391         .stats = &stats,
2392         .original = true,
2393     };
2394 
2395     var pm = PassManager.init(allocator);
2396     const func_pm = try pm.nest("test.func");
2397     try func_pm.addPass(Pass{
2398         .name = "cloned-state-parallel-check",
2399         .description = "runs with cloned state per target",
2400         .state = original,
2401         .run_with_state_fn = ClonedStateParallelPass.run,
2402         .state_clone_fn = ClonedStateParallelPass.clone,
2403         .state_deinit_fn = ClonedStateParallelPass.deinit,
2404         .mutation_scope = .read_only,
2405     });
2406 
2407     const result = pm.runWithOptions(module_op.op, &ctx, .{ .max_threads = 2 });
2408     try testing.expectEqual(PassResult.success, result);
2409     try testing.expectEqual(@as(usize, 2), stats.clones.load(.acquire));
2410     try testing.expectEqual(@as(usize, 2), stats.runs.load(.acquire));
2411     try testing.expectEqual(@as(usize, 2), stats.deinit_count.load(.acquire));
2412 
2413     pm.deinit();
2414     try testing.expectEqual(@as(usize, 3), stats.deinit_count.load(.acquire));
2415 }
2416 
2417 const ParallelVerifierReadOnlyPass = struct {
2418     fn run(ctx: *PassContext) PassResult {
2419         ctx.preserveAllAnalyses();
2420         return .success;
2421     }
2422 };
2423 
2424 const ParallelFixedPointRerunPass = struct {
2425     var runs = std.atomic.Value(usize).init(0);
2426 
2427     fn run(ctx: *PassContext) PassResult {
2428         if (!ctx.ir_ctx.isMultithreadedExecution()) return .failure;
2429         _ = runs.fetchAdd(1, .acq_rel);
2430         ctx.preserveAllAnalyses();
2431         return .success;
2432     }
2433 };
2434 
2435 test "PassManager.runWithOptions merges parallel fixed-point skips" {
2436     const testing = std.testing;
2437     const test_dialect = @import("../../dialects/fixture/root.zig");
2438 
2439     const allocator = testing.allocator;
2440 
2441     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2442     defer ctx.deinit(allocator);
2443     try ir.dialects.loadDialectSpec(&ctx, test_dialect.TestDialect.spec);
2444 
2445     const loc = ir.Location.getUnknown();
2446     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
2447     const module_block = module_op.getBodyBlock();
2448     const first_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "first", &.{});
2449     try module_block.addOperation(first_func.op);
2450     const second_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "second", &.{});
2451     try module_block.addOperation(second_func.op);
2452 
2453     ParallelFixedPointRerunPass.runs.store(0, .release);
2454 
2455     const rerun_pass = Pass{
2456         .name = "parallel-fixed-point-rerun",
2457         .description = "",
2458         .run_fn = ParallelFixedPointRerunPass.run,
2459         .mutation_scope = .read_only,
2460         .rerun_policy = .skip_if_unchanged,
2461     };
2462 
2463     var pm = PassManager.init(allocator);
2464     defer pm.deinit();
2465     const func_pm = try pm.nest(test_dialect.TestDialect.FuncOp.operation_name);
2466     try func_pm.addPass(rerun_pass);
2467     try func_pm.addPass(rerun_pass);
2468 
2469     const result = pm.runWithOptions(module_op.op, &ctx, .{ .max_threads = 2 });
2470     try testing.expectEqual(PassResult.success, result);
2471     try testing.expectEqual(@as(usize, 2), ParallelFixedPointRerunPass.runs.load(.acquire));
2472     try testing.expectEqual(@as(u64, 2), pm.stats.pass_runs);
2473     try testing.expectEqual(@as(u64, 2), pm.stats.passes_skipped);
2474 }
2475 
2476 test "PassManager.runWithOptions merges parallel verifier failures in target order" {
2477     const testing = std.testing;
2478     const test_dialect = @import("../../dialects/fixture/root.zig");
2479 
2480     var arena = alloc_arena.Arena.init(std.testing.allocator);
2481     defer arena.deinit();
2482     const allocator = arena.allocator();
2483 
2484     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2485     defer ctx.deinit(allocator);
2486     try ir.dialects.loadDialectSpec(&ctx, test_dialect.TestDialect.spec);
2487 
2488     const loc = ir.Location.getUnknown();
2489     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
2490     const module_block = module_op.getBodyBlock();
2491 
2492     const first_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "first", &.{});
2493     try module_block.addOperation(first_func.op);
2494     try appendInvalidTestConstantToBlock(&ctx, first_func.getEntryBlock());
2495 
2496     const nested_module = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
2497     try module_block.addOperation(nested_module.op);
2498     try appendInvalidTestConstantToBlock(&ctx, nested_module.getBodyBlock());
2499 
2500     var pm = PassManager.init(allocator);
2501     defer pm.deinit();
2502     pm.enableVerifier();
2503 
2504     const any_pm = try pm.nestAny();
2505     try any_pm.addPass(Pass{
2506         .name = "read-only-verify-check",
2507         .description = "lets verifier inspect preexisting invalid IR",
2508         .run_fn = ParallelVerifierReadOnlyPass.run,
2509         .mutation_scope = .read_only,
2510     });
2511 
2512     const result = pm.runWithOptions(module_op.op, &ctx, .{ .max_threads = 2 });
2513     try testing.expectEqual(PassResult.failure, result);
2514     try testing.expectEqual(@as(u64, 2), pm.stats.pass_runs);
2515     try testing.expectEqual(@as(u64, 2), pm.stats.verifier_failures);
2516 
2517     const failure = pm.getLastVerifierFailure() orelse return error.TestExpectedVerifierFailure;
2518     try testing.expectEqualStrings("read-only-verify-check", failure.pass_name);
2519     try testing.expectEqualStrings(test_dialect.TestDialect.FuncOp.operation_name, failure.target_op_name);
2520     try testing.expectEqual(error.MissingRequiredAttribute, failure.err);
2521 }
2522 
2523 const parallel_stress_target_count = 12;
2524 
2525 const ParallelStressRecorder = struct {
2526     symbols: [parallel_stress_target_count][]const u8 = undefined,
2527     seen: usize = 0,
2528 
2529     fn handle(
2530         context: ?*anyopaque,
2531         diagnostic: *const diagnostics.Diagnostic,
2532     ) !diagnostics.HandlerResult {
2533         const self: *@This() = @ptrCast(@alignCast(context.?));
2534         const op = diagnostic.operation orelse return error.TestMissingDiagnosticOperation;
2535         const symbol = ir.SymbolTable.getSymbolName(op) orelse return error.TestMissingSymbol;
2536         self.symbols[self.seen] = symbol;
2537         self.seen += 1;
2538         return .consumed;
2539     }
2540 };
2541 
2542 const ParallelStressPassState = struct {
2543     attr_value: i64,
2544     started: std.atomic.Value(usize) = std.atomic.Value(usize).init(0),
2545     release: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
2546     total_runs: std.atomic.Value(usize) = std.atomic.Value(usize).init(0),
2547 };
2548 
2549 const ParallelStressPass = struct {
2550     fn run(raw: ?*anyopaque, ctx: *PassContext) PassResult {
2551         const state: *ParallelStressPassState = @ptrCast(@alignCast(raw orelse return .failure));
2552         if (!ctx.ir_ctx.isMultithreadedExecution()) return .failure;
2553         if (ctx.workerCount(parallel_stress_target_count) < 2) return .failure;
2554         _ = ctx.ir_ctx.getI64Attr(state.attr_value) catch return .failure;
2555 
2556         const arrived = state.started.fetchAdd(1, .acq_rel) + 1;
2557         if (arrived == 2) state.release.store(true, .release);
2558 
2559         var spins: usize = 0;
2560         while (!state.release.load(.acquire)) {
2561             spins += 1;
2562             if (spins > 1_000_000) return .failure;
2563             sys.thread.yield();
2564         }
2565 
2566         var diagnostic = ctx.op.emitWarning("parallel stress target");
2567         defer diagnostic.deinit();
2568         _ = diagnostic.emit() catch return .failure;
2569 
2570         _ = state.total_runs.fetchAdd(1, .acq_rel);
2571         ctx.preserveAllAnalyses();
2572         return .success;
2573     }
2574 };
2575 
2576 const ParallelStressForcedFailurePass = struct {
2577     fn run(ctx: *PassContext) PassResult {
2578         const symbol = ir.SymbolTable.getSymbolName(ctx.op) orelse return .failure;
2579         ctx.preserveAllAnalyses();
2580         if (std.mem.eql(u8, symbol, "fail_7")) return .failure;
2581         return .success;
2582     }
2583 };
2584 
2585 const ParallelStressReadOnlyPass = struct {
2586     fn run(ctx: *PassContext) PassResult {
2587         ctx.preserveAllAnalyses();
2588         return .success;
2589     }
2590 };
2591 
2592 const ParallelStressStorageMissPass = struct {
2593     fn run(ctx: *PassContext) PassResult {
2594         _ = ctx.ir_ctx.getI64Attr(9999) catch return .failure;
2595         ctx.preserveAllAnalyses();
2596         return .success;
2597     }
2598 };
2599 
2600 test "PassManager.runWithOptions stresses read-only parallel target runs" {
2601     const testing = std.testing;
2602     const test_dialect = @import("../../dialects/fixture/root.zig");
2603 
2604     for (0..4) |iteration| {
2605         var arena = alloc_arena.Arena.init(std.testing.allocator);
2606         defer arena.deinit();
2607         const allocator = arena.allocator();
2608 
2609         var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2610         defer ctx.deinit(allocator);
2611         try ir.dialects.loadDialectSpec(&ctx, test_dialect.TestDialect.spec);
2612         _ = try ctx.getI64Attr(@intCast(40 + iteration));
2613 
2614         var recorder = ParallelStressRecorder{};
2615         _ = try ctx.registerDiagnosticHandler(.{
2616             .context = &recorder,
2617             .handle = ParallelStressRecorder.handle,
2618         });
2619 
2620         const loc = ir.Location.getUnknown();
2621         const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
2622         const module_block = module_op.getBodyBlock();
2623         var expected_symbols: [parallel_stress_target_count][]const u8 = undefined;
2624 
2625         for (0..parallel_stress_target_count) |slot| {
2626             const ordinal = (slot * 5 + iteration * 3) % parallel_stress_target_count;
2627             const name = try std.fmt.allocPrint(allocator, "stress_{d}_{d}", .{ iteration, ordinal });
2628             expected_symbols[slot] = name;
2629             const func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, name, &.{});
2630             try module_block.addOperation(func.op);
2631         }
2632 
2633         var state = ParallelStressPassState{ .attr_value = @intCast(40 + iteration) };
2634         var pm = PassManager.init(allocator);
2635         defer pm.deinit();
2636         pm.enableVerifier();
2637 
2638         const func_pm = try pm.nest(test_dialect.TestDialect.FuncOp.operation_name);
2639         try func_pm.addPass(Pass{
2640             .name = "stress-read-only-parallel",
2641             .description = "stress read-only parallel target execution",
2642             .state = &state,
2643             .run_with_state_fn = ParallelStressPass.run,
2644             .state_concurrency = .shared,
2645             .mutation_scope = .read_only,
2646         });
2647 
2648         const result = pm.runWithOptions(module_op.op, &ctx, .{ .max_threads = 4 });
2649         try testing.expectEqual(PassResult.success, result);
2650         try testing.expectEqual(@as(u64, parallel_stress_target_count), pm.stats.pass_runs);
2651         try testing.expectEqual(@as(u64, 0), pm.stats.pass_failures);
2652         try testing.expectEqual(@as(u64, 0), pm.stats.verifier_failures);
2653         try testing.expectEqual(@as(u64, 0), pm.stats.passes_modified);
2654         try testing.expectEqual(
2655             @as(usize, parallel_stress_target_count),
2656             state.total_runs.load(.acquire),
2657         );
2658         try testing.expectEqual(@as(usize, parallel_stress_target_count), recorder.seen);
2659         for (expected_symbols, 0..) |expected, index| {
2660             try testing.expectEqualStrings(expected, recorder.symbols[index]);
2661         }
2662     }
2663 
2664     {
2665         var arena = alloc_arena.Arena.init(std.testing.allocator);
2666         defer arena.deinit();
2667         const allocator = arena.allocator();
2668 
2669         var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2670         defer ctx.deinit(allocator);
2671         try ir.dialects.loadDialectSpec(&ctx, test_dialect.TestDialect.spec);
2672 
2673         const loc = ir.Location.getUnknown();
2674         const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
2675         const module_block = module_op.getBodyBlock();
2676         for (0..parallel_stress_target_count) |index| {
2677             const name = try std.fmt.allocPrint(allocator, "fail_{d}", .{index});
2678             const func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, name, &.{});
2679             try module_block.addOperation(func.op);
2680         }
2681 
2682         var pm = PassManager.init(allocator);
2683         defer pm.deinit();
2684         const func_pm = try pm.nest(test_dialect.TestDialect.FuncOp.operation_name);
2685         try func_pm.addPass(Pass{
2686             .name = "stress-forced-failure",
2687             .description = "fails one parallel target",
2688             .run_fn = ParallelStressForcedFailurePass.run,
2689             .mutation_scope = .read_only,
2690         });
2691 
2692         const result = pm.runWithOptions(module_op.op, &ctx, .{ .max_threads = 4 });
2693         try testing.expectEqual(PassResult.failure, result);
2694         try testing.expectEqual(@as(u64, parallel_stress_target_count), pm.stats.pass_runs);
2695         try testing.expectEqual(@as(u64, 1), pm.stats.pass_failures);
2696 
2697         const reproducer = pm.getLastFailureReproducer() orelse return error.TestExpectedReproducer;
2698         try testing.expectEqual(PassFailureKind.pass, reproducer.failure_kind.?);
2699         try testing.expectEqualStrings("fail_7", reproducer.target_symbol_name.?);
2700     }
2701 
2702     {
2703         var arena = alloc_arena.Arena.init(std.testing.allocator);
2704         defer arena.deinit();
2705         const allocator = arena.allocator();
2706 
2707         var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2708         defer ctx.deinit(allocator);
2709         try ir.dialects.loadDialectSpec(&ctx, test_dialect.TestDialect.spec);
2710 
2711         const loc = ir.Location.getUnknown();
2712         const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
2713         const module_block = module_op.getBodyBlock();
2714         var invalid_targets: u64 = 0;
2715         for (0..parallel_stress_target_count) |index| {
2716             const name = try std.fmt.allocPrint(allocator, "verify_{d}", .{index});
2717             const func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, name, &.{});
2718             try module_block.addOperation(func.op);
2719             if (index % 3 == 1) {
2720                 try appendInvalidTestConstantToBlock(&ctx, func.getEntryBlock());
2721                 invalid_targets += 1;
2722             }
2723         }
2724 
2725         var pm = PassManager.init(allocator);
2726         defer pm.deinit();
2727         pm.enableVerifier();
2728         const func_pm = try pm.nest(test_dialect.TestDialect.FuncOp.operation_name);
2729         try func_pm.addPass(Pass{
2730             .name = "stress-verifier-failure",
2731             .description = "lets verifier fail invalid parallel targets",
2732             .run_fn = ParallelStressReadOnlyPass.run,
2733             .mutation_scope = .read_only,
2734         });
2735 
2736         const result = pm.runWithOptions(module_op.op, &ctx, .{ .max_threads = 4 });
2737         try testing.expectEqual(PassResult.failure, result);
2738         try testing.expectEqual(@as(u64, parallel_stress_target_count), pm.stats.pass_runs);
2739         try testing.expectEqual(invalid_targets, pm.stats.verifier_failures);
2740 
2741         const reproducer = pm.getLastFailureReproducer() orelse return error.TestExpectedReproducer;
2742         try testing.expectEqual(PassFailureKind.verifier, reproducer.failure_kind.?);
2743         try testing.expectEqual(error.MissingRequiredAttribute, reproducer.verifier_error.?);
2744         try testing.expectEqualStrings("MissingRequiredAttribute", reproducer.verifier_error_name.?);
2745     }
2746 
2747     {
2748         var arena = alloc_arena.Arena.init(std.testing.allocator);
2749         defer arena.deinit();
2750         const allocator = arena.allocator();
2751 
2752         var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2753         defer ctx.deinit(allocator);
2754         try ir.dialects.loadDialectSpec(&ctx, test_dialect.TestDialect.spec);
2755 
2756         const loc = ir.Location.getUnknown();
2757         const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
2758         const module_block = module_op.getBodyBlock();
2759         for (0..4) |index| {
2760             const name = try std.fmt.allocPrint(allocator, "miss_{d}", .{index});
2761             const func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, name, &.{});
2762             try module_block.addOperation(func.op);
2763         }
2764 
2765         var pm = PassManager.init(allocator);
2766         defer pm.deinit();
2767         const func_pm = try pm.nest(test_dialect.TestDialect.FuncOp.operation_name);
2768         try func_pm.addPass(Pass{
2769             .name = "stress-context-storage-miss",
2770             .description = "fails on context storage miss",
2771             .run_fn = ParallelStressStorageMissPass.run,
2772             .mutation_scope = .read_only,
2773         });
2774 
2775         const result = pm.runWithOptions(module_op.op, &ctx, .{ .max_threads = 4 });
2776         try testing.expectEqual(PassResult.failure, result);
2777         try testing.expectEqual(@as(u64, 4), pm.stats.pass_runs);
2778         try testing.expectEqual(@as(u64, 4), pm.stats.pass_failures);
2779     }
2780 }
2781 
2782 const SerialThreadingCheckPass = struct {
2783     fn run(ctx: *PassContext) PassResult {
2784         if (ctx.ir_ctx.isMultithreadedExecution()) return .failure;
2785         ctx.preserveAllAnalyses();
2786         return .success;
2787     }
2788 };
2789 
2790 test "PassManager.run keeps serial passes outside multithreaded execution" {
2791     const testing = std.testing;
2792     const test_dialect = @import("../../dialects/fixture/root.zig");
2793 
2794     var arena = alloc_arena.Arena.init(std.testing.allocator);
2795     defer arena.deinit();
2796     const allocator = arena.allocator();
2797 
2798     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2799     defer ctx.deinit(allocator);
2800 
2801     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
2802 
2803     var pm = PassManager.init(allocator);
2804     defer pm.deinit();
2805     try pm.addPass(Pass{
2806         .name = "serial-threading-check",
2807         .description = "checks serial context execution state",
2808         .run_fn = SerialThreadingCheckPass.run,
2809         .mutation_scope = .read_only,
2810     });
2811 
2812     const result = pm.run(module_op.op, &ctx);
2813     try testing.expectEqual(PassResult.success, result);
2814 }
2815 
2816 const WorkerAllocatorCheckPass = struct {
2817     var expected: std.mem.Allocator = undefined;
2818     var seen = std.atomic.Value(usize).init(0);
2819 
2820     fn run(ctx: *PassContext) PassResult {
2821         if (ctx.allocator.ptr != expected.ptr) return .failure;
2822         if (ctx.allocator.vtable != expected.vtable) return .failure;
2823         const marker = ctx.allocator.create(u32) catch return .failure;
2824         ctx.allocator.destroy(marker);
2825         ctx.preserveAllAnalyses();
2826         _ = seen.fetchAdd(1, .acq_rel);
2827         return .success;
2828     }
2829 };
2830 
2831 test "PassManager.runWithOptions uses configured worker allocator for read-only targets" {
2832     const testing = std.testing;
2833     const test_dialect = @import("../../dialects/fixture/root.zig");
2834 
2835     var arena = alloc_arena.Arena.init(std.testing.allocator);
2836     defer arena.deinit();
2837     const allocator = arena.allocator();
2838 
2839     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2840     defer ctx.deinit(allocator);
2841 
2842     const loc = ir.Location.getUnknown();
2843     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
2844     const module_block = module_op.getBodyBlock();
2845 
2846     const first_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "first", &.{});
2847     try module_block.addOperation(first_func.op);
2848     const second_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "second", &.{});
2849     try module_block.addOperation(second_func.op);
2850 
2851     WorkerAllocatorCheckPass.seen.store(0, .release);
2852 
2853     var pm = PassManager.init(allocator);
2854     defer pm.deinit();
2855 
2856     const func_pm = try pm.nest("test.func");
2857     try func_pm.addPass(Pass{
2858         .name = "worker-allocator-check",
2859         .description = "checks read-only worker allocator",
2860         .run_fn = WorkerAllocatorCheckPass.run,
2861         .mutation_scope = .read_only,
2862     });
2863 
2864     var worker_gpa = alloc_observe.debug.Allocator(.{}).init(
2865         testing.allocator,
2866     );
2867     defer {
2868         const status = worker_gpa.deinit();
2869         testing.expect(status == .ok) catch @panic("pass worker allocator leaked allocations");
2870     }
2871     const worker_allocator = worker_gpa.allocator();
2872     WorkerAllocatorCheckPass.expected = worker_allocator;
2873 
2874     const result = pm.runWithOptions(module_op.op, &ctx, .{
2875         .max_threads = 2,
2876         .worker_allocator = worker_allocator,
2877     });
2878     try testing.expectEqual(PassResult.success, result);
2879     try testing.expectEqual(@as(usize, 2), WorkerAllocatorCheckPass.seen.load(.acquire));
2880 }
2881 
2882 const RunOptionsCheckPass = struct {
2883     var expected_allocator: std.mem.Allocator = undefined;
2884 
2885     fn run(ctx: *PassContext) PassResult {
2886         if (ctx.workerCount(8) != 3) return .failure;
2887         const worker_allocator = ctx.workerAllocator();
2888         if (worker_allocator.ptr != expected_allocator.ptr) return .failure;
2889         if (worker_allocator.vtable != expected_allocator.vtable) return .failure;
2890         ctx.preserveAllAnalyses();
2891         return .success;
2892     }
2893 };
2894 
2895 test "PassManager.runWithOptions exposes options to pass context" {
2896     const testing = std.testing;
2897     const test_dialect = @import("../../dialects/fixture/root.zig");
2898 
2899     var arena = alloc_arena.Arena.init(std.testing.allocator);
2900     defer arena.deinit();
2901     const allocator = arena.allocator();
2902 
2903     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2904     defer ctx.deinit(allocator);
2905 
2906     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
2907 
2908     var pm = PassManager.init(allocator);
2909     defer pm.deinit();
2910     try pm.addPass(Pass{
2911         .name = "option-check",
2912         .description = "checks pass context run options",
2913         .run_fn = RunOptionsCheckPass.run,
2914         .mutation_scope = .read_only,
2915     });
2916 
2917     var worker_gpa = alloc_observe.debug.Allocator(.{}).init(
2918         testing.allocator,
2919     );
2920     defer {
2921         const status = worker_gpa.deinit();
2922         testing.expect(status == .ok) catch @panic("pass option worker allocator leaked allocations");
2923     }
2924     const worker_allocator = worker_gpa.allocator();
2925     RunOptionsCheckPass.expected_allocator = worker_allocator;
2926 
2927     const result = pm.runWithOptions(module_op.op, &ctx, .{
2928         .max_threads = 3,
2929         .worker_allocator = worker_allocator,
2930     });
2931     try testing.expectEqual(PassResult.success, result);
2932 }
2933 
2934 const SameTypeFuncCounter = struct {
2935     var count: usize = 0;
2936 
2937     fn run(ctx: *PassContext) PassResult {
2938         if (std.mem.eql(u8, ctx.op.name.name, "test.func")) {
2939             count += 1;
2940         }
2941         return .success;
2942     }
2943 };
2944 
2945 test "Same-type nesting: func inside func both processed" {
2946     const testing = std.testing;
2947     const test_dialect = @import("../../dialects/fixture/root.zig");
2948 
2949     var arena = alloc_arena.Arena.init(std.testing.allocator);
2950     defer arena.deinit();
2951     const allocator = arena.allocator();
2952 
2953     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2954     defer ctx.deinit(allocator);
2955 
2956     const loc = ir.Location.getUnknown();
2957     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
2958     const module_block = module_op.getBodyBlock();
2959 
2960     const outer_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "outer", &.{});
2961     try module_block.addOperation(outer_func.op);
2962 
2963     const inner_func = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "inner", &.{});
2964     try outer_func.getEntryBlock().addOperation(inner_func.op);
2965 
2966     var pm = PassManager.init(allocator);
2967     defer pm.deinit();
2968 
2969     SameTypeFuncCounter.count = 0;
2970 
2971     const func_pm = try pm.nest("test.func");
2972     try func_pm.addPass(Pass{
2973         .name = "func-counter",
2974         .description = "Counts func ops",
2975         .run_fn = SameTypeFuncCounter.run,
2976     });
2977 
2978     const result = pm.run(module_op.op, &ctx);
2979     try testing.expectEqual(PassResult.success, result);
2980 
2981     try testing.expectEqual(@as(usize, 2), SameTypeFuncCounter.count);
2982 }
2983 
2984 test "PassManager with instrumentation invokes hooks" {
2985     const testing = std.testing;
2986     const test_dialect = @import("../../dialects/fixture/root.zig");
2987 
2988     var arena = alloc_arena.Arena.init(std.testing.allocator);
2989     defer arena.deinit();
2990     const allocator = arena.allocator();
2991 
2992     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2993     defer ctx.deinit(allocator);
2994 
2995     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
2996 
2997     var pm = PassManager.init(allocator);
2998     defer pm.deinit();
2999 
3000     var counter = CountingInstrumentation{};
3001     try pm.addInstrumentation(counter.instrumentation());
3002 
3003     try pm.addPass(Pass{
3004         .name = "noop-pass",
3005         .description = "Does nothing",
3006         .run_fn = test_pass_success,
3007     });
3008 
3009     const result = pm.run(module_op.op, &ctx);
3010     try testing.expectEqual(PassResult.success, result);
3011 
3012     try testing.expectEqual(@as(usize, 1), counter.pipeline_count);
3013     try testing.expectEqual(@as(usize, 1), counter.pass_count);
3014     try testing.expectEqual(@as(usize, 0), counter.pass_failures);
3015 }
3016 
3017 test "PassManager timing instrumentation" {
3018     const testing = std.testing;
3019     const test_dialect = @import("../../dialects/fixture/root.zig");
3020 
3021     var arena = alloc_arena.Arena.init(std.testing.allocator);
3022     defer arena.deinit();
3023     const allocator = arena.allocator();
3024 
3025     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
3026     defer ctx.deinit(allocator);
3027 
3028     const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
3029 
3030     var pm = PassManager.init(allocator);
3031     defer pm.deinit();
3032 
3033     var timing = TimingInstrumentation.init(allocator);
3034     defer timing.deinit();
3035     try pm.addInstrumentation(timing.instrumentation());
3036 
3037     try pm.addPass(Pass{
3038         .name = "timed-pass",
3039         .description = "Timed pass",
3040         .run_fn = test_pass_success,
3041     });
3042 
3043     const result = pm.run(module_op.op, &ctx);
3044     try testing.expectEqual(PassResult.success, result);
3045 
3046     try testing.expect(timing.getPassCount("timed-pass") != null);
3047     try testing.expectEqual(@as(u64, 1), timing.getPassCount("timed-pass").?);
3048     try testing.expect(timing.hasPipelineTimings());
3049 
3050     const pipeline_summaries = try timing.pipelineSummariesAlloc(allocator);
3051     try testing.expectEqual(@as(usize, 1), pipeline_summaries.len);
3052     try testing.expectEqual(@as(?[]const u8, null), pipeline_summaries[0].target);
3053     try testing.expectEqual(@as(usize, 0), pipeline_summaries[0].depth);
3054     try testing.expect(pipeline_summaries[0].total_ns >= 0);
3055 }
3056 
3057 fn meteredAnalysisBounds(_: subject.work.Input) !subject.work.Bounds {
3058     return .{
3059         .work = .{
3060             .analysis_computations = 1,
3061             .structural_visits = 5,
3062             .allocation_capacity = 4096,
3063         },
3064         .workspace = 4096,
3065     };
3066 }
3067 
3068 test "U0 analysis admission refuses before computation and keeps the exhausted charge" {
3069     const revision = @import("../../product/revision/root.zig");
3070     const fixture = @import("../../dialects/fixture/root.zig");
3071     const allocator = std.testing.allocator;
3072     var context = try ir.Context.init(allocator, ir.Context.Limits.testing);
3073     defer context.deinit(allocator);
3074     const module = try fixture.TestDialect.ModuleOp.create(&context, ir.Location.getUnknown());
3075     const ledger = try revision.AccountingV1.create(allocator, .{
3076         .allowance = .{
3077             .structural_visits = 4,
3078             .analysis_computations = 1,
3079             .allocation_capacity = 65536,
3080         },
3081         .workspace = 4096,
3082         .events = 8,
3083     }, &.{});
3084     defer ledger.destroy();
3085     var stats: PassManagerStats = .{};
3086     var cache = try AnalysisCache.initAccounted(allocator, &stats, ledger, .{}, 4);
3087     defer cache.deinit();
3088     var notifications = CountingInstrumentation{};
3089     var instrumentor = PassInstrumentor.init(allocator);
3090     defer instrumentor.deinit();
3091     try instrumentor.addInstrumentation(notifications.instrumentation());
3092     var ctx = PassContext.initWithInstrumentor(
3093         module.op,
3094         &context,
3095         allocator,
3096         &cache,
3097         &instrumentor,
3098     );
3099     defer ctx.deinit();
3100     const descriptor = AnalysisDescriptor{
3101         .id = analysisId("u0-metered-analysis"),
3102         .name = "u0-metered-analysis",
3103         .work_contract = .{
3104             .identity = .{ .name = "u0-metered-analysis", .version = 1 },
3105             .estimate = meteredAnalysisBounds,
3106         },
3107     };
3108     analysis_counter = 0;
3109     const result = ctx.getAnalysis(module.op, &descriptor, computeCounter, cleanupCounter);
3110     try std.testing.expectError(error.WorkExhausted, result);
3111     try std.testing.expectEqual(0, analysis_counter);
3112     try std.testing.expectEqual(0, stats.analysis_misses);
3113     try std.testing.expectEqual(0, notifications.analysis_count);
3114     try std.testing.expectEqual(.exhausted, ledger.view().outcome);
3115     try std.testing.expectEqual(5, ledger.view().charged.structural_visits);
3116     try std.testing.expectEqual(0, cache.entries.count());
3117 }
3118 
3119 test "U0 analysis accounting records computations and hits without charging a second computation" {
3120     const revision = @import("../../product/revision/root.zig");
3121     const fixture = @import("../../dialects/fixture/root.zig");
3122     const allocator = std.testing.allocator;
3123     var context = try ir.Context.init(allocator, ir.Context.Limits.testing);
3124     defer context.deinit(allocator);
3125     const module = try fixture.TestDialect.ModuleOp.create(&context, ir.Location.getUnknown());
3126     const pipeline = [_]revision.record.Version{.{ .name = "host", .version = 1 }};
3127     const ledger = try revision.AccountingV1.create(allocator, .{
3128         .allowance = .{
3129             .structural_visits = 6,
3130             .analysis_computations = 1,
3131             .allocation_capacity = 65536,
3132         },
3133         .workspace = 4096,
3134         .events = 8,
3135     }, &pipeline);
3136     defer ledger.destroy();
3137     var stats: PassManagerStats = .{};
3138     var cache = try AnalysisCache.initAccounted(allocator, &stats, ledger, .{}, 4);
3139     defer cache.deinit();
3140     const parent = try ledger.begin(.pass, .{
3141         .identity = pipeline[0],
3142         .work = .{ .structural_visits = 1 },
3143     });
3144     var ctx = PassContext.init(module.op, &context, allocator, &cache);
3145     defer ctx.deinit();
3146     const descriptor = AnalysisDescriptor{
3147         .id = analysisId("u0-metered-analysis"),
3148         .name = "u0-metered-analysis",
3149         .work_contract = .{
3150             .identity = .{ .name = "u0-metered-analysis", .version = 1 },
3151             .estimate = meteredAnalysisBounds,
3152         },
3153     };
3154     analysis_counter = 0;
3155     const first = try ctx.getAnalysis(module.op, &descriptor, computeCounter, cleanupCounter);
3156     const second = try ctx.getAnalysis(module.op, &descriptor, computeCounter, cleanupCounter);
3157     try ledger.finish(parent, .success, .{ .counters = .{ .pass_runs = 1 } });
3158     try std.testing.expectEqual(first, second);
3159     try std.testing.expectEqual(1, analysis_counter);
3160     try std.testing.expectEqual(1, ledger.view().charged.analysis_computations);
3161     try std.testing.expectEqual(
3162         stats.analysis_misses,
3163         ledger.view().executed.counters.analysis_misses,
3164     );
3165     try std.testing.expectEqual(stats.analysis_hits, ledger.view().executed.counters.analysis_hits);
3166     try std.testing.expectEqual(1, ledger.view().events[parent].executed.counters.analysis_hits);
3167     try std.testing.expectEqual(.analysis, ledger.view().events[2].phase);
3168     try std.testing.expectEqual(parent, ledger.view().events[2].parent.?);
3169     try std.testing.expectEqual(1, ledger.view().executed.counters.pass_runs);
3170     try ledger.producersComplete();
3171 }
3172 
3173 test "U0 analysis missing contract permits transient computation and refuses qualification" {
3174     const revision = @import("../../product/revision/root.zig");
3175     const fixture = @import("../../dialects/fixture/root.zig");
3176     const allocator = std.testing.allocator;
3177     var context = try ir.Context.init(allocator, ir.Context.Limits.testing);
3178     defer context.deinit(allocator);
3179     const module = try fixture.TestDialect.ModuleOp.create(&context, ir.Location.getUnknown());
3180     const ledger = try revision.AccountingV1.create(allocator, .{
3181         .allowance = .{ .allocation_capacity = 65536 },
3182         .workspace = 4096,
3183         .events = 8,
3184     }, &.{});
3185     defer ledger.destroy();
3186     var cache = try AnalysisCache.initAccounted(allocator, null, ledger, .{}, 4);
3187     defer cache.deinit();
3188     var ctx = PassContext.init(module.op, &context, allocator, &cache);
3189     defer ctx.deinit();
3190     const descriptor = AnalysisDescriptor{
3191         .id = analysisId("u0-unmetered-analysis"),
3192         .name = "u0-unmetered-analysis",
3193     };
3194     analysis_counter = 0;
3195     _ = try ctx.getAnalysis(module.op, &descriptor, computeCounter, cleanupCounter);
3196     try std.testing.expectEqual(1, analysis_counter);
3197     try std.testing.expectEqual(1, ledger.view().executed.counters.analysis_misses);
3198     try std.testing.expectError(error.MissingWorkContract, ledger.producersComplete());
3199     try std.testing.expect(ledger.view().missing_work_contract);
3200     try std.testing.expectEqual(1, ledger.view().events.len);
3201 }
3202 
3203 fn computeMeteredTyped(ctx: *PassContext, op: *ir.Operation) anyerror!*anyopaque {
3204     return @ptrCast(try computeTypedCounter(ctx, op));
3205 }
3206 
3207 fn cleanupMeteredTyped(value: *anyopaque, allocator: std.mem.Allocator) void {
3208     cleanupTypedCounter(@ptrCast(@alignCast(value)), allocator);
3209 }
3210 
3211 test "U0 analysis computation failure stays charged and cannot retry the same request" {
3212     const revision = @import("../../product/revision/root.zig");
3213     const fixture = @import("../../dialects/fixture/root.zig");
3214     const allocator = std.testing.allocator;
3215     var context = try ir.Context.init(allocator, ir.Context.Limits.testing);
3216     defer context.deinit(allocator);
3217     const module = try fixture.TestDialect.ModuleOp.create(&context, ir.Location.getUnknown());
3218     const ledger = try revision.AccountingV1.create(allocator, .{
3219         .allowance = .{
3220             .structural_visits = 5,
3221             .analysis_computations = 1,
3222             .allocation_capacity = 65536,
3223         },
3224         .workspace = 4096,
3225         .events = 8,
3226     }, &.{});
3227     defer ledger.destroy();
3228     var failing = std.testing.FailingAllocator.init(allocator, .{ .fail_index = 1 });
3229     var stats: PassManagerStats = .{};
3230     var cache = try AnalysisCache.initAccounted(failing.allocator(), &stats, ledger, .{}, 4);
3231     defer cache.deinit();
3232     var ctx = PassContext.init(module.op, &context, failing.allocator(), &cache);
3233     defer ctx.deinit();
3234     const descriptor = AnalysisDescriptor{
3235         .id = analysisId("u0-metered-analysis"),
3236         .name = "u0-metered-analysis",
3237         .work_contract = .{
3238             .identity = .{ .name = "u0-metered-analysis", .version = 1 },
3239             .estimate = meteredAnalysisBounds,
3240         },
3241     };
3242     typed_analysis_counter = 0;
3243     typed_analysis_cleanup_count = 0;
3244     try std.testing.expectError(
3245         error.OutOfMemory,
3246         ctx.getAnalysis(module.op, &descriptor, computeMeteredTyped, cleanupMeteredTyped),
3247     );
3248     try std.testing.expect(failing.has_induced_failure);
3249     try std.testing.expectEqual(1, typed_analysis_counter);
3250     try std.testing.expectEqual(0, typed_analysis_cleanup_count);
3251     try std.testing.expectEqual(0, cache.entries.count());
3252     try std.testing.expectEqual(.rejected, ledger.view().outcome);
3253     try std.testing.expectEqual(5, ledger.view().charged.structural_visits);
3254     try std.testing.expectEqual(1, ledger.view().executed.work.analysis_computations);
3255     try std.testing.expectEqual(0, ledger.view().executed.counters.analysis_misses);
3256     failing.fail_index = std.math.maxInt(usize);
3257     try std.testing.expectError(
3258         error.TerminalWorkOutcome,
3259         ctx.getAnalysis(module.op, &descriptor, computeMeteredTyped, cleanupMeteredTyped),
3260     );
3261     try std.testing.expectEqual(1, typed_analysis_counter);
3262 }
3263 
3264 fn refreshMeteredCounter(_: *PassContext, _: *ir.Operation, value: *anyopaque) !void {
3265     analysis_counter += 1;
3266     const counter: *u32 = @ptrCast(@alignCast(value));
3267     counter.* = analysis_counter;
3268 }
3269 
3270 test "U0 analysis refresh charges before replacing a cached value" {
3271     const revision = @import("../../product/revision/root.zig");
3272     const fixture = @import("../../dialects/fixture/root.zig");
3273     const allocator = std.testing.allocator;
3274     var context = try ir.Context.init(allocator, ir.Context.Limits.testing);
3275     defer context.deinit(allocator);
3276     const module = try fixture.TestDialect.ModuleOp.create(&context, ir.Location.getUnknown());
3277     const descriptor = AnalysisDescriptor{
3278         .id = analysisId("u0-metered-analysis"),
3279         .name = "u0-metered-analysis",
3280         .work_contract = .{
3281             .identity = .{ .name = "u0-metered-analysis", .version = 1 },
3282             .estimate = meteredAnalysisBounds,
3283         },
3284     };
3285     for ([_]u64{ 1, 2 }) |computations| {
3286         const ledger = try revision.AccountingV1.create(allocator, .{
3287             .allowance = .{
3288                 .structural_visits = 10,
3289                 .analysis_computations = computations,
3290                 .allocation_capacity = 65536,
3291             },
3292             .workspace = 4096,
3293             .events = 8,
3294         }, &.{});
3295         defer ledger.destroy();
3296         var stats: PassManagerStats = .{};
3297         var cache = try AnalysisCache.initAccounted(allocator, &stats, ledger, .{}, 4);
3298         defer cache.deinit();
3299         var ctx = PassContext.init(module.op, &context, allocator, &cache);
3300         defer ctx.deinit();
3301         analysis_counter = 0;
3302         const first = try ctx.getAnalysis(module.op, &descriptor, computeCounter, cleanupCounter);
3303         const result = ctx.refreshAnalysis(module.op, &descriptor, refreshMeteredCounter);
3304         if (computations == 1) {
3305             try std.testing.expectError(error.WorkExhausted, result);
3306             try std.testing.expectEqual(1, @as(*u32, @ptrCast(@alignCast(first))).*);
3307             try std.testing.expectEqual(.exhausted, ledger.view().outcome);
3308         } else {
3309             try result;
3310             const next = try ctx.getAnalysis(
3311                 module.op,
3312                 &descriptor,
3313                 computeCounter,
3314                 cleanupCounter,
3315             );
3316             try std.testing.expectEqual(first, next);
3317             try std.testing.expectEqual(2, @as(*u32, @ptrCast(@alignCast(next))).*);
3318         }
3319         try std.testing.expectEqual(computations, analysis_counter);
3320         try std.testing.expectEqual(computations, stats.analysis_misses);
3321         try std.testing.expectEqual(2, ledger.view().charged.analysis_computations);
3322     }
3323 }
3324 
3325 const MeteredTypedAnalysis = Analysis(
3326     u32,
3327     "u0-metered-typed-analysis",
3328     &.{},
3329     computeTypedCounter,
3330     cleanupTypedCounter,
3331     .{
3332         .identity = .{ .name = "u0-metered-typed-analysis", .version = 1 },
3333         .estimate = meteredAnalysisBounds,
3334     },
3335 );
3336 
3337 test "U0 analysis typed registration carries its normative declaration" {
3338     const revision = @import("../../product/revision/root.zig");
3339     const fixture = @import("../../dialects/fixture/root.zig");
3340     const allocator = std.testing.allocator;
3341     var context = try ir.Context.init(allocator, ir.Context.Limits.testing);
3342     defer context.deinit(allocator);
3343     const module = try fixture.TestDialect.ModuleOp.create(&context, ir.Location.getUnknown());
3344     const ledger = try revision.AccountingV1.create(allocator, .{
3345         .allowance = .{ .analysis_computations = 0, .allocation_capacity = 65536 },
3346         .workspace = 4096,
3347         .events = 8,
3348     }, &.{});
3349     defer ledger.destroy();
3350     var cache = try AnalysisCache.initAccounted(allocator, null, ledger, .{}, 4);
3351     defer cache.deinit();
3352     var ctx = PassContext.init(module.op, &context, allocator, &cache);
3353     defer ctx.deinit();
3354     typed_analysis_counter = 0;
3355     try std.testing.expectError(error.WorkExhausted, MeteredTypedAnalysis.get(&ctx, module.op));
3356     try std.testing.expectEqual(0, typed_analysis_counter);
3357     try std.testing.expect(!ledger.view().missing_work_contract);
3358     try std.testing.expectEqualStrings(
3359         "u0-metered-typed-analysis",
3360         ledger.view().events[1].identity().name,
3361     );
3362 }
3363 
3364 test "U0 analysis fixed workspace exhaustion retains cleanup and refuses a fresh retry" {
3365     const revision = @import("../../product/revision/root.zig");
3366     const fixture = @import("../../dialects/fixture/root.zig");
3367     const fixed = @import("alloc_fixed");
3368     const allocator = std.testing.allocator;
3369     var context = try ir.Context.init(allocator, ir.Context.Limits.testing);
3370     defer context.deinit(allocator);
3371     const module = try fixture.TestDialect.ModuleOp.create(&context, ir.Location.getUnknown());
3372     const ledger = try revision.AccountingV1.create(allocator, .{
3373         .allowance = .{
3374             .structural_visits = 5,
3375             .analysis_computations = 1,
3376             .allocation_capacity = 65536,
3377         },
3378         .workspace = 4096,
3379         .events = 8,
3380     }, &.{});
3381     defer ledger.destroy();
3382     var bytes: [4096]u8 align(@alignOf(usize)) = undefined;
3383     var storage = fixed.Tracked.init(&bytes);
3384     var cache = try AnalysisCache.initAccounted(
3385         storage.allocator(),
3386         null,
3387         ledger,
3388         .{ .workspace = &storage.exhausted },
3389         4,
3390     );
3391     defer cache.deinit();
3392     var ctx = PassContext.init(module.op, &context, storage.allocator(), &cache);
3393     defer ctx.deinit();
3394     typed_analysis_counter = 0;
3395     typed_analysis_cleanup_count = 0;
3396     try std.testing.expectError(error.WorkExhausted, OversizedTypedAnalysis.get(&ctx, module.op));
3397     try std.testing.expectEqual(1, typed_analysis_counter);
3398     try std.testing.expectEqual(0, typed_analysis_cleanup_count);
3399     try std.testing.expectEqual(0, cache.entries.count());
3400     try std.testing.expectEqual(.exhausted, ledger.view().outcome);
3401     try std.testing.expectEqual(1, ledger.view().charged.analysis_computations);
3402     try std.testing.expectEqual(0, ledger.view().executed.counters.analysis_misses);
3403     try std.testing.expectError(
3404         error.TerminalWorkOutcome,
3405         OversizedTypedAnalysis.get(&ctx, module.op),
3406     );
3407     try std.testing.expectEqual(1, typed_analysis_counter);
3408 }
3409 
3410 test "U0 analysis cache reserves its declared table before computations" {
3411     const revision = @import("../../product/revision/root.zig");
3412     const allocator = std.testing.allocator;
3413     const ledger = try revision.AccountingV1.create(allocator, .{
3414         .allowance = .{ .allocation_capacity = 65536 },
3415         .workspace = 4096,
3416         .events = 8,
3417     }, &.{});
3418     defer ledger.destroy();
3419     var failing = std.testing.FailingAllocator.init(allocator, .{ .fail_index = 0 });
3420     const result = AnalysisCache.initAccounted(failing.allocator(), null, ledger, .{}, 4);
3421     if (result) |value| {
3422         var cache = value;
3423         cache.deinit();
3424     } else |_| {}
3425     try std.testing.expectError(error.OutOfMemory, result);
3426     try std.testing.expect(failing.has_induced_failure);
3427     try std.testing.expectEqual(.rejected, ledger.view().outcome);
3428     try std.testing.expect(ledger.view().charged.allocation_capacity > 0);
3429     try std.testing.expectEqual(0, ledger.view().executed.work.analysis_computations);
3430 }
3431 
3432 const OversizedTypedAnalysis = Analysis(
3433     u32,
3434     "u0-oversized-analysis",
3435     &.{},
3436     computeOversizedCounter,
3437     cleanupTypedCounter,
3438     .{
3439         .identity = .{ .name = "u0-oversized-analysis", .version = 1 },
3440         .estimate = oversizedAnalysisBounds,
3441     },
3442 );
3443 
3444 fn computeOversizedCounter(ctx: *PassContext, _: *ir.Operation) !*u32 {
3445     typed_analysis_counter += 1;
3446     {
3447         const scratch = try ctx.allocator.alloc(u8, 4096);
3448         defer ctx.allocator.free(scratch);
3449         @memset(scratch, 0);
3450     }
3451     const value = try ctx.allocator.create(u32);
3452     value.* = typed_analysis_counter;
3453     return value;
3454 }
3455 
3456 fn oversizedAnalysisBounds(input: subject.work.Input) !subject.work.Bounds {
3457     var bounds = try meteredAnalysisBounds(input);
3458     bounds.work.allocation_capacity = 8192;
3459     return bounds;
3460 }
3461 
3462 test "U0 analysis cache storage bound covers real table allocation and teardown" {
3463     const revision = @import("../../product/revision/root.zig");
3464     const allocator = std.testing.allocator;
3465     for ([_]u32{ 0, 1, 6, 7, 8, 13, 64, 1024, 65536 }) |limit| {
3466         const bound = try AnalysisCache.storageBound(limit);
3467         const ledger = try revision.AccountingV1.create(allocator, .{
3468             .allowance = .{ .allocation_capacity = bound },
3469             .workspace = bound,
3470             .events = 8,
3471         }, &.{});
3472         defer ledger.destroy();
3473         var observed = std.testing.FailingAllocator.init(allocator, .{});
3474         var cache = try AnalysisCache.initAccounted(
3475             observed.allocator(),
3476             null,
3477             ledger,
3478             .{},
3479             limit,
3480         );
3481         try std.testing.expect(observed.allocated_bytes <= bound);
3482         try std.testing.expectEqual(@as(usize, if (limit == 0) 0 else 1), observed.allocations);
3483         try std.testing.expectEqual(0, cache.entries.count());
3484         try std.testing.expectEqual(bound, ledger.view().charged.allocation_capacity);
3485         try std.testing.expectEqual(.success, ledger.view().events[0].outcome);
3486         cache.deinit();
3487         try std.testing.expectEqual(observed.allocated_bytes, observed.freed_bytes);
3488     }
3489     try std.testing.expectError(
3490         error.WorkOverflow,
3491         AnalysisCache.storageBound(std.math.maxInt(u32)),
3492     );
3493 }
3494 
3495 test "U0 analysis cache refuses its entry limit before the next computation without table growth" {
3496     const revision = @import("../../product/revision/root.zig");
3497     const fixture = @import("../../dialects/fixture/root.zig");
3498     const allocator = std.testing.allocator;
3499     var context = try ir.Context.init(allocator, ir.Context.Limits.testing);
3500     defer context.deinit(allocator);
3501     const module = try fixture.TestDialect.ModuleOp.create(&context, .unknown);
3502     const ledger = try revision.AccountingV1.create(allocator, .{
3503         .allowance = .{
3504             .structural_visits = 10,
3505             .analysis_computations = 2,
3506             .allocation_capacity = 65536,
3507         },
3508         .workspace = 4096,
3509         .events = 8,
3510     }, &.{});
3511     defer ledger.destroy();
3512     var observed = std.testing.FailingAllocator.init(allocator, .{});
3513     var cache = try AnalysisCache.initAccounted(observed.allocator(), null, ledger, .{}, 1);
3514     defer cache.deinit();
3515     const capacity = cache.entries.capacity();
3516     var ctx = PassContext.init(module.op, &context, observed.allocator(), &cache);
3517     defer ctx.deinit();
3518     typed_analysis_counter = 0;
3519     _ = try MeteredTypedAnalysis.get(&ctx, module.op);
3520     try std.testing.expectEqual(2, observed.allocations);
3521     var another = MeteredTypedAnalysis.descriptor;
3522     another.id = analysisId("another-analysis");
3523     try std.testing.expectError(
3524         error.WorkExhausted,
3525         ctx.getAnalysis(module.op, &another, computeMeteredTyped, cleanupMeteredTyped),
3526     );
3527     try std.testing.expectEqual(1, typed_analysis_counter);
3528     try std.testing.expectEqual(2, observed.allocations);
3529     try std.testing.expectEqual(capacity, cache.entries.capacity());
3530     try std.testing.expectEqual(1, cache.entries.count());
3531     try std.testing.expectEqual(2, ledger.view().charged.analysis_computations);
3532     try std.testing.expectEqual(1, ledger.view().executed.work.analysis_computations);
3533     try std.testing.expectEqual(.exhausted, ledger.view().events[2].outcome);
3534     try std.testing.expectEqual(0, ledger.view().events[2].executed.work.analysis_computations);
3535 }
3536 
3537 var metered_pass_runs: u32 = 0;
3538 
3539 fn runMeteredPass(ctx: *PassContext) PassResult {
3540     metered_pass_runs += 1;
3541     ctx.preserveAllAnalyses();
3542     return .success;
3543 }
3544 
3545 fn meteredPassBounds(_: subject.work.Input) !subject.work.Bounds {
3546     return .{ .work = .{ .structural_visits = 5 } };
3547 }
3548 
3549 test "U0 pass admission refuses before the callback and uses the existing failure finalization" {
3550     const revision = @import("../../product/revision/root.zig");
3551     const fixture = @import("../../dialects/fixture/root.zig");
3552     const allocator = std.testing.allocator;
3553     var context = try ir.Context.init(allocator, ir.Context.Limits.testing);
3554     defer context.deinit(allocator);
3555     const module = try fixture.TestDialect.ModuleOp.create(&context, .unknown);
3556     const identity = revision.record.Version{ .name = "u0-metered-pass", .version = 1 };
3557     const ledger = try revision.AccountingV1.create(allocator, .{
3558         .allowance = .{ .structural_visits = 4, .allocation_capacity = 65536 },
3559         .workspace = 4096,
3560         .events = 8,
3561     }, &.{identity});
3562     defer ledger.destroy();
3563     var cache = try AnalysisCache.initAccounted(allocator, null, ledger, .{}, 4);
3564     defer cache.deinit();
3565     var manager = PassManager.init(allocator);
3566     defer manager.deinit();
3567     try manager.addPass(.{
3568         .name = identity.name,
3569         .description = "bounded pass witness",
3570         .run_fn = runMeteredPass,
3571         .work_contract = .{ .identity = identity, .estimate = meteredPassBounds },
3572     });
3573     metered_pass_runs = 0;
3574     try std.testing.expectEqual(
3575         .failure,
3576         manager.runWithAnalysisCache(module.op, &context, &cache, .{}),
3577     );
3578     try std.testing.expectEqual(0, metered_pass_runs);
3579     try std.testing.expectEqual(0, manager.stats.pass_runs);
3580     try std.testing.expectEqual(1, manager.stats.pass_failures);
3581     try std.testing.expectEqual(.exhausted, ledger.view().outcome);
3582     try std.testing.expectEqual(5, ledger.view().charged.structural_visits);
3583     const failure = manager.getLastFailureReproducer() orelse return error.TestExpectedFailure;
3584     try std.testing.expectEqual(.exhausted, failure.failure_kind.?);
3585     const charged = ledger.view().charged;
3586     try std.testing.expectEqual(.failure, manager.runWithAnalysisCache(
3587         module.op,
3588         &context,
3589         &cache,
3590         .{ .max_threads = 2 },
3591     ));
3592     try std.testing.expectEqual(0, metered_pass_runs);
3593     try std.testing.expectEqual(1, manager.stats.pass_failures);
3594     try std.testing.expectEqualDeep(charged, ledger.view().charged);
3595     try std.testing.expectEqual(.exhausted, manager.getLastFailureReproducer().?.failure_kind.?);
3596 }
3597 
3598 fn runMeteredAnalysisPass(ctx: *PassContext) PassResult {
3599     metered_pass_runs += 1;
3600     _ = MeteredTypedAnalysis.get(ctx, ctx.op) catch return .failure;
3601     ctx.preserveAllAnalyses();
3602     return .success;
3603 }
3604 
3605 fn runSuppressingAnalysisFailure(ctx: *PassContext) PassResult {
3606     metered_pass_runs += 1;
3607     _ = MeteredTypedAnalysis.get(ctx, ctx.op) catch return .success;
3608     return .success;
3609 }
3610 
3611 test "U0 pass accounting executes repeated entries and records one cached analysis computation" {
3612     const revision = @import("../../product/revision/root.zig");
3613     const fixture = @import("../../dialects/fixture/root.zig");
3614     const allocator = std.testing.allocator;
3615     var context = try ir.Context.init(allocator, ir.Context.Limits.testing);
3616     defer context.deinit(allocator);
3617     const module = try fixture.TestDialect.ModuleOp.create(&context, .unknown);
3618     const identity = revision.record.Version{ .name = "u0-metered-pass", .version = 1 };
3619     const ledger = try revision.AccountingV1.create(allocator, .{
3620         .allowance = .{ .structural_visits = 15, .analysis_computations = 1, .allocation_capacity = 65536 },
3621         .workspace = 4096,
3622         .events = 8,
3623     }, &.{ identity, identity });
3624     defer ledger.destroy();
3625     var cache = try AnalysisCache.initAccounted(allocator, null, ledger, .{}, 4);
3626     defer cache.deinit();
3627     var manager = PassManager.init(allocator);
3628     defer manager.deinit();
3629     const entry = Pass{
3630         .name = identity.name,
3631         .description = "bounded pass witness",
3632         .run_fn = runMeteredAnalysisPass,
3633         .rerun_policy = .skip_if_unchanged,
3634         .work_contract = .{ .identity = identity, .estimate = meteredPassBounds },
3635     };
3636     try manager.addPass(entry);
3637     try manager.addPass(entry);
3638     metered_pass_runs = 0;
3639     typed_analysis_counter = 0;
3640     try std.testing.expectEqual(.success, manager.runWithAnalysisCache(module.op, &context, &cache, .{}));
3641     const receipt = ledger.view();
3642     try std.testing.expectEqual(2, metered_pass_runs);
3643     try std.testing.expectEqual(1, typed_analysis_counter);
3644     try std.testing.expectEqual(0, manager.stats.passes_skipped);
3645     try std.testing.expectEqual(2, receipt.executed.counters.pass_runs);
3646     try std.testing.expectEqual(1, receipt.executed.counters.analysis_misses);
3647     try std.testing.expectEqual(1, receipt.executed.counters.analysis_hits);
3648     try std.testing.expectEqual(15, receipt.charged.structural_visits);
3649     try std.testing.expectEqual(1, receipt.events[2].parent.?);
3650     try ledger.producersComplete();
3651 }
3652 
3653 test "U0 pass propagates analysis exhaustion even when native code reports success" {
3654     const revision = @import("../../product/revision/root.zig");
3655     const fixture = @import("../../dialects/fixture/root.zig");
3656     const allocator = std.testing.allocator;
3657     var context = try ir.Context.init(allocator, ir.Context.Limits.testing);
3658     defer context.deinit(allocator);
3659     const module = try fixture.TestDialect.ModuleOp.create(&context, .unknown);
3660     const callbacks = [_]*const fn (*PassContext) PassResult{
3661         runMeteredAnalysisPass, runSuppressingAnalysisFailure,
3662     };
3663     for (callbacks) |callback| {
3664         const identity = revision.record.Version{ .name = "u0-metered-pass", .version = 1 };
3665         const ledger = try revision.AccountingV1.create(allocator, .{
3666             .allowance = .{ .structural_visits = 9, .analysis_computations = 1, .allocation_capacity = 65536 },
3667             .workspace = 4096,
3668             .events = 8,
3669         }, &.{identity});
3670         defer ledger.destroy();
3671         var cache = try AnalysisCache.initAccounted(allocator, null, ledger, .{}, 4);
3672         defer cache.deinit();
3673         var manager = PassManager.init(allocator);
3674         defer manager.deinit();
3675         try manager.addPass(.{
3676             .name = identity.name,
3677             .description = "bounded pass witness",
3678             .run_fn = callback,
3679             .work_contract = .{ .identity = identity, .estimate = meteredPassBounds },
3680         });
3681         metered_pass_runs = 0;
3682         typed_analysis_counter = 0;
3683         try std.testing.expectEqual(.failure, manager.runWithAnalysisCache(module.op, &context, &cache, .{}));
3684         try std.testing.expectEqual(1, metered_pass_runs);
3685         try std.testing.expectEqual(0, typed_analysis_counter);
3686         try std.testing.expectEqual(1, manager.stats.pass_runs);
3687         try std.testing.expectEqual(1, manager.stats.pass_failures);
3688         try std.testing.expectEqual(1, ledger.view().executed.counters.pass_runs);
3689         try std.testing.expectEqual(.exhausted, ledger.view().events[1].outcome);
3690         const failure = manager.getLastFailureReproducer() orelse return error.TestExpectedFailure;
3691         try std.testing.expectEqual(.exhausted, failure.failure_kind.?);
3692     }
3693 }
3694 
3695 test "U0 pass refuses scheduling outside the declared serial job before its callback" {
3696     const revision = @import("../../product/revision/root.zig");
3697     const fixture = @import("../../dialects/fixture/root.zig");
3698     const allocator = std.testing.allocator;
3699     var context = try ir.Context.init(allocator, ir.Context.Limits.testing);
3700     defer context.deinit(allocator);
3701     const module = try fixture.TestDialect.ModuleOp.create(&context, .unknown);
3702     const ledger = try revision.AccountingV1.create(allocator, .{
3703         .allowance = .{ .allocation_capacity = 65536 },
3704         .workspace = 4096,
3705         .events = 8,
3706     }, &.{});
3707     defer ledger.destroy();
3708     var cache = try AnalysisCache.initAccounted(allocator, null, ledger, .{}, 4);
3709     defer cache.deinit();
3710     var manager = PassManager.init(allocator);
3711     defer manager.deinit();
3712     try manager.addPass(.{ .name = "transient", .description = "", .run_fn = runMeteredPass });
3713     metered_pass_runs = 0;
3714     try std.testing.expectEqual(.failure, manager.runWithAnalysisCache(
3715         module.op,
3716         &context,
3717         &cache,
3718         .{ .max_threads = 2 },
3719     ));
3720     try std.testing.expectEqual(0, metered_pass_runs);
3721     try std.testing.expect(ledger.view().missing_work_contract);
3722     try std.testing.expectEqual(.rejected, ledger.view().outcome);
3723 }
3724 
3725 test "U0 pass missing contract records successful and failed physical callbacks without qualification" {
3726     const revision = @import("../../product/revision/root.zig");
3727     const fixture = @import("../../dialects/fixture/root.zig");
3728     const allocator = std.testing.allocator;
3729     var context = try ir.Context.init(allocator, ir.Context.Limits.testing);
3730     defer context.deinit(allocator);
3731     const module = try fixture.TestDialect.ModuleOp.create(&context, .unknown);
3732     const callbacks = [_]*const fn (*PassContext) PassResult{
3733         runMeteredPass, runMeteredAnalysisPass,
3734     };
3735     for (callbacks, 0..) |callback, index| {
3736         const ledger = try revision.AccountingV1.create(allocator, .{
3737             .allowance = .{ .allocation_capacity = 65536 },
3738             .workspace = 4096,
3739             .events = 8,
3740         }, &.{});
3741         defer ledger.destroy();
3742         var cache = try AnalysisCache.initAccounted(allocator, null, ledger, .{}, 4);
3743         defer cache.deinit();
3744         var manager = PassManager.init(allocator);
3745         defer manager.deinit();
3746         try manager.addPass(.{ .name = "uncontracted", .description = "", .run_fn = callback });
3747         const result = manager.runWithAnalysisCache(module.op, &context, &cache, .{});
3748         try std.testing.expectEqual(if (index == 0) PassResult.success else .failure, result);
3749         try std.testing.expect(ledger.view().missing_work_contract);
3750         try std.testing.expectEqual(1, manager.stats.pass_runs);
3751         try std.testing.expectEqual(1, ledger.view().executed.counters.pass_runs);
3752         if (index == 0) {
3753             try std.testing.expectError(error.MissingWorkContract, ledger.producersComplete());
3754         } else {
3755             try std.testing.expectEqual(.exhausted, ledger.view().outcome);
3756             try std.testing.expectError(error.WorkExhausted, ledger.producersComplete());
3757         }
3758     }
3759 }
3760 
3761 fn runSuppressingContextFailure(ctx: *PassContext) PassResult {
3762     const payload: [1024]u8 = @splat('x');
3763     _ = ctx.ir_ctx.getStringAttr(&payload) catch return .success;
3764     return .success;
3765 }
3766 
3767 test "U0 pass caught Context exhaustion stops subsequent callbacks through one finalizer" {
3768     try checkContextPassFailure(false);
3769     try checkContextPassFailure(true);
3770 }
3771 
3772 fn checkContextPassFailure(already_exhausted: bool) !void {
3773     const revision = @import("../../product/revision/root.zig");
3774     const fixture = @import("../../dialects/fixture/root.zig");
3775     const allocator = std.testing.allocator;
3776     var limits = ir.Context.Limits.testing;
3777     limits.attributes.payload_bytes = 128;
3778     var context = try ir.Context.init(allocator, limits);
3779     defer context.deinit(allocator);
3780     const module = try fixture.TestDialect.ModuleOp.create(&context, .unknown);
3781     const identity = revision.record.Version{ .name = "u0-context-pass", .version = 1 };
3782     const ledger = try revision.AccountingV1.create(allocator, .{
3783         .allowance = revision.WorkVector.uniform(65536),
3784         .workspace = 4096,
3785         .events = 8,
3786     }, &.{ identity, identity });
3787     defer ledger.destroy();
3788     var cache = try AnalysisCache.initAccounted(
3789         allocator,
3790         null,
3791         ledger,
3792         .{ .context = &context },
3793         4,
3794     );
3795     defer cache.deinit();
3796     var manager = PassManager.init(allocator);
3797     defer manager.deinit();
3798     const contract = subject.work.Contract{ .identity = identity, .estimate = meteredPassBounds };
3799     try manager.addPass(.{
3800         .name = identity.name,
3801         .description = "caught Context storage failure",
3802         .run_fn = runSuppressingContextFailure,
3803         .work_contract = contract,
3804     });
3805     try manager.addPass(.{
3806         .name = identity.name,
3807         .description = "must not execute after Context exhaustion",
3808         .run_fn = runMeteredPass,
3809         .work_contract = contract,
3810     });
3811     if (already_exhausted) {
3812         var ctx = PassContext.init(module.op, &context, allocator, &cache);
3813         defer ctx.deinit();
3814         _ = runSuppressingContextFailure(&ctx);
3815     }
3816     metered_pass_runs = 0;
3817     const result = manager.runWithAnalysisCache(module.op, &context, &cache, .{});
3818     try std.testing.expectEqual(.attribute_payloads, context.exhaustedSegment().?);
3819     try std.testing.expectEqual(.failure, result);
3820     try std.testing.expectEqual(0, metered_pass_runs);
3821     try std.testing.expectEqual(@intFromBool(!already_exhausted), manager.stats.pass_runs);
3822     try std.testing.expectEqual(.exhausted, ledger.view().outcome);
3823     if (!already_exhausted) {
3824         try std.testing.expectEqual(.exhausted, ledger.view().events[1].outcome);
3825     }
3826     try std.testing.expectEqual(.exhausted, manager.getLastFailureReproducer().?.failure_kind.?);
3827 }
3828 
3829 fn runSuppressingStorageFailure(ctx: *PassContext) PassResult {
3830     const bytes = ctx.analysis_cache.allocator.alloc(u8, 4096) catch return .success;
3831     ctx.analysis_cache.allocator.free(bytes);
3832     return .success;
3833 }
3834 
3835 fn computeSuppressingContextFailure(ctx: *PassContext, op: *ir.Operation) !*anyopaque {
3836     const value = try computeMeteredTyped(ctx, op);
3837     _ = runSuppressingContextFailure(ctx);
3838     return value;
3839 }
3840 
3841 fn refreshSuppressingContextFailure(ctx: *PassContext, _: *ir.Operation, _: *anyopaque) !void {
3842     analysis_counter += 1;
3843     _ = runSuppressingContextFailure(ctx);
3844 }
3845 
3846 const ContextFailureMode = enum { compute, hit, refresh_before, refresh_during };
3847 
3848 test "U0 analysis refuses setup after Context exhaustion without acquiring cache storage" {
3849     const revision = @import("../../product/revision/root.zig");
3850     const allocator = std.testing.allocator;
3851     var limits = ir.Context.Limits.testing;
3852     limits.attributes.payload_bytes = 128;
3853     var context = try ir.Context.init(allocator, limits);
3854     defer context.deinit(allocator);
3855     const payload: [1024]u8 = @splat('x');
3856     try std.testing.expectError(error.OutOfMemory, context.getStringAttr(&payload));
3857     const ledger = try revision.AccountingV1.create(allocator, .{
3858         .allowance = revision.WorkVector.uniform(65536),
3859         .workspace = 4096,
3860         .events = 8,
3861     }, &.{});
3862     defer ledger.destroy();
3863     var failing = std.testing.FailingAllocator.init(allocator, .{ .fail_index = 0 });
3864     const result = AnalysisCache.initAccounted(
3865         failing.allocator(),
3866         null,
3867         ledger,
3868         .{ .context = &context },
3869         4,
3870     );
3871     if (result) |value| {
3872         var cache = value;
3873         cache.deinit();
3874     } else |_| {}
3875     try std.testing.expectError(error.WorkExhausted, result);
3876     try std.testing.expect(!failing.has_induced_failure);
3877     try std.testing.expectEqual(.exhausted, ledger.view().outcome);
3878     try std.testing.expectEqual(0, ledger.view().charged.allocation_capacity);
3879 }
3880 
3881 test "U0 analysis caught Context exhaustion refuses computation results hits and refresh" {
3882     for (std.enums.values(ContextFailureMode)) |mode| try checkContextAnalysisFailure(mode);
3883 }
3884 
3885 fn checkContextAnalysisFailure(mode: ContextFailureMode) !void {
3886     const revision = @import("../../product/revision/root.zig");
3887     const fixture = @import("../../dialects/fixture/root.zig");
3888     const allocator = std.testing.allocator;
3889     var limits = ir.Context.Limits.testing;
3890     limits.attributes.payload_bytes = 128;
3891     var context = try ir.Context.init(allocator, limits);
3892     defer context.deinit(allocator);
3893     const module = try fixture.TestDialect.ModuleOp.create(&context, .unknown);
3894     const ledger = try revision.AccountingV1.create(allocator, .{
3895         .allowance = revision.WorkVector.uniform(65536),
3896         .workspace = 4096,
3897         .events = 8,
3898     }, &.{});
3899     defer ledger.destroy();
3900     var stats: PassManagerStats = .{};
3901     var cache = try AnalysisCache.initAccounted(
3902         allocator,
3903         &stats,
3904         ledger,
3905         .{ .context = &context },
3906         4,
3907     );
3908     defer cache.deinit();
3909     var ctx = PassContext.init(module.op, &context, allocator, &cache);
3910     defer ctx.deinit();
3911     typed_analysis_counter = 0;
3912     typed_analysis_cleanup_count = 0;
3913     analysis_counter = 0;
3914     const descriptor = &MeteredTypedAnalysis.descriptor;
3915     if (mode != .compute) _ = try MeteredTypedAnalysis.get(&ctx, module.op);
3916     if (mode == .hit or mode == .refresh_before) _ = runSuppressingContextFailure(&ctx);
3917     switch (mode) {
3918         .compute, .hit => try std.testing.expectError(error.WorkExhausted, ctx.getAnalysis(
3919             module.op,
3920             descriptor,
3921             computeSuppressingContextFailure,
3922             cleanupMeteredTyped,
3923         )),
3924         .refresh_before, .refresh_during => try std.testing.expectError(
3925             error.WorkExhausted,
3926             ctx.refreshAnalysis(module.op, descriptor, refreshSuppressingContextFailure),
3927         ),
3928     }
3929     try std.testing.expectEqual(.attribute_payloads, context.exhaustedSegment().?);
3930     try std.testing.expectEqual(.exhausted, ledger.view().outcome);
3931     try std.testing.expectEqual(1, typed_analysis_counter);
3932     try std.testing.expectEqual(@intFromBool(mode == .compute), typed_analysis_cleanup_count);
3933     try std.testing.expectEqual(@intFromBool(mode != .compute), cache.entries.count());
3934     try std.testing.expectEqual(@intFromBool(mode == .refresh_during), analysis_counter);
3935     try std.testing.expectEqual(0, stats.analysis_hits);
3936     try std.testing.expectEqual(@intFromBool(mode != .compute), stats.analysis_misses);
3937     try std.testing.expectError(
3938         error.TerminalWorkOutcome,
3939         MeteredTypedAnalysis.get(&ctx, module.op),
3940     );
3941     try std.testing.expectEqual(1, typed_analysis_counter);
3942 }
3943 
3944 test "U0 pass caught workspace exhaustion stops subsequent callbacks through one finalizer" {
3945     const revision = @import("../../product/revision/root.zig");
3946     const fixture = @import("../../dialects/fixture/root.zig");
3947     const fixed = @import("alloc_fixed");
3948     const allocator = std.testing.allocator;
3949     var context = try ir.Context.init(allocator, ir.Context.Limits.testing);
3950     defer context.deinit(allocator);
3951     const module = try fixture.TestDialect.ModuleOp.create(&context, .unknown);
3952     for ([_]bool{ false, true }) |already_exhausted| {
3953         const identity = revision.record.Version{ .name = "u0-storage-pass", .version = 1 };
3954         const ledger = try revision.AccountingV1.create(allocator, .{
3955             .allowance = revision.WorkVector.uniform(65536),
3956             .workspace = 4096,
3957             .events = 8,
3958         }, &.{ identity, identity });
3959         defer ledger.destroy();
3960         var bytes: [4096]u8 align(@alignOf(usize)) = undefined;
3961         var storage = fixed.Tracked.init(&bytes);
3962         var cache = try AnalysisCache.initAccounted(
3963             storage.allocator(),
3964             null,
3965             ledger,
3966             .{ .workspace = &storage.exhausted },
3967             4,
3968         );
3969         defer cache.deinit();
3970         var manager = PassManager.init(allocator);
3971         defer manager.deinit();
3972         const contract = subject.work.Contract{ .identity = identity, .estimate = meteredPassBounds };
3973         try manager.addPass(.{
3974             .name = identity.name,
3975             .description = "caught fixed storage failure",
3976             .run_fn = runSuppressingStorageFailure,
3977             .work_contract = contract,
3978         });
3979         try manager.addPass(.{
3980             .name = identity.name,
3981             .description = "must not execute after exhaustion",
3982             .run_fn = runMeteredPass,
3983             .work_contract = contract,
3984         });
3985         if (already_exhausted) {
3986             try std.testing.expectError(error.OutOfMemory, storage.allocator().alloc(u8, 4096));
3987         }
3988         metered_pass_runs = 0;
3989         try std.testing.expectEqual(
3990             .failure,
3991             manager.runWithAnalysisCache(module.op, &context, &cache, .{}),
3992         );
3993         try std.testing.expect(storage.exhausted);
3994         try std.testing.expectEqual(0, metered_pass_runs);
3995         try std.testing.expectEqual(@intFromBool(!already_exhausted), manager.stats.pass_runs);
3996         try std.testing.expectEqual(1, manager.stats.pass_failures);
3997         try std.testing.expectEqual(.exhausted, ledger.view().outcome);
3998         try std.testing.expectEqual(.exhausted, manager.getLastFailureReproducer().?.failure_kind.?);
3999         if (!already_exhausted) {
4000             try std.testing.expectEqual(.exhausted, ledger.view().events[1].outcome);
4001         }
4002     }
4003 }
4004 
4005 fn computeSuppressingStorageFailure(ctx: *PassContext, op: *ir.Operation) !*anyopaque {
4006     const value = try computeMeteredTyped(ctx, op);
4007     _ = runSuppressingStorageFailure(ctx);
4008     return value;
4009 }
4010 
4011 fn refreshSuppressingStorageFailure(ctx: *PassContext, _: *ir.Operation, _: *anyopaque) !void {
4012     analysis_counter += 1;
4013     _ = runSuppressingStorageFailure(ctx);
4014 }
4015 
4016 test "U0 analysis caught workspace exhaustion refuses computation results hits and refresh" {
4017     const revision = @import("../../product/revision/root.zig");
4018     const fixture = @import("../../dialects/fixture/root.zig");
4019     const fixed = @import("alloc_fixed");
4020     const allocator = std.testing.allocator;
4021     var context = try ir.Context.init(allocator, ir.Context.Limits.testing);
4022     defer context.deinit(allocator);
4023     const module = try fixture.TestDialect.ModuleOp.create(&context, .unknown);
4024     const Mode = enum { compute, hit, refresh_before, refresh_during };
4025     for (std.enums.values(Mode)) |mode| {
4026         const ledger = try revision.AccountingV1.create(allocator, .{
4027             .allowance = revision.WorkVector.uniform(65536),
4028             .workspace = 4096,
4029             .events = 8,
4030         }, &.{});
4031         defer ledger.destroy();
4032         var bytes: [4096]u8 align(@alignOf(usize)) = undefined;
4033         var storage = fixed.Tracked.init(&bytes);
4034         var stats: PassManagerStats = .{};
4035         var cache = try AnalysisCache.initAccounted(
4036             storage.allocator(),
4037             &stats,
4038             ledger,
4039             .{ .workspace = &storage.exhausted },
4040             4,
4041         );
4042         defer cache.deinit();
4043         var ctx = PassContext.init(module.op, &context, storage.allocator(), &cache);
4044         defer ctx.deinit();
4045         typed_analysis_counter = 0;
4046         typed_analysis_cleanup_count = 0;
4047         analysis_counter = 0;
4048         const descriptor = &MeteredTypedAnalysis.descriptor;
4049         if (mode != .compute) _ = try MeteredTypedAnalysis.get(&ctx, module.op);
4050         if (mode == .hit or mode == .refresh_before) _ = runSuppressingStorageFailure(&ctx);
4051         switch (mode) {
4052             .compute, .hit => try std.testing.expectError(error.WorkExhausted, ctx.getAnalysis(
4053                 module.op,
4054                 descriptor,
4055                 computeSuppressingStorageFailure,
4056                 cleanupMeteredTyped,
4057             )),
4058             .refresh_before, .refresh_during => try std.testing.expectError(
4059                 error.WorkExhausted,
4060                 ctx.refreshAnalysis(module.op, descriptor, refreshSuppressingStorageFailure),
4061             ),
4062         }
4063         try std.testing.expect(storage.exhausted);
4064         try std.testing.expectEqual(.exhausted, ledger.view().outcome);
4065         try std.testing.expectEqual(1, typed_analysis_counter);
4066         try std.testing.expectEqual(@intFromBool(mode == .compute), typed_analysis_cleanup_count);
4067         try std.testing.expectEqual(@intFromBool(mode != .compute), cache.entries.count());
4068         try std.testing.expectEqual(@intFromBool(mode == .refresh_during), analysis_counter);
4069         try std.testing.expectEqual(0, stats.analysis_hits);
4070         try std.testing.expectEqual(@intFromBool(mode != .compute), stats.analysis_misses);
4071         try std.testing.expectError(
4072             error.TerminalWorkOutcome,
4073             MeteredTypedAnalysis.get(&ctx, module.op),
4074         );
4075         try std.testing.expectEqual(1, typed_analysis_counter);
4076     }
4077 }
4078 
4079 test "U0 analysis host OOM before workspace allocation stays rejected" {
4080     const revision = @import("../../product/revision/root.zig");
4081     const fixed = @import("alloc_fixed");
4082     const allocator = std.testing.allocator;
4083     const ledger = try revision.AccountingV1.create(allocator, .{
4084         .allowance = .{ .allocation_capacity = 65536 },
4085         .workspace = 4096,
4086         .events = 8,
4087     }, &.{});
4088     defer ledger.destroy();
4089     var bytes: [4096]u8 align(@alignOf(usize)) = undefined;
4090     var storage = fixed.Tracked.init(&bytes);
4091     var failing = std.testing.FailingAllocator.init(storage.allocator(), .{ .fail_index = 0 });
4092     const result = AnalysisCache.initAccounted(
4093         failing.allocator(),
4094         null,
4095         ledger,
4096         .{ .workspace = &storage.exhausted },
4097         4,
4098     );
4099     if (result) |value| {
4100         var cache = value;
4101         cache.deinit();
4102     } else |_| {}
4103     try std.testing.expect(failing.has_induced_failure);
4104     try std.testing.expect(!storage.exhausted);
4105     try std.testing.expectError(error.OutOfMemory, result);
4106     try std.testing.expectEqual(.rejected, ledger.view().outcome);
4107     try std.testing.expect(ledger.view().charged.allocation_capacity > 0);
4108     try std.testing.expectEqual(0, ledger.view().executed.work.analysis_computations);
4109 }
4110 
4111 fn allocateWorkerByte(ctx: *PassContext) !void {
4112     analysis_counter += 1;
4113     const allocator = ctx.workerAllocator();
4114     const bytes = try allocator.alloc(u8, 1);
4115     defer allocator.free(bytes);
4116 }
4117 
4118 fn computeWithWorkerAllocation(ctx: *PassContext, op: *ir.Operation) !*anyopaque {
4119     try allocateWorkerByte(ctx);
4120     return computeMeteredTyped(ctx, op);
4121 }
4122 
4123 fn refreshWithWorkerAllocation(ctx: *PassContext, _: *ir.Operation, value: *anyopaque) !void {
4124     try allocateWorkerByte(ctx);
4125     const typed: *u32 = @ptrCast(@alignCast(value));
4126     typed.* += 1;
4127 }
4128 
4129 test "U0 analysis separate worker host OOM rejects computation without retry" {
4130     try checkWorkerAllocationFailure(false, false);
4131 }
4132 
4133 test "U0 analysis separate worker host OOM rejects refresh without retry" {
4134     try checkWorkerAllocationFailure(true, false);
4135 }
4136 
4137 test "U0 analysis caught workspace exhaustion dominates a later compute error" {
4138     try checkWorkerAllocationFailure(false, true);
4139 }
4140 
4141 test "U0 analysis caught workspace exhaustion dominates a later refresh error" {
4142     try checkWorkerAllocationFailure(true, true);
4143 }
4144 
4145 fn computeAfterWorkspaceExhaustion(ctx: *PassContext, _: *ir.Operation) !*anyopaque {
4146     analysis_counter += 1;
4147     _ = runSuppressingStorageFailure(ctx);
4148     return error.UnsupportedOperation;
4149 }
4150 
4151 fn refreshAfterWorkspaceExhaustion(ctx: *PassContext, _: *ir.Operation, _: *anyopaque) !void {
4152     analysis_counter += 1;
4153     _ = runSuppressingStorageFailure(ctx);
4154     return error.UnsupportedOperation;
4155 }
4156 
4157 fn checkWorkerAllocationFailure(refresh: bool, exhaust_workspace: bool) !void {
4158     const revision = @import("../../product/revision/root.zig");
4159     const fixture = @import("../../dialects/fixture/root.zig");
4160     const fixed = @import("alloc_fixed");
4161     const allocator = std.testing.allocator;
4162     var context = try ir.Context.init(allocator, ir.Context.Limits.testing);
4163     defer context.deinit(allocator);
4164     const module = try fixture.TestDialect.ModuleOp.create(&context, .unknown);
4165     const ledger = try revision.AccountingV1.create(allocator, .{
4166         .allowance = revision.WorkVector.uniform(65536),
4167         .workspace = 4096,
4168         .events = 8,
4169     }, &.{});
4170     defer ledger.destroy();
4171     var bytes: [4096]u8 align(@alignOf(usize)) = undefined;
4172     var storage = fixed.Tracked.init(&bytes);
4173     var stats: PassManagerStats = .{};
4174     var cache = try AnalysisCache.initAccounted(
4175         storage.allocator(),
4176         &stats,
4177         ledger,
4178         .{ .workspace = &storage.exhausted },
4179         4,
4180     );
4181     defer cache.deinit();
4182     var failing = std.testing.FailingAllocator.init(allocator, .{ .fail_index = 0 });
4183     var ctx = PassContext.initWithOptions(module.op, &context, storage.allocator(), &cache, .{
4184         .worker_allocator = failing.allocator(),
4185     });
4186     defer ctx.deinit();
4187     typed_analysis_counter = 0;
4188     analysis_counter = 0;
4189     const value = if (refresh) try MeteredTypedAnalysis.get(&ctx, module.op) else null;
4190     const descriptor = &MeteredTypedAnalysis.descriptor;
4191     const expected = if (exhaust_workspace) error.WorkExhausted else error.OutOfMemory;
4192     if (refresh) {
4193         try std.testing.expectError(expected, ctx.refreshAnalysis(
4194             module.op,
4195             descriptor,
4196             if (exhaust_workspace) refreshAfterWorkspaceExhaustion else refreshWithWorkerAllocation,
4197         ));
4198         try std.testing.expectEqual(@as(u32, 1), value.?.*);
4199     } else {
4200         try std.testing.expectError(expected, ctx.getAnalysis(
4201             module.op,
4202             descriptor,
4203             if (exhaust_workspace) computeAfterWorkspaceExhaustion else computeWithWorkerAllocation,
4204             cleanupMeteredTyped,
4205         ));
4206     }
4207     try std.testing.expectEqual(!exhaust_workspace, failing.has_induced_failure);
4208     try std.testing.expectEqual(exhaust_workspace, storage.exhausted);
4209     try std.testing.expectEqual(
4210         if (exhaust_workspace) @as(@TypeOf(ledger.view().outcome), .exhausted) else .rejected,
4211         ledger.view().outcome,
4212     );
4213     try std.testing.expectEqual(@intFromBool(refresh), cache.entries.count());
4214     try std.testing.expectEqual(@intFromBool(refresh), stats.analysis_misses);
4215     try std.testing.expectEqual(
4216         1 + @as(u64, @intFromBool(refresh)),
4217         ledger.view().charged.analysis_computations,
4218     );
4219     try std.testing.expectError(
4220         error.TerminalWorkOutcome,
4221         MeteredTypedAnalysis.get(&ctx, module.op),
4222     );
4223     try std.testing.expectEqual(1, analysis_counter);
4224 }