summaryrefslogtreecommitdiffstats
path: root/idatui
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-07 04:16:01 +0200
committeruser <user@clank>2026-08-07 04:16:01 +0200
commitb2b59e0ed8bbd63877700292337eca4153f09bd4 (patch)
tree2252a0531b8e8a36bdceb889794bbda9658bf7fd /idatui
parentautoresearch: final playbook update - budget, floors, dead ends (diff)
downloadida-tui-b2b59e0ed8bbd63877700292337eca4153f09bd4.tar.gz
ida-tui-b2b59e0ed8bbd63877700292337eca4153f09bd4.tar.xz
ida-tui-b2b59e0ed8bbd63877700292337eca4153f09bd4.zip
Keep a listing row's spans and operand extents exactly as they came off the wire instead of copying them into tuples. The copy re-proved types the worker's own tool guarantees, and it destroyed the object sharing the worker's line cache had created — 228k rows now reference 125k span lists, not 228k private tuples.
Result: {"status":"keep","total_ms":17700.4,"lg_boot_ms":776,"lg_decomp_ms":2690.1,"lg_graph_ms":888.1,"lg_hex_ms":449.1,"lg_index_ms":97.4,"lg_listing_cold_ms":439.4,"lg_listing_warm_ms":404.6,"lg_nav_ms":6762.9,"lg_palette_ms":4.6,"lg_render_ms":230.8,"lg_search_ms":762.9,"pure_graph_ms":217.1,"sm_boot_ms":452.6,"sm_decomp_ms":1270,"sm_graph_ms":698,"sm_hex_ms":433.2,"sm_index_ms":2.4,"sm_listing_cold_ms":259.1,"sm_listing_warm_ms":260.5,"sm_nav_ms":300.4,"sm_palette_ms":0.3,"sm_render_ms":258.3,"sm_search_ms":42.7,"fails":0}
Diffstat (limited to 'idatui')
-rw-r--r--idatui/domain.py27
1 files changed, 16 insertions, 11 deletions
diff --git a/idatui/domain.py b/idatui/domain.py
index e107786..b5081cf 100644
--- a/idatui/domain.py
+++ b/idatui/domain.py
@@ -27,6 +27,7 @@ import re
import threading
import urllib.request
from concurrent.futures import ThreadPoolExecutor
+from collections.abc import Sequence
from dataclasses import dataclass, field, replace
from typing import NamedTuple
from typing import Callable, TYPE_CHECKING
@@ -115,12 +116,18 @@ class Head(NamedTuple):
#: [(kind, text)] from IDA's own colour tags — mnem/reg/num/name/str/punct/…
#: None when the worker didn't provide them (older worker, or the spans
#: disagreed with the plain text, in which case the text wins).
- spans: tuple[tuple[str, str], ...] | None = None
+ #:
+ #: Held exactly as it came off the wire, and **read-only**. The worker
+ #: memoises its per-line render, so one list is shared by every row that
+ #: says the same thing — pickle preserves that, and 228 000 rows of bash
+ #: reference about 53 000 lists. Copying each row's into a fresh tuple threw
+ #: the sharing away and cost 0.9 µs a row for nothing.
+ spans: Sequence | None = None
#: [(start, end, n)] — where each operand sits in ``text``, from IDA's own
#: COLOR_OPND markers. Lets the view show which operand the cursor is on,
#: and is the same information the worker maps a column through, so the
- #: highlight and the edit can't disagree.
- ops: tuple[tuple[int, int, int], ...] | None = None
+ #: highlight and the edit can't disagree. Read-only, as ``spans`` is.
+ ops: Sequence | None = None
@property
def label(self) -> str | None: # Line-compatible alias
@@ -135,12 +142,10 @@ class Head(NamedTuple):
@classmethod
def from_raw(cls, d: dict, raw: bytes | None = None) -> "Head":
- sp = d.get("spans")
- ops = d.get("ops")
- # ``tuple(map(tuple, ...))`` rather than a per-item genexpr with str()/
- # int() coercion: this runs once per listing row (hundreds of thousands
- # on a real binary) and the worker's own tool already emits [str, str]
- # and [int, int, int]. The coercion was re-proving that on every row.
+ # Spans and operand extents are stored as they arrive: the worker's own
+ # tool emits [str, str] and [int, int, int], so re-coercing them was
+ # re-proving that once per listing row -- and copying them into tuples
+ # destroyed the sharing the worker's line cache had just created.
return cls(
ea=_as_int(d["ea"]),
kind=d.get("kind", "unknown"),
@@ -148,8 +153,8 @@ class Head(NamedTuple):
text=d.get("text", ""),
name=d.get("name"),
raw=raw,
- spans=tuple(map(tuple, sp)) if sp else None,
- ops=tuple(map(tuple, ops)) if ops else None,
+ spans=d.get("spans") or None,
+ ops=d.get("ops") or None,
)