simdref 0.0.0__py3-none-any.whl
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/__init__.py +6 -0
- simdref/__main__.py +6 -0
- simdref/annotate.py +448 -0
- simdref/arm_instructions.py +417 -0
- simdref/cli.py +1598 -0
- simdref/display.py +963 -0
- simdref/filters.py +318 -0
- simdref/ingest.py +113 -0
- simdref/ingest_catalog.py +1172 -0
- simdref/ingest_pdf.py +188 -0
- simdref/ingest_sources.py +580 -0
- simdref/lsp.py +208 -0
- simdref/manpages.py +139 -0
- simdref/models.py +225 -0
- simdref/pdfparse/__init__.py +13 -0
- simdref/pdfparse/base.py +116 -0
- simdref/pdfparse/intel.py +614 -0
- simdref/pdfparse/registry.py +19 -0
- simdref/pdfparse/types.py +77 -0
- simdref/pdfrefs.py +95 -0
- simdref/perf.py +220 -0
- simdref/perf_sources/__init__.py +51 -0
- simdref/perf_sources/cores.py +101 -0
- simdref/perf_sources/llvm_mca.py +176 -0
- simdref/perf_sources/llvm_scheduling.py +625 -0
- simdref/perf_sources/merge.py +121 -0
- simdref/queries.py +207 -0
- simdref/riscv.py +446 -0
- simdref/search.py +288 -0
- simdref/storage.py +504 -0
- simdref/templates/__init__.py +0 -0
- simdref/templates/app.js +1590 -0
- simdref/templates/favicon.svg +5 -0
- simdref/templates/index.html +112 -0
- simdref/templates/logo.svg +12 -0
- simdref/templates/style.css +680 -0
- simdref/tui.py +2366 -0
- simdref/web.py +403 -0
- simdref-0.0.0.dist-info/METADATA +240 -0
- simdref-0.0.0.dist-info/RECORD +44 -0
- simdref-0.0.0.dist-info/WHEEL +5 -0
- simdref-0.0.0.dist-info/entry_points.txt +4 -0
- simdref-0.0.0.dist-info/licenses/LICENSE +674 -0
- simdref-0.0.0.dist-info/top_level.txt +1 -0
simdref/display.py
ADDED
|
@@ -0,0 +1,963 @@
|
|
|
1
|
+
"""Terminal display and formatting for simdref CLI output.
|
|
2
|
+
|
|
3
|
+
This module owns the shared :class:`~rich.console.Console` instance and all
|
|
4
|
+
functions that render intrinsics, instructions, performance tables, and ISA
|
|
5
|
+
metadata to the terminal. The CLI command handlers in :mod:`simdref.cli`
|
|
6
|
+
delegate to these functions for all Rich-based output.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.panel import Panel
|
|
16
|
+
from rich.rule import Rule
|
|
17
|
+
from rich.table import Table
|
|
18
|
+
|
|
19
|
+
from simdref.perf import (
|
|
20
|
+
latency_cycle_values,
|
|
21
|
+
variant_perf_summary,
|
|
22
|
+
variant_perf_summary_labeled,
|
|
23
|
+
)
|
|
24
|
+
from simdref.pdfrefs import normalize_pdf_refs, pdf_ref_label
|
|
25
|
+
from simdref.queries import linked_instruction_records
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
import sqlite3
|
|
29
|
+
|
|
30
|
+
from simdref.models import Catalog, InstructionRecord, IntrinsicRecord
|
|
31
|
+
from simdref.search import SearchResult
|
|
32
|
+
|
|
33
|
+
console = Console()
|
|
34
|
+
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
# Microarchitecture constants
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
UARCH_ORDER = [
|
|
40
|
+
"ARL-P", "ARL-E", "MTL-P", "MTL-E", "EMR",
|
|
41
|
+
"ADL-P", "ADL-E", "RKL", "TGL", "ICL",
|
|
42
|
+
"CLX", "CNL", "SKX", "CFL", "KBL", "SKL",
|
|
43
|
+
"BDW", "HSW", "IVB", "SNB",
|
|
44
|
+
"ZEN5", "ZEN4", "ZEN3", "ZEN2", "ZEN+",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
UARCH_LABELS: dict[str, tuple[str, str]] = {
|
|
48
|
+
"ARL-P": ("Arrow Lake-P", "2024"),
|
|
49
|
+
"ARL-E": ("Arrow Lake-E", "2024"),
|
|
50
|
+
"MTL-P": ("Meteor Lake-P", "2023"),
|
|
51
|
+
"MTL-E": ("Meteor Lake-E", "2023"),
|
|
52
|
+
"EMR": ("Emerald Rapids", "2023"),
|
|
53
|
+
"ADL-P": ("Alder Lake-P", "2021"),
|
|
54
|
+
"ADL-E": ("Alder Lake-E", "2021"),
|
|
55
|
+
"RKL": ("Rocket Lake", "2021"),
|
|
56
|
+
"TGL": ("Tiger Lake", "2020"),
|
|
57
|
+
"ICL": ("Ice Lake", "2019"),
|
|
58
|
+
"CLX": ("Cascade Lake", "2019"),
|
|
59
|
+
"CNL": ("Cannon Lake", "2018"),
|
|
60
|
+
"SKX": ("Skylake-X", "2017"),
|
|
61
|
+
"CFL": ("Coffee Lake", "2017"),
|
|
62
|
+
"KBL": ("Kaby Lake", "2016"),
|
|
63
|
+
"SKL": ("Skylake", "2015"),
|
|
64
|
+
"BDW": ("Broadwell", "2014"),
|
|
65
|
+
"HSW": ("Haswell", "2013"),
|
|
66
|
+
"IVB": ("Ivy Bridge", "2012"),
|
|
67
|
+
"SNB": ("Sandy Bridge", "2011"),
|
|
68
|
+
"ZEN5": ("Zen 5", "2024"),
|
|
69
|
+
"ZEN4": ("Zen 4", "2022"),
|
|
70
|
+
"ZEN3": ("Zen 3", "2020"),
|
|
71
|
+
"ZEN2": ("Zen 2", "2019"),
|
|
72
|
+
"ZEN+": ("Zen+", "2018"),
|
|
73
|
+
"AMT": ("Atom", "2012"),
|
|
74
|
+
"BNL": ("Bonnell", "2008"),
|
|
75
|
+
"CON": ("Conroe", "2006"),
|
|
76
|
+
"GLM": ("Goldmont", "2016"),
|
|
77
|
+
"GLP": ("Goldmont Plus", "2017"),
|
|
78
|
+
"NHM": ("Nehalem", "2008"),
|
|
79
|
+
"TRM": ("Tremont", "2019"),
|
|
80
|
+
"WOL": ("Wolfdale", "2007"),
|
|
81
|
+
"WSM": ("Westmere", "2010"),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# ISA chronological ordering for sort keys.
|
|
85
|
+
ISA_CHRONOLOGY: dict[str, tuple[int, int]] = {
|
|
86
|
+
"I86": (0, 0), "MMX": (1, 0),
|
|
87
|
+
"SSE": (2, 0), "SSE2": (2, 1), "SSE3": (2, 2), "SSSE3": (2, 3),
|
|
88
|
+
"SSE4A": (2, 4), "SSE4.1": (2, 5), "SSE4.2": (2, 6),
|
|
89
|
+
"AES": (2, 7), "PCLMULQDQ": (2, 8),
|
|
90
|
+
"F16C": (3, 0), "FMA": (3, 1),
|
|
91
|
+
"AVX": (4, 0), "NEON": (4, 1), "AVX2": (5, 0),
|
|
92
|
+
"AVX512F": (6, 0), "AVX512DQ": (6, 1), "AVX512IFMA": (6, 2),
|
|
93
|
+
"AVX512PF": (6, 3), "AVX512ER": (6, 4), "AVX512CD": (6, 5),
|
|
94
|
+
"AVX512BW": (6, 6), "AVX512VL": (6, 7), "AVX512VBMI": (6, 8),
|
|
95
|
+
"AVX512VBMI2": (6, 9), "AVX512VNNI": (6, 10), "AVX512BITALG": (6, 11),
|
|
96
|
+
"AVX512VPOPCNTDQ": (6, 12), "AVX5124VNNIW": (6, 13),
|
|
97
|
+
"AVX5124FMAPS": (6, 14), "AVX512VP2INTERSECT": (6, 15),
|
|
98
|
+
"AVX512BF16": (6, 16), "AVX512FP16": (6, 99),
|
|
99
|
+
"AVX10": (7, 0), "AMX": (7, 0), "SVE": (8, 0), "SVE2": (8, 1), "V": (8, 2), "ZVE": (8, 3), "ZV": (8, 4), "APX": (9, 0),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
_ISA_PREFIXES_BY_LEN = sorted(ISA_CHRONOLOGY.items(), key=lambda kv: len(kv[0]), reverse=True)
|
|
103
|
+
|
|
104
|
+
# ISA family/sub-ISA constants live in simdref.filters (single source of truth).
|
|
105
|
+
# Re-exported here for back-compat with TUI, web export, and external callers.
|
|
106
|
+
from simdref.filters import ( # noqa: E402 (re-export)
|
|
107
|
+
DEFAULT_ENABLED_ISAS,
|
|
108
|
+
DEFAULT_SUBS,
|
|
109
|
+
FAMILY_SUB_ORDER,
|
|
110
|
+
ISA_FAMILY_ORDER,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
X86_BASE_ISAS: frozenset[str] = frozenset({
|
|
114
|
+
"I86", "I186", "I286", "I386", "I486", "I586", "X87", "CMOV",
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
# Compiled patterns (simple helpers avoid regex where possible)
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
_NORMALIZE_TEXT_RE = re.compile(r"[^a-z0-9]+")
|
|
122
|
+
|
|
123
|
+
# Matches display-only instruction decorators like ``{evex}``, ``{load}``,
|
|
124
|
+
# ``{disp8}``, etc. at the beginning of a form/mnemonic.
|
|
125
|
+
_LEADING_INSTR_TAG_RE = re.compile(r"^(?:\s*\{[^}]+\}\s*)+")
|
|
126
|
+
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
# Microarchitecture helpers
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def uarch_sort_key(name: str) -> tuple[int, str]:
|
|
133
|
+
"""Sort key that places known architectures in chronological order."""
|
|
134
|
+
try:
|
|
135
|
+
return (UARCH_ORDER.index(name), name)
|
|
136
|
+
except ValueError:
|
|
137
|
+
return (len(UARCH_ORDER), name)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _uarch_display_mode() -> str:
|
|
141
|
+
width = console.size.width
|
|
142
|
+
if width >= 145:
|
|
143
|
+
return "full"
|
|
144
|
+
if width >= 115:
|
|
145
|
+
return "year"
|
|
146
|
+
return "short"
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def display_uarch(name: str, mode: str | None = None) -> str:
|
|
150
|
+
"""Format a microarchitecture code for display."""
|
|
151
|
+
if not name:
|
|
152
|
+
return "-"
|
|
153
|
+
mode = mode or _uarch_display_mode()
|
|
154
|
+
label = UARCH_LABELS.get(name)
|
|
155
|
+
if label is None:
|
|
156
|
+
return name
|
|
157
|
+
full_name, year = label
|
|
158
|
+
if mode == "full":
|
|
159
|
+
return f"{full_name} ({year})"
|
|
160
|
+
if mode == "year":
|
|
161
|
+
return f"{name} ({year})"
|
|
162
|
+
return name
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _uarch_display_mode_for_table(rows: list[dict], columns: list[str], label_map: dict[str, str]) -> str:
|
|
166
|
+
if "uarch" not in columns:
|
|
167
|
+
return "short"
|
|
168
|
+
available = console.size.width - 8
|
|
169
|
+
sample_rows = rows[:64]
|
|
170
|
+
for column in columns:
|
|
171
|
+
if column == "uarch":
|
|
172
|
+
continue
|
|
173
|
+
header = label_map.get(column, column)
|
|
174
|
+
max_cell = max((len(str(row.get(column, "-"))) for row in sample_rows), default=1)
|
|
175
|
+
available -= min(max(len(header), max_cell), 22) + 3
|
|
176
|
+
uarch_names = [str(row.get("uarch", "-")) for row in sample_rows]
|
|
177
|
+
full_needed = max((len(display_uarch(name, mode="full")) for name in uarch_names), default=0)
|
|
178
|
+
year_needed = max((len(display_uarch(name, mode="year")) for name in uarch_names), default=0)
|
|
179
|
+
if available >= full_needed + 2:
|
|
180
|
+
return "full"
|
|
181
|
+
if available >= year_needed + 2:
|
|
182
|
+
return "year"
|
|
183
|
+
return "short"
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _column_width_budget(rows: list[dict], columns: list[str], label_map: dict[str, str], uarch_mode: str) -> dict[str, int]:
|
|
187
|
+
"""Compute column widths that fit the terminal, shrinking least important first."""
|
|
188
|
+
sample_rows = rows[:64]
|
|
189
|
+
desired: dict[str, int] = {}
|
|
190
|
+
minimum: dict[str, int] = {}
|
|
191
|
+
for column in columns:
|
|
192
|
+
header = label_map.get(column, column)
|
|
193
|
+
if column == "uarch":
|
|
194
|
+
values = [display_uarch(str(row.get("uarch", "-")), mode=uarch_mode) for row in sample_rows]
|
|
195
|
+
desired[column] = max([len(header), *[len(v) for v in values]] or [len(header)])
|
|
196
|
+
minimum[column] = 5
|
|
197
|
+
continue
|
|
198
|
+
values = [str(row.get(column, "-")) for row in sample_rows]
|
|
199
|
+
widest = max([len(header), *[len(v) for v in values]] or [len(header)])
|
|
200
|
+
desired[column] = min(widest, 34 if column == "ports" else 22)
|
|
201
|
+
if column == "ports":
|
|
202
|
+
minimum[column] = 10
|
|
203
|
+
elif column.startswith("TP") or column.startswith("cycle/") or column == "latency":
|
|
204
|
+
minimum[column] = 8
|
|
205
|
+
elif column == "uops":
|
|
206
|
+
minimum[column] = 4
|
|
207
|
+
else:
|
|
208
|
+
minimum[column] = min(max(len(header), 4), 10)
|
|
209
|
+
|
|
210
|
+
separator_cost = max(0, (len(columns) - 1) * 3)
|
|
211
|
+
available = max(40, console.size.width - 8 - separator_cost)
|
|
212
|
+
current = sum(desired.values())
|
|
213
|
+
if current <= available:
|
|
214
|
+
return desired
|
|
215
|
+
|
|
216
|
+
widths = dict(desired)
|
|
217
|
+
shrink_order = ["ports", "TP_unrolled", "TP_loop", "TP_ports", "TP", "latency", "uarch", "uops"]
|
|
218
|
+
shrink_order += [c for c in columns if c not in shrink_order]
|
|
219
|
+
deficit = current - available
|
|
220
|
+
while deficit > 0:
|
|
221
|
+
changed = False
|
|
222
|
+
for column in shrink_order:
|
|
223
|
+
if column not in widths:
|
|
224
|
+
continue
|
|
225
|
+
if widths[column] > minimum[column]:
|
|
226
|
+
widths[column] -= 1
|
|
227
|
+
deficit -= 1
|
|
228
|
+
changed = True
|
|
229
|
+
if deficit <= 0:
|
|
230
|
+
break
|
|
231
|
+
if not changed:
|
|
232
|
+
break
|
|
233
|
+
return widths
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
# ---------------------------------------------------------------------------
|
|
237
|
+
# ISA display helpers
|
|
238
|
+
# ---------------------------------------------------------------------------
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def strip_instruction_decorators(text: str) -> str:
|
|
242
|
+
"""Remove display-only leading decorators and EVEX suffix markers."""
|
|
243
|
+
result = text or ""
|
|
244
|
+
while True:
|
|
245
|
+
stripped = _LEADING_INSTR_TAG_RE.sub("", result, count=1)
|
|
246
|
+
if stripped == result:
|
|
247
|
+
break
|
|
248
|
+
result = stripped
|
|
249
|
+
return result.replace("_EVEX", "").strip()
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def display_isa(values: list[str]) -> str:
|
|
253
|
+
"""Normalize and deduplicate ISA extension names for display."""
|
|
254
|
+
def normalize_token(value: str) -> str:
|
|
255
|
+
# Strip width and scalar suffixes.
|
|
256
|
+
base = value
|
|
257
|
+
for suffix in ("_128", "_256", "_512", "_SCALAR"):
|
|
258
|
+
if base.endswith(suffix):
|
|
259
|
+
base = base[: -len(suffix)]
|
|
260
|
+
upper = base.upper()
|
|
261
|
+
if upper in {"ADV_SIMD", "ADVSIMD", "NEON"}:
|
|
262
|
+
return "NEON"
|
|
263
|
+
if upper.startswith("SVE2"):
|
|
264
|
+
return "SVE2"
|
|
265
|
+
if upper.startswith("SVE"):
|
|
266
|
+
return "SVE"
|
|
267
|
+
if upper == "AARCH64":
|
|
268
|
+
return "AArch64"
|
|
269
|
+
if upper.startswith("AVX10_"):
|
|
270
|
+
parts = base.split("_")
|
|
271
|
+
if len(parts) >= 2:
|
|
272
|
+
version = parts[1]
|
|
273
|
+
suffix_str = " ".join(parts[2:]) if len(parts) > 2 else ""
|
|
274
|
+
return f"AVX10.{version}{(' ' + suffix_str) if suffix_str else ''}"
|
|
275
|
+
if upper.startswith("AVX512"):
|
|
276
|
+
tail = base[len("AVX512"):]
|
|
277
|
+
if tail.startswith("_"):
|
|
278
|
+
return f"AVX512 {tail[1:].replace('_', ' ')}"
|
|
279
|
+
return f"AVX512{tail}"
|
|
280
|
+
if upper.startswith("AMX_"):
|
|
281
|
+
return f"AMX-{base.split('_', 1)[1].replace('_', '-')}"
|
|
282
|
+
return base
|
|
283
|
+
|
|
284
|
+
rendered: list[str] = []
|
|
285
|
+
seen: set[str] = set()
|
|
286
|
+
for value in values:
|
|
287
|
+
normalized = normalize_token(value)
|
|
288
|
+
if normalized not in seen:
|
|
289
|
+
seen.add(normalized)
|
|
290
|
+
rendered.append(normalized)
|
|
291
|
+
return ", ".join(rendered) or "-"
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def display_architecture(architecture: str) -> str:
|
|
295
|
+
return {
|
|
296
|
+
"x86": "x86",
|
|
297
|
+
"arm": "Arm",
|
|
298
|
+
"riscv": "RISC-V",
|
|
299
|
+
}.get((architecture or "").lower(), architecture or "-")
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def normalize_isa_token(isa: str) -> str:
|
|
303
|
+
"""Normalize ISA tokens for family and sub-ISA matching."""
|
|
304
|
+
return isa.upper().replace(" ", "").replace("-", "").replace("_", "").replace(".", "")
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def isa_family(isa: str) -> str:
|
|
308
|
+
"""Map a single ISA token to its high-level family."""
|
|
309
|
+
d = normalize_isa_token(display_isa([isa]) or isa)
|
|
310
|
+
if not d or d == "-":
|
|
311
|
+
return "Other"
|
|
312
|
+
if d in X86_BASE_ISAS or d.startswith("BMI") or d in ("ADX", "AES", "PCLMULQDQ", "CRC32"):
|
|
313
|
+
return "x86"
|
|
314
|
+
if d == "SVML":
|
|
315
|
+
return "SVML"
|
|
316
|
+
if d in {"AARCH64", "NEON"} or d.startswith("SVE"):
|
|
317
|
+
return "Arm"
|
|
318
|
+
if d == "V" or d.startswith("RV") or d.startswith("ZV") or d.startswith("ZVE"):
|
|
319
|
+
return "RISC-V"
|
|
320
|
+
if d.startswith("APX"):
|
|
321
|
+
return "APX"
|
|
322
|
+
if d.startswith("AMX"):
|
|
323
|
+
return "AMX"
|
|
324
|
+
if d.startswith("AVX10"):
|
|
325
|
+
return "AVX10"
|
|
326
|
+
if d.startswith("AVX512") or d in ("VAES", "VPCLMULQDQ", "GFNI"):
|
|
327
|
+
return "AVX-512"
|
|
328
|
+
if d in ("AVX", "AVX2", "AVX2GATHER") or d.startswith("AVX") or d in ("FMA", "FMA4", "F16C", "XOP"):
|
|
329
|
+
return "AVX"
|
|
330
|
+
if d.startswith("SSE") or d.startswith("SSSE"):
|
|
331
|
+
return "SSE"
|
|
332
|
+
if d.startswith("MMX") or d in ("3DNOW", "PENTIUMMMX"):
|
|
333
|
+
return "MMX"
|
|
334
|
+
return "Other"
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def isa_families(values: list[str]) -> list[str]:
|
|
338
|
+
"""Deduplicated ISA families for a list of ISA tokens."""
|
|
339
|
+
seen: set[str] = set()
|
|
340
|
+
families: list[str] = []
|
|
341
|
+
for value in values:
|
|
342
|
+
family = isa_family(value)
|
|
343
|
+
if family and family not in seen:
|
|
344
|
+
seen.add(family)
|
|
345
|
+
families.append(family)
|
|
346
|
+
return families
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def isa_to_sub_isa(raw_isa: str) -> str | None:
|
|
350
|
+
"""Map a raw ISA token to its configured family sub-ISA.
|
|
351
|
+
|
|
352
|
+
Longest matching sub wins so that ``SSE2``/``SSE4.1`` don't collapse
|
|
353
|
+
to ``SSE`` just because SSE appears earlier in FAMILY_SUB_ORDER.
|
|
354
|
+
"""
|
|
355
|
+
family = isa_family(raw_isa)
|
|
356
|
+
subs = FAMILY_SUB_ORDER.get(family)
|
|
357
|
+
if not subs:
|
|
358
|
+
return None
|
|
359
|
+
normalized = normalize_isa_token(display_isa([raw_isa]) or raw_isa)
|
|
360
|
+
# Exact match first, then longest-prefix match.
|
|
361
|
+
for sub in subs:
|
|
362
|
+
if normalize_isa_token(sub) == normalized:
|
|
363
|
+
return sub
|
|
364
|
+
best: str | None = None
|
|
365
|
+
best_len = -1
|
|
366
|
+
for sub in subs:
|
|
367
|
+
candidate = normalize_isa_token(sub)
|
|
368
|
+
if normalized.startswith(candidate) and len(candidate) > best_len:
|
|
369
|
+
best = sub
|
|
370
|
+
best_len = len(candidate)
|
|
371
|
+
return best
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def isa_sort_key(values: list[str]) -> tuple[int, int, str]:
|
|
375
|
+
"""Chronological sort key for a list of ISA extensions."""
|
|
376
|
+
display = display_isa(values)
|
|
377
|
+
normalized_values = [display_isa([v]).upper() for v in values] or [display.upper()]
|
|
378
|
+
|
|
379
|
+
def isa_rank(value: str) -> tuple[int, int]:
|
|
380
|
+
compact = value.replace(" ", "")
|
|
381
|
+
for prefix, rank in _ISA_PREFIXES_BY_LEN:
|
|
382
|
+
if compact.startswith(prefix):
|
|
383
|
+
return rank
|
|
384
|
+
for family in ("AVX10", "AMX", "APX"):
|
|
385
|
+
if compact.startswith(family):
|
|
386
|
+
return ISA_CHRONOLOGY[family]
|
|
387
|
+
return (8, 0)
|
|
388
|
+
|
|
389
|
+
return min((isa_rank(v) for v in normalized_values), default=(8, 0)) + (display,)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def is_apx_isa(values: list[str]) -> bool:
|
|
393
|
+
return display_isa(values).upper().startswith("APX")
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def is_fp16_or_bf16_isa(values: list[str]) -> bool:
|
|
397
|
+
display = display_isa(values).upper()
|
|
398
|
+
return "FP16" in display or "BF16" in display
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def isa_visible(values: list[str], show_fp16: bool = False) -> bool:
|
|
402
|
+
"""Whether an ISA variant should be shown (hides APX and optionally FP16/BF16)."""
|
|
403
|
+
if is_apx_isa(values):
|
|
404
|
+
return False
|
|
405
|
+
if not show_fp16 and is_fp16_or_bf16_isa(values):
|
|
406
|
+
return False
|
|
407
|
+
return True
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
# ---------------------------------------------------------------------------
|
|
411
|
+
# Instruction text helpers
|
|
412
|
+
# ---------------------------------------------------------------------------
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def canonical_url(path: str) -> str:
|
|
416
|
+
if not path:
|
|
417
|
+
return ""
|
|
418
|
+
if path.startswith("http"):
|
|
419
|
+
return path
|
|
420
|
+
return f"https://www.{path.lstrip('/')}"
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def instruction_query_text(item) -> str:
|
|
424
|
+
"""Build a clean query string from an instruction's form."""
|
|
425
|
+
form = strip_instruction_decorators(item.form or "")
|
|
426
|
+
name = strip_instruction_decorators(item.mnemonic or "")
|
|
427
|
+
if "(" in form:
|
|
428
|
+
form_name = form.split("(", 1)[0].strip()
|
|
429
|
+
if form_name:
|
|
430
|
+
name = form_name
|
|
431
|
+
form = form[len(form_name):].strip()
|
|
432
|
+
elif form.casefold().startswith(item.mnemonic.casefold()):
|
|
433
|
+
form = form[len(item.mnemonic):].strip()
|
|
434
|
+
if form.startswith("(") and form.endswith(")"):
|
|
435
|
+
form = form[1:-1]
|
|
436
|
+
tokens = [t for t in re.split(r"[\s,()]+", form) if t]
|
|
437
|
+
return " ".join([name, *tokens]).strip()
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def display_instruction_form(form: str) -> str:
|
|
441
|
+
return strip_instruction_decorators(form or "") or "-"
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def display_instruction_title(item) -> str:
|
|
445
|
+
return display_instruction_form(item.key)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def normalize_instruction_query(value: str) -> str:
|
|
449
|
+
"""Normalize an instruction query to lowercase alphanumeric tokens."""
|
|
450
|
+
text = value.replace("_", " ").replace(",", " ").replace("{", " ").replace("}", " ")
|
|
451
|
+
tokens = []
|
|
452
|
+
for ch in text:
|
|
453
|
+
if ch.isalnum():
|
|
454
|
+
tokens.append(ch.lower())
|
|
455
|
+
elif tokens and tokens[-1] != " ":
|
|
456
|
+
tokens.append(" ")
|
|
457
|
+
return "".join(tokens).strip()
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def natural_query_sort_key(value: str) -> tuple[object, ...]:
|
|
461
|
+
"""Sort key that orders numbers numerically within text."""
|
|
462
|
+
parts = re.findall(r"[A-Za-z]+|\d+", value.casefold())
|
|
463
|
+
key: list[object] = []
|
|
464
|
+
for part in parts:
|
|
465
|
+
if part.isdigit():
|
|
466
|
+
key.append((1, int(part)))
|
|
467
|
+
else:
|
|
468
|
+
key.append((0, part))
|
|
469
|
+
return tuple(key)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def instruction_variant_items(items):
|
|
473
|
+
"""Sort instruction variants by ISA chronology then natural form order."""
|
|
474
|
+
return sorted(
|
|
475
|
+
items,
|
|
476
|
+
key=lambda item: (
|
|
477
|
+
isa_sort_key(item.isa),
|
|
478
|
+
natural_query_sort_key(instruction_query_text(item)),
|
|
479
|
+
item.key,
|
|
480
|
+
),
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def normalized_sentence(text: str) -> str:
|
|
485
|
+
return _NORMALIZE_TEXT_RE.sub(" ", text.casefold()).strip()
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
# ---------------------------------------------------------------------------
|
|
489
|
+
# Row extraction helpers
|
|
490
|
+
# ---------------------------------------------------------------------------
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def measurement_rows(item) -> list[dict]:
|
|
494
|
+
"""Extract per-microarchitecture measurement rows from an instruction.
|
|
495
|
+
|
|
496
|
+
Each row carries a ``source`` column (``measured`` / ``modeled``) so
|
|
497
|
+
downstream renderers can show provenance without re-reading the
|
|
498
|
+
``arch_details`` entry.
|
|
499
|
+
"""
|
|
500
|
+
rows: list[dict] = []
|
|
501
|
+
for arch, details in item.arch_details.items():
|
|
502
|
+
measurement = details.get("measurement") or {}
|
|
503
|
+
if measurement:
|
|
504
|
+
row = {"uarch": arch, **measurement}
|
|
505
|
+
values = latency_cycle_values(details.get("latencies") or [])
|
|
506
|
+
if values:
|
|
507
|
+
row["latency"] = values[0]
|
|
508
|
+
row["source"] = details.get("source_kind") or "measured"
|
|
509
|
+
rows.append(row)
|
|
510
|
+
return rows
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def doc_rows(item) -> list[dict]:
|
|
514
|
+
rows: list[dict] = []
|
|
515
|
+
for arch, details in item.arch_details.items():
|
|
516
|
+
doc = details.get("doc") or {}
|
|
517
|
+
if doc:
|
|
518
|
+
rows.append({"uarch": arch, **doc})
|
|
519
|
+
return rows
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def iaca_rows(item) -> list[dict]:
|
|
523
|
+
rows: list[dict] = []
|
|
524
|
+
for arch, details in item.arch_details.items():
|
|
525
|
+
for iaca in details.get("iaca") or []:
|
|
526
|
+
rows.append({"uarch": arch, **iaca})
|
|
527
|
+
return rows
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def latency_rows(item) -> list[dict]:
|
|
531
|
+
rows: list[dict] = []
|
|
532
|
+
for arch, details in item.arch_details.items():
|
|
533
|
+
values = latency_cycle_values(details.get("latencies") or [])
|
|
534
|
+
if values:
|
|
535
|
+
rows.append({"uarch": arch, "cycles": ", ".join(values)})
|
|
536
|
+
return rows
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
# ---------------------------------------------------------------------------
|
|
540
|
+
# Rich table printing
|
|
541
|
+
# ---------------------------------------------------------------------------
|
|
542
|
+
|
|
543
|
+
_GENERIC_TABLE_LABEL_MAP = {
|
|
544
|
+
"uarch": "microarch",
|
|
545
|
+
"latency": "LAT",
|
|
546
|
+
"TP_loop": "CPI",
|
|
547
|
+
"TP_unrolled": "CPI unroll",
|
|
548
|
+
"TP_ports": "CPI ports",
|
|
549
|
+
"TP": "CPI",
|
|
550
|
+
"TP_no_interiteration": "cycle/instr (no interiteration)",
|
|
551
|
+
"kind": "op",
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
_PERF_PANEL_ORDER = ("measured", "modeled")
|
|
556
|
+
_PERF_PANEL_TITLES = {
|
|
557
|
+
"measured": "perf (measured)",
|
|
558
|
+
"modeled": "perf (modeled)",
|
|
559
|
+
}
|
|
560
|
+
_PERF_PANEL_BORDER = {
|
|
561
|
+
"measured": "green",
|
|
562
|
+
"modeled": "yellow",
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def split_perf_rows(rows: list[dict]) -> list[tuple[str, list[dict]]]:
|
|
567
|
+
"""Group measurement rows by ``source`` kind in a stable order.
|
|
568
|
+
|
|
569
|
+
Returns ``[(kind, rows), ...]`` with measured first, then modeled, so
|
|
570
|
+
callers can render one panel per kind without re-sorting. Rows
|
|
571
|
+
missing a ``source`` field fall under ``"measured"`` for back-compat
|
|
572
|
+
with older arch_details entries.
|
|
573
|
+
"""
|
|
574
|
+
buckets: dict[str, list[dict]] = {}
|
|
575
|
+
for row in rows:
|
|
576
|
+
kind = row.get("source") or "measured"
|
|
577
|
+
buckets.setdefault(kind, []).append(row)
|
|
578
|
+
ordered = [(k, buckets[k]) for k in _PERF_PANEL_ORDER if k in buckets]
|
|
579
|
+
# Any unexpected kinds (e.g. a new source kind) appear after the
|
|
580
|
+
# known ones, sorted for determinism.
|
|
581
|
+
for kind in sorted(buckets):
|
|
582
|
+
if kind not in _PERF_PANEL_ORDER:
|
|
583
|
+
ordered.append((kind, buckets[kind]))
|
|
584
|
+
return ordered
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def perf_panel_title(kind: str) -> str:
|
|
588
|
+
"""Human-readable panel title for a given source kind."""
|
|
589
|
+
return _PERF_PANEL_TITLES.get(kind, f"perf ({kind})")
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def perf_panel_border(kind: str) -> str:
|
|
593
|
+
"""Rich border style that distinguishes measured from modeled panels."""
|
|
594
|
+
return _PERF_PANEL_BORDER.get(kind, "green")
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
def print_generic_table(
|
|
600
|
+
rows: list[dict],
|
|
601
|
+
title: str,
|
|
602
|
+
preferred_order: list[str] | None = None,
|
|
603
|
+
border_style: str = "green",
|
|
604
|
+
exclude_keys: set[str] | None = None,
|
|
605
|
+
include_extras: bool = True,
|
|
606
|
+
) -> None:
|
|
607
|
+
"""Render a performance data table inside a Rich Panel."""
|
|
608
|
+
if not rows:
|
|
609
|
+
return
|
|
610
|
+
preferred_order = preferred_order or []
|
|
611
|
+
exclude_keys = exclude_keys or set()
|
|
612
|
+
keys = [k for k in preferred_order if any(k in row for row in rows)]
|
|
613
|
+
extras = sorted({k for row in rows for k in row if k not in keys and k not in exclude_keys}) if include_extras else []
|
|
614
|
+
columns = keys + extras
|
|
615
|
+
table = Table(header_style=f"bold {border_style}", expand=True)
|
|
616
|
+
uarch_mode = _uarch_display_mode_for_table(rows, columns, _GENERIC_TABLE_LABEL_MAP)
|
|
617
|
+
widths = _column_width_budget(rows, columns, _GENERIC_TABLE_LABEL_MAP, uarch_mode)
|
|
618
|
+
for column in columns:
|
|
619
|
+
label = _GENERIC_TABLE_LABEL_MAP.get(column, column)
|
|
620
|
+
if column == "uarch":
|
|
621
|
+
table.add_column(label, no_wrap=True, width=widths[column])
|
|
622
|
+
elif column == "ports":
|
|
623
|
+
table.add_column(label, no_wrap=True, width=widths[column], overflow="ellipsis")
|
|
624
|
+
else:
|
|
625
|
+
table.add_column(label, width=widths[column], overflow="fold")
|
|
626
|
+
|
|
627
|
+
def row_sort(row: dict):
|
|
628
|
+
return uarch_sort_key(row.get("uarch", "")) if "uarch" in row else ("",)
|
|
629
|
+
|
|
630
|
+
for row in sorted(rows, key=row_sort):
|
|
631
|
+
rendered = []
|
|
632
|
+
for column in columns:
|
|
633
|
+
value = row.get(column, "-")
|
|
634
|
+
if column == "uarch":
|
|
635
|
+
rendered.append(display_uarch(str(value), mode=uarch_mode))
|
|
636
|
+
else:
|
|
637
|
+
rendered.append(str(value))
|
|
638
|
+
table.add_row(*rendered)
|
|
639
|
+
console.print(Panel(table, title=title, border_style=border_style))
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
def print_perf_tables(rows: list[dict]) -> None:
|
|
643
|
+
"""Render one Rich panel per source kind (measured vs modeled).
|
|
644
|
+
|
|
645
|
+
Splits *rows* on the ``source`` column and prints each group as its
|
|
646
|
+
own table with a distinct title and border — no ``source`` column
|
|
647
|
+
inside the table itself, since the panel header already names it.
|
|
648
|
+
"""
|
|
649
|
+
for kind, group in split_perf_rows(rows):
|
|
650
|
+
print_generic_table(
|
|
651
|
+
group,
|
|
652
|
+
perf_panel_title(kind),
|
|
653
|
+
preferred_order=_MEASUREMENT_PREFERRED_ORDER,
|
|
654
|
+
border_style=perf_panel_border(kind),
|
|
655
|
+
exclude_keys=_MEASUREMENT_EXCLUDE_KEYS,
|
|
656
|
+
include_extras=False,
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
def print_operand_block(item) -> None:
|
|
661
|
+
"""Render an operand details table."""
|
|
662
|
+
if not item.operand_details:
|
|
663
|
+
return
|
|
664
|
+
table = Table(header_style="bold blue")
|
|
665
|
+
table.add_column("idx", width=4)
|
|
666
|
+
table.add_column("rw", width=4)
|
|
667
|
+
table.add_column("type", width=8)
|
|
668
|
+
table.add_column("width", width=6)
|
|
669
|
+
table.add_column("xtype", width=8)
|
|
670
|
+
table.add_column("name", width=10)
|
|
671
|
+
for operand in item.operand_details:
|
|
672
|
+
rw = "".join(flag for flag in ("r", "w") if operand.get(flag) == "1")
|
|
673
|
+
table.add_row(
|
|
674
|
+
operand.get("idx", "-"),
|
|
675
|
+
rw or "-",
|
|
676
|
+
operand.get("type", "-"),
|
|
677
|
+
operand.get("width", "-"),
|
|
678
|
+
operand.get("xtype", "-"),
|
|
679
|
+
operand.get("name", "-"),
|
|
680
|
+
)
|
|
681
|
+
console.print(Panel(table, title="operands", border_style="blue"))
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
def print_instruction_mapping(catalog, intrinsic, conn=None) -> None:
|
|
685
|
+
"""Show which instructions implement a given intrinsic."""
|
|
686
|
+
linked = linked_instruction_records(catalog, intrinsic, conn=conn)
|
|
687
|
+
if not linked:
|
|
688
|
+
return
|
|
689
|
+
table = Table(header_style="bold cyan")
|
|
690
|
+
table.add_column("instruction", style="cyan")
|
|
691
|
+
table.add_column("arch", width=6)
|
|
692
|
+
table.add_column("summary")
|
|
693
|
+
table.add_column("isa", width=12)
|
|
694
|
+
for instr in linked:
|
|
695
|
+
table.add_row(instr.key, display_architecture(instr.architecture), instr.summary or "-", ", ".join(instr.isa) or "-")
|
|
696
|
+
console.print(table)
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def print_intrinsic_mapping(catalog, item, conn=None, find_intrinsic_fn=None) -> None:
|
|
700
|
+
"""Show which intrinsics use a given instruction."""
|
|
701
|
+
if not item.linked_intrinsics:
|
|
702
|
+
return
|
|
703
|
+
from simdref.storage import load_intrinsic_from_db
|
|
704
|
+
from simdref.search import find_intrinsic as _find_intrinsic
|
|
705
|
+
|
|
706
|
+
find_fn = find_intrinsic_fn or _find_intrinsic
|
|
707
|
+
table = Table(header_style="bold cyan")
|
|
708
|
+
table.add_column("intrinsic", style="cyan")
|
|
709
|
+
table.add_column("arch", width=6)
|
|
710
|
+
table.add_column("summary")
|
|
711
|
+
table.add_column("isa", width=12)
|
|
712
|
+
for intrinsic_name in item.linked_intrinsics:
|
|
713
|
+
intrinsic = load_intrinsic_from_db(conn, intrinsic_name) if conn is not None else find_fn(catalog, intrinsic_name)
|
|
714
|
+
if intrinsic is None:
|
|
715
|
+
table.add_row(intrinsic_name, "-", "-", "-")
|
|
716
|
+
continue
|
|
717
|
+
table.add_row(
|
|
718
|
+
intrinsic.signature or intrinsic.name,
|
|
719
|
+
display_architecture(intrinsic.architecture),
|
|
720
|
+
intrinsic.description or "-",
|
|
721
|
+
", ".join(intrinsic.isa) or "-",
|
|
722
|
+
)
|
|
723
|
+
console.print(table)
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
_MEASUREMENT_PREFERRED_ORDER = [
|
|
727
|
+
"uarch", "latency", "TP_loop", "TP_ports", "uops", "ports", "kind",
|
|
728
|
+
]
|
|
729
|
+
_MEASUREMENT_EXCLUDE_KEYS = {
|
|
730
|
+
"uops_retire_slots", "uops_MITE", "uops_MS", "macro_fusible", "source",
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
def print_instruction_metadata(item) -> None:
|
|
735
|
+
table = Table(show_header=False, box=None)
|
|
736
|
+
for key, value in instruction_metadata_rows(item):
|
|
737
|
+
table.add_row(key, value)
|
|
738
|
+
console.print(Panel(table, title="instruction metadata", border_style="magenta"))
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
def instruction_metadata_rows(item) -> list[tuple[str, str]]:
|
|
742
|
+
rows: list[tuple[str, str]] = [("summary", item.summary or "-")]
|
|
743
|
+
url = item.metadata.get("url", "")
|
|
744
|
+
if url:
|
|
745
|
+
rows.append(("url", canonical_url(url)))
|
|
746
|
+
if item.metadata.get("url-ref"):
|
|
747
|
+
rows.append(("reference", canonical_url(item.metadata["url-ref"])))
|
|
748
|
+
for ref in normalize_pdf_refs(getattr(item, "pdf_refs", []), item.metadata):
|
|
749
|
+
url = ref.get("url", "")
|
|
750
|
+
label = ref.get("label", "pdf").lower()
|
|
751
|
+
page_label = pdf_ref_label(ref)
|
|
752
|
+
if page_label.casefold() != (ref.get("label", "") or "").casefold():
|
|
753
|
+
rows.append((label, f"{url} [{page_label}]"))
|
|
754
|
+
else:
|
|
755
|
+
rows.append((label, url))
|
|
756
|
+
if item.metadata.get("extension"):
|
|
757
|
+
rows.append(("isa", item.metadata["extension"]))
|
|
758
|
+
if item.metadata.get("category"):
|
|
759
|
+
rows.append(("category", item.metadata["category"]))
|
|
760
|
+
if item.metadata.get("cpl"):
|
|
761
|
+
rows.append(("cpl", item.metadata["cpl"]))
|
|
762
|
+
return rows
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
_DESCRIPTION_ORDER = [
|
|
766
|
+
"Description", "Operation", "Intrinsic Equivalents",
|
|
767
|
+
"Flags Affected", "FPU Flags Affected",
|
|
768
|
+
"Exceptions", "SIMD Floating-Point Exceptions",
|
|
769
|
+
"Floating-Point Exceptions", "x87 FPU and SIMD Floating-Point Exceptions",
|
|
770
|
+
"Numeric Exceptions", "Other Exceptions", "Other Mode Exceptions",
|
|
771
|
+
"Protected Mode Exceptions", "Real-Address Mode Exceptions",
|
|
772
|
+
"Real Address Mode Exceptions",
|
|
773
|
+
"Virtual-8086 Mode Exceptions", "Virtual-8086 Exceptions",
|
|
774
|
+
"Virtual 8086 Mode Exceptions",
|
|
775
|
+
"Compatibility Mode Exceptions", "64-Bit Mode Exceptions",
|
|
776
|
+
]
|
|
777
|
+
|
|
778
|
+
# Sections that are always shown expanded.
|
|
779
|
+
_EXPANDED_SECTIONS: set[str] = set()
|
|
780
|
+
|
|
781
|
+
# Syntax language per code section.
|
|
782
|
+
_CODE_SECTION_LANG = {
|
|
783
|
+
"Operation": "asm",
|
|
784
|
+
"ACLE Operation": "asm",
|
|
785
|
+
"Intrinsic Equivalents": "c",
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
def print_description_sections(
|
|
790
|
+
description: dict[str, str],
|
|
791
|
+
full: bool = False,
|
|
792
|
+
) -> None:
|
|
793
|
+
"""Render instruction description sections as Rich panels.
|
|
794
|
+
|
|
795
|
+
By default, only Description and Flags Affected are expanded.
|
|
796
|
+
Other sections show a collapsed summary line. Pass *full=True*
|
|
797
|
+
to expand everything.
|
|
798
|
+
"""
|
|
799
|
+
if not description:
|
|
800
|
+
return
|
|
801
|
+
shown: set[str] = set()
|
|
802
|
+
for key in _DESCRIPTION_ORDER:
|
|
803
|
+
if key in description:
|
|
804
|
+
expand = full or key in _EXPANDED_SECTIONS
|
|
805
|
+
_print_section(key, description[key], expand=expand)
|
|
806
|
+
shown.add(key)
|
|
807
|
+
for key, value in description.items():
|
|
808
|
+
if key not in shown:
|
|
809
|
+
expand = full or key in _EXPANDED_SECTIONS
|
|
810
|
+
_print_section(key, value, expand=expand)
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
def _print_section(title: str, body: str, expand: bool = True) -> None:
|
|
814
|
+
from rich.syntax import Syntax
|
|
815
|
+
from rich.text import Text
|
|
816
|
+
|
|
817
|
+
if not expand:
|
|
818
|
+
line_count = body.count("\n") + 1
|
|
819
|
+
summary = Text(f" \u25b8 {title} ({line_count} lines)", style="dim")
|
|
820
|
+
console.print(summary)
|
|
821
|
+
return
|
|
822
|
+
|
|
823
|
+
lang = _CODE_SECTION_LANG.get(title)
|
|
824
|
+
if lang:
|
|
825
|
+
syntax = Syntax(body, lang, theme="monokai", word_wrap=True)
|
|
826
|
+
console.print(Panel(syntax, title=title, border_style="dim"))
|
|
827
|
+
else:
|
|
828
|
+
console.print(Panel(body, title=title, border_style="dim"))
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
# ---------------------------------------------------------------------------
|
|
832
|
+
# High-level render functions
|
|
833
|
+
# ---------------------------------------------------------------------------
|
|
834
|
+
|
|
835
|
+
|
|
836
|
+
def render_intrinsic(catalog, item, conn=None, short: bool = False, full: bool = False) -> None:
|
|
837
|
+
"""Render full intrinsic detail view to the terminal."""
|
|
838
|
+
table = Table(show_header=False, box=None)
|
|
839
|
+
table.add_row("signature", item.signature or "-")
|
|
840
|
+
table.add_row("header", item.header or "-")
|
|
841
|
+
if item.url:
|
|
842
|
+
table.add_row("source", item.url)
|
|
843
|
+
table.add_row("architecture", display_architecture(item.architecture))
|
|
844
|
+
table.add_row("isa", ", ".join(item.isa) or "-")
|
|
845
|
+
table.add_row("category", item.category or "-")
|
|
846
|
+
for key in ("reference_url", "argument_preparation", "result", "supported_architectures", "classification_path"):
|
|
847
|
+
if item.metadata.get(key):
|
|
848
|
+
table.add_row(key, item.metadata[key])
|
|
849
|
+
table.add_row("notes", "; ".join(item.notes) or "-")
|
|
850
|
+
linked = linked_instruction_records(catalog, item, conn=conn)
|
|
851
|
+
primary = linked[0] if linked else None
|
|
852
|
+
if primary:
|
|
853
|
+
for key, value in instruction_metadata_rows(primary):
|
|
854
|
+
if key == "summary":
|
|
855
|
+
continue
|
|
856
|
+
table.add_row(key, value)
|
|
857
|
+
console.print(Panel(table, title=f"intrinsic: {item.name}", border_style="cyan"))
|
|
858
|
+
if not short and item.doc_sections:
|
|
859
|
+
print_description_sections(item.doc_sections, full=full)
|
|
860
|
+
if not short and primary and primary.description:
|
|
861
|
+
print_description_sections(primary.description, full=full)
|
|
862
|
+
if linked:
|
|
863
|
+
console.print(Rule("intrinsic to instruction mapping", style="cyan"))
|
|
864
|
+
print_instruction_mapping(catalog, item, conn=conn)
|
|
865
|
+
console.print(Rule(f"instruction details: {display_instruction_title(primary)}", style="magenta"))
|
|
866
|
+
print_operand_block(primary)
|
|
867
|
+
print_perf_tables(measurement_rows(primary))
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
def render_instruction_sections(catalog, item, include_title: bool = True, conn=None, short: bool = False, full: bool = False) -> None:
|
|
871
|
+
"""Render instruction detail with optional title panel."""
|
|
872
|
+
if include_title:
|
|
873
|
+
table = Table(show_header=False, box=None)
|
|
874
|
+
table.add_row("mnemonic", item.mnemonic)
|
|
875
|
+
table.add_row("form", display_instruction_form(item.form))
|
|
876
|
+
table.add_row("architecture", display_architecture(item.architecture))
|
|
877
|
+
table.add_row("isa", display_isa(item.isa))
|
|
878
|
+
for key, value in instruction_metadata_rows(item):
|
|
879
|
+
table.add_row(key, value)
|
|
880
|
+
console.print(Panel(table, title=f"instruction: {display_instruction_title(item)}", border_style="magenta"))
|
|
881
|
+
else:
|
|
882
|
+
print_instruction_metadata(item)
|
|
883
|
+
if not short and item.description:
|
|
884
|
+
print_description_sections(item.description, full=full)
|
|
885
|
+
console.print(Rule("instruction to intrinsic mapping", style="cyan"))
|
|
886
|
+
print_intrinsic_mapping(catalog, item, conn=conn)
|
|
887
|
+
print_operand_block(item)
|
|
888
|
+
print_perf_tables(measurement_rows(item))
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
def render_instruction(catalog, item, conn=None, short: bool = False, full: bool = False) -> None:
|
|
892
|
+
"""Render full instruction detail view."""
|
|
893
|
+
render_instruction_sections(catalog, item, include_title=True, conn=conn, short=short, full=full)
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
def render_instruction_variants(query: str, items, show_fp16: bool = False) -> None:
|
|
897
|
+
"""Render a variant selection table for a mnemonic with multiple forms."""
|
|
898
|
+
all_items = instruction_variant_items(items)
|
|
899
|
+
visible_items = [item for item in all_items if isa_visible(item.isa, show_fp16=show_fp16)]
|
|
900
|
+
items = visible_items or all_items
|
|
901
|
+
table = Table(header_style="bold cyan")
|
|
902
|
+
table.add_column("#", width=3, style="cyan")
|
|
903
|
+
table.add_column("query", style="cyan")
|
|
904
|
+
table.add_column("arch", width=6)
|
|
905
|
+
table.add_column("isa", width=14)
|
|
906
|
+
table.add_column("lat", width=5)
|
|
907
|
+
table.add_column("cpi", width=5)
|
|
908
|
+
table.add_column("summary")
|
|
909
|
+
for index, item in enumerate(items, start=1):
|
|
910
|
+
lat, cpi = variant_perf_summary_labeled(item.arch_details)
|
|
911
|
+
table.add_row(
|
|
912
|
+
str(index),
|
|
913
|
+
instruction_query_text(item),
|
|
914
|
+
display_architecture(item.architecture),
|
|
915
|
+
display_isa(item.isa),
|
|
916
|
+
_label_perf(lat),
|
|
917
|
+
_label_perf(cpi),
|
|
918
|
+
item.summary or "-",
|
|
919
|
+
)
|
|
920
|
+
|
|
921
|
+
|
|
922
|
+
def _label_perf(value) -> str:
|
|
923
|
+
"""Render a :class:`PerfValue` as ``"val (measured, core)"`` for tables.
|
|
924
|
+
|
|
925
|
+
Empty / missing values render as ``"-"`` to match the legacy behaviour
|
|
926
|
+
the unit tests depend on.
|
|
927
|
+
"""
|
|
928
|
+
raw = getattr(value, "value", value)
|
|
929
|
+
if not raw or raw == "-":
|
|
930
|
+
return "-"
|
|
931
|
+
kind = getattr(value, "source_kind", "")
|
|
932
|
+
core = getattr(value, "core", "")
|
|
933
|
+
if kind and core:
|
|
934
|
+
return f"{raw} ({kind[0]}, {core})"
|
|
935
|
+
return str(raw)
|
|
936
|
+
console.print(Panel(table, title=f"instruction variants: {query}", border_style="magenta"))
|
|
937
|
+
base_query = query.split()[0] if query.split() else query
|
|
938
|
+
if items:
|
|
939
|
+
hidden_count = len(all_items) - len(items)
|
|
940
|
+
hidden_note = f" Hidden {hidden_count} APX/FP16/BF16 forms by default." if hidden_count > 0 else ""
|
|
941
|
+
console.print(
|
|
942
|
+
f"[dim]Open one with `simdref {instruction_query_text(items[0])}` or `simdref {base_query} <index>`. Showing {len(items)} forms.{hidden_note}[/dim]"
|
|
943
|
+
)
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
def render_search_results(
|
|
947
|
+
results: list[tuple],
|
|
948
|
+
) -> None:
|
|
949
|
+
"""Render search results table from pre-computed rows.
|
|
950
|
+
|
|
951
|
+
Each element in *results* is ``(SearchResult, arch_str, isa_str, lat, cpi)``.
|
|
952
|
+
"""
|
|
953
|
+
table = Table(show_header=True, header_style="bold cyan", expand=True)
|
|
954
|
+
table.add_column("#", width=3, style="cyan")
|
|
955
|
+
table.add_column("query", width=30, no_wrap=True, overflow="ellipsis")
|
|
956
|
+
table.add_column("Arch", width=6)
|
|
957
|
+
table.add_column("ISA", width=14)
|
|
958
|
+
table.add_column("lat", width=5)
|
|
959
|
+
table.add_column("cpi", width=5)
|
|
960
|
+
table.add_column("summary", min_width=18, overflow="fold")
|
|
961
|
+
for index, (result, arch, isa, lat, cpi) in enumerate(results, start=1):
|
|
962
|
+
table.add_row(str(index), result.title, arch, isa, lat, cpi, result.subtitle)
|
|
963
|
+
console.print(table)
|