|
Benchmarking the port against master op-by-op (rather than only asking whether
tests pass) turned up three real bugs, all in the most user-visible path:
opening pseudocode.
1. decompile was doing decomp_map's job. It called the full per-column line
map purely to fill in each line's /*0xEA*/ anchor. The tool ida-tui was
written against takes ONE get_line_item at column 0 per line; the port took
one per COLUMN, i.e. thousands of get_line_item+dstr() calls per function
instead of one per line. Every pseudocode open cost the same as opening the
split view. Carried the real implementation over: 1888ms -> 53ms.
2. decomp_map used the pre-optimisation line map. Ours memoises obj_id -> ea
for the whole function (commit 853d90c: dstr() was 79% of the tool, and
consecutive columns report the same ctree item), the port's did not.
1925ms -> 287ms.
3. _idatui_compact imported ida_pro_mcp on every call. Under Code Mode that
package is not installed in the database process, so the import failed every
time -- and a FAILED import is never cached, so each one re-searched the
whole of sys.path: 422 failed imports per pc_nums call, which was most of
its runtime. 1428ms -> 257ms.
The same bug was a correctness bug hiding behind the perf bug: the fallback
path collapsed whitespace INSIDE string literals, where the real function
preserves it. Pseudocode columns are served in those coordinates, so on any
line containing a string with two spaces, every literal's mark and every
reformat would have been placed on the wrong column. It never fired on
master because ida_pro_mcp is installed there. Now calls the byte-identical
module-level shim directly, with the deviation from the extracted original
documented in place.
Narrow verification: decomp/split_view/opfmt/follow/comment/structs scenarios,
72 passed, 0 failed. Full gate running separately.
|
|
them
This closes the five operations the port was missing and restores the listing's
own tooling instead of a re-implementation of it.
idatui/remote_tools.py is the port's IDAPython island: `heads` (the continuous
listing) and `op_format`/`pc_nums`/`pc_num_format` (`o`/`O`), extracted verbatim
from the BODY that server/patch_server.py used to inject. They are real, diffable
source shipped to the database process as text, not string literals, because this
is the most performance-tuned and behaviour-sensitive code in the project.
Why carry `heads` over rather than keep the port's version: the port's rewrite
emitted no per-operand extents ("ops"), so no keypress could show which literal
it would reformat (opfmt_highlight had no two-operand row to find); it had no
digest/`expect` support, so every page was re-sent after any edit; and its span
walk was the per-character loop ours had already been rewritten out of. It also
dropped struct-member expansion sizing and the func banner/label rows' exact
shapes.
The library is installed ONCE per database process (sys.modules, keyed by a hash
of the source) and then called by name. Code Mode's execute_python builds a fresh
namespace per call, so a library exec'd inline is rebuilt every time and its
module-level caches thrown away -- the per-line render lru_cache in particular,
which the perf work sized to 65536 entries. Installing it once took `heads`
count=200 from 181ms to 92ms; the cache reports 211 hits on a second call where
it previously reported none. (Extraction footgun recorded: ast FunctionDef.lineno
points at `def`, not at the decorators, so a naive slice silently drops
@lru_cache.)
Also ported: flowchart, survey_binary, and the xref contract.
Live pilot suite on targets/echo: 301 passed, 0 failed -- identical to master.
Known, quantified, and NOT fixed here: Code Mode's transport is much slower than
the unix-socket worker for the listing's paging. heads count=200 is 2.6ms on
master vs 92ms here, count=500 is 6.3ms vs 214ms. Roughly half of that is
to_jsonable + HTTP framing per call and is inherent to the architecture; the
empty round trip alone is 2ms. The digest/`expect` path (unchanged pages) is the
main mitigation and is restored.
|