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.
This commit is contained in:
Andrew Kelley 2024-08-28 12:11:08 -07:00
parent 43dc8db068
commit b8d99a3323
5 changed files with 249 additions and 98 deletions

View File

@ -30,19 +30,6 @@ fn logOverride(
export threadlocal var __sancov_lowest_stack: usize = std.math.maxInt(usize); export threadlocal var __sancov_lowest_stack: usize = std.math.maxInt(usize);
var module_count_8bc: usize = 0;
var module_count_pcs: usize = 0;
export fn __sanitizer_cov_8bit_counters_init(start: [*]u8, end: [*]u8) void {
assert(@atomicRmw(usize, &module_count_8bc, .Add, 1, .monotonic) == 0);
fuzzer.pc_counters = start[0 .. end - start];
}
export fn __sanitizer_cov_pcs_init(start: [*]const Fuzzer.FlaggedPc, end: [*]const Fuzzer.FlaggedPc) void {
assert(@atomicRmw(usize, &module_count_pcs, .Add, 1, .monotonic) == 0);
fuzzer.flagged_pcs = start[0 .. end - start];
}
export fn __sanitizer_cov_trace_const_cmp1(arg1: u8, arg2: u8) void { export fn __sanitizer_cov_trace_const_cmp1(arg1: u8, arg2: u8) void {
handleCmp(@returnAddress(), arg1, arg2); handleCmp(@returnAddress(), arg1, arg2);
} }
@ -105,7 +92,7 @@ const Fuzzer = struct {
gpa: Allocator, gpa: Allocator,
rng: std.Random.DefaultPrng, rng: std.Random.DefaultPrng,
input: std.ArrayListUnmanaged(u8), input: std.ArrayListUnmanaged(u8),
flagged_pcs: []const FlaggedPc, pcs: []const usize,
pc_counters: []u8, pc_counters: []u8,
n_runs: usize, n_runs: usize,
recent_cases: RunMap, recent_cases: RunMap,
@ -174,32 +161,18 @@ const Fuzzer = struct {
} }
}; };
const FlaggedPc = extern struct {
addr: usize,
flags: packed struct(usize) {
entry: bool,
_: @Type(.{ .int = .{ .signedness = .unsigned, .bits = @bitSizeOf(usize) - 1 } }),
},
};
const Analysis = struct { const Analysis = struct {
score: usize, score: usize,
id: Run.Id, id: Run.Id,
}; };
fn init(f: *Fuzzer, cache_dir: std.fs.Dir) !void { fn init(f: *Fuzzer, cache_dir: std.fs.Dir, pc_counters: []u8, pcs: []const usize) !void {
const flagged_pcs = f.flagged_pcs;
f.cache_dir = cache_dir; f.cache_dir = cache_dir;
f.pc_counters = pc_counters;
f.pcs = pcs;
// Choose a file name for the coverage based on a hash of the PCs that will be stored within. // Choose a file name for the coverage based on a hash of the PCs that will be stored within.
const pc_digest = d: { const pc_digest = std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(pcs));
var hasher = std.hash.Wyhash.init(0);
for (flagged_pcs) |flagged_pc| {
hasher.update(std.mem.asBytes(&flagged_pc.addr));
}
break :d f.coverage.run_id_hasher.final();
};
f.coverage_id = pc_digest; f.coverage_id = pc_digest;
const hex_digest = std.fmt.hex(pc_digest); const hex_digest = std.fmt.hex(pc_digest);
const coverage_file_path = "v/" ++ hex_digest; const coverage_file_path = "v/" ++ hex_digest;
@ -213,12 +186,12 @@ const Fuzzer = struct {
.truncate = false, .truncate = false,
}); });
defer coverage_file.close(); defer coverage_file.close();
const n_bitset_elems = (flagged_pcs.len + @bitSizeOf(usize) - 1) / @bitSizeOf(usize); const n_bitset_elems = (pcs.len + @bitSizeOf(usize) - 1) / @bitSizeOf(usize);
comptime assert(SeenPcsHeader.trailing[0] == .pc_bits_usize); comptime assert(SeenPcsHeader.trailing[0] == .pc_bits_usize);
comptime assert(SeenPcsHeader.trailing[1] == .pc_addr); comptime assert(SeenPcsHeader.trailing[1] == .pc_addr);
const bytes_len = @sizeOf(SeenPcsHeader) + const bytes_len = @sizeOf(SeenPcsHeader) +
n_bitset_elems * @sizeOf(usize) + n_bitset_elems * @sizeOf(usize) +
flagged_pcs.len * @sizeOf(usize); pcs.len * @sizeOf(usize);
const existing_len = coverage_file.getEndPos() catch |err| { const existing_len = coverage_file.getEndPos() catch |err| {
fatal("unable to check len of coverage file: {s}", .{@errorName(err)}); fatal("unable to check len of coverage file: {s}", .{@errorName(err)});
}; };
@ -233,12 +206,12 @@ const Fuzzer = struct {
fatal("unable to init coverage memory map: {s}", .{@errorName(err)}); fatal("unable to init coverage memory map: {s}", .{@errorName(err)});
}; };
if (existing_len != 0) { if (existing_len != 0) {
const existing_pcs_bytes = f.seen_pcs.items[@sizeOf(SeenPcsHeader) + @sizeOf(usize) * n_bitset_elems ..][0 .. flagged_pcs.len * @sizeOf(usize)]; const existing_pcs_bytes = f.seen_pcs.items[@sizeOf(SeenPcsHeader) + @sizeOf(usize) * n_bitset_elems ..][0 .. pcs.len * @sizeOf(usize)];
const existing_pcs = std.mem.bytesAsSlice(usize, existing_pcs_bytes); const existing_pcs = std.mem.bytesAsSlice(usize, existing_pcs_bytes);
for (existing_pcs, flagged_pcs, 0..) |old, new, i| { for (existing_pcs, pcs, 0..) |old, new, i| {
if (old != new.addr) { if (old != new) {
fatal("incompatible existing coverage file (differing PC at index {d}: {x} != {x})", .{ fatal("incompatible existing coverage file (differing PC at index {d}: {x} != {x})", .{
i, old, new.addr, i, old, new,
}); });
} }
} }
@ -246,14 +219,12 @@ const Fuzzer = struct {
const header: SeenPcsHeader = .{ const header: SeenPcsHeader = .{
.n_runs = 0, .n_runs = 0,
.unique_runs = 0, .unique_runs = 0,
.pcs_len = flagged_pcs.len, .pcs_len = pcs.len,
.lowest_stack = std.math.maxInt(usize), .lowest_stack = std.math.maxInt(usize),
}; };
f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&header)); f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&header));
f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems * @sizeOf(usize)); f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems * @sizeOf(usize));
for (flagged_pcs) |flagged_pc| { f.seen_pcs.appendSliceAssumeCapacity(std.mem.sliceAsBytes(pcs));
f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&flagged_pc.addr));
}
} }
} }
@ -307,8 +278,8 @@ const Fuzzer = struct {
// Track code coverage from all runs. // Track code coverage from all runs.
comptime assert(SeenPcsHeader.trailing[0] == .pc_bits_usize); comptime assert(SeenPcsHeader.trailing[0] == .pc_bits_usize);
const header_end_ptr: [*]volatile usize = @ptrCast(f.seen_pcs.items[@sizeOf(SeenPcsHeader)..]); const header_end_ptr: [*]volatile usize = @ptrCast(f.seen_pcs.items[@sizeOf(SeenPcsHeader)..]);
const remainder = f.flagged_pcs.len % @bitSizeOf(usize); const remainder = f.pcs.len % @bitSizeOf(usize);
const aligned_len = f.flagged_pcs.len - remainder; const aligned_len = f.pcs.len - remainder;
const seen_pcs = header_end_ptr[0..aligned_len]; const seen_pcs = header_end_ptr[0..aligned_len];
const pc_counters = std.mem.bytesAsSlice([@bitSizeOf(usize)]u8, f.pc_counters[0..aligned_len]); const pc_counters = std.mem.bytesAsSlice([@bitSizeOf(usize)]u8, f.pc_counters[0..aligned_len]);
const V = @Vector(@bitSizeOf(usize), u8); const V = @Vector(@bitSizeOf(usize), u8);
@ -433,7 +404,7 @@ var fuzzer: Fuzzer = .{
.gpa = general_purpose_allocator.allocator(), .gpa = general_purpose_allocator.allocator(),
.rng = std.Random.DefaultPrng.init(0), .rng = std.Random.DefaultPrng.init(0),
.input = .{}, .input = .{},
.flagged_pcs = undefined, .pcs = undefined,
.pc_counters = undefined, .pc_counters = undefined,
.n_runs = 0, .n_runs = 0,
.recent_cases = .{}, .recent_cases = .{},
@ -455,8 +426,32 @@ export fn fuzzer_next() Fuzzer.Slice {
} }
export fn fuzzer_init(cache_dir_struct: Fuzzer.Slice) void { export fn fuzzer_init(cache_dir_struct: Fuzzer.Slice) void {
if (module_count_8bc == 0) fatal("__sanitizer_cov_8bit_counters_init was never called", .{}); // Linkers are expected to automatically add `__start_<section>` and
if (module_count_pcs == 0) fatal("__sanitizer_cov_pcs_init was never called", .{}); // `__stop_<section>` symbols when section names are valid C identifiers.
const pc_counters_start = @extern([*]u8, .{
.name = "__start___sancov_cntrs",
.linkage = .weak,
}) orelse fatal("missing __start___sancov_cntrs symbol");
const pc_counters_end = @extern([*]u8, .{
.name = "__stop___sancov_cntrs",
.linkage = .weak,
}) orelse fatal("missing __stop___sancov_cntrs symbol");
const pc_counters = pc_counters_start[0 .. pc_counters_end - pc_counters_start];
const pcs_start = @extern([*]usize, .{
.name = "__start___sancov_pcs1",
.linkage = .weak,
}) orelse fatal("missing __start___sancov_pcs1 symbol");
const pcs_end = @extern([*]usize, .{
.name = "__stop___sancov_pcs1",
.linkage = .weak,
}) orelse fatal("missing __stop___sancov_pcs1 symbol");
const pcs = pcs_start[0 .. pcs_end - pcs_start];
const cache_dir_path = cache_dir_struct.toZig(); const cache_dir_path = cache_dir_struct.toZig();
const cache_dir = if (cache_dir_path.len == 0) const cache_dir = if (cache_dir_path.len == 0)
@ -466,7 +461,8 @@ export fn fuzzer_init(cache_dir_struct: Fuzzer.Slice) void {
fatal("unable to open fuzz directory '{s}': {s}", .{ cache_dir_path, @errorName(err) }); fatal("unable to open fuzz directory '{s}': {s}", .{ cache_dir_path, @errorName(err) });
}; };
fuzzer.init(cache_dir) catch |err| fatal("unable to init fuzzer: {s}", .{@errorName(err)}); fuzzer.init(cache_dir, pc_counters, pcs) catch |err|
fatal("unable to init fuzzer: {s}", .{@errorName(err)});
} }
/// Like `std.ArrayListUnmanaged(u8)` but backed by memory mapping. /// Like `std.ArrayListUnmanaged(u8)` but backed by memory mapping.

