asmdiff 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. asmdiff-0.3.0/2026-07-10-reverb-session-feedback-handoff.md +102 -0
  2. {asmdiff-0.2.0 → asmdiff-0.3.0}/PKG-INFO +86 -5
  3. {asmdiff-0.2.0 → asmdiff-0.3.0}/README.md +85 -4
  4. {asmdiff-0.2.0 → asmdiff-0.3.0}/asmdiff.example.toml +6 -0
  5. {asmdiff-0.2.0 → asmdiff-0.3.0}/asmdiff.py +429 -54
  6. asmdiff-0.3.0/demo/delay_macro.c +414 -0
  7. asmdiff-0.3.0/demo/delay_orig.c +355 -0
  8. asmdiff-0.3.0/demo/delay_restrict.c +361 -0
  9. asmdiff-0.3.0/demo/delay_struct.c +415 -0
  10. asmdiff-0.3.0/demo/esp32-matrix.png +0 -0
  11. asmdiff-0.3.0/demo/quickstart.png +0 -0
  12. asmdiff-0.3.0/hackaday-tip-draft.md +101 -0
  13. asmdiff-0.3.0/hn-show-draft.md +57 -0
  14. {asmdiff-0.2.0 → asmdiff-0.3.0}/pyproject.toml +4 -1
  15. {asmdiff-0.2.0 → asmdiff-0.3.0}/test_asmdiff.py +490 -0
  16. {asmdiff-0.2.0 → asmdiff-0.3.0}/.claude/settings.local.json +0 -0
  17. {asmdiff-0.2.0 → asmdiff-0.3.0}/.gitignore +0 -0
  18. {asmdiff-0.2.0 → asmdiff-0.3.0}/2026-07-06-inspect-mode.md +0 -0
  19. {asmdiff-0.2.0 → asmdiff-0.3.0}/2026-07-07-codegen-hotspots-handoff.md:Zone.Identifier +0 -0
  20. {asmdiff-0.2.0 → asmdiff-0.3.0}/2026-07-07-elf-mode-handoff.md +0 -0
  21. {asmdiff-0.2.0 → asmdiff-0.3.0}/2026-07-07-table-layout-handoff.md +0 -0
  22. {asmdiff-0.2.0 → asmdiff-0.3.0}/CLAUDE.md +0 -0
  23. {asmdiff-0.2.0 → asmdiff-0.3.0}/FIELD-TEST-REPORT.md +0 -0
  24. {asmdiff-0.2.0 → asmdiff-0.3.0}/LICENSE +0 -0
  25. {asmdiff-0.2.0 → asmdiff-0.3.0}/Screenshot 2026-07-07 225145.jpg +0 -0
  26. {asmdiff-0.2.0 → asmdiff-0.3.0}/amy-hotspot-analysis.md +0 -0
  27. {asmdiff-0.2.0 → asmdiff-0.3.0}/asmdiff_example.c +0 -0
  28. {asmdiff-0.2.0 → asmdiff-0.3.0}/demo/esp32-matrix.gif +0 -0
  29. {asmdiff-0.2.0 → asmdiff-0.3.0}/demo/quickstart.gif +0 -0
  30. {asmdiff-0.2.0 → asmdiff-0.3.0}/dist_1/.gitignore +0 -0
  31. {asmdiff-0.2.0 → asmdiff-0.3.0}/dist_1/asmdiff-0.1.1-py3-none-any.whl +0 -0
  32. {asmdiff-0.2.0 → asmdiff-0.3.0}/dist_1/asmdiff-0.1.1.tar.gz +0 -0
  33. {asmdiff-0.2.0 → asmdiff-0.3.0}/dist_1/asmdiff-0.1.2-py3-none-any.whl +0 -0
  34. {asmdiff-0.2.0 → asmdiff-0.3.0}/dist_1/asmdiff-0.1.2.tar.gz +0 -0
  35. {asmdiff-0.2.0 → asmdiff-0.3.0}/dist_1/asmdiff-0.1.3-py3-none-any.whl +0 -0
  36. {asmdiff-0.2.0 → asmdiff-0.3.0}/dist_1/asmdiff-0.1.3.tar.gz +0 -0
  37. {asmdiff-0.2.0 → asmdiff-0.3.0}/dist_1/asmdiff-0.1.4-py3-none-any.whl +0 -0
  38. {asmdiff-0.2.0 → asmdiff-0.3.0}/dist_1/asmdiff-0.1.4.tar.gz +0 -0
  39. {asmdiff-0.2.0 → asmdiff-0.3.0}/docs/superpowers/plans/2026-07-08-asmdiff-demo-gifs.md +0 -0
  40. {asmdiff-0.2.0 → asmdiff-0.3.0}/docs/superpowers/specs/2026-07-07-edit-config-design.md +0 -0
  41. {asmdiff-0.2.0 → asmdiff-0.3.0}/docs/superpowers/specs/2026-07-08-asmdiff-demo-gifs-design.md +0 -0
  42. {asmdiff-0.2.0 → asmdiff-0.3.0}/docs/superpowers/specs/2026-07-08-asmdiff-demo-gifs-pipeline.mmd +0 -0
  43. {asmdiff-0.2.0 → asmdiff-0.3.0}/gif-plan.md +0 -0
  44. {asmdiff-0.2.0 → asmdiff-0.3.0}/how-could-i-create-velvety-hollerith.md:Zone.Identifier +0 -0
