authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-08 21:44:37-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-08 21:46:36-07:00
logf6f1ecf0f9227bee33b963a6ff5dfc3001ad2c46
tree0995711a5355f2526ab4455909892a8107e500be
parent4e32edbff5d34dfc779797d73bb61d32ffa02953

more optimized and correct management of 8-bit PC counters

* Upgrade from u8 to usize element types. - WebAssembly assumes u64. It should probably try to be target-aware instead. * Move the covered PC bits to after the header so it goes on the same page with the other rapidly changing memory (the header stats). depends on the semantics of accepted proposal #19755 closes #20994

5 files changed, 107 insertions(+), 77 deletions(-)

lib/fuzzer.zig+31-26
......@@ -213,11 +213,12 @@ const Fuzzer = struct {
213213 .truncate = false,
214214 });
215215 defer coverage_file.close();
216 const n_bitset_elems = (flagged_pcs.len + 7) / 8;
217 comptime assert(SeenPcsHeader.trailing[0] == .pc_addr);
218 comptime assert(SeenPcsHeader.trailing[1][0] == .pc_bits);
219 comptime assert(SeenPcsHeader.trailing[1][1] == u8);
220 const bytes_len = @sizeOf(SeenPcsHeader) + flagged_pcs.len * @sizeOf(usize) + n_bitset_elems;
216 const n_bitset_elems = (flagged_pcs.len + @bitSizeOf(usize) - 1) / @bitSizeOf(usize);
217 comptime assert(SeenPcsHeader.trailing[0] == .pc_bits_usize);
218 comptime assert(SeenPcsHeader.trailing[1] == .pc_addr);
219 const bytes_len = @sizeOf(SeenPcsHeader) +
220 n_bitset_elems * @sizeOf(usize) +
221 flagged_pcs.len * @sizeOf(usize);
221222 const existing_len = coverage_file.getEndPos() catch |err| {
222223 fatal("unable to check len of coverage file: {s}", .{@errorName(err)});
223224 };
......@@ -232,7 +233,7 @@ const Fuzzer = struct {
232233 fatal("unable to init coverage memory map: {s}", .{@errorName(err)});
233234 };
234235 if (existing_len != 0) {
235 const existing_pcs_bytes = f.seen_pcs.items[@sizeOf(SeenPcsHeader)..][0 .. flagged_pcs.len * @sizeOf(usize)];
236 const existing_pcs_bytes = f.seen_pcs.items[@sizeOf(SeenPcsHeader) + @sizeOf(usize) * n_bitset_elems ..][0 .. flagged_pcs.len * @sizeOf(usize)];
236237 const existing_pcs = std.mem.bytesAsSlice(usize, existing_pcs_bytes);
237238 for (existing_pcs, flagged_pcs, 0..) |old, new, i| {
238239 if (old != new.addr) {
......@@ -249,10 +250,10 @@ const Fuzzer = struct {
249250 .lowest_stack = std.math.maxInt(usize),
250251 };
251252 f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&header));
253 f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems * @sizeOf(usize));
252254 for (flagged_pcs) |flagged_pc| {
253255 f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&flagged_pc.addr));
254256 }
255 f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems);
256257 }
257258 }
258259
......@@ -302,26 +303,30 @@ const Fuzzer = struct {
302303 .score = analysis.score,
303304 };
304305
305 // Track code coverage from all runs.
306306 {
307 comptime assert(SeenPcsHeader.trailing[0] == .pc_addr);
308 comptime assert(SeenPcsHeader.trailing[1][0] == .pc_bits);
309 comptime assert(SeenPcsHeader.trailing[1][1] == u8);
310
311 const seen_pcs = f.seen_pcs.items[@sizeOf(SeenPcsHeader) + f.flagged_pcs.len * @sizeOf(usize) ..];
312 for (seen_pcs, 0..) |*elem, i| {
313 const byte_i = i * 8;
314 const mask: u8 =
315 (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 0] != 0)) << 0) |
316 (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 1] != 0)) << 1) |
317 (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 2] != 0)) << 2) |
318 (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 3] != 0)) << 3) |
319 (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 4] != 0)) << 4) |
320 (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 5] != 0)) << 5) |
321 (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 6] != 0)) << 6) |
322 (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 7] != 0)) << 7);
323
324 _ = @atomicRmw(u8, elem, .Or, mask, .monotonic);
307 // Track code coverage from all runs.
308 comptime assert(SeenPcsHeader.trailing[0] == .pc_bits_usize);
309 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;
312 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]);
314 const V = @Vector(@bitSizeOf(usize), u8);
315 const zero_v: V = @splat(0);
316
317 for (header_end_ptr[0..pc_counters.len], pc_counters) |*elem, *array| {
318 const v: V = array.*;
319 const mask: usize = @bitCast(v != zero_v);
320 _ = @atomicRmw(usize, elem, .Or, mask, .monotonic);
321 }
322 if (remainder > 0) {
323 const i = pc_counters.len;
324 const elem = &seen_pcs[i];
325 var mask: usize = 0;
326 for (f.pc_counters[i * @bitSizeOf(usize) ..][0..remainder], 0..) |byte, bit_index| {
327 mask |= @as(usize, @intFromBool(byte != 0)) << @intCast(bit_index);
328 }
329 _ = @atomicRmw(usize, elem, .Or, mask, .monotonic);
325330 }
326331 }
327332
lib/fuzzer/wasm/main.zig+20-16
......@@ -125,12 +125,12 @@ export fn coveredSourceLocations() usize {
125125}
126126
127127export fn totalRuns() u64 {
128 const header: *abi.CoverageUpdateHeader = @ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)]);
128 const header: *abi.CoverageUpdateHeader = @alignCast(@ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)]));
129129 return header.n_runs;
130130}
131131
132132export fn uniqueRuns() u64 {
133 const header: *abi.CoverageUpdateHeader = @ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)]);
133 const header: *abi.CoverageUpdateHeader = @alignCast(@ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)]));
134134 return header.unique_runs;
135135}
136136
......@@ -335,7 +335,7 @@ fn computeSourceAnnotations(
335335 if (next_loc_index >= locs.items.len) return;
336336 const next_sli = locs.items[next_loc_index];
337337 const next_sl = next_sli.ptr();
338 if (next_sl.line > line or (next_sl.line == line and next_sl.column > column)) break;
338 if (next_sl.line > line or (next_sl.line == line and next_sl.column >= column)) break;
339339 try annotations.append(gpa, .{
340340 .file_byte_offset = offset,
341341 .dom_id = @intFromEnum(next_sli),
......@@ -349,7 +349,7 @@ var coverage = Coverage.init;
349349/// Index of type `SourceLocationIndex`.
350350var coverage_source_locations: std.ArrayListUnmanaged(Coverage.SourceLocation) = .{};
351351/// Contains the most recent coverage update message, unmodified.
352var recent_coverage_update: std.ArrayListUnmanaged(u8) = .{};
352var recent_coverage_update: std.ArrayListAlignedUnmanaged(u8, @alignOf(u64)) = .{};
353353
354354fn updateCoverage(
355355 directories: []const Coverage.String,
......@@ -406,19 +406,23 @@ export fn sourceLocationFileCoveredList(sli_file: SourceLocationIndex) Slice(Sou
406406 };
407407 const want_file = sli_file.ptr().file;
408408 global.result.clearRetainingCapacity();
409 const covered_bits = recent_coverage_update.items[@sizeOf(abi.CoverageUpdateHeader)..];
409
410 // This code assumes 64-bit elements, which is incorrect if the executable
411 // being fuzzed is not a 64-bit CPU. It also assumes little-endian which
412 // can also be incorrect.
413 comptime assert(abi.CoverageUpdateHeader.trailing[0] == .pc_bits_usize);
414 const n_bitset_elems = (coverage_source_locations.items.len + @bitSizeOf(u64) - 1) / @bitSizeOf(u64);
415 const covered_bits = std.mem.bytesAsSlice(
416 u64,
417 recent_coverage_update.items[@sizeOf(abi.CoverageUpdateHeader)..][0 .. n_bitset_elems * @sizeOf(u64)],
418 );
410419 var sli: u32 = 0;
411 for (covered_bits) |byte| {
412 global.result.ensureUnusedCapacity(gpa, 8) catch @panic("OOM");
413 if ((byte & 0b0000_0001) != 0) global.add(sli + 0, want_file);
414 if ((byte & 0b0000_0010) != 0) global.add(sli + 1, want_file);
415 if ((byte & 0b0000_0100) != 0) global.add(sli + 2, want_file);
416 if ((byte & 0b0000_1000) != 0) global.add(sli + 3, want_file);
417 if ((byte & 0b0001_0000) != 0) global.add(sli + 4, want_file);
418 if ((byte & 0b0010_0000) != 0) global.add(sli + 5, want_file);
419 if ((byte & 0b0100_0000) != 0) global.add(sli + 6, want_file);
420 if ((byte & 0b1000_0000) != 0) global.add(sli + 7, want_file);
421 sli += 8;
420 for (covered_bits) |elem| {
421 global.result.ensureUnusedCapacity(gpa, 64) catch @panic("OOM");
422 for (0..@bitSizeOf(u64)) |i| {
423 if ((elem & (@as(u64, 1) << @intCast(i))) != 0) global.add(sli, want_file);
424 sli += 1;
425 }
422426 }
423427 return Slice(SourceLocationIndex).init(global.result.items);
424428}
lib/std/Build/Fuzz/WebServer.zig+5-12
......@@ -384,10 +384,7 @@ fn sendCoverageContext(
384384 // TODO: make each events URL correspond to one coverage map
385385 const coverage_map = &coverage_maps[0];
386386 const cov_header: *const abi.SeenPcsHeader = @ptrCast(coverage_map.mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]);
387 comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_addr);
388 const seen_pcs = coverage_map.mapped_memory[@sizeOf(abi.SeenPcsHeader) + coverage_map.source_locations.len * @sizeOf(usize) ..];
389 comptime assert(abi.SeenPcsHeader.trailing[1][0] == .pc_bits);
390 comptime assert(abi.SeenPcsHeader.trailing[1][1] == u8);
387 const seen_pcs = cov_header.seenBits();
391388 const n_runs = @atomicLoad(usize, &cov_header.n_runs, .monotonic);
392389 const unique_runs = @atomicLoad(usize, &cov_header.unique_runs, .monotonic);
393390 const lowest_stack = @atomicLoad(usize, &cov_header.lowest_stack, .monotonic);
......@@ -419,7 +416,7 @@ fn sendCoverageContext(
419416 };
420417 const iovecs: [2]std.posix.iovec_const = .{
421418 makeIov(std.mem.asBytes(&header)),
422 makeIov(seen_pcs),
419 makeIov(std.mem.sliceAsBytes(seen_pcs)),
423420 };
424421 try web_socket.writeMessagev(&iovecs, .binary);
425422
......@@ -634,9 +631,7 @@ fn prepareTables(
634631 gop.value_ptr.mapped_memory = mapped_memory;
635632
636633 const header: *const abi.SeenPcsHeader = @ptrCast(mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]);
637 comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_addr);
638 const pcs_bytes = mapped_memory[@sizeOf(abi.SeenPcsHeader)..][0 .. header.pcs_len * @sizeOf(usize)];
639 const pcs = std.mem.bytesAsSlice(usize, pcs_bytes);
634 const pcs = header.pcAddrs();
640635 const source_locations = try gpa.alloc(Coverage.SourceLocation, pcs.len);
641636 errdefer gpa.free(source_locations);
642637 debug_info.resolveAddresses(gpa, pcs, source_locations) catch |err| {
......@@ -653,10 +648,8 @@ fn addEntryPoint(ws: *WebServer, coverage_id: u64, addr: u64) error{ AlreadyRepo
653648 defer ws.coverage_mutex.unlock();
654649
655650 const coverage_map = ws.coverage_files.getPtr(coverage_id).?;
656 const ptr = coverage_map.mapped_memory;
657 comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_addr);
658 const pcs_bytes = ptr[@sizeOf(abi.SeenPcsHeader)..][0 .. coverage_map.source_locations.len * @sizeOf(usize)];
659 const pcs: []const usize = @alignCast(std.mem.bytesAsSlice(usize, pcs_bytes));
651 const header: *const abi.SeenPcsHeader = @ptrCast(coverage_map.mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]);
652 const pcs = header.pcAddrs();
660653 const index = std.sort.upperBound(usize, pcs, addr, struct {
661654 fn order(context: usize, item: usize) std.math.Order {
662655 return std.math.order(item, context);
lib/std/Build/Fuzz/abi.zig+36-7
......@@ -7,8 +7,8 @@
77/// make the ints be the size of the target used with libfuzzer.
88///
99/// Trailing:
10/// * 1 bit per pc_addr, usize elements
1011/// * pc_addr: usize for each pcs_len
11/// * 1 bit per pc_addr, u8 elements
1212pub const SeenPcsHeader = extern struct {
1313 n_runs: usize,
1414 unique_runs: usize,
......@@ -18,9 +18,29 @@ pub const SeenPcsHeader = extern struct {
1818 /// Used for comptime assertions. Provides a mechanism for strategically
1919 /// causing compile errors.
2020 pub const trailing = .{
21 .pc_bits_usize,
2122 .pc_addr,
22 .{ .pc_bits, u8 },
2323 };
24
25 pub fn headerEnd(header: *const SeenPcsHeader) []const usize {
26 const ptr: [*]align(@alignOf(usize)) const u8 = @ptrCast(header);
27 const header_end_ptr: [*]const usize = @ptrCast(ptr + @sizeOf(SeenPcsHeader));
28 const pcs_len = header.pcs_len;
29 return header_end_ptr[0 .. pcs_len + seenElemsLen(pcs_len)];
30 }
31
32 pub fn seenBits(header: *const SeenPcsHeader) []const usize {
33 return header.headerEnd()[0..seenElemsLen(header.pcs_len)];
34 }
35
36 pub fn seenElemsLen(pcs_len: usize) usize {
37 return (pcs_len + @bitSizeOf(usize) - 1) / @bitSizeOf(usize);
38 }
39
40 pub fn pcAddrs(header: *const SeenPcsHeader) []const usize {
41 const pcs_len = header.pcs_len;
42 return header.headerEnd()[seenElemsLen(pcs_len)..][0..pcs_len];
43 }
2444};
2545
2646pub const ToClientTag = enum(u8) {
......@@ -54,12 +74,21 @@ pub const SourceIndexHeader = extern struct {
5474/// changes.
5575///
5676/// Trailing:
57/// * one bit per source_locations_len, contained in u8 elements
77/// * one bit per source_locations_len, contained in u64 elements
5878pub const CoverageUpdateHeader = extern struct {
59 tag: ToClientTag = .coverage_update,
60 n_runs: u64 align(1),
61 unique_runs: u64 align(1),
62 lowest_stack: u64 align(1),
79 flags: Flags = .{},
80 n_runs: u64,
81 unique_runs: u64,
82 lowest_stack: u64,
83
84 pub const Flags = packed struct(u64) {
85 tag: ToClientTag = .coverage_update,
86 _: u56 = 0,
87 };
88
89 pub const trailing = .{
90 .pc_bits_usize,
91 };
6392};
6493
6594/// Sent to the fuzzer web client when the set of entry points is updated.
tools/dump-cov.zig+15-16
......@@ -5,6 +5,7 @@ const std = @import("std");
55const fatal = std.process.fatal;
66const Path = std.Build.Cache.Path;
77const assert = std.debug.assert;
8const SeenPcsHeader = std.Build.Fuzz.abi.SeenPcsHeader;
89
910pub fn main() !void {
1011 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .{};
......@@ -36,24 +37,29 @@ pub fn main() !void {
3637 };
3738 defer debug_info.deinit(gpa);
3839
39 const cov_bytes = cov_path.root_dir.handle.readFileAlloc(arena, cov_path.sub_path, 1 << 30) catch |err| {
40 const cov_bytes = cov_path.root_dir.handle.readFileAllocOptions(
41 arena,
42 cov_path.sub_path,
43 1 << 30,
44 null,
45 @alignOf(SeenPcsHeader),
46 null,
47 ) catch |err| {
4048 fatal("failed to load coverage file {}: {s}", .{ cov_path, @errorName(err) });
4149 };
4250
4351 var bw = std.io.bufferedWriter(std.io.getStdOut().writer());
4452 const stdout = bw.writer();
4553
46 const header: *align(1) SeenPcsHeader = @ptrCast(cov_bytes);
54 const header: *SeenPcsHeader = @ptrCast(cov_bytes);
4755 try stdout.print("{any}\n", .{header.*});
48 //const n_bitset_elems = (header.pcs_len + 7) / 8;
49 const pcs_bytes = cov_bytes[@sizeOf(SeenPcsHeader)..][0 .. header.pcs_len * @sizeOf(usize)];
50 const pcs = try arena.alloc(usize, header.pcs_len);
51 for (0..pcs_bytes.len / @sizeOf(usize), pcs) |i, *pc| {
52 pc.* = std.mem.readInt(usize, pcs_bytes[i * @sizeOf(usize) ..][0..@sizeOf(usize)], .little);
56 const pcs = header.pcAddrs();
57 for (0.., pcs[0 .. pcs.len - 1], pcs[1..]) |i, a, b| {
58 if (a > b) std.log.err("{d}: 0x{x} > 0x{x}", .{ i, a, b });
5359 }
5460 assert(std.sort.isSorted(usize, pcs, {}, std.sort.asc(usize)));
5561
56 const seen_pcs = cov_bytes[@sizeOf(SeenPcsHeader) + pcs.len * @sizeOf(usize) ..];
62 const seen_pcs = header.seenBits();
5763
5864 const source_locations = try arena.alloc(std.debug.Coverage.SourceLocation, pcs.len);
5965 try debug_info.resolveAddresses(gpa, pcs, source_locations);
......@@ -62,7 +68,7 @@ pub fn main() !void {
6268 const file = debug_info.coverage.fileAt(sl.file);
6369 const dir_name = debug_info.coverage.directories.keys()[file.directory_index];
6470 const dir_name_slice = debug_info.coverage.stringAt(dir_name);
65 const hit: u1 = @truncate(seen_pcs[i / 8] >> @intCast(i % 8));
71 const hit: u1 = @truncate(seen_pcs[i / @bitSizeOf(usize)] >> @intCast(i % @bitSizeOf(usize)));
6672 try stdout.print("{c}{x}: {s}/{s}:{d}:{d}\n", .{
6773 "-+"[hit], pc, dir_name_slice, debug_info.coverage.stringAt(file.basename), sl.line, sl.column,
6874 });
......@@ -70,10 +76,3 @@ pub fn main() !void {
7076
7177 try bw.flush();
7278}
73
74const SeenPcsHeader = extern struct {
75 n_runs: usize,
76 deduplicated_runs: usize,
77 pcs_len: usize,
78 lowest_stack: usize,
79};