simdref 0.0.3__tar.gz → 0.0.5__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.
- {simdref-0.0.3/src/simdref.egg-info → simdref-0.0.5}/PKG-INFO +5 -3
- {simdref-0.0.3 → simdref-0.0.5}/README.md +3 -2
- {simdref-0.0.3 → simdref-0.0.5}/pyproject.toml +2 -1
- simdref-0.0.5/src/simdref/__init__.py +10 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/annotate.py +123 -35
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/cli.py +325 -59
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/ingest_catalog.py +24 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/manpages.py +13 -15
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/perf.py +8 -1
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/adapters/perf.py +46 -7
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/hotloop.py +70 -29
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/orchestrate.py +39 -17
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/storage.py +88 -18
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/templates/app.js +155 -28
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/web.py +60 -15
- {simdref-0.0.3 → simdref-0.0.5/src/simdref.egg-info}/PKG-INFO +5 -3
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref.egg-info/SOURCES.txt +7 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref.egg-info/requires.txt +1 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_annotate.py +16 -0
- simdref-0.0.5/tests/test_auto_update_on_version_change.py +174 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_cli_llm.py +19 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_coverage_live.py +2 -1
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_coverage_parity.py +2 -2
- simdref-0.0.5/tests/test_intel_operation_and_url.py +119 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_lsp_web.py +24 -23
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_perf.py +21 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_presentation.py +21 -8
- simdref-0.0.5/tests/test_riscv_docs_pages.py +52 -0
- simdref-0.0.5/tests/test_search_index_js.py +34 -0
- simdref-0.0.5/tests/test_storage_payload.py +56 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_storage_schema.py +3 -3
- simdref-0.0.5/tests/test_version_matches_pyproject.py +32 -0
- simdref-0.0.5/tests/test_web_e2e.py +202 -0
- simdref-0.0.3/src/simdref/__init__.py +0 -5
- {simdref-0.0.3 → simdref-0.0.5}/LICENSE +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/setup.cfg +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/__main__.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/arm_instructions.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/display.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/filters.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/ingest.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/ingest_pdf.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/ingest_sources.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/lsp.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/models.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/pdfparse/__init__.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/pdfparse/base.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/pdfparse/intel.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/pdfparse/registry.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/pdfparse/types.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/pdfrefs.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/perf_sources/__init__.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/perf_sources/cores.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/perf_sources/llvm_mca.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/perf_sources/llvm_scheduling.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/perf_sources/merge.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/__init__.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/adapters/__init__.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/adapters/exegesis.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/adapters/mca.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/adapters/uprof.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/adapters/vtune.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/adapters/xctrace.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/merge.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/model.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/registry.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/queries.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/riscv.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/search.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/templates/__init__.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/templates/favicon.svg +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/templates/index.html +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/templates/logo.svg +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/templates/style.css +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/tui.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref/ui_labels.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref.egg-info/dependency_links.txt +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref.egg-info/entry_points.txt +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/src/simdref.egg-info/top_level.txt +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_audit_coverage.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_cli_bare_query.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_cli_bootstrap_progress.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_cli_help.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_display.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_filters.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_ingest_sources.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_issue2_fixes.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_models.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_pdfparse.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_perf_sources.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_preset_persistence.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_search.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_search_pushdown.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_source_kind_filter.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_source_validation.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_tui.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_ui_labels_parity.py +0 -0
- {simdref-0.0.3 → simdref-0.0.5}/tests/test_x86_linking.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: simdref
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: Searchable SIMD intrinsic and instruction reference with CLI, manpages, TUI, LSP, and static web export.
|
|
5
5
|
Author: Marco
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -18,6 +18,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
18
18
|
Requires-Python: >=3.11
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
+
Requires-Dist: click<10,>=8.1
|
|
21
22
|
Requires-Dist: httpx<1,>=0.28
|
|
22
23
|
Requires-Dist: msgpack<2,>=1.0
|
|
23
24
|
Requires-Dist: pdfplumber<1,>=0.11
|
|
@@ -38,8 +39,9 @@ Dynamic: license-file
|
|
|
38
39
|
A single searchable reference for SIMD intrinsics and instructions across
|
|
39
40
|
**x86 (Intel + uops.info)**, **Arm (ACLE / AARCHMRS)**, and **RISC-V
|
|
40
41
|
(RVV + unified-db)**. Runs as a CLI, a Textual TUI, an LSP server,
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
on-demand manpages (`simdref man` — or run `simdref install-manpages`
|
|
43
|
+
so plain `man vpaddd` works), a static web app, and a structured JSON
|
|
44
|
+
interface for LLM skills.
|
|
43
45
|
|
|
44
46
|
[Web App](https://diamondinoia.github.io/simdref/) ·
|
|
45
47
|
[TestPyPI](https://test.pypi.org/project/simdref/) ·
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
A single searchable reference for SIMD intrinsics and instructions across
|
|
8
8
|
**x86 (Intel + uops.info)**, **Arm (ACLE / AARCHMRS)**, and **RISC-V
|
|
9
9
|
(RVV + unified-db)**. Runs as a CLI, a Textual TUI, an LSP server,
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
on-demand manpages (`simdref man` — or run `simdref install-manpages`
|
|
11
|
+
so plain `man vpaddd` works), a static web app, and a structured JSON
|
|
12
|
+
interface for LLM skills.
|
|
12
13
|
|
|
13
14
|
[Web App](https://diamondinoia.github.io/simdref/) ·
|
|
14
15
|
[TestPyPI](https://test.pypi.org/project/simdref/) ·
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "simdref"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.5"
|
|
8
8
|
description = "Searchable SIMD intrinsic and instruction reference with CLI, manpages, TUI, LSP, and static web export."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -26,6 +26,7 @@ classifiers = [
|
|
|
26
26
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
27
|
]
|
|
28
28
|
dependencies = [
|
|
29
|
+
"click>=8.1,<10",
|
|
29
30
|
"httpx>=0.28,<1",
|
|
30
31
|
"msgpack>=1.0,<2",
|
|
31
32
|
"pdfplumber>=0.11,<1",
|
|
@@ -42,23 +42,59 @@ class AsmLine:
|
|
|
42
42
|
source_line: int | None = None
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
# Instruction head without trailing comment: the '#' comment (and GAS-style
|
|
46
|
+
# annotations) is split off with str.partition — per the regex HOWTO, string
|
|
47
|
+
# methods beat a lazy re group for a fixed single-character cut.
|
|
45
48
|
_INSTR_RE = re.compile(
|
|
46
49
|
r"^(?P<indent>[ \t]*)"
|
|
47
50
|
r"(?P<mnemonic>[A-Za-z][A-Za-z0-9_.]*)"
|
|
48
|
-
r"(?:[ \t]+(?P<operands
|
|
49
|
-
r"(?:[ \t]*(?P<comment>#.*))?$"
|
|
51
|
+
r"(?:[ \t]+(?P<operands>.*))?$"
|
|
50
52
|
)
|
|
51
53
|
_LABEL_RE = re.compile(r"^[ \t]*[A-Za-z_.$][\w.$]*:")
|
|
52
54
|
# objdump -d line shape: optional whitespace, hex VA, ':', hex bytes, mnemonic ops.
|
|
55
|
+
# Byte tokens are 2-8 hex digits: x86 emits one whitespace-separated token per
|
|
56
|
+
# byte ("48 8b 45 f8"), ARM/RISC-V emit the whole encoding as one token
|
|
57
|
+
# ("a9bf7bfd" / "00050513").
|
|
53
58
|
_OBJDUMP_INSTR_RE = re.compile(
|
|
54
59
|
r"^\s*(?P<addr>[0-9a-fA-F]+):\s+"
|
|
55
|
-
r"(?:(?:[0-9a-fA-F]{2}\s+){1,10})?"
|
|
60
|
+
r"(?:(?:[0-9a-fA-F]{2,8}\s+){1,10})?"
|
|
56
61
|
r"(?P<rest>\S.*?)\s*$"
|
|
57
62
|
)
|
|
58
63
|
# objdump -S injects "file:line" comment lines before the instruction block.
|
|
59
64
|
_OBJDUMP_SRC_RE = re.compile(r"^\s*(?P<file>[^ \t/][^:]*):(?P<line>\d+)\s*$")
|
|
60
65
|
|
|
61
66
|
|
|
67
|
+
def _parse_objdump_instr(line: str) -> tuple[int, str] | None:
|
|
68
|
+
"""Match an ``objdump -d`` instruction line: ``<hexaddr>: [bytes] mnemonic ...``.
|
|
69
|
+
|
|
70
|
+
Returns ``(address, rest)`` where ``rest`` starts at the mnemonic, or None
|
|
71
|
+
when the line is not objdump-shaped (e.g. a column-0 numeric local label
|
|
72
|
+
``1:`` does not match).
|
|
73
|
+
"""
|
|
74
|
+
# Cheap str gates before the regex (HOWTO: "use string methods"): objdump
|
|
75
|
+
# never starts an instruction line at column 0 (GAS local labels ``1:``
|
|
76
|
+
# do), addresses start with a hex char, and the mandatory ':' sits within
|
|
77
|
+
# the first 24 columns.
|
|
78
|
+
if not line or line[0] not in " \t":
|
|
79
|
+
return None
|
|
80
|
+
bare = line.lstrip(" \t")
|
|
81
|
+
if not bare or bare[0] not in "0123456789abcdefABCDEF" or ":" not in bare[:24]:
|
|
82
|
+
return None
|
|
83
|
+
obj_m = _OBJDUMP_INSTR_RE.match(line)
|
|
84
|
+
if not obj_m:
|
|
85
|
+
return None
|
|
86
|
+
try:
|
|
87
|
+
return int(obj_m.group("addr"), 16), obj_m.group("rest")
|
|
88
|
+
except ValueError:
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _split_comment(text: str) -> tuple[str, str]:
|
|
93
|
+
"""Split AT&T ``#`` trailing comment off; returns (head, comment)."""
|
|
94
|
+
head, sep, comment = text.partition("#")
|
|
95
|
+
return head, (sep + comment if sep else "")
|
|
96
|
+
|
|
97
|
+
|
|
62
98
|
def parse_asm_line(line: str, *, track_positions: bool = False) -> AsmLine:
|
|
63
99
|
stripped = line.rstrip("\n")
|
|
64
100
|
if not stripped.strip():
|
|
@@ -68,19 +104,16 @@ def parse_asm_line(line: str, *, track_positions: bool = False) -> AsmLine:
|
|
|
68
104
|
return AsmLine(LineKind.COMMENT, stripped)
|
|
69
105
|
if bare.startswith("."):
|
|
70
106
|
return AsmLine(LineKind.DIRECTIVE, stripped)
|
|
71
|
-
if
|
|
72
|
-
|
|
107
|
+
if bare.startswith("Disassembly of "):
|
|
108
|
+
# objdump section banner: "Disassembly of section .text:"
|
|
109
|
+
return AsmLine(LineKind.COMMENT, stripped)
|
|
73
110
|
|
|
74
|
-
address: int | None = None
|
|
75
111
|
if track_positions:
|
|
76
|
-
|
|
77
|
-
if
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
address = None
|
|
82
|
-
rest = obj_m.group("rest")
|
|
83
|
-
m = _INSTR_RE.match(rest)
|
|
112
|
+
obj = _parse_objdump_instr(stripped)
|
|
113
|
+
if obj is not None:
|
|
114
|
+
address, rest = obj
|
|
115
|
+
head, comment = _split_comment(rest)
|
|
116
|
+
m = _INSTR_RE.match(head)
|
|
84
117
|
if m:
|
|
85
118
|
return AsmLine(
|
|
86
119
|
kind=LineKind.INSTRUCTION,
|
|
@@ -88,7 +121,7 @@ def parse_asm_line(line: str, *, track_positions: bool = False) -> AsmLine:
|
|
|
88
121
|
indent=(stripped[: stripped.find(rest)] if rest in stripped else ""),
|
|
89
122
|
mnemonic=m.group("mnemonic") or "",
|
|
90
123
|
operands=(m.group("operands") or "").strip(),
|
|
91
|
-
trailing_comment=
|
|
124
|
+
trailing_comment=comment.strip(),
|
|
92
125
|
address=address,
|
|
93
126
|
)
|
|
94
127
|
# In objdump mode, any non-matching line is an objdump header,
|
|
@@ -97,7 +130,30 @@ def parse_asm_line(line: str, *, track_positions: bool = False) -> AsmLine:
|
|
|
97
130
|
# source keywords ("return", "if", "for", ...) as mnemonics.
|
|
98
131
|
return AsmLine(LineKind.COMMENT, stripped)
|
|
99
132
|
|
|
100
|
-
|
|
133
|
+
if _LABEL_RE.match(stripped):
|
|
134
|
+
return AsmLine(LineKind.LABEL, stripped)
|
|
135
|
+
|
|
136
|
+
# objdump-style input (workflow §1d: `objdump -d ... > file.s`) is a
|
|
137
|
+
# first-class input even without --track-positions: strip the leading
|
|
138
|
+
# " <hexaddr>:\t" (and any raw bytes column) so the mnemonic parses.
|
|
139
|
+
obj = _parse_objdump_instr(stripped)
|
|
140
|
+
if obj is not None:
|
|
141
|
+
address, rest = obj
|
|
142
|
+
head, comment = _split_comment(rest)
|
|
143
|
+
m = _INSTR_RE.match(head)
|
|
144
|
+
if m:
|
|
145
|
+
return AsmLine(
|
|
146
|
+
kind=LineKind.INSTRUCTION,
|
|
147
|
+
raw=stripped,
|
|
148
|
+
indent=(stripped[: stripped.find(rest)] if rest in stripped else ""),
|
|
149
|
+
mnemonic=m.group("mnemonic") or "",
|
|
150
|
+
operands=(m.group("operands") or "").strip(),
|
|
151
|
+
trailing_comment=comment.strip(),
|
|
152
|
+
address=address,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
head, comment = _split_comment(stripped)
|
|
156
|
+
m = _INSTR_RE.match(head)
|
|
101
157
|
if not m:
|
|
102
158
|
return AsmLine(LineKind.COMMENT, stripped)
|
|
103
159
|
return AsmLine(
|
|
@@ -106,8 +162,8 @@ def parse_asm_line(line: str, *, track_positions: bool = False) -> AsmLine:
|
|
|
106
162
|
indent=m.group("indent") or "",
|
|
107
163
|
mnemonic=m.group("mnemonic") or "",
|
|
108
164
|
operands=(m.group("operands") or "").strip(),
|
|
109
|
-
trailing_comment=
|
|
110
|
-
address=
|
|
165
|
+
trailing_comment=comment.strip(),
|
|
166
|
+
address=None,
|
|
111
167
|
)
|
|
112
168
|
|
|
113
169
|
|
|
@@ -613,9 +669,23 @@ def _annotate_instruction(
|
|
|
613
669
|
parsed: AsmLine,
|
|
614
670
|
opts: AnnotateOptions,
|
|
615
671
|
conn: sqlite3.Connection,
|
|
672
|
+
lookup_cache: dict[str, list[InstructionRecord]] | None = None,
|
|
673
|
+
stats: dict[str, int] | None = None,
|
|
616
674
|
) -> tuple[str, dict[str, Any] | None]:
|
|
617
675
|
"""Return the rendered output line and an optional JSON record."""
|
|
618
|
-
|
|
676
|
+
if lookup_cache is not None:
|
|
677
|
+
records = lookup_cache.get(parsed.mnemonic)
|
|
678
|
+
if records is None:
|
|
679
|
+
# Module-level call (not a captured reference) so tests can
|
|
680
|
+
# keep monkeypatching ``annotate.lookup`` with a 2-arg fake.
|
|
681
|
+
records = lookup(parsed.mnemonic, conn)
|
|
682
|
+
lookup_cache[parsed.mnemonic] = records
|
|
683
|
+
else:
|
|
684
|
+
records = lookup(parsed.mnemonic, conn)
|
|
685
|
+
if stats is not None:
|
|
686
|
+
stats["parsed"] = stats.get("parsed", 0) + 1
|
|
687
|
+
if records:
|
|
688
|
+
stats["recognized"] = stats.get("recognized", 0) + 1
|
|
619
689
|
record = pick_record(records, arch=opts.arch, operands=parsed.operands)
|
|
620
690
|
|
|
621
691
|
if record is None:
|
|
@@ -692,34 +762,52 @@ def annotate_stream(
|
|
|
692
762
|
*,
|
|
693
763
|
opts: AnnotateOptions,
|
|
694
764
|
conn: sqlite3.Connection,
|
|
765
|
+
stats: dict[str, int] | None = None,
|
|
695
766
|
) -> Iterator[str]:
|
|
696
|
-
"""Yield annotated lines for each input line (newline-terminated).
|
|
767
|
+
"""Yield annotated lines for each input line (newline-terminated).
|
|
768
|
+
|
|
769
|
+
``stats`` (optional) accumulates parse/recognition counters — keys
|
|
770
|
+
``parsed``, ``recognized``, ``content`` — so callers can warn when the
|
|
771
|
+
input format was not understood (e.g. 0 of N instruction lines found).
|
|
772
|
+
"""
|
|
697
773
|
json_records: list[dict[str, Any]] = []
|
|
698
774
|
collecting_json = opts.fmt == "json"
|
|
699
775
|
pending_src_file: str | None = None
|
|
700
776
|
pending_src_line: int | None = None
|
|
777
|
+
lookup_cache: dict[str, list[InstructionRecord]] = {}
|
|
701
778
|
|
|
702
779
|
for line in lines:
|
|
703
|
-
if opts.track_positions:
|
|
704
|
-
src_m = _OBJDUMP_SRC_RE.match(line.rstrip("\n"))
|
|
705
|
-
if src_m:
|
|
706
|
-
pending_src_file = src_m.group("file").strip()
|
|
707
|
-
try:
|
|
708
|
-
pending_src_line = int(src_m.group("line"))
|
|
709
|
-
except ValueError:
|
|
710
|
-
pending_src_line = None
|
|
711
|
-
if not collecting_json:
|
|
712
|
-
yield line if line.endswith("\n") else line + "\n"
|
|
713
|
-
continue
|
|
714
780
|
parsed = parse_asm_line(line, track_positions=opts.track_positions)
|
|
715
|
-
if opts.track_positions
|
|
716
|
-
parsed.
|
|
717
|
-
|
|
781
|
+
if opts.track_positions:
|
|
782
|
+
if parsed.kind == LineKind.INSTRUCTION:
|
|
783
|
+
parsed.source_file = pending_src_file
|
|
784
|
+
parsed.source_line = pending_src_line
|
|
785
|
+
else:
|
|
786
|
+
# `-S` source interleave: objdump injects "file:line" marker
|
|
787
|
+
# lines immediately before the block they describe. Only
|
|
788
|
+
# non-instruction lines reach the (pricier) marker regex.
|
|
789
|
+
src_m = _OBJDUMP_SRC_RE.match(parsed.raw)
|
|
790
|
+
if src_m:
|
|
791
|
+
pending_src_file = src_m.group("file").strip()
|
|
792
|
+
try:
|
|
793
|
+
pending_src_line = int(src_m.group("line"))
|
|
794
|
+
except ValueError:
|
|
795
|
+
pending_src_line = None
|
|
796
|
+
if not collecting_json:
|
|
797
|
+
yield parsed.raw + "\n"
|
|
798
|
+
continue
|
|
799
|
+
if stats is not None and parsed.kind != LineKind.BLANK:
|
|
800
|
+
# "content" = anything that is not blank, an assembler directive,
|
|
801
|
+
# or a real comment — i.e. instructions, labels, and unrecognised
|
|
802
|
+
# lines the parser rejected (so "recognised 0/N" can warn).
|
|
803
|
+
s = parsed.raw.lstrip()
|
|
804
|
+
if s and not s.startswith((".", "#", "//")):
|
|
805
|
+
stats["content"] = stats.get("content", 0) + 1
|
|
718
806
|
if parsed.kind != LineKind.INSTRUCTION:
|
|
719
807
|
if not collecting_json:
|
|
720
808
|
yield parsed.raw + "\n"
|
|
721
809
|
continue
|
|
722
|
-
out_line, record = _annotate_instruction(parsed, opts, conn)
|
|
810
|
+
out_line, record = _annotate_instruction(parsed, opts, conn, lookup_cache, stats)
|
|
723
811
|
if collecting_json:
|
|
724
812
|
if record is not None:
|
|
725
813
|
json_records.append(record)
|