Statistics & reports
The ziskemu cost report, emitted either as a full text report (--stats) or a compact, CI-friendly SDK report (--sdk). Explains the summary's cost-distribution categories, the per-opcode breakdown, and the SDK report's optional sections.
Profiling is ziskemu's main analysis feature. After a run it can emit a
cost report, either as a full text report (--stats) or as a compact
SDK report (--sdk). Adding symbols (--read-symbols) unlocks the
function-level views in Function analysis.
Profiling works on any ELF that carries symbols, including optimized
release builds — no instrumentation is required.
The cost report (--stats)
-X / --stats prints overall execution statistics plus a per-opcode
breakdown.
ziskemu -e program.elf -i input.bin -X
The report begins with a summary:
REPORT
----------------------------------------
STEPS 92,875,129
COST DISTRIBUTION COST %
------------------------------------------------
BASE 293,601,280 2.57%
MAIN 6,315,508,772 55.22%
OPCODES 1,334,639,984 11.67%
PRECOMPILES 2,565,960,716 22.43%
MEMORY 927,932,629 8.11%
TOTAL 11,437,643,381 100.00%
FROPS 963,440,253 8.42%
RAM USAGE 18,465,008 3.47%
| Section | Meaning |
|---|---|
STEPS | Processor cycles / instructions executed — a proxy for execution length. |
BASE | Fixed overhead (tables, range checks) independent of program logic. |
MAIN | Processor cost without per-operation cost. Proportional to STEPS. |
OPCODES | Simple arithmetic/logic operations on 64-bit values. |
PRECOMPILES | Complex operations whose operands exceed 64 bits (256-bit math, EC ops, Keccak, DMA). |
MEMORY | Direct memory reads/writes plus unaligned-access state machines. |
TOTAL | Sum of all categories; each row shows its share of the total. |
FROPS | Frequently-used operations that are pre-calculated; the figure is the cost saved by that optimization. |
RAM USAGE | Memory used (reported only with the default bump allocator). |
Below the summary, COST BY OPCODE and FROPS BY OPCODE tables list
per-operation COUNT, COST, and percentage, with the four most expensive
operations marked #1–#4. Rows are ordered by cost, highest first.
Optional sections
The cost report shows more when asked. Each of these adds a section and
requires -X:
| Flag | Default | Adds |
|---|---|---|
--mem-stats | false | MEM COST BY TYPE — memory cost split by access type. |
--mem-full-stats | false | DETAILED MEM COST — the fully itemized memory breakdown. |
--opcode-breakdown | false | Per-opcode breakdown of the cheaper-to-prove variants under each opcode, e.g. add split into add_hi0 / add_hif. |
--pattern-analysis | false | OPERAND PATTERN ANALYSIS — per base opcode, the operand categories covering a significant share of its operations. |
--log-costly-unaligned | false | A log line per costly unaligned memory access (a 4-byte or 8-byte access crossing a word boundary), with pc, function, address, offset, and value. |
--pattern-analysis only reports a category when it covers more than
10% of that opcode's operations and at least 4,000 operations
in absolute terms, so rare patterns do not crowd the section.
Ordering and FROPS tables
| Flag | Default | Description |
|---|---|---|
--sort-by-units | false | Sort the opcode / precompile / FROPS sections by operation count instead of by cost. Requires -X. |
--legacy-frops | false | Compute FROPS coverage against the legacy FROPS tables (the snapshot taken before the FROPS overhaul), so a new FROPS version can be measured against the previous one. |
Precompile duplicates
--duplicates looks for precompile calls that repeat the same input
content — same input, same output — and reports how much cost went into
those repeats. That figure is the saving a deduplication cache would
buy. Every precompile except DMA is covered.
ziskemu -e program.elf -i input.bin -X --duplicates
PRECOMPILE TOTAL UNIQUE % DUP % MAX DUP DUP COST %
-----------------------------------------------------------------------------------------------------------------
keccak 124,500 31,120 25.00% 93,380 75.00% 4,096 1,893,440,000 16.55%
UNIQUE counts distinct inputs, DUP the calls that repeated one,
MAX DUP the most repeated single input, and DUP COST the cost
attributable to the repeats. Rows are ordered by DUP COST, and a
TOTAL row is added when more than one precompile appears.
| Flag | Default | Description |
|---|---|---|
--duplicates | false | Run the duplicate analysis. Requires -X. |
--duplicates-ops <OPS> | — | Restrict the analysis to these precompiles, comma-separated by opcode name (e.g. keccak,sha256,arith256). All supported precompiles when omitted. |
--duplicates-detail | false | Add the per-precompile call-path detail: where the duplicates come from, most costly first. Requires -S. |
--duplicates-depth <N> | 4 | How many innermost call-stack frames (leaf plus callers) to record per call for the detail report. |
# Which call paths are re-hashing the same data?
ziskemu -e program.elf -i input.bin -X -S \
--duplicates --duplicates-ops keccak --duplicates-detail --duplicates-depth 6
Register step distance
A register that is not touched for a long stretch still has to hold its value across that whole gap, and the proof has to carry it. These flags measure those gaps.
--reg-step-distance prints a REGISTER STEP DISTANCE table, one row
per accessed register:
ziskemu -e program.elf -i input.bin -X --reg-step-distance
| Column | Meaning |
|---|---|
ACCESSES | Number of times the register was accessed. |
MAX DIST | Largest gap, in steps, between two consecutive accesses. |
RATIO | MAX DIST as a fraction of the limit. |
MAX FDIST | The same maximum, assuming a periodic flush touches every register. |
FRATIO | MAX FDIST as a fraction of the limit. |
>=80% / >=LIMIT / >=2x | How many gaps reached 80%, 100%, and 200% of the limit. |
| Flag | Default | Description |
|---|---|---|
--reg-step-distance | false | Emit the table above. Requires -X. |
--reg-step-limit-bits <BITS> | 22 | The distance limit is 2^BITS steps — 4194304 by default. Sets the reference for RATIO, FRATIO, and the three gap counters. |
--reg-step-flush-bits <BITS> | 22 | Flush period: every 2^BITS steps every register is assumed to be forcibly accessed. Those forced accesses are not counted in ACCESSES; they only feed MAX FDIST / FRATIO. |
--reg-step-check is the fast version. It splits the execution into
instances of 2^--reg-step-flush-bits steps, each starting with a flush
that accesses every register, and reports how many instances contain at
least one gap over the limit. It runs on the fast emulation path and does
not need -X, so a whole program can be checked quickly:
ziskemu -e program.elf -i input.bin --reg-step-check
REGISTER STEP CHECK: OK, no instance over the 4194304 steps limit (limit=4194304 instance=2^22=4194304 max_dist=1839221)
When the check fails, the verdict becomes EXCEEDED in <n> of <total> instances and a second line names the registers involved with their
worst distance.
Snapshots and comparison
A snapshot is an aggregate view of a run — cost distribution, RAM/ROM usage, memory tables, base opcodes, precompiles, and FROPS — written as a CSV. There is no per-function detail in it, so snapshots stay small and are meant to be committed, kept in CI artifacts, and diffed.
| Flag | Default | Description |
|---|---|---|
--save-stats <FILE> | — | Write this run's snapshot to FILE. Requires -X. |
--ref-stats <FILE> | — | Load a snapshot saved earlier and print a reference-vs-current comparison. Requires -X. |
--diff-stats <OLD> <NEW> | — | Compare two saved snapshots and print the comparison without running the emulator — no ELF or input needed. OLD is the reference, so deltas are NEW - OLD. |
# Record a baseline
ziskemu -e program.elf -i input.bin -X --save-stats baseline.csv
# Measure a change against it
ziskemu -e program.elf -i input.bin -X --ref-stats baseline.csv
# Or compare two recorded runs later, with no run at all
ziskemu --diff-stats baseline.csv candidate.csv
The comparison covers the cost distribution, base opcodes, precompiles, and FROPS.
Comparison output
| Flag | Default | Description |
|---|---|---|
--diff-format <FORMAT> | color | color for the human-readable, colour-coded view; csv for the plain separator-delimited view, for scripting. |
--color <WHEN> | auto | auto colours only when stdout is a terminal; always and never force the choice. |
--legacy-display | false | Equivalent to --diff-format csv. Also implied by --sdk. |
--csv-separator <SEP> | , | Field separator for --save-stats and the csv comparison view. A single character. |
Snapshots are read back with the separator auto-detected from the file,
so a snapshot saved with a non-default --csv-separator still compares
correctly without repeating the flag.
HTML reports
--html-report renders the statistics as a standalone HTML page instead
of a CSV. It collects the statistics itself, so -X is not required —
though the run still prints the text report to stdout.
# Single run -> report.html
ziskemu -e program.elf -i input.bin --html-report
# Explicit path
ziskemu -e program.elf -i input.bin --html-report cost.html
# Comparison against a baseline
ziskemu -e program.elf -i input.bin --ref-stats baseline.csv --html-report diff.html
# Comparison of two snapshots, without running
ziskemu --diff-stats baseline.csv candidate.csv --html-report diff.html
| Flag | Default | Description |
|---|---|---|
--html-report [FILE] | report.html | Render the page to FILE. The value is optional; passing the flag alone writes report.html in the current directory. |
The snapshot content is passed straight to the renderer, so no
intermediate CSV file is written unless --save-stats also asked for
one. When a reference is supplied — with --ref-stats, or with
--diff-stats, which needs no run — the comparison report is rendered
instead of the single-run one.
The page is self-contained (inline CSS, inline logo, no external requests) and organized into collapsible sections: cost distribution, cost by base opcode, cost by precompiled opcode, FROPS by opcode, the memory tables, and memory offsets. Opcode tables can be re-sorted by count or by cost in the browser.
The report binary
The ZisK source tree also builds a small report binary that renders
snapshot CSVs that already exist:
# From a ZisK checkout
cargo run --bin report -- stats.csv # single-run report
cargo run --bin report -- baseline.csv new.csv # comparison (first file is the reference)
report always writes to emulator/src/report/report.html inside the
ZisK source tree — the destination is baked in at compile time and
ignores the directory you run it from. It is also not part of a ZisK
release: ziskup installs cargo-zisk, cargo-zisk-dev, and
ziskemu only. Use ziskemu --diff-stats <OLD> <NEW> --html-report <FILE> instead unless you are working inside the repository.
SDK report mode (--sdk)
--sdk produces a compact, boxed summary ideal for CI/CD and quick checks.
By default it shows only the summary; request extra sections explicitly.
ziskemu -e program.elf -i input.bin --sdk
| Flag | Default | Description |
|---|---|---|
--sdk | false | Emit the compact SDK report (summary only unless a section flag is added). |
--opcodes | false | Add the opcode-distribution section. |
--top-functions | false | Add the top-cost-functions section (requires --read-symbols). |
--profile-tags | false | Add the profile-tags section (developer-inserted measurement markers). |
--sdk-width <WIDTH> | 120 | Width, in characters, of the SDK report. |
# Summary + opcodes + top functions
ziskemu -e program.elf -i input.bin --sdk --opcodes --top-functions -S
For per-function rankings, the PC histogram, call tracking, and the Firefox Profiler export, continue to Function analysis.