authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-28 23:20:21-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-08-28 23:20:21-07:00
loge9a00ba7f4ef2546cd0c98559002431c749374fe
tree18102a8fd19ea54f1049ba6a0be522391a2bb7c7
parent6a21875ddbe0f509122fbd220f1abb015cc7bac7
parent13b5cee4cce2be7b5d1423fcd59b00ff1807142e
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #21236 from ziglang/fuzz

exclude unreachable code paths from having coverage instrumentation

9 files changed, 333 insertions(+), 137 deletions(-)

lib/compiler/test_runner.zig+1-2
......@@ -166,6 +166,7 @@ fn mainServer() !void {
166166 if (log_err_count != 0) @panic("error logs detected");
167167 if (first) {
168168 first = false;
169 const entry_addr = @intFromPtr(test_fn.func);
169170 try server.serveU64Message(.fuzz_start_addr, entry_addr);
170171 }
171172 }
......@@ -347,7 +348,6 @@ const FuzzerSlice = extern struct {
347348};
348349
349350var is_fuzz_test: bool = undefined;
350var entry_addr: usize = 0;
351351
352352extern fn fuzzer_next() FuzzerSlice;
353353extern fn fuzzer_init(cache_dir: FuzzerSlice) void;
......@@ -358,7 +358,6 @@ pub fn fuzzInput(options: testing.FuzzInputOptions) []const u8 {
358358 if (crippled) return "";
359359 is_fuzz_test = true;
360360 if (builtin.fuzz) {
361 if (entry_addr == 0) entry_addr = @returnAddress();
362361 return fuzzer_next().toSlice();
363362 }
364363 if (options.corpus.len == 0) return "";
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.
lib/std/Build/Fuzz/WebServer.zig+11-6
......@@ -664,11 +664,16 @@ fn addEntryPoint(ws: *WebServer, coverage_id: u64, addr: u64) error{ AlreadyRepo
664664 const coverage_map = ws.coverage_files.getPtr(coverage_id).?;
665665 const header: *const abi.SeenPcsHeader = @ptrCast(coverage_map.mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]);
666666 const pcs = header.pcAddrs();
667 const index = std.sort.upperBound(usize, pcs, addr, struct {
668 fn order(context: usize, item: usize) std.math.Order {
669 return std.math.order(item, context);
667 // Since this pcs list is unsorted, we must linear scan for the best index.
668 const index = i: {
669 var best: usize = 0;
670 for (pcs[1..], 1..) |elem_addr, i| {
671 if (elem_addr == addr) break :i i;
672 if (elem_addr > addr) continue;
673 if (elem_addr > pcs[best]) best = i;
670674 }
671 }.order);
675 break :i best;
676 };
672677 if (index >= pcs.len) {
673678 log.err("unable to find unit test entry address 0x{x} in source locations (range: 0x{x} to 0x{x})", .{
674679 addr, pcs[0], pcs[pcs.len - 1],
......@@ -678,8 +683,8 @@ fn addEntryPoint(ws: *WebServer, coverage_id: u64, addr: u64) error{ AlreadyRepo
678683 if (false) {
679684 const sl = coverage_map.source_locations[index];
680685 const file_name = coverage_map.coverage.stringAt(coverage_map.coverage.fileAt(sl.file).basename);
681 log.debug("server found entry point for 0x{x} at {s}:{d}:{d}", .{
682 addr, file_name, sl.line, sl.column,
686 log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index {d} between {x} and {x}", .{
687 addr, file_name, sl.line, sl.column, index, pcs[index - 1], pcs[index + 1],
683688 });
684689 }
685690 const gpa = ws.gpa;
lib/std/Build/Step/Compile.zig+11-5
......@@ -218,12 +218,18 @@ no_builtin: bool = false,
218218/// Managed by the build runner, not user build script.
219219zig_process: ?*Step.ZigProcess,
220220
221/// Enables deprecated coverage instrumentation that is only useful if you
222/// are using third party fuzzers that depend on it. Otherwise, slows down
223/// the instrumented binary with unnecessary function calls.
221/// Enables coverage instrumentation that is only useful if you are using third
222/// party fuzzers that depend on it. Otherwise, slows down the instrumented
223/// binary with unnecessary function calls.
224224///
225/// To enable fuzz testing instrumentation on a compilation, see the `fuzz`
226/// flag in `Module`.
225/// This kind of coverage instrumentation is used by AFLplusplus v4.21c,
226/// however, modern fuzzers - including Zig - have switched to using "inline
227/// 8-bit counters" or "inline bool flag" which incurs only a single
228/// instruction for coverage, along with "trace cmp" which instruments
229/// comparisons and reports the operands.
230///
231/// To instead enable fuzz testing instrumentation on a compilation using Zig's
232/// builtin fuzzer, see the `fuzz` flag in `Module`.
227233sanitize_coverage_trace_pc_guard: ?bool = null,
228234
229235pub const ExpectedCompileErrors = union(enum) {
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+155-46
......@@ -822,6 +822,9 @@ pub const Object = struct {
822822 /// This is denormalized data.
823823 struct_field_map: std.AutoHashMapUnmanaged(ZigStructField, c_uint),
824824
825 /// Values for `@llvm.used`.
826 used: std.ArrayListUnmanaged(Builder.Constant),
827
825828 const ZigStructField = struct {
826829 struct_ty: InternPool.Index,
827830 field_index: u32,
......@@ -975,6 +978,7 @@ pub const Object = struct {
975978 .error_name_table = .none,
976979 .null_opt_usize = .no_init,
977980 .struct_field_map = .{},
981 .used = .{},
978982 };
979983 return obj;
980984 }
......@@ -1097,44 +1101,57 @@ pub const Object = struct {
10971101 lto: bool,
10981102 };
10991103
1100 pub fn emit(self: *Object, options: EmitOptions) !void {
1101 const zcu = self.pt.zcu;
1104 pub fn emit(o: *Object, options: EmitOptions) !void {
1105 const zcu = o.pt.zcu;
11021106 const comp = zcu.comp;
11031107
11041108 {
1105 try self.genErrorNameTable();
1106 try self.genCmpLtErrorsLenFunction();
1107 try self.genModuleLevelAssembly();
1109 try o.genErrorNameTable();
1110 try o.genCmpLtErrorsLenFunction();
1111 try o.genModuleLevelAssembly();
1112
1113 if (o.used.items.len > 0) {
1114 const array_llvm_ty = try o.builder.arrayType(o.used.items.len, .ptr);
1115 const init_val = try o.builder.arrayConst(array_llvm_ty, o.used.items);
1116 const compiler_used_variable = try o.builder.addVariable(
1117 try o.builder.strtabString("llvm.used"),
1118 array_llvm_ty,
1119 .default,
1120 );
1121 compiler_used_variable.setLinkage(.appending, &o.builder);
1122 compiler_used_variable.setSection(try o.builder.string("llvm.metadata"), &o.builder);
1123 try compiler_used_variable.setInitializer(init_val, &o.builder);
1124 }
11081125
1109 if (!self.builder.strip) {
1126 if (!o.builder.strip) {
11101127 {
11111128 var i: usize = 0;
1112 while (i < self.debug_unresolved_namespace_scopes.count()) : (i += 1) {
1113 const namespace_index = self.debug_unresolved_namespace_scopes.keys()[i];
1114 const fwd_ref = self.debug_unresolved_namespace_scopes.values()[i];
1129 while (i < o.debug_unresolved_namespace_scopes.count()) : (i += 1) {
1130 const namespace_index = o.debug_unresolved_namespace_scopes.keys()[i];
1131 const fwd_ref = o.debug_unresolved_namespace_scopes.values()[i];
11151132
11161133 const namespace = zcu.namespacePtr(namespace_index);
1117 const debug_type = try self.lowerDebugType(Type.fromInterned(namespace.owner_type));
1134 const debug_type = try o.lowerDebugType(Type.fromInterned(namespace.owner_type));
11181135
1119 self.builder.debugForwardReferenceSetType(fwd_ref, debug_type);
1136 o.builder.debugForwardReferenceSetType(fwd_ref, debug_type);
11201137 }
11211138 }
11221139
1123 self.builder.debugForwardReferenceSetType(
1124 self.debug_enums_fwd_ref,
1125 try self.builder.metadataTuple(self.debug_enums.items),
1140 o.builder.debugForwardReferenceSetType(
1141 o.debug_enums_fwd_ref,
1142 try o.builder.metadataTuple(o.debug_enums.items),
11261143 );
11271144
1128 self.builder.debugForwardReferenceSetType(
1129 self.debug_globals_fwd_ref,
1130 try self.builder.metadataTuple(self.debug_globals.items),
1145 o.builder.debugForwardReferenceSetType(
1146 o.debug_globals_fwd_ref,
1147 try o.builder.metadataTuple(o.debug_globals.items),
11311148 );
11321149 }
11331150 }
11341151
11351152 const target_triple_sentinel =
1136 try self.gpa.dupeZ(u8, self.builder.target_triple.slice(&self.builder).?);
1137 defer self.gpa.free(target_triple_sentinel);
1153 try o.gpa.dupeZ(u8, o.builder.target_triple.slice(&o.builder).?);
1154 defer o.gpa.free(target_triple_sentinel);
11381155
11391156 const emit_asm_msg = options.asm_path orelse "(none)";
11401157 const emit_bin_msg = options.bin_path orelse "(none)";
......@@ -1147,15 +1164,15 @@ pub const Object = struct {
11471164 const context, const module = emit: {
11481165 if (options.pre_ir_path) |path| {
11491166 if (std.mem.eql(u8, path, "-")) {
1150 self.builder.dump();
1167 o.builder.dump();
11511168 } else {
1152 _ = try self.builder.printToFile(path);
1169 _ = try o.builder.printToFile(path);
11531170 }
11541171 }
11551172
1156 const bitcode = try self.builder.toBitcode(self.gpa);
1157 defer self.gpa.free(bitcode);
1158 self.builder.clearAndFree();
1173 const bitcode = try o.builder.toBitcode(o.gpa);
1174 defer o.gpa.free(bitcode);
1175 o.builder.clearAndFree();
11591176
11601177 if (options.pre_bc_path) |path| {
11611178 var file = try std.fs.cwd().createFile(path, .{});
......@@ -1283,7 +1300,10 @@ pub const Object = struct {
12831300 .bitcode_filename = null,
12841301 .coverage = .{
12851302 .CoverageType = .Edge,
1286 .IndirectCalls = true,
1303 // Works in tandem with Inline8bitCounters or InlineBoolFlag.
1304 // Zig does not yet implement its own version of this but it
1305 // needs to for better fuzzing logic.
1306 .IndirectCalls = false,
12871307 .TraceBB = false,
12881308 .TraceCmp = true,
12891309 .TraceDiv = false,
......@@ -1291,10 +1311,13 @@ pub const Object = struct {
12911311 .Use8bitCounters = false,
12921312 .TracePC = false,
12931313 .TracePCGuard = comp.config.san_cov_trace_pc_guard,
1294 .Inline8bitCounters = true,
1314 // Zig emits its own inline 8-bit counters instrumentation.
1315 .Inline8bitCounters = false,
12951316 .InlineBoolFlag = false,
1296 .PCTable = true,
1317 // Zig emits its own PC table instrumentation.
1318 .PCTable = false,
12971319 .NoPrune = false,
1320 // Workaround for https://github.com/llvm/llvm-project/pull/106464
12981321 .StackDepth = true,
12991322 .TraceLoads = false,
13001323 .TraceStores = false,
......@@ -1655,6 +1678,29 @@ pub const Object = struct {
16551678 break :debug_info .{ file, subprogram };
16561679 } else .{.none} ** 2;
16571680
1681 const fuzz: ?FuncGen.Fuzz = f: {
1682 if (!owner_mod.fuzz) break :f null;
1683 if (func_analysis.disable_instrumentation) break :f null;
1684 if (is_naked) break :f null;
1685 if (comp.config.san_cov_trace_pc_guard) break :f null;
1686
1687 // The void type used here is a placeholder to be replaced with an
1688 // array of the appropriate size after the POI count is known.
1689
1690 // Due to error "members of llvm.compiler.used must be named", this global needs a name.
1691 const anon_name = try o.builder.strtabStringFmt("__sancov_gen_.{d}", .{o.used.items.len});
1692 const counters_variable = try o.builder.addVariable(anon_name, .void, .default);
1693 try o.used.append(gpa, counters_variable.toConst(&o.builder));
1694 counters_variable.setLinkage(.private, &o.builder);
1695 counters_variable.setAlignment(comptime Builder.Alignment.fromByteUnits(1), &o.builder);
1696 counters_variable.setSection(try o.builder.string("__sancov_cntrs"), &o.builder);
1697
1698 break :f .{
1699 .counters_variable = counters_variable,
1700 .pcs = .{},
1701 };
1702 };
1703
16581704 var fg: FuncGen = .{
16591705 .gpa = gpa,
16601706 .air = air,
......@@ -1662,6 +1708,7 @@ pub const Object = struct {
16621708 .ng = &ng,
16631709 .wip = wip,
16641710 .is_naked = fn_info.cc == .Naked,
1711 .fuzz = fuzz,
16651712 .ret_ptr = ret_ptr,
16661713 .args = args.items,
16671714 .arg_index = 0,
......@@ -1679,15 +1726,36 @@ pub const Object = struct {
16791726 defer fg.deinit();
16801727 deinit_wip = false;
16811728
1682 fg.genBody(air.getMainBody()) catch |err| switch (err) {
1729 fg.genBody(air.getMainBody(), .poi) catch |err| switch (err) {
16831730 error.CodegenFail => {
1684 try zcu.failed_codegen.put(zcu.gpa, func.owner_nav, ng.err_msg.?);
1731 try zcu.failed_codegen.put(gpa, func.owner_nav, ng.err_msg.?);
16851732 ng.err_msg = null;
16861733 return;
16871734 },
16881735 else => |e| return e,
16891736 };
16901737
1738 if (fg.fuzz) |*f| {
1739 {
1740 const array_llvm_ty = try o.builder.arrayType(f.pcs.items.len, .i8);
1741 f.counters_variable.ptrConst(&o.builder).global.ptr(&o.builder).type = array_llvm_ty;
1742 const zero_init = try o.builder.zeroInitConst(array_llvm_ty);
1743 try f.counters_variable.setInitializer(zero_init, &o.builder);
1744 }
1745
1746 const array_llvm_ty = try o.builder.arrayType(f.pcs.items.len, .ptr);
1747 const init_val = try o.builder.arrayConst(array_llvm_ty, f.pcs.items);
1748 // Due to error "members of llvm.compiler.used must be named", this global needs a name.
1749 const anon_name = try o.builder.strtabStringFmt("__sancov_gen_.{d}", .{o.used.items.len});
1750 const pcs_variable = try o.builder.addVariable(anon_name, array_llvm_ty, .default);
1751 try o.used.append(gpa, pcs_variable.toConst(&o.builder));
1752 pcs_variable.setLinkage(.private, &o.builder);
1753 pcs_variable.setMutability(.constant, &o.builder);
1754 pcs_variable.setAlignment(Type.usize.abiAlignment(zcu).toLlvm(), &o.builder);
1755 pcs_variable.setSection(try o.builder.string("__sancov_pcs1"), &o.builder);
1756 try pcs_variable.setInitializer(init_val, &o.builder);
1757 }
1758
16911759 try fg.wip.finish();
16921760 }
16931761
......@@ -4729,6 +4797,7 @@ pub const FuncGen = struct {
47294797 liveness: Liveness,
47304798 wip: Builder.WipFunction,
47314799 is_naked: bool,
4800 fuzz: ?Fuzz,
47324801
47334802 file: Builder.Metadata,
47344803 scope: Builder.Metadata,
......@@ -4769,6 +4838,16 @@ pub const FuncGen = struct {
47694838
47704839 sync_scope: Builder.SyncScope,
47714840
4841 const Fuzz = struct {
4842 counters_variable: Builder.Variable.Index,
4843 pcs: std.ArrayListUnmanaged(Builder.Constant),
4844
4845 fn deinit(f: *Fuzz, gpa: Allocator) void {
4846 f.pcs.deinit(gpa);
4847 f.* = undefined;
4848 }
4849 };
4850
47724851 const BreakList = union {
47734852 list: std.MultiArrayList(struct {
47744853 bb: Builder.Function.Block.Index,
......@@ -4778,9 +4857,11 @@ pub const FuncGen = struct {
47784857 };
47794858
47804859 fn deinit(self: *FuncGen) void {
4860 const gpa = self.gpa;
4861 if (self.fuzz) |*f| f.deinit(self.gpa);
47814862 self.wip.deinit();
4782 self.func_inst_table.deinit(self.gpa);
4783 self.blocks.deinit(self.gpa);
4863 self.func_inst_table.deinit(gpa);
4864 self.blocks.deinit(gpa);
47844865 }
47854866
47864867 fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) Error {
......@@ -4836,11 +4917,33 @@ pub const FuncGen = struct {
48364917 return o.null_opt_usize;
48374918 }
48384919
4839 fn genBody(self: *FuncGen, body: []const Air.Inst.Index) Error!void {
4920 fn genBody(self: *FuncGen, body: []const Air.Inst.Index, coverage_point: Air.CoveragePoint) Error!void {
48404921 const o = self.ng.object;
48414922 const zcu = o.pt.zcu;
48424923 const ip = &zcu.intern_pool;
48434924 const air_tags = self.air.instructions.items(.tag);
4925 switch (coverage_point) {
4926 .none => {},
4927 .poi => if (self.fuzz) |*fuzz| {
4928 const poi_index = fuzz.pcs.items.len;
4929 const base_ptr = fuzz.counters_variable.toValue(&o.builder);
4930 const ptr = if (poi_index == 0) base_ptr else try self.wip.gep(.inbounds, .i8, base_ptr, &.{
4931 try o.builder.intValue(.i32, poi_index),
4932 }, "");
4933 const counter = try self.wip.load(.normal, .i8, ptr, .default, "");
4934 const one = try o.builder.intValue(.i8, 1);
4935 const counter_incremented = try self.wip.bin(.add, counter, one, "");
4936 _ = try self.wip.store(.normal, counter_incremented, ptr, .default);
4937
4938 // LLVM does not allow blockaddress on the entry block.
4939 const pc = if (self.wip.cursor.block == .entry)
4940 self.wip.function.toConst(&o.builder)
4941 else
4942 try o.builder.blockAddrConst(self.wip.function, self.wip.cursor.block);
4943 const gpa = self.gpa;
4944 try fuzz.pcs.append(gpa, pc);
4945 },
4946 }
48444947 for (body, 0..) |inst, i| {
48454948 if (self.liveness.isUnused(inst) and !self.air.mustLower(inst, ip)) continue;
48464949
......@@ -4949,7 +5052,7 @@ pub const FuncGen = struct {
49495052 .ret_ptr => try self.airRetPtr(inst),
49505053 .arg => try self.airArg(inst),
49515054 .bitcast => try self.airBitCast(inst),
4952 .int_from_bool => try self.airIntFromBool(inst),
5055 .int_from_bool => try self.airIntFromBool(inst),
49535056 .block => try self.airBlock(inst),
49545057 .br => try self.airBr(inst),
49555058 .switch_br => try self.airSwitchBr(inst),
......@@ -4966,7 +5069,7 @@ pub const FuncGen = struct {
49665069 .trunc => try self.airTrunc(inst),
49675070 .fptrunc => try self.airFptrunc(inst),
49685071 .fpext => try self.airFpext(inst),
4969 .int_from_ptr => try self.airIntFromPtr(inst),
5072 .int_from_ptr => try self.airIntFromPtr(inst),
49705073 .load => try self.airLoad(body[i..]),
49715074 .loop => try self.airLoop(inst),
49725075 .not => try self.airNot(inst),
......@@ -5089,8 +5192,13 @@ pub const FuncGen = struct {
50895192 }
50905193 }
50915194
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);
5195 fn genBodyDebugScope(
5196 self: *FuncGen,
5197 maybe_inline_func: ?InternPool.Index,
5198 body: []const Air.Inst.Index,
5199 coverage_point: Air.CoveragePoint,
5200 ) Error!void {
5201 if (self.wip.strip) return self.genBody(body, coverage_point);
50945202
50955203 const old_file = self.file;
50965204 const old_inlined = self.inlined;
......@@ -5137,7 +5245,8 @@ pub const FuncGen = struct {
51375245 .sp_flags = .{
51385246 .Optimized = mod.optimize_mode != .Debug,
51395247 .Definition = true,
5140 .LocalToUnit = true, // TODO: we can't know this at this point, since the function could be exported later!
5248 // TODO: we can't know this at this point, since the function could be exported later!
5249 .LocalToUnit = true,
51415250 },
51425251 },
51435252 o.debug_compile_unit,
......@@ -5171,7 +5280,7 @@ pub const FuncGen = struct {
51715280 .no_location => {},
51725281 };
51735282
5174 try self.genBody(body);
5283 try self.genBody(body, coverage_point);
51755284 }
51765285
51775286 pub const CallAttr = enum {
......@@ -5881,7 +5990,7 @@ pub const FuncGen = struct {
58815990 const inst_ty = self.typeOfIndex(inst);
58825991
58835992 if (inst_ty.isNoReturn(zcu)) {
5884 try self.genBodyDebugScope(maybe_inline_func, body);
5993 try self.genBodyDebugScope(maybe_inline_func, body, .none);
58855994 return .none;
58865995 }
58875996
......@@ -5897,7 +6006,7 @@ pub const FuncGen = struct {
58976006 });
58986007 defer assert(self.blocks.remove(inst));
58996008
5900 try self.genBodyDebugScope(maybe_inline_func, body);
6009 try self.genBodyDebugScope(maybe_inline_func, body, .none);
59016010
59026011 self.wip.cursor = .{ .block = parent_bb };
59036012
......@@ -5996,11 +6105,11 @@ pub const FuncGen = struct {
59966105
59976106 self.wip.cursor = .{ .block = then_block };
59986107 if (hint == .then_cold) _ = try self.wip.callIntrinsicAssumeCold();
5999 try self.genBodyDebugScope(null, then_body);
6108 try self.genBodyDebugScope(null, then_body, extra.data.branch_hints.then_cov);
60006109
60016110 self.wip.cursor = .{ .block = else_block };
60026111 if (hint == .else_cold) _ = try self.wip.callIntrinsicAssumeCold();
6003 try self.genBodyDebugScope(null, else_body);
6112 try self.genBodyDebugScope(null, else_body, extra.data.branch_hints.else_cov);
60046113
60056114 // No need to reset the insert cursor since this instruction is noreturn.
60066115 return .none;
......@@ -6085,7 +6194,7 @@ pub const FuncGen = struct {
60856194
60866195 fg.wip.cursor = .{ .block = return_block };
60876196 if (err_cold) _ = try fg.wip.callIntrinsicAssumeCold();
6088 try fg.genBodyDebugScope(null, body);
6197 try fg.genBodyDebugScope(null, body, .poi);
60896198
60906199 fg.wip.cursor = .{ .block = continue_block };
60916200 }
......@@ -6196,14 +6305,14 @@ pub const FuncGen = struct {
61966305 }
61976306 self.wip.cursor = .{ .block = case_block };
61986307 if (switch_br.getHint(case.idx) == .cold) _ = try self.wip.callIntrinsicAssumeCold();
6199 try self.genBodyDebugScope(null, case.body);
6308 try self.genBodyDebugScope(null, case.body, .poi);
62006309 }
62016310
62026311 const else_body = it.elseBody();
62036312 self.wip.cursor = .{ .block = else_block };
62046313 if (switch_br.getElseHint() == .cold) _ = try self.wip.callIntrinsicAssumeCold();
62056314 if (else_body.len != 0) {
6206 try self.genBodyDebugScope(null, else_body);
6315 try self.genBodyDebugScope(null, else_body, .poi);
62076316 } else {
62086317 _ = try self.wip.@"unreachable"();
62096318 }
......@@ -6222,7 +6331,7 @@ pub const FuncGen = struct {
62226331 _ = try self.wip.br(loop_block);
62236332
62246333 self.wip.cursor = .{ .block = loop_block };
6225 try self.genBodyDebugScope(null, body);
6334 try self.genBodyDebugScope(null, body, .none);
62266335
62276336 // TODO instead of this logic, change AIR to have the property that
62286337 // every block is guaranteed to end with a noreturn instruction.
src/codegen/llvm/Builder.zig+3-2
......@@ -10046,8 +10046,9 @@ pub fn printUnbuffered(
1004610046 }
1004710047
1004810048 if (maybe_dbg_index) |dbg_index| {
10049 try writer.print(", !dbg !{}\n", .{dbg_index});
10050 } else try writer.writeByte('\n');
10049 try writer.print(", !dbg !{}", .{dbg_index});
10050 }
10051 try writer.writeByte('\n');
1005110052 }
1005210053 try writer.writeByte('}');
1005310054 }
src/print_air.zig+16-2
......@@ -791,7 +791,14 @@ const Writer = struct {
791791
792792 try w.writeOperand(s, inst, 0, pl_op.operand);
793793 if (w.skip_body) return s.writeAll(", ...");
794 try s.writeAll(", {\n");
794 try s.writeAll(",");
795 if (extra.data.branch_hints.true != .none) {
796 try s.print(" {s}", .{@tagName(extra.data.branch_hints.true)});
797 }
798 if (extra.data.branch_hints.then_cov != .none) {
799 try s.print(" {s}", .{@tagName(extra.data.branch_hints.then_cov)});
800 }
801 try s.writeAll(" {\n");
795802 const old_indent = w.indent;
796803 w.indent += 2;
797804
......@@ -806,7 +813,14 @@ const Writer = struct {
806813
807814 try w.writeBody(s, then_body);
808815 try s.writeByteNTimes(' ', old_indent);
809 try s.writeAll("}, {\n");
816 try s.writeAll("},");
817 if (extra.data.branch_hints.false != .none) {
818 try s.print(" {s}", .{@tagName(extra.data.branch_hints.false)});
819 }
820 if (extra.data.branch_hints.else_cov != .none) {
821 try s.print(" {s}", .{@tagName(extra.data.branch_hints.else_cov)});
822 }
823 try s.writeAll(" {\n");
810824
811825 if (liveness_condbr.else_deaths.len != 0) {
812826 try s.writeByteNTimes(' ', w.indent);