Merge pull request #21236 from ziglang/fuzz

exclude unreachable code paths from having coverage instrumentation
This commit is contained in:
Andrew Kelley 2024-08-28 23:20:21 -07:00 committed by GitHub
commit e9a00ba7f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 333 additions and 137 deletions

View File

@ -166,6 +166,7 @@ fn mainServer() !void {
if (log_err_count != 0) @panic("error logs detected");
if (first) {
first = false;
const entry_addr = @intFromPtr(test_fn.func);
try server.serveU64Message(.fuzz_start_addr, entry_addr);
}
}
@ -347,7 +348,6 @@ const FuzzerSlice = extern struct {
};
var is_fuzz_test: bool = undefined;
var entry_addr: usize = 0;
extern fn fuzzer_next() FuzzerSlice;
extern fn fuzzer_init(cache_dir: FuzzerSlice) void;
@ -358,7 +358,6 @@ pub fn fuzzInput(options: testing.FuzzInputOptions) []const u8 {
if (crippled) return "";
is_fuzz_test = true;
if (builtin.fuzz) {
if (entry_addr == 0) entry_addr = @returnAddress();
return fuzzer_next().toSlice();
}
if (options.corpus.len == 0) return "";

View File

@ -30,19 +30,6 @@ fn logOverride(
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 {
handleCmp(@returnAddress(), arg1, arg2);
}
@ -105,7 +92,7 @@ const Fuzzer = struct {
gpa: Allocator,
rng: std.Random.DefaultPrng,
input: std.ArrayListUnmanaged(u8),
flagged_pcs: []const FlaggedPc,
pcs: []const usize,
pc_counters: []u8,
n_runs: usize,
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 {
score: usize,
id: Run.Id,
};
fn init(f: *Fuzzer, cache_dir: std.fs.Dir) !void {
const flagged_pcs = f.flagged_pcs;
fn init(f: *Fuzzer, cache_dir: std.fs.Dir, pc_counters: []u8, pcs: []const usize) !void {
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.
const pc_digest = d: {
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();
};
const pc_digest = std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(pcs));
f.coverage_id = pc_digest;
const hex_digest = std.fmt.hex(pc_digest);
const coverage_file_path = "v/" ++ hex_digest;
@ -213,12 +186,12 @@ const Fuzzer = struct {
.truncate = false,
});
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[1] == .pc_addr);
const bytes_len = @sizeOf(SeenPcsHeader) +
n_bitset_elems * @sizeOf(usize) +
flagged_pcs.len * @sizeOf(usize);
pcs.len * @sizeOf(usize);
const existing_len = coverage_file.getEndPos() catch |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)});
};
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);
for (existing_pcs, flagged_pcs, 0..) |old, new, i| {
if (old != new.addr) {
for (existing_pcs, pcs, 0..) |old, new, i| {
if (old != new) {
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 = .{
.n_runs = 0,
.unique_runs = 0,
.pcs_len = flagged_pcs.len,
.pcs_len = pcs.len,
.lowest_stack = std.math.maxInt(usize),
};
f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&header));
f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems * @sizeOf(usize));
for (flagged_pcs) |flagged_pc| {
f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&flagged_pc.addr));
}
f.seen_pcs.appendSliceAssumeCapacity(std.mem.sliceAsBytes(pcs));
}
}
@ -307,8 +278,8 @@ const Fuzzer = struct {
// Track code coverage from all runs.
comptime assert(SeenPcsHeader.trailing[0] == .pc_bits_usize);
const header_end_ptr: [*]volatile usize = @ptrCast(f.seen_pcs.items[@sizeOf(SeenPcsHeader)..]);
const remainder = f.flagged_pcs.len % @bitSizeOf(usize);
const aligned_len = f.flagged_pcs.len - remainder;
const remainder = f.pcs.len % @bitSizeOf(usize);
const aligned_len = f.pcs.len - remainder;
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 V = @Vector(@bitSizeOf(usize), u8);
@ -433,7 +404,7 @@ var fuzzer: Fuzzer = .{
.gpa = general_purpose_allocator.allocator(),
.rng = std.Random.DefaultPrng.init(0),
.input = .{},
.flagged_pcs = undefined,
.pcs = undefined,
.pc_counters = undefined,
.n_runs = 0,
.recent_cases = .{},
@ -455,8 +426,32 @@ export fn fuzzer_next() Fuzzer.Slice {
}
export fn fuzzer_init(cache_dir_struct: Fuzzer.Slice) void {
if (module_count_8bc == 0) fatal("__sanitizer_cov_8bit_counters_init was never called", .{});
if (module_count_pcs == 0) fatal("__sanitizer_cov_pcs_init was never called", .{});
// Linkers are expected to automatically add `__start_<section>` and
// `__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 = 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) });
};
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.

View File

@ -664,11 +664,16 @@ fn addEntryPoint(ws: *WebServer, coverage_id: u64, addr: u64) error{ AlreadyRepo
const coverage_map = ws.coverage_files.getPtr(coverage_id).?;
const header: *const abi.SeenPcsHeader = @ptrCast(coverage_map.mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]);
const pcs = header.pcAddrs();
const index = std.sort.upperBound(usize, pcs, addr, struct {
fn order(context: usize, item: usize) std.math.Order {
return std.math.order(item, context);
// Since this pcs list is unsorted, we must linear scan for the best index.
const index = i: {
var best: usize = 0;
for (pcs[1..], 1..) |elem_addr, i| {
if (elem_addr == addr) break :i i;
if (elem_addr > addr) continue;
if (elem_addr > pcs[best]) best = i;
}
}.order);
break :i best;
};
if (index >= pcs.len) {
log.err("unable to find unit test entry address 0x{x} in source locations (range: 0x{x} to 0x{x})", .{
addr, pcs[0], pcs[pcs.len - 1],
@ -678,8 +683,8 @@ fn addEntryPoint(ws: *WebServer, coverage_id: u64, addr: u64) error{ AlreadyRepo
if (false) {
const sl = coverage_map.source_locations[index];
const file_name = coverage_map.coverage.stringAt(coverage_map.coverage.fileAt(sl.file).basename);
log.debug("server found entry point for 0x{x} at {s}:{d}:{d}", .{
addr, file_name, sl.line, sl.column,
log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index {d} between {x} and {x}", .{
addr, file_name, sl.line, sl.column, index, pcs[index - 1], pcs[index + 1],
});
}
const gpa = ws.gpa;

View File

@ -218,12 +218,18 @@ no_builtin: bool = false,
/// Managed by the build runner, not user build script.
zig_process: ?*Step.ZigProcess,
/// Enables deprecated coverage instrumentation that is only useful if you
/// are using third party fuzzers that depend on it. Otherwise, slows down
/// the instrumented binary with unnecessary function calls.
/// Enables coverage instrumentation that is only useful if you are using third
/// party fuzzers that depend on it. Otherwise, slows down the instrumented
/// binary with unnecessary function calls.
///
/// To enable fuzz testing instrumentation on a compilation, see the `fuzz`
/// flag in `Module`.
/// This kind of coverage instrumentation is used by AFLplusplus v4.21c,
/// however, modern fuzzers - including Zig - have switched to using "inline
/// 8-bit counters" or "inline bool flag" which incurs only a single
/// instruction for coverage, along with "trace cmp" which instruments
/// comparisons and reports the operands.
///
/// To instead enable fuzz testing instrumentation on a compilation using Zig's
/// builtin fuzzer, see the `fuzz` flag in `Module`.
sanitize_coverage_trace_pc_guard: ?bool = null,
pub const ExpectedCompileErrors = union(enum) {

View File

@ -1126,7 +1126,9 @@ pub const CondBr = struct {
pub const BranchHints = packed struct(u32) {
true: 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 typeFullyResolved = types_resolved.checkType;
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{
.then_body_len = @intCast(then_block.instructions.items.len),
.else_body_len = @intCast(else_block.instructions.items.len),
// weight against error branch
.branch_hints = .{ .true = .likely, .false = .unlikely },
.branch_hints = .{
// 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(.{
.tag = .cond_br,
.data = .{ .pl_op = .{
.operand = cond,
.payload = sema.addExtraAssumeCapacity(Air.CondBr{
.then_body_len = @intCast(true_instructions.len),
.else_body_len = @intCast(sub_block.instructions.items.len),
.branch_hints = .{ .true = non_error_hint, .false = .none },
}),
} },
.data = .{
.pl_op = .{
.operand = cond,
.payload = sema.addExtraAssumeCapacity(Air.CondBr{
.then_body_len = @intCast(true_instructions.len),
.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(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{
.then_body_len = @intCast(prev_then_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(cond_body));
@ -13133,7 +13153,12 @@ fn analyzeSwitchRuntimeBlock(
sema.air_instructions.items(.data)[@intFromEnum(prev_cond_br)].pl_op.payload = sema.addExtraAssumeCapacity(Air.CondBr{
.then_body_len = @intCast(prev_then_body.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(case_block.instructions.items));
@ -19250,7 +19275,17 @@ fn zirBoolBr(
&else_block,
lhs,
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 (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);
_ = try parent_block.addInst(.{
.tag = .cond_br,
.data = .{ .pl_op = .{
.operand = cond,
.payload = sema.addExtraAssumeCapacity(Air.CondBr{
.then_body_len = @intCast(true_instructions.len),
.else_body_len = @intCast(sub_block.instructions.items.len),
.branch_hints = .{ .true = true_hint, .false = false_hint },
}),
} },
.data = .{
.pl_op = .{
.operand = cond,
.payload = sema.addExtraAssumeCapacity(Air.CondBr{
.then_body_len = @intCast(true_instructions.len),
.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(sub_block.instructions.items));
@ -19851,8 +19894,14 @@ fn retWithErrTracing(
const cond_br_payload = sema.addExtraAssumeCapacity(Air.CondBr{
.then_body_len = @intCast(then_block.instructions.items.len),
.else_body_len = @intCast(else_block.instructions.items.len),
// weight against error branch
.branch_hints = .{ .true = .likely, .false = .unlikely },
.branch_hints = .{
// 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(else_block.instructions.items));
@ -27473,8 +27522,14 @@ fn addSafetyCheckExtra(
.payload = sema.addExtraAssumeCapacity(Air.CondBr{
.then_body_len = 1,
.else_body_len = @intCast(fail_block.instructions.items.len),
// safety check failure branch is cold
.branch_hints = .{ .true = .likely, .false = .cold },
.branch_hints = .{
// 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

@ -822,6 +822,9 @@ pub const Object = struct {
/// This is denormalized data.
struct_field_map: std.AutoHashMapUnmanaged(ZigStructField, c_uint),
/// Values for `@llvm.used`.
used: std.ArrayListUnmanaged(Builder.Constant),
const ZigStructField = struct {
struct_ty: InternPool.Index,
field_index: u32,
@ -975,6 +978,7 @@ pub const Object = struct {
.error_name_table = .none,
.null_opt_usize = .no_init,
.struct_field_map = .{},
.used = .{},
};
return obj;
}
@ -1097,44 +1101,57 @@ pub const Object = struct {
lto: bool,
};
pub fn emit(self: *Object, options: EmitOptions) !void {
const zcu = self.pt.zcu;
pub fn emit(o: *Object, options: EmitOptions) !void {
const zcu = o.pt.zcu;
const comp = zcu.comp;
{
try self.genErrorNameTable();
try self.genCmpLtErrorsLenFunction();
try self.genModuleLevelAssembly();
try o.genErrorNameTable();
try o.genCmpLtErrorsLenFunction();
try o.genModuleLevelAssembly();
if (!self.builder.strip) {
if (o.used.items.len > 0) {
const array_llvm_ty = try o.builder.arrayType(o.used.items.len, .ptr);
const init_val = try o.builder.arrayConst(array_llvm_ty, o.used.items);
const compiler_used_variable = try o.builder.addVariable(
try o.builder.strtabString("llvm.used"),
array_llvm_ty,
.default,
);
compiler_used_variable.setLinkage(.appending, &o.builder);
compiler_used_variable.setSection(try o.builder.string("llvm.metadata"), &o.builder);
try compiler_used_variable.setInitializer(init_val, &o.builder);
}
if (!o.builder.strip) {
{
var i: usize = 0;
while (i < self.debug_unresolved_namespace_scopes.count()) : (i += 1) {
const namespace_index = self.debug_unresolved_namespace_scopes.keys()[i];
const fwd_ref = self.debug_unresolved_namespace_scopes.values()[i];
while (i < o.debug_unresolved_namespace_scopes.count()) : (i += 1) {
const namespace_index = o.debug_unresolved_namespace_scopes.keys()[i];
const fwd_ref = o.debug_unresolved_namespace_scopes.values()[i];
const namespace = zcu.namespacePtr(namespace_index);
const debug_type = try self.lowerDebugType(Type.fromInterned(namespace.owner_type));
const debug_type = try o.lowerDebugType(Type.fromInterned(namespace.owner_type));
self.builder.debugForwardReferenceSetType(fwd_ref, debug_type);
o.builder.debugForwardReferenceSetType(fwd_ref, debug_type);
}
}
self.builder.debugForwardReferenceSetType(
self.debug_enums_fwd_ref,
try self.builder.metadataTuple(self.debug_enums.items),
o.builder.debugForwardReferenceSetType(
o.debug_enums_fwd_ref,
try o.builder.metadataTuple(o.debug_enums.items),
);
self.builder.debugForwardReferenceSetType(
self.debug_globals_fwd_ref,
try self.builder.metadataTuple(self.debug_globals.items),
o.builder.debugForwardReferenceSetType(
o.debug_globals_fwd_ref,
try o.builder.metadataTuple(o.debug_globals.items),
);
}
}
const target_triple_sentinel =
try self.gpa.dupeZ(u8, self.builder.target_triple.slice(&self.builder).?);
defer self.gpa.free(target_triple_sentinel);
try o.gpa.dupeZ(u8, o.builder.target_triple.slice(&o.builder).?);
defer o.gpa.free(target_triple_sentinel);
const emit_asm_msg = options.asm_path orelse "(none)";
const emit_bin_msg = options.bin_path orelse "(none)";
@ -1147,15 +1164,15 @@ pub const Object = struct {
const context, const module = emit: {
if (options.pre_ir_path) |path| {
if (std.mem.eql(u8, path, "-")) {
self.builder.dump();
o.builder.dump();
} else {
_ = try self.builder.printToFile(path);
_ = try o.builder.printToFile(path);
}
}
const bitcode = try self.builder.toBitcode(self.gpa);
defer self.gpa.free(bitcode);
self.builder.clearAndFree();
const bitcode = try o.builder.toBitcode(o.gpa);
defer o.gpa.free(bitcode);
o.builder.clearAndFree();
if (options.pre_bc_path) |path| {
var file = try std.fs.cwd().createFile(path, .{});
@ -1283,7 +1300,10 @@ pub const Object = struct {
.bitcode_filename = null,
.coverage = .{
.CoverageType = .Edge,
.IndirectCalls = true,
// Works in tandem with Inline8bitCounters or InlineBoolFlag.
// Zig does not yet implement its own version of this but it
// needs to for better fuzzing logic.
.IndirectCalls = false,
.TraceBB = false,
.TraceCmp = true,
.TraceDiv = false,
@ -1291,10 +1311,13 @@ pub const Object = struct {
.Use8bitCounters = false,
.TracePC = false,
.TracePCGuard = comp.config.san_cov_trace_pc_guard,
.Inline8bitCounters = true,
// Zig emits its own inline 8-bit counters instrumentation.
.Inline8bitCounters = false,
.InlineBoolFlag = false,
.PCTable = true,
// Zig emits its own PC table instrumentation.
.PCTable = false,
.NoPrune = false,
// Workaround for https://github.com/llvm/llvm-project/pull/106464
.StackDepth = true,
.TraceLoads = false,
.TraceStores = false,
@ -1655,6 +1678,29 @@ pub const Object = struct {
break :debug_info .{ file, subprogram };
} 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;
if (comp.config.san_cov_trace_pc_guard) 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.
// Due to error "members of llvm.compiler.used must be named", this global needs a name.
const anon_name = try o.builder.strtabStringFmt("__sancov_gen_.{d}", .{o.used.items.len});
const counters_variable = try o.builder.addVariable(anon_name, .void, .default);
try o.used.append(gpa, counters_variable.toConst(&o.builder));
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 = .{
.gpa = gpa,
.air = air,
@ -1662,6 +1708,7 @@ pub const Object = struct {
.ng = &ng,
.wip = wip,
.is_naked = fn_info.cc == .Naked,
.fuzz = fuzz,
.ret_ptr = ret_ptr,
.args = args.items,
.arg_index = 0,
@ -1679,15 +1726,36 @@ pub const Object = struct {
defer fg.deinit();
deinit_wip = false;
fg.genBody(air.getMainBody()) catch |err| switch (err) {
fg.genBody(air.getMainBody(), .poi) catch |err| switch (err) {
error.CodegenFail => {
try zcu.failed_codegen.put(zcu.gpa, func.owner_nav, ng.err_msg.?);
try zcu.failed_codegen.put(gpa, func.owner_nav, ng.err_msg.?);
ng.err_msg = null;
return;
},
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);
// Due to error "members of llvm.compiler.used must be named", this global needs a name.
const anon_name = try o.builder.strtabStringFmt("__sancov_gen_.{d}", .{o.used.items.len});
const pcs_variable = try o.builder.addVariable(anon_name, array_llvm_ty, .default);
try o.used.append(gpa, pcs_variable.toConst(&o.builder));
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();
}
@ -4729,6 +4797,7 @@ pub const FuncGen = struct {
liveness: Liveness,
wip: Builder.WipFunction,
is_naked: bool,
fuzz: ?Fuzz,
file: Builder.Metadata,
scope: Builder.Metadata,
@ -4769,6 +4838,16 @@ pub const FuncGen = struct {
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 {
list: std.MultiArrayList(struct {
bb: Builder.Function.Block.Index,
@ -4778,9 +4857,11 @@ pub const FuncGen = struct {
};
fn deinit(self: *FuncGen) void {
const gpa = self.gpa;
if (self.fuzz) |*f| f.deinit(self.gpa);
self.wip.deinit();
self.func_inst_table.deinit(self.gpa);
self.blocks.deinit(self.gpa);
self.func_inst_table.deinit(gpa);
self.blocks.deinit(gpa);
}
fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) Error {
@ -4836,11 +4917,33 @@ pub const FuncGen = struct {
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 zcu = o.pt.zcu;
const ip = &zcu.intern_pool;
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| {
if (self.liveness.isUnused(inst) and !self.air.mustLower(inst, ip)) continue;
@ -4949,7 +5052,7 @@ pub const FuncGen = struct {
.ret_ptr => try self.airRetPtr(inst),
.arg => try self.airArg(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),
.br => try self.airBr(inst),
.switch_br => try self.airSwitchBr(inst),
@ -4966,7 +5069,7 @@ pub const FuncGen = struct {
.trunc => try self.airTrunc(inst),
.fptrunc => try self.airFptrunc(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..]),
.loop => try self.airLoop(inst),
.not => try self.airNot(inst),
@ -5089,8 +5192,13 @@ pub const FuncGen = struct {
}
}
fn genBodyDebugScope(self: *FuncGen, maybe_inline_func: ?InternPool.Index, body: []const Air.Inst.Index) Error!void {
if (self.wip.strip) return self.genBody(body);
fn genBodyDebugScope(
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_inlined = self.inlined;
@ -5137,7 +5245,8 @@ pub const FuncGen = struct {
.sp_flags = .{
.Optimized = mod.optimize_mode != .Debug,
.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,
@ -5171,7 +5280,7 @@ pub const FuncGen = struct {
.no_location => {},
};
try self.genBody(body);
try self.genBody(body, coverage_point);
}
pub const CallAttr = enum {
@ -5881,7 +5990,7 @@ pub const FuncGen = struct {
const inst_ty = self.typeOfIndex(inst);
if (inst_ty.isNoReturn(zcu)) {
try self.genBodyDebugScope(maybe_inline_func, body);
try self.genBodyDebugScope(maybe_inline_func, body, .none);
return .none;
}
@ -5897,7 +6006,7 @@ pub const FuncGen = struct {
});
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 };
@ -5996,11 +6105,11 @@ pub const FuncGen = struct {
self.wip.cursor = .{ .block = then_block };
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 };
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.
return .none;
@ -6085,7 +6194,7 @@ pub const FuncGen = struct {
fg.wip.cursor = .{ .block = return_block };
if (err_cold) _ = try fg.wip.callIntrinsicAssumeCold();
try fg.genBodyDebugScope(null, body);
try fg.genBodyDebugScope(null, body, .poi);
fg.wip.cursor = .{ .block = continue_block };
}
@ -6196,14 +6305,14 @@ pub const FuncGen = struct {
}
self.wip.cursor = .{ .block = case_block };
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();
self.wip.cursor = .{ .block = else_block };
if (switch_br.getElseHint() == .cold) _ = try self.wip.callIntrinsicAssumeCold();
if (else_body.len != 0) {
try self.genBodyDebugScope(null, else_body);
try self.genBodyDebugScope(null, else_body, .poi);
} else {
_ = try self.wip.@"unreachable"();
}
@ -6222,7 +6331,7 @@ pub const FuncGen = struct {
_ = try self.wip.br(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
// every block is guaranteed to end with a noreturn instruction.

View File

@ -10046,8 +10046,9 @@ pub fn printUnbuffered(
}
if (maybe_dbg_index) |dbg_index| {
try writer.print(", !dbg !{}\n", .{dbg_index});
} else try writer.writeByte('\n');
try writer.print(", !dbg !{}", .{dbg_index});
}
try writer.writeByte('\n');
}
try writer.writeByte('}');
}

View File

@ -791,7 +791,14 @@ const Writer = struct {
try w.writeOperand(s, inst, 0, pl_op.operand);
if (w.skip_body) return s.writeAll(", ...");
try s.writeAll(", {\n");
try s.writeAll(",");
if (extra.data.branch_hints.true != .none) {
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");
const old_indent = w.indent;
w.indent += 2;
@ -806,7 +813,14 @@ const Writer = struct {
try w.writeBody(s, then_body);
try s.writeByteNTimes(' ', old_indent);
try s.writeAll("}, {\n");
try s.writeAll("},");
if (extra.data.branch_hints.false != .none) {
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");
if (liveness_condbr.else_deaths.len != 0) {
try s.writeByteNTimes(' ', w.indent);