authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-28 12:11:08-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-28 18:07:13-07:00
logb8d99a332395ec0a1b9ed1a8e18f0db8db131b3c
tree556064f0106c74ccd25f8be96aad973cef320e26
parent43dc8db068f65f31cd7cf808429c627b29058582

implement code coverage instrumentation manually

instead of relying on the LLVM sancov pass. The LLVM pass is still executed if trace_pc_guard is requested, disabled otherwise. The LLVM backend emits the instrumentation directly. It uses `__sancov_pcs1` symbol name instead of `__sancov_pcs` because each element is 1 usize instead of 2. AIR: add CoveragePoint to branch hints which indicates whether those branches are interesting for code coverage purposes. Update libfuzzer to use the new instrumentation. It's simplified since we no longer need the constructor and the pcs are now in a continguous list. This is a regression in the fuzzing functionality because the instrumentation for comparisons is no longer emitted, resulting in worse fuzzer inputs generated. A future commit will add that instrumentation back.

5 files changed, 249 insertions(+), 98 deletions(-)

lib/fuzzer.zig+44-48
......@@ -30,19 +30,6 @@ fn logOverride(
3030
3131export threadlocal var __sancov_lowest_stack: usize = std.math.maxInt(usize);
3232
33var module_count_8bc: usize = 0;
34var module_count_pcs: usize = 0;
35
36export fn __sanitizer_cov_8bit_counters_init(start: [*]u8, end: [*]u8) void {
37 assert(@atomicRmw(usize, &module_count_8bc, .Add, 1, .monotonic) == 0);
38 fuzzer.pc_counters = start[0 .. end - start];
39}
40
41export fn __sanitizer_cov_pcs_init(start: [*]const Fuzzer.FlaggedPc, end: [*]const Fuzzer.FlaggedPc) void {
42 assert(@atomicRmw(usize, &module_count_pcs, .Add, 1, .monotonic) == 0);
43 fuzzer.flagged_pcs = start[0 .. end - start];
44}
45
4633export fn __sanitizer_cov_trace_const_cmp1(arg1: u8, arg2: u8) void {
4734 handleCmp(@returnAddress(), arg1, arg2);
4835}
......@@ -105,7 +92,7 @@ const Fuzzer = struct {
10592 gpa: Allocator,
10693 rng: std.Random.DefaultPrng,
10794 input: std.ArrayListUnmanaged(u8),
108 flagged_pcs: []const FlaggedPc,
95 pcs: []const usize,
10996 pc_counters: []u8,
11097 n_runs: usize,
11198 recent_cases: RunMap,
......@@ -174,32 +161,18 @@ const Fuzzer = struct {
174161 }
175162 };
176163
177 const FlaggedPc = extern struct {
178 addr: usize,
179 flags: packed struct(usize) {
180 entry: bool,
181 _: @Type(.{ .int = .{ .signedness = .unsigned, .bits = @bitSizeOf(usize) - 1 } }),
182 },
183 };
184
185164 const Analysis = struct {
186165 score: usize,
187166 id: Run.Id,
188167 };
189168
190 fn init(f: *Fuzzer, cache_dir: std.fs.Dir) !void {
191 const flagged_pcs = f.flagged_pcs;
192
169 fn init(f: *Fuzzer, cache_dir: std.fs.Dir, pc_counters: []u8, pcs: []const usize) !void {
193170 f.cache_dir = cache_dir;
171 f.pc_counters = pc_counters;
172 f.pcs = pcs;
194173
195174 // Choose a file name for the coverage based on a hash of the PCs that will be stored within.
196 const pc_digest = d: {
197 var hasher = std.hash.Wyhash.init(0);
198 for (flagged_pcs) |flagged_pc| {
199 hasher.update(std.mem.asBytes(&flagged_pc.addr));
200 }
201 break :d f.coverage.run_id_hasher.final();
202 };
175 const pc_digest = std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(pcs));
203176 f.coverage_id = pc_digest;
204177 const hex_digest = std.fmt.hex(pc_digest);
205178 const coverage_file_path = "v/" ++ hex_digest;
......@@ -213,12 +186,12 @@ const Fuzzer = struct {
213186 .truncate = false,
214187 });
215188 defer coverage_file.close();
216 const n_bitset_elems = (flagged_pcs.len + @bitSizeOf(usize) - 1) / @bitSizeOf(usize);
189 const n_bitset_elems = (pcs.len + @bitSizeOf(usize) - 1) / @bitSizeOf(usize);
217190 comptime assert(SeenPcsHeader.trailing[0] == .pc_bits_usize);
218191 comptime assert(SeenPcsHeader.trailing[1] == .pc_addr);
219192 const bytes_len = @sizeOf(SeenPcsHeader) +
220193 n_bitset_elems * @sizeOf(usize) +
221 flagged_pcs.len * @sizeOf(usize);
194 pcs.len * @sizeOf(usize);
222195 const existing_len = coverage_file.getEndPos() catch |err| {
223196 fatal("unable to check len of coverage file: {s}", .{@errorName(err)});
224197 };
......@@ -233,12 +206,12 @@ const Fuzzer = struct {
233206 fatal("unable to init coverage memory map: {s}", .{@errorName(err)});
234207 };
235208 if (existing_len != 0) {
236 const existing_pcs_bytes = f.seen_pcs.items[@sizeOf(SeenPcsHeader) + @sizeOf(usize) * n_bitset_elems ..][0 .. flagged_pcs.len * @sizeOf(usize)];
209 const existing_pcs_bytes = f.seen_pcs.items[@sizeOf(SeenPcsHeader) + @sizeOf(usize) * n_bitset_elems ..][0 .. pcs.len * @sizeOf(usize)];
237210 const existing_pcs = std.mem.bytesAsSlice(usize, existing_pcs_bytes);
238 for (existing_pcs, flagged_pcs, 0..) |old, new, i| {
239 if (old != new.addr) {
211 for (existing_pcs, pcs, 0..) |old, new, i| {
212 if (old != new) {
240213 fatal("incompatible existing coverage file (differing PC at index {d}: {x} != {x})", .{
241 i, old, new.addr,
214 i, old, new,
242215 });
243216 }
244217 }
......@@ -246,14 +219,12 @@ const Fuzzer = struct {
246219 const header: SeenPcsHeader = .{
247220 .n_runs = 0,
248221 .unique_runs = 0,
249 .pcs_len = flagged_pcs.len,
222 .pcs_len = pcs.len,
250223 .lowest_stack = std.math.maxInt(usize),
251224 };
252225 f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&header));
253226 f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems * @sizeOf(usize));
254 for (flagged_pcs) |flagged_pc| {
255 f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&flagged_pc.addr));
256 }
227 f.seen_pcs.appendSliceAssumeCapacity(std.mem.sliceAsBytes(pcs));
257228 }
258229 }
259230
......@@ -307,8 +278,8 @@ const Fuzzer = struct {
307278 // Track code coverage from all runs.
308279 comptime assert(SeenPcsHeader.trailing[0] == .pc_bits_usize);
309280 const header_end_ptr: [*]volatile usize = @ptrCast(f.seen_pcs.items[@sizeOf(SeenPcsHeader)..]);
310 const remainder = f.flagged_pcs.len % @bitSizeOf(usize);
311 const aligned_len = f.flagged_pcs.len - remainder;
281 const remainder = f.pcs.len % @bitSizeOf(usize);
282 const aligned_len = f.pcs.len - remainder;
312283 const seen_pcs = header_end_ptr[0..aligned_len];
313284 const pc_counters = std.mem.bytesAsSlice([@bitSizeOf(usize)]u8, f.pc_counters[0..aligned_len]);
314285 const V = @Vector(@bitSizeOf(usize), u8);
......@@ -433,7 +404,7 @@ var fuzzer: Fuzzer = .{
433404 .gpa = general_purpose_allocator.allocator(),
434405 .rng = std.Random.DefaultPrng.init(0),
435406 .input = .{},
436 .flagged_pcs = undefined,
407 .pcs = undefined,
437408 .pc_counters = undefined,
438409 .n_runs = 0,
439410 .recent_cases = .{},
......@@ -455,8 +426,32 @@ export fn fuzzer_next() Fuzzer.Slice {
455426}
456427
457428export fn fuzzer_init(cache_dir_struct: Fuzzer.Slice) void {
458 if (module_count_8bc == 0) fatal("__sanitizer_cov_8bit_counters_init was never called", .{});
459 if (module_count_pcs == 0) fatal("__sanitizer_cov_pcs_init was never called", .{});
429 // Linkers are expected to automatically add `__start_<section>` and
430 // `__stop_<section>` symbols when section names are valid C identifiers.
431
432 const pc_counters_start = @extern([*]u8, .{
433 .name = "__start___sancov_cntrs",
434 .linkage = .weak,
435 }) orelse fatal("missing __start___sancov_cntrs symbol");
436
437 const pc_counters_end = @extern([*]u8, .{
438 .name = "__stop___sancov_cntrs",
439 .linkage = .weak,
440 }) orelse fatal("missing __stop___sancov_cntrs symbol");
441
442 const pc_counters = pc_counters_start[0 .. pc_counters_end - pc_counters_start];
443
444 const pcs_start = @extern([*]usize, .{
445 .name = "__start___sancov_pcs1",
446 .linkage = .weak,
447 }) orelse fatal("missing __start___sancov_pcs1 symbol");
448
449 const pcs_end = @extern([*]usize, .{
450 .name = "__stop___sancov_pcs1",
451 .linkage = .weak,
452 }) orelse fatal("missing __stop___sancov_pcs1 symbol");
453
454 const pcs = pcs_start[0 .. pcs_end - pcs_start];
460455
461456 const cache_dir_path = cache_dir_struct.toZig();
462457 const cache_dir = if (cache_dir_path.len == 0)
......@@ -466,7 +461,8 @@ export fn fuzzer_init(cache_dir_struct: Fuzzer.Slice) void {
466461 fatal("unable to open fuzz directory '{s}': {s}", .{ cache_dir_path, @errorName(err) });
467462 };
468463
469 fuzzer.init(cache_dir) catch |err| fatal("unable to init fuzzer: {s}", .{@errorName(err)});
464 fuzzer.init(cache_dir, pc_counters, pcs) catch |err|
465 fatal("unable to init fuzzer: {s}", .{@errorName(err)});
470466}
471467
472468/// Like `std.ArrayListUnmanaged(u8)` but backed by memory mapping.
src/Air.zig+12-1
......@@ -1126,7 +1126,9 @@ pub const CondBr = struct {
11261126 pub const BranchHints = packed struct(u32) {
11271127 true: std.builtin.BranchHint,
11281128 false: std.builtin.BranchHint,
1129 _: u26 = 0,
1129 then_cov: CoveragePoint,
1130 else_cov: CoveragePoint,
1131 _: u24 = 0,
11301132 };
11311133};
11321134
......@@ -1903,3 +1905,12 @@ pub fn unwrapSwitch(air: *const Air, switch_inst: Inst.Index) UnwrappedSwitch {
19031905pub const typesFullyResolved = types_resolved.typesFullyResolved;
19041906pub const typeFullyResolved = types_resolved.checkType;
19051907pub const valFullyResolved = types_resolved.checkVal;
1908
1909pub const CoveragePoint = enum(u1) {
1910 /// Indicates the block is not a place of interest corresponding to
1911 /// a source location for coverage purposes.
1912 none,
1913 /// Point of interest. The next instruction emitted corresponds to
1914 /// a source location used for coverage instrumentation.
1915 poi,
1916};
src/Sema.zig+80-25
......@@ -6898,8 +6898,14 @@ fn popErrorReturnTrace(
68986898 .payload = sema.addExtraAssumeCapacity(Air.CondBr{
68996899 .then_body_len = @intCast(then_block.instructions.items.len),
69006900 .else_body_len = @intCast(else_block.instructions.items.len),
6901 // weight against error branch
6902 .branch_hints = .{ .true = .likely, .false = .unlikely },
6901 .branch_hints = .{
6902 // Weight against error branch.
6903 .true = .likely,
6904 .false = .unlikely,
6905 // Code coverage is not valuable on either branch.
6906 .then_cov = .none,
6907 .else_cov = .none,
6908 },
69036909 }),
69046910 },
69056911 },
......@@ -11796,14 +11802,22 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp
1179611802
1179711803 _ = try child_block.addInst(.{
1179811804 .tag = .cond_br,
11799 .data = .{ .pl_op = .{
11800 .operand = cond,
11801 .payload = sema.addExtraAssumeCapacity(Air.CondBr{
11802 .then_body_len = @intCast(true_instructions.len),
11803 .else_body_len = @intCast(sub_block.instructions.items.len),
11804 .branch_hints = .{ .true = non_error_hint, .false = .none },
11805 }),
11806 } },
11805 .data = .{
11806 .pl_op = .{
11807 .operand = cond,
11808 .payload = sema.addExtraAssumeCapacity(Air.CondBr{
11809 .then_body_len = @intCast(true_instructions.len),
11810 .else_body_len = @intCast(sub_block.instructions.items.len),
11811 .branch_hints = .{
11812 .true = non_error_hint,
11813 .false = .none,
11814 // Code coverage is desired for error handling.
11815 .then_cov = .poi,
11816 .else_cov = .poi,
11817 },
11818 }),
11819 },
11820 },
1180711821 });
1180811822 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(true_instructions));
1180911823 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(sub_block.instructions.items));
......@@ -12853,7 +12867,13 @@ fn analyzeSwitchRuntimeBlock(
1285312867 sema.air_instructions.items(.data)[@intFromEnum(prev_cond_br)].pl_op.payload = sema.addExtraAssumeCapacity(Air.CondBr{
1285412868 .then_body_len = @intCast(prev_then_body.len),
1285512869 .else_body_len = @intCast(cond_body.len),
12856 .branch_hints = .{ .true = prev_hint, .false = .none },
12870 .branch_hints = .{
12871 .true = prev_hint,
12872 .false = .none,
12873 // Code coverage is desired for error handling.
12874 .then_cov = .poi,
12875 .else_cov = .poi,
12876 },
1285712877 });
1285812878 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(prev_then_body));
1285912879 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(cond_body));
......@@ -13133,7 +13153,12 @@ fn analyzeSwitchRuntimeBlock(
1313313153 sema.air_instructions.items(.data)[@intFromEnum(prev_cond_br)].pl_op.payload = sema.addExtraAssumeCapacity(Air.CondBr{
1313413154 .then_body_len = @intCast(prev_then_body.len),
1313513155 .else_body_len = @intCast(case_block.instructions.items.len),
13136 .branch_hints = .{ .true = prev_hint, .false = else_hint },
13156 .branch_hints = .{
13157 .true = prev_hint,
13158 .false = else_hint,
13159 .then_cov = .poi,
13160 .else_cov = .poi,
13161 },
1313713162 });
1313813163 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(prev_then_body));
1313913164 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
......@@ -19250,7 +19275,17 @@ fn zirBoolBr(
1925019275 &else_block,
1925119276 lhs,
1925219277 block_inst,
19253 if (is_bool_or) .{ .true = .none, .false = rhs_hint } else .{ .true = rhs_hint, .false = .none },
19278 if (is_bool_or) .{
19279 .true = .none,
19280 .false = rhs_hint,
19281 .then_cov = .poi,
19282 .else_cov = .poi,
19283 } else .{
19284 .true = rhs_hint,
19285 .false = .none,
19286 .then_cov = .poi,
19287 .else_cov = .poi,
19288 },
1925419289 );
1925519290 if (!rhs_noret) {
1925619291 if (try sema.resolveDefinedValue(rhs_block, rhs_src, coerced_rhs_result)) |rhs_val| {
......@@ -19467,14 +19502,22 @@ fn zirCondbr(
1946719502 true_instructions.len + sub_block.instructions.items.len);
1946819503 _ = try parent_block.addInst(.{
1946919504 .tag = .cond_br,
19470 .data = .{ .pl_op = .{
19471 .operand = cond,
19472 .payload = sema.addExtraAssumeCapacity(Air.CondBr{
19473 .then_body_len = @intCast(true_instructions.len),
19474 .else_body_len = @intCast(sub_block.instructions.items.len),
19475 .branch_hints = .{ .true = true_hint, .false = false_hint },
19476 }),
19477 } },
19505 .data = .{
19506 .pl_op = .{
19507 .operand = cond,
19508 .payload = sema.addExtraAssumeCapacity(Air.CondBr{
19509 .then_body_len = @intCast(true_instructions.len),
19510 .else_body_len = @intCast(sub_block.instructions.items.len),
19511 .branch_hints = .{
19512 .true = true_hint,
19513 .false = false_hint,
19514 // Code coverage is desired for error handling.
19515 .then_cov = .poi,
19516 .else_cov = .poi,
19517 },
19518 }),
19519 },
19520 },
1947819521 });
1947919522 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(true_instructions));
1948019523 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(sub_block.instructions.items));
......@@ -19851,8 +19894,14 @@ fn retWithErrTracing(
1985119894 const cond_br_payload = sema.addExtraAssumeCapacity(Air.CondBr{
1985219895 .then_body_len = @intCast(then_block.instructions.items.len),
1985319896 .else_body_len = @intCast(else_block.instructions.items.len),
19854 // weight against error branch
19855 .branch_hints = .{ .true = .likely, .false = .unlikely },
19897 .branch_hints = .{
19898 // Weight against error branch.
19899 .true = .likely,
19900 .false = .unlikely,
19901 // Code coverage is not valuable on either branch.
19902 .then_cov = .none,
19903 .else_cov = .none,
19904 },
1985619905 });
1985719906 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(then_block.instructions.items));
1985819907 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(else_block.instructions.items));
......@@ -27473,8 +27522,14 @@ fn addSafetyCheckExtra(
2747327522 .payload = sema.addExtraAssumeCapacity(Air.CondBr{
2747427523 .then_body_len = 1,
2747527524 .else_body_len = @intCast(fail_block.instructions.items.len),
27476 // safety check failure branch is cold
27477 .branch_hints = .{ .true = .likely, .false = .cold },
27525 .branch_hints = .{
27526 // Safety check failure branch is cold.
27527 .true = .likely,
27528 .false = .cold,
27529 // Code coverage not wanted for panic branches.
27530 .then_cov = .none,
27531 .else_cov = .none,
27532 },
2747827533 }),
2747927534 },
2748027535 },
src/codegen/llvm.zig+107-24
......@@ -1275,7 +1275,7 @@ pub const Object = struct {
12751275 .is_small = options.is_small,
12761276 .time_report = options.time_report,
12771277 .tsan = options.sanitize_thread,
1278 .sancov = options.fuzz,
1278 .sancov = sanCovPassEnabled(comp.config.san_cov_trace_pc_guard),
12791279 .lto = options.lto,
12801280 .asm_filename = null,
12811281 .bin_filename = options.bin_path,
......@@ -1283,19 +1283,19 @@ pub const Object = struct {
12831283 .bitcode_filename = null,
12841284 .coverage = .{
12851285 .CoverageType = .Edge,
1286 .IndirectCalls = true,
1286 .IndirectCalls = false,
12871287 .TraceBB = false,
1288 .TraceCmp = true,
1288 .TraceCmp = false,
12891289 .TraceDiv = false,
12901290 .TraceGep = false,
12911291 .Use8bitCounters = false,
12921292 .TracePC = false,
12931293 .TracePCGuard = comp.config.san_cov_trace_pc_guard,
1294 .Inline8bitCounters = true,
1294 .Inline8bitCounters = false,
12951295 .InlineBoolFlag = false,
1296 .PCTable = true,
1296 .PCTable = false,
12971297 .NoPrune = false,
1298 .StackDepth = true,
1298 .StackDepth = false,
12991299 .TraceLoads = false,
13001300 .TraceStores = false,
13011301 .CollectControlFlow = false,
......@@ -1655,6 +1655,25 @@ pub const Object = struct {
16551655 break :debug_info .{ file, subprogram };
16561656 } else .{.none} ** 2;
16571657
1658 const fuzz: ?FuncGen.Fuzz = f: {
1659 if (!owner_mod.fuzz) break :f null;
1660 if (func_analysis.disable_instrumentation) break :f null;
1661 if (is_naked) break :f null;
1662
1663 // The void type used here is a placeholder to be replaced with an
1664 // array of the appropriate size after the POI count is known.
1665
1666 const counters_variable = try o.builder.addVariable(.empty, .void, .default);
1667 counters_variable.setLinkage(.private, &o.builder);
1668 counters_variable.setAlignment(comptime Builder.Alignment.fromByteUnits(1), &o.builder);
1669 counters_variable.setSection(try o.builder.string("__sancov_cntrs"), &o.builder);
1670
1671 break :f .{
1672 .counters_variable = counters_variable,
1673 .pcs = .{},
1674 };
1675 };
1676
16581677 var fg: FuncGen = .{
16591678 .gpa = gpa,
16601679 .air = air,
......@@ -1662,6 +1681,7 @@ pub const Object = struct {
16621681 .ng = &ng,
16631682 .wip = wip,
16641683 .is_naked = fn_info.cc == .Naked,
1684 .fuzz = fuzz,
16651685 .ret_ptr = ret_ptr,
16661686 .args = args.items,
16671687 .arg_index = 0,
......@@ -1679,7 +1699,7 @@ pub const Object = struct {
16791699 defer fg.deinit();
16801700 deinit_wip = false;
16811701
1682 fg.genBody(air.getMainBody()) catch |err| switch (err) {
1702 fg.genBody(air.getMainBody(), .poi) catch |err| switch (err) {
16831703 error.CodegenFail => {
16841704 try zcu.failed_codegen.put(zcu.gpa, func.owner_nav, ng.err_msg.?);
16851705 ng.err_msg = null;
......@@ -1688,6 +1708,24 @@ pub const Object = struct {
16881708 else => |e| return e,
16891709 };
16901710
1711 if (fg.fuzz) |*f| {
1712 {
1713 const array_llvm_ty = try o.builder.arrayType(f.pcs.items.len, .i8);
1714 f.counters_variable.ptrConst(&o.builder).global.ptr(&o.builder).type = array_llvm_ty;
1715 const zero_init = try o.builder.zeroInitConst(array_llvm_ty);
1716 try f.counters_variable.setInitializer(zero_init, &o.builder);
1717 }
1718
1719 const array_llvm_ty = try o.builder.arrayType(f.pcs.items.len, .ptr);
1720 const init_val = try o.builder.arrayConst(array_llvm_ty, f.pcs.items);
1721 const pcs_variable = try o.builder.addVariable(.empty, array_llvm_ty, .default);
1722 pcs_variable.setLinkage(.private, &o.builder);
1723 pcs_variable.setMutability(.constant, &o.builder);
1724 pcs_variable.setAlignment(Type.usize.abiAlignment(zcu).toLlvm(), &o.builder);
1725 pcs_variable.setSection(try o.builder.string("__sancov_pcs1"), &o.builder);
1726 try pcs_variable.setInitializer(init_val, &o.builder);
1727 }
1728
16911729 try fg.wip.finish();
16921730 }
16931731
......@@ -4729,6 +4767,7 @@ pub const FuncGen = struct {
47294767 liveness: Liveness,
47304768 wip: Builder.WipFunction,
47314769 is_naked: bool,
4770 fuzz: ?Fuzz,
47324771
47334772 file: Builder.Metadata,
47344773 scope: Builder.Metadata,
......@@ -4769,6 +4808,16 @@ pub const FuncGen = struct {
47694808
47704809 sync_scope: Builder.SyncScope,
47714810
4811 const Fuzz = struct {
4812 counters_variable: Builder.Variable.Index,
4813 pcs: std.ArrayListUnmanaged(Builder.Constant),
4814
4815 fn deinit(f: *Fuzz, gpa: Allocator) void {
4816 f.pcs.deinit(gpa);
4817 f.* = undefined;
4818 }
4819 };
4820
47724821 const BreakList = union {
47734822 list: std.MultiArrayList(struct {
47744823 bb: Builder.Function.Block.Index,
......@@ -4778,9 +4827,11 @@ pub const FuncGen = struct {
47784827 };
47794828
47804829 fn deinit(self: *FuncGen) void {
4830 const gpa = self.gpa;
4831 if (self.fuzz) |*f| f.deinit(self.gpa);
47814832 self.wip.deinit();
4782 self.func_inst_table.deinit(self.gpa);
4783 self.blocks.deinit(self.gpa);
4833 self.func_inst_table.deinit(gpa);
4834 self.blocks.deinit(gpa);
47844835 }
47854836
47864837 fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) Error {
......@@ -4836,11 +4887,33 @@ pub const FuncGen = struct {
48364887 return o.null_opt_usize;
48374888 }
48384889
4839 fn genBody(self: *FuncGen, body: []const Air.Inst.Index) Error!void {
4890 fn genBody(self: *FuncGen, body: []const Air.Inst.Index, coverage_point: Air.CoveragePoint) Error!void {
48404891 const o = self.ng.object;
48414892 const zcu = o.pt.zcu;
48424893 const ip = &zcu.intern_pool;
48434894 const air_tags = self.air.instructions.items(.tag);
4895 switch (coverage_point) {
4896 .none => {},
4897 .poi => if (self.fuzz) |*fuzz| {
4898 const poi_index = fuzz.pcs.items.len;
4899 const base_ptr = fuzz.counters_variable.toValue(&o.builder);
4900 const ptr = if (poi_index == 0) base_ptr else try self.wip.gep(.inbounds, .i8, base_ptr, &.{
4901 try o.builder.intValue(.i32, poi_index),
4902 }, "");
4903 const counter = try self.wip.load(.normal, .i8, ptr, .default, "");
4904 const one = try o.builder.intValue(.i8, 1);
4905 const counter_incremented = try self.wip.bin(.add, counter, one, "");
4906 _ = try self.wip.store(.normal, counter_incremented, ptr, .default);
4907
4908 // LLVM does not allow blockaddress on the entry block.
4909 const pc = if (self.wip.cursor.block == .entry)
4910 self.wip.function.toConst(&o.builder)
4911 else
4912 try o.builder.blockAddrConst(self.wip.function, self.wip.cursor.block);
4913 const gpa = self.gpa;
4914 try fuzz.pcs.append(gpa, pc);
4915 },
4916 }
48444917 for (body, 0..) |inst, i| {
48454918 if (self.liveness.isUnused(inst) and !self.air.mustLower(inst, ip)) continue;
48464919
......@@ -4949,7 +5022,7 @@ pub const FuncGen = struct {
49495022 .ret_ptr => try self.airRetPtr(inst),
49505023 .arg => try self.airArg(inst),
49515024 .bitcast => try self.airBitCast(inst),
4952 .int_from_bool => try self.airIntFromBool(inst),
5025 .int_from_bool => try self.airIntFromBool(inst),
49535026 .block => try self.airBlock(inst),
49545027 .br => try self.airBr(inst),
49555028 .switch_br => try self.airSwitchBr(inst),
......@@ -4966,7 +5039,7 @@ pub const FuncGen = struct {
49665039 .trunc => try self.airTrunc(inst),
49675040 .fptrunc => try self.airFptrunc(inst),
49685041 .fpext => try self.airFpext(inst),
4969 .int_from_ptr => try self.airIntFromPtr(inst),
5042 .int_from_ptr => try self.airIntFromPtr(inst),
49705043 .load => try self.airLoad(body[i..]),
49715044 .loop => try self.airLoop(inst),
49725045 .not => try self.airNot(inst),
......@@ -5089,8 +5162,13 @@ pub const FuncGen = struct {
50895162 }
50905163 }
50915164
5092 fn genBodyDebugScope(self: *FuncGen, maybe_inline_func: ?InternPool.Index, body: []const Air.Inst.Index) Error!void {
5093 if (self.wip.strip) return self.genBody(body);
5165 fn genBodyDebugScope(
5166 self: *FuncGen,
5167 maybe_inline_func: ?InternPool.Index,
5168 body: []const Air.Inst.Index,
5169 coverage_point: Air.CoveragePoint,
5170 ) Error!void {
5171 if (self.wip.strip) return self.genBody(body, coverage_point);
50945172
50955173 const old_file = self.file;
50965174 const old_inlined = self.inlined;
......@@ -5137,7 +5215,8 @@ pub const FuncGen = struct {
51375215 .sp_flags = .{
51385216 .Optimized = mod.optimize_mode != .Debug,
51395217 .Definition = true,
5140 .LocalToUnit = true, // TODO: we can't know this at this point, since the function could be exported later!
5218 // TODO: we can't know this at this point, since the function could be exported later!
5219 .LocalToUnit = true,
51415220 },
51425221 },
51435222 o.debug_compile_unit,
......@@ -5171,7 +5250,7 @@ pub const FuncGen = struct {
51715250 .no_location => {},
51725251 };
51735252
5174 try self.genBody(body);
5253 try self.genBody(body, coverage_point);
51755254 }
51765255
51775256 pub const CallAttr = enum {
......@@ -5881,7 +5960,7 @@ pub const FuncGen = struct {
58815960 const inst_ty = self.typeOfIndex(inst);
58825961
58835962 if (inst_ty.isNoReturn(zcu)) {
5884 try self.genBodyDebugScope(maybe_inline_func, body);
5963 try self.genBodyDebugScope(maybe_inline_func, body, .none);
58855964 return .none;
58865965 }
58875966
......@@ -5897,7 +5976,7 @@ pub const FuncGen = struct {
58975976 });
58985977 defer assert(self.blocks.remove(inst));
58995978
5900 try self.genBodyDebugScope(maybe_inline_func, body);
5979 try self.genBodyDebugScope(maybe_inline_func, body, .none);
59015980
59025981 self.wip.cursor = .{ .block = parent_bb };
59035982
......@@ -5996,11 +6075,11 @@ pub const FuncGen = struct {
59966075
59976076 self.wip.cursor = .{ .block = then_block };
59986077 if (hint == .then_cold) _ = try self.wip.callIntrinsicAssumeCold();
5999 try self.genBodyDebugScope(null, then_body);
6078 try self.genBodyDebugScope(null, then_body, extra.data.branch_hints.then_cov);
60006079
60016080 self.wip.cursor = .{ .block = else_block };
60026081 if (hint == .else_cold) _ = try self.wip.callIntrinsicAssumeCold();
6003 try self.genBodyDebugScope(null, else_body);
6082 try self.genBodyDebugScope(null, else_body, extra.data.branch_hints.else_cov);
60046083
60056084 // No need to reset the insert cursor since this instruction is noreturn.
60066085 return .none;
......@@ -6085,7 +6164,7 @@ pub const FuncGen = struct {
60856164
60866165 fg.wip.cursor = .{ .block = return_block };
60876166 if (err_cold) _ = try fg.wip.callIntrinsicAssumeCold();
6088 try fg.genBodyDebugScope(null, body);
6167 try fg.genBodyDebugScope(null, body, .poi);
60896168
60906169 fg.wip.cursor = .{ .block = continue_block };
60916170 }
......@@ -6196,14 +6275,14 @@ pub const FuncGen = struct {
61966275 }
61976276 self.wip.cursor = .{ .block = case_block };
61986277 if (switch_br.getHint(case.idx) == .cold) _ = try self.wip.callIntrinsicAssumeCold();
6199 try self.genBodyDebugScope(null, case.body);
6278 try self.genBodyDebugScope(null, case.body, .poi);
62006279 }
62016280
62026281 const else_body = it.elseBody();
62036282 self.wip.cursor = .{ .block = else_block };
62046283 if (switch_br.getElseHint() == .cold) _ = try self.wip.callIntrinsicAssumeCold();
62056284 if (else_body.len != 0) {
6206 try self.genBodyDebugScope(null, else_body);
6285 try self.genBodyDebugScope(null, else_body, .poi);
62076286 } else {
62086287 _ = try self.wip.@"unreachable"();
62096288 }
......@@ -6222,7 +6301,7 @@ pub const FuncGen = struct {
62226301 _ = try self.wip.br(loop_block);
62236302
62246303 self.wip.cursor = .{ .block = loop_block };
6225 try self.genBodyDebugScope(null, body);
6304 try self.genBodyDebugScope(null, body, .none);
62266305
62276306 // TODO instead of this logic, change AIR to have the property that
62286307 // every block is guaranteed to end with a noreturn instruction.
......@@ -12194,3 +12273,7 @@ pub fn initializeLLVMTarget(arch: std.Target.Cpu.Arch) void {
1219412273 => unreachable,
1219512274 }
1219612275}
12276
12277fn sanCovPassEnabled(trace_pc_guard: bool) bool {
12278 return trace_pc_guard;
12279}
src/print_air.zig+6
......@@ -795,6 +795,9 @@ const Writer = struct {
795795 if (extra.data.branch_hints.true != .none) {
796796 try s.print(" {s}", .{@tagName(extra.data.branch_hints.true)});
797797 }
798 if (extra.data.branch_hints.then_cov != .none) {
799 try s.print(" {s}", .{@tagName(extra.data.branch_hints.then_cov)});
800 }
798801 try s.writeAll(" {\n");
799802 const old_indent = w.indent;
800803 w.indent += 2;
......@@ -814,6 +817,9 @@ const Writer = struct {
814817 if (extra.data.branch_hints.false != .none) {
815818 try s.print(" {s}", .{@tagName(extra.data.branch_hints.false)});
816819 }
820 if (extra.data.branch_hints.else_cov != .none) {
821 try s.print(" {s}", .{@tagName(extra.data.branch_hints.else_cov)});
822 }
817823 try s.writeAll(" {\n");
818824
819825 if (liveness_condbr.else_deaths.len != 0) {