Analysis Pipeline

Precedes is a local command-line static performance analyzer. It analyzes source code locally and never executes user binaries. No code is uploaded.

Source Code → Compilation Database → Clang AST / CFG → Semantic Analysis → Diagnostics → Validation → Measurement

Implementation Status

Source → Diagnostics Implemented — the core static analysis path
Validation Implemented — functional checks, sanitizer gates, patch replay
Measurement Experimental — opt-in trusted native A/B via precedes optimize

Version 0.3.2 candidate. Toolchain: Clang/LLVM 21.1, GCC 15.2, clang-tidy. Pinned compiler versions.

Clang AST-Based Recognition

The analyzer operates over full Clang JSON AST dumps. Over 130 Python modules implement 49 rule files, each producing diagnostics in the PCPP-* namespace.

What the AST layer recognizes

  • Loop constructs: ForStmt, CXXForRangeStmt, WhileStmt, DoStmt
  • Function declarations: FunctionDecl, CXXMethodDecl, CXXConstructorDecl, CXXDestructorDecl
  • Implicit casts, parenthesized expressions, cleanup objects, materialized temporaries
  • Allocation patterns, vector reserve opportunities, STL antipatterns

Example: vector reserve detection

// Input
void fill(std::vector<int>& v, int n) {
    for (int i = 0; i < n; ++i)
        v.push_back(i);
}

// Precedes reports: PCPP-RESERVE-001
// "push_back in counted loop without prior reserve"

The diagnostic identifies a counted loop calling push_back on a vector that has no preceding reserve call with a matching bound. This is a static observation, not an automatic rewrite.

CFG and Control-Flow Reasoning

The semantic_flow module implements bounded abstract interpretation over Clang 21 CFGs. A native C++ LibTooling helper (semantic_flow.inc) extracts intraprocedural control-flow graphs; the Python layer runs reaching-value analysis and loop-region invariance proofs.

FunctionFlow — bounded abstract interpretation

Reaching values Forward dataflow over CFG basic blocks with merge at join points
Loop invariance Proves that a variable's value does not change across a loop region
Addressable tracking Variables whose address is taken are marked conservatively

Analysis budgets

ParameterLimitPurpose
MAX_STEPS20,000Abstract-interpretation steps before termination
MAX_CELLS250,000Tracked abstract-value cells
MAX_REGIONS32Concurrent loop-region analyses
MAX_DEPTH12Nesting depth for region invariance

Budget exhaustion is an explicit terminal state — it never silently proves invariance.

Source Call Graph and Function Indexing

The source_call_graph module builds a bounded, source-visible resolved call graph shared by effect-propagation and recursion rules.

Indexed declarations

  • FunctionDecl — free functions
  • CXXMethodDecl — member functions
  • CXXConversionDecl — conversion operators
  • CXXConstructorDecl — constructors, indexed by type and signature
  • CXXDestructorDecl — destructors

Key mechanisms

Canonical resolution Follows previousDecl chains to join in-class and out-of-line definitions
Variable bindings Tracks VarDecl bindings for indirect-call resolution
Lambda handling Lambda expressions are separate graph vertices with capture semantics
Virtual dispatch Methods in classes with bases are tracked as potentially virtual
Budget 200,000 AST node hard limit; returns None on overflow

Interprocedural Effect Propagation

The call_effects module computes may-effects of source-visible calls. "Unknown" is tracked separately from known effects — an unknown call never implies a pure body.

Effects model

Direct effects Syntactic evaluated accesses on global objects within a function body
Transitive effects Propagated through the call graph, including SCC (strongly connected component) resolution
Witnesses Source locations that prove an observed access
Unknown set Explicitly tracked: volatile/atomic, inline assembly, lifetime/implicit calls, indirect memory, unresolved calls, virtual dispatch
Conditions Conditional execution paths that gate an effect

Computation

A reverse worklist algorithm computes a finite may-effect fixed point. The analysis is bounded by a 4,096 global-object budget; exceeding it adds an explicit "global-object budget exhausted" unknown entry rather than silently dropping objects.

Access modes tracked per global object: read, write, and address-taken.

Source-Provable Indirect-Call Resolution

The call graph resolves indirect calls only when the source provably determines the target. This is a conservative, bounded analysis — no speculative devirtualization.

