simdref 0.0.0__py3-none-any.whl

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/filters.py ADDED
@@ -0,0 +1,318 @@
1
+ """Shared filter specification for intrinsic/instruction lookups.
2
+
3
+ Single source of truth for the ISA-family / sub-ISA / category facets used by
4
+ the TUI, web SPA, CLI, and LLM subcommands. The Python constants here are
5
+ re-exported through :mod:`simdref.display` for back-compat and serialised to
6
+ ``filter_spec.json`` for the static web bundle.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass, field
12
+ import sqlite3
13
+ from typing import Any, Iterable
14
+
15
+
16
+ ISA_FAMILY_ORDER: dict[str, int] = {
17
+ "x86": 0, "MMX": 1, "SSE": 2, "AVX": 3,
18
+ "AVX-512": 4, "AVX10": 5, "AMX": 6, "APX": 7,
19
+ "Arm": 8, "RISC-V": 9, "SVML": 10, "Other": 11,
20
+ }
21
+
22
+ DEFAULT_ENABLED_ISAS: tuple[str, ...] = ("SSE", "AVX", "AVX-512", "Arm", "RISC-V")
23
+
24
+ FAMILY_SUB_ORDER: dict[str, list[str]] = {
25
+ "SSE": ["SSE", "SSE2", "SSE3", "SSSE3", "SSE4.1", "SSE4.2"],
26
+ "AVX": ["AVX", "AVX2", "FMA", "F16C", "AVX_VNNI", "AVX_VNNI_INT8",
27
+ "AVX_VNNI_INT16", "AVX_IFMA", "AVX_NE_CONVERT"],
28
+ "AVX-512": [
29
+ "AVX512F", "AVX512VL", "AVX512BW", "AVX512DQ", "AVX512CD",
30
+ "AVX512_VNNI", "AVX512_FP16", "AVX512_BF16",
31
+ "AVX512_VBMI", "AVX512_VBMI2", "AVX512_BITALG",
32
+ "AVX512IFMA52", "AVX512VPOPCNTDQ", "AVX512_VP2INTERSECT",
33
+ "VAES", "VPCLMULQDQ", "GFNI",
34
+ ],
35
+ "AMX": ["AMX-TILE", "AMX-INT8", "AMX-BF16", "AMX-FP16", "AMX-COMPLEX"],
36
+ "Arm": ["NEON", "SVE", "SVE2"],
37
+ "RISC-V": ["V", "ZVE", "ZV"],
38
+ }
39
+
40
+ DEFAULT_SUBS: dict[str, set[str]] = {
41
+ "SSE": {"SSE", "SSE2", "SSE3", "SSSE3", "SSE4.1", "SSE4.2"},
42
+ "AVX": {"AVX", "AVX2", "FMA", "F16C"},
43
+ "AVX-512": {"AVX512F", "AVX512VL", "AVX512BW", "AVX512DQ", "AVX512CD"},
44
+ "AMX": {"AMX-TILE", "AMX-INT8", "AMX-BF16"},
45
+ "Arm": {"NEON", "SVE", "SVE2"},
46
+ "RISC-V": {"V", "ZVE", "ZV"},
47
+ }
48
+
49
+
50
+ # Strict x86-64-v4 sub-ISAs — what ships on every psABI-v4 target.
51
+ # Explicitly excludes: AVX-512 _VNNI/_FP16/_BF16/_VBMI*/_VPOPCNTDQ/_BITALG/
52
+ # _IFMA/_VP2INTERSECT, VAES, VPCLMULQDQ, GFNI, AMX, APX, AVX10, SVML.
53
+ X86_64_V4_SUBS: set[str] = {
54
+ # psABI x86-64-v2
55
+ "SSE", "SSE2", "SSE3", "SSSE3", "SSE4.1", "SSE4.2", "POPCNT",
56
+ # psABI x86-64-v3
57
+ "AVX", "AVX2", "F16C", "FMA", "BMI1", "BMI2", "LZCNT", "MOVBE",
58
+ # psABI x86-64-v4
59
+ "AVX512F", "AVX512VL", "AVX512BW", "AVX512DQ", "AVX512CD",
60
+ }
61
+
62
+ ARM_ALL_SUBS: set[str] = {"NEON", "SVE", "SVE2", "SME", "MVE"}
63
+ ARM32_SUBS: set[str] = {"NEON", "MVE"}
64
+ ARM64_SUBS: set[str] = {"NEON", "SVE", "SVE2", "SME"}
65
+ RVV_SUBS: set[str] = {"V", "ZVE", "ZV"}
66
+
67
+ # Default preset mirrors DEFAULT_ENABLED_ISAS but uses strict-v4 on x86.
68
+ DEFAULT_PRESET_SUBS: set[str] = X86_64_V4_SUBS | ARM_ALL_SUBS | RVV_SUBS
69
+
70
+ # arm_arch facet values (NULL excluded; filter logic handles missing values).
71
+ ARM_ARCH_VALUES: tuple[str, ...] = ("A32", "A64", "BOTH")
72
+
73
+
74
+ @dataclass(frozen=True)
75
+ class PresetSpec:
76
+ """Declarative preset: families + subs + arm_arch + kind applied atomically."""
77
+
78
+ families: frozenset[str]
79
+ subs: frozenset[str]
80
+ arm_arch: frozenset[str] | None # None = no arm_arch filter
81
+ kind: frozenset[str]
82
+
83
+ def to_dict(self) -> dict[str, Any]:
84
+ return {
85
+ "families": sorted(self.families),
86
+ "subs": sorted(self.subs),
87
+ "arm_arch": sorted(self.arm_arch) if self.arm_arch is not None else None,
88
+ "kind": sorted(self.kind),
89
+ }
90
+
91
+ @classmethod
92
+ def empty(cls) -> "PresetSpec":
93
+ return cls(frozenset(), frozenset(), None, frozenset())
94
+
95
+ @classmethod
96
+ def all(cls) -> "PresetSpec":
97
+ return cls(
98
+ families=frozenset(ISA_FAMILY_ORDER.keys()),
99
+ subs=frozenset(sub for subs in FAMILY_SUB_ORDER.values() for sub in subs)
100
+ | ARM_ALL_SUBS,
101
+ arm_arch=None,
102
+ kind=frozenset({"intrinsic", "instruction"}),
103
+ )
104
+
105
+
106
+ ARCH_PRESETS: dict[str, PresetSpec] = {
107
+ "default": PresetSpec(
108
+ families=frozenset({"SSE", "AVX", "AVX-512", "Arm", "RISC-V"}),
109
+ subs=frozenset(DEFAULT_PRESET_SUBS),
110
+ arm_arch=None,
111
+ kind=frozenset({"intrinsic"}),
112
+ ),
113
+ "intel": PresetSpec(
114
+ # Include "x86" so BMI/POPCNT/LZCNT intrinsics (family=x86) are reachable.
115
+ families=frozenset({"x86", "SSE", "AVX", "AVX-512"}),
116
+ subs=frozenset(X86_64_V4_SUBS),
117
+ arm_arch=None,
118
+ kind=frozenset({"intrinsic"}),
119
+ ),
120
+ "arm32": PresetSpec(
121
+ families=frozenset({"Arm"}),
122
+ subs=frozenset(ARM32_SUBS),
123
+ arm_arch=frozenset({"A32", "BOTH"}),
124
+ kind=frozenset({"intrinsic"}),
125
+ ),
126
+ "arm64": PresetSpec(
127
+ families=frozenset({"Arm"}),
128
+ subs=frozenset(ARM64_SUBS),
129
+ arm_arch=frozenset({"A64", "BOTH"}),
130
+ kind=frozenset({"intrinsic"}),
131
+ ),
132
+ "riscv": PresetSpec(
133
+ families=frozenset({"RISC-V"}),
134
+ subs=frozenset(RVV_SUBS),
135
+ arm_arch=None,
136
+ kind=frozenset({"intrinsic"}),
137
+ ),
138
+ "none": PresetSpec.empty(),
139
+ "all": PresetSpec.all(),
140
+ }
141
+
142
+
143
+ @dataclass(frozen=True)
144
+ class CategorySpec:
145
+ """A category facet derived from the catalog database."""
146
+
147
+ family: str
148
+ category: str
149
+ subcategory: str = ""
150
+ count: int = 0
151
+
152
+ def to_dict(self) -> dict[str, Any]:
153
+ return {
154
+ "family": self.family,
155
+ "category": self.category,
156
+ "subcategory": self.subcategory,
157
+ "count": self.count,
158
+ }
159
+
160
+
161
+ @dataclass
162
+ class FilterSpec:
163
+ """Filterable facets usable by TUI, web, CLI, and LLM consumers."""
164
+
165
+ family_order: dict[str, int] = field(default_factory=lambda: dict(ISA_FAMILY_ORDER))
166
+ family_sub_order: dict[str, list[str]] = field(
167
+ default_factory=lambda: {k: list(v) for k, v in FAMILY_SUB_ORDER.items()}
168
+ )
169
+ default_enabled: tuple[str, ...] = DEFAULT_ENABLED_ISAS
170
+ default_subs: dict[str, set[str]] = field(
171
+ default_factory=lambda: {k: set(v) for k, v in DEFAULT_SUBS.items()}
172
+ )
173
+ categories: list[CategorySpec] = field(default_factory=list)
174
+ presets: dict[str, PresetSpec] = field(
175
+ default_factory=lambda: dict(ARCH_PRESETS)
176
+ )
177
+ arm_arch_values: tuple[str, ...] = ARM_ARCH_VALUES
178
+
179
+ def to_json(self) -> dict[str, Any]:
180
+ """JSON-friendly projection used by the web SPA."""
181
+ return {
182
+ "family_order": dict(self.family_order),
183
+ "family_sub_order": {k: list(v) for k, v in self.family_sub_order.items()},
184
+ "default_enabled": list(self.default_enabled),
185
+ "default_subs": {k: sorted(v) for k, v in self.default_subs.items()},
186
+ "categories": [c.to_dict() for c in self.categories],
187
+ "presets": {name: spec.to_dict() for name, spec in self.presets.items()},
188
+ "arm_arch_values": list(self.arm_arch_values),
189
+ }
190
+
191
+ # --- in-memory predicate --------------------------------------------------
192
+
193
+ def matches(
194
+ self,
195
+ record: Any,
196
+ enabled_families: Iterable[str] | None = None,
197
+ enabled_categories: Iterable[str] | None = None,
198
+ ) -> bool:
199
+ """Return whether *record* passes the given family/category filter.
200
+
201
+ Accepts anything with an ``isa`` iterable and optional ``category``.
202
+ """
203
+ families = set(enabled_families) if enabled_families is not None else None
204
+ categories = set(enabled_categories) if enabled_categories is not None else None
205
+ if families is not None:
206
+ from simdref.display import isa_family # local import avoids cycle
207
+ record_families = {isa_family(v) for v in (getattr(record, "isa", None) or [])}
208
+ if not record_families & families:
209
+ return False
210
+ if categories is not None:
211
+ record_category = getattr(record, "category", None)
212
+ if not record_category:
213
+ record_category = (getattr(record, "metadata", {}) or {}).get("category", "")
214
+ if record_category not in categories:
215
+ return False
216
+ return True
217
+
218
+ # --- SQL pushdown helper --------------------------------------------------
219
+
220
+ def sql_predicate(
221
+ self,
222
+ table: str,
223
+ enabled_families: Iterable[str] | None = None,
224
+ enabled_categories: Iterable[str] | None = None,
225
+ enabled_arm_arch: Iterable[str] | None = None,
226
+ ) -> tuple[str, list[Any]]:
227
+ """Return a SQL ``WHERE`` fragment and bind values for *table*.
228
+
229
+ Both ``intrinsics_data`` and ``instructions_data`` carry an indexed
230
+ ``category`` column (schema v10+). ISA family filtering relies on
231
+ substring match against the space-joined ``isa`` column.
232
+ """
233
+ clauses: list[str] = []
234
+ binds: list[Any] = []
235
+ if enabled_families:
236
+ family_tokens: set[str] = set()
237
+ for fam in enabled_families:
238
+ family_tokens.update(self.family_sub_order.get(fam, []))
239
+ family_tokens.add(fam)
240
+ if family_tokens:
241
+ clauses.append(
242
+ "(" + " OR ".join(f"{table}.isa LIKE ?" for _ in family_tokens) + ")"
243
+ )
244
+ binds.extend(f"%{tok}%" for tok in family_tokens)
245
+ if enabled_categories and table in ("intrinsics_data", "instructions_data"):
246
+ categories_list = list(enabled_categories)
247
+ if categories_list:
248
+ placeholders = ",".join("?" for _ in categories_list)
249
+ clauses.append(f"{table}.category IN ({placeholders})")
250
+ binds.extend(categories_list)
251
+ if enabled_arm_arch and table == "intrinsics_data":
252
+ arch_list = list(enabled_arm_arch)
253
+ if arch_list:
254
+ placeholders = ",".join("?" for _ in arch_list)
255
+ clauses.append(f"{table}.arm_arch IN ({placeholders})")
256
+ binds.extend(arch_list)
257
+ return (" AND ".join(clauses), binds)
258
+
259
+
260
+ # ---------------------------------------------------------------------------
261
+ # DB-backed category aggregation
262
+ # ---------------------------------------------------------------------------
263
+
264
+
265
+ def load_categories_from_db(conn: sqlite3.Connection) -> list[CategorySpec]:
266
+ """Derive the category facet list from ``intrinsics_data``.
267
+
268
+ Runs at build time so categories are data-driven rather than hardcoded.
269
+ Instruction-side categories are reached through ``metadata->>'category'``
270
+ on the payload and are skipped here (schema lacks an indexed column).
271
+ """
272
+ from simdref.display import isa_family # local import avoids cycle
273
+ queries = [
274
+ """
275
+ SELECT category, subcategory, isa, COUNT(*) AS n
276
+ FROM intrinsics_data
277
+ WHERE category != ''
278
+ GROUP BY category, subcategory, isa
279
+ """,
280
+ """
281
+ SELECT category, '' AS subcategory, isa, COUNT(*) AS n
282
+ FROM instructions_data
283
+ WHERE category != ''
284
+ GROUP BY category, isa
285
+ """,
286
+ ]
287
+ aggregate: dict[tuple[str, str, str], int] = {}
288
+ for sql in queries:
289
+ try:
290
+ rows = conn.execute(sql).fetchall()
291
+ except sqlite3.Error:
292
+ continue
293
+ for row in rows:
294
+ isa_str = row["isa"] if hasattr(row, "keys") else row[2]
295
+ category = row["category"] if hasattr(row, "keys") else row[0]
296
+ subcategory = row["subcategory"] if hasattr(row, "keys") else row[1]
297
+ count = row["n"] if hasattr(row, "keys") else row[3]
298
+ tokens = [t for t in str(isa_str or "").split() if t]
299
+ families = {isa_family(t) for t in tokens} or {"Other"}
300
+ for family in families:
301
+ key = (family, category, subcategory)
302
+ aggregate[key] = aggregate.get(key, 0) + int(count)
303
+ specs = [
304
+ CategorySpec(family=fam, category=cat, subcategory=sub, count=n)
305
+ for (fam, cat, sub), n in sorted(aggregate.items(), key=lambda kv: (kv[0][0], kv[0][1], kv[0][2]))
306
+ ]
307
+ return specs
308
+
309
+
310
+ def build_filter_spec(conn: sqlite3.Connection | None = None) -> FilterSpec:
311
+ """Build a :class:`FilterSpec`, deriving categories from *conn* if given."""
312
+ spec = FilterSpec()
313
+ if conn is not None:
314
+ try:
315
+ spec.categories = load_categories_from_db(conn)
316
+ except sqlite3.Error:
317
+ spec.categories = []
318
+ return spec
simdref/ingest.py ADDED
@@ -0,0 +1,113 @@
1
+ """Public ingest entrypoints and compatibility wrappers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ from pathlib import Path
7
+ from typing import Callable
8
+
9
+ import msgpack
10
+
11
+ from simdref.ingest_catalog import (
12
+ _instruction_summary,
13
+ _normalize_operand_xtype,
14
+ build_catalog,
15
+ link_records,
16
+ parse_intel_payload,
17
+ parse_riscv_instruction_payload,
18
+ parse_riscv_intrinsics_payload,
19
+ parse_uops_xml,
20
+ )
21
+ from simdref.ingest_pdf import merge_pdf_enrichment
22
+ from simdref.ingest_sources import (
23
+ fetch_intel_data,
24
+ fetch_riscv_rvv_intrinsics_data,
25
+ fetch_riscv_unified_db_data,
26
+ fetch_uops_xml,
27
+ now_iso,
28
+ )
29
+ from simdref.pdfparse.intel import INTEL_SDM_CACHE_PATH, INTEL_SDM_URL, parse_intel_sdm
30
+ from simdref.storage import ensure_dir
31
+
32
+
33
+ def _sha256_file(path: Path) -> str:
34
+ hasher = hashlib.sha256()
35
+ with path.open("rb") as fh:
36
+ for chunk in iter(lambda: fh.read(1024 * 1024), b""):
37
+ hasher.update(chunk)
38
+ return hasher.hexdigest()
39
+
40
+
41
+ def load_or_parse_intel_sdm(
42
+ pdf_path: Path,
43
+ *,
44
+ status: Callable[[str], None] | None = None,
45
+ cache_path: Path = INTEL_SDM_CACHE_PATH,
46
+ ) -> dict[str, dict[str, object]]:
47
+ if cache_path.exists():
48
+ try:
49
+ payload = msgpack.unpackb(cache_path.read_bytes(), raw=False)
50
+ if payload.get("pdf_url") == INTEL_SDM_URL and payload.get("pdf_sha256") == _sha256_file(pdf_path):
51
+ descriptions = payload.get("descriptions")
52
+ if isinstance(descriptions, dict):
53
+ return descriptions
54
+ except Exception:
55
+ pass
56
+ descriptions = parse_intel_sdm(pdf_path, status=status)
57
+ if hasattr(descriptions, "descriptions"):
58
+ descriptions = {
59
+ key: {
60
+ "sections": value.sections,
61
+ "source_url": value.source_url,
62
+ "page_start": value.page_start,
63
+ "page_end": value.page_end,
64
+ }
65
+ for key, value in descriptions.descriptions.items()
66
+ }
67
+ ensure_dir(cache_path.parent)
68
+ payload = {
69
+ "pdf_url": INTEL_SDM_URL,
70
+ "pdf_sha256": _sha256_file(pdf_path),
71
+ "descriptions": descriptions,
72
+ }
73
+ cache_path.write_bytes(msgpack.packb(payload, use_bin_type=True))
74
+ return descriptions
75
+
76
+
77
+ def _merge_descriptions(
78
+ instructions,
79
+ descriptions: dict[str, dict[str, object]],
80
+ ) -> None:
81
+ from simdref.pdfparse.types import PdfDescriptionPayload, PdfEnrichmentResult
82
+
83
+ result = PdfEnrichmentResult(
84
+ descriptions={
85
+ key: PdfDescriptionPayload(
86
+ sections=dict(value.get("sections") or {}),
87
+ source_url=str(value.get("source_url") or "https://cdrdv2.intel.com/v1/dl/getContent/671200"),
88
+ page_start=value.get("page_start") if isinstance(value.get("page_start"), int) else None,
89
+ page_end=value.get("page_end") if isinstance(value.get("page_end"), int) else None,
90
+ )
91
+ for key, value in descriptions.items()
92
+ }
93
+ )
94
+ merge_pdf_enrichment(instructions, "intel-sdm", result)
95
+
96
+
97
+ __all__ = [
98
+ "build_catalog",
99
+ "fetch_intel_data",
100
+ "fetch_riscv_rvv_intrinsics_data",
101
+ "fetch_riscv_unified_db_data",
102
+ "fetch_uops_xml",
103
+ "link_records",
104
+ "load_or_parse_intel_sdm",
105
+ "_instruction_summary",
106
+ "_normalize_operand_xtype",
107
+ "now_iso",
108
+ "parse_intel_sdm",
109
+ "parse_intel_payload",
110
+ "parse_riscv_instruction_payload",
111
+ "parse_riscv_intrinsics_payload",
112
+ "parse_uops_xml",
113
+ ]