simdref 0.0.3__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.
- {simdref-0.0.3/src/simdref.egg-info → simdref-0.0.4}/PKG-INFO +2 -1
- {simdref-0.0.3 → simdref-0.0.4}/pyproject.toml +2 -1
- simdref-0.0.4/src/simdref/__init__.py +10 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/annotate.py +3 -2
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/cli.py +107 -3
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/ingest_catalog.py +24 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/perf.py +8 -1
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/storage.py +21 -1
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/templates/app.js +155 -28
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/web.py +60 -15
- {simdref-0.0.3 → simdref-0.0.4/src/simdref.egg-info}/PKG-INFO +2 -1
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref.egg-info/SOURCES.txt +5 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref.egg-info/requires.txt +1 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_annotate.py +16 -0
- simdref-0.0.4/tests/test_auto_update_on_version_change.py +174 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_cli_llm.py +19 -0
- simdref-0.0.4/tests/test_intel_operation_and_url.py +119 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_lsp_web.py +21 -17
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_perf.py +21 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_presentation.py +21 -8
- simdref-0.0.4/tests/test_search_index_js.py +34 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_storage_schema.py +3 -3
- simdref-0.0.4/tests/test_version_matches_pyproject.py +32 -0
- simdref-0.0.4/tests/test_web_e2e.py +201 -0
- simdref-0.0.3/src/simdref/__init__.py +0 -5
- {simdref-0.0.3 → simdref-0.0.4}/LICENSE +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/README.md +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/setup.cfg +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/__main__.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/arm_instructions.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/display.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/filters.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/ingest.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/ingest_pdf.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/ingest_sources.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/lsp.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/manpages.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/models.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/pdfparse/__init__.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/pdfparse/base.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/pdfparse/intel.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/pdfparse/registry.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/pdfparse/types.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/pdfrefs.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/perf_sources/__init__.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/perf_sources/cores.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/perf_sources/llvm_mca.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/perf_sources/llvm_scheduling.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/perf_sources/merge.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/profile/__init__.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/profile/adapters/__init__.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/profile/adapters/exegesis.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/profile/adapters/mca.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/profile/adapters/perf.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/profile/adapters/uprof.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/profile/adapters/vtune.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/profile/adapters/xctrace.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/profile/hotloop.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/profile/merge.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/profile/model.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/profile/orchestrate.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/profile/registry.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/queries.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/riscv.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/search.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/templates/__init__.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/templates/favicon.svg +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/templates/index.html +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/templates/logo.svg +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/templates/style.css +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/tui.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref/ui_labels.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref.egg-info/dependency_links.txt +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref.egg-info/entry_points.txt +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/src/simdref.egg-info/top_level.txt +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_audit_coverage.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_cli_bare_query.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_cli_bootstrap_progress.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_cli_help.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_coverage_live.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_coverage_parity.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_display.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_filters.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_ingest_sources.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_issue2_fixes.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_models.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_pdfparse.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_perf_sources.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_preset_persistence.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_search.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_search_pushdown.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_source_kind_filter.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_source_validation.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_tui.py +0 -0
- {simdref-0.0.3 → simdref-0.0.4}/tests/test_ui_labels_parity.py +0 -0
- {simdref-0.0.3 → 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.
|
|
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
|
|
@@ -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.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",
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
|
@@ -21,7 +21,7 @@ const themeIconDark = $("theme-icon-dark");
|
|
|
21
21
|
const shortcutsOverlay = $("shortcuts-overlay");
|
|
22
22
|
|
|
23
23
|
/* ── State ────────────────────────────────────────────────────────── */
|
|
24
|
-
let catalog = null; // search-index
|
|
24
|
+
let catalog = null; // merged search-index-* shard payload
|
|
25
25
|
let searchEntries = [];
|
|
26
26
|
let searchTokenIndex = new Map();
|
|
27
27
|
let searchPrefixIndex = new Map();
|
|
@@ -207,8 +207,18 @@ function _pushBucket(map, key, i) {
|
|
|
207
207
|
|
|
208
208
|
function buildBuckets() {
|
|
209
209
|
byKind.clear(); byFamily.clear(); byCategory.clear(); byArmArch.clear();
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
extendBuckets(searchEntries, 0);
|
|
211
|
+
bucketsBuilt = true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* Append ``entries`` to the kind/family/category/arm-arch buckets at
|
|
215
|
+
* absolute positions ``baseIndex + offset``. Used by Phase-2 intrinsic
|
|
216
|
+
* ingest so newly-arrived entries are reachable via the kind filter
|
|
217
|
+
* without rebuilding from scratch. */
|
|
218
|
+
function extendBuckets(entries, baseIndex) {
|
|
219
|
+
for (let k = 0; k < entries.length; k++) {
|
|
220
|
+
const e = entries[k];
|
|
221
|
+
const i = baseIndex + k;
|
|
212
222
|
_pushBucket(byKind, e.kind, i);
|
|
213
223
|
const fams = e.item.isa_families || [];
|
|
214
224
|
if (fams.length === 0) _pushBucket(byFamily, "__none", i);
|
|
@@ -218,7 +228,6 @@ function buildBuckets() {
|
|
|
218
228
|
const arch = e.item.arm_arch || "__none";
|
|
219
229
|
_pushBucket(byArmArch, arch, i);
|
|
220
230
|
}
|
|
221
|
-
bucketsBuilt = true;
|
|
222
231
|
}
|
|
223
232
|
|
|
224
233
|
function _unionKeys(map, keys) {
|
|
@@ -468,18 +477,27 @@ function rankEntry(query, entry) {
|
|
|
468
477
|
function buildSearchIndexes(entries) {
|
|
469
478
|
searchTokenIndex = new Map();
|
|
470
479
|
searchPrefixIndex = new Map();
|
|
471
|
-
entries
|
|
480
|
+
extendSearchIndexes(entries, 0);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/* Append ``entries`` to the existing token/prefix maps, treating their
|
|
484
|
+
* absolute position in ``searchEntries`` as ``baseIndex + offset``. Used
|
|
485
|
+
* by the Phase-2 batched intrinsic ingest to avoid the full-rebuild stall. */
|
|
486
|
+
function extendSearchIndexes(entries, baseIndex) {
|
|
487
|
+
for (let k = 0; k < entries.length; k++) {
|
|
488
|
+
const entry = entries[k];
|
|
489
|
+
const idx = baseIndex + k;
|
|
472
490
|
entry.searchTokens = [...new Set(entry.fields.flatMap(f => tokens(f)))];
|
|
473
491
|
for (const t of entry.searchTokens) {
|
|
474
492
|
if (!searchTokenIndex.has(t)) searchTokenIndex.set(t, []);
|
|
475
|
-
searchTokenIndex.get(t).push(
|
|
493
|
+
searchTokenIndex.get(t).push(idx);
|
|
476
494
|
for (let sz = 1; sz <= Math.min(t.length, 6); sz++) {
|
|
477
495
|
const pfx = t.slice(0, sz);
|
|
478
496
|
if (!searchPrefixIndex.has(pfx)) searchPrefixIndex.set(pfx, []);
|
|
479
|
-
searchPrefixIndex.get(pfx).push(
|
|
497
|
+
searchPrefixIndex.get(pfx).push(idx);
|
|
480
498
|
}
|
|
481
499
|
}
|
|
482
|
-
}
|
|
500
|
+
}
|
|
483
501
|
}
|
|
484
502
|
|
|
485
503
|
function candidateIndexes(query) {
|
|
@@ -1508,22 +1526,54 @@ queryInput.addEventListener("keydown", (e) => {
|
|
|
1508
1526
|
});
|
|
1509
1527
|
|
|
1510
1528
|
/* ── Hash navigation ──────────────────────────────────────────────── */
|
|
1529
|
+
let intrinsicsReady = null; // resolved by Phase-2 bootstrap
|
|
1530
|
+
|
|
1531
|
+
function _hashEntry(key) {
|
|
1532
|
+
if (!catalog || !key) return null;
|
|
1533
|
+
return resultPool.find(e => e.key === key)
|
|
1534
|
+
|| (catalog.intrByName && catalog.intrByName[key] ? {kind: "intrinsic", key, title: key, subtitle: catalog.intrByName[key].subtitle || "", item: catalog.intrByName[key], fields: catalog.intrByName[key].search_fields || []} : null)
|
|
1535
|
+
|| (catalog.instrByKey && catalog.instrByKey[key] ? {kind: "instruction", key, title: catalog.instrByKey[key].display_key || key, subtitle: catalog.instrByKey[key].summary || "", item: catalog.instrByKey[key], fields: catalog.instrByKey[key].search_fields || []} : null);
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1511
1538
|
window.addEventListener("hashchange", () => {
|
|
1512
1539
|
const key = decodeURIComponent(location.hash.replace(/^#/, ""));
|
|
1513
1540
|
if (!catalog || !key) return;
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
if (
|
|
1541
|
+
let entry = _hashEntry(key);
|
|
1542
|
+
if (entry) { renderDetail(entry); return; }
|
|
1543
|
+
// Hash may name an intrinsic that Phase 2 hasn't ingested yet.
|
|
1544
|
+
if (intrinsicsReady) {
|
|
1545
|
+
intrinsicsReady.then(() => {
|
|
1546
|
+
const retry = _hashEntry(key);
|
|
1547
|
+
if (retry) renderDetail(retry);
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
});
|
|
1551
|
+
|
|
1552
|
+
/* ── Bootstrap ──────────────────────────────────────────────────────
|
|
1553
|
+
*
|
|
1554
|
+
* Two-phase load:
|
|
1555
|
+
* Phase 1 — meta + filter_spec + build_stamp + instructions. These
|
|
1556
|
+
* are small (~1 MB gz total) and let us paint the UI with
|
|
1557
|
+
* the instruction pool searchable immediately.
|
|
1558
|
+
* Phase 2 — intrinsics (~1.7 MB gz). Fetched in parallel from boot,
|
|
1559
|
+
* joined into the search index in idle-time batches so the
|
|
1560
|
+
* page stays responsive while ~93 k entries hydrate.
|
|
1561
|
+
*/
|
|
1562
|
+
const _intrinsicsFetch = fetchJson("search-index-intrinsics.json").catch((err) => {
|
|
1563
|
+
console.error("simdref: failed to load intrinsic search shard", err);
|
|
1564
|
+
return null;
|
|
1518
1565
|
});
|
|
1519
1566
|
|
|
1520
|
-
/* ── Bootstrap ────────────────────────────────────────────────────── */
|
|
1521
1567
|
Promise.all([
|
|
1522
|
-
fetchJson("search-index.json"),
|
|
1568
|
+
fetchJson("search-index-meta.json"),
|
|
1569
|
+
fetchJson("search-index-instructions.json"),
|
|
1523
1570
|
fetchJson("filter_spec.json").catch(() => null),
|
|
1524
1571
|
fetchJson("build_stamp.json").catch(() => null),
|
|
1525
1572
|
])
|
|
1526
|
-
.then(([
|
|
1573
|
+
.then(([meta, instructions, spec, stamp]) => {
|
|
1574
|
+
const data = meta || {};
|
|
1575
|
+
data.instructions = instructions || [];
|
|
1576
|
+
data.intrinsics = []; // populated in Phase 2
|
|
1527
1577
|
catalog = data;
|
|
1528
1578
|
// Prefer filter_spec.json (single source of truth) over embedded isa_config.
|
|
1529
1579
|
const config = spec || data.isa_config || {};
|
|
@@ -1541,25 +1591,24 @@ Promise.all([
|
|
|
1541
1591
|
metaNode.dataset.stamp = label;
|
|
1542
1592
|
if (stale) metaNode.classList.add("stale");
|
|
1543
1593
|
}
|
|
1544
|
-
catalog.intrByName = Object.
|
|
1594
|
+
catalog.intrByName = Object.create(null);
|
|
1545
1595
|
catalog.instrByKey = Object.fromEntries(data.instructions.map(i => [i.key, i]));
|
|
1546
1596
|
catalog.instrByDisplayKey = Object.fromEntries(data.instructions.map(i => [i.display_key || i.key, i]));
|
|
1547
1597
|
catalog.instrByMnem = Object.fromEntries(data.instructions.map(i => [i.mnemonic, i]));
|
|
1548
1598
|
|
|
1549
|
-
searchEntries =
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
})),
|
|
1554
|
-
...data.instructions.map(i => ({
|
|
1555
|
-
kind: "instruction", key: i.key, title: i.display_key || i.key, subtitle: i.summary || "", item: i,
|
|
1556
|
-
fields: i.search_fields || [i.display_mnemonic || i.mnemonic || "", i.display_form || i.form || "", i.summary || "", i.display_isa || displayIsa(i.isa)],
|
|
1557
|
-
})),
|
|
1558
|
-
];
|
|
1599
|
+
searchEntries = data.instructions.map(i => ({
|
|
1600
|
+
kind: "instruction", key: i.key, title: i.display_key || i.key, subtitle: i.summary || "", item: i,
|
|
1601
|
+
fields: i.search_fields || [i.display_mnemonic || i.mnemonic || "", i.display_form || i.form || "", i.summary || "", i.display_isa || displayIsa(i.isa)],
|
|
1602
|
+
}));
|
|
1559
1603
|
|
|
1560
1604
|
buildSearchIndexes(searchEntries);
|
|
1561
1605
|
|
|
1562
|
-
|
|
1606
|
+
// Seed the filter chips from the meta shard so the union of ISA
|
|
1607
|
+
// families is correct from first paint, not just instruction families.
|
|
1608
|
+
const seededIsas = Array.isArray(data.available_isas) && data.available_isas.length
|
|
1609
|
+
? data.available_isas
|
|
1610
|
+
: [...new Set(searchEntries.flatMap(e => e.item.isa_families || []))];
|
|
1611
|
+
availableIsas = [...new Set(seededIsas)]
|
|
1563
1612
|
.sort((a, b) => (isaFamilyOrder[a] ?? 99) - (isaFamilyOrder[b] ?? 99) || a.localeCompare(b));
|
|
1564
1613
|
enabledIsas = new Set([...defaultEnabledIsas].filter(v => availableIsas.includes(v)));
|
|
1565
1614
|
initEnabledSubIsas();
|
|
@@ -1588,12 +1637,16 @@ Promise.all([
|
|
|
1588
1637
|
// Build-stamp badge text is set above if a stamp is present; only fall
|
|
1589
1638
|
// back to the catalog-size summary when no stamp was emitted.
|
|
1590
1639
|
if (!metaNode.dataset.stamp) {
|
|
1591
|
-
|
|
1640
|
+
// Intrinsic count is filled in by Phase 2 once the shard arrives.
|
|
1641
|
+
metaNode.textContent = `${data.instructions.length} instructions`;
|
|
1592
1642
|
}
|
|
1593
1643
|
|
|
1594
1644
|
const fromHash = decodeURIComponent(location.hash.replace(/^#/, ""));
|
|
1595
1645
|
if (fromHash) queryInput.value = fromHash;
|
|
1596
1646
|
renderResults();
|
|
1647
|
+
|
|
1648
|
+
// Phase 2: fold intrinsics into the search index without blocking.
|
|
1649
|
+
intrinsicsReady = _ingestIntrinsics(metaNode);
|
|
1597
1650
|
})
|
|
1598
1651
|
.catch((err) => {
|
|
1599
1652
|
// Fetch failure (CORS, 404, gzip misconfig) should not leave "Loading..." up forever.
|
|
@@ -1606,6 +1659,80 @@ Promise.all([
|
|
|
1606
1659
|
}
|
|
1607
1660
|
});
|
|
1608
1661
|
|
|
1662
|
+
/* Phase 2 — fold the intrinsic shard into searchEntries / search index in
|
|
1663
|
+
* idle-time batches so the main thread keeps responding to scrolls and
|
|
1664
|
+
* keystrokes. Resolves once every intrinsic is searchable. */
|
|
1665
|
+
function _ingestIntrinsics(metaNode) {
|
|
1666
|
+
const BATCH = 5000;
|
|
1667
|
+
const schedule = window.requestIdleCallback
|
|
1668
|
+
? (cb) => window.requestIdleCallback(cb, {timeout: 250})
|
|
1669
|
+
: (cb) => setTimeout(cb, 0);
|
|
1670
|
+
|
|
1671
|
+
// Badge: surface to the user that intrinsics are still streaming in.
|
|
1672
|
+
let badge = null;
|
|
1673
|
+
const savedStamp = metaNode ? metaNode.dataset.stamp || "" : "";
|
|
1674
|
+
if (metaNode) {
|
|
1675
|
+
badge = document.createElement("span");
|
|
1676
|
+
badge.className = "meta-loading";
|
|
1677
|
+
badge.style.marginLeft = "0.5rem";
|
|
1678
|
+
badge.style.color = "var(--text-muted, #888)";
|
|
1679
|
+
badge.textContent = "loading intrinsics…";
|
|
1680
|
+
metaNode.appendChild(badge);
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
return _intrinsicsFetch.then(intrinsics => new Promise(resolve => {
|
|
1684
|
+
if (!Array.isArray(intrinsics) || !intrinsics.length) {
|
|
1685
|
+
if (badge && badge.parentNode) badge.parentNode.removeChild(badge);
|
|
1686
|
+
resolve();
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
catalog.intrinsics = intrinsics;
|
|
1690
|
+
for (const item of intrinsics) catalog.intrByName[item.name] = item;
|
|
1691
|
+
|
|
1692
|
+
let cursor = 0;
|
|
1693
|
+
const total = intrinsics.length;
|
|
1694
|
+
const pump = () => {
|
|
1695
|
+
const stop = Math.min(cursor + BATCH, total);
|
|
1696
|
+
const batch = [];
|
|
1697
|
+
for (let i = cursor; i < stop; i++) {
|
|
1698
|
+
const item = intrinsics[i];
|
|
1699
|
+
batch.push({
|
|
1700
|
+
kind: "intrinsic",
|
|
1701
|
+
key: item.name,
|
|
1702
|
+
title: item.name,
|
|
1703
|
+
subtitle: item.subtitle || item.description || "",
|
|
1704
|
+
item,
|
|
1705
|
+
fields: item.search_fields || [item.name, item.description || "", item.display_isa || displayIsa(item.isa), (item.instructions || []).join(" ")],
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
const base = searchEntries.length;
|
|
1709
|
+
for (const e of batch) searchEntries.push(e);
|
|
1710
|
+
extendSearchIndexes(batch, base);
|
|
1711
|
+
if (bucketsBuilt) extendBuckets(batch, base);
|
|
1712
|
+
cursor = stop;
|
|
1713
|
+
|
|
1714
|
+
// Re-render if the user is already typing or the visible list is
|
|
1715
|
+
// short enough that new hits would actually show up.
|
|
1716
|
+
visibleSet = null;
|
|
1717
|
+
if ((queryInput && queryInput.value) || resultPool.length < 100) {
|
|
1718
|
+
renderResults();
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
if (cursor < total) {
|
|
1722
|
+
schedule(pump);
|
|
1723
|
+
} else {
|
|
1724
|
+
if (badge && badge.parentNode) badge.parentNode.removeChild(badge);
|
|
1725
|
+
if (metaNode && !metaNode.dataset.stamp) {
|
|
1726
|
+
metaNode.textContent = `${total} intrinsics · ${(catalog.instructions || []).length} instructions`;
|
|
1727
|
+
}
|
|
1728
|
+
renderResults();
|
|
1729
|
+
resolve();
|
|
1730
|
+
}
|
|
1731
|
+
};
|
|
1732
|
+
schedule(pump);
|
|
1733
|
+
}));
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1609
1736
|
/* ── Annotate tab ────────────────────────────────────────────────────
|
|
1610
1737
|
* Client-side port of src/simdref/annotate.py. Reuses the existing
|
|
1611
1738
|
* fetchJson, chunkPrefix, loadChunk helpers. Zero backend.
|