gisel 0.2.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.
gisel/eval.py ADDED
@@ -0,0 +1,1008 @@
1
+ #!/usr/bin/env python3
2
+ """Independent evaluator for the Core IR normal form (schema 0.4).
3
+
4
+ This is the Phase 2 reference implementation. It reads a
5
+ `legalizer-spec-core` JSON document (the `-gen-legalizer-core` normal form),
6
+ a per-configuration feature truth table, and a list of queries (one JSON
7
+ object per line, the same shape as the frozen AArch64 oracle dump), and
8
+ answers each query with the triple
9
+
10
+ (action, type_idx, new_type)
11
+
12
+ exactly as the hand-written C++ builder chain does. It does not import
13
+ TableGen, does not read the emitter, and does not parse any generated `.inc`.
14
+
15
+ Evaluation is first-match: rule sets in document order, rules inside a rule
16
+ set in `rule_index` order, the first rule whose effective guard is true wins.
17
+
18
+ The effective guard of a rule is `guard && shim_guard`, where `guard` is the
19
+ serialised `predicate` and `shim_guard` is the implicit size/count/vector test
20
+ the C++ shim adds for lattice operators (for example `widen slot pow2` only
21
+ fires when the slot is a scalar whose size is not a power of two). Terminal
22
+ actions and whole-type `replace` mutations carry no implicit guard.
23
+
24
+ An unknown feature string, predicate node, value node, target form, or action
25
+ raises `EvalError` instead of guessing, matching the deliberate-no-defaults
26
+ contract of the earlier 0.3 evaluator.
27
+
28
+ Usage:
29
+ python tools/core_eval_04.py \
30
+ --doc ~/legalizer-baseline/aarch64.core.json \
31
+ --features tests/aarch64-oracle/features.json \
32
+ --cases tests/aarch64-oracle/a64_ref_dump.jsonl
33
+ """
34
+ import argparse
35
+ import json
36
+ import re
37
+ import sys
38
+
39
+ NO_MUTATION_ACTIONS = {"Legal", "Lower", "Libcall", "Custom", "Unsupported"}
40
+ SCALARIZE_ACTION = "FewerElements" # scalarize projects to FewerElements
41
+
42
+
43
+ class EvalError(Exception):
44
+ def __init__(self, code, detail):
45
+ Exception.__init__(self, "%s: %s" % (code, detail))
46
+ self.code = code
47
+ self.detail = detail
48
+
49
+
50
+ # ---------------------------------------------------------------------------
51
+ # LLT model
52
+ #
53
+ # LLTs are plain tuples; everything below is closed over this representation.
54
+ # ("scalar", bits) ANY_SCALAR
55
+ # ("integer", bits) INTEGER (extended LLT)
56
+ # ("float", bits) IEEE float, bits in {16,32,64,128}
57
+ # ("bf16",) bfloat16
58
+ # ("pointer", addrspace, bits) POINTER
59
+ # ("vector", lanes, scalable, elem) fixed or scalable vector
60
+ # ("invalid",) LLT_invalid
61
+ # ---------------------------------------------------------------------------
62
+
63
+ _FLOAT_BITS = {16, 32, 64, 128}
64
+
65
+
66
+ def is_scalar(t):
67
+ """LLT::isScalar(): ANY_SCALAR, INTEGER and FLOAT are all scalar."""
68
+ return t[0] in ("scalar", "integer", "float", "bf16")
69
+
70
+
71
+ def is_integer(t):
72
+ return t[0] == "integer"
73
+
74
+
75
+ def is_float(t):
76
+ return t[0] == "float" or t[0] == "bf16"
77
+
78
+
79
+ def is_float_ieee(t):
80
+ return t[0] == "float"
81
+
82
+
83
+ def is_bf16(t):
84
+ return t[0] == "bf16"
85
+
86
+
87
+ def is_pointer(t):
88
+ return t[0] == "pointer"
89
+
90
+
91
+ def is_vector(t):
92
+ return t[0] == "vector"
93
+
94
+
95
+ def is_scalable(t):
96
+ return is_vector(t) and t[2]
97
+
98
+
99
+ def is_fixed_vector(t):
100
+ return is_vector(t) and not t[2]
101
+
102
+
103
+ def is_pointer_vector(t):
104
+ return is_vector(t) and t[3][0] == "pointer"
105
+
106
+
107
+ def is_valid(t):
108
+ return t[0] != "invalid"
109
+
110
+
111
+ def scalar_size(t):
112
+ """getScalarSizeInBits: element size for vectors, scalar/pointer width else."""
113
+ if t[0] == "vector":
114
+ return scalar_size(t[3])
115
+ if t[0] == "pointer":
116
+ return t[2]
117
+ if t[0] == "bf16":
118
+ return 16
119
+ return t[1]
120
+
121
+
122
+ def size_in_bits(t):
123
+ """(known_min_bits, scalable) for getSizeInBits."""
124
+ if t[0] == "vector":
125
+ elt, scalable = size_in_bits(t[3])
126
+ return (elt * t[1], scalable or t[2])
127
+ if t[0] == "pointer":
128
+ return (t[2], False)
129
+ if t[0] == "bf16":
130
+ return (16, False)
131
+ return (t[1], False)
132
+
133
+
134
+ def size_in_bytes(t):
135
+ """getSizeInBytes: (min + 7) // 8."""
136
+ bits, scalable = size_in_bits(t)
137
+ return ((bits + 7) // 8, scalable)
138
+
139
+
140
+ def num_elems(t):
141
+ # In the NDEBUG build the oracle runs in, getElementCount()'s
142
+ # isVector() assert is compiled out and a scalar/pointer stores
143
+ # ElementCount::getFixed(0), so getNumElements() returns 0 there.
144
+ if is_vector(t):
145
+ return t[1]
146
+ return 0
147
+
148
+
149
+ def get_scalar_type(t):
150
+ return t[3] if is_vector(t) else t
151
+
152
+
153
+ def get_element_type(t):
154
+ assert is_vector(t)
155
+ return t[3]
156
+
157
+
158
+ def get_address_space(t):
159
+ assert t[0] == "pointer" or (is_vector(t) and t[3][0] == "pointer")
160
+ if is_vector(t):
161
+ return t[3][2]
162
+ return t[1]
163
+
164
+
165
+ def change_element_type(t, new_elt):
166
+ """changeElementType: vector keeps its lanes; scalar becomes new_elt."""
167
+ if is_vector(t):
168
+ return ("vector", t[1], t[2], new_elt)
169
+ return new_elt
170
+
171
+
172
+ def change_element_size(t, new_size):
173
+ """changeElementSize: invalid for pointers, keeps vector lanes."""
174
+ assert t[0] != "pointer" and not is_pointer_vector(t)
175
+ if is_vector(t):
176
+ return ("vector", t[1], t[2], change_element_size(t[3], new_size))
177
+ if is_integer(t):
178
+ return ("integer", new_size)
179
+ if is_float_ieee(t):
180
+ return ("float", new_size)
181
+ # bf16 and ANY_SCALAR both degrade to a plain scalar here, matching
182
+ # LLT::changeElementSize (isFloatIEEE() is false for bf16).
183
+ return ("scalar", new_size)
184
+
185
+
186
+ def scalar_or_vector(lanes, scalable, elem):
187
+ """LLT::scalarOrVector: one fixed lane is the element type itself."""
188
+ if lanes == 1 and not scalable:
189
+ return elem
190
+ return ("vector", lanes, scalable, elem)
191
+
192
+
193
+ def change_element_count_div(t, n):
194
+ """divLanes: element count / n, keeping the scalable flag."""
195
+ assert is_vector(t)
196
+ return scalar_or_vector(t[1] // n, t[2], t[3])
197
+
198
+
199
+ def fixed_vector(lanes, elem):
200
+ return ("vector", lanes, False, elem)
201
+
202
+
203
+ def scalable_vector(lanes, elem):
204
+ return ("vector", lanes, True, elem)
205
+
206
+
207
+ def integer_type(bits):
208
+ return ("integer", bits)
209
+
210
+
211
+ def pow2ceil(x):
212
+ # LLVM PowerOf2Ceil: 1 << ceil(log2(x)); for 0 returns 1.
213
+ if x <= 1:
214
+ return 1
215
+ p = 1
216
+ while p < x:
217
+ p <<= 1
218
+ return p
219
+
220
+
221
+ def is_pow2_32(x):
222
+ return x != 0 and (x & (x - 1)) == 0
223
+
224
+
225
+ def align_to(x, n):
226
+ return ((x + n - 1) // n) * n
227
+
228
+
229
+ # ---------------------------------------------------------------------------
230
+ # Parsers
231
+ # ---------------------------------------------------------------------------
232
+
233
+ _CORE_VEC_RE = re.compile(r"^(nxv|v)(\d+)(.*)$")
234
+ _CORE_SCALAR_RE = re.compile(r"^(s|i|f)(\d+)$")
235
+ _CORE_PTR_RE = re.compile(r"^p(\d+):(\d+|\*)$")
236
+ _ORACLE_VEC_RE = re.compile(r"^<(\d+|vscale x \d+) x (.*)>$")
237
+
238
+
239
+ def _parse_elem_spelling(s, oracle):
240
+ if s == "bf16":
241
+ return ("bf16",)
242
+ m = _CORE_SCALAR_RE.match(s)
243
+ if m:
244
+ kind, bits = m.group(1), int(m.group(2))
245
+ if kind == "s":
246
+ return ("scalar", bits)
247
+ if kind == "i":
248
+ return ("integer", bits)
249
+ return ("float", bits)
250
+ if not oracle:
251
+ m = _CORE_PTR_RE.match(s)
252
+ if m:
253
+ w = m.group(2)
254
+ return ("pointer", int(m.group(1)), int(w) if w != "*" else "*")
255
+ else:
256
+ m = re.match(r"^p(\d+)$", s)
257
+ if m:
258
+ # The frozen oracle was produced with PointerBits=64.
259
+ return ("pointer", int(m.group(1)), 64)
260
+ raise EvalError("bad_llt", "cannot parse LLT element %r" % (s,))
261
+
262
+
263
+ def parse_core_llt(s):
264
+ """Canonical Core IR spelling: s32, i32, f32, bf16, p0:64, v4s32, nxv4s32."""
265
+ if s == "bf16":
266
+ return ("bf16",)
267
+ m = _CORE_VEC_RE.match(s)
268
+ if m:
269
+ lanes = int(m.group(2))
270
+ scalable = m.group(1) == "nxv"
271
+ elem = _parse_elem_spelling(m.group(3), oracle=False)
272
+ return ("vector", lanes, scalable, elem)
273
+ m = _CORE_PTR_RE.match(s)
274
+ if m:
275
+ w = m.group(2)
276
+ return ("pointer", int(m.group(1)), int(w) if w != "*" else "*")
277
+ return _parse_elem_spelling(s, oracle=False)
278
+
279
+
280
+ def parse_oracle_llt(s):
281
+ """LLT::print spelling: s32, i32, f32, bf16, p0, <4 x s32>, <vscale x 4 x s32>."""
282
+ if s == "LLT_invalid":
283
+ return ("invalid",)
284
+ if s == "bf16":
285
+ return ("bf16",)
286
+ m = _ORACLE_VEC_RE.match(s)
287
+ if m:
288
+ head, elem = m.group(1), m.group(2)
289
+ if head.startswith("vscale"):
290
+ lanes = int(head.split()[2])
291
+ scalable = True
292
+ else:
293
+ lanes = int(head)
294
+ scalable = False
295
+ return ("vector", lanes, scalable, _parse_elem_spelling(elem, oracle=True))
296
+ return _parse_elem_spelling(s, oracle=True)
297
+
298
+
299
+ def parse_case_llt(s):
300
+ """A frozen-case type: Core IR spelling or LLT::print spelling.
301
+
302
+ Our own oracle dumps write `p0:32`/`v4s32` (kind and pointer width are
303
+ explicit); the older AArch64 dump writes LLT::print (`<4 x s32>`, `p0`).
304
+ """
305
+ if s == "LLT_invalid":
306
+ return ("invalid",)
307
+ if s.startswith("<"):
308
+ return parse_oracle_llt(s)
309
+ try:
310
+ return parse_core_llt(s)
311
+ except EvalError:
312
+ return parse_oracle_llt(s) # `p0`: LLT::print drops the pointer width
313
+
314
+
315
+ def _is_scalar_like(t):
316
+ """LLT::isScalar(): ANY_SCALAR, INTEGER and FLOAT are all 'scalar'."""
317
+ return t[0] in ("scalar", "integer", "float", "bf16")
318
+
319
+
320
+ def has_wildcard(t):
321
+ """A pointer whose width the configuration decides (`p0:*`)."""
322
+ if t[0] == "pointer":
323
+ return t[2] == "*"
324
+ if t[0] == "vector":
325
+ return has_wildcard(t[3])
326
+ return False
327
+
328
+
329
+ def llt_eq(a, b):
330
+ """LLT equality; a `p<as>:*` element matches any width (see core JSON)."""
331
+ """LLT operator==, including the ANY_SCALAR bag-of-bits rule.
332
+
333
+ If either side is ANY_SCALAR the comparison is `isScalar() && same
334
+ scalar size`, so s32 == i32 == f32 while i32 != f32. Vector equality
335
+ recurses through element types, so <8 x s8> == v8i8.
336
+ """
337
+ if a == b:
338
+ return True
339
+ if a[0] == "pointer" and b[0] == "pointer":
340
+ return a[1] == b[1] and (a[2] == "*" or b[2] == "*" or a[2] == b[2])
341
+ if a[0] == "scalar" or b[0] == "scalar":
342
+ return (_is_scalar_like(a) and _is_scalar_like(b)
343
+ and scalar_size(a) == scalar_size(b))
344
+ if is_vector(a) and is_vector(b):
345
+ return (a[1] == b[1] and a[2] == b[2]
346
+ and llt_eq(a[3], b[3]))
347
+ return False
348
+
349
+
350
+ def values_equal(a, b):
351
+ """Compare two Core IR values: LLTs use LLT operator==, rest use ==."""
352
+ if isinstance(a, tuple) and isinstance(b, tuple):
353
+ return llt_eq(a, b)
354
+ return a == b
355
+
356
+
357
+ def fmt_oracle_llt(t):
358
+ if not is_valid(t):
359
+ return "LLT_invalid"
360
+ if is_vector(t):
361
+ if t[2]:
362
+ return "<vscale x %d x %s>" % (t[1], fmt_oracle_llt(t[3]))
363
+ return "<%d x %s>" % (t[1], fmt_oracle_llt(t[3]))
364
+ if t[0] == "pointer":
365
+ return "p%d" % t[1]
366
+ if t[0] == "bf16":
367
+ return "bf16"
368
+ if t[0] == "float":
369
+ return "f%d" % t[1]
370
+ if t[0] == "integer":
371
+ return "i%d" % t[1]
372
+ if t[0] == "scalar":
373
+ return "s%d" % t[1]
374
+ return "LLT_invalid"
375
+
376
+
377
+ # ---------------------------------------------------------------------------
378
+ # Atomic ordering lattice (matching llvm/Support/AtomicOrdering.h)
379
+ # ---------------------------------------------------------------------------
380
+
381
+ _ORDERING_INDEX = {
382
+ "NotAtomic": 0, "Unordered": 1, "Monotonic": 2, "Acquire": 4,
383
+ "Release": 5, "AcquireRelease": 6, "SequentiallyConsistent": 7,
384
+ }
385
+ _ORDERING_FROM_TAG = {
386
+ "orderingUnordered": "Unordered",
387
+ "orderingMonotonic": "Monotonic",
388
+ "orderingAcquire": "Acquire",
389
+ "orderingRelease": "Release",
390
+ "orderingAcquireRelease": "AcquireRelease",
391
+ "orderingSequentiallyConsistent": "SequentiallyConsistent",
392
+ }
393
+
394
+ _STRONGER = {
395
+ 0: [False] * 8,
396
+ 1: [True, False, False, False, False, False, False, False],
397
+ 2: [True, True, False, False, False, False, False, False],
398
+ 4: [True, True, True, True, False, False, False, False],
399
+ 5: [True, True, True, False, False, False, False, False],
400
+ 6: [True, True, True, True, True, True, False, False],
401
+ 7: [True, True, True, True, True, True, True, False],
402
+ }
403
+ _AT_LEAST = {
404
+ 0: [True, False, False, False, False, False, False, False],
405
+ 1: [True, True, False, False, False, False, False, False],
406
+ 2: [True, True, True, False, False, False, False, False],
407
+ 4: [True, True, True, True, True, False, False, False],
408
+ 5: [True, True, True, False, False, True, False, False],
409
+ 6: [True, True, True, True, True, True, True, False],
410
+ 7: [True] * 8,
411
+ }
412
+
413
+
414
+ def is_stronger_than(a, b):
415
+ return _STRONGER[a][b]
416
+
417
+
418
+ def is_at_least_or_stronger_than(a, b):
419
+ return _AT_LEAST[a][b]
420
+
421
+
422
+ # ---------------------------------------------------------------------------
423
+ # Feature truth table
424
+ # ---------------------------------------------------------------------------
425
+
426
+ class Features:
427
+ def __init__(self, table):
428
+ self.table = table # config -> {feature_string: bool}
429
+
430
+ def get(self, config, feat):
431
+ row = self.table.get(config)
432
+ if row is None:
433
+ raise EvalError("unknown_config", "no feature row for %r" % (config,))
434
+ if feat not in row:
435
+ raise EvalError("unknown_feature", "no truth value for %r in %r" % (feat, config))
436
+ return row[feat]
437
+
438
+
439
+ # ---------------------------------------------------------------------------
440
+ # Document / query model
441
+ # ---------------------------------------------------------------------------
442
+
443
+ class MemDesc:
444
+ __slots__ = ("memory_ty", "align", "ordering", "failure_ordering")
445
+
446
+ def __init__(self, memory_ty, align, ordering="NotAtomic",
447
+ failure_ordering="NotAtomic"):
448
+ self.memory_ty = memory_ty
449
+ self.align = align
450
+ self.ordering = ordering
451
+ self.failure_ordering = failure_ordering
452
+
453
+
454
+ class Evaluator:
455
+ def __init__(self, doc, features):
456
+ self.features = features
457
+ self.rulesets = doc["rulesets"]
458
+ self.by_opcode = {}
459
+ for rs_idx, rs in enumerate(self.rulesets):
460
+ for op in rs["opcodes"]:
461
+ self.by_opcode.setdefault(op, []).append(rs_idx)
462
+
463
+ # -- value nodes -------------------------------------------------------
464
+ def value(self, node, types, mems, config):
465
+ k = node["kind"]
466
+ a = node.get("args", [])
467
+ if k == "int":
468
+ return node["value"]
469
+ if k == "type":
470
+ return self.type_value(node["value"], types, mems, config)
471
+ if k == "slot":
472
+ return types[node["index"]]
473
+ if k == "memSlot":
474
+ return mems[a[0]["value"]]
475
+ if k == "elemOf":
476
+ return get_element_type(self.value(a[0], types, mems, config))
477
+ if k == "scalarOf":
478
+ return get_scalar_type(self.value(a[0], types, mems, config))
479
+ if k == "numElems":
480
+ return num_elems(self.value(a[0], types, mems, config))
481
+ if k == "bitsOf":
482
+ return scalar_size(self.value(a[0], types, mems, config))
483
+ if k == "sizeOf":
484
+ arg = a[0]
485
+ if arg.get("kind") == "memSlot":
486
+ m = self.value(arg, types, mems, config)
487
+ return size_in_bits(m.memory_ty)[0]
488
+ return size_in_bits(self.value(arg, types, mems, config))[0]
489
+ if k == "sizeInBytesOf":
490
+ return size_in_bytes(self.value(a[0], types, mems, config))[0]
491
+ if k == "alignOf":
492
+ return self.value(a[0], types, mems, config).align
493
+ if k == "addrSpace":
494
+ return get_address_space(self.value(a[0], types, mems, config))
495
+ if k == "memTy":
496
+ return self.value(a[0], types, mems, config).memory_ty
497
+ if k == "memOrdering":
498
+ return self.value(a[0], types, mems, config).ordering
499
+ if k == "memFailureOrdering":
500
+ return self.value(a[0], types, mems, config).failure_ordering
501
+ if k == "ty":
502
+ return self.value(a[0], types, mems, config)
503
+ if k == "sameAs":
504
+ return types[a[0]["value"]]
505
+ if k == "select":
506
+ if self.bool_value(a[0], types, mems, config):
507
+ return self.value(a[1], types, mems, config)
508
+ return self.value(a[2], types, mems, config)
509
+ if k == "mul":
510
+ return self.int_value(a[0], types, mems, config) * self.int_value(a[1], types, mems, config)
511
+ if k == "urem":
512
+ return self.int_value(a[0], types, mems, config) % self.int_value(a[1], types, mems, config)
513
+ if k == "udiv":
514
+ return self.int_value(a[0], types, mems, config) // self.int_value(a[1], types, mems, config)
515
+ if k == "pow2Ceil":
516
+ return pow2ceil(self.int_value(a[0], types, mems, config))
517
+ if k == "intOfSize":
518
+ return integer_type(self.int_value(a[0], types, mems, config))
519
+ if k == "vecOf":
520
+ return fixed_vector(self.int_value(a[0], types, mems, config),
521
+ self.llt_value(a[1], types, mems, config))
522
+ if k == "scalarOrVecOf":
523
+ return scalar_or_vector(self.int_value(a[0], types, mems, config),
524
+ False,
525
+ self.llt_value(a[1], types, mems, config))
526
+ if k == "eltTypeTo":
527
+ return change_element_type(self.llt_value(a[0], types, mems, config),
528
+ self.llt_value(a[1], types, mems, config))
529
+ if k == "eltSizeTo":
530
+ return change_element_size(self.llt_value(a[0], types, mems, config),
531
+ self.int_value(a[1], types, mems, config))
532
+ if k == "divLanes":
533
+ return change_element_count_div(self.llt_value(a[0], types, mems, config),
534
+ self.int_value(a[1], types, mems, config))
535
+ if k == "replace":
536
+ return self.value(a[0], types, mems, config)
537
+ raise EvalError("unknown_value", "value node %r" % (k,))
538
+
539
+ def type_value(self, v, types, mems, config):
540
+ """A type-valued node: an LLT spelling, or a feature-selected pair.
541
+
542
+ The core JSON writes a subtarget-dependent type as
543
+ ``{"kind":"type","value":{"select":{...}}}`` (emitter ``coreTypeValue``);
544
+ the branch is resolved here, not in the document.
545
+ """
546
+ if isinstance(v, str):
547
+ return parse_core_llt(v)
548
+ sel = v["select"]
549
+ if self.bool_value(sel["cond"], types, mems, config):
550
+ return self.type_value(sel["then"], types, mems, config)
551
+ return self.type_value(sel["else"], types, mems, config)
552
+
553
+ def int_value(self, node, types, mems, config):
554
+ v = self.value(node, types, mems, config)
555
+ if isinstance(v, bool) or not isinstance(v, int):
556
+ raise EvalError("bad_int", "node %r is not an Int" % (node.get("kind"),))
557
+ return v
558
+
559
+ def llt_value(self, node, types, mems, config):
560
+ v = self.value(node, types, mems, config)
561
+ if not isinstance(v, tuple):
562
+ raise EvalError("bad_llt", "node %r is not an LLT" % (node.get("kind"),))
563
+ return v
564
+
565
+ # -- boolean nodes -----------------------------------------------------
566
+ def bool_value(self, node, types, mems, config):
567
+ k = node["kind"]
568
+ a = node.get("args", [])
569
+ if k == "leaf":
570
+ name = node.get("name")
571
+ if name == "true":
572
+ return True
573
+ if name == "hasFeature":
574
+ return self.features.get(config, a[0])
575
+ raise EvalError("unknown_leaf", "leaf %r" % (name,))
576
+ if k == "true":
577
+ return True
578
+ if k == "all":
579
+ return all(self.bool_value(c, types, mems, config) for c in a)
580
+ if k == "any":
581
+ return any(self.bool_value(c, types, mems, config) for c in a)
582
+ if k == "not":
583
+ # schema-0.4 writes `{"kind":"not","arg":...}` for a negated DAG
584
+ # predicate and `args` for a CoreNot node; accept both.
585
+ child = a[0] if a else node["arg"]
586
+ return not self.bool_value(child, types, mems, config)
587
+ if k == "nary":
588
+ # `{"kind":"nary","op":"all_of"|"any_of","args":[...]}`: the
589
+ # DAG-shaped spelling the emitter uses inside select guards.
590
+ op = node.get("op")
591
+ if op == "all_of":
592
+ return all(self.bool_value(c, types, mems, config) for c in a)
593
+ if op == "any_of":
594
+ return any(self.bool_value(c, types, mems, config) for c in a)
595
+ raise EvalError("unknown_nary", "nary op %r" % (op,))
596
+ if k == "isPow2":
597
+ return is_pow2_32(self.int_value(a[0], types, mems, config))
598
+ if k == "isByteSized":
599
+ bits, _ = size_in_bits(self.llt_value(a[0], types, mems, config))
600
+ return bits % 8 == 0
601
+ if k == "isBFloat16":
602
+ return is_bf16(self.llt_value(a[0], types, mems, config))
603
+ if k == "isFloat16":
604
+ t = self.llt_value(a[0], types, mems, config)
605
+ return t[0] == "float" and t[1] == 16
606
+ if k in ("in", "inPairs", "inTriples", "inMemDesc"):
607
+ return self.membership(node, types, mems, config)
608
+ if k in ("eq", "ne", "lt", "le", "gt", "ge"):
609
+ return self.compare(node, types, mems, config)
610
+ raise EvalError("unknown_bool", "bool node %r" % (k,))
611
+
612
+ # -- membership --------------------------------------------------------
613
+ def _slot_index(self, node):
614
+ if node.get("kind") == "slot":
615
+ return node["index"]
616
+ raise EvalError("bad_slot", "tuple membership needs a literal slot")
617
+
618
+ def membership(self, node, types, mems, config):
619
+ k = node["kind"]
620
+ a = node["args"]
621
+ if k == "inMemDesc":
622
+ i0 = self._slot_index(a[0])
623
+ i1 = self._slot_index(a[1])
624
+ mnode = a[2]
625
+ if mnode.get("kind") != "memSlot":
626
+ raise EvalError("bad_memslot", "inMemDesc third operand")
627
+ m = mems[mnode["args"][0]["value"]]
628
+ elems = a[3]["elems"]
629
+ for e in elems:
630
+ t0 = self.type_value(e[0], types, mems, config)
631
+ t1 = self.type_value(e[1], types, mems, config)
632
+ mt = self.type_value(e[2], types, mems, config)
633
+ align = e[3]
634
+ size_ok = (llt_eq(m.memory_ty, mt) if has_wildcard(mt)
635
+ else size_in_bits(m.memory_ty)[0] == size_in_bits(mt)[0])
636
+ if (llt_eq(types[i0], t0) and llt_eq(types[i1], t1)
637
+ and m.align >= align and size_ok):
638
+ return True
639
+ return False
640
+
641
+ lhs = a[0]
642
+ rhs = a[-1]
643
+ nslots = len(a) - 1
644
+
645
+ if k == "inPairs":
646
+ i0 = self._slot_index(a[0]); i1 = self._slot_index(a[1])
647
+ arity = rhs.get("arity", 2)
648
+ for e in rhs["elems"]:
649
+ if (llt_eq(types[i0], self.type_value(e[0], types, mems, config))
650
+ and llt_eq(types[i1], self.type_value(e[1], types, mems, config))):
651
+ return True
652
+ return False
653
+ if k == "inTriples":
654
+ i0 = self._slot_index(a[0]); i1 = self._slot_index(a[1]); i2 = self._slot_index(a[2])
655
+ for e in rhs["elems"]:
656
+ if (llt_eq(types[i0], self.type_value(e[0], types, mems, config))
657
+ and llt_eq(types[i1], self.type_value(e[1], types, mems, config))
658
+ and llt_eq(types[i2], self.type_value(e[2], types, mems, config))):
659
+ return True
660
+ return False
661
+
662
+ # one-slot `in`
663
+ if rhs.get("kind") == "clamp":
664
+ v = self.int_value(lhs, types, mems, config)
665
+ lo = rhs["args"][0]["value"]
666
+ hi = rhs["args"][1]["value"]
667
+ return lo <= v <= hi
668
+ if rhs.get("kind") == "type":
669
+ return llt_eq(self.llt_value(lhs, types, mems, config),
670
+ self.type_value(rhs["value"], types, mems, config))
671
+ if rhs.get("kind") == "set" and rhs.get("of") == "Type":
672
+ v = self.llt_value(lhs, types, mems, config)
673
+ for e in rhs["elems"]:
674
+ if llt_eq(v, self.type_value(e, types, mems, config)):
675
+ return True
676
+ return False
677
+ if rhs.get("kind") == "set" and rhs.get("of") == "Kind":
678
+ # Lhs must be SortOf; the emitter lowers it to a disjunction of
679
+ # kind predicates over the SortOf operand.
680
+ if lhs.get("kind") != "sortOf":
681
+ raise EvalError("bad_kind_set", "Kind set can only test SortOf")
682
+ t = self.llt_value(lhs["args"][0], types, mems, config)
683
+ for e in rhs["elems"]:
684
+ if _kind_predicate(e, t):
685
+ return True
686
+ return False
687
+ raise EvalError("unknown_set", "set %r" % (rhs.get("kind"),))
688
+
689
+ # -- comparisons -------------------------------------------------------
690
+ def compare(self, node, types, mems, config):
691
+ k = node["kind"]
692
+ lhs = node["args"][0]
693
+ rhs = node["args"][1]
694
+ if lhs.get("kind") == "sortOf":
695
+ t = self.llt_value(lhs["args"][0], types, mems, config)
696
+ pred = _kind_predicate(rhs.get("kind"), t)
697
+ return (not pred) if k == "ne" else pred
698
+ if lhs.get("kind") in ("memOrdering", "memFailureOrdering"):
699
+ v = self.value(lhs, types, mems, config)
700
+ o = _ORDERING_FROM_TAG.get(rhs.get("kind"))
701
+ if o is None:
702
+ raise EvalError("bad_ordering", "not an ordering tag: %r" % (rhs.get("kind"),))
703
+ vi = _ORDERING_INDEX[v]
704
+ oi = _ORDERING_INDEX[o]
705
+ if k == "eq":
706
+ return vi == oi
707
+ if k == "ne":
708
+ return vi != oi
709
+ if k == "ge":
710
+ return is_at_least_or_stronger_than(vi, oi)
711
+ if k == "gt":
712
+ return is_stronger_than(vi, oi)
713
+ if k == "le":
714
+ return not is_stronger_than(vi, oi)
715
+ if k == "lt":
716
+ return not is_at_least_or_stronger_than(vi, oi)
717
+ raise EvalError("bad_ordering_cmp", k)
718
+ lv = self.value(lhs, types, mems, config)
719
+ rv = self.value(rhs, types, mems, config)
720
+ if k == "eq":
721
+ return values_equal(lv, rv)
722
+ if k == "ne":
723
+ return not values_equal(lv, rv)
724
+ if k == "lt":
725
+ return lv < rv
726
+ if k == "le":
727
+ return lv <= rv
728
+ if k == "gt":
729
+ return lv > rv
730
+ return lv >= rv
731
+
732
+
733
+ def _kind_predicate(tag, t):
734
+ """SortOf tag -> LLT kind predicate."""
735
+ if tag == "kindScalar":
736
+ return is_scalar(t)
737
+ if tag == "kindVector":
738
+ return is_vector(t)
739
+ if tag == "kindScalableVector":
740
+ return is_scalable(t)
741
+ if tag == "kindPointer":
742
+ return is_pointer(t)
743
+ if tag == "kindPointerVector":
744
+ return is_pointer_vector(t)
745
+ raise EvalError("bad_kind", "kind tag %r" % (tag,))
746
+
747
+
748
+ # ---------------------------------------------------------------------------
749
+ # Shim: implicit guard + new_type for (op, on-form, to-form)
750
+ # ---------------------------------------------------------------------------
751
+
752
+ def _slot_of(on_node):
753
+ if on_node.get("kind") == "slot":
754
+ return on_node["index"]
755
+ if on_node.get("kind") == "elemOf":
756
+ inner = on_node["args"][0]
757
+ if inner.get("kind") != "slot":
758
+ raise EvalError("bad_on", "nested ElemOf has no type-index projection")
759
+ return inner["index"]
760
+ raise EvalError("bad_on", "observation is not a slot/elemOf")
761
+
762
+
763
+ def shim_guard_and_newtype(op, on_node, to_node, types, mems, ev, config):
764
+ """Return (implicit_guard, new_type) for a matched rule.
765
+
766
+ `new_type` is an LLT tuple; ("invalid",) means "not mutated". When the
767
+ implicit guard is false the mutation is deliberately not computed, exactly
768
+ as the C++ shim's `actionIf` skips the mutation lambda for a query that
769
+ fails its size/count test.
770
+ """
771
+ if on_node is None:
772
+ # Terminal action: guard is the whole rule.
773
+ return True, ("invalid",)
774
+ idx = _slot_of(on_node)
775
+ on_form = "elem" if on_node.get("kind") == "elemOf" else "slot"
776
+
777
+ if to_node is None:
778
+ # scalarize
779
+ t = types[idx]
780
+ if not is_vector(t):
781
+ return False, ("invalid",)
782
+ return True, get_element_type(t)
783
+
784
+ if to_node.get("kind") == "int":
785
+ to_form = "int"
786
+ to_int = to_node["value"]
787
+ else:
788
+ to_form = to_node["kind"]
789
+
790
+ t = types[idx]
791
+ ssz = scalar_size(t)
792
+ size, _ = size_in_bits(t)
793
+
794
+ # Whole-type mutation: the target is the rule's own type expression.
795
+ if to_form == "replace":
796
+ return True, ev.llt_value(to_node["args"][0], types, mems, config)
797
+
798
+ if op == "widen":
799
+ if on_form == "slot" and to_form in ("select", "int"):
800
+ minsz = ev.int_value(to_node, types, mems, config)
801
+ guard = (ssz < minsz) or (not is_pow2_32(ssz))
802
+ if not guard:
803
+ return False, ("invalid",)
804
+ return True, change_element_size(t, max(pow2ceil(ssz), minsz))
805
+ if to_form == "pow2":
806
+ if on_form == "slot":
807
+ guard = is_scalar(t) and not is_pow2_32(size)
808
+ else:
809
+ guard = not is_pow2_32(ssz)
810
+ if not guard:
811
+ return False, ("invalid",)
812
+ return True, change_element_size(t, pow2ceil(ssz))
813
+ if to_form == "pow2Min":
814
+ n = to_node["args"][0]["value"]
815
+ if on_form == "slot":
816
+ guard = is_scalar(t) and not is_pow2_32(size)
817
+ else:
818
+ guard = not is_pow2_32(ssz)
819
+ if not guard:
820
+ return False, ("invalid",)
821
+ return True, change_element_size(t, max(pow2ceil(ssz), n))
822
+ if to_form == "multipleOf":
823
+ n = to_node["args"][0]["value"]
824
+ if on_form == "slot":
825
+ guard = is_scalar(t) and (size % n != 0)
826
+ else:
827
+ guard = ssz % n != 0
828
+ if not guard:
829
+ return False, ("invalid",)
830
+ return True, change_element_size(t, align_to(ssz, n))
831
+ if to_form == "ty":
832
+ target = ev.llt_value(to_node["args"][0], types, mems, config)
833
+ tsz = scalar_size(target)
834
+ if on_form == "slot":
835
+ guard = is_scalar(t) and size < tsz
836
+ else:
837
+ guard = ssz < tsz
838
+ if not guard:
839
+ return False, ("invalid",)
840
+ return True, change_element_size(t, tsz)
841
+ if to_form == "sameAs":
842
+ k = to_node["args"][0]["value"]
843
+ other = scalar_size(types[k])
844
+ guard = other > ssz
845
+ if not guard:
846
+ return False, ("invalid",)
847
+ return True, change_element_size(t, other)
848
+ if to_form == "minVectorSize":
849
+ n = to_node["args"][0]["value"]
850
+ if not (is_fixed_vector(t) and size < n):
851
+ return False, ("invalid",)
852
+ num = num_elems(t)
853
+ new_elt = change_element_size(get_element_type(t), n // num)
854
+ return True, fixed_vector(num, new_elt)
855
+ raise EvalError("unknown_shim", "%s %s %s" % (op, on_form, to_form))
856
+
857
+ if op == "narrow":
858
+ if to_form == "ty":
859
+ target = ev.llt_value(to_node["args"][0], types, mems, config)
860
+ tsz = scalar_size(target)
861
+ if on_form == "slot":
862
+ guard = is_scalar(t) and size > tsz
863
+ else:
864
+ guard = ssz > tsz
865
+ if not guard:
866
+ return False, ("invalid",)
867
+ return True, change_element_size(t, tsz)
868
+ if to_form == "sameAs":
869
+ k = to_node["args"][0]["value"]
870
+ other = scalar_size(types[k])
871
+ guard = other < ssz
872
+ if not guard:
873
+ return False, ("invalid",)
874
+ return True, change_element_size(t, other)
875
+ raise EvalError("unknown_shim", "%s %s %s" % (op, on_form, to_form))
876
+
877
+ if op == "more":
878
+ if to_form == "pow2":
879
+ if not (is_fixed_vector(t) and not is_pow2_32(num_elems(t))):
880
+ return False, ("invalid",)
881
+ return True, fixed_vector(pow2ceil(num_elems(t)), get_element_type(t))
882
+ raise EvalError("unknown_shim", "%s %s %s" % (op, on_form, to_form))
883
+
884
+ if op == "fewer":
885
+ if to_form == "ty":
886
+ target = ev.llt_value(to_node["args"][0], types, mems, config)
887
+ tsize, _ = size_in_bits(target)
888
+ if not (is_vector(t) and size > tsize):
889
+ return False, ("invalid",)
890
+ return True, change_element_type(t, target)
891
+ raise EvalError("unknown_shim", "%s %s %s" % (op, on_form, to_form))
892
+
893
+ if op == "bitcast":
894
+ if to_form == "ty":
895
+ target = ev.llt_value(to_node["args"][0], types, mems, config)
896
+ return True, change_element_type(t, target)
897
+ raise EvalError("unknown_shim", "%s %s %s" % (op, on_form, to_form))
898
+
899
+ raise EvalError("unknown_shim", "%s %s %s" % (op, on_form, to_form))
900
+
901
+ # ---------------------------------------------------------------------------
902
+ # Query evaluation
903
+ # ---------------------------------------------------------------------------
904
+
905
+ def evaluate(evaluator, config, opcode, types, mems):
906
+ idxs = evaluator.by_opcode.get(opcode)
907
+ if idxs is None:
908
+ raise EvalError("no_rules", "opcode %r has no rule sets" % (opcode,))
909
+ for rs_idx in idxs:
910
+ rs = evaluator.rulesets[rs_idx]
911
+ for rule in rs["rules"]:
912
+ if not evaluator.bool_value(rule["predicate"], types, mems, config):
913
+ continue
914
+ guard, new_type = shim_guard_and_newtype(
915
+ rule.get("op"), rule.get("on"), rule.get("to"),
916
+ types, mems, evaluator, config)
917
+ if not guard:
918
+ continue
919
+ action = rule["action"]
920
+ type_idx = rule.get("type_idx", 0)
921
+ return action, type_idx, new_type
922
+ # LegalizerInfo::getAction falls back to Unsupported when no rule fires.
923
+ return "Unsupported", 0, ("invalid",)
924
+
925
+
926
+ # ---------------------------------------------------------------------------
927
+ # Runner
928
+ # ---------------------------------------------------------------------------
929
+
930
+ def load_doc(path):
931
+ with open(path) as f:
932
+ doc = json.load(f)
933
+ if doc.get("version") != "0.4":
934
+ raise EvalError("bad_version", "expected schema 0.4, got %r" % (doc.get("version"),))
935
+ return doc
936
+
937
+
938
+ def load_features(path):
939
+ with open(path) as f:
940
+ return Features(json.load(f))
941
+
942
+
943
+ def run_cases(evaluator, cases_path):
944
+ total = 0
945
+ disagree = 0
946
+ errors = 0
947
+ first_failure = None
948
+ action_counts = {}
949
+ with open(cases_path) as f:
950
+ for line in f:
951
+ line = line.strip()
952
+ if not line:
953
+ continue
954
+ q = json.loads(line)
955
+ total += 1
956
+ types = [parse_case_llt(s) for s in q["types"]]
957
+ mems = []
958
+ if q["mem"] is not None:
959
+ mems.append(MemDesc(parse_case_llt(q["mem"][0]), q["mem"][1]))
960
+ try:
961
+ action, type_idx, new_type = evaluate(
962
+ evaluator, q["config"], q["name"], types, mems)
963
+ except EvalError as e:
964
+ errors += 1
965
+ if first_failure is None:
966
+ first_failure = "ERROR %s: %s" % (q, e)
967
+ continue
968
+ new_type_str = fmt_oracle_llt(new_type) if new_type is not None else "LLT_invalid"
969
+ want_new = parse_case_llt(q["new_type"])
970
+ action_counts[action] = action_counts.get(action, 0) + 1
971
+ if (action != q["action"] or type_idx != q["type_idx"]
972
+ or new_type != want_new):
973
+ disagree += 1
974
+ if first_failure is None:
975
+ first_failure = (
976
+ "%s %s %s mem=%s: got (%s,%d,%s) want (%s,%d,%s)"
977
+ % (q["config"], q["name"], q["types"], q["mem"],
978
+ action, type_idx, new_type_str,
979
+ q["action"], q["type_idx"], q["new_type"]))
980
+ return total, disagree, errors, first_failure, action_counts
981
+
982
+
983
+ def main(argv=None):
984
+ ap = argparse.ArgumentParser()
985
+ ap.add_argument("--doc", required=True)
986
+ ap.add_argument("--features", required=True)
987
+ ap.add_argument("--cases", required=True)
988
+ ap.add_argument("--verbose", action="store_true")
989
+ args = ap.parse_args(argv)
990
+
991
+ doc = load_doc(args.doc)
992
+ features = load_features(args.features)
993
+ evaluator = Evaluator(doc, features)
994
+
995
+ total, disagree, errors, first_failure, counts = run_cases(
996
+ evaluator, args.cases)
997
+
998
+ print("cases=%d disagreements=%d errors=%d" % (total, disagree, errors))
999
+ print("actions:", json.dumps(counts, sort_keys=True))
1000
+ if first_failure is not None:
1001
+ print("first failure:", first_failure)
1002
+ if disagree or errors:
1003
+ sys.exit(1)
1004
+ print("OK")
1005
+
1006
+
1007
+ if __name__ == "__main__":
1008
+ main()