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(...@@ -30,19 +30,6 @@ fn logOverride(
3030
31export threadlocal var __sancov_lowest_stack: usize = std.math.maxInt(usize);31export 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
46export fn __sanitizer_cov_trace_const_cmp1(arg1: u8, arg2: u8) void {33export fn __sanitizer_cov_trace_const_cmp1(arg1: u8, arg2: u8) void {
47 handleCmp(@returnAddress(), arg1, arg2);34 handleCmp(@returnAddress(), arg1, arg2);
48}35}
...@@ -105,7 +92,7 @@ const Fuzzer = struct {...@@ -105,7 +92,7 @@ const Fuzzer = struct {
105 gpa: Allocator,92 gpa: Allocator,
106 rng: std.Random.DefaultPrng,93 rng: std.Random.DefaultPrng,
107 input: std.ArrayListUnmanaged(u8),94 input: std.ArrayListUnmanaged(u8),
108 flagged_pcs: []const FlaggedPc,95 pcs: []const usize,
109 pc_counters: []u8,96 pc_counters: []u8,
110 n_runs: usize,97 n_runs: usize,
111 recent_cases: RunMap,98 recent_cases: RunMap,
...@@ -174,32 +161,18 @@ const Fuzzer = struct {...@@ -174,32 +161,18 @@ const Fuzzer = struct {
174 }161 }
175 };162 };
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
185 const Analysis = struct {164 const Analysis = struct {
186 score: usize,165 score: usize,
187 id: Run.Id,166 id: Run.Id,
188 };167 };
189168
190 fn init(f: *Fuzzer, cache_dir: std.fs.Dir) !void {169 fn init(f: *Fuzzer, cache_dir: std.fs.Dir, pc_counters: []u8, pcs: []const usize) !void {
191 const flagged_pcs = f.flagged_pcs;
192
193 f.cache_dir = cache_dir;170 f.cache_dir = cache_dir;
171 f.pc_counters = pc_counters;
172 f.pcs = pcs;
194173
195 // Choose a file name for the coverage based on a hash of the PCs that will be stored within.174 // Choose a file name for the coverage based on a hash of the PCs that will be stored within.
196 const pc_digest = d: {175 const pc_digest = std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(pcs));
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 };
203 f.coverage_id = pc_digest;176 f.coverage_id = pc_digest;
204 const hex_digest = std.fmt.hex(pc_digest);177 const hex_digest = std.fmt.hex(pc_digest);
205 const coverage_file_path = "v/" ++ hex_digest;178 const coverage_file_path = "v/" ++ hex_digest;
...@@ -213,12 +186,12 @@ const Fuzzer = struct {...@@ -213,12 +186,12 @@ const Fuzzer = struct {
213 .truncate = false,186 .truncate = false,
214 });187 });
215 defer coverage_file.close();188 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);
217 comptime assert(SeenPcsHeader.trailing[0] == .pc_bits_usize);190 comptime assert(SeenPcsHeader.trailing[0] == .pc_bits_usize);
218 comptime assert(SeenPcsHeader.trailing[1] == .pc_addr);191 comptime assert(SeenPcsHeader.trailing[1] == .pc_addr);
219 const bytes_len = @sizeOf(SeenPcsHeader) +192 const bytes_len = @sizeOf(SeenPcsHeader) +
220 n_bitset_elems * @sizeOf(usize) +193 n_bitset_elems * @sizeOf(usize) +
221 flagged_pcs.len * @sizeOf(usize);194 pcs.len * @sizeOf(usize);
222 const existing_len = coverage_file.getEndPos() catch |err| {195 const existing_len = coverage_file.getEndPos() catch |err| {
223 fatal("unable to check len of coverage file: {s}", .{@errorName(err)});196 fatal("unable to check len of coverage file: {s}", .{@errorName(err)});
224 };197 };
...@@ -233,12 +206,12 @@ const Fuzzer = struct {...@@ -233,12 +206,12 @@ const Fuzzer = struct {
233 fatal("unable to init coverage memory map: {s}", .{@errorName(err)});206 fatal("unable to init coverage memory map: {s}", .{@errorName(err)});
234 };207 };
235 if (existing_len != 0) {208 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)];
237 const existing_pcs = std.mem.bytesAsSlice(usize, existing_pcs_bytes);210 const existing_pcs = std.mem.bytesAsSlice(usize, existing_pcs_bytes);
238 for (existing_pcs, flagged_pcs, 0..) |old, new, i| {211 for (existing_pcs, pcs, 0..) |old, new, i| {
239 if (old != new.addr) {212 if (old != new) {
240 fatal("incompatible existing coverage file (differing PC at index {d}: {x} != {x})", .{213 fatal("incompatible existing coverage file (differing PC at index {d}: {x} != {x})", .{
241 i, old, new.addr,214 i, old, new,
242 });215 });
243 }216 }
244 }217 }
...@@ -246,14 +219,12 @@ const Fuzzer = struct {...@@ -246,14 +219,12 @@ const Fuzzer = struct {
246 const header: SeenPcsHeader = .{219 const header: SeenPcsHeader = .{
247 .n_runs = 0,220 .n_runs = 0,
248 .unique_runs = 0,221 .unique_runs = 0,
249 .pcs_len = flagged_pcs.len,222 .pcs_len = pcs.len,
250 .lowest_stack = std.math.maxInt(usize),223 .lowest_stack = std.math.maxInt(usize),
251 };224 };
252 f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&header));225 f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&header));
253 f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems * @sizeOf(usize));226 f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems * @sizeOf(usize));
254 for (flagged_pcs) |flagged_pc| {227 f.seen_pcs.appendSliceAssumeCapacity(std.mem.sliceAsBytes(pcs));
255 f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&flagged_pc.addr));
256 }
257 }228 }
258 }229 }
259230
...@@ -307,8 +278,8 @@ const Fuzzer = struct {...@@ -307,8 +278,8 @@ const Fuzzer = struct {
307 // Track code coverage from all runs.278 // Track code coverage from all runs.
308 comptime assert(SeenPcsHeader.trailing[0] == .pc_bits_usize);279 comptime assert(SeenPcsHeader.trailing[0] == .pc_bits_usize);
309 const header_end_ptr: [*]volatile usize = @ptrCast(f.seen_pcs.items[@sizeOf(SeenPcsHeader)..]);280 const header_end_ptr: [*]volatile usize = @ptrCast(f.seen_pcs.items[@sizeOf(SeenPcsHeader)..]);
310 const remainder = f.flagged_pcs.len % @bitSizeOf(usize);281 const remainder = f.pcs.len % @bitSizeOf(usize);
311 const aligned_len = f.flagged_pcs.len - remainder;282 const aligned_len = f.pcs.len - remainder;
312 const seen_pcs = header_end_ptr[0..aligned_len];283 const seen_pcs = header_end_ptr[0..aligned_len];
313 const pc_counters = std.mem.bytesAsSlice([@bitSizeOf(usize)]u8, f.pc_counters[0..aligned_len]);284 const pc_counters = std.mem.bytesAsSlice([@bitSizeOf(usize)]u8, f.pc_counters[0..aligned_len]);
314 const V = @Vector(@bitSizeOf(usize), u8);285 const V = @Vector(@bitSizeOf(usize), u8);
...@@ -433,7 +404,7 @@ var fuzzer: Fuzzer = .{...@@ -433,7 +404,7 @@ var fuzzer: Fuzzer = .{
433 .gpa = general_purpose_allocator.allocator(),404 .gpa = general_purpose_allocator.allocator(),
434 .rng = std.Random.DefaultPrng.init(0),405 .rng = std.Random.DefaultPrng.init(0),
435 .input = .{},406 .input = .{},
436 .flagged_pcs = undefined,407 .pcs = undefined,
437 .pc_counters = undefined,408 .pc_counters = undefined,
438 .n_runs = 0,409 .n_runs = 0,
439 .recent_cases = .{},410 .recent_cases = .{},
...@@ -455,8 +426,32 @@ export fn fuzzer_next() Fuzzer.Slice {...@@ -455,8 +426,32 @@ export fn fuzzer_next() Fuzzer.Slice {
455}426}
456427
457export fn fuzzer_init(cache_dir_struct: Fuzzer.Slice) void {428export fn fuzzer_init(cache_dir_struct: Fuzzer.Slice) void {
458 if (module_count_8bc == 0) fatal("__sanitizer_cov_8bit_counters_init was never called", .{});429 // Linkers are expected to automatically add `__start_<section>` and
459 if (module_count_pcs == 0) fatal("__sanitizer_cov_pcs_init was never called", .{});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
461 const cache_dir_path = cache_dir_struct.toZig();456 const cache_dir_path = cache_dir_struct.toZig();
462 const cache_dir = if (cache_dir_path.len == 0)457 const cache_dir = if (cache_dir_path.len == 0)
...@@ -466,7 +461,8 @@ export fn fuzzer_init(cache_dir_struct: Fuzzer.Slice) void {...@@ -466,7 +461,8 @@ export fn fuzzer_init(cache_dir_struct: Fuzzer.Slice) void {
466 fatal("unable to open fuzz directory '{s}': {s}", .{ cache_dir_path, @errorName(err) });461 fatal("unable to open fuzz directory '{s}': {s}", .{ cache_dir_path, @errorName(err) });
467 };462 };
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)});
470}466}
471467
472/// Like `std.ArrayListUnmanaged(u8)` but backed by memory mapping.468/// Like `std.ArrayListUnmanaged(u8)` but backed by memory mapping.
src/Air.zig+12-1
...@@ -1126,7 +1126,9 @@ pub const CondBr = struct {...@@ -1126,7 +1126,9 @@ pub const CondBr = struct {
1126 pub const BranchHints = packed struct(u32) {1126 pub const BranchHints = packed struct(u32) {
1127 true: std.builtin.BranchHint,1127 true: std.builtin.BranchHint,
1128 false: std.builtin.BranchHint,1128 false: std.builtin.BranchHint,
1129 _: u26 = 0,1129 then_cov: CoveragePoint,
1130 else_cov: CoveragePoint,
1131 _: u24 = 0,
1130 };1132 };
1131};1133};
11321134
...@@ -1903,3 +1905,12 @@ pub fn unwrapSwitch(air: *const Air, switch_inst: Inst.Index) UnwrappedSwitch {...@@ -1903,3 +1905,12 @@ pub fn unwrapSwitch(air: *const Air, switch_inst: Inst.Index) UnwrappedSwitch {
1903pub const typesFullyResolved = types_resolved.typesFullyResolved;1905pub const typesFullyResolved = types_resolved.typesFullyResolved;
1904pub const typeFullyResolved = types_resolved.checkType;1906pub const typeFullyResolved = types_resolved.checkType;
1905pub const valFullyResolved = types_resolved.checkVal;1907pub 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(...@@ -6898,8 +6898,14 @@ fn popErrorReturnTrace(
6898 .payload = sema.addExtraAssumeCapacity(Air.CondBr{6898 .payload = sema.addExtraAssumeCapacity(Air.CondBr{
6899 .then_body_len = @intCast(then_block.instructions.items.len),6899 .then_body_len = @intCast(then_block.instructions.items.len),
6900 .else_body_len = @intCast(else_block.instructions.items.len),6900 .else_body_len = @intCast(else_block.instructions.items.len),
6901 // weight against error branch6901 .branch_hints = .{
6902 .branch_hints = .{ .true = .likely, .false = .unlikely },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 },
6903 }),6909 }),
6904 },6910 },
6905 },6911 },
...@@ -11796,14 +11802,22 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp...@@ -11796,14 +11802,22 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp
1179611802
11797 _ = try child_block.addInst(.{11803 _ = try child_block.addInst(.{
11798 .tag = .cond_br,11804 .tag = .cond_br,
11799 .data = .{ .pl_op = .{11805 .data = .{
11800 .operand = cond,11806 .pl_op = .{
11801 .payload = sema.addExtraAssumeCapacity(Air.CondBr{11807 .operand = cond,
11802 .then_body_len = @intCast(true_instructions.len),11808 .payload = sema.addExtraAssumeCapacity(Air.CondBr{
11803 .else_body_len = @intCast(sub_block.instructions.items.len),11809 .then_body_len = @intCast(true_instructions.len),
11804 .branch_hints = .{ .true = non_error_hint, .false = .none },11810 .else_body_len = @intCast(sub_block.instructions.items.len),
11805 }),11811 .branch_hints = .{
11806 } },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 },
11807 });11821 });
11808 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(true_instructions));11822 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(true_instructions));
11809 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(sub_block.instructions.items));11823 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(sub_block.instructions.items));
...@@ -12853,7 +12867,13 @@ fn analyzeSwitchRuntimeBlock(...@@ -12853,7 +12867,13 @@ fn analyzeSwitchRuntimeBlock(
12853 sema.air_instructions.items(.data)[@intFromEnum(prev_cond_br)].pl_op.payload = sema.addExtraAssumeCapacity(Air.CondBr{12867 sema.air_instructions.items(.data)[@intFromEnum(prev_cond_br)].pl_op.payload = sema.addExtraAssumeCapacity(Air.CondBr{
12854 .then_body_len = @intCast(prev_then_body.len),12868 .then_body_len = @intCast(prev_then_body.len),
12855 .else_body_len = @intCast(cond_body.len),12869 .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 },
12857 });12877 });
12858 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(prev_then_body));12878 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(prev_then_body));
12859 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(cond_body));12879 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(cond_body));
...@@ -13133,7 +13153,12 @@ fn analyzeSwitchRuntimeBlock(...@@ -13133,7 +13153,12 @@ fn analyzeSwitchRuntimeBlock(
13133 sema.air_instructions.items(.data)[@intFromEnum(prev_cond_br)].pl_op.payload = sema.addExtraAssumeCapacity(Air.CondBr{13153 sema.air_instructions.items(.data)[@intFromEnum(prev_cond_br)].pl_op.payload = sema.addExtraAssumeCapacity(Air.CondBr{
13134 .then_body_len = @intCast(prev_then_body.len),13154 .then_body_len = @intCast(prev_then_body.len),
13135 .else_body_len = @intCast(case_block.instructions.items.len),13155 .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 },
13137 });13162 });
13138 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(prev_then_body));13163 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(prev_then_body));
13139 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));13164 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
...@@ -19250,7 +19275,17 @@ fn zirBoolBr(...@@ -19250,7 +19275,17 @@ fn zirBoolBr(
19250 &else_block,19275 &else_block,
19251 lhs,19276 lhs,
19252 block_inst,19277 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 },
19254 );19289 );
19255 if (!rhs_noret) {19290 if (!rhs_noret) {
19256 if (try sema.resolveDefinedValue(rhs_block, rhs_src, coerced_rhs_result)) |rhs_val| {19291 if (try sema.resolveDefinedValue(rhs_block, rhs_src, coerced_rhs_result)) |rhs_val| {
...@@ -19467,14 +19502,22 @@ fn zirCondbr(...@@ -19467,14 +19502,22 @@ fn zirCondbr(
19467 true_instructions.len + sub_block.instructions.items.len);19502 true_instructions.len + sub_block.instructions.items.len);
19468 _ = try parent_block.addInst(.{19503 _ = try parent_block.addInst(.{
19469 .tag = .cond_br,19504 .tag = .cond_br,
19470 .data = .{ .pl_op = .{19505 .data = .{
19471 .operand = cond,19506 .pl_op = .{
19472 .payload = sema.addExtraAssumeCapacity(Air.CondBr{19507 .operand = cond,
19473 .then_body_len = @intCast(true_instructions.len),19508 .payload = sema.addExtraAssumeCapacity(Air.CondBr{
19474 .else_body_len = @intCast(sub_block.instructions.items.len),19509 .then_body_len = @intCast(true_instructions.len),
19475 .branch_hints = .{ .true = true_hint, .false = false_hint },19510 .else_body_len = @intCast(sub_block.instructions.items.len),
19476 }),19511 .branch_hints = .{
19477 } },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 },
19478 });19521 });
19479 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(true_instructions));19522 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(true_instructions));
19480 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(sub_block.instructions.items));19523 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(sub_block.instructions.items));
...@@ -19851,8 +19894,14 @@ fn retWithErrTracing(...@@ -19851,8 +19894,14 @@ fn retWithErrTracing(
19851 const cond_br_payload = sema.addExtraAssumeCapacity(Air.CondBr{19894 const cond_br_payload = sema.addExtraAssumeCapacity(Air.CondBr{
19852 .then_body_len = @intCast(then_block.instructions.items.len),19895 .then_body_len = @intCast(then_block.instructions.items.len),
19853 .else_body_len = @intCast(else_block.instructions.items.len),19896 .else_body_len = @intCast(else_block.instructions.items.len),
19854 // weight against error branch19897 .branch_hints = .{
19855 .branch_hints = .{ .true = .likely, .false = .unlikely },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 },
19856 });19905 });
19857 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(then_block.instructions.items));19906 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(then_block.instructions.items));
19858 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(else_block.instructions.items));19907 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(else_block.instructions.items));
...@@ -27473,8 +27522,14 @@ fn addSafetyCheckExtra(...@@ -27473,8 +27522,14 @@ fn addSafetyCheckExtra(
27473 .payload = sema.addExtraAssumeCapacity(Air.CondBr{27522 .payload = sema.addExtraAssumeCapacity(Air.CondBr{
27474 .then_body_len = 1,27523 .then_body_len = 1,
27475 .else_body_len = @intCast(fail_block.instructions.items.len),27524 .else_body_len = @intCast(fail_block.instructions.items.len),
27476 // safety check failure branch is cold27525 .branch_hints = .{
27477 .branch_hints = .{ .true = .likely, .false = .cold },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 },
27478 }),27533 }),
27479 },27534 },
27480 },27535 },
src/codegen/llvm.zig+107-24
...@@ -1275,7 +1275,7 @@ pub const Object = struct {...@@ -1275,7 +1275,7 @@ pub const Object = struct {
1275 .is_small = options.is_small,1275 .is_small = options.is_small,
1276 .time_report = options.time_report,1276 .time_report = options.time_report,
1277 .tsan = options.sanitize_thread,1277 .tsan = options.sanitize_thread,
1278 .sancov = options.fuzz,1278 .sancov = sanCovPassEnabled(comp.config.san_cov_trace_pc_guard),
1279 .lto = options.lto,1279 .lto = options.lto,
1280 .asm_filename = null,1280 .asm_filename = null,
1281 .bin_filename = options.bin_path,1281 .bin_filename = options.bin_path,
...@@ -1283,19 +1283,19 @@ pub const Object = struct {...@@ -1283,19 +1283,19 @@ pub const Object = struct {
1283 .bitcode_filename = null,1283 .bitcode_filename = null,
1284 .coverage = .{1284 .coverage = .{
1285 .CoverageType = .Edge,1285 .CoverageType = .Edge,
1286 .IndirectCalls = true,1286 .IndirectCalls = false,
1287 .TraceBB = false,1287 .TraceBB = false,
1288 .TraceCmp = true,1288 .TraceCmp = false,
1289 .TraceDiv = false,1289 .TraceDiv = false,
1290 .TraceGep = false,1290 .TraceGep = false,
1291 .Use8bitCounters = false,1291 .Use8bitCounters = false,
1292 .TracePC = false,1292 .TracePC = false,
1293 .TracePCGuard = comp.config.san_cov_trace_pc_guard,1293 .TracePCGuard = comp.config.san_cov_trace_pc_guard,
1294 .Inline8bitCounters = true,1294 .Inline8bitCounters = false,
1295 .InlineBoolFlag = false,1295 .InlineBoolFlag = false,
1296 .PCTable = true,1296 .PCTable = false,
1297 .NoPrune = false,1297 .NoPrune = false,
1298 .StackDepth = true,1298 .StackDepth = false,
1299 .TraceLoads = false,1299 .TraceLoads = false,
1300 .TraceStores = false,1300 .TraceStores = false,
1301 .CollectControlFlow = false,1301 .CollectControlFlow = false,
...@@ -1655,6 +1655,25 @@ pub const Object = struct {...@@ -1655,6 +1655,25 @@ pub const Object = struct {
1655 break :debug_info .{ file, subprogram };1655 break :debug_info .{ file, subprogram };
1656 } else .{.none} ** 2;1656 } 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
1658 var fg: FuncGen = .{1677 var fg: FuncGen = .{
1659 .gpa = gpa,1678 .gpa = gpa,
1660 .air = air,1679 .air = air,
...@@ -1662,6 +1681,7 @@ pub const Object = struct {...@@ -1662,6 +1681,7 @@ pub const Object = struct {
1662 .ng = &ng,1681 .ng = &ng,
1663 .wip = wip,1682 .wip = wip,
1664 .is_naked = fn_info.cc == .Naked,1683 .is_naked = fn_info.cc == .Naked,
1684 .fuzz = fuzz,
1665 .ret_ptr = ret_ptr,1685 .ret_ptr = ret_ptr,
1666 .args = args.items,1686 .args = args.items,
1667 .arg_index = 0,1687 .arg_index = 0,
...@@ -1679,7 +1699,7 @@ pub const Object = struct {...@@ -1679,7 +1699,7 @@ pub const Object = struct {
1679 defer fg.deinit();1699 defer fg.deinit();
1680 deinit_wip = false;1700 deinit_wip = false;
16811701
1682 fg.genBody(air.getMainBody()) catch |err| switch (err) {1702 fg.genBody(air.getMainBody(), .poi) catch |err| switch (err) {
1683 error.CodegenFail => {1703 error.CodegenFail => {
1684 try zcu.failed_codegen.put(zcu.gpa, func.owner_nav, ng.err_msg.?);1704 try zcu.failed_codegen.put(zcu.gpa, func.owner_nav, ng.err_msg.?);
1685 ng.err_msg = null;1705 ng.err_msg = null;
...@@ -1688,6 +1708,24 @@ pub const Object = struct {...@@ -1688,6 +1708,24 @@ pub const Object = struct {
1688 else => |e| return e,1708 else => |e| return e,
1689 };1709 };
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
1691 try fg.wip.finish();1729 try fg.wip.finish();
1692 }1730 }
16931731
...@@ -4729,6 +4767,7 @@ pub const FuncGen = struct {...@@ -4729,6 +4767,7 @@ pub const FuncGen = struct {
4729 liveness: Liveness,4767 liveness: Liveness,
4730 wip: Builder.WipFunction,4768 wip: Builder.WipFunction,
4731 is_naked: bool,4769 is_naked: bool,
4770 fuzz: ?Fuzz,
47324771
4733 file: Builder.Metadata,4772 file: Builder.Metadata,
4734 scope: Builder.Metadata,4773 scope: Builder.Metadata,
...@@ -4769,6 +4808,16 @@ pub const FuncGen = struct {...@@ -4769,6 +4808,16 @@ pub const FuncGen = struct {
47694808
4770 sync_scope: Builder.SyncScope,4809 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
4772 const BreakList = union {4821 const BreakList = union {
4773 list: std.MultiArrayList(struct {4822 list: std.MultiArrayList(struct {
4774 bb: Builder.Function.Block.Index,4823 bb: Builder.Function.Block.Index,
...@@ -4778,9 +4827,11 @@ pub const FuncGen = struct {...@@ -4778,9 +4827,11 @@ pub const FuncGen = struct {
4778 };4827 };
47794828
4780 fn deinit(self: *FuncGen) void {4829 fn deinit(self: *FuncGen) void {
4830 const gpa = self.gpa;
4831 if (self.fuzz) |*f| f.deinit(self.gpa);
4781 self.wip.deinit();4832 self.wip.deinit();
4782 self.func_inst_table.deinit(self.gpa);4833 self.func_inst_table.deinit(gpa);
4783 self.blocks.deinit(self.gpa);4834 self.blocks.deinit(gpa);
4784 }4835 }
47854836
4786 fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) Error {4837 fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) Error {
...@@ -4836,11 +4887,33 @@ pub const FuncGen = struct {...@@ -4836,11 +4887,33 @@ pub const FuncGen = struct {
4836 return o.null_opt_usize;4887 return o.null_opt_usize;
4837 }4888 }
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 {
4840 const o = self.ng.object;4891 const o = self.ng.object;
4841 const zcu = o.pt.zcu;4892 const zcu = o.pt.zcu;
4842 const ip = &zcu.intern_pool;4893 const ip = &zcu.intern_pool;
4843 const air_tags = self.air.instructions.items(.tag);4894 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 }
4844 for (body, 0..) |inst, i| {4917 for (body, 0..) |inst, i| {
4845 if (self.liveness.isUnused(inst) and !self.air.mustLower(inst, ip)) continue;4918 if (self.liveness.isUnused(inst) and !self.air.mustLower(inst, ip)) continue;
48464919
...@@ -4949,7 +5022,7 @@ pub const FuncGen = struct {...@@ -4949,7 +5022,7 @@ pub const FuncGen = struct {
4949 .ret_ptr => try self.airRetPtr(inst),5022 .ret_ptr => try self.airRetPtr(inst),
4950 .arg => try self.airArg(inst),5023 .arg => try self.airArg(inst),
4951 .bitcast => try self.airBitCast(inst),5024 .bitcast => try self.airBitCast(inst),
4952 .int_from_bool => try self.airIntFromBool(inst),5025 .int_from_bool => try self.airIntFromBool(inst),
4953 .block => try self.airBlock(inst),5026 .block => try self.airBlock(inst),
4954 .br => try self.airBr(inst),5027 .br => try self.airBr(inst),
4955 .switch_br => try self.airSwitchBr(inst),5028 .switch_br => try self.airSwitchBr(inst),
...@@ -4966,7 +5039,7 @@ pub const FuncGen = struct {...@@ -4966,7 +5039,7 @@ pub const FuncGen = struct {
4966 .trunc => try self.airTrunc(inst),5039 .trunc => try self.airTrunc(inst),
4967 .fptrunc => try self.airFptrunc(inst),5040 .fptrunc => try self.airFptrunc(inst),
4968 .fpext => try self.airFpext(inst),5041 .fpext => try self.airFpext(inst),
4969 .int_from_ptr => try self.airIntFromPtr(inst),5042 .int_from_ptr => try self.airIntFromPtr(inst),
4970 .load => try self.airLoad(body[i..]),5043 .load => try self.airLoad(body[i..]),
4971 .loop => try self.airLoop(inst),5044 .loop => try self.airLoop(inst),
4972 .not => try self.airNot(inst),5045 .not => try self.airNot(inst),
...@@ -5089,8 +5162,13 @@ pub const FuncGen = struct {...@@ -5089,8 +5162,13 @@ pub const FuncGen = struct {
5089 }5162 }
5090 }5163 }
50915164
5092 fn genBodyDebugScope(self: *FuncGen, maybe_inline_func: ?InternPool.Index, body: []const Air.Inst.Index) Error!void {5165 fn genBodyDebugScope(
5093 if (self.wip.strip) return self.genBody(body);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
5095 const old_file = self.file;5173 const old_file = self.file;
5096 const old_inlined = self.inlined;5174 const old_inlined = self.inlined;
...@@ -5137,7 +5215,8 @@ pub const FuncGen = struct {...@@ -5137,7 +5215,8 @@ pub const FuncGen = struct {
5137 .sp_flags = .{5215 .sp_flags = .{
5138 .Optimized = mod.optimize_mode != .Debug,5216 .Optimized = mod.optimize_mode != .Debug,
5139 .Definition = true,5217 .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,
5141 },5220 },
5142 },5221 },
5143 o.debug_compile_unit,5222 o.debug_compile_unit,
...@@ -5171,7 +5250,7 @@ pub const FuncGen = struct {...@@ -5171,7 +5250,7 @@ pub const FuncGen = struct {
5171 .no_location => {},5250 .no_location => {},
5172 };5251 };
51735252
5174 try self.genBody(body);5253 try self.genBody(body, coverage_point);
5175 }5254 }
51765255
5177 pub const CallAttr = enum {5256 pub const CallAttr = enum {
...@@ -5881,7 +5960,7 @@ pub const FuncGen = struct {...@@ -5881,7 +5960,7 @@ pub const FuncGen = struct {
5881 const inst_ty = self.typeOfIndex(inst);5960 const inst_ty = self.typeOfIndex(inst);
58825961
5883 if (inst_ty.isNoReturn(zcu)) {5962 if (inst_ty.isNoReturn(zcu)) {
5884 try self.genBodyDebugScope(maybe_inline_func, body);5963 try self.genBodyDebugScope(maybe_inline_func, body, .none);
5885 return .none;5964 return .none;
5886 }5965 }
58875966
...@@ -5897,7 +5976,7 @@ pub const FuncGen = struct {...@@ -5897,7 +5976,7 @@ pub const FuncGen = struct {
5897 });5976 });
5898 defer assert(self.blocks.remove(inst));5977 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
5902 self.wip.cursor = .{ .block = parent_bb };5981 self.wip.cursor = .{ .block = parent_bb };
59035982
...@@ -5996,11 +6075,11 @@ pub const FuncGen = struct {...@@ -5996,11 +6075,11 @@ pub const FuncGen = struct {
59966075
5997 self.wip.cursor = .{ .block = then_block };6076 self.wip.cursor = .{ .block = then_block };
5998 if (hint == .then_cold) _ = try self.wip.callIntrinsicAssumeCold();6077 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
6001 self.wip.cursor = .{ .block = else_block };6080 self.wip.cursor = .{ .block = else_block };
6002 if (hint == .else_cold) _ = try self.wip.callIntrinsicAssumeCold();6081 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
6005 // No need to reset the insert cursor since this instruction is noreturn.6084 // No need to reset the insert cursor since this instruction is noreturn.
6006 return .none;6085 return .none;
...@@ -6085,7 +6164,7 @@ pub const FuncGen = struct {...@@ -6085,7 +6164,7 @@ pub const FuncGen = struct {
60856164
6086 fg.wip.cursor = .{ .block = return_block };6165 fg.wip.cursor = .{ .block = return_block };
6087 if (err_cold) _ = try fg.wip.callIntrinsicAssumeCold();6166 if (err_cold) _ = try fg.wip.callIntrinsicAssumeCold();
6088 try fg.genBodyDebugScope(null, body);6167 try fg.genBodyDebugScope(null, body, .poi);
60896168
6090 fg.wip.cursor = .{ .block = continue_block };6169 fg.wip.cursor = .{ .block = continue_block };
6091 }6170 }
...@@ -6196,14 +6275,14 @@ pub const FuncGen = struct {...@@ -6196,14 +6275,14 @@ pub const FuncGen = struct {
6196 }6275 }
6197 self.wip.cursor = .{ .block = case_block };6276 self.wip.cursor = .{ .block = case_block };
6198 if (switch_br.getHint(case.idx) == .cold) _ = try self.wip.callIntrinsicAssumeCold();6277 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);
6200 }6279 }
62016280
6202 const else_body = it.elseBody();6281 const else_body = it.elseBody();
6203 self.wip.cursor = .{ .block = else_block };6282 self.wip.cursor = .{ .block = else_block };
6204 if (switch_br.getElseHint() == .cold) _ = try self.wip.callIntrinsicAssumeCold();6283 if (switch_br.getElseHint() == .cold) _ = try self.wip.callIntrinsicAssumeCold();
6205 if (else_body.len != 0) {6284 if (else_body.len != 0) {
6206 try self.genBodyDebugScope(null, else_body);6285 try self.genBodyDebugScope(null, else_body, .poi);
6207 } else {6286 } else {
6208 _ = try self.wip.@"unreachable"();6287 _ = try self.wip.@"unreachable"();
6209 }6288 }
...@@ -6222,7 +6301,7 @@ pub const FuncGen = struct {...@@ -6222,7 +6301,7 @@ pub const FuncGen = struct {
6222 _ = try self.wip.br(loop_block);6301 _ = try self.wip.br(loop_block);
62236302
6224 self.wip.cursor = .{ .block = loop_block };6303 self.wip.cursor = .{ .block = loop_block };
6225 try self.genBodyDebugScope(null, body);6304 try self.genBodyDebugScope(null, body, .none);
62266305
6227 // TODO instead of this logic, change AIR to have the property that6306 // TODO instead of this logic, change AIR to have the property that
6228 // every block is guaranteed to end with a noreturn instruction.6307 // every block is guaranteed to end with a noreturn instruction.
...@@ -12194,3 +12273,7 @@ pub fn initializeLLVMTarget(arch: std.Target.Cpu.Arch) void {...@@ -12194,3 +12273,7 @@ pub fn initializeLLVMTarget(arch: std.Target.Cpu.Arch) void {
12194 => unreachable,12273 => unreachable,
12195 }12274 }
12196}12275}
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 {...@@ -795,6 +795,9 @@ const Writer = struct {
795 if (extra.data.branch_hints.true != .none) {795 if (extra.data.branch_hints.true != .none) {
796 try s.print(" {s}", .{@tagName(extra.data.branch_hints.true)});796 try s.print(" {s}", .{@tagName(extra.data.branch_hints.true)});
797 }797 }
798 if (extra.data.branch_hints.then_cov != .none) {
799 try s.print(" {s}", .{@tagName(extra.data.branch_hints.then_cov)});
800 }
798 try s.writeAll(" {\n");801 try s.writeAll(" {\n");
799 const old_indent = w.indent;802 const old_indent = w.indent;
800 w.indent += 2;803 w.indent += 2;
...@@ -814,6 +817,9 @@ const Writer = struct {...@@ -814,6 +817,9 @@ const Writer = struct {
814 if (extra.data.branch_hints.false != .none) {817 if (extra.data.branch_hints.false != .none) {
815 try s.print(" {s}", .{@tagName(extra.data.branch_hints.false)});818 try s.print(" {s}", .{@tagName(extra.data.branch_hints.false)});
816 }819 }
820 if (extra.data.branch_hints.else_cov != .none) {
821 try s.print(" {s}", .{@tagName(extra.data.branch_hints.else_cov)});
822 }
817 try s.writeAll(" {\n");823 try s.writeAll(" {\n");
818824
819 if (liveness_condbr.else_deaths.len != 0) {825 if (liveness_condbr.else_deaths.len != 0) {