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.
Files changed (98) hide show
  1. {simdref-0.0.3/src/simdref.egg-info → simdref-0.0.5}/PKG-INFO +5 -3
  2. {simdref-0.0.3 → simdref-0.0.5}/README.md +3 -2
  3. {simdref-0.0.3 → simdref-0.0.5}/pyproject.toml +2 -1
  4. simdref-0.0.5/src/simdref/__init__.py +10 -0
  5. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/annotate.py +123 -35
  6. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/cli.py +325 -59
  7. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/ingest_catalog.py +24 -0
  8. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/manpages.py +13 -15
  9. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/perf.py +8 -1
  10. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/adapters/perf.py +46 -7
  11. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/hotloop.py +70 -29
  12. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/orchestrate.py +39 -17
  13. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/storage.py +88 -18
  14. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/templates/app.js +155 -28
  15. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/web.py +60 -15
  16. {simdref-0.0.3 → simdref-0.0.5/src/simdref.egg-info}/PKG-INFO +5 -3
  17. {simdref-0.0.3 → simdref-0.0.5}/src/simdref.egg-info/SOURCES.txt +7 -0
  18. {simdref-0.0.3 → simdref-0.0.5}/src/simdref.egg-info/requires.txt +1 -0
  19. {simdref-0.0.3 → simdref-0.0.5}/tests/test_annotate.py +16 -0
  20. simdref-0.0.5/tests/test_auto_update_on_version_change.py +174 -0
  21. {simdref-0.0.3 → simdref-0.0.5}/tests/test_cli_llm.py +19 -0
  22. {simdref-0.0.3 → simdref-0.0.5}/tests/test_coverage_live.py +2 -1
  23. {simdref-0.0.3 → simdref-0.0.5}/tests/test_coverage_parity.py +2 -2
  24. simdref-0.0.5/tests/test_intel_operation_and_url.py +119 -0
  25. {simdref-0.0.3 → simdref-0.0.5}/tests/test_lsp_web.py +24 -23
  26. {simdref-0.0.3 → simdref-0.0.5}/tests/test_perf.py +21 -0
  27. {simdref-0.0.3 → simdref-0.0.5}/tests/test_presentation.py +21 -8
  28. simdref-0.0.5/tests/test_riscv_docs_pages.py +52 -0
  29. simdref-0.0.5/tests/test_search_index_js.py +34 -0
  30. simdref-0.0.5/tests/test_storage_payload.py +56 -0
  31. {simdref-0.0.3 → simdref-0.0.5}/tests/test_storage_schema.py +3 -3
  32. simdref-0.0.5/tests/test_version_matches_pyproject.py +32 -0
  33. simdref-0.0.5/tests/test_web_e2e.py +202 -0
  34. simdref-0.0.3/src/simdref/__init__.py +0 -5
  35. {simdref-0.0.3 → simdref-0.0.5}/LICENSE +0 -0
  36. {simdref-0.0.3 → simdref-0.0.5}/setup.cfg +0 -0
  37. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/__main__.py +0 -0
  38. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/arm_instructions.py +0 -0
  39. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/display.py +0 -0
  40. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/filters.py +0 -0
  41. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/ingest.py +0 -0
  42. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/ingest_pdf.py +0 -0
  43. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/ingest_sources.py +0 -0
  44. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/lsp.py +0 -0
  45. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/models.py +0 -0
  46. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/pdfparse/__init__.py +0 -0
  47. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/pdfparse/base.py +0 -0
  48. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/pdfparse/intel.py +0 -0
  49. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/pdfparse/registry.py +0 -0
  50. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/pdfparse/types.py +0 -0
  51. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/pdfrefs.py +0 -0
  52. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/perf_sources/__init__.py +0 -0
  53. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/perf_sources/cores.py +0 -0
  54. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/perf_sources/llvm_mca.py +0 -0
  55. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/perf_sources/llvm_scheduling.py +0 -0
  56. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/perf_sources/merge.py +0 -0
  57. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/__init__.py +0 -0
  58. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/adapters/__init__.py +0 -0
  59. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/adapters/exegesis.py +0 -0
  60. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/adapters/mca.py +0 -0
  61. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/adapters/uprof.py +0 -0
  62. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/adapters/vtune.py +0 -0
  63. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/adapters/xctrace.py +0 -0
  64. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/merge.py +0 -0
  65. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/model.py +0 -0
  66. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/profile/registry.py +0 -0
  67. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/queries.py +0 -0
  68. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/riscv.py +0 -0
  69. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/search.py +0 -0
  70. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/templates/__init__.py +0 -0
  71. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/templates/favicon.svg +0 -0
  72. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/templates/index.html +0 -0
  73. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/templates/logo.svg +0 -0
  74. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/templates/style.css +0 -0
  75. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/tui.py +0 -0
  76. {simdref-0.0.3 → simdref-0.0.5}/src/simdref/ui_labels.py +0 -0
  77. {simdref-0.0.3 → simdref-0.0.5}/src/simdref.egg-info/dependency_links.txt +0 -0
  78. {simdref-0.0.3 → simdref-0.0.5}/src/simdref.egg-info/entry_points.txt +0 -0
  79. {simdref-0.0.3 → simdref-0.0.5}/src/simdref.egg-info/top_level.txt +0 -0
  80. {simdref-0.0.3 → simdref-0.0.5}/tests/test_audit_coverage.py +0 -0
  81. {simdref-0.0.3 → simdref-0.0.5}/tests/test_cli_bare_query.py +0 -0
  82. {simdref-0.0.3 → simdref-0.0.5}/tests/test_cli_bootstrap_progress.py +0 -0
  83. {simdref-0.0.3 → simdref-0.0.5}/tests/test_cli_help.py +0 -0
  84. {simdref-0.0.3 → simdref-0.0.5}/tests/test_display.py +0 -0
  85. {simdref-0.0.3 → simdref-0.0.5}/tests/test_filters.py +0 -0
  86. {simdref-0.0.3 → simdref-0.0.5}/tests/test_ingest_sources.py +0 -0
  87. {simdref-0.0.3 → simdref-0.0.5}/tests/test_issue2_fixes.py +0 -0
  88. {simdref-0.0.3 → simdref-0.0.5}/tests/test_models.py +0 -0
  89. {simdref-0.0.3 → simdref-0.0.5}/tests/test_pdfparse.py +0 -0
  90. {simdref-0.0.3 → simdref-0.0.5}/tests/test_perf_sources.py +0 -0
  91. {simdref-0.0.3 → simdref-0.0.5}/tests/test_preset_persistence.py +0 -0
  92. {simdref-0.0.3 → simdref-0.0.5}/tests/test_search.py +0 -0
  93. {simdref-0.0.3 → simdref-0.0.5}/tests/test_search_pushdown.py +0 -0
  94. {simdref-0.0.3 → simdref-0.0.5}/tests/test_source_kind_filter.py +0 -0
  95. {simdref-0.0.3 → simdref-0.0.5}/tests/test_source_validation.py +0 -0
  96. {simdref-0.0.3 → simdref-0.0.5}/tests/test_tui.py +0 -0
  97. {simdref-0.0.3 → simdref-0.0.5}/tests/test_ui_labels_parity.py +0 -0
  98. {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
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
- generated manpages, a static web app, and a structured JSON interface
42
- for LLM skills.
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
- generated manpages, a static web app, and a structured JSON interface
11
- for LLM skills.
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.3"
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",
@@ -0,0 +1,10 @@
1
+ """simdref package."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ __all__ = ["__version__"]
6
+
7
+ try:
8
+ __version__ = version("simdref")
9
+ except PackageNotFoundError: # uninstalled source checkout
10
+ __version__ = "0.0.0+source"
@@ -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>[^#\n]*?))?"
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 _LABEL_RE.match(stripped):
72
- return AsmLine(LineKind.LABEL, stripped)
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
- obj_m = _OBJDUMP_INSTR_RE.match(stripped)
77
- if obj_m:
78
- try:
79
- address = int(obj_m.group("addr"), 16)
80
- except ValueError:
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=(m.group("comment") or "").strip(),
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
- m = _INSTR_RE.match(stripped)
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=(m.group("comment") or "").strip(),
110
- address=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
- records = lookup(parsed.mnemonic, conn)
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 and parsed.kind == LineKind.INSTRUCTION:
716
- parsed.source_file = pending_src_file
717
- parsed.source_line = pending_src_line
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)