asmdiff 0.1.5__tar.gz → 0.1.7__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 (26) hide show
  1. {asmdiff-0.1.5 → asmdiff-0.1.7}/.claude/settings.local.json +3 -1
  2. asmdiff-0.1.7/2026-07-07-elf-mode-handoff.md +156 -0
  3. {asmdiff-0.1.5 → asmdiff-0.1.7}/CLAUDE.md +7 -2
  4. {asmdiff-0.1.5 → asmdiff-0.1.7}/PKG-INFO +439 -128
  5. {asmdiff-0.1.5 → asmdiff-0.1.7}/README.md +437 -126
  6. asmdiff-0.1.7/Screenshot 2026-07-07 225145.jpg +0 -0
  7. asmdiff-0.1.7/asmdiff.example.toml +100 -0
  8. {asmdiff-0.1.5 → asmdiff-0.1.7}/asmdiff.py +489 -36
  9. asmdiff-0.1.7/docs/superpowers/specs/2026-07-07-edit-config-design.md +97 -0
  10. {asmdiff-0.1.5 → asmdiff-0.1.7}/pyproject.toml +2 -2
  11. {asmdiff-0.1.5 → asmdiff-0.1.7}/test_asmdiff.py +525 -8
  12. asmdiff-0.1.5/asmdiff.example.toml +0 -51
  13. {asmdiff-0.1.5 → asmdiff-0.1.7}/.gitignore +0 -0
  14. {asmdiff-0.1.5 → asmdiff-0.1.7}/2026-07-06-inspect-mode.md +0 -0
  15. {asmdiff-0.1.5 → asmdiff-0.1.7}/FIELD-TEST-REPORT.md +0 -0
  16. {asmdiff-0.1.5 → asmdiff-0.1.7}/LICENSE +0 -0
  17. {asmdiff-0.1.5 → asmdiff-0.1.7}/asmdiff_example.c +0 -0
  18. {asmdiff-0.1.5 → asmdiff-0.1.7}/dist_1/.gitignore +0 -0
  19. {asmdiff-0.1.5 → asmdiff-0.1.7}/dist_1/asmdiff-0.1.1-py3-none-any.whl +0 -0
  20. {asmdiff-0.1.5 → asmdiff-0.1.7}/dist_1/asmdiff-0.1.1.tar.gz +0 -0
  21. {asmdiff-0.1.5 → asmdiff-0.1.7}/dist_1/asmdiff-0.1.2-py3-none-any.whl +0 -0
  22. {asmdiff-0.1.5 → asmdiff-0.1.7}/dist_1/asmdiff-0.1.2.tar.gz +0 -0
  23. {asmdiff-0.1.5 → asmdiff-0.1.7}/dist_1/asmdiff-0.1.3-py3-none-any.whl +0 -0
  24. {asmdiff-0.1.5 → asmdiff-0.1.7}/dist_1/asmdiff-0.1.3.tar.gz +0 -0
  25. {asmdiff-0.1.5 → asmdiff-0.1.7}/dist_1/asmdiff-0.1.4-py3-none-any.whl +0 -0
  26. {asmdiff-0.1.5 → asmdiff-0.1.7}/dist_1/asmdiff-0.1.4.tar.gz +0 -0
@@ -4,7 +4,9 @@
4
4
  "Bash(python3 test_asmdiff.py)",
5
5
  "Bash(git -C /home/fatta/esp-idf/tools/asmdiff log --oneline -5)",
6
6
  "Bash(git -C /home/fatta/esp-idf/tools/asmdiff diff main...HEAD --stat)",
7
- "Bash(npx --no-install mmdc --version)"
7
+ "Bash(npx --no-install mmdc --version)",
8
+ "Bash(git -C /home/fatta/esp-idf/tools/asmdiff diff --stat)",
9
+ "Bash(git -C /home/fatta/esp-idf/tools/asmdiff diff)"
8
10
  ]
9
11
  }
10
12
  }
