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/riscv.py ADDED
@@ -0,0 +1,446 @@
1
+ """RISC-V RVV instruction/intrinsic parsing and normalization."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import html
6
+ import json
7
+ import re
8
+ from typing import Any
9
+
10
+ from simdref.models import InstructionRecord, IntrinsicRecord
11
+
12
+ RISCV_RVV_INTRINSICS_PROJECT_URL = "https://github.com/riscv-non-isa/riscv-rvv-intrinsic-doc"
13
+
14
+
15
+ def _normalize_isa(value: Any) -> list[str]:
16
+ if isinstance(value, dict):
17
+ normalized: list[str] = []
18
+ for key in ("isa", "extensions", "extension", "name", "value"):
19
+ normalized.extend(_normalize_isa(value.get(key)))
20
+ if normalized:
21
+ return list(dict.fromkeys(normalized))
22
+ return [str(key).strip() for key, enabled in value.items() if enabled and str(key).strip()]
23
+ if isinstance(value, list):
24
+ normalized: list[str] = []
25
+ for item in value:
26
+ normalized.extend(_normalize_isa(item))
27
+ return list(dict.fromkeys(normalized))
28
+ if isinstance(value, str):
29
+ return [part.strip() for part in re.split(r"[,/|]\s*|\s{2,}", value) if part.strip()]
30
+ return []
31
+
32
+
33
+ def _string(value: Any) -> str:
34
+ return str(value or "").strip()
35
+
36
+
37
+ def _string_map(value: Any) -> dict[str, str]:
38
+ if not isinstance(value, dict):
39
+ return {}
40
+ return {
41
+ str(key).strip(): str(item).strip()
42
+ for key, item in value.items()
43
+ if str(key).strip() and str(item).strip()
44
+ }
45
+
46
+
47
+ def _string_list(value: Any) -> list[str]:
48
+ if isinstance(value, list):
49
+ return [str(item).strip() for item in value if str(item).strip()]
50
+ if isinstance(value, str):
51
+ return [value.strip()] if value.strip() else []
52
+ return []
53
+
54
+
55
+ def _strip_tags(text: str) -> str:
56
+ clean = re.sub(r"<br\s*/?>", "\n", text, flags=re.IGNORECASE)
57
+ clean = re.sub(r"</p\s*>", "\n\n", clean, flags=re.IGNORECASE)
58
+ clean = re.sub(r"</?(?:code|span|strong|em)[^>]*>", "", clean, flags=re.IGNORECASE)
59
+ clean = re.sub(r"</?(?:pre|div|section|article|ul|ol|li)[^>]*>", "\n", clean, flags=re.IGNORECASE)
60
+ clean = re.sub(r"<[^>]+>", " ", clean)
61
+ clean = html.unescape(clean)
62
+ clean = clean.replace("\r", "")
63
+ clean = re.sub(r"[ \t]+\n", "\n", clean)
64
+ clean = re.sub(r"\n{3,}", "\n\n", clean)
65
+ clean = re.sub(r"[ \t]{2,}", " ", clean)
66
+ clean = re.sub(r"\s*\n\s*", "\n", clean)
67
+ clean = re.sub(r"\n+", "\n", clean)
68
+ return clean.strip()
69
+
70
+
71
+ def _extract_html_section(doc: str, title: str) -> str:
72
+ pattern = re.compile(
73
+ rf"<h[1-6][^>]*>\s*{re.escape(title)}\s*</h[1-6]>(?P<body>.*?)(?=<h[1-6][^>]*>|$)",
74
+ re.IGNORECASE | re.DOTALL,
75
+ )
76
+ match = pattern.search(doc)
77
+ if not match:
78
+ return ""
79
+ return _strip_tags(match.group("body"))
80
+
81
+
82
+ def _section_slice(doc: str, mnemonic: str) -> str:
83
+ lowered = doc.casefold()
84
+ needle = mnemonic.casefold()
85
+ index = lowered.find(needle)
86
+ if index < 0:
87
+ return ""
88
+ start = max(
89
+ doc.rfind('<div class="sect4">', 0, index),
90
+ doc.rfind('<div class="sect3">', 0, index),
91
+ doc.rfind('<div class="sect2">', 0, index),
92
+ )
93
+ if start < 0:
94
+ start = max(0, index - 4000)
95
+ end_candidates = [
96
+ candidate
97
+ for marker in ('<div class="sect4">', '<div class="sect3">', '<div class="sect2">')
98
+ if (candidate := doc.find(marker, index + len(needle))) >= 0
99
+ ]
100
+ end = min(end_candidates) if end_candidates else min(len(doc), index + 12000)
101
+ return doc[start:end]
102
+
103
+
104
+ def _extract_instruction_section_semantics(doc: str, mnemonic: str) -> dict[str, str]:
105
+ section = _section_slice(doc, mnemonic)
106
+ if not section:
107
+ return {}
108
+
109
+ description = ""
110
+ paragraphs = re.findall(r"<div class=\"paragraph\">(.*?)</div>", section, re.IGNORECASE | re.DOTALL)
111
+ for raw in paragraphs:
112
+ text = _strip_tags(raw)
113
+ if not text:
114
+ continue
115
+ description = text
116
+ if mnemonic.casefold() in text.casefold():
117
+ break
118
+
119
+ operation = ""
120
+ op_heading = re.search(r"<dt[^>]*>\s*Operation\s*</dt>", section, re.IGNORECASE | re.DOTALL)
121
+ if op_heading:
122
+ pre_match = re.search(r"<pre[^>]*>(.*?)</pre>", section[op_heading.end():], re.IGNORECASE | re.DOTALL)
123
+ if pre_match:
124
+ operation = _strip_tags(pre_match.group(1))
125
+ if not operation:
126
+ for pre in re.findall(r"<pre[^>]*>(.*?)</pre>", section, re.IGNORECASE | re.DOTALL):
127
+ text = _strip_tags(pre)
128
+ if mnemonic.casefold() in text.casefold():
129
+ operation = text
130
+ break
131
+ if not operation:
132
+ listings = re.findall(r"<div class=\"listingblock\">(.*?)</div>\s*</div>", section, re.IGNORECASE | re.DOTALL)
133
+ for listing in listings:
134
+ text = _strip_tags(listing)
135
+ if mnemonic.casefold() in text.casefold():
136
+ operation = text
137
+ break
138
+ result: dict[str, str] = {}
139
+ if description:
140
+ result["Description"] = description
141
+ if operation:
142
+ result["Operation"] = operation
143
+ return result
144
+
145
+
146
+ def _normalize_sections(item: dict[str, Any]) -> dict[str, str]:
147
+ sections: dict[str, str] = {}
148
+ for key in ("description", "doc_sections", "sections", "descriptions"):
149
+ value = item.get(key)
150
+ if isinstance(value, dict):
151
+ sections.update(_string_map(value))
152
+ if not sections:
153
+ description = _string(item.get("description_text") or item.get("description"))
154
+ if description:
155
+ sections["Description"] = description
156
+ operation = _string(item.get("operation") or item.get("operation_text") or item.get("pseudocode"))
157
+ if operation:
158
+ sections["Operation"] = operation
159
+ return sections
160
+
161
+
162
+ def _docs_page_map(payload: Any) -> dict[str, str]:
163
+ if not isinstance(payload, dict):
164
+ return {}
165
+ docs_pages = payload.get("docs_pages") or payload.get("doc_pages") or payload.get("docs")
166
+ if isinstance(docs_pages, dict):
167
+ return {
168
+ _string(key): _string(value)
169
+ for key, value in docs_pages.items()
170
+ if _string(key) and _string(value)
171
+ }
172
+ if isinstance(docs_pages, list):
173
+ mapped: dict[str, str] = {}
174
+ for item in docs_pages:
175
+ if not isinstance(item, dict):
176
+ continue
177
+ url = _string(item.get("url") or item.get("reference_url"))
178
+ page = _string(item.get("html") or item.get("content"))
179
+ if url and page:
180
+ mapped[url] = page
181
+ return mapped
182
+ return {}
183
+
184
+
185
+ def _doc_candidates(url: str, docs_pages: dict[str, str]) -> list[str]:
186
+ if not url:
187
+ return []
188
+ base = url.split("#", 1)[0]
189
+ candidates = [url]
190
+ if base != url:
191
+ candidates.append(base)
192
+ return [candidate for candidate in candidates if candidate in docs_pages]
193
+
194
+
195
+ def _instruction_semantics(item: dict[str, Any], url: str, docs_pages: dict[str, str]) -> dict[str, str]:
196
+ sections = _normalize_sections(item)
197
+ if sections.get("Description") and sections.get("Operation"):
198
+ return sections
199
+ mnemonic = _string(item.get("mnemonic") or item.get("name") or item.get("instruction") or item.get("syntax")).casefold()
200
+ for candidate in _doc_candidates(url, docs_pages):
201
+ page = docs_pages[candidate]
202
+ if not sections.get("Description"):
203
+ description = _extract_html_section(page, "Description")
204
+ if description:
205
+ sections["Description"] = description
206
+ if not sections.get("Operation"):
207
+ operation = _extract_html_section(page, "Operation")
208
+ if operation:
209
+ sections["Operation"] = operation
210
+ if not sections.get("Description") or not sections.get("Operation"):
211
+ extracted = _extract_instruction_section_semantics(page, mnemonic)
212
+ for key, value in extracted.items():
213
+ if not sections.get(key):
214
+ sections[key] = value
215
+ if sections.get("Description") and sections.get("Operation"):
216
+ break
217
+ return sections
218
+
219
+
220
+ def _normalize_policy(value: Any) -> str:
221
+ normalized = _string(value).casefold().replace("-", "").replace("_", "")
222
+ aliases = {
223
+ "": "agnostic",
224
+ "ta": "agnostic",
225
+ "ma": "agnostic",
226
+ "tama": "agnostic",
227
+ "tum": "tum",
228
+ "tu": "tu",
229
+ "mu": "mu",
230
+ "tumu": "tumu",
231
+ }
232
+ return aliases.get(normalized, normalized)
233
+
234
+
235
+ def _normalize_masking(value: Any) -> str:
236
+ normalized = _string(value).casefold().replace("-", "").replace("_", "")
237
+ if normalized in {"", "nomask", "unmasked", "false"}:
238
+ return "unmasked"
239
+ if normalized in {"mask", "masked", "true"}:
240
+ return "masked"
241
+ return normalized
242
+
243
+
244
+ def _tail_policy(policy: str) -> str:
245
+ if "tu" in policy:
246
+ return "undisturbed"
247
+ return "agnostic"
248
+
249
+
250
+ def _mask_policy(policy: str, masking: str) -> str:
251
+ if masking != "masked":
252
+ return ""
253
+ if "mu" in policy:
254
+ return "undisturbed"
255
+ return "agnostic"
256
+
257
+
258
+ def _instruction_form(item: dict[str, Any], mnemonic: str) -> str:
259
+ explicit = _string(item.get("form"))
260
+ if explicit:
261
+ return explicit
262
+ policy = _normalize_policy(item.get("policy") or item.get("tail_policy"))
263
+ masking = _normalize_masking(item.get("masking"))
264
+ suffix: list[str] = []
265
+ if masking and masking != "unmasked":
266
+ suffix.append(masking)
267
+ if policy and policy not in {"agnostic", "default"}:
268
+ suffix.append(policy)
269
+ if suffix:
270
+ return f"{mnemonic} [{' '.join(suffix)}]"
271
+ return mnemonic
272
+
273
+
274
+ def parse_riscv_instruction_payload(text: str) -> list[InstructionRecord]:
275
+ payload = json.loads(text)
276
+ docs_pages = _docs_page_map(payload)
277
+ if isinstance(payload, dict) and payload.get("format") == "riscv-unified-db-v1":
278
+ candidates = payload.get("instructions") or []
279
+ elif isinstance(payload, list):
280
+ candidates = payload
281
+ else:
282
+ candidates = payload.get("instructions") or payload.get("records") or []
283
+
284
+ records: list[InstructionRecord] = []
285
+ for item in candidates:
286
+ if not isinstance(item, dict):
287
+ continue
288
+ mnemonic = _string(
289
+ item.get("mnemonic")
290
+ or item.get("name")
291
+ or item.get("instruction")
292
+ or item.get("syntax")
293
+ or item.get("asm")
294
+ ).casefold()
295
+ if not mnemonic:
296
+ continue
297
+ metadata = _string_map(item.get("metadata"))
298
+ url = _string(item.get("url") or item.get("reference_url") or item.get("reference"))
299
+ if url:
300
+ metadata["url"] = url
301
+ policy = _normalize_policy(
302
+ item.get("policy")
303
+ or item.get("tail_policy")
304
+ or metadata.get("policy")
305
+ or metadata.get("tail_policy")
306
+ or "agnostic"
307
+ )
308
+ masking = _normalize_masking(item.get("masking") or metadata.get("masking") or "unmasked")
309
+ metadata.setdefault("policy", policy)
310
+ metadata.setdefault("masking", masking)
311
+ metadata.setdefault("tail_policy", _tail_policy(policy))
312
+ mask_policy = _string(item.get("mask_policy") or metadata.get("mask_policy") or _mask_policy(policy, masking))
313
+ if mask_policy:
314
+ metadata.setdefault("mask_policy", mask_policy)
315
+ if item.get("extension"):
316
+ metadata.setdefault("extension", _string(item.get("extension")))
317
+ description = _instruction_semantics(item, url, docs_pages)
318
+ records.append(
319
+ InstructionRecord(
320
+ mnemonic=mnemonic,
321
+ form=_instruction_form(item, mnemonic),
322
+ summary=_string(
323
+ item.get("summary")
324
+ or item.get("brief")
325
+ or item.get("description_text")
326
+ or f"{mnemonic} instruction."
327
+ ).rstrip(".") + ".",
328
+ architecture="riscv",
329
+ isa=_normalize_isa(
330
+ item.get("isa")
331
+ or item.get("extensions")
332
+ or item.get("extension")
333
+ or metadata.get("extensions")
334
+ or ["V"]
335
+ ),
336
+ operand_details=[
337
+ {
338
+ key: str(value).strip()
339
+ for key, value in operand.items()
340
+ if str(value).strip()
341
+ }
342
+ for operand in (item.get("operand_details") or [])
343
+ if isinstance(operand, dict)
344
+ ],
345
+ metadata=metadata,
346
+ aliases=_string_list(item.get("aliases")),
347
+ description=description,
348
+ source="riscv-unified-db",
349
+ )
350
+ )
351
+ return records
352
+
353
+
354
+ def _infer_intrinsic_policy(name: str) -> tuple[str, str]:
355
+ lowered = name.casefold()
356
+ policy = "agnostic"
357
+ masking = "masked" if lowered.endswith(("_m", "_mu", "_tum", "_tumu")) or "_m_" in lowered else "unmasked"
358
+ for suffix in ("_tumu", "_tum", "_mu", "_tu"):
359
+ if lowered.endswith(suffix):
360
+ policy = suffix.removeprefix("_")
361
+ break
362
+ return policy, masking
363
+
364
+
365
+ def _normalize_instruction_ref(raw_ref: dict[str, Any], intrinsic_name: str) -> dict[str, str]:
366
+ ref_name = _string(raw_ref.get("name") or raw_ref.get("mnemonic") or raw_ref.get("instruction"))
367
+ form = _string(raw_ref.get("form") or raw_ref.get("syntax") or ref_name)
368
+ inferred_policy, inferred_masking = _infer_intrinsic_policy(intrinsic_name)
369
+ policy = _normalize_policy(raw_ref.get("policy") or raw_ref.get("tail_policy") or inferred_policy)
370
+ masking = _normalize_masking(raw_ref.get("masking") or inferred_masking)
371
+ ref = {
372
+ "architecture": "riscv",
373
+ "name": ref_name,
374
+ "form": form or ref_name,
375
+ "isa": _string(raw_ref.get("isa") or raw_ref.get("extension")),
376
+ "policy": policy,
377
+ "masking": masking,
378
+ "tail_policy": _string(raw_ref.get("tail_policy") or _tail_policy(policy)),
379
+ "mask_policy": _string(raw_ref.get("mask_policy") or _mask_policy(policy, masking)),
380
+ }
381
+ return {key: value for key, value in ref.items() if value}
382
+
383
+
384
+ def parse_riscv_intrinsics_payload(text: str) -> list[IntrinsicRecord]:
385
+ payload = json.loads(text)
386
+ if isinstance(payload, dict) and payload.get("format") == "riscv-rvv-intrinsics-v1":
387
+ candidates = payload.get("intrinsics") or []
388
+ elif isinstance(payload, list):
389
+ candidates = payload
390
+ else:
391
+ candidates = payload.get("intrinsics") or payload.get("records") or []
392
+
393
+ records: list[IntrinsicRecord] = []
394
+ for item in candidates:
395
+ if not isinstance(item, dict):
396
+ continue
397
+ name = _string(item.get("name"))
398
+ if not name:
399
+ continue
400
+ refs: list[dict[str, str]] = []
401
+ rendered_instructions: list[str] = []
402
+ for raw_ref in item.get("instruction_refs") or []:
403
+ if not isinstance(raw_ref, dict):
404
+ continue
405
+ ref = _normalize_instruction_ref(raw_ref, name)
406
+ if not ref["name"]:
407
+ continue
408
+ if not ref["form"]:
409
+ ref["form"] = ref["name"]
410
+ rendered_instructions.append(ref["form"])
411
+ refs.append(ref)
412
+ if not refs:
413
+ for raw_instruction in item.get("instructions") or []:
414
+ ref = _normalize_instruction_ref({"name": raw_instruction, "form": raw_instruction}, name)
415
+ if ref["name"]:
416
+ rendered_instructions.append(ref["form"])
417
+ refs.append(ref)
418
+ inferred_policy, inferred_masking = _infer_intrinsic_policy(name)
419
+ metadata = _string_map(item.get("metadata"))
420
+ metadata.setdefault("policy", inferred_policy)
421
+ metadata.setdefault("masking", inferred_masking)
422
+ metadata.setdefault("tail_policy", _tail_policy(inferred_policy))
423
+ mask_policy = _mask_policy(inferred_policy, inferred_masking)
424
+ if mask_policy:
425
+ metadata.setdefault("mask_policy", mask_policy)
426
+ records.append(
427
+ IntrinsicRecord(
428
+ name=name,
429
+ signature=_string(item.get("signature") or name),
430
+ description=_string(item.get("description")),
431
+ header=_string(item.get("header") or "riscv_vector.h"),
432
+ url=_string(item.get("url") or item.get("reference_url") or RISCV_RVV_INTRINSICS_PROJECT_URL),
433
+ architecture="riscv",
434
+ isa=_normalize_isa(item.get("isa") or ["V"]),
435
+ category=_string(item.get("category") or "RVV"),
436
+ subcategory=_string(item.get("subcategory")),
437
+ instructions=rendered_instructions,
438
+ instruction_refs=refs,
439
+ metadata=metadata,
440
+ doc_sections=_string_map(item.get("doc_sections") or item.get("sections")),
441
+ notes=_string_list(item.get("notes")),
442
+ aliases=_string_list(item.get("aliases")),
443
+ source="rvv-intrinsic-doc",
444
+ )
445
+ )
446
+ return records