lib/choir/src/passes/instrumentation.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const pretty = @import("pretty");
3 const sys = @import("sys");
4 const ir = @import("../core/root.zig");
5 const pass_mod = @import("pass/root.zig");
6 const verify_mod = @import("../core/root.zig").verify;
7 const hashing = @import("../root.zig").product.hashing;
8
9 fn nowNanos() i128 {
10 return sys.time.nanoTimestamp();
11 }
12
13 pub const PassInfo = struct {
14 name: []const u8,
15 description: []const u8,
16 target_op: ?*ir.Operation,
17 mutation_scope: pass_mod.PassMutationScope = .whole_module,
18 };
19
20 pub const PipelineInfo = struct {
21 target_op_name: ?[]const u8,
22 depth: usize,
23 };
24
25 pub const AnalysisInfo = struct {
26 id: pass_mod.AnalysisId,
27 name: []const u8,
28 target_op: *ir.Operation,
29 };
30
31 pub const PassStatisticInfo = struct {
32 pass: PassInfo,
33 name: []const u8,
34 description: []const u8,
35 value: u64,
36 };
37
38 const PassStatisticsEntry = struct {
39 pass_name: []u8,
40 name: []u8,
41 description: []u8,
42 value: u64,
43 };
44
45 pub const PassStatisticSummary = struct {
46 pass_name: []const u8,
47 name: []const u8,
48 description: []const u8,
49 value: u64,
50 };
51
52 pub const PassInstrumentation = struct {
53 ctx: ?*anyopaque = null,
54
55 runBeforePipeline: ?*const fn (ctx: ?*anyopaque, info: PipelineInfo, op: *ir.Operation) void = null,
56
57 runAfterPipeline: ?*const fn (ctx: ?*anyopaque, info: PipelineInfo, op: *ir.Operation, failed: bool) void = null,
58
59 runBeforePass: ?*const fn (ctx: ?*anyopaque, info: PassInfo) void = null,
60
61 runAfterPass: ?*const fn (ctx: ?*anyopaque, info: PassInfo, modified: bool) void = null,
62
63 runAfterPassFailed: ?*const fn (ctx: ?*anyopaque, info: PassInfo) void = null,
64
65 runBeforeAnalysis: ?*const fn (ctx: ?*anyopaque, info: AnalysisInfo) void = null,
66
67 runAfterAnalysis: ?*const fn (ctx: ?*anyopaque, info: AnalysisInfo) void = null,
68
69 runPassStatistic: ?*const fn (ctx: ?*anyopaque, info: PassStatisticInfo) void = null,
70
71 pub fn beforePipeline(self: *const PassInstrumentation, info: PipelineInfo, op: *ir.Operation) void {
72 if (self.runBeforePipeline) |hook| {
73 hook(self.ctx, info, op);
74 }
75 }
76
77 pub fn afterPipeline(self: *const PassInstrumentation, info: PipelineInfo, op: *ir.Operation, failed: bool) void {
78 if (self.runAfterPipeline) |hook| {
79 hook(self.ctx, info, op, failed);
80 }
81 }
82
83 pub fn beforePass(self: *const PassInstrumentation, info: PassInfo) void {
84 if (self.runBeforePass) |hook| {
85 hook(self.ctx, info);
86 }
87 }
88
89 pub fn afterPass(self: *const PassInstrumentation, info: PassInfo, modified: bool) void {
90 if (self.runAfterPass) |hook| {
91 hook(self.ctx, info, modified);
92 }
93 }
94
95 pub fn afterPassFailed(self: *const PassInstrumentation, info: PassInfo) void {
96 if (self.runAfterPassFailed) |hook| {
97 hook(self.ctx, info);
98 }
99 }
100
101 pub fn beforeAnalysis(self: *const PassInstrumentation, info: AnalysisInfo) void {
102 if (self.runBeforeAnalysis) |hook| {
103 hook(self.ctx, info);
104 }
105 }
106
107 pub fn afterAnalysis(self: *const PassInstrumentation, info: AnalysisInfo) void {
108 if (self.runAfterAnalysis) |hook| {
109 hook(self.ctx, info);
110 }
111 }
112
113 pub fn passStatistic(self: *const PassInstrumentation, info: PassStatisticInfo) void {
114 if (self.runPassStatistic) |hook| {
115 hook(self.ctx, info);
116 }
117 }
118 };
119
120 pub const PassInstrumentor = struct {
121 allocator: std.mem.Allocator,
122 instrumentations: std.ArrayListUnmanaged(PassInstrumentation),
123
124 pub fn init(allocator: std.mem.Allocator) PassInstrumentor {
125 return .{
126 .allocator = allocator,
127 .instrumentations = .empty,
128 };
129 }
130
131 pub fn deinit(self: *PassInstrumentor) void {
132 self.instrumentations.deinit(self.allocator);
133 }
134
135 pub fn addInstrumentation(self: *PassInstrumentor, inst: PassInstrumentation) !void {
136 try self.instrumentations.append(self.allocator, inst);
137 }
138
139 pub fn clear(self: *PassInstrumentor) void {
140 self.instrumentations.clearRetainingCapacity();
141 }
142
143 pub fn runBeforePipeline(self: *const PassInstrumentor, info: PipelineInfo, op: *ir.Operation) void {
144 for (self.instrumentations.items) |*inst| {
145 inst.beforePipeline(info, op);
146 }
147 }
148
149 pub fn runAfterPipeline(self: *const PassInstrumentor, info: PipelineInfo, op: *ir.Operation, failed: bool) void {
150 var i = self.instrumentations.items.len;
151 while (i > 0) {
152 i -= 1;
153 self.instrumentations.items[i].afterPipeline(info, op, failed);
154 }
155 }
156
157 pub fn runBeforePass(self: *const PassInstrumentor, info: PassInfo) void {
158 for (self.instrumentations.items) |*inst| {
159 inst.beforePass(info);
160 }
161 }
162
163 pub fn runAfterPass(self: *const PassInstrumentor, info: PassInfo, modified: bool) void {
164 var i = self.instrumentations.items.len;
165 while (i > 0) {
166 i -= 1;
167 self.instrumentations.items[i].afterPass(info, modified);
168 }
169 }
170
171 pub fn runAfterPassFailed(self: *const PassInstrumentor, info: PassInfo) void {
172 var i = self.instrumentations.items.len;
173 while (i > 0) {
174 i -= 1;
175 self.instrumentations.items[i].afterPassFailed(info);
176 }
177 }
178
179 pub fn runBeforeAnalysis(self: *const PassInstrumentor, info: AnalysisInfo) void {
180 for (self.instrumentations.items) |*inst| {
181 inst.beforeAnalysis(info);
182 }
183 }
184
185 pub fn runAfterAnalysis(self: *const PassInstrumentor, info: AnalysisInfo) void {
186 var i = self.instrumentations.items.len;
187 while (i > 0) {
188 i -= 1;
189 self.instrumentations.items[i].afterAnalysis(info);
190 }
191 }
192
193 pub fn runPassStatistic(self: *const PassInstrumentor, info: PassStatisticInfo) void {
194 for (self.instrumentations.items) |*inst| {
195 inst.passStatistic(info);
196 }
197 }
198 };
199
200 pub const PassStatisticsInstrumentation = struct {
201 allocator: std.mem.Allocator,
202 entries: std.ArrayListUnmanaged(PassStatisticsEntry),
203
204 pub fn init(allocator: std.mem.Allocator) PassStatisticsInstrumentation {
205 return .{
206 .allocator = allocator,
207 .entries = .empty,
208 };
209 }
210
211 pub fn deinit(self: *PassStatisticsInstrumentation) void {
212 self.reset();
213 self.entries.deinit(self.allocator);
214 }
215
216 pub fn instrumentation(self: *PassStatisticsInstrumentation) PassInstrumentation {
217 return .{
218 .ctx = self,
219 .runPassStatistic = recordImpl,
220 };
221 }
222
223 pub fn reset(self: *PassStatisticsInstrumentation) void {
224 for (self.entries.items) |entry| {
225 self.allocator.free(entry.pass_name);
226 self.allocator.free(entry.name);
227 self.allocator.free(entry.description);
228 }
229 self.entries.clearRetainingCapacity();
230 }
231
232 pub fn record(self: *PassStatisticsInstrumentation, info: PassStatisticInfo) !void {
233 if (self.findEntry(info.pass.name, info.name)) |entry| {
234 entry.value +|= info.value;
235 return;
236 }
237
238 const pass_name = try self.allocator.dupe(u8, info.pass.name);
239 errdefer self.allocator.free(pass_name);
240 const name = try self.allocator.dupe(u8, info.name);
241 errdefer self.allocator.free(name);
242 const description = try self.allocator.dupe(u8, info.description);
243 errdefer self.allocator.free(description);
244
245 try self.entries.append(self.allocator, .{
246 .pass_name = pass_name,
247 .name = name,
248 .description = description,
249 .value = info.value,
250 });
251 }
252
253 pub fn get(self: *const PassStatisticsInstrumentation, pass_name: []const u8, name: []const u8) ?u64 {
254 if (self.findEntry(pass_name, name)) |entry| return entry.value;
255 return null;
256 }
257
258 pub fn summariesAlloc(
259 self: *const PassStatisticsInstrumentation,
260 allocator: std.mem.Allocator,
261 ) ![]PassStatisticSummary {
262 var summaries = try allocator.alloc(PassStatisticSummary, self.entries.items.len);
263 errdefer allocator.free(summaries);
264
265 for (self.entries.items, 0..) |entry, index| {
266 summaries[index] = .{
267 .pass_name = entry.pass_name,
268 .name = entry.name,
269 .description = entry.description,
270 .value = entry.value,
271 };
272 }
273 std.mem.sort(PassStatisticSummary, summaries, {}, summaryLessThan);
274 return summaries;
275 }
276
277 fn findEntry(self: anytype, pass_name: []const u8, name: []const u8) ?@TypeOf(&self.entries.items[0]) {
278 for (self.entries.items) |*entry| {
279 if (std.mem.eql(u8, entry.pass_name, pass_name) and std.mem.eql(u8, entry.name, name)) {
280 return entry;
281 }
282 }
283 return null;
284 }
285
286 fn recordImpl(ctx: ?*anyopaque, info: PassStatisticInfo) void {
287 const self: *PassStatisticsInstrumentation = @ptrCast(@alignCast(ctx.?));
288 self.record(info) catch {};
289 }
290
291 fn summaryLessThan(_: void, lhs: PassStatisticSummary, rhs: PassStatisticSummary) bool {
292 const pass_order = std.mem.order(u8, lhs.pass_name, rhs.pass_name);
293 if (pass_order != .eq) return pass_order == .lt;
294 return std.mem.lessThan(u8, lhs.name, rhs.name);
295 }
296 };
297
298 pub const PassTimingOptions = struct {
299 collect_pass_ir_sizes: bool = false,
300 allocation_snapshot_provider: ?PassAllocationSnapshotProvider = null,
301 };
302
303 pub const PassAllocationSnapshot = struct {
304 alloc_count: u64 = 0,
305 free_count: u64 = 0,
306 alloc_bytes: u64 = 0,
307
308 fn delta(after: PassAllocationSnapshot, before: PassAllocationSnapshot) PassAllocationSnapshot {
309 return .{
310 .alloc_count = after.alloc_count -| before.alloc_count,
311 .free_count = after.free_count -| before.free_count,
312 .alloc_bytes = after.alloc_bytes -| before.alloc_bytes,
313 };
314 }
315 };
316
317 pub const PassAllocationSnapshotProvider = struct {
318 context: ?*const anyopaque = null,
319 snapshot: *const fn (?*const anyopaque) PassAllocationSnapshot,
320
321 fn read(self: PassAllocationSnapshotProvider) PassAllocationSnapshot {
322 return self.snapshot(self.context);
323 }
324 };
325
326 const PassTimingEntry = struct {
327 total_ns: i128 = 0,
328 count: u64 = 0,
329 modified_count: u64 = 0,
330 max_ns: i128 = 0,
331 min_ns: i128 = std.math.maxInt(i128),
332 };
333
334 const PassIrSizeEntry = struct {
335 count: u64 = 0,
336 before_total: u64 = 0,
337 after_total: u64 = 0,
338 delta_total: i128 = 0,
339 };
340
341 const PassMemoryEntry = struct {
342 count: u64 = 0,
343 alloc_count: u64 = 0,
344 free_count: u64 = 0,
345 alloc_bytes: u64 = 0,
346 };
347
348 pub const PipelineTimingSummary = struct {
349 target: ?[]const u8,
350 depth: usize,
351 total_ns: i128,
352 };
353
354 pub const PassRunTimingSummary = struct {
355 ordinal: u64,
356 name: []const u8,
357 elapsed_ns: i128,
358 modified: bool,
359 op_count_before: ?u64 = null,
360 op_count_after: ?u64 = null,
361 op_count_delta: ?i128 = null,
362 alloc_count: ?u64 = null,
363 free_count: ?u64 = null,
364 alloc_bytes: ?u64 = null,
365 };
366
367 pub const PassTimingSummary = struct {
368 name: []const u8,
369 total_ns: i128,
370 count: u64,
371 modified_count: u64 = 0,
372 max_ns: i128,
373 min_ns: i128,
374 op_count_before: ?u64 = null,
375 op_count_after: ?u64 = null,
376 op_count_delta: ?i128 = null,
377 alloc_count: ?u64 = null,
378 free_count: ?u64 = null,
379 alloc_bytes: ?u64 = null,
380 };
381
382 pub const TimingInstrumentation = struct {
383 allocator: std.mem.Allocator,
384 options: PassTimingOptions,
385 pass_times: std.StringHashMapUnmanaged(PassTimingEntry),
386 analysis_times: std.StringHashMapUnmanaged(PassTimingEntry),
387 pass_ir_sizes: std.StringHashMapUnmanaged(PassIrSizeEntry),
388 pass_memory: std.StringHashMapUnmanaged(PassMemoryEntry),
389 pass_runs: std.ArrayListUnmanaged(PassRunTimingSummary),
390 pipeline_times: std.ArrayListUnmanaged(PipelineTimingSummary),
391 timing_stack: std.ArrayListUnmanaged(i128),
392 pass_ir_size_stack: std.ArrayListUnmanaged(u64),
393 pass_memory_stack: std.ArrayListUnmanaged(PassAllocationSnapshot),
394
395 const FAILED_PUSH_SENTINEL: i128 = std.math.minInt(i128);
396 const FAILED_IR_SIZE_SENTINEL: u64 = std.math.maxInt(u64);
397 const FAILED_MEMORY_SNAPSHOT = PassAllocationSnapshot{
398 .alloc_count = std.math.maxInt(u64),
399 .free_count = std.math.maxInt(u64),
400 .alloc_bytes = std.math.maxInt(u64),
401 };
402
403 pub fn init(allocator: std.mem.Allocator) TimingInstrumentation {
404 return initWithOptions(allocator, .{});
405 }
406
407 pub fn initWithOptions(
408 allocator: std.mem.Allocator,
409 options: PassTimingOptions,
410 ) TimingInstrumentation {
411 return .{
412 .allocator = allocator,
413 .options = options,
414 .pass_times = .{},
415 .analysis_times = .{},
416 .pass_ir_sizes = .{},
417 .pass_memory = .{},
418 .pass_runs = .empty,
419 .pipeline_times = .empty,
420 .timing_stack = .empty,
421 .pass_ir_size_stack = .empty,
422 .pass_memory_stack = .empty,
423 };
424 }
425
426 pub fn deinit(self: *TimingInstrumentation) void {
427 self.pass_times.deinit(self.allocator);
428 self.analysis_times.deinit(self.allocator);
429 self.pass_ir_sizes.deinit(self.allocator);
430 self.pass_memory.deinit(self.allocator);
431 self.pass_runs.deinit(self.allocator);
432 self.pipeline_times.deinit(self.allocator);
433 self.timing_stack.deinit(self.allocator);
434 self.pass_ir_size_stack.deinit(self.allocator);
435 self.pass_memory_stack.deinit(self.allocator);
436 }
437
438 pub fn setAllocationSnapshotProvider(
439 self: *TimingInstrumentation,
440 provider: ?PassAllocationSnapshotProvider,
441 ) void {
442 self.options.allocation_snapshot_provider = provider;
443 }
444
445 pub fn instrumentation(self: *TimingInstrumentation) PassInstrumentation {
446 return .{
447 .ctx = self,
448 .runBeforePipeline = beforePipelineImpl,
449 .runAfterPipeline = afterPipelineImpl,
450 .runBeforePass = beforePassImpl,
451 .runAfterPass = afterPassImpl,
452 .runAfterPassFailed = afterPassFailedImpl,
453 .runBeforeAnalysis = beforeAnalysisImpl,
454 .runAfterAnalysis = afterAnalysisImpl,
455 };
456 }
457
458 fn pushTimestamp(self: *TimingInstrumentation) void {
459 const timestamp = nowNanos();
460 self.timing_stack.append(self.allocator, timestamp) catch {
461 self.timing_stack.append(self.allocator, FAILED_PUSH_SENTINEL) catch {};
462 };
463 }
464
465 fn popTimestamp(self: *TimingInstrumentation) ?i128 {
466 if (self.timing_stack.pop()) |value| {
467 if (value == FAILED_PUSH_SENTINEL) {
468 return null;
469 }
470 return value;
471 }
472 return null;
473 }
474
475 fn beforePipelineImpl(ctx: ?*anyopaque, _: PipelineInfo, _: *ir.Operation) void {
476 const self: *TimingInstrumentation = @ptrCast(@alignCast(ctx.?));
477 self.pushTimestamp();
478 }
479
480 fn afterPipelineImpl(ctx: ?*anyopaque, info: PipelineInfo, _: *ir.Operation, _: bool) void {
481 const self: *TimingInstrumentation = @ptrCast(@alignCast(ctx.?));
482 if (self.popTimestamp()) |start| {
483 const elapsed = nowNanos() - start;
484 self.recordPipelineTiming(info.target_op_name, info.depth, elapsed);
485 }
486 }
487
488 fn beforePassImpl(ctx: ?*anyopaque, info: PassInfo) void {
489 const self: *TimingInstrumentation = @ptrCast(@alignCast(ctx.?));
490 self.pushTimestamp();
491 if (self.options.collect_pass_ir_sizes) {
492 self.pushPassIrSize(info);
493 }
494 if (self.options.allocation_snapshot_provider != null) {
495 self.pushPassMemory();
496 }
497 }
498
499 fn afterPassImpl(ctx: ?*anyopaque, info: PassInfo, modified: bool) void {
500 recordPassTime(ctx, info, modified);
501 }
502
503 fn afterPassFailedImpl(ctx: ?*anyopaque, info: PassInfo) void {
504 recordPassTime(ctx, info, false);
505 }
506
507 fn recordPassTime(ctx: ?*anyopaque, info: PassInfo, modified: bool) void {
508 const self: *TimingInstrumentation = @ptrCast(@alignCast(ctx.?));
509 const elapsed = if (self.popTimestamp()) |start| nowNanos() - start else null;
510 const before = if (self.options.collect_pass_ir_sizes) self.popPassIrSize() else null;
511 const before_memory = if (self.options.allocation_snapshot_provider != null) self.popPassMemory() else null;
512 var after: ?u64 = null;
513 var delta: ?i128 = null;
514 var memory_delta: ?PassAllocationSnapshot = null;
515 if (before_memory) |snapshot_before| {
516 if (self.options.allocation_snapshot_provider) |provider| {
517 memory_delta = PassAllocationSnapshot.delta(provider.read(), snapshot_before);
518 self.recordPassMemoryValues(info.name, memory_delta.?);
519 }
520 }
521 if (before) |before_count| {
522 if (info.target_op) |op| {
523 const after_count = countOperationTree(op);
524 after = after_count;
525 delta = @as(i128, @intCast(after_count)) - @as(i128, @intCast(before_count));
526 self.recordPassIrSizeValues(info.name, before_count, after_count, delta.?);
527 }
528 }
529 if (elapsed) |actual| {
530 self.record(&self.pass_times, info.name, actual, modified);
531 self.recordPassRun(info.name, actual, modified, before, after, delta, memory_delta);
532 }
533 }
534
535 fn recordPassRun(
536 self: *TimingInstrumentation,
537 name: []const u8,
538 elapsed_ns: i128,
539 modified: bool,
540 op_count_before: ?u64,
541 op_count_after: ?u64,
542 op_count_delta: ?i128,
543 memory_delta: ?PassAllocationSnapshot,
544 ) void {
545 self.pass_runs.append(self.allocator, .{
546 .ordinal = @intCast(self.pass_runs.items.len + 1),
547 .name = name,
548 .elapsed_ns = elapsed_ns,
549 .modified = modified,
550 .op_count_before = op_count_before,
551 .op_count_after = op_count_after,
552 .op_count_delta = op_count_delta,
553 .alloc_count = if (memory_delta) |delta| delta.alloc_count else null,
554 .free_count = if (memory_delta) |delta| delta.free_count else null,
555 .alloc_bytes = if (memory_delta) |delta| delta.alloc_bytes else null,
556 }) catch {};
557 }
558
559 fn recordPassIrSizeValues(
560 self: *TimingInstrumentation,
561 name: []const u8,
562 before: u64,
563 after: u64,
564 delta: i128,
565 ) void {
566 const gop = self.pass_ir_sizes.getOrPut(self.allocator, name) catch return;
567 if (!gop.found_existing) {
568 gop.value_ptr.* = .{};
569 }
570 gop.value_ptr.count += 1;
571 gop.value_ptr.before_total +|= before;
572 gop.value_ptr.after_total +|= after;
573 gop.value_ptr.delta_total += delta;
574 }
575
576 fn recordPassMemoryValues(
577 self: *TimingInstrumentation,
578 name: []const u8,
579 memory_delta: PassAllocationSnapshot,
580 ) void {
581 const gop = self.pass_memory.getOrPut(self.allocator, name) catch return;
582 if (!gop.found_existing) {
583 gop.value_ptr.* = .{};
584 }
585 gop.value_ptr.count += 1;
586 gop.value_ptr.alloc_count +|= memory_delta.alloc_count;
587 gop.value_ptr.free_count +|= memory_delta.free_count;
588 gop.value_ptr.alloc_bytes +|= memory_delta.alloc_bytes;
589 }
590
591 fn beforeAnalysisImpl(ctx: ?*anyopaque, _: AnalysisInfo) void {
592 const self: *TimingInstrumentation = @ptrCast(@alignCast(ctx.?));
593 self.pushTimestamp();
594 }
595
596 fn afterAnalysisImpl(ctx: ?*anyopaque, info: AnalysisInfo) void {
597 const self: *TimingInstrumentation = @ptrCast(@alignCast(ctx.?));
598 if (self.popTimestamp()) |start| {
599 const elapsed = nowNanos() - start;
600 self.record(&self.analysis_times, info.name, elapsed, false);
601 }
602 }
603
604 fn record(
605 self: *TimingInstrumentation,
606 table: *std.StringHashMapUnmanaged(PassTimingEntry),
607 name: []const u8,
608 elapsed: i128,
609 modified: bool,
610 ) void {
611 const gop = table.getOrPut(self.allocator, name) catch return;
612 if (!gop.found_existing) {
613 gop.value_ptr.* = .{};
614 }
615 gop.value_ptr.total_ns += elapsed;
616 gop.value_ptr.count += 1;
617 if (modified) gop.value_ptr.modified_count += 1;
618 if (elapsed > gop.value_ptr.max_ns) gop.value_ptr.max_ns = elapsed;
619 if (elapsed < gop.value_ptr.min_ns) gop.value_ptr.min_ns = elapsed;
620 }
621
622 pub fn recordPipelineTiming(
623 self: *TimingInstrumentation,
624 target: ?[]const u8,
625 depth: usize,
626 elapsed_ns: i128,
627 ) void {
628 self.pipeline_times.append(self.allocator, .{
629 .target = target,
630 .depth = depth,
631 .total_ns = elapsed_ns,
632 }) catch {};
633 }
634
635 fn pushPassIrSize(self: *TimingInstrumentation, info: PassInfo) void {
636 const size = if (info.target_op) |op| countOperationTree(op) else FAILED_IR_SIZE_SENTINEL;
637 self.pass_ir_size_stack.append(self.allocator, size) catch {
638 self.pass_ir_size_stack.append(self.allocator, FAILED_IR_SIZE_SENTINEL) catch {};
639 };
640 }
641
642 fn popPassIrSize(self: *TimingInstrumentation) ?u64 {
643 const size = self.pass_ir_size_stack.pop() orelse return null;
644 if (size == FAILED_IR_SIZE_SENTINEL) return null;
645 return size;
646 }
647
648 fn pushPassMemory(self: *TimingInstrumentation) void {
649 const provider = self.options.allocation_snapshot_provider orelse return;
650 self.pass_memory_stack.append(self.allocator, provider.read()) catch {
651 self.pass_memory_stack.append(self.allocator, FAILED_MEMORY_SNAPSHOT) catch {};
652 };
653 }
654
655 fn popPassMemory(self: *TimingInstrumentation) ?PassAllocationSnapshot {
656 const snapshot = self.pass_memory_stack.pop() orelse return null;
657 if (snapshot.alloc_count == FAILED_MEMORY_SNAPSHOT.alloc_count and
658 snapshot.free_count == FAILED_MEMORY_SNAPSHOT.free_count and
659 snapshot.alloc_bytes == FAILED_MEMORY_SNAPSHOT.alloc_bytes)
660 {
661 return null;
662 }
663 return snapshot;
664 }
665
666 fn countOperationTree(op: *ir.Operation) u64 {
667 var count: u64 = 1;
668 for (op.regions.items) |*region| {
669 var block_iter = region.getBlocks();
670 while (block_iter.next()) |block| {
671 var op_node = block.operations.head;
672 while (op_node) |node| {
673 const child: *ir.Operation = @ptrCast(@alignCast(node));
674 count +|= countOperationTree(child);
675 op_node = child.next_op;
676 }
677 }
678 }
679 return count;
680 }
681
682 pub fn getPassTime(self: *const TimingInstrumentation, name: []const u8) ?i128 {
683 if (self.pass_times.get(name)) |entry| {
684 return entry.total_ns;
685 }
686 return null;
687 }
688
689 pub fn getPassCount(self: *const TimingInstrumentation, name: []const u8) ?u64 {
690 if (self.pass_times.get(name)) |entry| {
691 return entry.count;
692 }
693 return null;
694 }
695
696 pub fn getPassModifiedCount(self: *const TimingInstrumentation, name: []const u8) ?u64 {
697 if (self.pass_times.get(name)) |entry| {
698 return entry.modified_count;
699 }
700 return null;
701 }
702
703 pub fn getPassOpCountBefore(self: *const TimingInstrumentation, name: []const u8) ?u64 {
704 if (self.pass_ir_sizes.get(name)) |entry| {
705 return entry.before_total;
706 }
707 return null;
708 }
709
710 pub fn getPassOpCountAfter(self: *const TimingInstrumentation, name: []const u8) ?u64 {
711 if (self.pass_ir_sizes.get(name)) |entry| {
712 return entry.after_total;
713 }
714 return null;
715 }
716
717 pub fn getPassOpCountDelta(self: *const TimingInstrumentation, name: []const u8) ?i128 {
718 if (self.pass_ir_sizes.get(name)) |entry| {
719 return entry.delta_total;
720 }
721 return null;
722 }
723
724 pub fn getPassAllocCount(self: *const TimingInstrumentation, name: []const u8) ?u64 {
725 if (self.pass_memory.get(name)) |entry| {
726 return entry.alloc_count;
727 }
728 return null;
729 }
730
731 pub fn getPassFreeCount(self: *const TimingInstrumentation, name: []const u8) ?u64 {
732 if (self.pass_memory.get(name)) |entry| {
733 return entry.free_count;
734 }
735 return null;
736 }
737
738 pub fn getPassAllocBytes(self: *const TimingInstrumentation, name: []const u8) ?u64 {
739 if (self.pass_memory.get(name)) |entry| {
740 return entry.alloc_bytes;
741 }
742 return null;
743 }
744
745 pub fn getAnalysisTime(self: *const TimingInstrumentation, name: []const u8) ?i128 {
746 if (self.analysis_times.get(name)) |entry| {
747 return entry.total_ns;
748 }
749 return null;
750 }
751
752 pub fn getAnalysisCount(self: *const TimingInstrumentation, name: []const u8) ?u64 {
753 if (self.analysis_times.get(name)) |entry| {
754 return entry.count;
755 }
756 return null;
757 }
758
759 pub fn hasPassTimings(self: *const TimingInstrumentation) bool {
760 return self.pass_times.count() != 0;
761 }
762
763 pub fn hasAnalysisTimings(self: *const TimingInstrumentation) bool {
764 return self.analysis_times.count() != 0;
765 }
766
767 pub fn hasPipelineTimings(self: *const TimingInstrumentation) bool {
768 return self.pipeline_times.items.len != 0;
769 }
770
771 pub fn collectsPassIrSizes(self: *const TimingInstrumentation) bool {
772 return self.options.collect_pass_ir_sizes;
773 }
774
775 pub fn collectsPassMemory(self: *const TimingInstrumentation) bool {
776 return self.options.allocation_snapshot_provider != null;
777 }
778
779 pub fn pipelineSummariesAlloc(
780 self: *const TimingInstrumentation,
781 allocator: std.mem.Allocator,
782 ) ![]PipelineTimingSummary {
783 const summaries = try allocator.alloc(PipelineTimingSummary, self.pipeline_times.items.len);
784 @memcpy(summaries, self.pipeline_times.items);
785 return summaries;
786 }
787
788 pub fn passRunSummariesAlloc(
789 self: *const TimingInstrumentation,
790 allocator: std.mem.Allocator,
791 ) ![]PassRunTimingSummary {
792 const summaries = try allocator.alloc(PassRunTimingSummary, self.pass_runs.items.len);
793 @memcpy(summaries, self.pass_runs.items);
794 return summaries;
795 }
796
797 pub fn passSummariesAlloc(
798 self: *const TimingInstrumentation,
799 allocator: std.mem.Allocator,
800 ) ![]PassTimingSummary {
801 var summaries = try allocator.alloc(PassTimingSummary, self.pass_times.count());
802 errdefer allocator.free(summaries);
803
804 var index: usize = 0;
805 var iter = self.pass_times.iterator();
806 while (iter.next()) |entry| : (index += 1) {
807 summaries[index] = timingSummary(entry.key_ptr.*, entry.value_ptr.*);
808 if (self.pass_ir_sizes.get(entry.key_ptr.*)) |ir_size| {
809 summaries[index].op_count_before = ir_size.before_total;
810 summaries[index].op_count_after = ir_size.after_total;
811 summaries[index].op_count_delta = ir_size.delta_total;
812 }
813 if (self.pass_memory.get(entry.key_ptr.*)) |memory| {
814 summaries[index].alloc_count = memory.alloc_count;
815 summaries[index].free_count = memory.free_count;
816 summaries[index].alloc_bytes = memory.alloc_bytes;
817 }
818 }
819 std.mem.sort(PassTimingSummary, summaries, {}, timingSummaryLessThan);
820 return summaries;
821 }
822
823 pub fn analysisSummariesAlloc(
824 self: *const TimingInstrumentation,
825 allocator: std.mem.Allocator,
826 ) ![]PassTimingSummary {
827 var summaries = try allocator.alloc(PassTimingSummary, self.analysis_times.count());
828 errdefer allocator.free(summaries);
829
830 var index: usize = 0;
831 var iter = self.analysis_times.iterator();
832 while (iter.next()) |entry| : (index += 1) {
833 summaries[index] = timingSummary(entry.key_ptr.*, entry.value_ptr.*);
834 }
835 std.mem.sort(PassTimingSummary, summaries, {}, timingSummaryLessThan);
836 return summaries;
837 }
838
839 fn timingSummary(name: []const u8, entry: PassTimingEntry) PassTimingSummary {
840 return .{
841 .name = name,
842 .total_ns = entry.total_ns,
843 .count = entry.count,
844 .modified_count = entry.modified_count,
845 .max_ns = entry.max_ns,
846 .min_ns = entry.min_ns,
847 };
848 }
849
850 fn timingSummaryLessThan(_: void, lhs: PassTimingSummary, rhs: PassTimingSummary) bool {
851 return std.mem.lessThan(u8, lhs.name, rhs.name);
852 }
853 };
854
855 pub const IRPrintingOptions = struct {
856 print_before: bool = false,
857 print_after: bool = false,
858 print_after_change: bool = false,
859 print_before_pipeline: bool = false,
860 print_after_pipeline: bool = false,
861 writer: ?*std.Io.Writer = null,
862 };
863
864 pub const IRPrintingInstrumentation = struct {
865 allocator: std.mem.Allocator,
866 options: IRPrintingOptions,
867 last_hash: ?u64 = null,
868 output: std.ArrayListUnmanaged(u8),
869
870 pub fn init(
871 allocator: std.mem.Allocator,
872 options: IRPrintingOptions,
873 ) IRPrintingInstrumentation {
874 return .{
875 .allocator = allocator,
876 .options = options,
877 .output = .empty,
878 };
879 }
880
881 pub fn deinit(self: *IRPrintingInstrumentation) void {
882 self.output.deinit(self.allocator);
883 }
884
885 pub fn instrumentation(self: *IRPrintingInstrumentation) PassInstrumentation {
886 return .{
887 .ctx = self,
888 .runBeforePipeline = if (self.options.print_before_pipeline) beforePipelineImpl else null,
889 .runAfterPipeline = if (self.options.print_after_pipeline) afterPipelineImpl else null,
890 .runBeforePass = if (self.options.print_before) beforePassImpl else null,
891 .runAfterPass = if (self.options.print_after or self.options.print_after_change) afterPassImpl else null,
892 };
893 }
894
895 fn beforePipelineImpl(ctx: ?*anyopaque, info: PipelineInfo, op: *ir.Operation) void {
896 const self: *IRPrintingInstrumentation = @ptrCast(@alignCast(ctx.?));
897 self.printHeader("Before pipeline", info.target_op_name, info.depth);
898 self.printOp(op);
899 }
900
901 fn afterPipelineImpl(ctx: ?*anyopaque, info: PipelineInfo, op: *ir.Operation, failed: bool) void {
902 const self: *IRPrintingInstrumentation = @ptrCast(@alignCast(ctx.?));
903 if (failed) {
904 self.printHeader("After pipeline (FAILED)", info.target_op_name, info.depth);
905 } else {
906 self.printHeader("After pipeline", info.target_op_name, info.depth);
907 }
908 self.printOp(op);
909 }
910
911 fn beforePassImpl(ctx: ?*anyopaque, info: PassInfo) void {
912 const self: *IRPrintingInstrumentation = @ptrCast(@alignCast(ctx.?));
913 self.printHeader("Before pass", info.name, 0);
914 if (info.target_op) |op| {
915 self.last_hash = self.computeOpHash(op);
916 self.printOp(op);
917 }
918 }
919
920 fn afterPassImpl(ctx: ?*anyopaque, info: PassInfo, modified: bool) void {
921 const self: *IRPrintingInstrumentation = @ptrCast(@alignCast(ctx.?));
922
923 if (self.options.print_after_change) {
924 if (info.target_op) |op| {
925 const new_hash = self.computeOpHash(op);
926 if (self.last_hash != null and new_hash != null) {
927 if (self.last_hash.? == new_hash.?) {
928 return;
929 }
930 }
931 }
932 }
933
934 const label = if (modified) "After pass (modified)" else "After pass";
935 self.printHeader(label, info.name, 0);
936 if (info.target_op) |op| {
937 self.printOp(op);
938 }
939 }
940
941 fn printHeader(self: *IRPrintingInstrumentation, label: []const u8, name: ?[]const u8, depth: usize) void {
942 self.print("\n", .{});
943 for (0..depth) |_| {
944 self.print(" ", .{});
945 }
946 self.print("// === {s}", .{label});
947 if (name) |n| {
948 self.print(": {s}", .{n});
949 }
950 self.print(" ===\n", .{});
951 }
952
953 fn printOp(self: *IRPrintingInstrumentation, op: *ir.Operation) void {
954 self.output.clearRetainingCapacity();
955
956 self.print("{s}", .{op.name.name});
957 self.printAttrs(op);
958 self.print("\n", .{});
959 }
960
961 fn printAttrs(self: *IRPrintingInstrumentation, op: *const ir.Operation) void {
962 var attrs = op.getAttrs();
963 const first = attrs.next() orelse return;
964 self.print(" {{{s}", .{first.name});
965 while (attrs.next()) |attr| self.print(", {s}", .{attr.name});
966 self.print("}}", .{});
967 }
968
969 fn print(self: *IRPrintingInstrumentation, comptime fmt: []const u8, args: anytype) void {
970 if (self.options.writer) |writer| {
971 writer.print(fmt, args) catch {};
972 } else {
973 pretty.diagnostic.writeStderrText(fmt, args);
974 }
975 }
976
977 fn computeOpHash(self: *IRPrintingInstrumentation, op: *ir.Operation) ?u64 {
978 return hashing.operationFingerprint(self.allocator, op) catch null;
979 }
980 };
981
982 pub const VerifierInstrumentationFailure = struct {
983 pass_name: []const u8,
984 err: anyerror,
985 };
986
987 pub const VerifierInstrumentationOptions = struct {
988 verify_options: verify_mod.VerifyOptions = verify_mod.default_options,
989 stop_on_failure: bool = true,
990 };
991
992 pub const VerifierInstrumentation = struct {
993 allocator: std.mem.Allocator,
994 options: verify_mod.VerifyOptions,
995 failures: std.ArrayListUnmanaged(VerifierInstrumentationFailure),
996 stop_on_failure: bool,
997 has_stopped: bool,
998
999 pub fn init(allocator: std.mem.Allocator, options: verify_mod.VerifyOptions) VerifierInstrumentation {
1000 return initWithOptions(allocator, .{ .verify_options = options });
1001 }
1002
1003 pub fn initWithOptions(
1004 allocator: std.mem.Allocator,
1005 options: VerifierInstrumentationOptions,
1006 ) VerifierInstrumentation {
1007 return .{
1008 .allocator = allocator,
1009 .options = options.verify_options,
1010 .failures = .empty,
1011 .stop_on_failure = options.stop_on_failure,
1012 .has_stopped = false,
1013 };
1014 }
1015
1016 pub fn deinit(self: *VerifierInstrumentation) void {
1017 self.failures.deinit(self.allocator);
1018 }
1019
1020 pub fn instrumentation(self: *VerifierInstrumentation) PassInstrumentation {
1021 return .{
1022 .ctx = self,
1023 .runAfterPass = afterPassImpl,
1024 };
1025 }
1026
1027 fn afterPassImpl(ctx: ?*anyopaque, info: PassInfo, _: bool) void {
1028 const self: *VerifierInstrumentation = @ptrCast(@alignCast(ctx.?));
1029
1030 if (self.stop_on_failure and self.has_stopped) {
1031 return;
1032 }
1033
1034 if (info.target_op) |op| {
1035 verify_mod.verifyOperation(op, self.options) catch |err| {
1036 self.failures.append(self.allocator, .{
1037 .pass_name = info.name,
1038 .err = err,
1039 }) catch {};
1040
1041 if (self.stop_on_failure) {
1042 self.has_stopped = true;
1043 }
1044 };
1045 }
1046 }
1047
1048 pub fn hasFailures(self: *const VerifierInstrumentation) bool {
1049 return self.failures.items.len > 0;
1050 }
1051
1052 pub fn getFailureCount(self: *const VerifierInstrumentation) usize {
1053 return self.failures.items.len;
1054 }
1055
1056 pub fn wasStopped(self: *const VerifierInstrumentation) bool {
1057 return self.has_stopped;
1058 }
1059
1060 pub fn reset(self: *VerifierInstrumentation) void {
1061 self.failures.clearRetainingCapacity();
1062 self.has_stopped = false;
1063 }
1064
1065 pub fn printFailures(self: *const VerifierInstrumentation, writer: anytype) !void {
1066 for (self.failures.items) |failure| {
1067 try writer.print("Verification failed after pass '{s}': {}\n", .{ failure.pass_name, failure.err });
1068 }
1069 if (self.has_stopped) {
1070 try writer.print("(Verification stopped after first failure)\n", .{});
1071 }
1072 }
1073 };
1074
1075 pub const CountingInstrumentation = struct {
1076 pipeline_count: usize = 0,
1077 pass_count: usize = 0,
1078 pass_failures: usize = 0,
1079 analysis_count: usize = 0,
1080
1081 pub fn instrumentation(self: *CountingInstrumentation) PassInstrumentation {
1082 return .{
1083 .ctx = self,
1084 .runBeforePipeline = beforePipelineImpl,
1085 .runBeforePass = beforePassImpl,
1086 .runAfterPassFailed = afterPassFailedImpl,
1087 .runBeforeAnalysis = beforeAnalysisImpl,
1088 };
1089 }
1090
1091 fn beforePipelineImpl(ctx: ?*anyopaque, _: PipelineInfo, _: *ir.Operation) void {
1092 const self: *CountingInstrumentation = @ptrCast(@alignCast(ctx.?));
1093 self.pipeline_count += 1;
1094 }
1095
1096 fn beforePassImpl(ctx: ?*anyopaque, _: PassInfo) void {
1097 const self: *CountingInstrumentation = @ptrCast(@alignCast(ctx.?));
1098 self.pass_count += 1;
1099 }
1100
1101 fn afterPassFailedImpl(ctx: ?*anyopaque, _: PassInfo) void {
1102 const self: *CountingInstrumentation = @ptrCast(@alignCast(ctx.?));
1103 self.pass_failures += 1;
1104 }
1105
1106 fn beforeAnalysisImpl(ctx: ?*anyopaque, _: AnalysisInfo) void {
1107 const self: *CountingInstrumentation = @ptrCast(@alignCast(ctx.?));
1108 self.analysis_count += 1;
1109 }
1110
1111 pub fn reset(self: *CountingInstrumentation) void {
1112 self.* = .{};
1113 }
1114 };
1115
1116 const BasicHookTracker = struct {
1117 var called_before: bool = false;
1118 var called_after: bool = false;
1119
1120 fn beforeImpl(_: ?*anyopaque, _: PassInfo) void {
1121 called_before = true;
1122 }
1123
1124 fn afterImpl(_: ?*anyopaque, _: PassInfo, _: bool) void {
1125 called_after = true;
1126 }
1127 };
1128
1129 fn ChainedInstrumentationType(comptime before_value: u8, comptime after_value: u8) type {
1130 return struct {
1131 var order_ptr: *[4]u8 = undefined;
1132 var idx_ptr: *usize = undefined;
1133
1134 fn before(_: ?*anyopaque, _: PassInfo) void {
1135 order_ptr[idx_ptr.*] = before_value;
1136 idx_ptr.* += 1;
1137 }
1138
1139 fn after(_: ?*anyopaque, _: PassInfo, _: bool) void {
1140 order_ptr[idx_ptr.*] = after_value;
1141 idx_ptr.* += 1;
1142 }
1143 };
1144 }
1145
1146 const FirstChainedInstrumentation = ChainedInstrumentationType('1', 'A');
1147 const SecondChainedInstrumentation = ChainedInstrumentationType('2', 'B');
1148
1149 test "PassInstrumentation basic hooks" {
1150 const testing = std.testing;
1151
1152 BasicHookTracker.called_before = false;
1153 BasicHookTracker.called_after = false;
1154
1155 const inst = PassInstrumentation{
1156 .runBeforePass = BasicHookTracker.beforeImpl,
1157 .runAfterPass = BasicHookTracker.afterImpl,
1158 };
1159
1160 const info = PassInfo{
1161 .name = "test-pass",
1162 .description = "Test pass",
1163 .target_op = null,
1164 };
1165
1166 inst.beforePass(info);
1167 try testing.expect(BasicHookTracker.called_before);
1168 try testing.expect(!BasicHookTracker.called_after);
1169
1170 inst.afterPass(info, false);
1171 try testing.expect(BasicHookTracker.called_after);
1172 }
1173
1174 test "PassInstrumentor chains multiple instrumentations" {
1175 const testing = std.testing;
1176 const allocator = testing.allocator;
1177
1178 var instrumentor = PassInstrumentor.init(allocator);
1179 defer instrumentor.deinit();
1180
1181 var order: [4]u8 = undefined;
1182 var idx: usize = 0;
1183
1184 FirstChainedInstrumentation.order_ptr = ℴ
1185 FirstChainedInstrumentation.idx_ptr = &idx;
1186 SecondChainedInstrumentation.order_ptr = ℴ
1187 SecondChainedInstrumentation.idx_ptr = &idx;
1188
1189 try instrumentor.addInstrumentation(.{
1190 .runBeforePass = FirstChainedInstrumentation.before,
1191 .runAfterPass = FirstChainedInstrumentation.after,
1192 });
1193 try instrumentor.addInstrumentation(.{
1194 .runBeforePass = SecondChainedInstrumentation.before,
1195 .runAfterPass = SecondChainedInstrumentation.after,
1196 });
1197
1198 const info = PassInfo{ .name = "test", .description = "", .target_op = null };
1199
1200 instrumentor.runBeforePass(info);
1201 instrumentor.runAfterPass(info, false);
1202
1203 try testing.expectEqualStrings("12BA", &order);
1204 }
1205
1206 test "TimingInstrumentation records pass times" {
1207 const testing = std.testing;
1208 const allocator = testing.allocator;
1209
1210 var timing = TimingInstrumentation.init(allocator);
1211 defer timing.deinit();
1212
1213 const inst = timing.instrumentation();
1214
1215 const info = PassInfo{
1216 .name = "test-pass",
1217 .description = "Test pass",
1218 .target_op = null,
1219 };
1220
1221 inst.beforePass(info);
1222 inst.afterPass(info, false);
1223
1224 try testing.expect(timing.getPassCount("test-pass") != null);
1225 try testing.expectEqual(@as(u64, 1), timing.getPassCount("test-pass").?);
1226 try testing.expectEqual(@as(u64, 0), timing.getPassModifiedCount("test-pass").?);
1227 try testing.expect(timing.getPassTime("test-pass") != null);
1228 try testing.expect(!timing.collectsPassIrSizes());
1229 try testing.expect(timing.getPassOpCountBefore("test-pass") == null);
1230 }
1231
1232 test "TimingInstrumentation records pass IR operation deltas when enabled" {
1233 const testing = std.testing;
1234 const allocator = testing.allocator;
1235 const test_dialect = @import("../dialects/fixture/root.zig");
1236
1237 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1238 defer ctx.deinit(allocator);
1239
1240 const loc = ir.Location.getUnknown();
1241 const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1242
1243 var timing = TimingInstrumentation.initWithOptions(allocator, .{ .collect_pass_ir_sizes = true });
1244 defer timing.deinit();
1245 const inst = timing.instrumentation();
1246
1247 const info = PassInfo{
1248 .name = "grow-ir",
1249 .description = "Test pass that adds an operation",
1250 .target_op = module_op.op,
1251 };
1252
1253 inst.beforePass(info);
1254 const func_op = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "f", &.{});
1255 try module_op.getBodyBlock().addOperation(func_op.op);
1256 inst.afterPass(info, true);
1257
1258 try testing.expect(timing.collectsPassIrSizes());
1259 try testing.expectEqual(@as(u64, 1), timing.getPassModifiedCount("grow-ir").?);
1260 try testing.expectEqual(@as(u64, 1), timing.getPassOpCountBefore("grow-ir").?);
1261 try testing.expectEqual(@as(u64, 2), timing.getPassOpCountAfter("grow-ir").?);
1262 try testing.expectEqual(@as(i128, 1), timing.getPassOpCountDelta("grow-ir").?);
1263
1264 const summaries = try timing.passSummariesAlloc(allocator);
1265 defer allocator.free(summaries);
1266 try testing.expectEqual(@as(usize, 1), summaries.len);
1267 try testing.expectEqualStrings("grow-ir", summaries[0].name);
1268 try testing.expectEqual(@as(u64, 1), summaries[0].modified_count);
1269 try testing.expectEqual(@as(u64, 1), summaries[0].op_count_before.?);
1270 try testing.expectEqual(@as(u64, 2), summaries[0].op_count_after.?);
1271 try testing.expectEqual(@as(i128, 1), summaries[0].op_count_delta.?);
1272
1273 const runs = try timing.passRunSummariesAlloc(allocator);
1274 defer allocator.free(runs);
1275 try testing.expectEqual(@as(usize, 1), runs.len);
1276 try testing.expectEqual(@as(u64, 1), runs[0].ordinal);
1277 try testing.expectEqualStrings("grow-ir", runs[0].name);
1278 try testing.expect(runs[0].modified);
1279 try testing.expectEqual(@as(u64, 1), runs[0].op_count_before.?);
1280 try testing.expectEqual(@as(u64, 2), runs[0].op_count_after.?);
1281 try testing.expectEqual(@as(i128, 1), runs[0].op_count_delta.?);
1282 }
1283
1284 test "TimingInstrumentation preserves duplicate pass run order" {
1285 const testing = std.testing;
1286 const allocator = testing.allocator;
1287 const test_dialect = @import("../dialects/fixture/root.zig");
1288
1289 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1290 defer ctx.deinit(allocator);
1291
1292 const loc = ir.Location.getUnknown();
1293 const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
1294
1295 var timing = TimingInstrumentation.initWithOptions(allocator, .{ .collect_pass_ir_sizes = true });
1296 defer timing.deinit();
1297 const inst = timing.instrumentation();
1298
1299 const info = PassInfo{
1300 .name = "duplicate-pass",
1301 .description = "Duplicate pass",
1302 .target_op = module_op.op,
1303 };
1304
1305 inst.beforePass(info);
1306 inst.afterPass(info, false);
1307
1308 inst.beforePass(info);
1309 const func_op = try test_dialect.TestDialect.FuncOp.create(&ctx, loc, "f", &.{});
1310 try module_op.getBodyBlock().addOperation(func_op.op);
1311 inst.afterPass(info, true);
1312
1313 const runs = try timing.passRunSummariesAlloc(allocator);
1314 defer allocator.free(runs);
1315 try testing.expectEqual(@as(usize, 2), runs.len);
1316 try testing.expectEqual(@as(u64, 1), runs[0].ordinal);
1317 try testing.expectEqual(@as(u64, 2), runs[1].ordinal);
1318 try testing.expectEqualStrings("duplicate-pass", runs[0].name);
1319 try testing.expectEqualStrings("duplicate-pass", runs[1].name);
1320 try testing.expect(!runs[0].modified);
1321 try testing.expect(runs[1].modified);
1322 try testing.expectEqual(@as(i128, 0), runs[0].op_count_delta.?);
1323 try testing.expectEqual(@as(i128, 1), runs[1].op_count_delta.?);
1324 }
1325
1326 test "CountingInstrumentation counts events" {
1327 const testing = std.testing;
1328 const allocator = testing.allocator;
1329
1330 var counter = CountingInstrumentation{};
1331 const inst = counter.instrumentation();
1332
1333 var instrumentor = PassInstrumentor.init(allocator);
1334 defer instrumentor.deinit();
1335 try instrumentor.addInstrumentation(inst);
1336
1337 const test_dialect = @import("../dialects/fixture/root.zig");
1338
1339 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1340 defer ctx.deinit(allocator);
1341
1342 const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, ir.Location.getUnknown());
1343
1344 instrumentor.runBeforePipeline(.{ .target_op_name = null, .depth = 0 }, module_op.op);
1345 instrumentor.runBeforePass(.{ .name = "pass1", .description = "", .target_op = null });
1346 instrumentor.runAfterPass(.{ .name = "pass1", .description = "", .target_op = null }, false);
1347 instrumentor.runBeforePass(.{ .name = "pass2", .description = "", .target_op = null });
1348 instrumentor.runAfterPassFailed(.{ .name = "pass2", .description = "", .target_op = null });
1349 instrumentor.runAfterPipeline(.{ .target_op_name = null, .depth = 0 }, module_op.op, true);
1350
1351 try testing.expectEqual(@as(usize, 1), counter.pipeline_count);
1352 try testing.expectEqual(@as(usize, 2), counter.pass_count);
1353 try testing.expectEqual(@as(usize, 1), counter.pass_failures);
1354 }
1355
1356 test "VerifierInstrumentation verifies operations" {
1357 const testing = std.testing;
1358 const allocator = testing.allocator;
1359
1360 const options = verify_mod.VerifyOptions{
1361 .check_terminators = false,
1362 .require_terminators = false,
1363 .recursive = false,
1364 .check_use_def = false,
1365 .check_cfg = false,
1366 };
1367
1368 var verifier = VerifierInstrumentation.init(allocator, options);
1369 defer verifier.deinit();
1370
1371 try testing.expect(!verifier.hasFailures());
1372 try testing.expectEqual(@as(usize, 0), verifier.getFailureCount());
1373 }
1374
1375 test "IRPrintingInstrumentation print_after_change" {
1376 const testing = std.testing;
1377 const allocator = testing.allocator;
1378
1379 var printer = IRPrintingInstrumentation.init(allocator, .{
1380 .print_after_change = true,
1381 });
1382 defer printer.deinit();
1383
1384 try testing.expect(printer.options.print_after_change);
1385 try testing.expect(!printer.options.print_before);
1386 try testing.expect(!printer.options.print_after);
1387 }
1388
1389 test "PassInstrumentor clear removes all instrumentations" {
1390 const testing = std.testing;
1391 const allocator = testing.allocator;
1392
1393 var instrumentor = PassInstrumentor.init(allocator);
1394 defer instrumentor.deinit();
1395
1396 try instrumentor.addInstrumentation(.{});
1397 try instrumentor.addInstrumentation(.{});
1398 try testing.expectEqual(@as(usize, 2), instrumentor.instrumentations.items.len);
1399
1400 instrumentor.clear();
1401 try testing.expectEqual(@as(usize, 0), instrumentor.instrumentations.items.len);
1402 }