View File

@ -1126,7 +1126,9 @@ pub const CondBr = struct {
pub const BranchHints = packed struct(u32) { pub const BranchHints = packed struct(u32) {
true: std.builtin.BranchHint, true: std.builtin.BranchHint,
false: std.builtin.BranchHint, false: std.builtin.BranchHint,
_: u26 = 0, then_cov: CoveragePoint,
else_cov: CoveragePoint,
_: u24 = 0,
}; };
}; };
@ -1903,3 +1905,12 @@ pub fn unwrapSwitch(air: *const Air, switch_inst: Inst.Index) UnwrappedSwitch {
pub const typesFullyResolved = types_resolved.typesFullyResolved; pub const typesFullyResolved = types_resolved.typesFullyResolved;
pub const typeFullyResolved = types_resolved.checkType; pub const typeFullyResolved = types_resolved.checkType;
pub const valFullyResolved = types_resolved.checkVal; pub const valFullyResolved = types_resolved.checkVal;
pub const CoveragePoint = enum(u1) {
/// Indicates the block is not a place of interest corresponding to
/// a source location for coverage purposes.
none,
/// Point of interest. The next instruction emitted corresponds to
/// a source location used for coverage instrumentation.
poi,
};

View File

@ -6898,8 +6898,14 @@ fn popErrorReturnTrace(
.payload = sema.addExtraAssumeCapacity(Air.CondBr{ .payload = sema.addExtraAssumeCapacity(Air.CondBr{
.then_body_len = @intCast(then_block.instructions.items.len), .then_body_len = @intCast(then_block.instructions.items.len),
.else_body_len = @intCast(else_block.instructions.items.len), .else_body_len = @intCast(else_block.instructions.items.len),
// weight against error branch .branch_hints = .{
.branch_hints = .{ .true = .likely, .false = .unlikely }, // Weight against error branch.
.true = .likely,
.false = .unlikely,
// Code coverage is not valuable on either branch.
.then_cov = .none,
.else_cov = .none,
},
}), }),
}, },
}, },
@ -11796,14 +11802,22 @@ fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Comp
_ = try child_block.addInst(.{ _ = try child_block.addInst(.{
.tag = .cond_br, .tag = .cond_br,
.data = .{ .pl_op = .{ .data = .{
.operand = cond, .pl_op = .{
.payload = sema.addExtraAssumeCapacity(Air.CondBr{ .operand = cond,
.then_body_len = @intCast(true_instructions.len), .payload = sema.addExtraAssumeCapacity(Air.CondBr{
.else_body_len = @intCast(sub_block.instructions.items.len), .then_body_len = @intCast(true_instructions.len),
.branch_hints = .{ .true = non_error_hint, .false = .none }, .else_body_len = @intCast(sub_block.instructions.items.len),
}), .branch_hints = .{
} }, .true = non_error_hint,
.false = .none,
// Code coverage is desired for error handling.
.then_cov = .poi,
.else_cov = .poi,
},
}),
},
},
}); });
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(true_instructions)); sema.air_extra.appendSliceAssumeCapacity(@ptrCast(true_instructions));
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(sub_block.instructions.items)); sema.air_extra.appendSliceAssumeCapacity(@ptrCast(sub_block.instructions.items));
@ -12853,7 +12867,13 @@ fn analyzeSwitchRuntimeBlock(
sema.air_instructions.items(.data)[@intFromEnum(prev_cond_br)].pl_op.payload = sema.addExtraAssumeCapacity(Air.CondBr{ sema.air_instructions.items(.data)[@intFromEnum(prev_cond_br)].pl_op.payload = sema.addExtraAssumeCapacity(Air.CondBr{
.then_body_len = @intCast(prev_then_body.len), .then_body_len = @intCast(prev_then_body.len),
.else_body_len = @intCast(cond_body.len), .else_body_len = @intCast(cond_body.len),
.branch_hints = .{ .true = prev_hint, .false = .none }, .branch_hints = .{
.true = prev_hint,
.false = .none,
// Code coverage is desired for error handling.
.then_cov = .poi,
.else_cov = .poi,
},
}); });
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(prev_then_body)); sema.air_extra.appendSliceAssumeCapacity(@ptrCast(prev_then_body));
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(cond_body)); sema.air_extra.appendSliceAssumeCapacity(@ptrCast(cond_body));
@ -13133,7 +13153,12 @@ fn analyzeSwitchRuntimeBlock(
sema.air_instructions.items(.data)[@intFromEnum(prev_cond_br)].pl_op.payload = sema.addExtraAssumeCapacity(Air.CondBr{ sema.air_instructions.items(.data)[@intFromEnum(prev_cond_br)].pl_op.payload = sema.addExtraAssumeCapacity(Air.CondBr{
.then_body_len = @intCast(prev_then_body.len), .then_body_len = @intCast(prev_then_body.len),
.else_body_len = @intCast(case_block.instructions.items.len), .else_body_len = @intCast(case_block.instructions.items.len),
.branch_hints = .{ .true = prev_hint, .false = else_hint }, .branch_hints = .{
.true = prev_hint,
.false = else_hint,
.then_cov = .poi,
.else_cov = .poi,
},
}); });
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(prev_then_body)); sema.air_extra.appendSliceAssumeCapacity(@ptrCast(prev_then_body));
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items)); sema.air_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
@ -19250,7 +19275,17 @@ fn zirBoolBr(
&else_block, &else_block,
lhs, lhs,
block_inst, block_inst,
if (is_bool_or) .{ .true = .none, .false = rhs_hint } else .{ .true = rhs_hint, .false = .none }, if (is_bool_or) .{
.true = .none,
.false = rhs_hint,
.then_cov = .poi,
.else_cov = .poi,
} else .{
.true = rhs_hint,
.false = .none,
.then_cov = .poi,
.else_cov = .poi,
},
); );
if (!rhs_noret) { if (!rhs_noret) {
if (try sema.resolveDefinedValue(rhs_block, rhs_src, coerced_rhs_result)) |rhs_val| { if (try sema.resolveDefinedValue(rhs_block, rhs_src, coerced_rhs_result)) |rhs_val| {
@ -19467,14 +19502,22 @@ fn zirCondbr(
true_instructions.len + sub_block.instructions.items.len); true_instructions.len + sub_block.instructions.items.len);
_ = try parent_block.addInst(.{ _ = try parent_block.addInst(.{
.tag = .cond_br, .tag = .cond_br,
.data = .{ .pl_op = .{ .data = .{
.operand = cond, .pl_op = .{
.payload = sema.addExtraAssumeCapacity(Air.CondBr{ .operand = cond,
.then_body_len = @intCast(true_instructions.len), .payload = sema.addExtraAssumeCapacity(Air.CondBr{
.else_body_len = @intCast(sub_block.instructions.items.len), .then_body_len = @intCast(true_instructions.len),
.branch_hints = .{ .true = true_hint, .false = false_hint }, .else_body_len = @intCast(sub_block.instructions.items.len),
}), .branch_hints = .{
} }, .true = true_hint,
.false = false_hint,
// Code coverage is desired for error handling.
.then_cov = .poi,
.else_cov = .poi,
},
}),
},
},
}); });
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(true_instructions)); sema.air_extra.appendSliceAssumeCapacity(@ptrCast(true_instructions));
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(sub_block.instructions.items)); sema.air_extra.appendSliceAssumeCapacity(@ptrCast(sub_block.instructions.items));
@ -19851,8 +19894,14 @@ fn retWithErrTracing(
const cond_br_payload = sema.addExtraAssumeCapacity(Air.CondBr{ const cond_br_payload = sema.addExtraAssumeCapacity(Air.CondBr{
.then_body_len = @intCast(then_block.instructions.items.len), .then_body_len = @intCast(then_block.instructions.items.len),
.else_body_len = @intCast(else_block.instructions.items.len), .else_body_len = @intCast(else_block.instructions.items.len),
// weight against error branch .branch_hints = .{
.branch_hints = .{ .true = .likely, .false = .unlikely }, // Weight against error branch.
.true = .likely,
.false = .unlikely,
// Code coverage is not valuable on either branch.
.then_cov = .none,
.else_cov = .none,
},
}); });
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(then_block.instructions.items)); sema.air_extra.appendSliceAssumeCapacity(@ptrCast(then_block.instructions.items));
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(else_block.instructions.items)); sema.air_extra.appendSliceAssumeCapacity(@ptrCast(else_block.instructions.items));
@ -27473,8 +27522,14 @@ fn addSafetyCheckExtra(
.payload = sema.addExtraAssumeCapacity(Air.CondBr{ .payload = sema.addExtraAssumeCapacity(Air.CondBr{
.then_body_len = 1, .then_body_len = 1,
.else_body_len = @intCast(fail_block.instructions.items.len), .else_body_len = @intCast(fail_block.instructions.items.len),
// safety check failure branch is cold .branch_hints = .{
.branch_hints = .{ .true = .likely, .false = .cold }, // Safety check failure branch is cold.
.true = .likely,
.false = .cold,
// Code coverage not wanted for panic branches.
.then_cov = .none,
.else_cov = .none,
},
}), }),
}, },
}, },