@@ -0,0 +1,102 @@
1
+ # Field feedback: S3-Amysynth reverb codegen session — handoff 2026-07-10
2
+
3
+ Feedback from a real investigation, not a scripted test matrix. Workload:
4
+ `stereo_reverb` in AMY's `delay.c` (ESP32-S3 project at `~/esp-idf/S3-Amysynth`,
5
+ xtensa-esp-elf-gcc 15.2.0, ship `-O2` flags via `-db`), comparing four source
6
+ variants: pre-optimization original (extracted from git history), the shipped
7
+ token-paste locals-cache, a struct/`static inline` respelling, and a
8
+ `restrict`-annotated original. Modes exercised: two-file `-a FUNC` compare,
9
+ `--target` matrix, `-db` + `--flags-like`, host-compiler contrast runs.
10
+
11
+ ## State
12
+
13
+ The tool answered every question the session asked, correctly. All findings
14
+ below are UX/ergonomics, not correctness: in each case asmdiff computed the
15
+ right thing and then made the caller dig for it. Ranked by time actually lost.
16
+
17
+ ## Findings
18
+
19
+ ### 1. Summary tables print last; no summaries-only mode
20
+ Two-file compare with the default target set produced a 102 KB dump (three full
21
+ side-by-side listings, esp32/s2/s3) with the `function/role/insns/loop
22
+ spans/calls` table at the *end* of each target section. Every subsequent
23
+ invocation was pre-piped through `grep -A4 "role.*insns"` to skip the listings.
24
+
25
+ Repro: `asmdiff delay_macro.c delay_struct.c -a stereo_reverb -db --flags-like
26
+ components/amy/src/delay.c` (no `--target`).
27
+
28
+ Proposal: `--summary-only` (or print summaries before listings). For
29
+ agent/scripted callers the table is the decision input; the listing is only
30
+ pulled when a delta needs explaining. A `--json` summary emitter would serve
31
+ the same callers.
32
+
33
+ ### 2. Doc drift: `host` target promised, absent from shipped config
34
+ The skill/README text says ESP profiles "+ host" are pre-configured;
35
+ `~/.config/asmdiff.toml` actually ships `gcc` and `clang`. `--target host`
36
+ errors out. The error's target list is what rescued the session — good — but
37
+ the drift costs one failed call per session that trusts the docs.
38
+
39
+ Fix: add a `[host]` alias target to the example/default toml, or correct the
40
+ README and the `~/.claude/skills/asmdiff` skill text (both say "host").
41
+
42
+ ### 3. `-db` on foreign-arch targets drops project include paths with the flags
43
+ For host targets, `-db` correctly refuses to borrow xtensa flags ("compiling
44
+ without its include/define flags") — but that also drops `-I`, so the compile
45
+ dies on `fatal error: amy.h: No such file or directory`. Workaround was manual:
46
+ `-- -I~/esp-idf/S3-Amysynth/components/amy/src`.
47
+
48
+ Proposal: the project's own `-I` paths are target-portable even when its `-D`s
49
+ are not. For foreign-arch targets, borrow `-I` only (skip `-D`), or offer
50
+ `--db-includes`. Keep the current loud note either way — the refusal itself is
51
+ right (see FIELD-TEST-REPORT Trap 1 for why silently borrowing would be worse).
52
+
53
+ ### 4. No per-span instruction mix (`--span-stats`)
54
+ The decisive number of the session — loop body 96 loads / 31 stores vs 85 / 26,
55
+ multiplies identical at 14 — required two rounds of hand-written awk over the
56
+ listing, and the first attempt silently counted past the loop end into the
57
+ epilogue. The tool's own doctrine is "weigh the span, not the function"; it
58
+ should be able to weigh it. The 2026-07-07 ELF round hit the same wall (ad-hoc
59
+ awk over objdump for loop tabulation), so this is a recurring gap.
60
+
61
+ Proposal: `--span-stats` — per-span opcode histogram (loads / stores / mul /
62
+ branch / other), per function, alongside the existing `span:count` column.
63
+
64
+ ### 5. Side-by-side pair listings don't collapse identical runs
65
+ Two ~500-insn functions differing by ~5 instructions print ~1000 listing lines
66
+ of which ~990 are identical pairs. Context-diff-style collapsing (keep N lines
67
+ around changed hunks, elide the rest with a count) would turn the 102 KB dump
68
+ from finding 1 into a few readable hunks. Interacts with finding 1: summary
69
+ first, collapsed hunks second, full listing on request.
70
+
71
+ ### 6. Minor: missing-toolchain error could name the compiler
72
+ `--target rp2350` with no ARM toolchain installed → "error: no usable compiler
73
+ in the matrix". Accurate, but naming the miss ("arm-none-eabi-gcc: not found
74
+ on PATH") makes the fix obvious.
75
+
76
+ ## Verified working this session (regression notes)
77
+
78
+ - `--flags-like <db-file>` on out-of-tree copies: exact borrowed-flag parity
79
+ both sides — this closes FIELD-TEST-REPORT Trap 2 ("before/after on a project
80
+ source has no supported path") and its Trap 1 hazard for the copy workflow.
81
+ - Two-file `-a FUNC` compare against a `git show <rev>:src/file.c` extraction:
82
+ frictionless; this is the natural "what did that upstream commit buy"
83
+ workflow.
84
+ - Default target resolution + multi-target matrix from config: zero toolchain
85
+ path digging; glob `cc` patterns resolved and announced.
86
+ - `span:count` + `calls` columns answered ZOL and libcall questions at a
87
+ glance.
88
+
89
+ Session results usable as a future test corpus (esp32s3, ship -O2, AMY at
90
+ v1.2.31): `stereo_reverb` original 437 insns / span 327; +`restrict` best
91
+ effort 473 / 306; shipped locals-cache 509 / 290; struct respelling 504 / 289.
92
+ Host: gcc -O3 467 = 467, clang -O3 443 = 443 (macro vs struct byte-count
93
+ identical). Variant sources preserved in the session job dir
94
+ (`~/.claude/jobs/b3de0cac/tmp/delay_{orig,restrict,macro,struct}.c`) — copy
95
+ them into `demo/` or the test suite if wanted before that dir is cleaned.
96
+
97
+ ## Next steps
98
+
99
+ 1. Finding 1 (`--summary-only`) — smallest change, largest per-call saving.
100
+ 2. Finding 4 (`--span-stats`) — recurring need across two sessions.
101
+ 3. Finding 5 (collapse identical hunks) — pairs with 1.
102
+ 4. Findings 2, 3, 6 — config/doc/message fixes, each ~minutes.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asmdiff
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Per-function assembly inspection and comparison, across a compiler matrix or from a shipped ELF
5
5
  Project-URL: Homepage, https://github.com/rt-rtos/asmdiff
6
6
  Project-URL: Repository, https://github.com/rt-rtos/asmdiff
@@ -358,9 +358,11 @@ zero-overhead loops", the empirical rules (verified on esp-gcc 15.2,
358
358
  ```
359
359
  asmdiff SOURCE.c [SOURCE2.c | FUNC...] [--pair OLD:NEW]... [--across FUNC]...
360
360
  [--cc 'CC FLAGS']... [--target NAME]... [--config PATH]
361
- [--compile-commands [PATH]] [--flags-like PATH]
361
+ [--compile-commands [PATH]] [--flags-like PATH] [--db-includes]
362
+ [--summary-only] [--collapse] [--span-stats]
362
363
  [--layout list|side-by-side] [-v] [-- EXTRA_FLAGS...]
363
364
  asmdiff FIRMWARE.elf [FUNC...] [--filter REGEX] [--objdump PATH]
365
+ [--summary-only] [--span-stats]
364
366
  ```
365
367
 
366
368
  | Option | Meaning |
@@ -376,6 +378,12 @@ asmdiff FIRMWARE.elf [FUNC...] [--filter REGEX] [--objdump PATH]
376
378
  | `--config PATH` | Config file to use. Default search: `asmdiff.toml` next to `SOURCE.c`, then in the current directory, then `~/.config/`. First hit wins. |
377
379
  | `-db, --compile-commands [PATH]` | Borrow each source's include/define flags from a `compile_commands.json`; with no `PATH`, walk up from the CWD checking each directory and its `build/` until the repository root. See [below](#borrowing-includes-from-compile_commandsjson). |
378
380
  | `--flags-like PATH` | A source with no `compile_commands` entry borrows the flags recorded for `PATH` — the way to compare a modified copy of a project source under its original's header environment. |
381
+ | `--db-includes` | Borrow only the header-search paths from the database, dropping its defines, forced includes, and `-specs`/`--sysroot`; kept paths are re-emitted as `-idirafter` so they cannot shadow the host's own system headers. This is how a host target compiles a cross project's source. |
382
+ | `-s`, `--summary-only` | Print only the summary/stats tables, suppressing every assembly listing (see [Shaping the output](#shaping-the-output-for-reading-vs-deciding)). |
383
+ | `--json` | Emit the summary as JSON on stdout instead of tables — one record per function per compiler. Implies `--summary-only`; errors stay plain text on stderr. |
384
+ | `--collapse` | In side-by-side listings, elide runs of identical line pairs, keeping 3 lines of context around each difference. |
385
+ | `--span-stats` | Follow each stats table with a per-loop-span instruction mix: load/store/mul/branch/other counts per span. |
386
+ | `--version` | Print the version and exit. |
379
387
  | `-v`, `--verbose` | On compile failure, print the full compiler command and complete error output. Default shows only the compiler, the source, and the first error lines. |
380
388
  | `-- FLAGS...` | Everything after a bare `--` is appended to *every* compiler invocation. |
381
389
 
@@ -399,9 +407,66 @@ asmdiff h.c -- -fno-math-errno
399
407
  ```
400
408
 
401
409
  Compilers missing from `PATH` are skipped with a warning; the run fails only
402
- if none are usable. Exit status is non-zero only for operational failures
403
- (compile error — the compiler's stderr is shown — unknown `--pair` name, no
404
- usable compiler). Differing assembly is the expected result, never an error.
410
+ if none are usable, and that error names each missed binary
411
+ (`no-such-gcc: not found on PATH`) so the fix is in the message, not in a
412
+ warning that scrolled away. Exit status is non-zero only for operational
413
+ failures (compile error — the compiler's stderr is shown — unknown `--pair`
414
+ name, no usable compiler). Differing assembly is the expected result, never
415
+ an error.
416
+
417
+ ## Shaping the output for reading vs deciding
418
+
419
+ A two-file compare over a three-target matrix prints three full
420
+ side-by-side listings with the summary table at the *end* of each — the
421
+ right shape for reading an unfamiliar delta, and roughly 100 KB of the
422
+ wrong shape when the table is all the decision needs. Three flags cut the
423
+ output to purpose:
424
+
425
+ - **`--summary-only`** (`-s`) keeps only the summary/stats tables. This is
426
+ the scripted/agent view: the table is the decision input, and a listing
427
+ is pulled with a second, narrower run only when a delta needs
428
+ explaining.
429
+ - **`--collapse`** elides runs of identical line pairs in every
430
+ side-by-side listing, keeping 3 lines of context around each difference
431
+ plus a `... N identical lines ...` marker. Two ~500-instruction
432
+ functions differing by five instructions render as a few readable hunks
433
+ instead of ~1000 lines.
434
+ - **`--span-stats`** follows each stats table with a per-loop-span
435
+ instruction mix, one row per span:
436
+
437
+ ```
438
+ function span insns load store mul branch other
439
+ stereo_reverb .L108 327 96 31 14 12 174
440
+ ```
441
+
442
+ This weighs the span rather than the whole function — the number that
443
+ actually decides a hot-loop comparison (is the rewrite trading loads
444
+ for stores? did the multiplies move?) without hand-counting a listing
445
+ and accidentally tallying past the loop end into the epilogue.
446
+ Buckets are by mnemonic (Xtensa, RISC-V, ARM) with an AT&T
447
+ memory-operand heuristic for x86; *branch* includes calls (outbound
448
+ calls are already itemised in the `calls` column), and anything the
449
+ tables don't know lands in *other*.
450
+
451
+ All three combine with every compile mode; `--summary-only` and
452
+ `--span-stats` also apply to ELF input.
453
+
454
+ For scripted callers, `--json` replaces the tables entirely with one
455
+ JSON document on stdout: a flat `results` list holding one record per
456
+ function per compiler — `cc`, `tag` (source label in two-file runs),
457
+ `role` (`baseline`/`candidate` in paired runs), `insns`, `loop_spans`,
458
+ `calls`, and `span_stats` when `--span-stats` is given. Flat records
459
+ keep it one `jq` expression away from any question the tables answer:
460
+
461
+ ```bash
462
+ asmdiff old.c new.c -a stereo_reverb --json --span-stats \
463
+ | jq '.results[] | {role, insns, spans: .loop_spans}'
464
+ ```
465
+
466
+ The top level carries `asmdiff` (version) and `mode`
467
+ (`pairs`/`across`/`inspect`/`summary`/`elf`; ELF runs add the binary's
468
+ path as `elf`). Warnings and errors stay plain text on stderr, so a
469
+ failed run never emits half a document.
405
470
 
406
471
  ## Config file: named targets
407
472
 
@@ -573,6 +638,17 @@ Details that make it robust:
573
638
  - **`compile_commands` expands `~` and `$VARS`.** The `==` header always
574
639
  prints the resolved compiler command; run with `-- -v` if you want to see
575
640
  every include path the compiler actually received.
641
+ - **`--db-includes` restricts the borrow to header-search paths**,
642
+ dropping `-D`/`-U`, forced includes, and `-specs`/`--sysroot`. Use it
643
+ when the *target* doesn't match the arch the database was built for —
644
+ host gcc given an ESP-IDF database would otherwise inherit
645
+ `-specs=picolibc.specs` and cross-only defines it cannot compile
646
+ with, yet still needs the project's include paths to find its
647
+ headers. The kept paths are re-emitted as `-idirafter`, searched
648
+ *after* the compiler's own system directories: ESP-IDF's include set
649
+ contains a libc-overlay directory (`esp_libc/platform_include`) whose
650
+ `stdio.h` would shadow the host's under plain `-I`; demoted, the
651
+ project's own headers still resolve and the host's libc wins.
576
652
 
577
653
  ### Auto-discovering the database
578
654
 
@@ -665,6 +741,11 @@ baseline (`../baseline/src/oscillators.c`), which is the same file under a
665
741
  path the database has never heard of. The absent-source error and the
666
742
  soft-miss note both point at this flag when a same-name entry exists.
667
743
 
744
+ To contrast the same pair on a host compiler (does gcc-on-x86 make the
745
+ same choice?), add `--db-includes`: the borrow then carries only the
746
+ project's include paths, which are target-portable, and none of the
747
+ cross-only defines or `-specs` a host gcc would choke on.
748
+
668
749
  ## Whole-file summary
669
750
 
670
751
  With no `--pair`, no `--across`, and no `old_*`/`new_*` functions to
@@ -340,9 +340,11 @@ zero-overhead loops", the empirical rules (verified on esp-gcc 15.2,
340
340
  ```
341
341
  asmdiff SOURCE.c [SOURCE2.c | FUNC...] [--pair OLD:NEW]... [--across FUNC]...
342
342
  [--cc 'CC FLAGS']... [--target NAME]... [--config PATH]
343
- [--compile-commands [PATH]] [--flags-like PATH]
343
+ [--compile-commands [PATH]] [--flags-like PATH] [--db-includes]
344
+ [--summary-only] [--collapse] [--span-stats]
344
345
  [--layout list|side-by-side] [-v] [-- EXTRA_FLAGS...]
345
346
  asmdiff FIRMWARE.elf [FUNC...] [--filter REGEX] [--objdump PATH]
347
+ [--summary-only] [--span-stats]
346
348
  ```
347
349
 
348
350
  | Option | Meaning |
@@ -358,6 +360,12 @@ asmdiff FIRMWARE.elf [FUNC...] [--filter REGEX] [--objdump PATH]
358
360
  | `--config PATH` | Config file to use. Default search: `asmdiff.toml` next to `SOURCE.c`, then in the current directory, then `~/.config/`. First hit wins. |
359
361
  | `-db, --compile-commands [PATH]` | Borrow each source's include/define flags from a `compile_commands.json`; with no `PATH`, walk up from the CWD checking each directory and its `build/` until the repository root. See [below](#borrowing-includes-from-compile_commandsjson). |
360
362
  | `--flags-like PATH` | A source with no `compile_commands` entry borrows the flags recorded for `PATH` — the way to compare a modified copy of a project source under its original's header environment. |
363
+ | `--db-includes` | Borrow only the header-search paths from the database, dropping its defines, forced includes, and `-specs`/`--sysroot`; kept paths are re-emitted as `-idirafter` so they cannot shadow the host's own system headers. This is how a host target compiles a cross project's source. |
364
+ | `-s`, `--summary-only` | Print only the summary/stats tables, suppressing every assembly listing (see [Shaping the output](#shaping-the-output-for-reading-vs-deciding)). |
365
+ | `--json` | Emit the summary as JSON on stdout instead of tables — one record per function per compiler. Implies `--summary-only`; errors stay plain text on stderr. |
366
+ | `--collapse` | In side-by-side listings, elide runs of identical line pairs, keeping 3 lines of context around each difference. |
367
+ | `--span-stats` | Follow each stats table with a per-loop-span instruction mix: load/store/mul/branch/other counts per span. |
368
+ | `--version` | Print the version and exit. |
361
369
  | `-v`, `--verbose` | On compile failure, print the full compiler command and complete error output. Default shows only the compiler, the source, and the first error lines. |
362
370
  | `-- FLAGS...` | Everything after a bare `--` is appended to *every* compiler invocation. |
363
371
 
@@ -381,9 +389,66 @@ asmdiff h.c -- -fno-math-errno
381
389
  ```
382
390
 
383
391
  Compilers missing from `PATH` are skipped with a warning; the run fails only
384
- if none are usable. Exit status is non-zero only for operational failures
385
- (compile error — the compiler's stderr is shown — unknown `--pair` name, no
386
- usable compiler). Differing assembly is the expected result, never an error.
392
+ if none are usable, and that error names each missed binary
393
+ (`no-such-gcc: not found on PATH`) so the fix is in the message, not in a
394
+ warning that scrolled away. Exit status is non-zero only for operational
395
+ failures (compile error — the compiler's stderr is shown — unknown `--pair`
396
+ name, no usable compiler). Differing assembly is the expected result, never
397
+ an error.
398
+
399
+ ## Shaping the output for reading vs deciding
400
+
401
+ A two-file compare over a three-target matrix prints three full
402
+ side-by-side listings with the summary table at the *end* of each — the
403
+ right shape for reading an unfamiliar delta, and roughly 100 KB of the
404
+ wrong shape when the table is all the decision needs. Three flags cut the
405
+ output to purpose:
406
+
407
+ - **`--summary-only`** (`-s`) keeps only the summary/stats tables. This is
408
+ the scripted/agent view: the table is the decision input, and a listing
409
+ is pulled with a second, narrower run only when a delta needs
410
+ explaining.
411
+ - **`--collapse`** elides runs of identical line pairs in every
412
+ side-by-side listing, keeping 3 lines of context around each difference
413
+ plus a `... N identical lines ...` marker. Two ~500-instruction
414
+ functions differing by five instructions render as a few readable hunks
415
+ instead of ~1000 lines.
416
+ - **`--span-stats`** follows each stats table with a per-loop-span
417
+ instruction mix, one row per span:
418
+
419
+ ```
420
+ function span insns load store mul branch other
421
+ stereo_reverb .L108 327 96 31 14 12 174
422
+ ```
423
+
424
+ This weighs the span rather than the whole function — the number that
425
+ actually decides a hot-loop comparison (is the rewrite trading loads
426
+ for stores? did the multiplies move?) without hand-counting a listing
427
+ and accidentally tallying past the loop end into the epilogue.
428
+ Buckets are by mnemonic (Xtensa, RISC-V, ARM) with an AT&T
429
+ memory-operand heuristic for x86; *branch* includes calls (outbound
430
+ calls are already itemised in the `calls` column), and anything the
431
+ tables don't know lands in *other*.
432
+
433
+ All three combine with every compile mode; `--summary-only` and
434
+ `--span-stats` also apply to ELF input.
435
+
436
+ For scripted callers, `--json` replaces the tables entirely with one
437
+ JSON document on stdout: a flat `results` list holding one record per
438
+ function per compiler — `cc`, `tag` (source label in two-file runs),
439
+ `role` (`baseline`/`candidate` in paired runs), `insns`, `loop_spans`,
440
+ `calls`, and `span_stats` when `--span-stats` is given. Flat records
441
+ keep it one `jq` expression away from any question the tables answer:
442
+
443
+ ```bash
444
+ asmdiff old.c new.c -a stereo_reverb --json --span-stats \
445
+ | jq '.results[] | {role, insns, spans: .loop_spans}'
446
+ ```
447
+
448
+ The top level carries `asmdiff` (version) and `mode`
449
+ (`pairs`/`across`/`inspect`/`summary`/`elf`; ELF runs add the binary's
450
+ path as `elf`). Warnings and errors stay plain text on stderr, so a
451
+ failed run never emits half a document.
387
452
 
388
453
  ## Config file: named targets
389
454
 
@@ -555,6 +620,17 @@ Details that make it robust:
555
620
  - **`compile_commands` expands `~` and `$VARS`.** The `==` header always
556
621
  prints the resolved compiler command; run with `-- -v` if you want to see
557
622
  every include path the compiler actually received.
623
+ - **`--db-includes` restricts the borrow to header-search paths**,
624
+ dropping `-D`/`-U`, forced includes, and `-specs`/`--sysroot`. Use it
625
+ when the *target* doesn't match the arch the database was built for —
626
+ host gcc given an ESP-IDF database would otherwise inherit
627
+ `-specs=picolibc.specs` and cross-only defines it cannot compile
628
+ with, yet still needs the project's include paths to find its
629
+ headers. The kept paths are re-emitted as `-idirafter`, searched
630
+ *after* the compiler's own system directories: ESP-IDF's include set
631
+ contains a libc-overlay directory (`esp_libc/platform_include`) whose
632
+ `stdio.h` would shadow the host's under plain `-I`; demoted, the
633
+ project's own headers still resolve and the host's libc wins.
558
634
 
559
635
  ### Auto-discovering the database
560
636
 
@@ -647,6 +723,11 @@ baseline (`../baseline/src/oscillators.c`), which is the same file under a
647
723
  path the database has never heard of. The absent-source error and the
648
724
  soft-miss note both point at this flag when a same-name entry exists.
649
725
 
726
+ To contrast the same pair on a host compiler (does gcc-on-x86 make the
727
+ same choice?), add `--db-includes`: the borrow then carries only the
728
+ project's include paths, which are target-portable, and none of the
729
+ cross-only defines or `-specs` a host gcc would choke on.
730
+
650
731
  ## Whole-file summary
651
732
 
652
733
  With no `--pair`, no `--across`, and no `old_*`/`new_*` functions to
@@ -26,6 +26,12 @@ flags = [
26
26
  cc = "clang"
27
27
  flags = ["-O3", "-Wall", "-Wextra"]
28
28
 
29
+ # The name the docs use for "your native compiler" (--target host): an
30
+ # alias of [gcc] kept as its own table so the examples work verbatim.
31
+ [host]
32
+ cc = "gcc"
33
+ flags = ["-O3", "-Wall", "-Wextra"]
34
+
29
35
  # cc values may use ~, $VARS, and glob patterns. A glob that matches
30
36
  # several installed toolchains resolves to the highest version-sorted
31
37
  # one (announced on stderr); pin the exact esp-NN directory instead if