simdref 0.0.1__tar.gz → 0.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. {simdref-0.0.1/src/simdref.egg-info → simdref-0.0.4}/PKG-INFO +3 -2
  2. {simdref-0.0.1 → simdref-0.0.4}/README.md +1 -1
  3. {simdref-0.0.1 → simdref-0.0.4}/pyproject.toml +2 -1
  4. simdref-0.0.4/src/simdref/__init__.py +10 -0
  5. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/annotate.py +3 -2
  6. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/cli.py +107 -3
  7. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/ingest_catalog.py +24 -0
  8. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/perf.py +8 -1
  9. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/storage.py +21 -1
  10. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/templates/app.js +155 -28
  11. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/tui.py +61 -21
  12. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/web.py +60 -15
  13. {simdref-0.0.1 → simdref-0.0.4/src/simdref.egg-info}/PKG-INFO +3 -2
  14. {simdref-0.0.1 → simdref-0.0.4}/src/simdref.egg-info/SOURCES.txt +5 -0
  15. {simdref-0.0.1 → simdref-0.0.4}/src/simdref.egg-info/requires.txt +1 -0
  16. {simdref-0.0.1 → simdref-0.0.4}/tests/test_annotate.py +16 -0
  17. simdref-0.0.4/tests/test_auto_update_on_version_change.py +174 -0
  18. {simdref-0.0.1 → simdref-0.0.4}/tests/test_cli_llm.py +19 -0
  19. simdref-0.0.4/tests/test_intel_operation_and_url.py +119 -0
  20. {simdref-0.0.1 → simdref-0.0.4}/tests/test_lsp_web.py +21 -17
  21. {simdref-0.0.1 → simdref-0.0.4}/tests/test_perf.py +21 -0
  22. {simdref-0.0.1 → simdref-0.0.4}/tests/test_presentation.py +21 -8
  23. simdref-0.0.4/tests/test_search_index_js.py +34 -0
  24. {simdref-0.0.1 → simdref-0.0.4}/tests/test_storage_schema.py +3 -3
  25. simdref-0.0.4/tests/test_version_matches_pyproject.py +32 -0
  26. simdref-0.0.4/tests/test_web_e2e.py +201 -0
  27. simdref-0.0.1/src/simdref/__init__.py +0 -5
  28. {simdref-0.0.1 → simdref-0.0.4}/LICENSE +0 -0
  29. {simdref-0.0.1 → simdref-0.0.4}/setup.cfg +0 -0
  30. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/__main__.py +0 -0
  31. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/arm_instructions.py +0 -0
  32. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/display.py +0 -0
  33. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/filters.py +0 -0
  34. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/ingest.py +0 -0
  35. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/ingest_pdf.py +0 -0
  36. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/ingest_sources.py +0 -0
  37. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/lsp.py +0 -0
  38. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/manpages.py +0 -0
  39. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/models.py +0 -0
  40. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/pdfparse/__init__.py +0 -0
  41. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/pdfparse/base.py +0 -0
  42. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/pdfparse/intel.py +0 -0
  43. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/pdfparse/registry.py +0 -0
  44. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/pdfparse/types.py +0 -0
  45. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/pdfrefs.py +0 -0
  46. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/perf_sources/__init__.py +0 -0
  47. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/perf_sources/cores.py +0 -0
  48. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/perf_sources/llvm_mca.py +0 -0
  49. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/perf_sources/llvm_scheduling.py +0 -0
  50. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/perf_sources/merge.py +0 -0
  51. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/profile/__init__.py +0 -0
  52. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/profile/adapters/__init__.py +0 -0
  53. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/profile/adapters/exegesis.py +0 -0
  54. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/profile/adapters/mca.py +0 -0
  55. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/profile/adapters/perf.py +0 -0
  56. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/profile/adapters/uprof.py +0 -0
  57. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/profile/adapters/vtune.py +0 -0
  58. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/profile/adapters/xctrace.py +0 -0
  59. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/profile/hotloop.py +0 -0
  60. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/profile/merge.py +0 -0
  61. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/profile/model.py +0 -0
  62. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/profile/orchestrate.py +0 -0
  63. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/profile/registry.py +0 -0
  64. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/queries.py +0 -0
  65. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/riscv.py +0 -0
  66. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/search.py +0 -0
  67. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/templates/__init__.py +0 -0
  68. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/templates/favicon.svg +0 -0
  69. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/templates/index.html +0 -0
  70. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/templates/logo.svg +0 -0
  71. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/templates/style.css +0 -0
  72. {simdref-0.0.1 → simdref-0.0.4}/src/simdref/ui_labels.py +0 -0
  73. {simdref-0.0.1 → simdref-0.0.4}/src/simdref.egg-info/dependency_links.txt +0 -0
  74. {simdref-0.0.1 → simdref-0.0.4}/src/simdref.egg-info/entry_points.txt +0 -0
  75. {simdref-0.0.1 → simdref-0.0.4}/src/simdref.egg-info/top_level.txt +0 -0
  76. {simdref-0.0.1 → simdref-0.0.4}/tests/test_audit_coverage.py +0 -0
  77. {simdref-0.0.1 → simdref-0.0.4}/tests/test_cli_bare_query.py +0 -0
  78. {simdref-0.0.1 → simdref-0.0.4}/tests/test_cli_bootstrap_progress.py +0 -0
  79. {simdref-0.0.1 → simdref-0.0.4}/tests/test_cli_help.py +0 -0
  80. {simdref-0.0.1 → simdref-0.0.4}/tests/test_coverage_live.py +0 -0
  81. {simdref-0.0.1 → simdref-0.0.4}/tests/test_coverage_parity.py +0 -0
  82. {simdref-0.0.1 → simdref-0.0.4}/tests/test_display.py +0 -0
  83. {simdref-0.0.1 → simdref-0.0.4}/tests/test_filters.py +0 -0
  84. {simdref-0.0.1 → simdref-0.0.4}/tests/test_ingest_sources.py +0 -0
  85. {simdref-0.0.1 → simdref-0.0.4}/tests/test_issue2_fixes.py +0 -0
  86. {simdref-0.0.1 → simdref-0.0.4}/tests/test_models.py +0 -0
  87. {simdref-0.0.1 → simdref-0.0.4}/tests/test_pdfparse.py +0 -0
  88. {simdref-0.0.1 → simdref-0.0.4}/tests/test_perf_sources.py +0 -0
  89. {simdref-0.0.1 → simdref-0.0.4}/tests/test_preset_persistence.py +0 -0
  90. {simdref-0.0.1 → simdref-0.0.4}/tests/test_search.py +0 -0
  91. {simdref-0.0.1 → simdref-0.0.4}/tests/test_search_pushdown.py +0 -0
  92. {simdref-0.0.1 → simdref-0.0.4}/tests/test_source_kind_filter.py +0 -0
  93. {simdref-0.0.1 → simdref-0.0.4}/tests/test_source_validation.py +0 -0
  94. {simdref-0.0.1 → simdref-0.0.4}/tests/test_tui.py +0 -0
  95. {simdref-0.0.1 → simdref-0.0.4}/tests/test_ui_labels_parity.py +0 -0
  96. {simdref-0.0.1 → simdref-0.0.4}/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.1
