simdref 0.0.14__tar.gz → 0.0.15__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.14/src/simdref.egg-info → simdref-0.0.15}/PKG-INFO +1 -1
- {simdref-0.0.14 → simdref-0.0.15}/pyproject.toml +1 -1
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/annotate.py +22 -2
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/cli.py +13 -1
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/lsp.py +42 -8
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/perf.py +22 -2
- {simdref-0.0.14 → simdref-0.0.15/src/simdref.egg-info}/PKG-INFO +1 -1
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref.egg-info/SOURCES.txt +1 -0
- simdref-0.0.15/tests/test_no_catalog_row.py +208 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_perf.py +31 -0
- {simdref-0.0.14 → simdref-0.0.15}/LICENSE +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/README.md +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/setup.cfg +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/__init__.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/__main__.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/arm_instructions.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/asm_identity.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/display.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/export.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/filters.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/ingest.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/ingest_catalog.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/ingest_pdf.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/ingest_sources.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/manpages.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/models.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/pdfparse/__init__.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/pdfparse/base.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/pdfparse/intel.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/pdfparse/registry.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/pdfparse/types.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/pdfrefs.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/perf_sources/__init__.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/perf_sources/cores.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/perf_sources/llvm_mca.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/perf_sources/llvm_scheduling.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/perf_sources/merge.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/queries.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/riscv.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/search.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/storage.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/tui.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref/ui_labels.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref.egg-info/dependency_links.txt +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref.egg-info/entry_points.txt +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref.egg-info/requires.txt +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/src/simdref.egg-info/top_level.txt +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_annotate.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_annotate_positions.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_asm_identity.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_asm_wire.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_audit_coverage.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_auto_update_on_version_change.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_cli_bare_query.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_cli_bootstrap_progress.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_cli_help.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_cli_llm.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_coverage_live.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_coverage_parity.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_display.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_export.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_filters.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_ingest_enc_fields.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_ingest_sources.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_intel_operation_and_url.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_issue2_fixes.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_issues_23_24_25.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_leftover_arm_form_pick.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_leftover_arm_postindex.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_leftover_att_movx.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_leftover_autoupdate.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_leftover_catalog_gaps.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_leftover_ingest_isa_merge.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_leftover_intel_size.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_leftover_perf_join.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_leftover_riscv_shared.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_lsp_stdio.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_lsp_web.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_models.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_pdfparse.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_perf_sources.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_presentation.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_preset_persistence.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_riscv_docs_pages.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_search.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_search_pushdown.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_source_kind_filter.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_source_validation.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_storage_payload.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_storage_schema.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_tui.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_ui_labels_parity.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_vendor_riscv_categories.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_vendor_riscv_sources.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/tests/test_version_matches_pyproject.py +0 -0
- {simdref-0.0.14 → simdref-0.0.15}/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.15"
|
|
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"
|
|
@@ -17,7 +17,7 @@ from enum import Enum
|
|
|
17
17
|
from typing import Any, Iterable, Iterator
|
|
18
18
|
|
|
19
19
|
from simdref import perf
|
|
20
|
-
from simdref.lsp import identity_keys
|
|
20
|
+
from simdref.lsp import NO_CATALOG_ROW, identity_keys
|
|
21
21
|
from simdref.models import InstructionRecord
|
|
22
22
|
from simdref.storage import load_instruction_from_db, load_instructions_by_mnemonic_from_db
|
|
23
23
|
|
|
@@ -460,7 +460,9 @@ class PerfSummary:
|
|
|
460
460
|
def _per_arch_value(details: dict[str, Any], value_fn) -> float | None:
|
|
461
461
|
for v in value_fn(details):
|
|
462
462
|
try:
|
|
463
|
-
|
|
463
|
+
# Latency ranges render as "min-max" strings (simdref#50); the
|
|
464
|
+
# float view takes the min so aggregates stay numeric.
|
|
465
|
+
return float(str(v).split("-", 1)[0])
|
|
464
466
|
except (TypeError, ValueError):
|
|
465
467
|
continue
|
|
466
468
|
return None
|
|
@@ -688,6 +690,24 @@ def _annotate_instruction(
|
|
|
688
690
|
"""Return the rendered output line and an optional JSON record."""
|
|
689
691
|
if stats is not None:
|
|
690
692
|
stats["parsed"] = stats.get("parsed", 0) + 1
|
|
693
|
+
if identity_key == NO_CATALOG_ROW:
|
|
694
|
+
# Byte identity identified the form and the catalog has no row for
|
|
695
|
+
# it (issue #49: uops.info carries no AVX reg-reg VMOVAPD/VMOVUPS):
|
|
696
|
+
# report the miss by name, never guess a neighbour form below.
|
|
697
|
+
if stats is not None:
|
|
698
|
+
stats["no_row"] = stats.get("no_row", 0) + 1
|
|
699
|
+
no_row: dict[str, Any] = {
|
|
700
|
+
"mnemonic": parsed.mnemonic,
|
|
701
|
+
"known": False,
|
|
702
|
+
"no_measured_row": True,
|
|
703
|
+
}
|
|
704
|
+
if parsed.address is not None:
|
|
705
|
+
no_row["address"] = f"0x{parsed.address:x}"
|
|
706
|
+
if parsed.source_file:
|
|
707
|
+
no_row["source_file"] = parsed.source_file
|
|
708
|
+
if parsed.source_line is not None:
|
|
709
|
+
no_row["source_line"] = parsed.source_line
|
|
710
|
+
return f"{parsed.raw} # {parsed.mnemonic}: no measured row", no_row
|
|
691
711
|
# Byte identity wins over the form pick: the record is what the line
|
|
692
712
|
# assembles to, and an empty row keeps the old pick unchanged.
|
|
693
713
|
record = load_instruction_from_db(conn, identity_key) if identity_key else None
|
|
@@ -1897,9 +1897,21 @@ def _build_llm_payload(
|
|
|
1897
1897
|
}
|
|
1898
1898
|
# Byte identity wins over the name lookups below: a full assembly line
|
|
1899
1899
|
# resolves to the one row its bytes name.
|
|
1900
|
-
from simdref.lsp import identity_keys
|
|
1900
|
+
from simdref.lsp import NO_CATALOG_ROW, identity_keys
|
|
1901
1901
|
|
|
1902
1902
|
keys = identity_keys([query_str], None, conn)
|
|
1903
|
+
if keys[0] == NO_CATALOG_ROW:
|
|
1904
|
+
# Identified form the catalog lacks (issue #49): report the miss by
|
|
1905
|
+
# name, never join the line to a neighbour form's row.
|
|
1906
|
+
mnemonic = query_str.split(None, 1)[0]
|
|
1907
|
+
return {
|
|
1908
|
+
"query": query_str,
|
|
1909
|
+
"mode": "exact",
|
|
1910
|
+
"match_kind": "instruction",
|
|
1911
|
+
"detail": f"{mnemonic}: no measured row",
|
|
1912
|
+
"results": [],
|
|
1913
|
+
**meta,
|
|
1914
|
+
}
|
|
1903
1915
|
if keys[0]:
|
|
1904
1916
|
item = load_instruction_from_db(conn, keys[0])
|
|
1905
1917
|
if item is not None:
|
|
@@ -963,6 +963,14 @@ def _document_arch(
|
|
|
963
963
|
return name, ("intel" if syntax else "att"), keep
|
|
964
964
|
|
|
965
965
|
|
|
966
|
+
# identity_keys slot value when byte identity ran on a schema-14 catalog and
|
|
967
|
+
# identified the instruction, but the catalog has no row for that form. Not a
|
|
968
|
+
# db_key: callers show the line as having no measured row and never fall back
|
|
969
|
+
# to the legacy pick. The legacy pick stays only where byte identity could
|
|
970
|
+
# not run (missing tools, a schema-13 catalog, a declined document).
|
|
971
|
+
NO_CATALOG_ROW = "?:no-catalog-row"
|
|
972
|
+
|
|
973
|
+
|
|
966
974
|
@functools.lru_cache(maxsize=32)
|
|
967
975
|
def _identity_batch(
|
|
968
976
|
texts: tuple[str, ...], intel: bool, syntax: bool, catalog: str, mtime_ns: int
|
|
@@ -989,22 +997,36 @@ def _identity_batch(
|
|
|
989
997
|
for index, held, result in zip(probed, keep, results):
|
|
990
998
|
# The text check keeps a row on its own line even if a text carries
|
|
991
999
|
# its own line break.
|
|
992
|
-
if
|
|
1000
|
+
if not held or result.text != texts[index]:
|
|
1001
|
+
continue
|
|
1002
|
+
if result.row is not None:
|
|
993
1003
|
slots[index] = result.row.db_key
|
|
1004
|
+
elif result.reason == "no catalog row" or result.reason.startswith(
|
|
1005
|
+
("no catalog row (iform ", "no catalog row (splits emptied ")
|
|
1006
|
+
):
|
|
1007
|
+
# Byte identity identified the form and the catalog lacks its row
|
|
1008
|
+
# (e.g. uops.info carries no AVX reg-reg VMOVAPD): mark the miss.
|
|
1009
|
+
# A fixup placeholder or a foreign decode mode keeps the line on
|
|
1010
|
+
# the old matcher: the row can exist, it is just not byte-proven.
|
|
1011
|
+
slots[index] = NO_CATALOG_ROW
|
|
994
1012
|
return tuple(slots)
|
|
995
1013
|
|
|
996
1014
|
|
|
997
1015
|
def identity_keys(
|
|
998
1016
|
texts: Sequence[str], intel: bool | None, conn: sqlite3.Connection
|
|
999
1017
|
) -> list[str | None]:
|
|
1000
|
-
"""Map each text to the catalog db_key its encoded bytes name, or
|
|
1018
|
+
"""Map each text to the catalog db_key its encoded bytes name, or a mark.
|
|
1001
1019
|
|
|
1002
1020
|
One shared entry for hover, inlay hints, annotate and the llm query: one
|
|
1003
1021
|
llvm-mc run per document batch, cached per document content. A None means
|
|
1004
|
-
byte identity
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1022
|
+
byte identity could not run for the line (tools missing, a schema-13
|
|
1023
|
+
catalog, a document byte identity declined, or a line that votes another
|
|
1024
|
+
arch) and the caller's old matcher answers that line unchanged.
|
|
1025
|
+
NO_CATALOG_ROW means byte identity identified the instruction and the
|
|
1026
|
+
catalog has no row for that form: the caller reports no measured row and
|
|
1027
|
+
never guesses a neighbour form. ``intel`` is the file-type syntax flag;
|
|
1028
|
+
None infers the x86 syntax from the text, and keeps the operand votes
|
|
1029
|
+
neutral so a bare AArch64 or RISC-V line never reads as forced x86.
|
|
1008
1030
|
"""
|
|
1009
1031
|
if not texts:
|
|
1010
1032
|
return []
|
|
@@ -1079,6 +1101,10 @@ def _hover_markdown(
|
|
|
1079
1101
|
|
|
1080
1102
|
if not allow_instruction:
|
|
1081
1103
|
return None
|
|
1104
|
+
if identity_key == NO_CATALOG_ROW:
|
|
1105
|
+
# Byte identity identified the form and the catalog has no row for
|
|
1106
|
+
# it: say so by name, never guess a neighbour form below.
|
|
1107
|
+
return f"`{word}`: no measured row"
|
|
1082
1108
|
# Byte identity wins over every matcher pick below: the row is what the
|
|
1083
1109
|
# line assembles to. None keeps the old matcher on its exact-name path.
|
|
1084
1110
|
instruction = load_instruction_from_db(conn, identity_key) if identity_key else None
|
|
@@ -1375,14 +1401,22 @@ def _inlay_hints(conn, text: str, language_id: str, uri: str, start: int, end: i
|
|
|
1375
1401
|
semicolon_is_comment = _semicolon_is_comment(language_id, uri)
|
|
1376
1402
|
candidates = _asm_candidates(text, language_id, uri)
|
|
1377
1403
|
keys = identity_keys([segment for _line, segment in candidates], semicolon_is_comment, conn)
|
|
1378
|
-
records = {
|
|
1404
|
+
records = {
|
|
1405
|
+
key: load_instruction_from_db(conn, key)
|
|
1406
|
+
for key in set(keys)
|
|
1407
|
+
if key is not None and key != NO_CATALOG_ROW
|
|
1408
|
+
}
|
|
1379
1409
|
hints: dict[int, list[str]] = {} # every instruction on the source line feeds its hint
|
|
1380
1410
|
labels: dict[str, str | None] = {} # one matcher run per distinct segment, not per line
|
|
1381
1411
|
for (line, segment), key in zip(candidates, keys):
|
|
1382
1412
|
if not start <= line <= end:
|
|
1383
1413
|
continue
|
|
1384
1414
|
record = records.get(key) if key else None
|
|
1385
|
-
if
|
|
1415
|
+
if key == NO_CATALOG_ROW:
|
|
1416
|
+
# Identified form the catalog lacks: name it, never the old pick.
|
|
1417
|
+
parsed = _mnemonic_from_asm_line(segment, semicolon_is_comment)
|
|
1418
|
+
label = f"{parsed[0]}: no measured row" if parsed else None
|
|
1419
|
+
elif record is not None:
|
|
1386
1420
|
label = _cut_label(record.summary or "")
|
|
1387
1421
|
elif segment in labels:
|
|
1388
1422
|
label = labels[segment]
|
|
@@ -62,8 +62,11 @@ def latency_cycle_values(latencies: list[dict[str, Any]]) -> list[str]:
|
|
|
62
62
|
"""Collect unique cycle-count strings from a list of latency dicts.
|
|
63
63
|
|
|
64
64
|
Each *latency* dict may contain keys like ``cycles``, ``cycles_mem``,
|
|
65
|
-
``cycles_addr``, or ``cycles_addr_index``.
|
|
66
|
-
|
|
65
|
+
``cycles_addr``, or ``cycles_addr_index``. uops.info divide/sqrt
|
|
66
|
+
measurements carry a ``min_cycles`` / ``max_cycles`` pair instead (and
|
|
67
|
+
``_mem`` / ``_addr`` / ``_addr_index`` variants); a pair renders as one
|
|
68
|
+
range string (``"13"`` when equal, ``"13-15"`` otherwise). Values are
|
|
69
|
+
collected in order, skipping duplicates.
|
|
67
70
|
|
|
68
71
|
Returns:
|
|
69
72
|
De-duplicated list of cycle-count strings (may be empty).
|
|
@@ -81,6 +84,23 @@ def latency_cycle_values(latencies: list[dict[str, Any]]) -> list[str]:
|
|
|
81
84
|
continue
|
|
82
85
|
if value not in values:
|
|
83
86
|
values.append(value)
|
|
87
|
+
for suffix in ("", "_mem", "_addr", "_addr_index"):
|
|
88
|
+
low = latency.get("min_cycles" + suffix, "")
|
|
89
|
+
high = latency.get("max_cycles" + suffix, "")
|
|
90
|
+
if not low and not high:
|
|
91
|
+
continue
|
|
92
|
+
low_only = low or high
|
|
93
|
+
if (
|
|
94
|
+
suffix == "_mem"
|
|
95
|
+
and latency.get("min_cycles_mem_is_upper_bound") == "1"
|
|
96
|
+
and _is_numeric(low_only)
|
|
97
|
+
and float(low_only) == 0
|
|
98
|
+
):
|
|
99
|
+
# Same not-measured convention as cycles_mem=0 above.
|
|
100
|
+
continue
|
|
101
|
+
value = f"{low}-{high}" if low and high and low != high else low_only
|
|
102
|
+
if value not in values:
|
|
103
|
+
values.append(value)
|
|
84
104
|
return values
|
|
85
105
|
|
|
86
106
|
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Issue #49: an identified form the catalog lacks reports no row.
|
|
2
|
+
|
|
3
|
+
uops.info has no AVX register-register VMOVAPD/VMOVUPS rows, so byte identity
|
|
4
|
+
correctly finds no row for ``vmovapd %xmm1, %xmm0``. Annotate, hover, hints
|
|
5
|
+
and the llm query must say "no measured row" by name and never fall back to
|
|
6
|
+
the legacy nearest-form pick (which joined the line to the store row, lat
|
|
7
|
+
12c). The legacy pick stays only where byte identity could not run.
|
|
8
|
+
|
|
9
|
+
Tool-dependent tests skip cleanly when llvm-mc or the XED decoder is absent;
|
|
10
|
+
nothing fakes a pass.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
from test_asm_wire import _fail
|
|
19
|
+
|
|
20
|
+
from simdref import annotate as annotate_mod
|
|
21
|
+
from simdref import cli as cli_mod
|
|
22
|
+
from simdref import lsp
|
|
23
|
+
from simdref.asm_identity import _tool_path
|
|
24
|
+
from simdref.lsp import NO_CATALOG_ROW, identity_keys
|
|
25
|
+
from simdref.models import Catalog, InstructionRecord, SourceVersion
|
|
26
|
+
from simdref.storage import build_sqlite, open_db
|
|
27
|
+
|
|
28
|
+
LLVM_MC = _tool_path("SIMDREF_LLVM_MC", "llvm-mc") or ""
|
|
29
|
+
XED = _tool_path("SIMDREF_XED", "xed") or ""
|
|
30
|
+
needs_xed = pytest.mark.skipif(not (LLVM_MC and XED), reason="llvm-mc or xed absent")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# A fixture catalog with only the store rows: the reg-reg iforms
|
|
34
|
+
# (VMOVAPD_XMMdq_XMMdq_28, VMOVUPS_XMMdq_XMMdq_10) are absent, like the real
|
|
35
|
+
# catalog. Measured SKL latency 12c mirrors the issue's wrong join.
|
|
36
|
+
def _store_row(mnemonic: str, iform: str) -> InstructionRecord:
|
|
37
|
+
return InstructionRecord(
|
|
38
|
+
mnemonic=mnemonic,
|
|
39
|
+
form=f"{mnemonic} (M128, XMM)",
|
|
40
|
+
summary=f"Store via {mnemonic}.",
|
|
41
|
+
architecture="x86",
|
|
42
|
+
arch_details={
|
|
43
|
+
"SKL": {
|
|
44
|
+
"latencies": [{"cycles": "12"}],
|
|
45
|
+
"measurement": {"TP": "1.0"},
|
|
46
|
+
"source_kind": "measured",
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
metadata={"iform": iform, "split_attrs": "{}"},
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _store_db(tmp_path):
|
|
54
|
+
rows = [
|
|
55
|
+
_store_row("VMOVAPD", "VMOVAPD_MEMdq_XMMdq"),
|
|
56
|
+
_store_row("VMOVUPS", "VMOVUPS_MEMdq_XMMdq"),
|
|
57
|
+
]
|
|
58
|
+
db = tmp_path / "store-only.db"
|
|
59
|
+
build_sqlite(
|
|
60
|
+
Catalog(
|
|
61
|
+
intrinsics=[],
|
|
62
|
+
instructions=rows,
|
|
63
|
+
sources=[
|
|
64
|
+
SourceVersion(source="t", version="t", fetched_at="2025-01-01", url="test://")
|
|
65
|
+
],
|
|
66
|
+
generated_at="2025-01-01",
|
|
67
|
+
),
|
|
68
|
+
db,
|
|
69
|
+
)
|
|
70
|
+
return db
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
REG_REG = ["vmovapd %xmm1, %xmm0", "vmovups %xmm3, %xmm2"]
|
|
74
|
+
STORE = ["vmovapd %xmm0, (%rax)", "vmovups %xmm2, (%rsi)"]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@needs_xed
|
|
78
|
+
class TestNoCatalogRow:
|
|
79
|
+
def test_identity_marks_the_miss(self, tmp_path):
|
|
80
|
+
conn = open_db(path=_store_db(tmp_path))
|
|
81
|
+
try:
|
|
82
|
+
keys = identity_keys(REG_REG + STORE, False, conn)
|
|
83
|
+
store = [open_key for open_key in keys[2:]]
|
|
84
|
+
finally:
|
|
85
|
+
conn.close()
|
|
86
|
+
assert keys[:2] == [NO_CATALOG_ROW, NO_CATALOG_ROW]
|
|
87
|
+
assert all(key and key != NO_CATALOG_ROW for key in store)
|
|
88
|
+
|
|
89
|
+
def test_annotate_reports_no_row(self, tmp_path, monkeypatch):
|
|
90
|
+
conn = open_db(path=_store_db(tmp_path))
|
|
91
|
+
monkeypatch.setattr(annotate_mod, "lookup", _fail("the old lookup ran"))
|
|
92
|
+
try:
|
|
93
|
+
out = "".join(
|
|
94
|
+
annotate_mod.annotate_stream(
|
|
95
|
+
[line + "\n" for line in REG_REG + STORE],
|
|
96
|
+
opts=annotate_mod.AnnotateOptions(),
|
|
97
|
+
conn=conn,
|
|
98
|
+
)
|
|
99
|
+
).splitlines()
|
|
100
|
+
finally:
|
|
101
|
+
conn.close()
|
|
102
|
+
assert "# vmovapd: no measured row" in out[0]
|
|
103
|
+
assert "# vmovups: no measured row" in out[1]
|
|
104
|
+
for line in (out[0], out[1]):
|
|
105
|
+
assert "Store via" not in line
|
|
106
|
+
assert "lat=" not in line
|
|
107
|
+
# A store line still gets the store row.
|
|
108
|
+
assert "Store via VMOVAPD." in out[2]
|
|
109
|
+
assert "lat=12.0c" in out[2]
|
|
110
|
+
assert "Store via VMOVUPS." in out[3]
|
|
111
|
+
|
|
112
|
+
def test_annotate_json_marks_no_row(self, tmp_path, monkeypatch):
|
|
113
|
+
conn = open_db(path=_store_db(tmp_path))
|
|
114
|
+
monkeypatch.setattr(annotate_mod, "lookup", _fail("the old lookup ran"))
|
|
115
|
+
try:
|
|
116
|
+
out = "".join(
|
|
117
|
+
annotate_mod.annotate_stream(
|
|
118
|
+
[REG_REG[0] + "\n", STORE[0] + "\n"],
|
|
119
|
+
opts=annotate_mod.AnnotateOptions(fmt="json"),
|
|
120
|
+
conn=conn,
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
finally:
|
|
124
|
+
conn.close()
|
|
125
|
+
records = json.loads(out)
|
|
126
|
+
assert records[0] == {
|
|
127
|
+
"mnemonic": "vmovapd",
|
|
128
|
+
"known": False,
|
|
129
|
+
"no_measured_row": True,
|
|
130
|
+
}
|
|
131
|
+
assert records[1]["known"] is True
|
|
132
|
+
|
|
133
|
+
def test_hover_reports_no_row(self, tmp_path, monkeypatch):
|
|
134
|
+
conn = open_db(path=_store_db(tmp_path))
|
|
135
|
+
monkeypatch.setattr(lsp, "_match_form", _fail("the old form matcher ran"))
|
|
136
|
+
monkeypatch.setattr(lsp, "_lookup_mnemonic", _fail("the old matcher ran"))
|
|
137
|
+
try:
|
|
138
|
+
key = identity_keys(REG_REG, False, conn)[0]
|
|
139
|
+
body = lsp._hover_markdown(conn, "vmovapd", identity_key=key)
|
|
140
|
+
finally:
|
|
141
|
+
conn.close()
|
|
142
|
+
assert body == "`vmovapd`: no measured row"
|
|
143
|
+
|
|
144
|
+
def test_hint_reports_no_row(self, tmp_path, monkeypatch):
|
|
145
|
+
conn = open_db(path=_store_db(tmp_path))
|
|
146
|
+
monkeypatch.setattr(lsp, "_hint_label", _fail("the old hint matcher ran"))
|
|
147
|
+
try:
|
|
148
|
+
hints = lsp._inlay_hints(
|
|
149
|
+
conn, REG_REG[0] + "\n" + STORE[0] + "\n", "asm", "file:///x.s", 0, 1
|
|
150
|
+
)
|
|
151
|
+
finally:
|
|
152
|
+
conn.close()
|
|
153
|
+
assert [hint["label"] for hint in hints] == [
|
|
154
|
+
"vmovapd: no measured row",
|
|
155
|
+
"Store via VMOVAPD.",
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
def test_llm_reports_no_row(self, tmp_path, monkeypatch):
|
|
159
|
+
conn = open_db(path=_store_db(tmp_path))
|
|
160
|
+
monkeypatch.setattr(cli_mod, "_find_instructions_fast", _fail("the old name lookup ran"))
|
|
161
|
+
monkeypatch.setattr(cli_mod, "_search_runtime", _fail("the old search ran"))
|
|
162
|
+
try:
|
|
163
|
+
payload = cli_mod._build_llm_payload(conn, REG_REG[0], 8, None, None, None)
|
|
164
|
+
finally:
|
|
165
|
+
conn.close()
|
|
166
|
+
assert payload["mode"] == "exact"
|
|
167
|
+
assert payload["match_kind"] == "instruction"
|
|
168
|
+
assert payload["detail"] == "vmovapd: no measured row"
|
|
169
|
+
assert payload["results"] == []
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class TestMarkerPathNeedsNoTools:
|
|
173
|
+
def test_marker_never_calls_the_legacy_pick(self, tmp_path, monkeypatch):
|
|
174
|
+
# The marker reaches annotate even with tools absent; the old lookup
|
|
175
|
+
# must stay silent either way.
|
|
176
|
+
conn = open_db(path=_store_db(tmp_path))
|
|
177
|
+
monkeypatch.setattr(lsp, "_identity_batch", lambda *a, **k: (NO_CATALOG_ROW,))
|
|
178
|
+
monkeypatch.setattr(annotate_mod, "lookup", _fail("the old lookup ran"))
|
|
179
|
+
try:
|
|
180
|
+
out = "".join(
|
|
181
|
+
annotate_mod.annotate_stream(
|
|
182
|
+
[REG_REG[0] + "\n"],
|
|
183
|
+
opts=annotate_mod.AnnotateOptions(),
|
|
184
|
+
conn=conn,
|
|
185
|
+
stats={},
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
finally:
|
|
189
|
+
conn.close()
|
|
190
|
+
assert "# vmovapd: no measured row" in out
|
|
191
|
+
|
|
192
|
+
def test_no_marker_keeps_the_legacy_pick(self, tmp_path, monkeypatch):
|
|
193
|
+
# Byte identity could not run (None): the old matcher answers exactly
|
|
194
|
+
# as before the fix — here the issue's line with only the store row.
|
|
195
|
+
conn = open_db(path=_store_db(tmp_path))
|
|
196
|
+
monkeypatch.setattr(lsp, "_identity_batch", lambda *a, **k: (None,))
|
|
197
|
+
try:
|
|
198
|
+
out = "".join(
|
|
199
|
+
annotate_mod.annotate_stream(
|
|
200
|
+
[REG_REG[0] + "\n"],
|
|
201
|
+
opts=annotate_mod.AnnotateOptions(),
|
|
202
|
+
conn=conn,
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
finally:
|
|
206
|
+
conn.close()
|
|
207
|
+
assert "no measured row" not in out
|
|
208
|
+
assert out.strip().startswith("vmovapd")
|
|
@@ -68,6 +68,37 @@ class PerfTests(unittest.TestCase):
|
|
|
68
68
|
latencies = [{"cycles_mem": "0"}]
|
|
69
69
|
self.assertEqual(latency_cycle_values(latencies), ["0"])
|
|
70
70
|
|
|
71
|
+
def test_latency_cycle_values_reads_min_max_pair(self):
|
|
72
|
+
# simdref#50: uops.info divide/sqrt rows carry only a min/max range.
|
|
73
|
+
latencies = [
|
|
74
|
+
{"min_cycles": "13", "max_cycles": "15", "min_cycles_is_upper_bound": "1"},
|
|
75
|
+
{"min_cycles": "13", "max_cycles": "15", "min_cycles_is_upper_bound": "1"},
|
|
76
|
+
]
|
|
77
|
+
self.assertEqual(latency_cycle_values(latencies), ["13-15"])
|
|
78
|
+
|
|
79
|
+
def test_latency_cycle_values_min_max_equal(self):
|
|
80
|
+
self.assertEqual(latency_cycle_values([{"min_cycles": "7", "max_cycles": "7"}]), ["7"])
|
|
81
|
+
|
|
82
|
+
def test_latency_cycle_values_min_only(self):
|
|
83
|
+
self.assertEqual(latency_cycle_values([{"min_cycles": "7"}]), ["7"])
|
|
84
|
+
|
|
85
|
+
def test_latency_cycle_values_drops_upper_bound_zero_mem_range(self):
|
|
86
|
+
latencies = [{"min_cycles_mem": "0", "min_cycles_mem_is_upper_bound": "1"}]
|
|
87
|
+
self.assertEqual(latency_cycle_values(latencies), [])
|
|
88
|
+
|
|
89
|
+
def test_min_max_pair_flows_to_every_output(self):
|
|
90
|
+
# simdref#50: the perf-root fix must reach every caller output: the
|
|
91
|
+
# string picks, the variant summary and the float annotate view.
|
|
92
|
+
from simdref.annotate import _latency_for, _per_arch_value
|
|
93
|
+
|
|
94
|
+
details = {"latencies": [{"min_cycles": "13", "max_cycles": "15"}]}
|
|
95
|
+
arch_details = {"MTL-P": details}
|
|
96
|
+
self.assertEqual(best_latency(arch_details), "13-15")
|
|
97
|
+
self.assertEqual(best_latency_labeled(arch_details).value, "13-15")
|
|
98
|
+
lat, _ = variant_perf_summary(arch_details)
|
|
99
|
+
self.assertEqual(lat, "13-15")
|
|
100
|
+
self.assertEqual(_per_arch_value(details, _latency_for), 13.0)
|
|
101
|
+
|
|
71
102
|
def test_best_numeric_picks_smallest(self):
|
|
72
103
|
self.assertEqual(best_numeric(["5", "3", "7"]), "3")
|
|
73
104
|
self.assertEqual(best_numeric(["1.5", "2.0"]), "1.5")
|
|
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
|
|
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
|