Find compiler interventions
September 4, 2026 ยท View on GitHub
ReproBit discovery is for a project's initial mismatch: the source builds, but
has not yet reproduced the reference bytes. It tries small, declaration-only
changes without changing the program's intended behavior. The normal workflow
is grind: let it make a bounded project pass, or select one source and
function when you need precise control. ReproBit saves a result only when you
explicitly approve a run whose candidate passes a fresh proof build. After the
project has reached an exact match, use rbit repair . for regressions caused
by later benign source edits instead of starting discovery again. This guide is
the source of truth for discovery workflow and approval behavior; the generated
option tables list exact flags and
defaults.
Automatic grind
Start from a configured ReproBit project whose normal build already runs.
Project-wide grind compares functions from project-owned reference .obj
files; it cannot derive those objects from the reference executable alone.
Obtain them from your project's archival or analysis inputs, place them under
reference/, then run a read-only project preview:
rbit discover grind .
Before compiling, grind reports how many eligible compiler steps have a paired reference object, how many are missing one, and how many functions fit in this bounded pass. A missing object skips that compiler step; it does not invalidate the rest of the preview. Full mapping and skip reasons remain in the report and NDJSON result.
ReproBit first matches an object to the source filename without its extension:
src/widget.cpp maps to reference/widget.obj. An exact translation-unit ID
also works. When names collide, provide the pair directly with
--reference-object TU=PATH; repeat the option for more translation units. A
single eligible source file and single reference object are paired
automatically.
The bounded search tries at most eight functions by default and samples one
from each source file before returning to the first. --max-symbols can raise
that limit to 64. Automatic directory scanning inspects at most 4,096 entries
and accepts at most 64 reference .obj files. Each object may index at most
4,096 functions. Exceeding a bound stops with a clear error instead of silently
dropping input.
The summary is written to
.reprobit-state/reports/grind/project/report.html. It links every attempted
function to a detailed decision report and keeps the exact bounded plan used for
that decision beside the report. Grind is a low-hanging-fruit pass, not a
promise to solve every compiler mismatch.
The report can offer either of two safe next steps:
- If one adjustment makes every target match, save only that exact result with
--accept-exact. - If several independent mismatches remain, save the locally proven functions
with
--accept-progress. That pass may itself reach an exact project; if it does not, run the ordinary preview again.
"Locally proven" has a narrow meaning: the freshly compiled function matches its project-owned reference object, its logic checks pass in a build from scratch, and it introduces no new authenticity exception. ReproBit saves these adjustments one at a time, checks the current project before every atomic update, and tests later functions against the newly saved state. It does not claim that the executable is closer overall or that the project is certified. Only a final fresh, byte-exact build can make that claim. Within each function, progress mode tries candidates cheapest-first and moves on as soon as one passes the local proof from scratch. Preview and exact-only approval keep searching the bounded set for a complete project match.
Use the copyable command shown in the report. A typical multi-mismatch loop is:
rbit discover grind . --accept-progress
# If the printed next step says the project still differs:
rbit discover grind .
# When the report offers the exact path instead:
rbit discover grind . --accept-exact
git diff -- reprobit/interventions reprobit/proofs
rbit verify .
Both save commands rerun the proof; they never trust an earlier preview. A
preview exits 0 when it finds an exact or locally proven adjustment and 1
when it finds none. A save command exits 0 only when it actually publishes the
requested kind of result. Invalid input or a runtime failure exits 2.
For one deliberately selected function, create the small expert plan once:
rbit discover init . \
--source src/widget.cpp \
--symbol '?Transform@Widget@@QAEHH@Z' \
--reference reference/widget.obj
ReproBit finds the matching saved compiler step and writes the compact
reprobit/discovery.json plan. It does not compile anything or change
project files. The default plan tries four declaration states and is
easy to widen deliberately:
{
"schema_version": 1,
"reference_object": "reference/widget.obj",
"target": "widget",
"translation_unit": "tu.widget",
"symbol": "?Transform@Widget@@QAEHH@Z",
"classes": {"start": 1, "stop": 4},
"functions": {"start": 10, "stop": 10}
}
Run that plan explicitly. An ordinary grind remains project-wide:
rbit discover grind . --expert-plan reprobit/discovery.json
Each candidate is compiled through the project's locked compiler graph. A
candidate can be saved as local progress only after a separate build from
scratch proves the function and required logic checks. It is exact only when
that same build also reproduces every target byte for byte.
Every completed bounded search writes a human summary to
.reprobit-state/reports/grind/report.html, including searches with no safe
solution. A chosen result links to its separate fresh-build evidence in the same
directory. The preview does not change project files.
When the result looks right, copy the exact or progress approval command from the report. For example:
rbit discover grind . \
--expert-plan reprobit/discovery.json \
--accept-progress
git diff -- reprobit/interventions reprobit/proofs
Advance approval is not proof and does not reuse an old preview verdict. ReproBit recompiles and verifies the solution from scratch, then changes only the owning intervention and proof shards in one compare-and-swap transaction. A concurrent source, plan, reference binary, toolchain, graph, or project-record edit aborts the save. If no result meets the selected approval mode, no project files change. Writing review reports is separate from saving the accepted intervention and proof records. If the local report cannot be written after those records were saved, the CLI emits a nonfatal warning while still reporting the project changes accurately.
Try the complete small project in the grind example. It intentionally starts one byte away, finds two low-cost records, and verifies the result again from scratch.
Advanced: raw request campaigns
rbit discover run is the lower-level inspection tool. It compiles a finite set
of declared states, indexes every emitted function, and reports whole-function,
private-donor, and bounded same-symbol mosaic proposals. These proposals are
review evidence only: this advanced command never edits source or project
records. Use it when you need to study candidates beyond the automatic grind's
small supported recipe.
Run a campaign
Start with the guided declaration-discovery example.
Its request, source, reference objects, and optional seed objects are resolved
relative to the request file. The committed
request schema
describes the structural fields and local bounds. The CLI additionally requires
canonical, case-insensitively sorted symbols, references, seeds, and placements;
checks relationships between ranges and max_cells; and keeps inputs, the report,
and private state from aliasing one another. Those cross-field rules are stated in
the schema description but cannot all be expressed by portable JSON Schema keywords.
rbit discover run discovery-request.json --jobs 4
This uses the compiler location remembered by rbit setup or
rbit toolchain provision. Pass --toolchain-root only to override it for one
run.
The request fixes the symbols, compiler arguments, search ranges, and maximum
cell count before any compiler starts. Source is staged under the fixed name
unit.cpp; consequently, __FILE__ observes that name. Local relative headers
are not copied into the cell, so source must be self-contained apart from headers
provided by the locked toolchain. /Gy is normally needed so candidate functions
live in isolated COMDAT sections.
Compiler arguments use a finite, path-free allowlist: the documented MSVC 4.x
CPU, calling-convention, runtime, optimization, warning, exception/RTTI, string
pooling, language-conformance, and debug-format switches. The CLI rejects source
inputs, response files, macros, include paths, output/listing/PCH paths, and every
unrecognized switch. ReproBit supplies /c, /Fo, /Fd, /FI when needed, and
the fixed unit.cpp input itself.
The four supported search families are declaration shapes, padding shapes,
forward-declaration runs, and paired extern runs. They generate declarations
only; arbitrary source rewriting is intentionally outside this command. Mosaic
analysis also has a fixed max_search_steps budget and fails closed if the
requested candidates cannot be considered within it.
Every emitted function remains indexed so collateral compiler effects are
reviewable. To keep that promise bounded, one object may contain at most 4,096
functions and the request's max_observed_functions caps the campaign total at
100,000; crossing either limit stops the campaign instead of silently dropping
functions.
Without --jobs the worker count is the number of CPUs the process may use,
capped at 8. An explicit --jobs runs independent cells in parallel; Wine is
capped at four workers either way. On POSIX hosts ReproBit clears the
campaign's exclusively locked private Wine prefix before compiling, then stops
and reaps its wineserver on success or failure. Use --wineserver when it is not
on PATH, and --cleanup-timeout to change the bounded shutdown limit. Terminal
output shows elapsed progress, while rbit --format ndjson discover ... emits
stable cache-hit and cache-miss events for CI and other tools.
Resume and review
By default, reusable state lives in .reprobit-discovery beside the request.
Each completed compiler cell is immutable and keyed by its exact compiler,
generated declarations, and compiler-visible working directory. Repeating an
unchanged request restores those cells. Extending one range compiles only the
new states; changing a reference or seed reruns analysis without recompiling
unchanged cells.
The report defaults to REQUEST_STEM.report.json. It includes:
- readable input and compiler receipts plus compile and analysis hashes;
- observations for every emitted function;
- schema-validated intervention proposals;
- the exact state and generated declarations behind each selected cell; and
- content-addressed paths for only the objects referenced by proposals.
Candidate objects remain resolvable under the discovery state directory after a successful run. Failed workspaces remain available for diagnosis. The report and artifacts are non-certifying and are safe to discard when review is done. The committed report schema can be used by review tools without importing ReproBit.
Discovery state directories are private working state. Every state level must be a real directory rather than a symbolic link or reparse point; workspace cleanup removes only the fixed, flat set of compiler files and never follows a redirected directory tree.
Raw campaigns deliberately keep this state separate from a project's
.reprobit-state. Preview its size before removal, then use the guarded cleanup
command:
rbit discover clean discovery-request.json --preview
rbit discover clean discovery-request.json
The command keeps the JSON and HTML reports. It removes only a state tree marked
by ReproBit for that request, refuses active campaigns, and
never follows symbolic links, junctions, or other redirected entries. If the
campaign used --state-directory DIR, pass the same option to discover clean.
The request file may be removed after a campaign; the guarded ownership marker
is enough for cleanup when the same request path is supplied.
When several request files deliberately reuse one state directory, cleanup
refuses to remove their shared cache unless you add --all-requests; use
--preview --all-requests first to review the combined size.
The tiny external lock marker may remain beside the request; reusable campaign
objects and compiler workspaces do not.