sometimes: implement reachableSometimes and build a list of sometimes branches at compile-time
This commit is contained in:
+165
-22
@@ -2,6 +2,112 @@ const std = @import("std");
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const SourceLocation = std.builtin.SourceLocation;
|
const SourceLocation = std.builtin.SourceLocation;
|
||||||
|
|
||||||
|
const sometimes_section = "zigmulator_sometimes";
|
||||||
|
const site_magic: u64 = 0x5a_69_67_53_6f_6d_65_74;
|
||||||
|
|
||||||
|
pub const SiteKind = enum {
|
||||||
|
assert,
|
||||||
|
reachable,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Site = struct {
|
||||||
|
file: []const u8,
|
||||||
|
fn_name: []const u8,
|
||||||
|
line: u32,
|
||||||
|
column: u32,
|
||||||
|
label: ?[]const u8,
|
||||||
|
kind: u8,
|
||||||
|
magic: u64,
|
||||||
|
|
||||||
|
pub fn init(comptime src: SourceLocation, comptime label: ?[]const u8, comptime kind: SiteKind) Site {
|
||||||
|
return .{
|
||||||
|
.file = src.file,
|
||||||
|
.fn_name = src.fn_name,
|
||||||
|
.line = src.line,
|
||||||
|
.column = src.column,
|
||||||
|
.label = label,
|
||||||
|
.kind = @intFromEnum(kind),
|
||||||
|
.magic = site_magic,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isValid(self: Site) bool {
|
||||||
|
return self.magic == site_magic and self.line != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn kindName(self: Site) []const u8 {
|
||||||
|
return switch (self.kind) {
|
||||||
|
@intFromEnum(SiteKind.assert) => "assert",
|
||||||
|
@intFromEnum(SiteKind.reachable) => "reachable",
|
||||||
|
else => "unknown",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches(self: Site, src: SourceLocation) bool {
|
||||||
|
return self.line == src.line and
|
||||||
|
self.column == src.column and
|
||||||
|
std.mem.eql(u8, self.file, src.file);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matchesSite(self: Site, other: Site) bool {
|
||||||
|
return self.line == other.line and
|
||||||
|
self.column == other.column and
|
||||||
|
std.mem.eql(u8, self.file, other.file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sentinel_site = Site{
|
||||||
|
.file = "",
|
||||||
|
.fn_name = "",
|
||||||
|
.line = 0,
|
||||||
|
.column = 0,
|
||||||
|
.label = null,
|
||||||
|
.kind = @intFromEnum(SiteKind.assert),
|
||||||
|
.magic = site_magic,
|
||||||
|
};
|
||||||
|
const sentinel_site_entry linksection(sometimes_section) = sentinel_site;
|
||||||
|
|
||||||
|
extern const __start_zigmulator_sometimes: Site;
|
||||||
|
extern const __stop_zigmulator_sometimes: Site;
|
||||||
|
|
||||||
|
pub fn registerSite(comptime src: SourceLocation, comptime label: ?[]const u8, comptime kind: SiteKind) *const Site {
|
||||||
|
const SiteDecl = struct {
|
||||||
|
const site linksection(sometimes_section) = Site.init(src, label, kind);
|
||||||
|
};
|
||||||
|
return &SiteDecl.site;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compileTimeSites() []const Site {
|
||||||
|
_ = &sentinel_site_entry;
|
||||||
|
const start: [*]const Site = @ptrCast(&__start_zigmulator_sometimes);
|
||||||
|
const stop_addr = @intFromPtr(&__stop_zigmulator_sometimes);
|
||||||
|
const start_addr = @intFromPtr(start);
|
||||||
|
const count = (stop_addr - start_addr) / @sizeOf(Site);
|
||||||
|
return start[0..count];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reportCompileTimeSites() void {
|
||||||
|
var count: usize = 0;
|
||||||
|
for (compileTimeSites()) |site| {
|
||||||
|
if (site.isValid()) count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count == 0) return;
|
||||||
|
|
||||||
|
std.debug.print("\n=== Sometimes assertion sites: {d} compiled ===\n", .{count});
|
||||||
|
for (compileTimeSites()) |site| {
|
||||||
|
if (!site.isValid()) continue;
|
||||||
|
|
||||||
|
std.debug.print(" [{s}] {s}:{d}:{d} ({s})", .{
|
||||||
|
site.kindName(), site.file, site.line, site.column, site.fn_name,
|
||||||
|
});
|
||||||
|
if (site.label) |label|
|
||||||
|
std.debug.print(" \"{s}\"", .{label});
|
||||||
|
std.debug.print("\n", .{});
|
||||||
|
}
|
||||||
|
std.debug.print("\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
// Registry of "sometimes" assertions.
|
// Registry of "sometimes" assertions.
|
||||||
//
|
//
|
||||||
// A sometimes-assertion records that a given condition was observed to be true
|
// A sometimes-assertion records that a given condition was observed to be true
|
||||||
@@ -14,10 +120,8 @@ const SourceLocation = std.builtin.SourceLocation;
|
|||||||
// physical assertSometimes() call is one entry regardless of how many times
|
// physical assertSometimes() call is one entry regardless of how many times
|
||||||
// it is evaluated. An optional human-readable label is shown in the report.
|
// it is evaluated. An optional human-readable label is shown in the report.
|
||||||
//
|
//
|
||||||
// The registry only knows about assertions that were *evaluated* at least once.
|
// The registry is seeded with compile-time call-site descriptors, so the final
|
||||||
// A call site that is reached with the condition true at least once is reported
|
// report can include branches that were compiled but never evaluated.
|
||||||
// as taken; one that is reached but whose condition is always false is reported
|
|
||||||
// as not-taken. A call site that is never reached at all cannot appear.
|
|
||||||
|
|
||||||
pub const Sometimes = struct {
|
pub const Sometimes = struct {
|
||||||
const Entry = struct {
|
const Entry = struct {
|
||||||
@@ -26,13 +130,14 @@ pub const Sometimes = struct {
|
|||||||
line: u32,
|
line: u32,
|
||||||
column: u32,
|
column: u32,
|
||||||
label: ?[]const u8,
|
label: ?[]const u8,
|
||||||
|
kind_name: []const u8,
|
||||||
eval_count: u64,
|
eval_count: u64,
|
||||||
true_count: u64,
|
true_count: u64,
|
||||||
|
|
||||||
fn matches(self: Entry, src: SourceLocation) bool {
|
fn matchesSite(self: Entry, site: Site) bool {
|
||||||
return self.line == src.line and
|
return self.line == site.line and
|
||||||
self.column == src.column and
|
self.column == site.column and
|
||||||
std.mem.eql(u8, self.file, src.file);
|
std.mem.eql(u8, self.file, site.file);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -46,6 +151,13 @@ pub const Sometimes = struct {
|
|||||||
self.reported = false;
|
self.reported = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn seedCompileTimeSites(self: *Sometimes) void {
|
||||||
|
for (compileTimeSites()) |site| {
|
||||||
|
if (!site.isValid()) continue;
|
||||||
|
self.ensureSite(site);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Sometimes) void {
|
pub fn deinit(self: *Sometimes) void {
|
||||||
self.entries.deinit(self.gpa);
|
self.entries.deinit(self.gpa);
|
||||||
}
|
}
|
||||||
@@ -53,12 +165,12 @@ pub const Sometimes = struct {
|
|||||||
// Records one evaluation of a sometimes-assertion. The strings carried by
|
// Records one evaluation of a sometimes-assertion. The strings carried by
|
||||||
// @src() (and any string-literal label) live for the whole program, so we
|
// @src() (and any string-literal label) live for the whole program, so we
|
||||||
// can store the slices directly without duplicating them.
|
// can store the slices directly without duplicating them.
|
||||||
pub fn record(self: *Sometimes, cond: bool, src: SourceLocation, label: ?[]const u8) void {
|
pub fn record(self: *Sometimes, cond: bool, site: Site) void {
|
||||||
for (self.entries.items) |*entry| {
|
for (self.entries.items) |*entry| {
|
||||||
if (entry.matches(src)) {
|
if (entry.matchesSite(site)) {
|
||||||
entry.eval_count += 1;
|
entry.eval_count += 1;
|
||||||
if (cond) entry.true_count += 1;
|
if (cond) entry.true_count += 1;
|
||||||
if (label != null and entry.label == null) entry.label = label;
|
if (site.label != null and entry.label == null) entry.label = site.label;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,17 +178,44 @@ pub const Sometimes = struct {
|
|||||||
// First time this call site is seen. If we can't grow the registry we
|
// First time this call site is seen. If we can't grow the registry we
|
||||||
// simply drop the entry: a sometimes-assertion must never affect the
|
// simply drop the entry: a sometimes-assertion must never affect the
|
||||||
// behaviour of the program under test.
|
// behaviour of the program under test.
|
||||||
|
self.appendSite(site, 1, if (cond) 1 else 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensureSite(self: *Sometimes, site: Site) void {
|
||||||
|
for (self.entries.items) |entry| {
|
||||||
|
if (site.matchesSite(.{
|
||||||
|
.file = entry.file,
|
||||||
|
.fn_name = entry.fn_name,
|
||||||
|
.line = entry.line,
|
||||||
|
.column = entry.column,
|
||||||
|
.label = entry.label,
|
||||||
|
.kind = 0,
|
||||||
|
.magic = site_magic,
|
||||||
|
})) return;
|
||||||
|
}
|
||||||
|
self.appendSite(site, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn appendSite(self: *Sometimes, site: Site, eval_count: u64, true_count: u64) void {
|
||||||
self.entries.append(self.gpa, .{
|
self.entries.append(self.gpa, .{
|
||||||
.file = src.file,
|
.file = site.file,
|
||||||
.fn_name = src.fn_name,
|
.fn_name = site.fn_name,
|
||||||
.line = src.line,
|
.line = site.line,
|
||||||
.column = src.column,
|
.column = site.column,
|
||||||
.label = label,
|
.label = site.label,
|
||||||
.eval_count = 1,
|
.kind_name = site.kindName(),
|
||||||
.true_count = if (cond) 1 else 0,
|
.eval_count = eval_count,
|
||||||
|
.true_count = true_count,
|
||||||
}) catch return;
|
}) catch return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn allReached(self: Sometimes) bool {
|
||||||
|
for (self.entries.items) |entry| {
|
||||||
|
if (entry.true_count == 0) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn report(self: *Sometimes) void {
|
pub fn report(self: *Sometimes) void {
|
||||||
self.reported = true;
|
self.reported = true;
|
||||||
|
|
||||||
@@ -90,13 +229,17 @@ pub const Sometimes = struct {
|
|||||||
|
|
||||||
std.debug.print("\n=== Sometimes assertions: {d}/{d} taken ===\n", .{ taken, self.entries.items.len });
|
std.debug.print("\n=== Sometimes assertions: {d}/{d} taken ===\n", .{ taken, self.entries.items.len });
|
||||||
for (self.entries.items) |entry| {
|
for (self.entries.items) |entry| {
|
||||||
const mark = if (entry.true_count > 0) "\u{2713}" else "\u{2717}";
|
std.debug.print(" [{d}/{d}] {s}:{d}:{d} ({s})", .{
|
||||||
std.debug.print(" [{s}] {s}:{d}:{d} ({s})", .{
|
entry.true_count,
|
||||||
mark, entry.file, entry.line, entry.column, entry.fn_name,
|
entry.eval_count,
|
||||||
|
entry.file,
|
||||||
|
entry.line,
|
||||||
|
entry.column,
|
||||||
|
entry.fn_name,
|
||||||
});
|
});
|
||||||
if (entry.label) |label|
|
if (entry.label) |label|
|
||||||
std.debug.print(" \"{s}\"", .{label});
|
std.debug.print(" \"{s}\"", .{label});
|
||||||
std.debug.print(" \u{2014} {d}/{d} true\n", .{ entry.true_count, entry.eval_count });
|
std.debug.print("\n", .{});
|
||||||
}
|
}
|
||||||
std.debug.print("\n", .{});
|
std.debug.print("\n", .{});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user