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.
@@ -0,0 +1,176 @@
1
+ """Drive the llvm-exegesis → llvm-mc → llvm-mca pipeline per core.
2
+
3
+ The ingester no longer synthesises per-mnemonic asm snippets. It
4
+ enumerates every LLVM-schedulable opcode per target ``(triple, cpu)``
5
+ via ``llvm-exegesis``, disassembles the captured bytes, and feeds the
6
+ result through ``llvm-mca --instruction-tables=full --json``. Three
7
+ subprocess calls per core replace the ~55,000 calls of the former
8
+ regex-based synthesiser.
9
+
10
+ Failure modes:
11
+
12
+ - ``llvm-mca`` / ``llvm-exegesis`` / ``llvm-mc`` absent or too old:
13
+ :class:`LLVMMcaUnavailable`, which the CLI surfaces with an install
14
+ hint.
15
+ - any subprocess emits non-zero status or unparseable output:
16
+ :class:`LLVMMcaError` with the offending core's triple + cpu.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import shutil
22
+ import subprocess
23
+ from dataclasses import dataclass
24
+ from typing import Any, Iterable
25
+
26
+ from simdref.models import InstructionRecord
27
+ from simdref.perf_sources.cores import CANONICAL_CORES, CoreSpec
28
+ from simdref.perf_sources.merge import PerfRow
29
+
30
+ LLVM_MCA_MIN_VERSION: int = 18 # JSON output stabilised in LLVM 18.
31
+ LLVM_MCA_CITATION = "https://llvm.org/docs/CommandGuide/llvm-mca.html"
32
+
33
+
34
+ class LLVMMcaUnavailable(RuntimeError):
35
+ """Raised when a required LLVM binary is missing or too old."""
36
+
37
+ install_hint: str = (
38
+ "Install the LLVM toolchain (llvm-mca, llvm-exegesis, llvm-mc) to "
39
+ "build the modeled perf catalog:\n"
40
+ " apt: sudo apt install llvm\n"
41
+ " brew: brew install llvm\n"
42
+ " conda: conda install -c conda-forge llvm-tools\n"
43
+ "Or download the pre-built release artifact: simdref update"
44
+ )
45
+
46
+
47
+ class LLVMMcaError(RuntimeError):
48
+ """Raised when one of the pipeline stages emits unusable output."""
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class LLVMMcaVersion:
53
+ major: int
54
+ raw: str
55
+
56
+
57
+ def detect_llvm_mca_version(executable: str = "llvm-mca") -> LLVMMcaVersion:
58
+ """Return the LLVM major version, or raise :class:`LLVMMcaUnavailable`."""
59
+ if shutil.which(executable) is None:
60
+ raise LLVMMcaUnavailable(f"{executable!r} not found on PATH")
61
+ try:
62
+ proc = subprocess.run(
63
+ [executable, "--version"],
64
+ check=True,
65
+ capture_output=True,
66
+ text=True,
67
+ timeout=10,
68
+ )
69
+ except (subprocess.SubprocessError, OSError) as exc:
70
+ raise LLVMMcaUnavailable(f"failed to run {executable} --version: {exc}") from exc
71
+ for line in (proc.stdout + proc.stderr).splitlines():
72
+ token = line.lower()
73
+ if "llvm version" in token:
74
+ tail = token.split("llvm version", 1)[1].strip().split()[0]
75
+ head = tail.split(".", 1)[0]
76
+ if head.isdigit():
77
+ major = int(head)
78
+ if major < LLVM_MCA_MIN_VERSION:
79
+ raise LLVMMcaUnavailable(
80
+ f"llvm-mca {major} is older than required {LLVM_MCA_MIN_VERSION}"
81
+ )
82
+ return LLVMMcaVersion(major=major, raw=tail)
83
+ raise LLVMMcaUnavailable(f"could not parse version from {executable} --version")
84
+
85
+
86
+ def parse_llvm_mca_json(
87
+ payload: dict[str, Any],
88
+ *,
89
+ core: CoreSpec,
90
+ mnemonic: str,
91
+ mca_version: str,
92
+ ) -> PerfRow | None:
93
+ """Extract one :class:`PerfRow` from a single-region ``llvm-mca --json`` payload.
94
+
95
+ Kept as a small utility for callers that want to run llvm-mca on a
96
+ standalone snippet (outside the scheduling pipeline). It tolerates
97
+ both the modern ``InstructionInfoView`` schema (LLVM 18+) and the
98
+ legacy flat ``Instructions`` + ``SummaryView.IPC`` schema used by
99
+ older fixtures.
100
+ """
101
+ regions = payload.get("CodeRegions") or []
102
+ if not regions:
103
+ return None
104
+ region = regions[0]
105
+ info_view = region.get("InstructionInfoView") or {}
106
+ info_list = info_view.get("InstructionList") or []
107
+ latency: Any = None
108
+ rthroughput: Any = None
109
+ if info_list and isinstance(info_list[0], dict):
110
+ latency = info_list[0].get("Latency")
111
+ rthroughput = info_list[0].get("RThroughput")
112
+ if latency is None and rthroughput is None:
113
+ insts = region.get("Instructions") or []
114
+ if insts and isinstance(insts[0], dict):
115
+ latency = insts[0].get("Latency")
116
+ summary = region.get("SummaryView") or {}
117
+ ipc = summary.get("IPC")
118
+ if isinstance(ipc, (int, float)) and ipc > 0:
119
+ rthroughput = 1.0 / float(ipc)
120
+ cpi = ""
121
+ if isinstance(rthroughput, (int, float)):
122
+ cpi = f"{float(rthroughput):.3f}".rstrip("0").rstrip(".")
123
+ return PerfRow(
124
+ mnemonic=mnemonic,
125
+ core=core.canonical_id,
126
+ source="llvm-mca",
127
+ source_kind="modeled",
128
+ source_version=mca_version,
129
+ architecture="arm" if core.architecture == "aarch64" else core.architecture,
130
+ latency=str(latency) if latency is not None else "",
131
+ cpi=cpi,
132
+ applies_to="mnemonic",
133
+ citation_url=LLVM_MCA_CITATION,
134
+ )
135
+
136
+
137
+ def ingest_llvm_mca(
138
+ records: Iterable[InstructionRecord],
139
+ *,
140
+ cores: Iterable[CoreSpec] | None = None,
141
+ executable: str = "llvm-mca",
142
+ cache_root: Any = None,
143
+ ) -> tuple[list[PerfRow], str]:
144
+ """Enumerate LLVM-schedulable opcodes across cores, returning rows + version.
145
+
146
+ Each aarch64 / riscv core triggers one llvm-exegesis +
147
+ llvm-mc + llvm-mca pipeline, cached under ``vendor/perf-cache/``.
148
+ x86 cores are skipped (scheduling models come from uops.info).
149
+
150
+ *records* is accepted for API symmetry with the previous implementation
151
+ but is no longer consumed — the pipeline reads LLVM's own InstrInfo
152
+ tables rather than the catalog.
153
+
154
+ Raises :class:`LLVMMcaUnavailable` when a required binary is absent
155
+ or too old. Raises :class:`LLVMMcaError` on any pipeline failure —
156
+ no silent fallback.
157
+ """
158
+ # Lazy import to break the circular dependency (llvm_scheduling
159
+ # imports the error classes + citation URL from this module).
160
+ from simdref.perf_sources import llvm_scheduling # noqa: PLC0415
161
+
162
+ version = detect_llvm_mca_version(executable)
163
+ _ = list(records) # drain iterator; kept for signature compatibility
164
+ target_cores = list(cores) if cores is not None else list(CANONICAL_CORES)
165
+ rows: list[PerfRow] = []
166
+ for core in target_cores:
167
+ if core.architecture == "x86":
168
+ continue
169
+ rows.extend(
170
+ llvm_scheduling.collect_core_schedule(
171
+ core,
172
+ mca_version=version.raw,
173
+ cache_root=cache_root,
174
+ )
175
+ )
176
+ return rows, version.raw