Resolution paths

  • DeclRefExpr — direct reference to a named function declaration
  • Const pointer/reference bindings — traced from VarDecl initializers
  • Member expressions — resolved to the referenced member declaration
  • Function-to-pointer decay and LValue-to-RValue conversions
  • Dereference operators on resolved pointers
  • Bounded recursion through stable (const-qualified, non-addressable) bindings

Virtual dispatch through base-class pointers is explicitly tracked as uncertainty, not resolved optimistically. Unresolved calls propagate unknown effects through the entire transitive call chain.

C++ Object Lifetime Semantics

A native Clang LibTooling helper (call_associations.inc) extracts source identities and reachable automatic destruction from the existing CFG. The Python layer (proven_call_associations.py) validates these source facts for use by effect and rule analyses.

Tracked lifetime categories

CategoryDescription
dispatchResolved virtual/non-virtual call dispatch
open_dispatchUnresolved virtual dispatch (explicit uncertainty)
destructorAutomatic variable destruction with in-loop tracking
structuralStructural destruction with operation/block/ordinal
temporaryTemporary object destruction
construction_failureC++ construction failure cleanup paths
allocationAllocator identity tracking (function/method target)
deallocationDeallocator identity tracking
failure_cleanupSubobject, array element, base, virtual base, delegated cleanup
handler_regionException handler regions with owner/site/end
destruction_unknownUnavailable cleanup: structural, temporary, reference, array, automatic, delete

Implicit allocation and deallocation calls (ImplicitAllocationCall, ImplicitDeallocationCall, ImplicitFailureDeallocationCall) are also tracked in the call graph. Failure-cleanup paths distinguish deallocation, array_element, member, base, virtual_base, and delegated_object conditions.

Loop Analysis

49 rule implementations cover a broad range of loop optimization opportunities. These are diagnostic rules — they identify optimization candidates, not apply transformations.

Rule categories

Interchange Loop interchange, matmul interchange, tripcount interchange
Tiling Loop tiling for cache locality
Fission Loop fission, fission promotion
Scalar reductions 6 variants: parallel, SIMD, offload, promotion, replace, candidates
Sparse reductions 3 variants: parallel, offload, candidates
Memory access Stride analysis, nonconsecutive access detection, pointer recurrence
Induction Direct loop induction proofs, bounded affine recurrences over CFG
Control flow Countable while loops, invariant condition hoisting, data-dependent conditions
Recursion Source-visible recursive call paths in loops, resolved by compiler identity

Induction variable analysis

Two specialized modules provide induction variable proofs:

  • Direct loop induction — zero-start, strict-bound proof for pure direct-loop models. Produces InductionProof with declaration, start, bound, mutation escape, and post-loop observability.
  • Affine recurrence — bounded affine counter proof over the CFG backedge structure. Extends FunctionFlow for recurrence detection.

Native CLI

The standalone executable needs no Python installation or web server. It embeds trusted native helpers built with Clang 21 LibTooling.

precedes doctor          # environment check
precedes check foo.cpp   # single-file analysis
precedes screening -p build/compile_commands.json  # project mode
precedes optimize --help # opt-in trusted optimization (experimental)

Commands

check Single-file analysis. Accepts one UTF-8 .cpp or .c file up to 256 KiB. Outputs PCPP-* diagnostics.
screening Project mode. Reads a local compilation database. Precedes never runs the project build or database compiler commands.
doctor Checks installed compiler versions and environment.
optimize Experimental Opt-in trusted optimization. Requires --allow-execution and --allow-model. Separate from static analysis.

Output

Use --format json for the versioned result envelope or --verbose for underlying evidence. Exit codes: 0 (no actionable findings), 1 (actionable findings), 2 (input/configuration errors), 3 (complete analyzer failure).

Known Limitations

Precedes is a bounded static analyzer, not a whole-program optimizing compiler.

Automatic rewrites 0 verified general rewrites in static mode. Diagnostics identify candidates; they do not apply transformations.
Binary execution Precedes never executes user binaries. It does not run build systems, compilers, or project test suites during static analysis.
Analysis scope Bounded intraprocedural. Not whole-program analysis. Call-graph effects are interprocedural but the CFG reasoning is per-function.
Virtual dispatch Tracked as explicit uncertainty. No speculative devirtualization.
Inline assembly Assembly and atomic effects are not summarized — they propagate unknown.
Indirect memory Indirect memory effects (through pointers not provably resolved) are not summarized.
File size Standard path: 256 KiB. Extended reserve-only path: up to 1 MiB. Regex and STRONG analysis unavailable above 256 KiB.