| author | |
| committer | |
| log | eb17fe85a595c81da0304f4168c5dcf8181bd972 |
| tree | e4cc1b923c05ea12826edb85c82919865d0950e6 |
| parent | 9f46abf598a1bc27aa42a96aa6d94f3112fa210b |
| parent | f6f1ecf0f9227bee33b963a6ff5dfc3001ad2c46 |
| signature |
fuzzing: more optimized and correct management of 8-bit PC counters5 files changed, 114 insertions(+), 64 deletions(-)
lib/fuzzer.zig+31-19| ... | ... | @@ -213,8 +213,12 @@ const Fuzzer = struct { |
| 213 | 213 | .truncate = false, |
| 214 | 214 | }); |
| 215 | 215 | defer coverage_file.close(); |
| 216 | const n_bitset_elems = (flagged_pcs.len + 7) / 8; | |
| 217 | 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); | |
| 218 | 222 | const existing_len = coverage_file.getEndPos() catch |err| { |
| 219 | 223 | fatal("unable to check len of coverage file: {s}", .{@errorName(err)}); |
| 220 | 224 | }; |
| ... | ... | @@ -229,7 +233,7 @@ const Fuzzer = struct { |
| 229 | 233 | fatal("unable to init coverage memory map: {s}", .{@errorName(err)}); |
| 230 | 234 | }; |
| 231 | 235 | if (existing_len != 0) { |
| 232 | 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)]; | |
| 233 | 237 | const existing_pcs = std.mem.bytesAsSlice(usize, existing_pcs_bytes); |
| 234 | 238 | for (existing_pcs, flagged_pcs, 0..) |old, new, i| { |
| 235 | 239 | if (old != new.addr) { |
| ... | ... | @@ -246,10 +250,10 @@ const Fuzzer = struct { |
| 246 | 250 | .lowest_stack = std.math.maxInt(usize), |
| 247 | 251 | }; |
| 248 | 252 | f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&header)); |
| 253 | f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems * @sizeOf(usize)); | |
| 249 | 254 | for (flagged_pcs) |flagged_pc| { |
| 250 | 255 | f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&flagged_pc.addr)); |
| 251 | 256 | } |
| 252 | f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems); | |
| 253 | 257 | } |
| 254 | 258 | } |
| 255 | 259 | |
| ... | ... | @@ -299,22 +303,30 @@ const Fuzzer = struct { |
| 299 | 303 | .score = analysis.score, |
| 300 | 304 | }; |
| 301 | 305 | |
| 302 | // Track code coverage from all runs. | |
| 303 | 306 | { |
| 304 | const seen_pcs = f.seen_pcs.items[@sizeOf(SeenPcsHeader) + f.flagged_pcs.len * @sizeOf(usize) ..]; | |
| 305 | for (seen_pcs, 0..) |*elem, i| { | |
| 306 | const byte_i = i * 8; | |
| 307 | const mask: u8 = | |
| 308 | (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 0] != 0)) << 0) | | |
| 309 | (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 1] != 0)) << 1) | | |
| 310 | (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 2] != 0)) << 2) | | |
| 311 | (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 3] != 0)) << 3) | | |
| 312 | (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 4] != 0)) << 4) | | |
| 313 | (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 5] != 0)) << 5) | | |
| 314 | (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 6] != 0)) << 6) | | |
| 315 | (@as(u8, @intFromBool(f.pc_counters.ptr[byte_i + 7] != 0)) << 7); | |
| 316 | ||
| 317 | _ = @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); | |
| 318 | 330 | } |
| 319 | 331 | } |
| 320 | 332 |
lib/fuzzer/wasm/main.zig+20-16| ... | ... | @@ -125,12 +125,12 @@ export fn coveredSourceLocations() usize { |
| 125 | 125 | } |
| 126 | 126 | |
| 127 | 127 | 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)])); | |
| 129 | 129 | return header.n_runs; |
| 130 | 130 | } |
| 131 | 131 | |
| 132 | 132 | 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)])); | |
| 134 | 134 | return header.unique_runs; |
| 135 | 135 | } |
| 136 | 136 | |
| ... | ... | @@ -335,7 +335,7 @@ fn computeSourceAnnotations( |
| 335 | 335 | if (next_loc_index >= locs.items.len) return; |
| 336 | 336 | const next_sli = locs.items[next_loc_index]; |
| 337 | 337 | 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; | |
| 339 | 339 | try annotations.append(gpa, .{ |
| 340 | 340 | .file_byte_offset = offset, |
| 341 | 341 | .dom_id = @intFromEnum(next_sli), |
| ... | ... | @@ -349,7 +349,7 @@ var coverage = Coverage.init; |
| 349 | 349 | /// Index of type `SourceLocationIndex`. |
| 350 | 350 | var coverage_source_locations: std.ArrayListUnmanaged(Coverage.SourceLocation) = .{}; |
| 351 | 351 | /// 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)) = .{}; | |
| 353 | 353 | |
| 354 | 354 | fn updateCoverage( |
| 355 | 355 | directories: []const Coverage.String, |
| ... | ... | @@ -406,19 +406,23 @@ export fn sourceLocationFileCoveredList(sli_file: SourceLocationIndex) Slice(Sou |
| 406 | 406 | }; |
| 407 | 407 | const want_file = sli_file.ptr().file; |
| 408 | 408 | 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 | ); | |
| 410 | 419 | 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 | } | |
| 422 | 426 | } |
| 423 | 427 | return Slice(SourceLocationIndex).init(global.result.items); |
| 424 | 428 | } |
lib/std/Build/Fuzz/WebServer.zig+6-7| ... | ... | @@ -7,6 +7,7 @@ const Step = std.Build.Step; |
| 7 | 7 | const Coverage = std.debug.Coverage; |
| 8 | 8 | const abi = std.Build.Fuzz.abi; |
| 9 | 9 | const log = std.log; |
| 10 | const assert = std.debug.assert; | |
| 10 | 11 | |
| 11 | 12 | const WebServer = @This(); |
| 12 | 13 | |
| ... | ... | @@ -383,7 +384,7 @@ fn sendCoverageContext( |
| 383 | 384 | // TODO: make each events URL correspond to one coverage map |
| 384 | 385 | const coverage_map = &coverage_maps[0]; |
| 385 | 386 | const cov_header: *const abi.SeenPcsHeader = @ptrCast(coverage_map.mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]); |
| 386 | const seen_pcs = coverage_map.mapped_memory[@sizeOf(abi.SeenPcsHeader) + coverage_map.source_locations.len * @sizeOf(usize) ..]; | |
| 387 | const seen_pcs = cov_header.seenBits(); | |
| 387 | 388 | const n_runs = @atomicLoad(usize, &cov_header.n_runs, .monotonic); |
| 388 | 389 | const unique_runs = @atomicLoad(usize, &cov_header.unique_runs, .monotonic); |
| 389 | 390 | const lowest_stack = @atomicLoad(usize, &cov_header.lowest_stack, .monotonic); |
| ... | ... | @@ -415,7 +416,7 @@ fn sendCoverageContext( |
| 415 | 416 | }; |
| 416 | 417 | const iovecs: [2]std.posix.iovec_const = .{ |
| 417 | 418 | makeIov(std.mem.asBytes(&header)), |
| 418 | makeIov(seen_pcs), | |
| 419 | makeIov(std.mem.sliceAsBytes(seen_pcs)), | |
| 419 | 420 | }; |
| 420 | 421 | try web_socket.writeMessagev(&iovecs, .binary); |
| 421 | 422 | |
| ... | ... | @@ -630,8 +631,7 @@ fn prepareTables( |
| 630 | 631 | gop.value_ptr.mapped_memory = mapped_memory; |
| 631 | 632 | |
| 632 | 633 | const header: *const abi.SeenPcsHeader = @ptrCast(mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]); |
| 633 | const pcs_bytes = mapped_memory[@sizeOf(abi.SeenPcsHeader)..][0 .. header.pcs_len * @sizeOf(usize)]; | |
| 634 | const pcs = std.mem.bytesAsSlice(usize, pcs_bytes); | |
| 634 | const pcs = header.pcAddrs(); | |
| 635 | 635 | const source_locations = try gpa.alloc(Coverage.SourceLocation, pcs.len); |
| 636 | 636 | errdefer gpa.free(source_locations); |
| 637 | 637 | debug_info.resolveAddresses(gpa, pcs, source_locations) catch |err| { |
| ... | ... | @@ -648,9 +648,8 @@ fn addEntryPoint(ws: *WebServer, coverage_id: u64, addr: u64) error{ AlreadyRepo |
| 648 | 648 | defer ws.coverage_mutex.unlock(); |
| 649 | 649 | |
| 650 | 650 | const coverage_map = ws.coverage_files.getPtr(coverage_id).?; |
| 651 | const ptr = coverage_map.mapped_memory; | |
| 652 | const pcs_bytes = ptr[@sizeOf(abi.SeenPcsHeader)..][0 .. coverage_map.source_locations.len * @sizeOf(usize)]; | |
| 653 | 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(); | |
| 654 | 653 | const index = std.sort.upperBound(usize, pcs, addr, struct { |
| 655 | 654 | fn order(context: usize, item: usize) std.math.Order { |
| 656 | 655 | return std.math.order(item, context); |
lib/std/Build/Fuzz/abi.zig+42-6| ... | ... | @@ -7,13 +7,40 @@ |
| 7 | 7 | /// make the ints be the size of the target used with libfuzzer. |
| 8 | 8 | /// |
| 9 | 9 | /// Trailing: |
| 10 | /// * pc_addr: usize for each pcs_len | |
| 11 | 10 | /// * 1 bit per pc_addr, usize elements |
| 11 | /// * pc_addr: usize for each pcs_len | |
| 12 | 12 | pub const SeenPcsHeader = extern struct { |
| 13 | 13 | n_runs: usize, |
| 14 | 14 | unique_runs: usize, |
| 15 | 15 | pcs_len: usize, |
| 16 | 16 | lowest_stack: usize, |
| 17 | ||
| 18 | /// Used for comptime assertions. Provides a mechanism for strategically | |
| 19 | /// causing compile errors. | |
| 20 | pub const trailing = .{ | |
| 21 | .pc_bits_usize, | |
| 22 | .pc_addr, | |
| 23 | }; | |
| 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 | } | |
| 17 | 44 | }; |
| 18 | 45 | |
| 19 | 46 | pub const ToClientTag = enum(u8) { |
| ... | ... | @@ -47,12 +74,21 @@ pub const SourceIndexHeader = extern struct { |
| 47 | 74 | /// changes. |
| 48 | 75 | /// |
| 49 | 76 | /// Trailing: |
| 50 | /// * one bit per source_locations_len, contained in u8 elements | |
| 77 | /// * one bit per source_locations_len, contained in u64 elements | |
| 51 | 78 | pub const CoverageUpdateHeader = extern struct { |
| 52 | tag: ToClientTag = .coverage_update, | |
| 53 | n_runs: u64 align(1), | |
| 54 | unique_runs: u64 align(1), | |
| 55 | 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 | }; | |
| 56 | 92 | }; |
| 57 | 93 | |
| 58 | 94 | /// 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"); |
| 5 | 5 | const fatal = std.process.fatal; |
| 6 | 6 | const Path = std.Build.Cache.Path; |
| 7 | 7 | const assert = std.debug.assert; |
| 8 | const SeenPcsHeader = std.Build.Fuzz.abi.SeenPcsHeader; | |
| 8 | 9 | |
| 9 | 10 | pub fn main() !void { |
| 10 | 11 | var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .{}; |
| ... | ... | @@ -36,24 +37,29 @@ pub fn main() !void { |
| 36 | 37 | }; |
| 37 | 38 | defer debug_info.deinit(gpa); |
| 38 | 39 | |
| 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| { | |
| 40 | 48 | fatal("failed to load coverage file {}: {s}", .{ cov_path, @errorName(err) }); |
| 41 | 49 | }; |
| 42 | 50 | |
| 43 | 51 | var bw = std.io.bufferedWriter(std.io.getStdOut().writer()); |
| 44 | 52 | const stdout = bw.writer(); |
| 45 | 53 | |
| 46 | const header: *align(1) SeenPcsHeader = @ptrCast(cov_bytes); | |
| 54 | const header: *SeenPcsHeader = @ptrCast(cov_bytes); | |
| 47 | 55 | 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 }); | |
| 53 | 59 | } |
| 54 | 60 | assert(std.sort.isSorted(usize, pcs, {}, std.sort.asc(usize))); |
| 55 | 61 | |
| 56 | const seen_pcs = cov_bytes[@sizeOf(SeenPcsHeader) + pcs.len * @sizeOf(usize) ..]; | |
| 62 | const seen_pcs = header.seenBits(); | |
| 57 | 63 | |
| 58 | 64 | const source_locations = try arena.alloc(std.debug.Coverage.SourceLocation, pcs.len); |
| 59 | 65 | try debug_info.resolveAddresses(gpa, pcs, source_locations); |
| ... | ... | @@ -62,7 +68,7 @@ pub fn main() !void { |
| 62 | 68 | const file = debug_info.coverage.fileAt(sl.file); |
| 63 | 69 | const dir_name = debug_info.coverage.directories.keys()[file.directory_index]; |
| 64 | 70 | 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))); | |
| 66 | 72 | try stdout.print("{c}{x}: {s}/{s}:{d}:{d}\n", .{ |
| 67 | 73 | "-+"[hit], pc, dir_name_slice, debug_info.coverage.stringAt(file.basename), sl.line, sl.column, |
| 68 | 74 | }); |
| ... | ... | @@ -70,10 +76,3 @@ pub fn main() !void { |
| 70 | 76 | |
| 71 | 77 | try bw.flush(); |
| 72 | 78 | } |
| 73 | ||
| 74 | const SeenPcsHeader = extern struct { | |
| 75 | n_runs: usize, | |
| 76 | deduplicated_runs: usize, | |
| 77 | pcs_len: usize, | |
| 78 | lowest_stack: usize, | |
| 79 | }; |