@@ -0,0 +1,156 @@
1
+ # asmdiff ELF input mode — checkpoint 2026-07-07
2
+
3
+ Handoff for a fresh session in THIS repo (`~/esp-idf/tools/asmdiff`). Feature is
4
+ scoped and estimated but zero code is written. Origin: a zero-overhead-loop (ZOL)
5
+ audit in `~/esp-idf/S3-Amysynth` (report: `S3-Amysynth/docs/zol-audit-2026-07-07.md`)
6
+ where single-TU `-S` output could not show post-LTO reality — the audit had to
7
+ hand-roll awk over `objdump -d`. This mode makes that reusable.
8
+
9
+ ## State
10
+
11
+ Design agreed with the user, sized at ~250-350 lines in `asmdiff.py` plus
12
+ ~200-300 lines of tests (~20% repo growth), judged in line with the tool's
13
+ direction ("what did the compiler do" gains "what actually shipped after LTO").
14
+ No branch created. The awk prototypes lived in an ephemeral
15
+ session scratchpad — everything needed from them is restated below.
16
+
17
+ **2026-07-07 later session: item 1 (objdump parser / ZOL identifier) DONE.**
18
+ `extract_functions_objdump()` + `_relabel_objdump()` in asmdiff.py (after
19
+ `extract_functions`), TDD'd via `TestObjdumpExtract` (12 tests, fixture
20
+ `OBJDUMP_ASM`) in test_asmdiff.py; full suite green except the pre-existing
21
+ env-dependent `TestShortAliases.test_p_is_pair` failure (a global
22
+ ~/.config/asmdiff.toml with target esp32s3 short-circuits it — fails at HEAD
23
+ too). Validated against the real S3-Amysynth ELF (1682 functions parsed):
24
+ ZOL counts match the fixtures exactly (render_lut 1, filter_process 6,
25
+ render_lut_cub 0, amy_fill_buffer 3; LTO-inlined trio absent). Branch-span
26
+ counts read HIGHER than the audit's (filter_process 4 vs 3,
27
+ amy_fill_buffer 11 vs 5) — explained, not a bug: the audit awk counted only
28
+ conditional backedges, while `loop_spans()` counts any backward label
29
+ reference incl. unconditional `j` latches (top-tested loops), same as it
30
+ does on -S output. Spans containing a conditional backedge match the audit
31
+ exactly (3 and 5).
32
+
33
+ **2026-07-07 third session: items 2-4 DONE — feature complete.**
34
+ - Longcall resolver (`_resolve_longcalls`, `L32R_VALUE`, `XTENSA_STORES`,
35
+ `TestLongcallResolver` 9 tests): nearest write to the call register in an
36
+ 8-insn window decides; annotated l32r resolves, unannotated l32r or a
37
+ non-store overwrite (e.g. l32i fetching a function pointer from a
38
+ just-loaded struct — a REAL false positive caught on the amysynth ELF,
39
+ where `amy_fill_buffer` briefly reported data symbol `amy_global` as a
40
+ callee) leaves it indirect. `render_partial` now reports `__divsf3` etc.
41
+ - CLI (`is_elf` magic-bytes, `derive_objdump` gcc→objdump swap,
42
+ `run_objdump`, `run_elf`, `--filter REGEX`, `--objdump PATH`,
43
+ `TestElfMode` 14 tests): named fns get listing + table, --filter adds
44
+ table-only rows, bare ELF errors with a hint, unknown names get difflib
45
+ close-match suggestions, --pair/--across/--layout rejected. Deriving
46
+ objdump resolves config targets with `want_db=False` (new param on
47
+ build_matrix/target_command) so a target's `compile_commands = true`
48
+ no longer triggers db discovery when nothing is compiled — found by
49
+ running against the real config.
50
+ - Docs: module docstring (five modes + ELF usage line), README section
51
+ "ELF input: what actually shipped after LTO" (incl. the ZOL kill rules),
52
+ command-reference rows, a pointer from "Why not objdump by hand", and a
53
+ narrowed indirect-call limitation. Example toml untouched (no config
54
+ keys added).
55
+ - Suite: 182 tests, sole failure the pre-existing env-dependent
56
+ `test_p_is_pair`. End-to-end validated on the real ELF via config-derived
57
+ objdump (glob → esp-15.2.0 toolchain). Nothing committed.
58
+
59
+ ## What the mode does
60
+
61
+ `asmdiff build/firmware.elf render_lut render_lut_cub` (or `--filter 'render_'`)
62
+ disassembles the linked ELF via the toolchain's objdump and feeds each function
63
+ through the EXISTING analyzers and renderers — same summary table
64
+ (`function / insns / calls / loop spans`, ZOL spans marked by `_LEND`-style
65
+ labels), no compile matrix.
66
+
67
+ Why it must exist: LTO fully inlines functions ("symbol from plugin" in the
68
+ linker map) — in the motivating audit, `stereo_reverb` / `parametric_eq_process` /
69
+ `delay_line_in_out` had no symbols at all; their loops lived inside
70
+ `amy_fill_buffer`. Only the ELF can answer "did this loop ship as ZOL".
71
+
72
+ ## Reuse map (verified against asmdiff.py @ 1298 lines)
73
+
74
+ Unchanged, reused verbatim:
75
+ - `analyze()` (line ~530) — insns + calls from "cleaned asm lines" (mnemonic +
76
+ operands, labels end with `:`). Format-agnostic.
77
+ - `loop_spans()` (~557) — backward-branch spans + Xtensa `loop*`-to-end-label
78
+ spans. Works on the same cleaned-lines shape IF branch targets are labels.
79
+ - `CALL_RE` (~524), `inspect_table`, `file_summary_table`, `format_spans`,
80
+ `format_calls`, `render_table`.
81
+
82
+ New code:
83
+ 1. **objdump parser** (~70 lines), sibling of `extract_functions()` (~477):
84
+ - Function header: `^([0-9a-f]+) <([^>]+)>:$`.
85
+ - Insn line: tab-separated `addr:\thexbytes\tmnemonic\toperands` (hex bytes
86
+ are ONE token, e.g. `004136` — do not confuse with the mnemonic).
87
+ - Convert to cleaned lines: rewrite branch-target addresses
88
+ (`beqz a7, 4037d176 <fn+0x82>`) into synthetic local labels (e.g. `.L82`)
89
+ and insert the label line at the target instruction, so `loop_spans()`
90
+ works unchanged. Xtensa `loop aN, <end-addr>` gets the same treatment;
91
+ name the synthetic end labels `.L<off>_LEND` so the existing ZOL-vs-branch
92
+ visual convention holds.
93
+ - Skip data regions: headers with `+`/`-` offset forms
94
+ (`<render_lpf_lut$constprop$0-0x1130>:` = literal pools / symbol-less
95
+ gaps that disassemble as garbage) and bare `...` filler lines.
96
+ 2. **Longcall resolver** (~25 lines, Xtensa): `-mlongcalls` turns direct calls
97
+ into `l32r aX, <lit> (VALUE <sym>)` … `callx8 aX`. Resolve by scanning back
98
+ ≤8 insns for the `l32r` into the same register and take the `<sym>` from its
99
+ parenthesized annotation; rewrite the line so `CALL_RE` reports the real
100
+ symbol instead of an indirect. Degrade gracefully to "indirect" when the
101
+ annotation is absent (binutils format drift) — do not promise universality.
102
+ 3. **objdump invocation + CLI** (~80 lines): detect an `.elf` positional (or
103
+ ELF magic), derive objdump from the resolved cc path (swap trailing `gcc` →
104
+ `objdump`; the resolve_cc glob machinery ~708 already finds the toolchain),
105
+ optional `--objdump PATH` override. A whole ELF has ~1700 functions, so
106
+ bare-ELF invocation needs `--filter REGEX` (function names remain the
107
+ positional form). No compile, no matrix, no `-db`.
108
+ 4. **Tests + docs**: golden objdump snippets, one class per feature area
109
+ (repo convention, `python3 test_asmdiff.py`, currently 147 tests pass).
110
+ README + module docstring + (if flags/config change) `asmdiff.example.toml`
111
+ AND its byte-identical `EXAMPLE_CONFIG` constant — a unit test enforces the
112
+ match. Stdlib-only, no third-party deps (hard repo rule).
113
+
114
+ ## Ground-truth fixtures (from the motivating audit; ELF at
115
+ `S3-Amysynth/build/S3-Amysynth.elf`, toolchain esp-15.2.0 xtensa-esp32s3-elf)
116
+
117
+ Use as acceptance checks (re-verify against the current build — it may have
118
+ been rebuilt since):
119
+ - `render_lut` 1 ZOL loop, call-free; `filter_process` 6 ZOL + 3 branch.
120
+ - `render_lut_cub` 0 ZOL, 1 backward-branch loop, call-free (register-pressure
121
+ loss — the interesting true-negative).
122
+ - `amy_fill_buffer` 3 ZOL + 5 branch loops, all call-free (holds the inlined
123
+ FX pipeline).
124
+ - `render_partial` loop contains `__divsf3` longcalls that resolve via the
125
+ l32r pattern — the longcall-resolver test case.
126
+
127
+ ## ZOL domain facts (empirically verified on esp-15.2.0, -O2)
128
+
129
+ One call surviving to codegen anywhere in the loop body kills ZOL even behind a
130
+ never-taken runtime condition; early `break` kills it; spilled loop counter
131
+ (register pressure) kills it; nested loops: inner only. These belong in the
132
+ README section explaining what the mode is for.
133
+
134
+ ## Hypotheses
135
+
136
+ N/A (feature work, not debugging). Open design choices, pick during TDD:
137
+ - OPEN: `.elf` detection — extension vs magic bytes (magic is safer, 4 bytes).
138
+ - OPEN: whether ELF mode with no names and no `--filter` prints the full
139
+ ~1700-row table or errors asking for a filter (lean: error with hint).
140
+
141
+ ## Next steps
142
+
143
+ 1. Read this repo's `CLAUDE.md`, `README.md` (inspect-mode section), and skim
144
+ `2026-07-06-inspect-mode.md` for how the last mode was integrated + doc'd.
145
+ 2. TDD: write the objdump-parser tests first (golden snippet with a ZOL loop,
146
+ a backward branch, a data region to skip, a longcall) — then implement the
147
+ parser to green.
148
+ 3. Longcall resolver + tests.
149
+ 4. CLI routing + objdump-path derivation + tests.
150
+ 5. README/docstring updates; full suite `python3 test_asmdiff.py`.
151
+ 6. Validate against the real S3-Amysynth ELF using the fixtures above.
152
+
153
+ ## Hardware-verify pending
154
+
155
+ None — this tool never touches hardware. Real-ELF validation (step 6) is the
156
+ equivalent gate.
@@ -22,7 +22,12 @@ PyPI as `asmdiff` (hatchling build, see pyproject.toml).
22
22
 
23
23
  - `asmdiff.py` — the entire tool.
24
24
  - `test_asmdiff.py` — unit tests, one class per feature area.
25
- - `asmdiff.example.toml` — documented starter config; keep it generic
26
- (portable gcc/clang defaults, ESP targets as commented examples).
25
+ - `asmdiff.example.toml` — documented starter config; keep it generic:
26
+ portable gcc/clang defaults plus active embedded target profiles
27
+ grouped by toolchain family (riscv32-esp, xtensa-esp, and basic
28
+ stm32/rp2350 entries), default install paths or bare PATH names and
29
+ minimal arch flags only, nothing machine-specific. Mirrored
30
+ byte-for-byte in asmdiff.py's EXAMPLE_CONFIG (a unit test enforces
31
+ the match): any edit to the toml must update the constant too.
27
32
  - README.md documents every user-visible behavior; changes to flags or
28
33
  config keys update README, example toml, and the module docstring.