View File

@ -1275,7 +1275,7 @@ pub const Object = struct {
.is_small = options.is_small, .is_small = options.is_small,
.time_report = options.time_report, .time_report = options.time_report,
.tsan = options.sanitize_thread, .tsan = options.sanitize_thread,
.sancov = options.fuzz, .sancov = sanCovPassEnabled(comp.config.san_cov_trace_pc_guard),
.lto = options.lto, .lto = options.lto,
.asm_filename = null, .asm_filename = null,
.bin_filename = options.bin_path, .bin_filename = options.bin_path,
@ -1283,19 +1283,19 @@ pub const Object = struct {
.bitcode_filename = null, .bitcode_filename = null,
.coverage = .{ .coverage = .{
.CoverageType = .Edge, .CoverageType = .Edge,
.IndirectCalls = true, .IndirectCalls = false,
.TraceBB = false, .TraceBB = false,
.TraceCmp = true, .TraceCmp = false,
.TraceDiv = false, .TraceDiv = false,
.TraceGep = false, .TraceGep = false,
.Use8bitCounters = false, .Use8bitCounters = false,
.TracePC = false, .TracePC = false,
.TracePCGuard = comp.config.san_cov_trace_pc_guard, .TracePCGuard = comp.config.san_cov_trace_pc_guard,
.Inline8bitCounters = true, .Inline8bitCounters = false,
.InlineBoolFlag = false, .InlineBoolFlag = false,
.PCTable = true, .PCTable = false,
.NoPrune = false, .NoPrune = false,
.StackDepth = true, .StackDepth = false,
.TraceLoads = false, .TraceLoads = false,
.TraceStores = false, .TraceStores = false,
.CollectControlFlow = false, .CollectControlFlow = false,
@ -1655,6 +1655,25 @@ pub const Object = struct {
break :debug_info .{ file, subprogram }; break :debug_info .{ file, subprogram };
} else .{.none} ** 2; } else .{.none} ** 2;
const fuzz: ?FuncGen.Fuzz = f: {
if (!owner_mod.fuzz) break :f null;
if (func_analysis.disable_instrumentation) break :f null;
if (is_naked) break :f null;
// The void type used here is a placeholder to be replaced with an
// array of the appropriate size after the POI count is known.
const counters_variable = try o.builder.addVariable(.empty, .void, .default);
counters_variable.setLinkage(.private, &o.builder);
counters_variable.setAlignment(comptime Builder.Alignment.fromByteUnits(1), &o.builder);
counters_variable.setSection(try o.builder.string("__sancov_cntrs"), &o.builder);
break :f .{
.counters_variable = counters_variable,
.pcs = .{},
};
};
var fg: FuncGen = .{ var fg: FuncGen = .{
.gpa = gpa, .gpa = gpa,
.air = air, .air = air,
@ -1662,6 +1681,7 @@ pub const Object = struct {
.ng = &ng, .ng = &ng,
.wip = wip, .wip = wip,
.is_naked = fn_info.cc == .Naked, .is_naked = fn_info.cc == .Naked,
.fuzz = fuzz,
.ret_ptr = ret_ptr, .ret_ptr = ret_ptr,
.args = args.items, .args = args.items,
.arg_index = 0, .arg_index = 0,
@ -1679,7 +1699,7 @@ pub const Object = struct {
defer fg.deinit(); defer fg.deinit();
deinit_wip = false; deinit_wip = false;
fg.genBody(air.getMainBody()) catch |err| switch (err) { fg.genBody(air.getMainBody(), .poi) catch |err| switch (err) {
error.CodegenFail => { error.CodegenFail => {
try zcu.failed_codegen.put(zcu.gpa, func.owner_nav, ng.err_msg.?); try zcu.failed_codegen.put(zcu.gpa, func.owner_nav, ng.err_msg.?);
ng.err_msg = null; ng.err_msg = null;
@ -1688,6 +1708,24 @@ pub const Object = struct {
else => |e| return e, else => |e| return e,
}; };
if (fg.fuzz) |*f| {
{
const array_llvm_ty = try o.builder.arrayType(f.pcs.items.len, .i8);
f.counters_variable.ptrConst(&o.builder).global.ptr(&o.builder).type = array_llvm_ty;
const zero_init = try o.builder.zeroInitConst(array_llvm_ty);
try f.counters_variable.setInitializer(zero_init, &o.builder);
}
const array_llvm_ty = try o.builder.arrayType(f.pcs.items.len, .ptr);
const init_val = try o.builder.arrayConst(array_llvm_ty, f.pcs.items);
const pcs_variable = try o.builder.addVariable(.empty, array_llvm_ty, .default);
pcs_variable.setLinkage(.private, &o.builder);
pcs_variable.setMutability(.constant, &o.builder);
pcs_variable.setAlignment(Type.usize.abiAlignment(zcu).toLlvm(), &o.builder);
pcs_variable.setSection(try o.builder.string("__sancov_pcs1"), &o.builder);
try pcs_variable.setInitializer(init_val, &o.builder);
}
try fg.wip.finish(); try fg.wip.finish();
} }
@ -4729,6 +4767,7 @@ pub const FuncGen = struct {
liveness: Liveness, liveness: Liveness,
wip: Builder.WipFunction, wip: Builder.WipFunction,
is_naked: bool, is_naked: bool,
fuzz: ?Fuzz,
file: Builder.Metadata, file: Builder.Metadata,
scope: Builder.Metadata, scope: Builder.Metadata,
@ -4769,6 +4808,16 @@ pub const FuncGen = struct {
sync_scope: Builder.SyncScope, sync_scope: Builder.SyncScope,
const Fuzz = struct {
counters_variable: Builder.Variable.Index,
pcs: std.ArrayListUnmanaged(Builder.Constant),
fn deinit(f: *Fuzz, gpa: Allocator) void {
f.pcs.deinit(gpa);
f.* = undefined;
}
};
const BreakList = union { const BreakList = union {
list: std.MultiArrayList(struct { list: std.MultiArrayList(struct {
bb: Builder.Function.Block.Index, bb: Builder.Function.Block.Index,
@ -4778,9 +4827,11 @@ pub const FuncGen = struct {
}; };
fn deinit(self: *FuncGen) void { fn deinit(self: *FuncGen) void {
const gpa = self.gpa;
if (self.fuzz) |*f| f.deinit(self.gpa);
self.wip.deinit(); self.wip.deinit();
self.func_inst_table.deinit(self.gpa); self.func_inst_table.deinit(gpa);
self.blocks.deinit(self.gpa); self.blocks.deinit(gpa);
} }
fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) Error { fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) Error {
@ -4836,11 +4887,33 @@ pub const FuncGen = struct {
return o.null_opt_usize; return o.null_opt_usize;
} }
fn genBody(self: *FuncGen, body: []const Air.Inst.Index) Error!void { fn genBody(self: *FuncGen, body: []const Air.Inst.Index, coverage_point: Air.CoveragePoint) Error!void {
const o = self.ng.object; const o = self.ng.object;
const zcu = o.pt.zcu; const zcu = o.pt.zcu;
const ip = &zcu.intern_pool; const ip = &zcu.intern_pool;
const air_tags = self.air.instructions.items(.tag); const air_tags = self.air.instructions.items(.tag);
switch (coverage_point) {
.none => {},
.poi => if (self.fuzz) |*fuzz| {
const poi_index = fuzz.pcs.items.len;
const base_ptr = fuzz.counters_variable.toValue(&o.builder);
const ptr = if (poi_index == 0) base_ptr else try self.wip.gep(.inbounds, .i8, base_ptr, &.{
try o.builder.intValue(.i32, poi_index),
}, "");
const counter = try self.wip.load(.normal, .i8, ptr, .default, "");
const one = try o.builder.intValue(.i8, 1);
const counter_incremented = try self.wip.bin(.add, counter, one, "");
_ = try self.wip.store(.normal, counter_incremented, ptr, .default);
// LLVM does not allow blockaddress on the entry block.
const pc = if (self.wip.cursor.block == .entry)
self.wip.function.toConst(&o.builder)
else
try o.builder.blockAddrConst(self.wip.function, self.wip.cursor.block);
const gpa = self.gpa;
try fuzz.pcs.append(gpa, pc);
},
}
for (body, 0..) |inst, i| { for (body, 0..) |inst, i| {
if (self.liveness.isUnused(inst) and !self.air.mustLower(inst, ip)) continue; if (self.liveness.isUnused(inst) and !self.air.mustLower(inst, ip)) continue;
@ -4949,7 +5022,7 @@ pub const FuncGen = struct {
.ret_ptr => try self.airRetPtr(inst), .ret_ptr => try self.airRetPtr(inst),
.arg => try self.airArg(inst), .arg => try self.airArg(inst),
.bitcast => try self.airBitCast(inst), .bitcast => try self.airBitCast(inst),
.int_from_bool => try self.airIntFromBool(inst), .int_from_bool => try self.airIntFromBool(inst),
.block => try self.airBlock(inst), .block => try self.airBlock(inst),
.br => try self.airBr(inst), .br => try self.airBr(inst),
.switch_br => try self.airSwitchBr(inst), .switch_br => try self.airSwitchBr(inst),
@ -4966,7 +5039,7 @@ pub const FuncGen = struct {
.trunc => try self.airTrunc(inst), .trunc => try self.airTrunc(inst),
.fptrunc => try self.airFptrunc(inst), .fptrunc => try self.airFptrunc(inst),
.fpext => try self.airFpext(inst), .fpext => try self.airFpext(inst),
.int_from_ptr => try self.airIntFromPtr(inst), .int_from_ptr => try self.airIntFromPtr(inst),
.load => try self.airLoad(body[i..]), .load => try self.airLoad(body[i..]),
.loop => try self.airLoop(inst), .loop => try self.airLoop(inst),
.not => try self.airNot(inst), .not => try self.airNot(inst),
@ -5089,8 +5162,13 @@ pub const FuncGen = struct {
} }
} }
fn genBodyDebugScope(self: *FuncGen, maybe_inline_func: ?InternPool.Index, body: []const Air.Inst.Index) Error!void { fn genBodyDebugScope(
if (self.wip.strip) return self.genBody(body); self: *FuncGen,
maybe_inline_func: ?InternPool.Index,
body: []const Air.Inst.Index,
coverage_point: Air.CoveragePoint,
) Error!void {
if (self.wip.strip) return self.genBody(body, coverage_point);
const old_file = self.file; const old_file = self.file;
const old_inlined = self.inlined; const old_inlined = self.inlined;
@ -5137,7 +5215,8 @@ pub const FuncGen = struct {
.sp_flags = .{ .sp_flags = .{
.Optimized = mod.optimize_mode != .Debug, .Optimized = mod.optimize_mode != .Debug,
.Definition = true, .Definition = true,
.LocalToUnit = true, // TODO: we can't know this at this point, since the function could be exported later! // TODO: we can't know this at this point, since the function could be exported later!
.LocalToUnit = true,
}, },
}, },
o.debug_compile_unit, o.debug_compile_unit,
@ -5171,7 +5250,7 @@ pub const FuncGen = struct {
.no_location => {}, .no_location => {},
}; };
try self.genBody(body); try self.genBody(body, coverage_point);
} }
pub const CallAttr = enum { pub const CallAttr = enum {
@ -5881,7 +5960,7 @@ pub const FuncGen = struct {
const inst_ty = self.typeOfIndex(inst); const inst_ty = self.typeOfIndex(inst);
if (inst_ty.isNoReturn(zcu)) { if (inst_ty.isNoReturn(zcu)) {
try self.genBodyDebugScope(maybe_inline_func, body); try self.genBodyDebugScope(maybe_inline_func, body, .none);
return .none; return .none;
} }
@ -5897,7 +5976,7 @@ pub const FuncGen = struct {
}); });
defer assert(self.blocks.remove(inst)); defer assert(self.blocks.remove(inst));
try self.genBodyDebugScope(maybe_inline_func, body); try self.genBodyDebugScope(maybe_inline_func, body, .none);
self.wip.cursor = .{ .block = parent_bb }; self.wip.cursor = .{ .block = parent_bb };
@ -5996,11 +6075,11 @@ pub const FuncGen = struct {
self.wip.cursor = .{ .block = then_block }; self.wip.cursor = .{ .block = then_block };
if (hint == .then_cold) _ = try self.wip.callIntrinsicAssumeCold(); if (hint == .then_cold) _ = try self.wip.callIntrinsicAssumeCold();
try self.genBodyDebugScope(null, then_body); try self.genBodyDebugScope(null, then_body, extra.data.branch_hints.then_cov);
self.wip.cursor = .{ .block = else_block }; self.wip.cursor = .{ .block = else_block };
if (hint == .else_cold) _ = try self.wip.callIntrinsicAssumeCold(); if (hint == .else_cold) _ = try self.wip.callIntrinsicAssumeCold();
try self.genBodyDebugScope(null, else_body); try self.genBodyDebugScope(null, else_body, extra.data.branch_hints.else_cov);
// No need to reset the insert cursor since this instruction is noreturn. // No need to reset the insert cursor since this instruction is noreturn.
return .none; return .none;
@ -6085,7 +6164,7 @@ pub const FuncGen = struct {
fg.wip.cursor = .{ .block = return_block }; fg.wip.cursor = .{ .block = return_block };
if (err_cold) _ = try fg.wip.callIntrinsicAssumeCold(); if (err_cold) _ = try fg.wip.callIntrinsicAssumeCold();
try fg.genBodyDebugScope(null, body); try fg.genBodyDebugScope(null, body, .poi);
fg.wip.cursor = .{ .block = continue_block }; fg.wip.cursor = .{ .block = continue_block };
} }
@ -6196,14 +6275,14 @@ pub const FuncGen = struct {
} }
self.wip.cursor = .{ .block = case_block }; self.wip.cursor = .{ .block = case_block };
if (switch_br.getHint(case.idx) == .cold) _ = try self.wip.callIntrinsicAssumeCold(); if (switch_br.getHint(case.idx) == .cold) _ = try self.wip.callIntrinsicAssumeCold();
try self.genBodyDebugScope(null, case.body); try self.genBodyDebugScope(null, case.body, .poi);
} }
const else_body = it.elseBody(); const else_body = it.elseBody();
self.wip.cursor = .{ .block = else_block }; self.wip.cursor = .{ .block = else_block };
if (switch_br.getElseHint() == .cold) _ = try self.wip.callIntrinsicAssumeCold(); if (switch_br.getElseHint() == .cold) _ = try self.wip.callIntrinsicAssumeCold();
if (else_body.len != 0) { if (else_body.len != 0) {
try self.genBodyDebugScope(null, else_body); try self.genBodyDebugScope(null, else_body, .poi);
} else { } else {
_ = try self.wip.@"unreachable"(); _ = try self.wip.@"unreachable"();
} }
@ -6222,7 +6301,7 @@ pub const FuncGen = struct {
_ = try self.wip.br(loop_block); _ = try self.wip.br(loop_block);
self.wip.cursor = .{ .block = loop_block }; self.wip.cursor = .{ .block = loop_block };
try self.genBodyDebugScope(null, body); try self.genBodyDebugScope(null, body, .none);
// TODO instead of this logic, change AIR to have the property that // TODO instead of this logic, change AIR to have the property that
// every block is guaranteed to end with a noreturn instruction. // every block is guaranteed to end with a noreturn instruction.
@ -12194,3 +12273,7 @@ pub fn initializeLLVMTarget(arch: std.Target.Cpu.Arch) void {
=> unreachable, => unreachable,
} }
} }
fn sanCovPassEnabled(trace_pc_guard: bool) bool {
return trace_pc_guard;
}

View File

@ -795,6 +795,9 @@ const Writer = struct {
if (extra.data.branch_hints.true != .none) { if (extra.data.branch_hints.true != .none) {
try s.print(" {s}", .{@tagName(extra.data.branch_hints.true)}); try s.print(" {s}", .{@tagName(extra.data.branch_hints.true)});
} }
if (extra.data.branch_hints.then_cov != .none) {
try s.print(" {s}", .{@tagName(extra.data.branch_hints.then_cov)});
}
try s.writeAll(" {\n"); try s.writeAll(" {\n");
const old_indent = w.indent; const old_indent = w.indent;
w.indent += 2; w.indent += 2;
@ -814,6 +817,9 @@ const Writer = struct {
if (extra.data.branch_hints.false != .none) { if (extra.data.branch_hints.false != .none) {
try s.print(" {s}", .{@tagName(extra.data.branch_hints.false)}); try s.print(" {s}", .{@tagName(extra.data.branch_hints.false)});
} }
if (extra.data.branch_hints.else_cov != .none) {
try s.print(" {s}", .{@tagName(extra.data.branch_hints.else_cov)});
}
try s.writeAll(" {\n"); try s.writeAll(" {\n");
if (liveness_condbr.else_deaths.len != 0) { if (liveness_condbr.else_deaths.len != 0) {