Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
acb866b
refactor!: mutators emit a single value per call (#41)
twof Jun 12, 2026
1743baf
feat!: engine-owned mutation pool (WeightedPool scheduler)
twof Jun 12, 2026
d95504b
feat: feature-ownership culling as a pool admission policy
twof Jun 12, 2026
e645937
feat: Entropic energy scheduler as a pool weight advisor
twof Jun 12, 2026
babdce5
feat: strategy-defined ledger features (pathTrie k-grams, hcb edge-bu…
twof Jun 12, 2026
221bb3d
feat: pathTrie(gramLength: nil) opts out of the gram vocabulary
twof Jun 12, 2026
7ff6c2e
feat: pool capacity bound (ghost-owner eviction on overflow)
twof Jun 12, 2026
07d99a2
feat: real input-size metric for REDUCE and capacity eviction
twof Jun 12, 2026
210fdea
feat: edge culling is every strategy's default vocabulary (grams stay…
twof Jun 12, 2026
d059a29
feat: trace-cmp C hooks — per-context comparison recorders
twof Jun 13, 2026
0d7d757
feat: comparisonCoverage strategy + ComparisonObserver bridge
twof Jun 13, 2026
690b295
docs: comparisonCoverage measured to under-perform newEdge (corpus bl…
twof Jun 13, 2026
ea4c6f4
feat: input-to-state mutation off the trace-cmp operands
twof Jun 13, 2026
f454f7e
feat: boundary-distance ownership (closest-witness culling on the val…
twof Jun 13, 2026
9f92939
feat: adaptive-depth pool policy (tuned) + SchedulerProbe + flaky-tes…
twof Jun 15, 2026
26c0fd4
fix: cmp-dispatch re-entry guard (stack-overflow crash) + global ever…
twof Jun 15, 2026
0b67469
feat: joint boundary-state vocabulary (.boundaryState + boundaryState…
twof Jun 15, 2026
fc2f796
feat: boundary-state sign mask (per-site side-set across loop hits)
twof Jun 15, 2026
6e6ab63
feat: PTK_SIGN_BLOWUP diagnostic — pairwise vs full-product vocab size
twof Jun 15, 2026
9aa2762
perf: open-addressing accumulator for the boundary cmp hot path (2.7x)
twof Jun 15, 2026
0339fa1
tools: headless cmp-dispatch profiling pipeline
twof Jun 15, 2026
cac8906
perf: coalesce per-comparison TLS into one struct (cmp-path tlv_get_a…
twof Jun 15, 2026
bef33a5
perf: lock-free cmp accumulator + elide per-comparison ARC (cmp overh…
twof Jun 15, 2026
ed8e8ca
diag: env-gated per-comparison census (PTK_CMP_CENSUS) for cmp-volume…
twof Jun 15, 2026
b53763d
feat: comparison drop filter (PTK_CMP_DROP_SYNTHESIZED) for trace-cmp…
twof Jun 15, 2026
a8e43d8
feat: enable the comparison drop filter by default (opt out with =0)
twof Jun 15, 2026
512f9a2
perf: run the cmp drop check before the TLS fetch (skip tlv_get_addr …
twof Jun 15, 2026
b39b7a6
perf: kill per-iteration allocation in boundaryState decide (1.6× thr…
twof Jun 15, 2026
c4d93f2
perf: replace the Set<UInt32> edge-coverage union with a test-and-set…
twof Jun 15, 2026
149c2e4
perf: no-SipHash FeatureHashSet for seenSigns/seenFeatures (drops ~5.…
twof Jun 15, 2026
4e6ab2f
perf: suppress coverage dispatch during input generation/mutation
twof Jun 15, 2026
ae40c9e
perf: excise locks from the coverage-strategy hot path (Finding 42)
twof Jun 16, 2026
e691319
perf: gate sancov_dispatch_cmp on a global cmp-recorder count
twof Jun 16, 2026
a21b04c
perf: check cmp-recorder gate before the drop filter (edge-only freebie)
twof Jun 16, 2026
cb712b1
chore(atomics): funnel atomic storage through an AtomicRep typealias
twof Jun 16, 2026
a67ba8b
fix(sancov): survive a freed task-local chain head in the inheritance…
twof Jun 16, 2026
c3f286c
fix(test): inject the WeightedPool draw RNG to kill the EntropicPolic…
twof Jun 16, 2026
88e6bef
perf(boundary): distance-only accumulator; drop the sign vocabulary
twof Jun 16, 2026
0443e6f
chore: sync Xcode project, drop jemalloc pin, harden profiling script
twof Jun 16, 2026
27aaa64
build: add in-repo LLVM pass plugins for compile-time coverage instru…
twof Jun 17, 2026
96604fd
build: load coverage pass plugins in all instrumented targets
twof Jun 17, 2026
08b0a9d
refactor: delete runtime edge + cmp filters (now done at compile time)
twof Jun 17, 2026
4a71968
feat: compose coverage strategies so cmp and edge channels mix-and-match
twof Jun 17, 2026
06d5799
perf: default pool admission to featureOwnership (culling), not every…
twof Jun 17, 2026
30daa8f
feat: re-port trace-cmp substrate onto the scheduler-owned engine (so…
twof Jun 19, 2026
200fbc4
test: port trace-cmp test suites to the scheduler-owned API; finish t…
twof Jun 19, 2026
a4927a1
chore: regenerate Xcode project for the trace-cmp file set
twof Jun 19, 2026
f8baa44
test: deterministic stop for the pool-less scheduler test (kill wall-…
twof Jun 19, 2026
cf7cf0d
add perf parsing script
twof Jun 19, 2026
7706d6c
feat: generic OwnershipLedger + edge/boundary evaluators (Signal/Eval…
twof Jun 19, 2026
84686ae
refactor: featureOwnership drives evaluators+ledger; delete the paral…
twof Jun 19, 2026
d7c64da
refactor: demote coverageStrategy — the scheduler vends its instrumen…
twof Jun 19, 2026
b43ab05
test: cmp as a scheduler-vended signal culled through the unified led…
twof Jun 19, 2026
77f4480
refactor: restore MutationScheduler as the intended wrapper struct (n…
twof Jun 20, 2026
5d846a6
refactor: build the corpus at run-end from the scheduler's retained s…
twof Jun 20, 2026
510c8eb
refactor: corpus is now purely the scheduler's retained set; drop sub…
twof Jun 20, 2026
1c4f840
refactor: delete the Corpus class; corpus is only CorpusSnapshot now
twof Jun 20, 2026
8ebacfd
Merge signal-evaluator-ledger into trace-cmp-substrate (SEL supersedes)
twof Jun 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Benchmarks/CoverageBenchmarks/CoverageBenchmarks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ let benchmarks: @Sendable () -> Void = {
let result = try await fuzz(
duration: .seconds(0.1),
persistence: .ephemeral,
coverageStrategy: .newEdge
scheduler: MutationScheduler.weightedPool(coverageStrategy: .newEdge)
) { (input: Int) in
blackHole(input)
}
Expand Down Expand Up @@ -140,7 +140,7 @@ let benchmarks: @Sendable () -> Void = {
let result = try await fuzz(
duration: .seconds(0.1),
persistence: .ephemeral,
coverageStrategy: CoverageStrategy(onEdge: { edge, _ in blackHole(edge) }) { _ in false }
scheduler: MutationScheduler.weightedPool(coverageStrategy: CoverageStrategy(onEdge: { edge, _ in blackHole(edge) }) { _ in false })
) { (input: Int) in
blackHole(input)
}
Expand Down Expand Up @@ -182,7 +182,7 @@ let benchmarks: @Sendable () -> Void = {
let result = try await fuzz(
duration: .seconds(0.1),
persistence: .ephemeral,
coverageStrategy: .pathTrie
scheduler: MutationScheduler.weightedPool(coverageStrategy: .pathTrie)
) { (input: Int) in
blackHole(input)
}
Expand Down
3 changes: 2 additions & 1 deletion Benchmarks/ProfiledBenchmark/ProfiledBenchmark.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ let benchmarks: @Sendable () -> Void = {
let startWall = DispatchTime.now().uptimeNanoseconds

let result = try await fuzz(
duration: .seconds(0.1), persistence: .replace, coverageStrategy: .pathTrie
duration: .seconds(0.1), persistence: .replace,
scheduler: MutationScheduler.weightedPool(coverageStrategy: .pathTrie)
) { (input: Int) in
blackHole(input)
}
Expand Down
125 changes: 125 additions & 0 deletions LLVMPasses/EmitCmpTrace.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Out-of-tree LLVM pass plugin: emit __sanitizer_cov_trace_cmp* callbacks
// ourselves for the comparisons we care about, replacing SanitizerCoverage's
// trace-cmp emission. Build the SUT with `-sanitize-coverage=edge,pc-table`
// (NO trace-cmp) and load this plugin; it emits cmp callbacks for every integer
// comparison EXCEPT trap guards (bounds / overflow / precondition checks, whose
// branch reaches `unreachable`). Faithful to InjectTraceForCmp otherwise.

#include "llvm/ADT/SmallVector.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/PassManager.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Support/Compiler.h"

using namespace llvm;

namespace {

// True iff following single-successor edges from BB reaches an `unreachable`
// terminator within MaxDepth hops. Swift lowers cond_fail (bounds/overflow/
// precondition checks) to a branch whose failure edge runs — directly or via an
// empty split critical edge + shared trap merge block — into a
// _fatalErrorMessage/llvm.trap block ending in `unreachable`.
static bool reachesUnreachable(const BasicBlock *BB, unsigned MaxDepth) {
for (unsigned I = 0; BB && I <= MaxDepth; ++I) {
if (isa<UnreachableInst>(BB->getTerminator()))
return true;
BB = BB->getSingleSuccessor();
}
return false;
}

static bool isTrapGuard(ICmpInst *CMP) {
if (!CMP->hasOneUse())
return false;
auto *BR = dyn_cast<BranchInst>(CMP->user_back());
if (!BR || !BR->isConditional())
return false;
for (BasicBlock *Succ : BR->successors())
if (reachesUnreachable(Succ, /*MaxDepth=*/3))
return true;
return false;
}

struct EmitCmpTrace : PassInfoMixin<EmitCmpTrace> {
PreservedAnalyses run(Module &M, ModuleAnalysisManager &) {
LLVMContext &Ctx = M.getContext();
const DataLayout &DL = M.getDataLayout();
Type *VoidTy = Type::getVoidTy(Ctx);
IntegerType *IntTys[4] = {Type::getInt8Ty(Ctx), Type::getInt16Ty(Ctx),
Type::getInt32Ty(Ctx), Type::getInt64Ty(Ctx)};
const char *CmpNames[4] = {
"__sanitizer_cov_trace_cmp1", "__sanitizer_cov_trace_cmp2",
"__sanitizer_cov_trace_cmp4", "__sanitizer_cov_trace_cmp8"};
const char *ConstNames[4] = {"__sanitizer_cov_trace_const_cmp1",
"__sanitizer_cov_trace_const_cmp2",
"__sanitizer_cov_trace_const_cmp4",
"__sanitizer_cov_trace_const_cmp8"};
FunctionCallee CmpFn[4], ConstFn[4];
for (int i = 0; i < 4; ++i) {
FunctionType *FT = FunctionType::get(VoidTy, {IntTys[i], IntTys[i]}, false);
CmpFn[i] = M.getOrInsertFunction(CmpNames[i], FT);
ConstFn[i] = M.getOrInsertFunction(ConstNames[i], FT);
}

bool Changed = false;
for (Function &F : M) {
if (F.isDeclaration())
continue;
if (F.getName().starts_with("__sanitizer_"))
continue;
if (F.hasFnAttribute(Attribute::NoSanitizeCoverage))
continue;

SmallVector<ICmpInst *, 16> Targets;
for (BasicBlock &BB : F)
for (Instruction &I : BB)
if (auto *CMP = dyn_cast<ICmpInst>(&I))
if (!isTrapGuard(CMP))
Targets.push_back(CMP);

for (ICmpInst *CMP : Targets) {
Value *A0 = CMP->getOperand(0);
Value *A1 = CMP->getOperand(1);
if (!A0->getType()->isIntegerTy())
continue;
uint64_t TS = DL.getTypeStoreSizeInBits(A0->getType());
int Idx = TS == 8 ? 0 : TS == 16 ? 1 : TS == 32 ? 2 : TS == 64 ? 3 : -1;
if (Idx < 0)
continue;
bool C0 = isa<ConstantInt>(A0), C1 = isa<ConstantInt>(A1);
if (C0 && C1)
continue; // both const: nothing to learn
FunctionCallee Fn = CmpFn[Idx];
if (C0 || C1) {
Fn = ConstFn[Idx];
if (C1)
std::swap(A0, A1); // const goes first, matching SanCov
}
IRBuilder<> IRB(CMP);
Type *Ty = IntTys[Idx];
IRB.CreateCall(Fn, {IRB.CreateIntCast(A0, Ty, /*isSigned=*/true),
IRB.CreateIntCast(A1, Ty, /*isSigned=*/true)});
Changed = true;
}
}
return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all();
}
};

} // namespace

extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo
llvmGetPassPluginInfo() {
return {LLVM_PLUGIN_API_VERSION, "EmitCmpTrace", "0.1",
[](PassBuilder &PB) {
PB.registerOptimizerLastEPCallback(
[](ModulePassManager &MPM, OptimizationLevel,
ThinOrFullLTOPhase) { MPM.addPass(EmitCmpTrace()); });
}};
}
94 changes: 94 additions & 0 deletions LLVMPasses/TagCompilerGenerated.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Out-of-tree LLVM pass plugin: tag compiler-generated Swift functions with
// `nosanitize_coverage` BEFORE SanitizerCoverage runs, so SanCov emits no edge
// guards / pc-table entries (and no cmp callbacks) for them. This replaces the
// RUNTIME edge filter (SanCovHooks.c: g_edge_state / sancov_apply_edge_filter /
// sancov_is_compiler_generated) with a compile-time decision.
//
// The name patterns are ported verbatim from sancov_is_compiler_generated.
// MUST run at OptimizerLast (after coroutine splitting, so async funclet names
// like ...TQ3_ exist) and before SanitizerCoverage (plugin EP callbacks are
// registered ahead of Swift's, so this pass runs first at OptimizerLast).

#include "llvm/ADT/StringRef.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/PassManager.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Support/Compiler.h"

using namespace llvm;

namespace {

// Verbatim port of SanCovHooks.c `sancov_is_compiler_generated`, operating on
// the function's mangled name. Async continuation edges (T[QY]<n>_) are filtered
// for pathTrie determinism, not just noise — keep parity exact.
static bool isCompilerGenerated(StringRef N) {
if (N.starts_with("__swift_"))
return true;
if (N.starts_with("_swift_"))
return true;
size_t len = N.size();
if (len < 3)
return false;

if (N.ends_with("Wl") || N.ends_with("WL") || N.ends_with("Ma"))
return true;
// WO + specifier (all outlined operations: WOh/c/d/r/b/e/...)
if (N[len - 3] == 'W' && N[len - 2] == 'O')
return true;
if (N.ends_with("TA") || N.ends_with("TR") || N.ends_with("TK") ||
N.ends_with("Mr"))
return true;
if (N.contains("TATQ") || N.contains("TATY") || N.contains("TRTQ") ||
N.contains("TRTY"))
return true;
// global/static variable addressor
if (N.ends_with("vau"))
return true;

// bare async resume/yield: ...T[QY]<digits>_
if (len >= 4 && N[len - 1] == '_') {
size_t p = len - 2;
while (p > 0 && N[p] >= '0' && N[p] <= '9')
--p;
if (p >= 1 && (N[p] == 'Q' || N[p] == 'Y') && N[p - 1] == 'T')
return true;
}

// default argument generator: ...fA_ or ...fA<digit>_
if (N[len - 3] == 'f' && N[len - 2] == 'A' && N[len - 1] == '_')
return true;
if (len >= 4 && N[len - 4] == 'f' && N[len - 3] == 'A' && N[len - 1] == '_')
return true;

return false;
}

struct TagCompilerGenerated : PassInfoMixin<TagCompilerGenerated> {
PreservedAnalyses run(Module &M, ModuleAnalysisManager &) {
for (Function &F : M) {
if (F.isDeclaration())
continue;
if (F.hasFnAttribute(Attribute::NoSanitizeCoverage))
continue;
if (isCompilerGenerated(F.getName()))
F.addFnAttr(Attribute::NoSanitizeCoverage);
}
// Only function attributes change; no IR/CFG mutation.
return PreservedAnalyses::all();
}
};

} // namespace

extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo
llvmGetPassPluginInfo() {
return {LLVM_PLUGIN_API_VERSION, "TagCompilerGenerated", "0.1",
[](PassBuilder &PB) {
PB.registerOptimizerLastEPCallback(
[](ModulePassManager &MPM, OptimizationLevel,
ThinOrFullLTOPhase) { MPM.addPass(TagCompilerGenerated()); });
}};
}
73 changes: 43 additions & 30 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,35 @@
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
import Foundation

// Compile-time coverage instrumentation is provided by two out-of-tree LLVM
// pass plugins (sources in LLVMPasses/, built by scripts/build-llvm-plugins.sh
// into .build/llvm-plugins). They replace the former runtime filters that used
// to live in SanCovHooks.c:
// TagCompilerGenerated — tags compiler-generated functions NoSanitizeCoverage
// so SanCov emits no edge/cmp guards for them (compile-
// time edge filter; async resume/yield edges stay out,
// preserving pathTrie determinism). MUST load first so
// EmitCmpTrace also skips those functions.
// EmitCmpTrace — emits __sanitizer_cov_trace_cmp* ourselves for the
// comparisons we want, dropping trap-guard cmps
// (bounds/overflow/precondition). Used INSTEAD of
// `-sanitize-coverage=…,trace-cmp`.
// build-local-toolchain.sh builds the plugins before compiling; for a raw
// `swift build` run scripts/build-llvm-plugins.sh first.
let pluginDir = URL(fileURLWithPath: #filePath)
.deletingLastPathComponent()
.appendingPathComponent(".build/llvm-plugins")
func loadPass(_ name: String) -> [String] {
["-Xfrontend", "-load-pass-plugin=\(pluginDir.appendingPathComponent(name + ".dylib").path)"]
}

// Edge coverage with the compile-time compiler-generated filter.
let edgeCoverage: [String] =
["-sanitize=undefined", "-sanitize-coverage=edge,pc-table"] + loadPass("TagCompilerGenerated")
// Edge + comparison coverage (the cmp channel via EmitCmpTrace, not stock trace-cmp).
let edgeCmpCoverage: [String] = edgeCoverage + loadPass("EmitCmpTrace")

let package = Package(
name: "PropertyTestingKit",
Expand Down Expand Up @@ -132,10 +161,10 @@ let package = Package(
],
exclude: ["Corpus", "Fuzzing/Corpus"],
swiftSettings: [
.unsafeFlags([
"-sanitize=undefined",
"-sanitize-coverage=edge,pc-table"
])
// edge + comparison coverage; the cmp channel (via EmitCmpTrace)
// lets the input-to-state integration tests exercise the real cmp
// hooks (FuzzInputToStateTests fuzzes a magic-value SUT in-target).
.unsafeFlags(edgeCmpCoverage)
]
),
.testTarget(
Expand All @@ -148,10 +177,7 @@ let package = Package(
.product(name: "Clocks", package: "swift-clocks"),
],
swiftSettings: [
.unsafeFlags([
"-sanitize=undefined",
"-sanitize-coverage=edge,pc-table"
])
.unsafeFlags(edgeCoverage)
]
),
.testTarget(
Expand All @@ -163,10 +189,7 @@ let package = Package(
],
exclude: ["Corpus"],
swiftSettings: [
.unsafeFlags([
"-sanitize=undefined",
"-sanitize-coverage=edge,pc-table"
])
.unsafeFlags(edgeCoverage)
]
),
.testTarget(
Expand All @@ -177,10 +200,7 @@ let package = Package(
],
swiftSettings: [
// Enable sanitizer coverage for thread-local coverage testing
.unsafeFlags([
"-sanitize=undefined",
"-sanitize-coverage=edge,pc-table"
])
.unsafeFlags(edgeCoverage)
]
),
// TSanTests: Race condition tests that exercise concurrent code paths.
Expand All @@ -205,10 +225,7 @@ let package = Package(
],
swiftSettings: [
.swiftLanguageMode(.v5),
.unsafeFlags([
"-sanitize=undefined",
"-sanitize-coverage=edge,pc-table"
])
.unsafeFlags(edgeCoverage)
]
),
.testTarget(
Expand Down Expand Up @@ -238,11 +255,7 @@ package.targets += [
swiftSettings: [
// Enable sanitizer coverage so we have realistic counter counts
// Note: sanitize-coverage requires a sanitizer to be enabled
.unsafeFlags([
"-O",
"-sanitize=undefined",
"-sanitize-coverage=edge,pc-table"
])
.unsafeFlags(["-O"] + edgeCoverage)
],
linkerSettings: [
// Add rpath for Testing.framework from Xcode (needed for local toolchain)
Expand All @@ -263,11 +276,11 @@ package.targets += [
],
path: "Benchmarks/ProfiledBenchmark",
swiftSettings: [
.unsafeFlags([
"-O",
"-sanitize=undefined",
"-sanitize-coverage=edge,pc-table"
])
// edge + comparison coverage (cmp channel via EmitCmpTrace) so the
// benchmark closure's integer comparisons dispatch through
// sancov_dispatch_cmp → the boundary observer, exercising the
// per-comparison hot path under profiling.
.unsafeFlags(["-O"] + edgeCmpCoverage)
],
linkerSettings: [
// Add rpath for Testing.framework from Xcode (needed for local toolchain)
Expand Down
Loading