Skip to content

Commit 059a43d

Browse files
andrewrkIgor Stojkovic
authored and
Igor Stojkovic
committed
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 ziglang#19755 closes ziglang#20994
1 parent 4f76b5f commit 059a43d

File tree

5 files changed

+107
-77
lines changed

5 files changed

+107
-77
lines changed

lib/fuzzer.zig

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,12 @@ export fn coveredSourceLocations() usize {
125125
}
126126

127127
export 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

132132
export 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`.
350350
var coverage_source_locations: std.ArrayListUnmanaged(Coverage.SourceLocation) = .{};
351351
/// Contains the most recent coverage update message, unmodified.
352-
var recent_coverage_update: std.ArrayListUnmanaged(u8) = .{};
352+
var recent_coverage_update: std.ArrayListAlignedUnmanaged(u8, @alignOf(u64)) = .{};
353353

354354
fn 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

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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
1212
pub 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

2646
pub 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
5878
pub 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

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const std = @import("std");
55
const fatal = std.process.fatal;
66
const Path = std.Build.Cache.Path;
77
const assert = std.debug.assert;
8+
const SeenPcsHeader = std.Build.Fuzz.abi.SeenPcsHeader;
89

910
pub 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,18 +68,11 @@ 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
});
6975
}
7076

7177
try bw.flush();
7278
}
73-
74-
const SeenPcsHeader = extern struct {
75-
n_runs: usize,
76-
deduplicated_runs: usize,
77-
pcs_len: usize,
78-
lowest_stack: usize,
79-
};

0 commit comments

Comments
 (0)