3
+ Version: 0.0.4
4
4
  Summary: Searchable SIMD intrinsic and instruction reference with CLI, manpages, TUI, LSP, and static web export.
5
5
  Author: Marco
6
6
  License: GPL-3.0-or-later
@@ -18,6 +18,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
18
  Requires-Python: >=3.11
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE
21
+ Requires-Dist: click<10,>=8.1
21
22
  Requires-Dist: httpx<1,>=0.28
22
23
  Requires-Dist: msgpack<2,>=1.0
23
24
  Requires-Dist: pdfplumber<1,>=0.11
@@ -33,7 +34,7 @@ Dynamic: license-file
33
34
 
34
35
  [![CI](https://github.com/DiamonDinoia/simdref/actions/workflows/ci.yml/badge.svg)](https://github.com/DiamonDinoia/simdref/actions/workflows/ci.yml)
35
36
  [![TestPyPI](https://img.shields.io/pypi/v/simdref?pypiBaseUrl=https%3A%2F%2Ftest.pypi.org&label=TestPyPI)](https://test.pypi.org/project/simdref/)
36
- [![Python](https://img.shields.io/pypi/pyversions/simdref?pypiBaseUrl=https%3A%2F%2Ftest.pypi.org)](https://test.pypi.org/project/simdref/)
37
+ [![Python](https://img.shields.io/pypi/pyversions/simdref?pypiBaseUrl=https%3A%2F%2Ftest.pypi.org)](https://pypi.org/project/simdref/)
37
38
 
38
39
  A single searchable reference for SIMD intrinsics and instructions across
39
40
  **x86 (Intel + uops.info)**, **Arm (ACLE / AARCHMRS)**, and **RISC-V
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![CI](https://github.com/DiamonDinoia/simdref/actions/workflows/ci.yml/badge.svg)](https://github.com/DiamonDinoia/simdref/actions/workflows/ci.yml)
4
4
  [![TestPyPI](https://img.shields.io/pypi/v/simdref?pypiBaseUrl=https%3A%2F%2Ftest.pypi.org&label=TestPyPI)](https://test.pypi.org/project/simdref/)
5
- [![Python](https://img.shields.io/pypi/pyversions/simdref?pypiBaseUrl=https%3A%2F%2Ftest.pypi.org)](https://test.pypi.org/project/simdref/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/simdref?pypiBaseUrl=https%3A%2F%2Ftest.pypi.org)](https://pypi.org/project/simdref/)
6
6
 
7
7
  A single searchable reference for SIMD intrinsics and instructions across
8
8
  **x86 (Intel + uops.info)**, **Arm (ACLE / AARCHMRS)**, and **RISC-V
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simdref"
7
- version = "0.0.1"
7
+ version = "0.0.4"
8
8
  description = "Searchable SIMD intrinsic and instruction reference with CLI, manpages, TUI, LSP, and static web export."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -26,6 +26,7 @@ classifiers = [
26
26
  "Topic :: Software Development :: Libraries :: Python Modules",
27
27
  ]
28
28
  dependencies = [
29
+ "click>=8.1,<10",
29
30
  "httpx>=0.28,<1",
30
31
  "msgpack>=1.0,<2",
31
32
  "pdfplumber>=0.11,<1",
@@ -0,0 +1,10 @@
1
+ """simdref package."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ __all__ = ["__version__"]
6
+
7
+ try:
8
+ __version__ = version("simdref")
9
+ except PackageNotFoundError: # uninstalled source checkout
10
+ __version__ = "0.0.0+source"
@@ -68,8 +68,6 @@ def parse_asm_line(line: str, *, track_positions: bool = False) -> AsmLine:
68
68
  return AsmLine(LineKind.COMMENT, stripped)
69
69
  if bare.startswith("."):
70
70
  return AsmLine(LineKind.DIRECTIVE, stripped)
71
- if _LABEL_RE.match(stripped):
72
- return AsmLine(LineKind.LABEL, stripped)
73
71
 
74
72
  address: int | None = None
75
73
  if track_positions:
@@ -97,6 +95,9 @@ def parse_asm_line(line: str, *, track_positions: bool = False) -> AsmLine:
97
95
  # source keywords ("return", "if", "for", ...) as mnemonics.
98
96
  return AsmLine(LineKind.COMMENT, stripped)
99
97
 
98
+ if _LABEL_RE.match(stripped):
99
+ return AsmLine(LineKind.LABEL, stripped)
100
+
100
101
  m = _INSTR_RE.match(stripped)
101
102
  if not m:
102
103
  return AsmLine(LineKind.COMMENT, stripped)
@@ -70,6 +70,7 @@ from simdref.ingest_sources import (
70
70
  refresh_local_arm_intrinsics_bundle,
71
71
  )
72
72
  from simdref.manpages import open_manpage, write_manpages
73
+ from simdref import perf
73
74
  from simdref.perf import variant_perf_summary
74
75
  from simdref.queries import intrinsic_perf_summary_runtime, instruction_rows_for_intrinsic
75
76
  from simdref.search import (
@@ -92,10 +93,12 @@ from simdref.storage import (
92
93
  load_instructions_by_mnemonic_from_db,
93
94
  load_instructions_by_mnemonic_prefix_from_db,
94
95
  open_db,
96
+ read_installed_version_stamp,
95
97
  save_catalog,
96
98
  search_instruction_candidates_from_db,
97
99
  search_intrinsic_candidates_from_db,
98
100
  sqlite_schema_is_current,
101
+ write_installed_version_stamp,
99
102
  )
100
103
  from simdref.web import export_web
101
104
 
@@ -287,6 +290,18 @@ def _download_from_release() -> None:
287
290
  f"failed to download {asset}: {exc.response.status_code}", style="red"
288
291
  )
289
292
  raise typer.Exit(code=1) from exc
293
+ except (
294
+ httpx.ConnectError,
295
+ httpx.ConnectTimeout,
296
+ httpx.ReadTimeout,
297
+ httpx.NetworkError,
298
+ ) as exc:
299
+ err_console.print(
300
+ "[bold yellow]warning:[/bold yellow] no internet connectivity — "
301
+ f"could not reach the release server ({exc.__class__.__name__}). "
302
+ "Some features may not work correctly until the catalog is refreshed.",
303
+ )
304
+ raise typer.Exit(code=1) from exc
290
305
  return False
291
306
 
292
307
  for asset in ("catalog.msgpack", "catalog.db"):
@@ -444,6 +459,7 @@ def _build_runtime_locally(*, man_dir: Path, include_sdm: bool = False) -> None:
444
459
  f"updated catalog with {len(catalog.intrinsics)} intrinsics and {len(catalog.instructions)} instructions",
445
460
  style="green",
446
461
  )
462
+ write_installed_version_stamp(__version__)
447
463
 
448
464
 
449
465
  def _refresh_runtime_from_existing_catalog(*, man_dir: Path) -> None:
@@ -463,6 +479,7 @@ def _refresh_runtime_from_existing_catalog(*, man_dir: Path) -> None:
463
479
  f"refreshed runtime from existing catalog with {len(catalog.intrinsics)} intrinsics and {len(catalog.instructions)} instructions",
464
480
  style="green",
465
481
  )
482
+ write_installed_version_stamp(__version__)
466
483
 
467
484
 
468
485
  def _finalize_runtime_from_download(*, man_dir: Path) -> None:
@@ -476,6 +493,7 @@ def _finalize_runtime_from_download(*, man_dir: Path) -> None:
476
493
  f"refreshed local web/man assets from downloaded catalog with {len(catalog.intrinsics)} intrinsics and {len(catalog.instructions)} instructions",
477
494
  style="green",
478
495
  )
496
+ write_installed_version_stamp(__version__)
479
497
 
480
498
 
481
499
  def _download_release_or_fallback(*, man_dir: Path) -> None:
@@ -559,9 +577,16 @@ def ensure_catalog():
559
577
 
560
578
 
561
579
  def ensure_runtime() -> None:
562
- """Ensure catalog + SQLite are present and current."""
580
+ """Ensure catalog + SQLite are present and current.
581
+
582
+ If the package version recorded in the data dir does not match the
583
+ currently installed version, transparently re-run the release-download
584
+ flow so users don't need to invoke ``simdref update`` manually after
585
+ a ``pip``/``uv`` install or upgrade. Honors ``SIMDREF_SKIP_AUTOUPDATE``.
586
+ """
563
587
  if not CATALOG_PATH.exists():
564
588
  _bootstrap_interactive()
589
+ _maybe_auto_update_for_version_change()
565
590
  return
566
591
  if not sqlite_schema_is_current():
567
592
  err_console.print(
@@ -569,6 +594,37 @@ def ensure_runtime() -> None:
569
594
  style="yellow",
570
595
  )
571
596
  _refresh_runtime_from_existing_catalog(man_dir=DEFAULT_MAN_DIR)
597
+ _maybe_auto_update_for_version_change()
598
+
599
+
600
+ def _maybe_auto_update_for_version_change() -> None:
601
+ """Refresh data when the package version differs from the stamped one."""
602
+ if os.environ.get("SIMDREF_SKIP_AUTOUPDATE"):
603
+ return
604
+ stamped = read_installed_version_stamp()
605
+ if stamped == __version__:
606
+ return
607
+ if stamped is None:
608
+ # First run after install: stamp without re-downloading. The catalog
609
+ # we just bootstrapped (or that already exists locally) is what the
610
+ # user expects to see.
611
+ write_installed_version_stamp(__version__)
612
+ return
613
+ err_console.print(
614
+ f"simdref upgraded from {stamped} to {__version__}; refreshing catalog "
615
+ "(set SIMDREF_SKIP_AUTOUPDATE=1 to disable)",
616
+ style="yellow",
617
+ )
618
+ try:
619
+ _download_release_or_fallback(man_dir=DEFAULT_MAN_DIR)
620
+ except typer.Exit:
621
+ err_console.print(
622
+ "[bold yellow]warning:[/bold yellow] auto-update failed; continuing with the existing catalog. "
623
+ "Some features may not work correctly until `simdref update` succeeds.",
624
+ )
625
+ # Stamp anyway so we don't retry on every invocation; the user has
626
+ # been warned and can re-run `simdref update` once back online.
627
+ write_installed_version_stamp(__version__)
572
628
 
573
629
 
574
630
  def _catalog_meta(catalog) -> dict:
@@ -700,10 +756,11 @@ def _llm_result_payload(
700
756
  intrinsic_map[result.key] = item
701
757
  if item is not None:
702
758
  lat, cpi = intrinsic_perf_summary_runtime(conn, item, instruction_map)
703
- return {
759
+ payload: dict = {
704
760
  "query": item.name,
705
761
  "intrinsic": item.name,
706
762
  "signature": item.signature,
763
+ "url": getattr(item, "url", "") or "",
707
764
  "instructions": item.instructions,
708
765
  "instruction_refs": item.instruction_refs,
709
766
  "summary": item.description,
@@ -711,6 +768,10 @@ def _llm_result_payload(
711
768
  "lat": lat,
712
769
  "cpi": cpi,
713
770
  }
771
+ operation = _intrinsic_operation_text(item)
772
+ if operation:
773
+ payload["operation"] = operation
774
+ return payload
714
775
  item = instruction_map.get(result.key)
715
776
  if item is None:
716
777
  item = load_instruction_from_db(conn, result.key)
@@ -739,7 +800,7 @@ def _llm_result_payload(
739
800
  def _llm_intrinsic_payload(conn, intrinsic) -> dict:
740
801
  instruction_map: dict[str, object] = {}
741
802
  lat, cpi = intrinsic_perf_summary_runtime(conn, intrinsic, instruction_map)
742
- return {
803
+ payload: dict = {
743
804
  "query": intrinsic.name,
744
805
  "intrinsic": intrinsic.name,
745
806
  "signature": intrinsic.signature,
@@ -751,6 +812,23 @@ def _llm_intrinsic_payload(conn, intrinsic) -> dict:
751
812
  "cpi": cpi,
752
813
  "summary": intrinsic.description,
753
814
  }
815
+ operation = _intrinsic_operation_text(intrinsic)
816
+ if operation:
817
+ # Encodes algorithmic quirks (e.g. the bit-1 selector in
818
+ # ``_mm_permutevar_pd``) that the one-line summary cannot convey.
819
+ payload["operation"] = operation
820
+ return payload
821
+
822
+
823
+ def _intrinsic_operation_text(intrinsic) -> str:
824
+ """Return the SDM-style ``Operation`` pseudocode for *intrinsic*, or ``""``.
825
+
826
+ Intel ingest stores it under ``doc_sections["Operation"]``; ARM ACLE under
827
+ ``doc_sections["ACLE Operation"]``. Both are pseudocode the LLM payload
828
+ needs to surface so callers can reason about behavior beyond the summary.
829
+ """
830
+ sections = getattr(intrinsic, "doc_sections", None) or {}
831
+ return (sections.get("Operation") or sections.get("ACLE Operation") or "").strip()
754
832
 
755
833
 
756
834
  def _llm_instruction_payload(item) -> dict:
@@ -762,9 +840,24 @@ def _llm_instruction_payload(item) -> dict:
762
840
  "lat": lat,
763
841
  "cpi": cpi,
764
842
  "summary": item.summary,
843
+ "source_kinds": _payload_source_kinds(item.arch_details),
765
844
  }
766
845
 
767
846
 
847
+ def _payload_source_kinds(arch_details) -> list[str]:
848
+ """Return the distinct provenance kinds present in an instruction's arch_details."""
849
+ if not isinstance(arch_details, dict):
850
+ return []
851
+ kinds: list[str] = []
852
+ for details in arch_details.values():
853
+ if not isinstance(details, dict):
854
+ continue
855
+ kind = perf._source_kind(details)
856
+ if kind not in kinds:
857
+ kinds.append(kind)
858
+ return kinds
859
+
860
+
768
861
  # ---------------------------------------------------------------------------
769
862
  # Search results
770
863
  # ---------------------------------------------------------------------------
@@ -1184,6 +1277,9 @@ def _llm_filter_records(
1184
1277
 
1185
1278
  def _record_has_source_kind(rec: dict, wanted: str) -> bool:
1186
1279
  """Check whether an llm payload dict carries at least one entry with *wanted* provenance."""
1280
+ slim = rec.get("source_kinds")
1281
+ if isinstance(slim, list) and any(k == wanted for k in slim):
1282
+ return True
1187
1283
  arch_details = rec.get("arch_details") or {}
1188
1284
  if isinstance(arch_details, dict):
1189
1285
  for details in arch_details.values():
@@ -1323,6 +1419,14 @@ def _llm_schema_payload() -> dict:
1323
1419
  "lat": {"type": "string"},
1324
1420
  "cpi": {"type": "string"},
1325
1421
  "summary": {"type": "string"},
1422
+ "url": {
1423
+ "type": "string",
1424
+ "description": "Vendor documentation URL (Intel Intrinsics Guide / ARM ACLE).",
1425
+ },
1426
+ "operation": {
1427
+ "type": "string",
1428
+ "description": "SDM-style pseudocode describing the intrinsic's behavior, when available.",
1429
+ },
1326
1430
  },
1327
1431
  },
1328
1432
  "results": {"type": "array", "items": {"$ref": "#/properties/result"}},
@@ -275,6 +275,13 @@ def parse_intel_payload(text: str) -> list[IntrinsicRecord]:
275
275
  name = node.attrib.get("name", "").strip()
276
276
  if not name:
277
277
  continue
278
+ operation_text = ""
279
+ op_node = node.find("./operation")
280
+ if op_node is not None:
281
+ # ``<operation>`` carries SDM-style pseudocode; preserve internal
282
+ # whitespace (it encodes the algorithm) but trim outer padding.
283
+ raw = "".join(op_node.itertext())
284
+ operation_text = raw.strip("\n").rstrip() if raw else ""
278
285
  return_node = node.find("./return")
279
286
  ret = (
280
287
  node.attrib.get("rettype", "").strip()
@@ -321,6 +328,7 @@ def parse_intel_payload(text: str) -> list[IntrinsicRecord]:
321
328
  header=(
322
329
  (node.findtext("./header") or "").strip() or node.attrib.get("header", "")
323
330
  ),
331
+ url=_intel_intrinsic_url(name),
324
332
  architecture="x86",
325
333
  isa=_normalize_isa(
326
334
  cpuid or node.attrib.get("isa", "") or node.attrib.get("tech", "")
@@ -332,6 +340,7 @@ def parse_intel_payload(text: str) -> list[IntrinsicRecord]:
332
340
  subcategory=node.attrib.get("tech", "").strip(),
333
341
  instructions=instructions,
334
342
  instruction_refs=[ref | {"architecture": "x86"} for ref in instruction_refs],
343
+ doc_sections=({"Operation": operation_text} if operation_text else {}),
335
344
  notes=notes,
336
345
  aliases=[],
337
346
  )
@@ -384,12 +393,16 @@ def parse_intel_payload(text: str) -> list[IntrinsicRecord]:
384
393
  description = str(
385
394
  item.get("description") or item.get("summary") or item.get("technology", "")
386
395
  ).strip()
396
+ operation_text = (
397
+ str(item.get("operation") or item.get("Operation") or "").strip("\n").rstrip()
398
+ )
387
399
  records.append(
388
400
  IntrinsicRecord(
389
401
  name=name,
390
402
  signature=signature,
391
403
  description=description,
392
404
  header=str(item.get("header") or item.get("include") or "").strip(),
405
+ url=str(item.get("url") or "").strip() or _intel_intrinsic_url(name),
393
406
  architecture="x86",
394
407
  isa=_normalize_isa(
395
408
  item.get("isa") or item.get("tech") or item.get("instructionSet") or []
@@ -402,6 +415,7 @@ def parse_intel_payload(text: str) -> list[IntrinsicRecord]:
402
415
  for value in instructions
403
416
  if str(value).strip()
404
417
  ],
418
+ doc_sections=({"Operation": operation_text} if operation_text else {}),
405
419
  notes=[str(value).strip() for value in notes if str(value).strip()],
406
420
  aliases=[str(value).strip() for value in aliases if str(value).strip()],
407
421
  )
@@ -476,6 +490,16 @@ def _arm_intrinsic_url(name: str) -> str:
476
490
  return urljoin(_ARM_ACLE_INTRINSIC_BASE_URL, quote(name))
477
491
 
478
492
 
493
+ _INTEL_INTRINSIC_BASE_URL = (
494
+ "https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html"
495
+ )
496
+
497
+
498
+ def _intel_intrinsic_url(name: str) -> str:
499
+ """Stable Intel Intrinsics Guide deep link for *name* (uses ``#text=`` fragment)."""
500
+ return f"{_INTEL_INTRINSIC_BASE_URL}#text={quote(name)}"
501
+
502
+
479
503
  def _arm_slug(text: str) -> str:
480
504
  slug = re.sub(r"[^a-z0-9]+", "-", text.casefold()).strip("-")
481
505
  return slug or "intrinsics"
@@ -70,9 +70,16 @@ def latency_cycle_values(latencies: list[dict[str, Any]]) -> list[str]:
70
70
  """
71
71
  values: list[str] = []
72
72
  for latency in latencies:
73
+ mem_is_upper = latency.get("cycles_mem_is_upper_bound") == "1"
73
74
  for key in ("cycles", "cycles_mem", "cycles_addr", "cycles_addr_index"):
74
75
  value = latency.get(key)
75
- if value and value not in values:
76
+ if not value:
77
+ continue
78
+ if key == "cycles_mem" and mem_is_upper and _is_numeric(value) and float(value) == 0:
79
+ # uops.info convention: cycles_mem=0 with the upper-bound flag
80
+ # is a "not measured" marker, not a real zero-cycle latency.
81
+ continue
82
+ if value not in values:
76
83
  values.append(value)
77
84
  return values
78
85
 
@@ -82,7 +82,27 @@ else:
82
82
 
83
83
  CATALOG_PATH = DATA_DIR / "catalog.msgpack"
84
84
  SQLITE_PATH = DATA_DIR / "catalog.db"
85
- SQLITE_SCHEMA_VERSION = "11"
85
+ INSTALLED_VERSION_STAMP = DATA_DIR / "installed_version"
86
+ SQLITE_SCHEMA_VERSION = "12"
87
+
88
+
89
+ def read_installed_version_stamp() -> str | None:
90
+ """Return the package version that last refreshed the data dir, or None."""
91
+ try:
92
+ return INSTALLED_VERSION_STAMP.read_text(encoding="utf-8").strip() or None
93
+ except OSError:
94
+ return None
95
+
96
+
97
+ def write_installed_version_stamp(version: str) -> None:
98
+ """Record the package version that just refreshed the data dir."""
99
+ try:
100
+ INSTALLED_VERSION_STAMP.parent.mkdir(parents=True, exist_ok=True)
101
+ INSTALLED_VERSION_STAMP.write_text(version.strip() + "\n", encoding="utf-8")
102
+ except OSError:
103
+ pass # best-effort — stamp persistence must not break commands
104
+
105
+
86
106
  FTS_TOKEN_RE = re.compile(r"[A-Za-z0-9]+")
87
107
  SQLITE_INSERT_BATCH_SIZE = 512
88
108