simdref 0.0.11__tar.gz → 0.0.13__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.11/src/simdref.egg-info → simdref-0.0.13}/PKG-INFO +6 -6
- {simdref-0.0.11 → simdref-0.0.13}/README.md +5 -5
- {simdref-0.0.11 → simdref-0.0.13}/pyproject.toml +1 -1
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/cli.py +1 -3
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/ingest_catalog.py +6 -3
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/lsp.py +322 -41
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/tui.py +11 -0
- {simdref-0.0.11 → simdref-0.0.13/src/simdref.egg-info}/PKG-INFO +6 -6
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_auto_update_on_version_change.py +5 -4
- simdref-0.0.13/tests/test_lsp_stdio.py +872 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_search.py +17 -1
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_tui.py +47 -0
- simdref-0.0.11/tests/test_lsp_stdio.py +0 -450
- {simdref-0.0.11 → simdref-0.0.13}/LICENSE +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/setup.cfg +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/__init__.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/__main__.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/annotate.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/arm_instructions.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/display.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/export.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/filters.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/ingest.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/ingest_pdf.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/ingest_sources.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/manpages.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/models.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/pdfparse/__init__.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/pdfparse/base.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/pdfparse/intel.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/pdfparse/registry.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/pdfparse/types.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/pdfrefs.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/perf.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/perf_sources/__init__.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/perf_sources/cores.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/perf_sources/llvm_mca.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/perf_sources/llvm_scheduling.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/perf_sources/merge.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/queries.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/riscv.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/search.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/storage.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref/ui_labels.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref.egg-info/SOURCES.txt +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref.egg-info/dependency_links.txt +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref.egg-info/entry_points.txt +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref.egg-info/requires.txt +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/src/simdref.egg-info/top_level.txt +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_annotate.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_annotate_positions.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_audit_coverage.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_cli_bare_query.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_cli_bootstrap_progress.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_cli_help.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_cli_llm.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_coverage_live.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_coverage_parity.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_display.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_export.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_filters.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_ingest_sources.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_intel_operation_and_url.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_issue2_fixes.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_issues_23_24_25.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_lsp_web.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_models.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_pdfparse.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_perf.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_perf_sources.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_presentation.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_preset_persistence.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_riscv_docs_pages.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_search_pushdown.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_source_kind_filter.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_source_validation.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_storage_payload.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_storage_schema.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_ui_labels_parity.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/tests/test_version_matches_pyproject.py +0 -0
- {simdref-0.0.11 → simdref-0.0.13}/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.13
|
|
4
4
|
Summary: Searchable SIMD intrinsic and instruction reference with CLI, manpages, TUI, and LSP.
|
|
5
5
|
Author: Marco
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -67,7 +67,7 @@ isa # open the TUI
|
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
The package installs two equivalent executables, `isa` and `simdref`. The
|
|
70
|
-
catalog download does not
|
|
70
|
+
catalog download does not use `llvm-mca`. Only `isa build` uses
|
|
71
71
|
`llvm-mca` 18+ on `PATH`.
|
|
72
72
|
|
|
73
73
|
Pre-release builds live on TestPyPI:
|
|
@@ -98,7 +98,7 @@ reads the JSON site data this repo exports:
|
|
|
98
98
|
isa export --out-dir ./site-data
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
The LSP server speaks JSON-RPC
|
|
101
|
+
The LSP server speaks JSON-RPC on stdio:
|
|
102
102
|
|
|
103
103
|
```bash
|
|
104
104
|
simdref-lsp
|
|
@@ -114,7 +114,7 @@ Editor clients, one for each editor:
|
|
|
114
114
|
Each client shows a one-line brief at the end of each instruction line and
|
|
115
115
|
the full manpage on hover, from the local catalog.
|
|
116
116
|
|
|
117
|
-
The LLM interface
|
|
117
|
+
The LLM interface writes JSON and NDJSON with exit codes 0 (match), 1 (bad
|
|
118
118
|
flag), 2 (no match), 3 (ambiguous):
|
|
119
119
|
|
|
120
120
|
```bash
|
|
@@ -148,11 +148,11 @@ Commands
|
|
|
148
148
|
| `isa doctor` | Check the install, non-zero exit on failure |
|
|
149
149
|
| `isa update` | Download the pre-built catalog, `--from-release` for the GitHub Release |
|
|
150
150
|
| `isa annotate <file.s>` | Annotate a `.s` file with summaries and latency/CPI, writes `<file>.sa` |
|
|
151
|
-
| `isa man <name>` | Show a manpage,
|
|
151
|
+
| `isa man <name>` | Show a manpage, made on demand |
|
|
152
152
|
| `isa install-manpages` | Pre-generate man7 pages so plain `man vpaddd` works |
|
|
153
153
|
| `isa llm query <q>` | Strict lookup to JSON, NDJSON, or Markdown |
|
|
154
154
|
| `isa llm batch` | Resolve many queries from stdin in one invocation |
|
|
155
|
-
| `isa llm list` |
|
|
155
|
+
| `isa llm list` | Write the FilterSpec or stream matching catalog entries |
|
|
156
156
|
| `isa llm schema` | Print the JSON schema for `llm` payloads |
|
|
157
157
|
|
|
158
158
|
Dev commands
|
|
@@ -33,7 +33,7 @@ isa # open the TUI
|
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
The package installs two equivalent executables, `isa` and `simdref`. The
|
|
36
|
-
catalog download does not
|
|
36
|
+
catalog download does not use `llvm-mca`. Only `isa build` uses
|
|
37
37
|
`llvm-mca` 18+ on `PATH`.
|
|
38
38
|
|
|
39
39
|
Pre-release builds live on TestPyPI:
|
|
@@ -64,7 +64,7 @@ reads the JSON site data this repo exports:
|
|
|
64
64
|
isa export --out-dir ./site-data
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
The LSP server speaks JSON-RPC
|
|
67
|
+
The LSP server speaks JSON-RPC on stdio:
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
70
|
simdref-lsp
|
|
@@ -80,7 +80,7 @@ Editor clients, one for each editor:
|
|
|
80
80
|
Each client shows a one-line brief at the end of each instruction line and
|
|
81
81
|
the full manpage on hover, from the local catalog.
|
|
82
82
|
|
|
83
|
-
The LLM interface
|
|
83
|
+
The LLM interface writes JSON and NDJSON with exit codes 0 (match), 1 (bad
|
|
84
84
|
flag), 2 (no match), 3 (ambiguous):
|
|
85
85
|
|
|
86
86
|
```bash
|
|
@@ -114,11 +114,11 @@ Commands
|
|
|
114
114
|
| `isa doctor` | Check the install, non-zero exit on failure |
|
|
115
115
|
| `isa update` | Download the pre-built catalog, `--from-release` for the GitHub Release |
|
|
116
116
|
| `isa annotate <file.s>` | Annotate a `.s` file with summaries and latency/CPI, writes `<file>.sa` |
|
|
117
|
-
| `isa man <name>` | Show a manpage,
|
|
117
|
+
| `isa man <name>` | Show a manpage, made on demand |
|
|
118
118
|
| `isa install-manpages` | Pre-generate man7 pages so plain `man vpaddd` works |
|
|
119
119
|
| `isa llm query <q>` | Strict lookup to JSON, NDJSON, or Markdown |
|
|
120
120
|
| `isa llm batch` | Resolve many queries from stdin in one invocation |
|
|
121
|
-
| `isa llm list` |
|
|
121
|
+
| `isa llm list` | Write the FilterSpec or stream matching catalog entries |
|
|
122
122
|
| `isa llm schema` | Print the JSON schema for `llm` payloads |
|
|
123
123
|
|
|
124
124
|
Dev commands
|
|
@@ -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.13"
|
|
8
8
|
description = "Searchable SIMD intrinsic and instruction reference with CLI, manpages, TUI, and LSP."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -659,9 +659,7 @@ def _maybe_auto_update_for_version_change() -> None:
|
|
|
659
659
|
"[bold yellow]warning:[/bold yellow] auto-update failed; continuing with the existing catalog. "
|
|
660
660
|
"Some features may not work correctly until `simdref update` succeeds.",
|
|
661
661
|
)
|
|
662
|
-
#
|
|
663
|
-
# been warned and can re-run `simdref update` once back online.
|
|
664
|
-
write_installed_version_stamp(__version__)
|
|
662
|
+
# Keep the old stamp so the next invocation retries the download.
|
|
665
663
|
|
|
666
664
|
|
|
667
665
|
def _catalog_meta(catalog) -> dict:
|
|
@@ -85,12 +85,13 @@ def _canonical_instruction_key(name: str, form: str) -> str:
|
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
@lru_cache(maxsize=128)
|
|
88
|
-
def _normalize_operand_xtype(value: str) -> str:
|
|
88
|
+
def _normalize_operand_xtype(value: str, width: str = "") -> str:
|
|
89
89
|
xtype = value.strip()
|
|
90
90
|
if not xtype:
|
|
91
91
|
return xtype
|
|
92
92
|
if xtype == "int":
|
|
93
|
-
|
|
93
|
+
# uops.info marks every scalar integer operand xtype=int; the width holds the size
|
|
94
|
+
return f"i{width}" if width in {"8", "16", "32", "64"} else "i32"
|
|
94
95
|
match = re.fullmatch(r"\d+([iu]\d+)", xtype)
|
|
95
96
|
if match:
|
|
96
97
|
return match.group(1)
|
|
@@ -1020,7 +1021,9 @@ def parse_uops_xml(source: str | Path) -> list[InstructionRecord]:
|
|
|
1020
1021
|
arch_details: dict[str, dict[str, Any]] = {}
|
|
1021
1022
|
for child in node:
|
|
1022
1023
|
if child.tag == "operand":
|
|
1023
|
-
xtype = _normalize_operand_xtype(
|
|
1024
|
+
xtype = _normalize_operand_xtype(
|
|
1025
|
+
child.attrib.get("xtype", "").strip(), child.attrib.get("width", "").strip()
|
|
1026
|
+
)
|
|
1024
1027
|
operand_payload = {
|
|
1025
1028
|
key: (
|
|
1026
1029
|
_intern_small(value.strip())
|
|
@@ -99,6 +99,11 @@ def _code_point_index(line: str, character: int) -> int:
|
|
|
99
99
|
return index
|
|
100
100
|
|
|
101
101
|
|
|
102
|
+
def _utf16_column(line: str, index: int) -> int:
|
|
103
|
+
# The reverse of _code_point_index: a code-point index to a UTF-16 column.
|
|
104
|
+
return sum(2 if ord(c) > 0xFFFF else 1 for c in line[:index])
|
|
105
|
+
|
|
106
|
+
|
|
102
107
|
def _line_prefix(text: str, line: int, character: int) -> str:
|
|
103
108
|
lines = text.splitlines()
|
|
104
109
|
if line >= len(lines):
|
|
@@ -108,31 +113,286 @@ def _line_prefix(text: str, line: int, character: int) -> str:
|
|
|
108
113
|
return match.group(0) if match else ""
|
|
109
114
|
|
|
110
115
|
|
|
111
|
-
|
|
112
|
-
|
|
116
|
+
_ATT_SUFFIX_WIDTH = {"b": 8, "w": 16, "l": 32, "q": 64}
|
|
117
|
+
_ACCUMULATOR = {"al": "r8", "ax": "r16", "eax": "r32", "rax": "r64"}
|
|
118
|
+
# x86 numbers only r8-r15; r0-r7 are ARM registers.
|
|
119
|
+
_GPR_PATTERNS = (
|
|
120
|
+
(64, r"r(ax|bx|cx|dx|si|di|bp|sp|8|9|1[0-5])"),
|
|
121
|
+
(32, r"e(ax|bx|cx|dx|si|di|bp|sp)|r(8|9|1[0-5])d"),
|
|
122
|
+
(16, r"(ax|bx|cx|dx|si|di|bp|sp)|r(8|9|1[0-5])w"),
|
|
123
|
+
(8, r"[a-d][lh]|(si|di|bp|sp)l|r(8|9|1[0-5])b"),
|
|
124
|
+
)
|
|
125
|
+
_STRING_OP_RE = re.compile(r"(movs|cmps|scas|lods|stos|ins|outs)[bwdq]", re.IGNORECASE)
|
|
126
|
+
_X86_VECTOR_WIDTHS = {"xmm": "v128", "ymm": "v256", "zmm": "v512"}
|
|
127
|
+
_X86_VECTOR_RE = re.compile(r"(?:[xyz]mm(?:[12]?[0-9]|3[01])|k[0-7]|mm[0-7])\Z")
|
|
128
|
+
_A64_ARRANGEMENT_RE = re.compile(r"[bdhqsvz](?:[0-9]|[12][0-9]|3[01])\.[0-9]*[bhsdq]\Z")
|
|
129
|
+
_A64_SCALAR_RE = re.compile(r"(?:x(?:[0-9]|[12][0-9]|30)|w(?:[0-9]|[12][0-9]|30)|xzr|wzr)\Z")
|
|
130
|
+
_A64_SCALAR_FP_RE = re.compile(r"[bhsdq](?:[0-9]|[12][0-9]|3[01])\Z")
|
|
131
|
+
# A bare v register is a vector spelling ARM and RISC-V share.
|
|
132
|
+
_A64_VECTOR_BARE_RE = re.compile(r"v(?:[0-9]|[12][0-9]|3[01])\Z")
|
|
133
|
+
_ARM32_SCALAR_RE = re.compile(r"(?:r(?:[0-9]|1[0-5])|ip|pc)\Z")
|
|
134
|
+
# ARM has "sp" and "fp"; RISC-V shares both spellings and adds these scalar aliases.
|
|
135
|
+
_RISCV_SCALARS = frozenset(
|
|
136
|
+
{f"a{i}" for i in range(8)}
|
|
137
|
+
| {f"s{i}" for i in range(12)}
|
|
138
|
+
| {f"t{i}" for i in range(7)}
|
|
139
|
+
| {"zero", "ra", "gp", "tp"}
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _lookup_mnemonic(conn, mnemonic: str, arch: frozenset = frozenset()):
|
|
144
|
+
"""Return (forms, width). The exact mnemonic wins, else drop an AT&T size suffix (addq -> add).
|
|
145
|
+
|
|
146
|
+
The suffix strip runs on an x86 or undecided line only: "addw" on a RISC-V
|
|
147
|
+
or ARM32 line must not land on x86 "add". Rows of other architectures stay
|
|
148
|
+
in the result; _match_form filters them by the line's candidates.
|
|
149
|
+
"""
|
|
150
|
+
forms = load_instructions_by_mnemonic_from_db(conn, mnemonic)
|
|
151
|
+
width = _ATT_SUFFIX_WIDTH.get(mnemonic[-1:].casefold())
|
|
152
|
+
# movsbl is a sign-extend, not the string op movsb.
|
|
153
|
+
if forms or not width or _STRING_OP_RE.fullmatch(mnemonic[:-1]):
|
|
154
|
+
return forms, None
|
|
155
|
+
if arch and "x86" not in arch:
|
|
156
|
+
return [], None
|
|
157
|
+
stripped = load_instructions_by_mnemonic_from_db(conn, mnemonic[:-1])
|
|
158
|
+
return [form for form in stripped if form.architecture == "x86"], width
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _classify_line(operands: str, intel: bool) -> tuple[frozenset, list[str | None], bool]:
|
|
162
|
+
"""One pass over the line: (candidate architecture buckets, one class per operand, reverse).
|
|
163
|
+
|
|
164
|
+
A strong vote (a %- or Intel-style register, an x86 vector, an AArch64
|
|
165
|
+
arrangement, an AArch64/ARM32/RISC-V-only scalar) names one bucket. A
|
|
166
|
+
spelling two architectures share ("sp", "fp", s0-s11, a bare v) votes for
|
|
167
|
+
every bucket it can mean and only counts when no strong vote fired. Empty
|
|
168
|
+
candidates mean undecided; callers then keep today's unfiltered behaviour.
|
|
169
|
+
"reverse" is True when the line's operand order flips into key order
|
|
170
|
+
(AT&T x86). A register of a non-candidate architecture reads as a symbol.
|
|
171
|
+
"""
|
|
172
|
+
parts = re.split(r",(?![^(\[{]*[)\]}])", operands)
|
|
173
|
+
strong, weak = set(), set()
|
|
174
|
+
for raw in parts:
|
|
175
|
+
op = raw.strip()
|
|
176
|
+
name = op.lstrip("*").lstrip("%").casefold()
|
|
177
|
+
prefixed = op.lstrip("*").startswith("%")
|
|
178
|
+
reg = next((w for w, pattern in _GPR_PATTERNS if re.fullmatch(pattern, name)), None)
|
|
179
|
+
if op.startswith("$"):
|
|
180
|
+
strong.add("x86")
|
|
181
|
+
elif _X86_VECTOR_RE.fullmatch(name):
|
|
182
|
+
strong.add("x86")
|
|
183
|
+
elif prefixed and reg:
|
|
184
|
+
strong.add("x86")
|
|
185
|
+
elif not name or "(" in op or "[" in op or "{" in op:
|
|
186
|
+
continue
|
|
187
|
+
elif _A64_ARRANGEMENT_RE.fullmatch(name) or _A64_SCALAR_RE.fullmatch(name) or name == "wsp":
|
|
188
|
+
strong.add("arm")
|
|
189
|
+
elif _ARM32_SCALAR_RE.fullmatch(name):
|
|
190
|
+
strong.add("arm")
|
|
191
|
+
elif _A64_SCALAR_FP_RE.fullmatch(name):
|
|
192
|
+
# s0-s11 are also RISC-V saved registers: a weak vote there, and
|
|
193
|
+
# the mnemonic's catalog rows arbitrate between arm and riscv.
|
|
194
|
+
if name in _RISCV_SCALARS:
|
|
195
|
+
weak.update(("arm", "riscv"))
|
|
196
|
+
else:
|
|
197
|
+
strong.add("arm")
|
|
198
|
+
elif name in _RISCV_SCALARS:
|
|
199
|
+
strong.add("riscv")
|
|
200
|
+
elif _A64_VECTOR_BARE_RE.fullmatch(name):
|
|
201
|
+
weak.update(("arm", "riscv"))
|
|
202
|
+
elif name in ("sp", "fp"):
|
|
203
|
+
weak.update(("arm", "riscv"))
|
|
204
|
+
elif name == "lr":
|
|
205
|
+
weak.add("arm")
|
|
206
|
+
arch = frozenset({"x86"}) if intel or "x86" in strong else frozenset(strong or weak)
|
|
207
|
+
classes = [_operand_class(op.strip(), intel, arch) for op in parts]
|
|
208
|
+
reverse = not intel and ("x86" in arch or not arch)
|
|
209
|
+
return arch, classes, reverse
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _operand_class(op: str, intel: bool, arch: frozenset) -> str | None:
|
|
213
|
+
"""Classify one operand on a line whose candidates are arch, or None.
|
|
214
|
+
|
|
215
|
+
Classes: r8..r64, v128/v256/v512/k/mm, av (AArch64 vector with an
|
|
216
|
+
arrangement), x/w/xzr/wzr/sp/wsp (AArch64 scalars; fp and lr read as x),
|
|
217
|
+
b/h/s/d/q (AArch64 scalar FP by width), ar (ARM32), rv (RISC-V), and
|
|
218
|
+
i, lit0/lit1, m, rel. A bare symbol is a memory reference in AT&T and a
|
|
219
|
+
rel target in Intel syntax.
|
|
220
|
+
"""
|
|
221
|
+
if not op:
|
|
222
|
+
return None
|
|
223
|
+
name = op.lstrip("*").lstrip("%").casefold()
|
|
224
|
+
if re.fullmatch(r"\$?0+", op):
|
|
225
|
+
return "lit0"
|
|
226
|
+
if re.fullmatch(r"\$?0*1", op):
|
|
227
|
+
return "lit1"
|
|
228
|
+
if op.startswith("$") or re.fullmatch(r"-?(0x[0-9a-f]+|\d+)", op, re.IGNORECASE):
|
|
229
|
+
return "i"
|
|
230
|
+
if "{1to" in op:
|
|
231
|
+
return "mbc"
|
|
232
|
+
if "{" in op:
|
|
233
|
+
op = re.sub(r"\{[^}]*\}", "", op).strip()
|
|
234
|
+
name = op.lstrip("*").lstrip("%").casefold()
|
|
235
|
+
if "(" in op or "[" in op:
|
|
236
|
+
return "m"
|
|
237
|
+
reg = next((w for w, pattern in _GPR_PATTERNS if re.fullmatch(pattern, name)), None)
|
|
238
|
+
# In AT&T a bare "sp" is the ARM stack pointer or a symbol, never the r16.
|
|
239
|
+
if reg and (intel or op.lstrip("*").startswith("%") or name not in ("sp", "bp", "si", "di")):
|
|
240
|
+
if intel or arch.isdisjoint({"arm", "riscv"}):
|
|
241
|
+
return f"r{reg}"
|
|
242
|
+
if _X86_VECTOR_RE.fullmatch(name):
|
|
243
|
+
if name[0] == "k":
|
|
244
|
+
return "k"
|
|
245
|
+
if name[0] == "m":
|
|
246
|
+
return "mm"
|
|
247
|
+
return _X86_VECTOR_WIDTHS[name[:3]]
|
|
248
|
+
if not intel and op.startswith("*"):
|
|
249
|
+
return "m"
|
|
250
|
+
if "arm" in arch:
|
|
251
|
+
if _A64_ARRANGEMENT_RE.fullmatch(name) or _A64_VECTOR_BARE_RE.fullmatch(name):
|
|
252
|
+
return "av"
|
|
253
|
+
if _A64_SCALAR_FP_RE.fullmatch(name):
|
|
254
|
+
return name[0]
|
|
255
|
+
if _A64_SCALAR_RE.fullmatch(name):
|
|
256
|
+
return {"xzr": "xzr", "wzr": "wzr"}.get(name, name[0])
|
|
257
|
+
if name in ("sp", "wsp", "fp", "lr"):
|
|
258
|
+
return {"sp": "sp", "wsp": "wsp"}.get(name, "x")
|
|
259
|
+
if _ARM32_SCALAR_RE.fullmatch(name):
|
|
260
|
+
return "ar"
|
|
261
|
+
if "riscv" in arch and (name in _RISCV_SCALARS or _A64_VECTOR_BARE_RE.fullmatch(name)):
|
|
262
|
+
return "rv"
|
|
263
|
+
if "x86" in arch or not arch:
|
|
264
|
+
return "rel" if intel else "m"
|
|
265
|
+
return None
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
_IMMEDIATE_ACCEPT = frozenset({"i", "lit0", "lit1"})
|
|
269
|
+
_LITERAL_ACCEPT = {frozenset({"lit0"}), frozenset({"lit1"})}
|
|
270
|
+
_CLASS_KIND = dict(
|
|
271
|
+
[(c, "register") for c in ("r8", "r16", "r32", "r64")]
|
|
272
|
+
+ [(c, "vector") for c in ("v128", "v256", "v512", "k", "mm", "av", "dfp", "pred")]
|
|
273
|
+
+ [(c, "memory") for c in ("m", "moffs", "mbc")]
|
|
274
|
+
+ [(c, "immediate") for c in ("i", "lit0", "lit1")]
|
|
275
|
+
+ [
|
|
276
|
+
(c, "scalar")
|
|
277
|
+
for c in ("x", "w", "xzr", "wzr", "sp", "wsp", "b", "h", "s", "d", "q", "ar", "rv")
|
|
278
|
+
]
|
|
279
|
+
+ [("rel", "rel")]
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _form_accept(op: str) -> frozenset:
|
|
284
|
+
"""The line classes a catalog-key operand accepts, as one frozenset.
|
|
285
|
+
|
|
286
|
+
An immediate-width key (i8..i64) takes every immediate class. A literal 0/1
|
|
287
|
+
takes only its own value: a fixed-1 shift is not the line's "2". An AArch64
|
|
288
|
+
placeholder maps to the register family it encodes (<XNSP> = x or sp).
|
|
289
|
+
"""
|
|
290
|
+
o = op.casefold()
|
|
291
|
+
if o in ("0", "1"):
|
|
292
|
+
return frozenset({"lit" + o})
|
|
293
|
+
if re.fullmatch(r"i\d+", o):
|
|
294
|
+
return _IMMEDIATE_ACCEPT
|
|
295
|
+
if o in _ACCUMULATOR or o == "cl":
|
|
296
|
+
return frozenset({_ACCUMULATOR.get(o, "r8")})
|
|
297
|
+
m = re.fullmatch(r"r(8|16|32|64)[hl]?", o)
|
|
298
|
+
if m:
|
|
299
|
+
return frozenset({"r" + m.group(1)})
|
|
300
|
+
if o in _X86_VECTOR_WIDTHS or o in ("k", "mm"):
|
|
301
|
+
return frozenset({_X86_VECTOR_WIDTHS.get(o, o)})
|
|
302
|
+
if re.fullmatch(r"m\d+_\d+to\d+", o):
|
|
303
|
+
return frozenset({"mbc"})
|
|
304
|
+
if re.fullmatch(r"m\d+", o):
|
|
305
|
+
return frozenset({"m"})
|
|
306
|
+
if re.fullmatch(r"moffs\d+", o):
|
|
307
|
+
return frozenset({"moffs"})
|
|
308
|
+
if re.fullmatch(r"rel\d*", o):
|
|
309
|
+
return frozenset({"rel"})
|
|
310
|
+
if op.startswith("["):
|
|
311
|
+
return frozenset({"m"})
|
|
312
|
+
name = re.sub(r"(#\d+)+$", "", op.upper())
|
|
313
|
+
if "<HASH>" in name:
|
|
314
|
+
return _IMMEDIATE_ACCEPT
|
|
315
|
+
if re.match(r"V\d+<|^[ZV]\d+\.|^\{|^<ZA>", name):
|
|
316
|
+
return frozenset({"av"})
|
|
317
|
+
# Numbered scalar FP placeholders: D0 or S1 style numbers, <BT> or <QT1> style aliases.
|
|
318
|
+
if re.fullmatch(r"[BHSDQ]\d+", name):
|
|
319
|
+
return frozenset({name[0].lower()})
|
|
320
|
+
if re.fullmatch(r"<[BHSDQ]T\d*>", name):
|
|
321
|
+
return frozenset({name[1].lower()})
|
|
322
|
+
if re.match(r"^D<|^<FPFAR_DT>$", name):
|
|
323
|
+
return frozenset({"dfp"})
|
|
324
|
+
if re.match(r"^P\d", name):
|
|
325
|
+
return frozenset({"pred"})
|
|
326
|
+
if "WORX" in name or "R_OPTION" in name:
|
|
327
|
+
return frozenset({"w", "x"})
|
|
328
|
+
if "WZR" in name:
|
|
329
|
+
return frozenset({"w", "wzr"})
|
|
330
|
+
if "XZR" in name:
|
|
331
|
+
return frozenset({"x", "xzr"})
|
|
332
|
+
if "WSP" in name:
|
|
333
|
+
return frozenset({"w", "wsp"})
|
|
334
|
+
if "SP" in name:
|
|
335
|
+
return frozenset({"x", "sp"})
|
|
336
|
+
return frozenset()
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _match_form(
|
|
340
|
+
matches,
|
|
341
|
+
operand_count: int | None,
|
|
342
|
+
line: tuple,
|
|
343
|
+
suffix_bits: int | None,
|
|
344
|
+
):
|
|
345
|
+
"""Pick the form the line names, under one total order.
|
|
346
|
+
|
|
347
|
+
line is _classify_line's (arch candidates, ordered classes, reverse). Rows
|
|
348
|
+
of a non-candidate architecture drop out before ranking; an undecided line
|
|
349
|
+
keeps every row. Rank: class-set score (accepted +1 / same kind 0 / else
|
|
350
|
+
-1, a literal scores only on its own value), position score, iN over a
|
|
351
|
+
literal 0/1, match with the AT&T suffix width, fewer operands, key name.
|
|
352
|
+
The no-same-count fallback uses the same key.
|
|
353
|
+
"""
|
|
354
|
+
arch, classes, reverse = line
|
|
355
|
+
if arch:
|
|
356
|
+
matches = [m for m in matches if m.architecture in arch]
|
|
113
357
|
if operand_count is None:
|
|
114
358
|
return matches[0] if matches else None
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
width =
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
359
|
+
same = [m for m in matches if len(_operands_of_key(m.key)) == operand_count]
|
|
360
|
+
pool = same or matches
|
|
361
|
+
if not pool:
|
|
362
|
+
return None
|
|
363
|
+
ordered = list(reversed(classes)) if reverse else classes
|
|
364
|
+
line_set = {c for c in classes if c}
|
|
365
|
+
line_kinds = {_CLASS_KIND[c] for c in line_set if c in _CLASS_KIND}
|
|
366
|
+
|
|
367
|
+
def _rank(form):
|
|
368
|
+
accepts = [_form_accept(op) for op in _operands_of_key(form.key)]
|
|
369
|
+
set_score = position = in_over_lit = width = 0
|
|
370
|
+
for acc in accepts:
|
|
371
|
+
if acc in _LITERAL_ACCEPT:
|
|
372
|
+
set_score += 0 if acc & line_set else -1
|
|
373
|
+
elif acc & line_set:
|
|
374
|
+
set_score += 1
|
|
375
|
+
elif {_CLASS_KIND[a] for a in acc} & line_kinds:
|
|
376
|
+
pass
|
|
377
|
+
else:
|
|
378
|
+
set_score -= 1
|
|
379
|
+
if any(
|
|
380
|
+
c in line_set and int(c[1:]) == suffix_bits
|
|
381
|
+
for c in acc
|
|
382
|
+
if re.fullmatch(r"[rv]\d+", c)
|
|
383
|
+
):
|
|
384
|
+
width += 1
|
|
385
|
+
for cls, acc in zip(ordered, accepts):
|
|
386
|
+
if cls in acc:
|
|
387
|
+
position += 1
|
|
388
|
+
if cls in ("i", "lit0", "lit1"):
|
|
389
|
+
if acc == _IMMEDIATE_ACCEPT:
|
|
390
|
+
in_over_lit += 1
|
|
391
|
+
elif acc in _LITERAL_ACCEPT:
|
|
392
|
+
in_over_lit -= 1
|
|
393
|
+
return (-set_score, -position, -in_over_lit, -width, len(accepts), form.key)
|
|
394
|
+
|
|
395
|
+
return min(pool, key=_rank)
|
|
136
396
|
|
|
137
397
|
|
|
138
398
|
def _hover_markdown(
|
|
@@ -141,6 +401,7 @@ def _hover_markdown(
|
|
|
141
401
|
allow_instruction: bool = True,
|
|
142
402
|
operand_count: int | None = None,
|
|
143
403
|
operand_text: str = "",
|
|
404
|
+
intel: bool = False,
|
|
144
405
|
) -> str | None:
|
|
145
406
|
intrinsic = load_intrinsic_from_db(conn, word)
|
|
146
407
|
if intrinsic is not None:
|
|
@@ -181,12 +442,22 @@ def _hover_markdown(
|
|
|
181
442
|
|
|
182
443
|
if not allow_instruction:
|
|
183
444
|
return None
|
|
445
|
+
line = _classify_line(operand_text, intel)
|
|
184
446
|
instruction = load_instruction_from_db(conn, word)
|
|
447
|
+
# An exact name can name a row with a different operand count (a bare
|
|
448
|
+
# "movsd" is the string move), or a row from the wrong architecture
|
|
449
|
+
# (a bare "nop" picks AArch64 NOP over x86 in an Intel file).
|
|
450
|
+
# In both cases, fall back to _match_form so hover and hint agree.
|
|
451
|
+
if instruction is not None:
|
|
452
|
+
if line[0] and instruction.architecture not in line[0]:
|
|
453
|
+
instruction = None
|
|
454
|
+
elif operand_count is not None and len(_operands_of_key(instruction.key)) != operand_count:
|
|
455
|
+
instruction = None
|
|
185
456
|
if instruction is None:
|
|
186
|
-
matches = load_instructions_by_mnemonic_from_db(conn, word)
|
|
187
457
|
# Match the form on the source line like _hint_label does, so
|
|
188
458
|
# "vaddps ymm0, ymm1, ymm2" gets the YMM form, not the first row.
|
|
189
|
-
|
|
459
|
+
matches, width = _lookup_mnemonic(conn, word, line[0])
|
|
460
|
+
instruction = _match_form(matches, operand_count, line, width)
|
|
190
461
|
if instruction is not None:
|
|
191
462
|
body = instruction_markdown(instruction, conn=conn)
|
|
192
463
|
links = [f"Full page: {web_deep_link('instruction', instruction.key)}"]
|
|
@@ -299,7 +570,7 @@ def _operand_count(operands: str) -> int:
|
|
|
299
570
|
|
|
300
571
|
|
|
301
572
|
def _mnemonic_from_asm_line(line: str, semicolon_is_comment: bool = False):
|
|
302
|
-
"""Return (mnemonic, operand_count) for an asm line, or None."""
|
|
573
|
+
"""Return (mnemonic, operand_count, operands) for an asm line, or None."""
|
|
303
574
|
line = ASM_COMMENT_RE.sub("", line)
|
|
304
575
|
if semicolon_is_comment:
|
|
305
576
|
line = ASM_SEMICOLON_COMMENT_RE.sub("", line)
|
|
@@ -314,7 +585,8 @@ def _mnemonic_from_asm_line(line: str, semicolon_is_comment: bool = False):
|
|
|
314
585
|
parts = line.split(None, 1)
|
|
315
586
|
if not MNEMONIC_RE.match(parts[0]):
|
|
316
587
|
return None
|
|
317
|
-
|
|
588
|
+
operands = parts[1] if len(parts) > 1 else ""
|
|
589
|
+
return parts[0], _operand_count(operands), operands
|
|
318
590
|
|
|
319
591
|
|
|
320
592
|
def _operands_of_key(key: str) -> list[str]:
|
|
@@ -328,24 +600,32 @@ def _cut_label(text: str) -> str:
|
|
|
328
600
|
|
|
329
601
|
|
|
330
602
|
@functools.lru_cache(maxsize=1024)
|
|
331
|
-
def _mnemonic_matches(db_path: str, mnemonic: str) -> tuple:
|
|
603
|
+
def _mnemonic_matches(db_path: str, mnemonic: str, arch: frozenset = frozenset()) -> tuple:
|
|
332
604
|
# The inlay-hint loop asks the same mnemonics for every line, so query once
|
|
333
|
-
# per (catalog, mnemonic) and reuse. The key carries the
|
|
334
|
-
# connection, so a reloaded catalog at the same path would
|
|
335
|
-
# clear the cache if catalog reload lands.
|
|
605
|
+
# per (catalog, mnemonic, arch candidates) and reuse. The key carries the
|
|
606
|
+
# path, not the connection, so a reloaded catalog at the same path would
|
|
607
|
+
# keep stale rows; clear the cache if catalog reload lands.
|
|
336
608
|
conn = open_db(path=Path(db_path))
|
|
337
609
|
try:
|
|
338
|
-
|
|
610
|
+
forms, width = _lookup_mnemonic(conn, mnemonic, arch)
|
|
611
|
+
return tuple(forms), width
|
|
339
612
|
finally:
|
|
340
613
|
conn.close()
|
|
341
614
|
|
|
342
615
|
|
|
343
|
-
def _hint_label(
|
|
344
|
-
|
|
616
|
+
def _hint_label(
|
|
617
|
+
db_path: str,
|
|
618
|
+
mnemonic: str,
|
|
619
|
+
operand_count: int,
|
|
620
|
+
operands: str = "",
|
|
621
|
+
intel: bool = False,
|
|
622
|
+
) -> str | None:
|
|
623
|
+
line = _classify_line(operands, intel)
|
|
624
|
+
matches, width = _mnemonic_matches(db_path, mnemonic, line[0])
|
|
345
625
|
if not matches:
|
|
346
626
|
return None
|
|
347
|
-
chosen = _match_form(matches, operand_count)
|
|
348
|
-
return _cut_label(chosen.summary or "")
|
|
627
|
+
chosen = _match_form(matches, operand_count, line, width)
|
|
628
|
+
return _cut_label(chosen.summary or "") if chosen else None
|
|
349
629
|
|
|
350
630
|
|
|
351
631
|
def _inlay_hints(conn, text: str, language_id: str, uri: str, start: int, end: int) -> list[dict]:
|
|
@@ -366,7 +646,7 @@ def _inlay_hints(conn, text: str, language_id: str, uri: str, start: int, end: i
|
|
|
366
646
|
if not start <= line <= end:
|
|
367
647
|
continue
|
|
368
648
|
parsed = _mnemonic_from_asm_line(segment, semicolon_is_comment)
|
|
369
|
-
label = _hint_label(db_path, *parsed) if parsed else None
|
|
649
|
+
label = _hint_label(db_path, *parsed, intel=semicolon_is_comment) if parsed else None
|
|
370
650
|
if label is not None:
|
|
371
651
|
hints.setdefault(line, []).append(label)
|
|
372
652
|
return [
|
|
@@ -374,7 +654,7 @@ def _inlay_hints(conn, text: str, language_id: str, uri: str, start: int, end: i
|
|
|
374
654
|
# LSP columns count UTF-16 code units.
|
|
375
655
|
"position": {
|
|
376
656
|
"line": line,
|
|
377
|
-
"character": len(source_lines[line]
|
|
657
|
+
"character": _utf16_column(source_lines[line], len(source_lines[line])),
|
|
378
658
|
},
|
|
379
659
|
"label": _cut_label("; ".join(hints[line])),
|
|
380
660
|
"paddingLeft": True,
|
|
@@ -449,7 +729,8 @@ def main() -> int:
|
|
|
449
729
|
line, character = params["position"]["line"], params["position"]["character"]
|
|
450
730
|
word = _word_at(text, line, character)
|
|
451
731
|
# In C/C++ only the asm strings hold instructions; intrinsics hover everywhere.
|
|
452
|
-
|
|
732
|
+
language_id = session.languages.get(uri, "")
|
|
733
|
+
is_c_doc = _is_c_doc(language_id, uri)
|
|
453
734
|
allow_instruction = not is_c_doc or (_in_asm_string(text, line, character))
|
|
454
735
|
operand_count = None
|
|
455
736
|
operand_text = ""
|
|
@@ -457,16 +738,15 @@ def main() -> int:
|
|
|
457
738
|
offset = sum(len(item) + 1 for item in text.split("\n")[:line]) + _code_point_index(
|
|
458
739
|
text.split("\n")[line], character
|
|
459
740
|
)
|
|
460
|
-
language_id = session.languages.get(uri, "")
|
|
461
741
|
segment = _asm_line_at(text, language_id, uri, offset)
|
|
462
742
|
if segment:
|
|
463
|
-
operand_text = segment
|
|
464
743
|
# The C-escape pair "\t" starts a segment in asm("...\n\t...").
|
|
465
744
|
# Strip the backslash so the mnemonic survives.
|
|
466
745
|
parsed = _mnemonic_from_asm_line(
|
|
467
746
|
segment.lstrip("\\"), _semicolon_is_comment(language_id, uri)
|
|
468
747
|
)
|
|
469
748
|
if parsed:
|
|
749
|
+
operand_text = parsed[2]
|
|
470
750
|
if parsed[0].casefold() == word.casefold():
|
|
471
751
|
operand_count = parsed[1]
|
|
472
752
|
elif word.casefold().endswith(parsed[0].casefold()):
|
|
@@ -482,6 +762,7 @@ def main() -> int:
|
|
|
482
762
|
allow_instruction,
|
|
483
763
|
operand_count=operand_count,
|
|
484
764
|
operand_text=operand_text,
|
|
765
|
+
intel=_semicolon_is_comment(language_id, uri),
|
|
485
766
|
)
|
|
486
767
|
if conn and word
|
|
487
768
|
else None
|
|
@@ -150,6 +150,12 @@ _INITIAL_RESULT_BATCH = 50
|
|
|
150
150
|
_RESULT_BATCH_SIZE = 10
|
|
151
151
|
_RESULT_PREFETCH_THRESHOLD = 5
|
|
152
152
|
|
|
153
|
+
|
|
154
|
+
def _NO_DATA(text: str) -> tuple[str, str]:
|
|
155
|
+
"""Detail line that marks a section with no data, instead of omitting it."""
|
|
156
|
+
return ("static_markup", f" [dim]{text}[/]")
|
|
157
|
+
|
|
158
|
+
|
|
153
159
|
_ISA_FAMILIES: list[str] = [
|
|
154
160
|
name for name, _ in sorted(ISA_FAMILY_ORDER.items(), key=lambda item: item[1])
|
|
155
161
|
]
|
|
@@ -1781,6 +1787,8 @@ class SimdrefApp(App):
|
|
|
1781
1787
|
if linked:
|
|
1782
1788
|
self._append_operand_section(payload, linked)
|
|
1783
1789
|
self._append_perf_sections(payload, linked)
|
|
1790
|
+
else:
|
|
1791
|
+
payload.append(_NO_DATA("no linked instruction: no operands or performance data"))
|
|
1784
1792
|
if intrinsic.doc_sections:
|
|
1785
1793
|
self._append_description_sections(payload, intrinsic.doc_sections)
|
|
1786
1794
|
if linked and linked.description:
|
|
@@ -1816,6 +1824,7 @@ class SimdrefApp(App):
|
|
|
1816
1824
|
|
|
1817
1825
|
def _append_operand_section(self, payload: list[tuple], item: InstructionRecord) -> None:
|
|
1818
1826
|
if not item.operand_details:
|
|
1827
|
+
payload.append(_NO_DATA("operands: no data"))
|
|
1819
1828
|
return
|
|
1820
1829
|
table = Table(header_style="bold blue")
|
|
1821
1830
|
table.add_column("idx", width=4)
|
|
@@ -1844,6 +1853,8 @@ class SimdrefApp(App):
|
|
|
1844
1853
|
payload.append(
|
|
1845
1854
|
("static_markup", f" [green]latency:[/] {lat} [green]CPI:[/] {cpi}")
|
|
1846
1855
|
)
|
|
1856
|
+
else:
|
|
1857
|
+
payload.append(_NO_DATA("performance: no data"))
|
|
1847
1858
|
return
|
|
1848
1859
|
label_map = {
|
|
1849
1860
|
"uarch": "microarch",
|