simdref 0.0.8__tar.gz → 0.0.9__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.8/src/simdref.egg-info → simdref-0.0.9}/PKG-INFO +1 -1
- {simdref-0.0.8 → simdref-0.0.9}/pyproject.toml +1 -1
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/display.py +63 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/lsp.py +74 -36
- {simdref-0.0.8 → simdref-0.0.9/src/simdref.egg-info}/PKG-INFO +1 -1
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_lsp_stdio.py +64 -2
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_lsp_web.py +1 -1
- {simdref-0.0.8 → simdref-0.0.9}/LICENSE +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/README.md +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/setup.cfg +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/__init__.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/__main__.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/annotate.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/arm_instructions.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/cli.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/export.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/filters.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/ingest.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/ingest_catalog.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/ingest_pdf.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/ingest_sources.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/manpages.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/models.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/pdfparse/__init__.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/pdfparse/base.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/pdfparse/intel.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/pdfparse/registry.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/pdfparse/types.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/pdfrefs.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/perf.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/perf_sources/__init__.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/perf_sources/cores.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/perf_sources/llvm_mca.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/perf_sources/llvm_scheduling.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/perf_sources/merge.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/queries.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/riscv.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/search.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/storage.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/tui.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref/ui_labels.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref.egg-info/SOURCES.txt +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref.egg-info/dependency_links.txt +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref.egg-info/entry_points.txt +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref.egg-info/requires.txt +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/src/simdref.egg-info/top_level.txt +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_annotate.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_annotate_positions.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_audit_coverage.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_auto_update_on_version_change.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_cli_bare_query.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_cli_bootstrap_progress.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_cli_help.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_cli_llm.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_coverage_live.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_coverage_parity.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_display.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_export.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_filters.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_ingest_sources.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_intel_operation_and_url.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_issue2_fixes.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_issues_23_24_25.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_models.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_pdfparse.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_perf.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_perf_sources.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_presentation.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_preset_persistence.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_riscv_docs_pages.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_search.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_search_pushdown.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_source_kind_filter.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_source_validation.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_storage_payload.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_storage_schema.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_tui.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_ui_labels_parity.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_version_matches_pyproject.py +0 -0
- {simdref-0.0.8 → simdref-0.0.9}/tests/test_x86_linking.py +0 -0
|
@@ -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.9"
|
|
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"
|
|
@@ -9,6 +9,7 @@ delegate to these functions for all Rich-based output.
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
import re
|
|
12
|
+
import urllib.parse
|
|
12
13
|
from typing import TYPE_CHECKING
|
|
13
14
|
|
|
14
15
|
from rich.console import Console
|
|
@@ -480,6 +481,68 @@ def isa_visible(values: list[str], show_fp16: bool = False) -> bool:
|
|
|
480
481
|
# ---------------------------------------------------------------------------
|
|
481
482
|
|
|
482
483
|
|
|
484
|
+
_WEB_APP_URL = "https://simdref.diamondinoia.com"
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def web_deep_link(kind: str, key: str) -> str:
|
|
488
|
+
"""Deep link into the simdref web app for a record key."""
|
|
489
|
+
# Match the web app's encodeURIComponent(entry.key): parens stay literal.
|
|
490
|
+
return f"{_WEB_APP_URL}/#{urllib.parse.quote(str(key), safe='()')}"
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def instruction_markdown(item, conn=None) -> str:
|
|
494
|
+
"""Render the full instruction page as Markdown (hover docs, exports).
|
|
495
|
+
|
|
496
|
+
Carries the same data as :func:`render_instruction` prints to the
|
|
497
|
+
terminal: metadata, every description section, the intrinsic mapping
|
|
498
|
+
and the per-microarchitecture performance table, plus reference URLs.
|
|
499
|
+
"""
|
|
500
|
+
lines = [f"```asm\n{item.key}\n```"]
|
|
501
|
+
lines.append(f"**isa.** {display_isa(item.isa)}")
|
|
502
|
+
for key, value in instruction_metadata_rows(item):
|
|
503
|
+
lines.append(f"**{key}.** {value}")
|
|
504
|
+
if item.description:
|
|
505
|
+
shown: set[str] = set()
|
|
506
|
+
for section in _DESCRIPTION_ORDER:
|
|
507
|
+
if section in item.description:
|
|
508
|
+
lines.append(f"## {section}\n\n{item.description[section].strip()}")
|
|
509
|
+
shown.add(section)
|
|
510
|
+
for section, body in item.description.items():
|
|
511
|
+
if section not in shown:
|
|
512
|
+
lines.append(f"## {section}\n\n{body.strip()}")
|
|
513
|
+
if item.linked_intrinsics:
|
|
514
|
+
lines.append(
|
|
515
|
+
"## instruction to intrinsic mapping\n\n"
|
|
516
|
+
+ "\n".join(f"- `{name}`" for name in item.linked_intrinsics)
|
|
517
|
+
)
|
|
518
|
+
lines.extend(_perf_markdown_lines(measurement_rows(item)))
|
|
519
|
+
return "\n\n".join(lines)
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def _perf_markdown_lines(rows: list[dict]) -> list[str]:
|
|
523
|
+
"""Markdown table for measurement rows, one section per source kind."""
|
|
524
|
+
sections: list[str] = []
|
|
525
|
+
for kind, group in split_perf_rows(rows):
|
|
526
|
+
group = [row for row in group if row]
|
|
527
|
+
columns = [
|
|
528
|
+
column
|
|
529
|
+
for column in _MEASUREMENT_PREFERRED_ORDER
|
|
530
|
+
if column not in _MEASUREMENT_EXCLUDE_KEYS and any(column in row for row in group)
|
|
531
|
+
]
|
|
532
|
+
if not columns:
|
|
533
|
+
continue
|
|
534
|
+
header = [_GENERIC_TABLE_LABEL_MAP.get(column, column) for column in columns]
|
|
535
|
+
table = ["| " + " | ".join(header) + " |", "|" + "---|" * len(columns)]
|
|
536
|
+
for row in sorted(group, key=lambda r: uarch_sort_key(r.get("uarch", ""))):
|
|
537
|
+
cells = []
|
|
538
|
+
for column in columns:
|
|
539
|
+
value = row.get(column, "-")
|
|
540
|
+
cells.append(display_uarch(str(value)) if column == "uarch" else str(value))
|
|
541
|
+
table.append("| " + " | ".join(cells) + " |")
|
|
542
|
+
sections.append(f"## {perf_panel_title(kind)}\n\n" + "\n".join(table))
|
|
543
|
+
return sections
|
|
544
|
+
|
|
545
|
+
|
|
483
546
|
def canonical_url(path: str) -> str:
|
|
484
547
|
if not path:
|
|
485
548
|
return ""
|
|
@@ -9,6 +9,7 @@ import sys
|
|
|
9
9
|
from dataclasses import dataclass, field
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
+
from simdref.display import canonical_url, instruction_markdown, web_deep_link
|
|
12
13
|
from simdref.perf import best_cpi, best_latency
|
|
13
14
|
from simdref.queries import linked_instruction_records
|
|
14
15
|
from simdref.search import search_records
|
|
@@ -97,7 +98,40 @@ def _line_prefix(text: str, line: int, character: int) -> str:
|
|
|
97
98
|
return match.group(0) if match else ""
|
|
98
99
|
|
|
99
100
|
|
|
100
|
-
def
|
|
101
|
+
def _match_form(matches, operand_count: int | None = None, operand_text: str = ""):
|
|
102
|
+
"""Pick the same form the line names: exact operand count, then width, then smallest."""
|
|
103
|
+
if operand_count is None:
|
|
104
|
+
return matches[0] if matches else None
|
|
105
|
+
# ponytail: a line has no architecture, so take the first form with the same operand count.
|
|
106
|
+
# The fallback takes the first form with fewest operands, never a form with more
|
|
107
|
+
# operands than the line: a zero-operand "movsd" must not get the MOVSD_XMM hint.
|
|
108
|
+
same_count = [m for m in matches if len(_operands_of_key(m.key)) == operand_count]
|
|
109
|
+
if not same_count:
|
|
110
|
+
return min(matches, key=lambda m: len(_operands_of_key(m.key)), default=None)
|
|
111
|
+
# Score candidates by how many operands agree with the register width the
|
|
112
|
+
# line names ("ymm0" vs "YMM, YMM, M128"). Skip the tiebreak on a tie.
|
|
113
|
+
tokens = re.findall(r"\b(zmm|ymm|xmm|m512|m256|m128|m64|m32)\d*\b", operand_text.casefold())
|
|
114
|
+
if tokens:
|
|
115
|
+
width = tokens[0].rstrip("0123456789")
|
|
116
|
+
if width.startswith(("x", "y", "z")):
|
|
117
|
+
|
|
118
|
+
def _score(item):
|
|
119
|
+
operands = [op.casefold().rstrip("0123456789") for op in _operands_of_key(item.key)]
|
|
120
|
+
return sum(1 for op in operands if op == width)
|
|
121
|
+
|
|
122
|
+
scored = sorted(same_count, key=lambda m: (-_score(m), m.key))
|
|
123
|
+
if _score(scored[0]) > _score(scored[-1]):
|
|
124
|
+
return scored[0]
|
|
125
|
+
return same_count[0]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _hover_markdown(
|
|
129
|
+
conn,
|
|
130
|
+
word: str,
|
|
131
|
+
allow_instruction: bool = True,
|
|
132
|
+
operand_count: int | None = None,
|
|
133
|
+
operand_text: str = "",
|
|
134
|
+
) -> str | None:
|
|
101
135
|
intrinsic = load_intrinsic_from_db(conn, word)
|
|
102
136
|
if intrinsic is not None:
|
|
103
137
|
lines = [f"```c\n{intrinsic.signature}\n```"]
|
|
@@ -139,32 +173,18 @@ def _hover_markdown(conn, word: str, allow_instruction: bool = True) -> str | No
|
|
|
139
173
|
return None
|
|
140
174
|
instruction = load_instruction_from_db(conn, word)
|
|
141
175
|
if instruction is None:
|
|
142
|
-
# Catalog keys look like "VADDPS (XMM, XMM, XMM)", so a bare mnemonic needs this lookup.
|
|
143
176
|
matches = load_instructions_by_mnemonic_from_db(conn, word)
|
|
144
|
-
|
|
177
|
+
# Match the form on the source line like _hint_label does, so
|
|
178
|
+
# "vaddps ymm0, ymm1, ymm2" gets the YMM form, not the first row.
|
|
179
|
+
instruction = _match_form(matches, operand_count, operand_text)
|
|
145
180
|
if instruction is not None:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if instruction.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
meta.append(f"category {instruction.metadata['category']}")
|
|
154
|
-
if meta:
|
|
155
|
-
lines.append(" | ".join(meta))
|
|
156
|
-
if instruction.linked_intrinsics:
|
|
157
|
-
lines.append(f"Intrinsics: {', '.join(instruction.linked_intrinsics[:6])}")
|
|
158
|
-
perf = []
|
|
159
|
-
lat = best_latency(instruction.arch_details)
|
|
160
|
-
cpi = best_cpi(instruction.arch_details)
|
|
161
|
-
if lat != "-":
|
|
162
|
-
perf.append(f"best latency {lat} cycles")
|
|
163
|
-
if cpi != "-":
|
|
164
|
-
perf.append(f"best cycle/instr {cpi}")
|
|
165
|
-
if perf:
|
|
166
|
-
lines.append("Performance: " + ", ".join(perf))
|
|
167
|
-
return "\n\n".join(lines)
|
|
181
|
+
body = instruction_markdown(instruction, conn=conn)
|
|
182
|
+
links = [f"Full page: {web_deep_link('instruction', instruction.key)}"]
|
|
183
|
+
if instruction.metadata.get("url"):
|
|
184
|
+
links.append(canonical_url(instruction.metadata["url"]))
|
|
185
|
+
if instruction.metadata.get("url-ref"):
|
|
186
|
+
links.append(canonical_url(instruction.metadata["url-ref"]))
|
|
187
|
+
return body + "\n\n" + "\n".join(links)
|
|
168
188
|
return None
|
|
169
189
|
|
|
170
190
|
|
|
@@ -275,13 +295,7 @@ def _hint_label(db_path: str, mnemonic: str, operand_count: int) -> str | None:
|
|
|
275
295
|
matches = _mnemonic_matches(db_path, mnemonic)
|
|
276
296
|
if not matches:
|
|
277
297
|
return None
|
|
278
|
-
|
|
279
|
-
# The fallback takes the first form with fewest operands, never a form with more
|
|
280
|
-
# operands than the line: a zero-operand "movsd" must not get the MOVSD_XMM hint.
|
|
281
|
-
chosen = next(
|
|
282
|
-
(m for m in matches if len(_operands_of_key(m.key)) == operand_count),
|
|
283
|
-
min(matches, key=lambda m: len(_operands_of_key(m.key))),
|
|
284
|
-
)
|
|
298
|
+
chosen = _match_form(matches, operand_count)
|
|
285
299
|
return _cut_label(chosen.summary or "")
|
|
286
300
|
|
|
287
301
|
|
|
@@ -289,7 +303,13 @@ def _inlay_hints(conn, text: str, language_id: str, uri: str, start: int, end: i
|
|
|
289
303
|
db_path = conn.execute("PRAGMA database_list").fetchone()[2]
|
|
290
304
|
source_lines = [line.rstrip("\r") for line in text.split("\n")]
|
|
291
305
|
is_c_doc = _is_c_doc(language_id, uri)
|
|
292
|
-
|
|
306
|
+
# A .s/.S extension means GAS syntax: ";" stays a statement separator there
|
|
307
|
+
# even if the client tags the buffer with an assembly languageId.
|
|
308
|
+
semicolon_is_comment = (
|
|
309
|
+
not is_c_doc
|
|
310
|
+
and not uri.lower().endswith(".s")
|
|
311
|
+
and (language_id in ("asm", "nasm", "masm") or uri.lower().endswith((".asm", ".nasm")))
|
|
312
|
+
)
|
|
293
313
|
if is_c_doc:
|
|
294
314
|
candidates = [
|
|
295
315
|
(text.count("\n", 0, offset), segment)
|
|
@@ -386,10 +406,28 @@ def main() -> int:
|
|
|
386
406
|
line, character = params["position"]["line"], params["position"]["character"]
|
|
387
407
|
word = _word_at(text, line, character)
|
|
388
408
|
# In C/C++ only the asm strings hold instructions; intrinsics hover everywhere.
|
|
389
|
-
|
|
390
|
-
|
|
409
|
+
is_c_doc = _is_c_doc(session.languages.get(uri, ""), uri)
|
|
410
|
+
allow_instruction = not is_c_doc or (_in_asm_string(text, line, character))
|
|
411
|
+
operand_count = None
|
|
412
|
+
operand_text = ""
|
|
413
|
+
if word and allow_instruction:
|
|
414
|
+
source_lines = text.split("\n")
|
|
415
|
+
source_line = source_lines[line] if line < len(source_lines) else ""
|
|
416
|
+
operand_text = source_line
|
|
417
|
+
parsed = _mnemonic_from_asm_line(source_line)
|
|
418
|
+
if parsed and parsed[0].casefold() == word.casefold():
|
|
419
|
+
operand_count = parsed[1]
|
|
420
|
+
body = (
|
|
421
|
+
_hover_markdown(
|
|
422
|
+
conn,
|
|
423
|
+
word,
|
|
424
|
+
allow_instruction,
|
|
425
|
+
operand_count=operand_count,
|
|
426
|
+
operand_text=operand_text,
|
|
427
|
+
)
|
|
428
|
+
if conn and word
|
|
429
|
+
else None
|
|
391
430
|
)
|
|
392
|
-
body = _hover_markdown(conn, word, allow_instruction) if conn and word else None
|
|
393
431
|
_jsonrpc_write(
|
|
394
432
|
{
|
|
395
433
|
"jsonrpc": "2.0",
|
|
@@ -79,14 +79,14 @@ def _read_msg(proc, timeout_s=10):
|
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
class _Server:
|
|
82
|
-
def __init__(self, catalog):
|
|
82
|
+
def __init__(self, catalog, extra_env=None):
|
|
83
83
|
self.proc = subprocess.Popen(
|
|
84
84
|
[sys.executable, "-m", "simdref.lsp"],
|
|
85
85
|
stdin=subprocess.PIPE,
|
|
86
86
|
stdout=subprocess.PIPE,
|
|
87
87
|
stderr=subprocess.PIPE,
|
|
88
88
|
bufsize=0,
|
|
89
|
-
env={**os.environ, "SIMDREF_CATALOG": str(catalog)},
|
|
89
|
+
env={**os.environ, "SIMDREF_CATALOG": str(catalog), **(extra_env or {})},
|
|
90
90
|
)
|
|
91
91
|
self.next_id = 0
|
|
92
92
|
self.notifications = []
|
|
@@ -198,6 +198,49 @@ class LspEndToEndTests(unittest.TestCase):
|
|
|
198
198
|
self.assertIsNotNone(result, "hover on vfmadd231ps returned null")
|
|
199
199
|
self.assertIn("VFMADD231PS", result["contents"]["value"])
|
|
200
200
|
|
|
201
|
+
def test_hover_instruction_full_page_operand_matched(self):
|
|
202
|
+
self.server.open(ASM_URI, "asm", ASM_TEXT)
|
|
203
|
+
result = self.server.hover(ASM_URI, 5, 6)
|
|
204
|
+
self.assertIsNotNone(result, "hover on vaddps returned null")
|
|
205
|
+
value = result["contents"]["value"]
|
|
206
|
+
# The 3-operand YMM form, not the first masked (XMM, K, ...) row.
|
|
207
|
+
self.assertIn("VADDPS (YMM, YMM, YMM)", value)
|
|
208
|
+
self.assertNotIn("VADDPS (XMM, K, XMM, M128)", value)
|
|
209
|
+
self.assertIn("## Description", value)
|
|
210
|
+
self.assertIn("Add Packed Single Precision Floating-Point Values", value)
|
|
211
|
+
self.assertIn("## SIMD Floating-Point Exceptions", value)
|
|
212
|
+
self.assertIn("## instruction to intrinsic mapping", value)
|
|
213
|
+
self.assertIn("_mm256_add_ps", value)
|
|
214
|
+
# The per-uarch perf table as a Markdown table.
|
|
215
|
+
self.assertIn("| microarch |", value)
|
|
216
|
+
self.assertIn("| ZEN5 |", value)
|
|
217
|
+
# Links at the end: web deep link, uops.info, felixcloutier.
|
|
218
|
+
self.assertIn("https://simdref.diamondinoia.com/#VADDPS%20(YMM%2C%20YMM%2C%20YMM)", value)
|
|
219
|
+
self.assertIn("https://www.uops.info/html-instr/VADDPS_YMM_YMM_YMM.html", value)
|
|
220
|
+
self.assertIn("https://www.felixcloutier.com/x86/addps", value)
|
|
221
|
+
|
|
222
|
+
def test_hover_makes_no_network_call(self):
|
|
223
|
+
# The server is a subprocess, so block sockets there via sitecustomize.
|
|
224
|
+
import tempfile
|
|
225
|
+
|
|
226
|
+
with tempfile.TemporaryDirectory() as sitepkg:
|
|
227
|
+
Path(sitepkg, "sitecustomize.py").write_text(
|
|
228
|
+
"import socket\n"
|
|
229
|
+
"def _blocked(*a, **k): raise OSError('network blocked in test')\n"
|
|
230
|
+
"socket.socket = _blocked\n"
|
|
231
|
+
"socket.create_connection = _blocked\n"
|
|
232
|
+
)
|
|
233
|
+
server = _Server(
|
|
234
|
+
CATALOG,
|
|
235
|
+
{"PYTHONPATH": sitepkg + os.pathsep + os.environ.get("PYTHONPATH", "")},
|
|
236
|
+
)
|
|
237
|
+
self.addCleanup(server.stop)
|
|
238
|
+
server.start()
|
|
239
|
+
server.open(ASM_URI, "asm", ASM_TEXT)
|
|
240
|
+
result = server.hover(ASM_URI, 5, 6)
|
|
241
|
+
self.assertIsNotNone(result)
|
|
242
|
+
self.assertIn("VADDPS (YMM, YMM, YMM)", result["contents"]["value"])
|
|
243
|
+
|
|
201
244
|
def test_cpp_two_instructions_on_one_source_line_get_one_joined_hint(self):
|
|
202
245
|
text = 'asm volatile("vaddps %ymm1, %ymm2, %ymm0\\n\\tvmulps %ymm0, %ymm0, %ymm3");\n'
|
|
203
246
|
self.server.open(CPP_URI, "cpp", text)
|
|
@@ -240,6 +283,25 @@ class LspEndToEndTests(unittest.TestCase):
|
|
|
240
283
|
[h["label"] for h in hints],
|
|
241
284
|
["Add Packed Single Precision Floating-Point Values."],
|
|
242
285
|
)
|
|
286
|
+
# "adc eax, ebx ; j, k" parses as 4 operands in a .s file even when the client
|
|
287
|
+
# sends languageId "nasm": the 4-operand form label differs from the 2-operand one.
|
|
288
|
+
self.server.open(uri, "nasm", "adc eax, ebx ; j, k\n")
|
|
289
|
+
self.assertEqual(self.server.hints(uri)[0]["label"], "Adc instruction.")
|
|
290
|
+
|
|
291
|
+
def test_asm_semicolon_comment_in_dot_nasm_file(self):
|
|
292
|
+
uri = "file:///tmp/simdref_lsp_test.nasm"
|
|
293
|
+
self.server.open(uri, "asm", "adc eax, ebx ; j, k\n")
|
|
294
|
+
hints = self.server.hints(uri)
|
|
295
|
+
self.assertEqual([h["position"]["line"] for h in hints], [0])
|
|
296
|
+
# With ";" a comment the line is the 2-operand form, not the 4-operand parse.
|
|
297
|
+
self.assertEqual(hints[0]["label"], "Add With Carry.")
|
|
298
|
+
|
|
299
|
+
def test_asm_semicolon_comment_from_language_id_masm(self):
|
|
300
|
+
uri = "file:///tmp/simdref_lsp_test.txt"
|
|
301
|
+
self.server.open(uri, "masm", "adc eax, ebx ; j, k\n")
|
|
302
|
+
hints = self.server.hints(uri)
|
|
303
|
+
self.assertEqual([h["position"]["line"] for h in hints], [0])
|
|
304
|
+
self.assertEqual(hints[0]["label"], "Add With Carry.")
|
|
243
305
|
|
|
244
306
|
def test_hint_avx512_mask_and_broadcast_operands(self):
|
|
245
307
|
uri = "file:///tmp/simdref_lsp_test_zmm.s"
|
|
@@ -36,7 +36,7 @@ class LspWebTests(unittest.TestCase):
|
|
|
36
36
|
markdown = _hover_markdown(self._conn, "vadd.vv")
|
|
37
37
|
self.assertIsNotNone(markdown)
|
|
38
38
|
self.assertIn("vadd.vv", markdown)
|
|
39
|
-
self.assertIn("
|
|
39
|
+
self.assertIn("**isa.** V", markdown)
|
|
40
40
|
|
|
41
41
|
def test_sqlite_runtime_preserves_riscv_counts_sections_and_policy_metadata(self):
|
|
42
42
|
intrinsic_count = self._conn.execute(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|