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/__init__.py +3 -0
- gisel/__main__.py +6 -0
- gisel/backend/__init__.py +0 -0
- gisel/backend/check_json.py +57 -0
- gisel/backend/core_json.py +192 -0
- gisel/backend/llvm_inc.py +495 -0
- gisel/backend/table.py +617 -0
- gisel/cli.py +238 -0
- gisel/data/generic_opcodes.json +290 -0
- gisel/dsl.py +1469 -0
- gisel/eval.py +1008 -0
- gisel/td.py +915 -0
- gisel/vocab.py +28 -0
- gisel-0.2.0.dist-info/METADATA +379 -0
- gisel-0.2.0.dist-info/RECORD +19 -0
- gisel-0.2.0.dist-info/WHEEL +5 -0
- gisel-0.2.0.dist-info/entry_points.txt +2 -0
- gisel-0.2.0.dist-info/licenses/LICENSE +279 -0
- gisel-0.2.0.dist-info/top_level.txt +1 -0
gisel/dsl.py
ADDED
|
@@ -0,0 +1,1469 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Spike: a free-form (C/Python-style) DSL for the GlobalISel legalizer core IR.
|
|
3
|
+
|
|
4
|
+
The point is not to add a second IR. The DSL is *sugar over the existing
|
|
5
|
+
schema-0.4 normal form*: it parses statement/infix syntax and (a) compiles it to the same
|
|
6
|
+
schema-0.4 normal form `llvm-tblgen -gen-legalizer-core` emits, and (b) emits
|
|
7
|
+
a self-contained core-IR `.td` file that the existing backends consume, so both
|
|
8
|
+
paths can be diffed field-by-field after stripping the per-rule bookkeeping
|
|
9
|
+
keys (origin / rule_index / ruleset / schema / def).
|
|
10
|
+
|
|
11
|
+
Subset supported here (enough to re-derive real X86/AArch64 rules):
|
|
12
|
+
rule <opcode>+ :
|
|
13
|
+
<stmt>
|
|
14
|
+
...
|
|
15
|
+
stmt := legal|lower|custom|unsupported|libcall <pred>
|
|
16
|
+
| widen|narrow|more|fewer|bitcast ( <expr> , <expr> ) [ when <pred> ]
|
|
17
|
+
| scalarize ( <expr> ) [ when <pred> ]
|
|
18
|
+
| clamp_min ( INT , TYPE , <expr> )
|
|
19
|
+
| clamp_max ( INT , TYPE , <expr> ) # lanes: int or select(...)
|
|
20
|
+
pred := or of ( and of ( not | primary ) )
|
|
21
|
+
primary := true | ( pred ) | feature ( STR ) | in ( expr , [TYPE,...] | TYPE )
|
|
22
|
+
| expr ( ==|!=|<|<=|>|>= ) expr
|
|
23
|
+
expr := INT | TYPE | KIND | call ( ... )
|
|
24
|
+
|
|
25
|
+
Static checks: J1 sort, J2b comparison sorts, J3a action table, J3b slot
|
|
26
|
+
bounds (arity imported from GenericOpcodes.td via build_arity_manifest).
|
|
27
|
+
| select ( <expr> , <expr> , <expr> ) # int select
|
|
28
|
+
| select_type ( <expr> , TYPE , TYPE ) # type select (in ty(...))
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import json
|
|
32
|
+
import os
|
|
33
|
+
import re
|
|
34
|
+
import subprocess
|
|
35
|
+
import sys
|
|
36
|
+
import tempfile
|
|
37
|
+
|
|
38
|
+
# ---- normal-form node builders (schema 0.4) -------------------------------
|
|
39
|
+
|
|
40
|
+
def _node(kind, **extra):
|
|
41
|
+
d = {"kind": kind}
|
|
42
|
+
d.update(extra)
|
|
43
|
+
return d
|
|
44
|
+
|
|
45
|
+
def slot(i): return _node("slot", index=i)
|
|
46
|
+
def int_(v): return _node("int", value=v)
|
|
47
|
+
def type_(v): return _node("type", value=v)
|
|
48
|
+
def kind_(name): return _node(name)
|
|
49
|
+
def leaf(name, args): return _node("leaf", name=name, args=args)
|
|
50
|
+
def call1(k, a): return _node(k, args=[a])
|
|
51
|
+
def call2(k, a, b): return _node(k, args=[a, b])
|
|
52
|
+
def op1(k): return _node(k) # zero-arg constructor, e.g. pow2
|
|
53
|
+
|
|
54
|
+
KIND_TAGS = {"vector": "kindVector", "scalable_vector": "kindScalableVector",
|
|
55
|
+
"scalar": "kindScalar", "pointer": "kindPointer",
|
|
56
|
+
"pointer_vector": "kindPointerVector"}
|
|
57
|
+
ORDERING_TAGS = {"ordering_unordered": "orderingUnordered",
|
|
58
|
+
"ordering_monotonic": "orderingMonotonic",
|
|
59
|
+
"ordering_acquire": "orderingAcquire",
|
|
60
|
+
"ordering_release": "orderingRelease",
|
|
61
|
+
"ordering_acquire_release": "orderingAcquireRelease",
|
|
62
|
+
"ordering_sequentially_consistent": "orderingSequentiallyConsistent"}
|
|
63
|
+
ZERO_ARG = dict(KIND_TAGS, **ORDERING_TAGS)
|
|
64
|
+
ZERO_ARG["pow2"] = "pow2"
|
|
65
|
+
|
|
66
|
+
UNARY_CALL = {"elem_of": "elemOf", "scalar_of": "scalarOf", "sort_of": "sortOf",
|
|
67
|
+
"num_elems": "numElems", "bits_of": "bitsOf", "size_of": "sizeOf",
|
|
68
|
+
"size_in_bytes_of": "sizeInBytesOf", "is_byte_sized": "isByteSized",
|
|
69
|
+
"addr_space": "addrSpace", "align_of": "alignOf",
|
|
70
|
+
"mem_slot": "memSlot", "mem_ty": "memTy", "mem_ordering": "memOrdering",
|
|
71
|
+
"mem_failure_ordering": "memFailureOrdering",
|
|
72
|
+
"is_bfloat16": "isBFloat16", "is_float16": "isFloat16",
|
|
73
|
+
"is_pow2": "isPow2", "replace": "replace", "pow2_ceil": "pow2Ceil",
|
|
74
|
+
"multiple_of": "multipleOf", "same_as": "sameAs",
|
|
75
|
+
"min_vector_size": "minVectorSize", "int_of_size": "intOfSize",
|
|
76
|
+
"ty": "ty", "pow2_min": "pow2Min"}
|
|
77
|
+
BINARY_CALL = {"vec_of": "vecOf", "scalar_or_vec_of": "scalarOrVecOf",
|
|
78
|
+
"elt_type_to": "eltTypeTo", "elt_size_to": "eltSizeTo",
|
|
79
|
+
"div_lanes": "divLanes", "mul": "mul", "urem": "urem",
|
|
80
|
+
"udiv": "udiv", "clamp": "clamp"}
|
|
81
|
+
|
|
82
|
+
# A canonical type literal: scalar (s8/i32/f16/bf16), pointer (p0:64/p0:*),
|
|
83
|
+
# fixed vector (v16s8, v2p0:*) or scalable vector (nxv16i8).
|
|
84
|
+
TYPE_TOKEN_RE = re.compile(
|
|
85
|
+
r"(?:(?:nx)?v\d+)?(?:s\d+|i\d+|f\d+|bf16|p\d+:\d+|p\d+:\*)")
|
|
86
|
+
|
|
87
|
+
# ---- tokenizer ------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
class Tok:
|
|
90
|
+
__slots__ = ("t", "v", "line")
|
|
91
|
+
def __init__(self, t, v, line):
|
|
92
|
+
self.t, self.v, self.line = t, v, line
|
|
93
|
+
|
|
94
|
+
def tokenize(text, line_map=None):
|
|
95
|
+
toks = []
|
|
96
|
+
lines = text.splitlines()
|
|
97
|
+
# indentation -> INDENT/DEDENT, Python-style
|
|
98
|
+
stack = [0]
|
|
99
|
+
for ln, raw in enumerate(lines, 1):
|
|
100
|
+
# line_map[k] is the .gisel line a snippet line came from
|
|
101
|
+
sl = line_map[ln - 1] if line_map and ln - 1 < len(line_map) else ln
|
|
102
|
+
if not raw.strip():
|
|
103
|
+
continue
|
|
104
|
+
indent = len(raw) - len(raw.lstrip(" "))
|
|
105
|
+
if indent > stack[-1]:
|
|
106
|
+
stack.append(indent); toks.append(Tok("INDENT", None, sl))
|
|
107
|
+
while indent < stack[-1]:
|
|
108
|
+
stack.pop(); toks.append(Tok("DEDENT", None, sl))
|
|
109
|
+
i = 0
|
|
110
|
+
line = raw.strip()
|
|
111
|
+
n = len(line)
|
|
112
|
+
while i < n:
|
|
113
|
+
c = line[i]
|
|
114
|
+
if c.isspace():
|
|
115
|
+
i += 1; continue
|
|
116
|
+
if line.startswith(("==", "!=", "<=", ">="), i):
|
|
117
|
+
toks.append(Tok("OP", line[i:i+2], sl)); i += 2; continue
|
|
118
|
+
if c in "<>":
|
|
119
|
+
toks.append(Tok("OP", c, sl)); i += 1; continue
|
|
120
|
+
if c in "(),[]:":
|
|
121
|
+
toks.append(Tok(c, c, sl)); i += 1; continue
|
|
122
|
+
if c == '"':
|
|
123
|
+
j = i + 1
|
|
124
|
+
while j < n and line[j] != '"':
|
|
125
|
+
j += 1
|
|
126
|
+
toks.append(Tok("STR", line[i+1:j], sl)); i = j + 1; continue
|
|
127
|
+
if c.isdigit():
|
|
128
|
+
j = i
|
|
129
|
+
while j < n and line[j].isdigit():
|
|
130
|
+
j += 1
|
|
131
|
+
toks.append(Tok("INT", int(line[i:j]), sl)); i = j; continue
|
|
132
|
+
if c.isalpha():
|
|
133
|
+
m = TYPE_TOKEN_RE.match(line, i)
|
|
134
|
+
if m and (m.end() == n or not (line[m.end()].isalnum()
|
|
135
|
+
or line[m.end()] == "_")):
|
|
136
|
+
toks.append(Tok("TYPE", m.group(), sl)); i = m.end(); continue
|
|
137
|
+
if c.isalpha() or c == "_":
|
|
138
|
+
j = i
|
|
139
|
+
while j < n and (line[j].isalnum() or line[j] == "_"):
|
|
140
|
+
j += 1
|
|
141
|
+
toks.append(Tok("ID", line[i:j], sl)); i = j; continue
|
|
142
|
+
raise ValueError(f"line {sl}: bad char {c!r}")
|
|
143
|
+
toks.append(Tok("NEWLINE", None, sl))
|
|
144
|
+
while len(stack) > 1:
|
|
145
|
+
stack.pop(); toks.append(Tok("DEDENT", None, None))
|
|
146
|
+
toks.append(Tok("EOF", None, None))
|
|
147
|
+
return toks
|
|
148
|
+
|
|
149
|
+
# ---- parser ---------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
class Parser:
|
|
152
|
+
def __init__(self, text, line_map=None):
|
|
153
|
+
self.toks = tokenize(text, line_map)
|
|
154
|
+
self.p = 0
|
|
155
|
+
|
|
156
|
+
def peek(self, t=None):
|
|
157
|
+
tok = self.toks[self.p]
|
|
158
|
+
if t is None or tok.t == t:
|
|
159
|
+
return tok
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
def next(self):
|
|
163
|
+
tok = self.toks[self.p]
|
|
164
|
+
self.p += 1
|
|
165
|
+
return tok
|
|
166
|
+
|
|
167
|
+
def expect(self, t):
|
|
168
|
+
tok = self.next()
|
|
169
|
+
if tok.t != t:
|
|
170
|
+
raise ValueError(f"line {tok.line}: expected {t}, got {tok.t} ({tok.v!r})")
|
|
171
|
+
return tok
|
|
172
|
+
|
|
173
|
+
def parse(self):
|
|
174
|
+
out = []
|
|
175
|
+
while self.peek() and self.peek().t == "ID" and self.peek().v == "rule":
|
|
176
|
+
out.append(self.parse_ruleset())
|
|
177
|
+
return out
|
|
178
|
+
|
|
179
|
+
def parse_ruleset(self):
|
|
180
|
+
self.expect("ID") # 'rule'
|
|
181
|
+
opcodes = []
|
|
182
|
+
while self.peek() and self.peek().t == "ID":
|
|
183
|
+
opcodes.append(self.next().v)
|
|
184
|
+
self.expect(":")
|
|
185
|
+
self.expect("NEWLINE")
|
|
186
|
+
self.expect("INDENT")
|
|
187
|
+
stmts = []
|
|
188
|
+
while self.peek() and self.peek().t != "DEDENT" and self.peek().t != "EOF":
|
|
189
|
+
stmts.append(self.parse_stmt())
|
|
190
|
+
self.expect("NEWLINE")
|
|
191
|
+
self.expect("DEDENT")
|
|
192
|
+
return {"opcodes": opcodes, "stmts": stmts}
|
|
193
|
+
|
|
194
|
+
def parse_stmt(self):
|
|
195
|
+
kw = self.next()
|
|
196
|
+
assert kw.t == "ID", kw
|
|
197
|
+
k = kw.v
|
|
198
|
+
line = kw.line
|
|
199
|
+
if k in ("legal", "lower", "custom", "unsupported", "libcall"):
|
|
200
|
+
pred = self.parse_pred()
|
|
201
|
+
return {"op": k, "pred": pred, "_line": line}
|
|
202
|
+
if k in ("widen", "narrow", "more", "fewer", "bitcast"):
|
|
203
|
+
self.expect("(")
|
|
204
|
+
on = self.parse_expr()
|
|
205
|
+
self.expect(",")
|
|
206
|
+
to = self.parse_expr()
|
|
207
|
+
self.expect(")")
|
|
208
|
+
pred = None
|
|
209
|
+
if self.peek() and self.peek().t == "ID" and self.peek().v == "when":
|
|
210
|
+
self.next(); pred = self.parse_pred()
|
|
211
|
+
return {"op": k, "on": on, "to": to, "pred": pred, "_line": line}
|
|
212
|
+
if k == "scalarize":
|
|
213
|
+
self.expect("(")
|
|
214
|
+
on = self.parse_expr()
|
|
215
|
+
self.expect(")")
|
|
216
|
+
pred = None
|
|
217
|
+
if self.peek() and self.peek().t == "ID" and self.peek().v == "when":
|
|
218
|
+
self.next(); pred = self.parse_pred()
|
|
219
|
+
return {"op": k, "on": on, "to": None, "pred": pred, "_line": line}
|
|
220
|
+
if k in ("clamp_min", "clamp_max"):
|
|
221
|
+
self.expect("(")
|
|
222
|
+
idx = self.next().v; self.expect(",")
|
|
223
|
+
elt = self.next().v; self.expect(",")
|
|
224
|
+
lanes = self.parse_expr()
|
|
225
|
+
self.expect(")")
|
|
226
|
+
return {"op": k, "idx": idx, "elt": elt, "lanes": lanes,
|
|
227
|
+
"_line": line}
|
|
228
|
+
raise ValueError(f"line {kw.line}: unknown statement {k!r}")
|
|
229
|
+
|
|
230
|
+
# predicate grammar: or -> and -> not -> primary
|
|
231
|
+
def parse_pred(self):
|
|
232
|
+
# or -> and -> not -> primary; n-ary (flat), matching CoreAny/CoreAll
|
|
233
|
+
args = [self.parse_and()]
|
|
234
|
+
while self.peek() and self.peek().t == "ID" and self.peek().v == "or":
|
|
235
|
+
self.next(); args.append(self.parse_and())
|
|
236
|
+
return args[0] if len(args) == 1 else _node("any", args=args)
|
|
237
|
+
|
|
238
|
+
def parse_and(self):
|
|
239
|
+
args = [self.parse_not()]
|
|
240
|
+
while self.peek() and self.peek().t == "ID" and self.peek().v == "and":
|
|
241
|
+
self.next(); args.append(self.parse_not())
|
|
242
|
+
return args[0] if len(args) == 1 else _node("all", args=args)
|
|
243
|
+
|
|
244
|
+
def parse_not(self):
|
|
245
|
+
if self.peek() and self.peek().t == "ID" and self.peek().v == "not":
|
|
246
|
+
self.next()
|
|
247
|
+
return call1("not", self.parse_not())
|
|
248
|
+
return self.parse_primary()
|
|
249
|
+
|
|
250
|
+
def parse_primary(self):
|
|
251
|
+
if self.peek() and self.peek().t == "ID" and self.peek().v == "true":
|
|
252
|
+
self.next(); return leaf("true", [])
|
|
253
|
+
if self.peek("("):
|
|
254
|
+
self.next(); p = self.parse_pred(); self.expect(")"); return p
|
|
255
|
+
if self.peek() and self.peek().t == "ID" and self.peek().v == "feature":
|
|
256
|
+
self.next(); self.expect("("); st = self.next().v; self.expect(")")
|
|
257
|
+
return leaf("hasFeature", [st])
|
|
258
|
+
# explicit n-ary combinators (all_of/any_of) for arity-1 and readability
|
|
259
|
+
if self.peek() and self.peek().t == "ID" and self.peek().v in (
|
|
260
|
+
"in", "in_pairs", "in_triples", "in_mem_desc"):
|
|
261
|
+
return self.parse_membership()
|
|
262
|
+
if self.peek() and self.peek().t == "ID" and self.peek().v in (
|
|
263
|
+
"all_of", "any_of"):
|
|
264
|
+
return self.parse_combinator()
|
|
265
|
+
# Bool-returning observers can stand alone as a predicate primary
|
|
266
|
+
if self.peek() and self.peek().t == "ID" and self.peek().v in (
|
|
267
|
+
"is_bfloat16", "is_float16", "is_byte_sized", "is_pow2"):
|
|
268
|
+
return self.parse_expr()
|
|
269
|
+
# comparison
|
|
270
|
+
left = self.parse_expr()
|
|
271
|
+
tok = self.next()
|
|
272
|
+
if tok.t != "OP":
|
|
273
|
+
raise ValueError(f"line {tok.line}: expected comparison, got {tok.v!r}")
|
|
274
|
+
right = self.parse_expr()
|
|
275
|
+
return call2({"==": "eq", "!=": "ne", "<": "lt", "<=": "le",
|
|
276
|
+
">": "gt", ">=": "ge"}[tok.v], left, right)
|
|
277
|
+
|
|
278
|
+
def parse_combinator(self):
|
|
279
|
+
name = self.next().v
|
|
280
|
+
self.expect("(")
|
|
281
|
+
args = []
|
|
282
|
+
while True:
|
|
283
|
+
args.append(self.parse_pred())
|
|
284
|
+
if not self.peek(","):
|
|
285
|
+
break
|
|
286
|
+
self.next()
|
|
287
|
+
self.expect(")")
|
|
288
|
+
return _node("all" if name == "all_of" else "any", args=args)
|
|
289
|
+
|
|
290
|
+
def parse_membership(self):
|
|
291
|
+
name = self.next().v
|
|
292
|
+
self.expect("(")
|
|
293
|
+
a = self.parse_expr()
|
|
294
|
+
self.expect(",")
|
|
295
|
+
if name == "in":
|
|
296
|
+
rhs = self.parse_set_or_type()
|
|
297
|
+
self.expect(")")
|
|
298
|
+
return call2("in", a, rhs)
|
|
299
|
+
b = self.parse_expr()
|
|
300
|
+
self.expect(",")
|
|
301
|
+
if name == "in_pairs":
|
|
302
|
+
st = self.parse_set()
|
|
303
|
+
self.expect(")")
|
|
304
|
+
return _node("inPairs", args=[a, b, st])
|
|
305
|
+
c = self.parse_expr()
|
|
306
|
+
self.expect(",")
|
|
307
|
+
st = self.parse_set()
|
|
308
|
+
self.expect(")")
|
|
309
|
+
if name == "in_triples":
|
|
310
|
+
return _node("inTriples", args=[a, b, c, st])
|
|
311
|
+
return _node("inMemDesc", args=[a, b, c, st])
|
|
312
|
+
|
|
313
|
+
def parse_set_or_type(self):
|
|
314
|
+
if self.peek("["):
|
|
315
|
+
return self.parse_set()
|
|
316
|
+
return type_(self.parse_type_value())
|
|
317
|
+
|
|
318
|
+
def parse_set(self):
|
|
319
|
+
self.expect("[")
|
|
320
|
+
if self.peek("["):
|
|
321
|
+
rows = []
|
|
322
|
+
while True:
|
|
323
|
+
self.expect("[")
|
|
324
|
+
row = []
|
|
325
|
+
while True:
|
|
326
|
+
row.append(self.parse_type_value())
|
|
327
|
+
if not self.peek(","):
|
|
328
|
+
break
|
|
329
|
+
self.next()
|
|
330
|
+
self.expect("]")
|
|
331
|
+
rows.append(row)
|
|
332
|
+
if not self.peek(","):
|
|
333
|
+
break
|
|
334
|
+
self.next()
|
|
335
|
+
self.expect("]")
|
|
336
|
+
return _node("set", of="Type", arity=len(rows[0]), elems=rows)
|
|
337
|
+
if self.peek().t == "ID" and self.peek().v == "mem_desc":
|
|
338
|
+
rows = []
|
|
339
|
+
while True:
|
|
340
|
+
self.next() # mem_desc
|
|
341
|
+
self.expect("(")
|
|
342
|
+
t0 = self.parse_type_value(); self.expect(",")
|
|
343
|
+
t1 = self.parse_type_value(); self.expect(",")
|
|
344
|
+
t2 = self.parse_type_value(); self.expect(",")
|
|
345
|
+
al = self.next().v; self.expect(")")
|
|
346
|
+
rows.append([t0, t1, t2, al])
|
|
347
|
+
if not self.peek(","):
|
|
348
|
+
break
|
|
349
|
+
self.next()
|
|
350
|
+
self.expect("]")
|
|
351
|
+
return _node("set", of="Type", arity=2, elems=rows)
|
|
352
|
+
elems = []
|
|
353
|
+
while True:
|
|
354
|
+
elems.append(self.parse_type_value())
|
|
355
|
+
if not self.peek(","):
|
|
356
|
+
break
|
|
357
|
+
self.next()
|
|
358
|
+
self.expect("]")
|
|
359
|
+
if len(elems) == 1:
|
|
360
|
+
return type_(elems[0])
|
|
361
|
+
return _node("set", of="Type", elems=elems)
|
|
362
|
+
|
|
363
|
+
def parse_type_value(self):
|
|
364
|
+
if self.peek().t == "TYPE":
|
|
365
|
+
return self.next().v
|
|
366
|
+
if self.peek().t == "ID" and self.peek().v == "select_type":
|
|
367
|
+
return self.parse_select_type()
|
|
368
|
+
if self.peek().t == "ID" and self.peek().v[:1].isupper():
|
|
369
|
+
# A named record from the target's scaffolding (a LegalizeNamedType,
|
|
370
|
+
# LegalizeSelectType or LegalizeCoreTypeSet). The .gisel source
|
|
371
|
+
# spells these by name; the generator keeps the name in the .td.
|
|
372
|
+
return self.next().v
|
|
373
|
+
raise ValueError(f"line {self.peek().line}: expected a type, got "
|
|
374
|
+
f"{self.peek().t} {self.peek().v!r}")
|
|
375
|
+
|
|
376
|
+
def parse_select_type(self):
|
|
377
|
+
self.next() # select_type
|
|
378
|
+
self.expect("(")
|
|
379
|
+
cond = self.parse_pred()
|
|
380
|
+
self.expect(",")
|
|
381
|
+
then = self.parse_type_value()
|
|
382
|
+
self.expect(",")
|
|
383
|
+
els = self.parse_type_value()
|
|
384
|
+
self.expect(")")
|
|
385
|
+
return {"select": {"cond": cond, "then": then, "else": els}}
|
|
386
|
+
|
|
387
|
+
def parse_expr(self):
|
|
388
|
+
tok = self.peek()
|
|
389
|
+
if tok.t == "INT":
|
|
390
|
+
self.next(); return int_(tok.v)
|
|
391
|
+
if tok.t == "TYPE":
|
|
392
|
+
self.next(); return type_(tok.v)
|
|
393
|
+
if tok.t == "ID" and tok.v[:1].isupper():
|
|
394
|
+
self.next(); return type_(tok.v)
|
|
395
|
+
if tok.t == "ID":
|
|
396
|
+
v = tok.v
|
|
397
|
+
if v in ZERO_ARG:
|
|
398
|
+
self.next(); return kind_(ZERO_ARG[v])
|
|
399
|
+
return self.parse_call()
|
|
400
|
+
raise ValueError(f"line {tok.line}: expected expr, got {tok.t} {tok.v!r}")
|
|
401
|
+
|
|
402
|
+
def parse_call(self):
|
|
403
|
+
name = self.next().v
|
|
404
|
+
self.expect("(")
|
|
405
|
+
if name == "feature":
|
|
406
|
+
st = self.next().v
|
|
407
|
+
self.expect(")")
|
|
408
|
+
return leaf("hasFeature", [st])
|
|
409
|
+
if name == "select":
|
|
410
|
+
cond = self.parse_pred(); self.expect(",")
|
|
411
|
+
a = self.parse_expr(); self.expect(",")
|
|
412
|
+
b = self.parse_expr(); self.expect(")")
|
|
413
|
+
return _node("select", args=[cond, a, b])
|
|
414
|
+
if name == "select_type":
|
|
415
|
+
cond = self.parse_pred(); self.expect(",")
|
|
416
|
+
t = self.parse_type_value(); self.expect(",")
|
|
417
|
+
e = self.parse_type_value(); self.expect(")")
|
|
418
|
+
return type_({"select": {"cond": cond, "then": t, "else": e}})
|
|
419
|
+
if name == "slot":
|
|
420
|
+
i = self.next().v
|
|
421
|
+
self.expect(")")
|
|
422
|
+
return slot(i)
|
|
423
|
+
args = []
|
|
424
|
+
if not self.peek(")"):
|
|
425
|
+
while True:
|
|
426
|
+
args.append(self.parse_expr())
|
|
427
|
+
if not self.peek(","):
|
|
428
|
+
break
|
|
429
|
+
self.next()
|
|
430
|
+
self.expect(")")
|
|
431
|
+
if name in UNARY_CALL:
|
|
432
|
+
if len(args) != 1:
|
|
433
|
+
raise ValueError(f"{name}: expects 1 argument, got {len(args)}")
|
|
434
|
+
return call1(UNARY_CALL[name], args[0])
|
|
435
|
+
if name in BINARY_CALL:
|
|
436
|
+
if len(args) != 2:
|
|
437
|
+
raise ValueError(f"{name}: expects 2 arguments, got {len(args)}")
|
|
438
|
+
return call2(BINARY_CALL[name], args[0], args[1])
|
|
439
|
+
raise ValueError(f"line {self.toks[self.p-2].line}: unknown constructor "
|
|
440
|
+
f"{name!r}")
|
|
441
|
+
|
|
442
|
+
# ---- compiler: AST -> normal form ----------------------------------------
|
|
443
|
+
|
|
444
|
+
def compile_rulesets(ast):
|
|
445
|
+
"""Return list of rule dicts in schema-0.4 normal form (semantic fields)."""
|
|
446
|
+
out = []
|
|
447
|
+
for rs in ast:
|
|
448
|
+
for st in rs["stmts"]:
|
|
449
|
+
rule = compile_stmt(st)
|
|
450
|
+
if "_line" in st:
|
|
451
|
+
rule["_line"] = st["_line"]
|
|
452
|
+
out.append(rule)
|
|
453
|
+
return out
|
|
454
|
+
|
|
455
|
+
def type_idx_of(on):
|
|
456
|
+
"""The type slot a rule mutates, from its observation (coreTypeIdxOf)."""
|
|
457
|
+
if on["kind"] == "slot":
|
|
458
|
+
return on["index"]
|
|
459
|
+
if on["kind"] == "elemOf":
|
|
460
|
+
return type_idx_of(on["args"][0])
|
|
461
|
+
if on["kind"] == "int":
|
|
462
|
+
return on["value"]
|
|
463
|
+
return 0
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def compile_stmt(st):
|
|
467
|
+
op = st["op"]
|
|
468
|
+
pred = st.get("pred")
|
|
469
|
+
if op in ("legal", "lower", "custom", "unsupported", "libcall"):
|
|
470
|
+
action = {"legal": "Legal", "lower": "Lower", "custom": "Custom",
|
|
471
|
+
"unsupported": "Unsupported", "libcall": "Libcall"}[op]
|
|
472
|
+
return {"action": action, "type_idx": 0, "op": op,
|
|
473
|
+
"predicate": pred if pred is not None else leaf("true", [])}
|
|
474
|
+
if op in ("widen", "narrow", "more", "fewer", "bitcast"):
|
|
475
|
+
action = {"widen": "WidenScalar", "narrow": "NarrowScalar",
|
|
476
|
+
"more": "MoreElements", "fewer": "FewerElements",
|
|
477
|
+
"bitcast": "Bitcast"}[op]
|
|
478
|
+
return {"action": action, "type_idx": type_idx_of(st["on"]), "op": op,
|
|
479
|
+
"on": st["on"], "to": st["to"],
|
|
480
|
+
"predicate": pred if pred is not None else leaf("true", [])}
|
|
481
|
+
if op == "scalarize":
|
|
482
|
+
return {"action": "FewerElements", "type_idx": type_idx_of(st["on"]),
|
|
483
|
+
"op": "scalarize", "on": st["on"],
|
|
484
|
+
"predicate": pred if pred is not None else leaf("true", [])}
|
|
485
|
+
if op in ("clamp_min", "clamp_max"):
|
|
486
|
+
idx, elt, lanes = st["idx"], st["elt"], st["lanes"]
|
|
487
|
+
if op == "clamp_min":
|
|
488
|
+
to = call1("replace", call2("vecOf", lanes, call1("elemOf", slot(idx))))
|
|
489
|
+
cmp = call2("lt", call1("numElems", slot(idx)), lanes)
|
|
490
|
+
action = "MoreElements"; opname = "more"
|
|
491
|
+
else:
|
|
492
|
+
to = call1("replace", call2("scalarOrVecOf", lanes, call1("elemOf", slot(idx))))
|
|
493
|
+
cmp = call2("gt", call1("numElems", slot(idx)), lanes)
|
|
494
|
+
action = "FewerElements"; opname = "fewer"
|
|
495
|
+
fixed = [
|
|
496
|
+
call2("eq", call1("sortOf", slot(idx)), kind_("kindVector")),
|
|
497
|
+
call1("not", call2("eq", call1("sortOf", slot(idx)), kind_("kindScalableVector"))),
|
|
498
|
+
call2("eq", call1("elemOf", slot(idx)), call1("ty", type_(elt))),
|
|
499
|
+
cmp,
|
|
500
|
+
]
|
|
501
|
+
guard = _node("all", args=fixed)
|
|
502
|
+
return {"action": action, "type_idx": int(idx), "op": opname,
|
|
503
|
+
"on": slot(idx), "to": to, "predicate": guard}
|
|
504
|
+
raise ValueError(f"unknown stmt {op!r}")
|
|
505
|
+
|
|
506
|
+
# ---- compare against real -gen-legalizer-core output ----------------------
|
|
507
|
+
|
|
508
|
+
STRIP_KEYS = {"schema", "ruleset", "rule_index", "origin", "def", "_line"}
|
|
509
|
+
|
|
510
|
+
def strip(rule):
|
|
511
|
+
return {k: v for k, v in rule.items() if k not in STRIP_KEYS}
|
|
512
|
+
|
|
513
|
+
def _llt_leaf(name):
|
|
514
|
+
"""A non-vector canonical type name -> the LLT expression a
|
|
515
|
+
LegalizeNamedType carries. This is the direct inverse of
|
|
516
|
+
normalizeLlt() in LegalizerInfoEmitter.cpp: any expression that
|
|
517
|
+
normalizes back to `name` is faithful."""
|
|
518
|
+
if re.fullmatch(r"s\d+", name):
|
|
519
|
+
return f"LLT::scalar({name[1:]})"
|
|
520
|
+
if re.fullmatch(r"i\d+", name):
|
|
521
|
+
return f"LLT::integer({name[1:]})"
|
|
522
|
+
kinds = {"f16": "float16", "f32": "float32", "f64": "float64",
|
|
523
|
+
"f128": "float128", "bf16": "bfloat16"}
|
|
524
|
+
if name in kinds:
|
|
525
|
+
return f"LLT::{kinds[name]}()"
|
|
526
|
+
m = re.fullmatch(r"p(\d+):(\d+)", name)
|
|
527
|
+
if m:
|
|
528
|
+
return f"LLT::pointer({m.group(1)}, {m.group(2)})"
|
|
529
|
+
m = re.fullmatch(r"p(\d+):\*", name)
|
|
530
|
+
if m:
|
|
531
|
+
# A target-config-dependent pointer width. normalizeLlt maps any
|
|
532
|
+
# non-digit size back to `pN:*`, so this round-trips even though the
|
|
533
|
+
# original expression was target-specific.
|
|
534
|
+
return f"LLT::pointer({m.group(1)}, 0 ? 64 : 32)"
|
|
535
|
+
raise ValueError(f"unsupported type name {name!r}")
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def _llt(name):
|
|
539
|
+
"""Canonical type name -> LLT string, vectors included."""
|
|
540
|
+
m = re.fullmatch(r"((?:nx)?v)(\d+)(.+)", name)
|
|
541
|
+
if m:
|
|
542
|
+
head = ("LLT::scalable_vector" if m.group(1) == "nxv"
|
|
543
|
+
else "LLT::fixed_vector")
|
|
544
|
+
return f"{head}({m.group(2)}, {_llt_leaf(m.group(3))})"
|
|
545
|
+
return _llt_leaf(name)
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
class TdEmitter:
|
|
549
|
+
"""Emit a self-contained core-IR .td file from schema-0.4 normal form.
|
|
550
|
+
|
|
551
|
+
Covers the whole vocabulary the X86/AArch64 corpora exercise, including the
|
|
552
|
+
tuple/memdesc membership sets and the legacy `nary` predicate-dag that a
|
|
553
|
+
LegalizeSelectType cond can still carry (predicateJson's escape path).
|
|
554
|
+
"""
|
|
555
|
+
|
|
556
|
+
_OPS = {"legal": "CoreLegal", "lower": "CoreLower", "custom": "CoreCustom",
|
|
557
|
+
"unsupported": "CoreUnsupported", "libcall": "CoreLibcall"}
|
|
558
|
+
_ALWAYS = {"legal": "CoreAlwaysLegal", "lower": "CoreAlwaysLower",
|
|
559
|
+
"custom": "CoreAlwaysCustom",
|
|
560
|
+
"unsupported": "CoreAlwaysUnsupported",
|
|
561
|
+
"libcall": "CoreAlwaysLibcall"}
|
|
562
|
+
_LATTICE = {"widen": "CoreWiden", "narrow": "CoreNarrow", "more": "CoreMore",
|
|
563
|
+
"fewer": "CoreFewer", "bitcast": "CoreBitcast"}
|
|
564
|
+
_KIND = {"kindVector": "KindVector", "kindScalableVector": "KindScalableVector",
|
|
565
|
+
"kindScalar": "KindScalar", "kindPointer": "KindPointer",
|
|
566
|
+
"kindPointerVector": "KindPointerVector"}
|
|
567
|
+
_ORDERING = {"orderingUnordered": "OrderingUnordered",
|
|
568
|
+
"orderingMonotonic": "OrderingMonotonic",
|
|
569
|
+
"orderingAcquire": "OrderingAcquire",
|
|
570
|
+
"orderingRelease": "OrderingRelease",
|
|
571
|
+
"orderingAcquireRelease": "OrderingAcquireRelease",
|
|
572
|
+
"orderingSequentiallyConsistent": "OrderingSequentiallyConsistent"}
|
|
573
|
+
_CMP = {"eq": "CoreEq", "ne": "CoreNe", "lt": "CoreLt", "le": "CoreLe",
|
|
574
|
+
"gt": "CoreGt", "ge": "CoreGe"}
|
|
575
|
+
_UNARY = {"elemOf": "ElemOf", "scalarOf": "ScalarOf", "sortOf": "SortOf",
|
|
576
|
+
"numElems": "NumElems", "bitsOf": "BitsOf", "sizeOf": "SizeOf",
|
|
577
|
+
"sizeInBytesOf": "SizeInBytesOf", "isByteSized": "IsByteSized",
|
|
578
|
+
"addrSpace": "AddrSpace", "alignOf": "AlignOf",
|
|
579
|
+
"memSlot": "MemSlot", "memTy": "MemTy", "memOrdering": "MemOrdering",
|
|
580
|
+
"memFailureOrdering": "MemFailureOrdering",
|
|
581
|
+
"isBFloat16": "IsBFloat16", "isFloat16": "IsFloat16",
|
|
582
|
+
"isPow2": "CoreIsPow2", "replace": "Replace",
|
|
583
|
+
"pow2Ceil": "Pow2Ceil", "multipleOf": "MultipleOf",
|
|
584
|
+
"sameAs": "SameAs", "minVectorSize": "MinVectorSize",
|
|
585
|
+
"intOfSize": "IntOfSize"}
|
|
586
|
+
_BINARY = {"eltTypeTo": "EltTypeTo", "eltSizeTo": "EltSizeTo",
|
|
587
|
+
"divLanes": "DivLanes", "mul": "CoreMul", "urem": "CoreURem",
|
|
588
|
+
"udiv": "CoreUDiv"}
|
|
589
|
+
|
|
590
|
+
def __init__(self, rulesets):
|
|
591
|
+
# rulesets: [{"def": str, "opcodes": [str], "rules": [rule, ...]}, ...]
|
|
592
|
+
self.rulesets = rulesets
|
|
593
|
+
self.features = {} # cond string -> record name
|
|
594
|
+
self.pred_dags = {} # reconstructed dag text -> record name
|
|
595
|
+
self.types = {} # canonical type name -> record name
|
|
596
|
+
self.selects = {} # (cond, then, else) -> record name (children first)
|
|
597
|
+
|
|
598
|
+
def _feat(self, s):
|
|
599
|
+
if s not in self.features:
|
|
600
|
+
self.features[s] = f"F_{len(self.features)}"
|
|
601
|
+
return self.features[s]
|
|
602
|
+
|
|
603
|
+
def _type(self, name):
|
|
604
|
+
if name not in self.types:
|
|
605
|
+
self.types[name] = f"T_{len(self.types)}"
|
|
606
|
+
return self.types[name]
|
|
607
|
+
|
|
608
|
+
def _type_value(self, v):
|
|
609
|
+
"""A type value (str or nested select dict) -> a record name."""
|
|
610
|
+
if isinstance(v, str):
|
|
611
|
+
return self._type(v)
|
|
612
|
+
sel = v["select"]
|
|
613
|
+
cond = self._select_cond(sel["cond"])
|
|
614
|
+
then = self._type_value(sel["then"])
|
|
615
|
+
els = self._type_value(sel["else"])
|
|
616
|
+
key = (cond, then, els)
|
|
617
|
+
if key not in self.selects:
|
|
618
|
+
self.selects[key] = f"ST_{len(self.selects)}"
|
|
619
|
+
return self.selects[key]
|
|
620
|
+
|
|
621
|
+
def _select_cond(self, cond):
|
|
622
|
+
"""A select-type guard -> a LegalizeBoolPredicate record name."""
|
|
623
|
+
k = cond["kind"]
|
|
624
|
+
if k == "leaf" and cond["name"] == "hasFeature":
|
|
625
|
+
return self._feat(cond["args"][0])
|
|
626
|
+
if k == "nary":
|
|
627
|
+
return self._pred_dag(cond)
|
|
628
|
+
raise ValueError(f"unsupported select cond {cond!r}")
|
|
629
|
+
|
|
630
|
+
def _pred_dag(self, node):
|
|
631
|
+
text = self._pred_dag_text(node)
|
|
632
|
+
if text not in self.pred_dags:
|
|
633
|
+
self.pred_dags[text] = f"PD_{len(self.pred_dags)}"
|
|
634
|
+
return self.pred_dags[text]
|
|
635
|
+
|
|
636
|
+
def _pred_dag_text(self, node):
|
|
637
|
+
k = node["kind"]
|
|
638
|
+
if k == "leaf" and node["name"] == "hasFeature":
|
|
639
|
+
return self._feat(node["args"][0])
|
|
640
|
+
if k == "nary":
|
|
641
|
+
return (f"({node['op']} "
|
|
642
|
+
+ ", ".join(self._pred_dag_text(a) for a in node["args"])
|
|
643
|
+
+ ")")
|
|
644
|
+
if k == "not":
|
|
645
|
+
# legacy singular form used by the nary escape path
|
|
646
|
+
return f"(not {self._pred_dag_text(node['arg'])})"
|
|
647
|
+
raise ValueError(f"unsupported predicate-dag node {node!r}")
|
|
648
|
+
|
|
649
|
+
def _set(self, n):
|
|
650
|
+
arity = n.get("arity", 1)
|
|
651
|
+
elems = n["elems"]
|
|
652
|
+
if not elems:
|
|
653
|
+
raise ValueError("empty set literal")
|
|
654
|
+
if arity == 1:
|
|
655
|
+
return "[" + ", ".join(self._type_value(e) for e in elems) + "]"
|
|
656
|
+
# memdesc: {Type0, Type1, MemTy, Align}; arity counts the type slots (2).
|
|
657
|
+
if arity == 2 and len(elems[0]) == 4:
|
|
658
|
+
return ("[" + ", ".join(
|
|
659
|
+
f"LegalizeMemDesc<{self._type_value(e[0])}, {self._type_value(e[1])}, "
|
|
660
|
+
f"{self._type_value(e[2])}, {e[3]}>" for e in elems) + "]")
|
|
661
|
+
# pair (arity 2) or triple (arity 3): a list of lists
|
|
662
|
+
return ("[" + ", ".join(
|
|
663
|
+
"[" + ", ".join(self._type_value(x) for x in e) + "]" for e in elems) + "]")
|
|
664
|
+
|
|
665
|
+
def dag(self, n):
|
|
666
|
+
k = n["kind"]
|
|
667
|
+
if k == "int":
|
|
668
|
+
return str(n["value"])
|
|
669
|
+
if k == "slot":
|
|
670
|
+
return f"(Slot {n['index']})"
|
|
671
|
+
if k == "type":
|
|
672
|
+
return self._type_value(n["value"])
|
|
673
|
+
if k in self._KIND:
|
|
674
|
+
return self._KIND[k]
|
|
675
|
+
if k in self._ORDERING:
|
|
676
|
+
return self._ORDERING[k]
|
|
677
|
+
if k == "leaf":
|
|
678
|
+
if n["name"] == "true":
|
|
679
|
+
return "(CoreTrue)"
|
|
680
|
+
if n["name"] == "hasFeature":
|
|
681
|
+
return self._feat(n["args"][0])
|
|
682
|
+
raise ValueError(n)
|
|
683
|
+
if k == "set":
|
|
684
|
+
return self._set(n)
|
|
685
|
+
if k == "ty":
|
|
686
|
+
return f"(Ty {self.dag(n['args'][0])})"
|
|
687
|
+
if k in self._UNARY:
|
|
688
|
+
return f"({self._UNARY[k]} {self.dag(n['args'][0])})"
|
|
689
|
+
if k in self._BINARY:
|
|
690
|
+
return (f"({self._BINARY[k]} {self.dag(n['args'][0])}, "
|
|
691
|
+
f"{self.dag(n['args'][1])})")
|
|
692
|
+
if k == "vecOf":
|
|
693
|
+
return f"(VecOf {self.dag(n['args'][0])}, {self.dag(n['args'][1])})"
|
|
694
|
+
if k == "scalarOrVecOf":
|
|
695
|
+
return f"(ScalarOrVecOf {self.dag(n['args'][0])}, {self.dag(n['args'][1])})"
|
|
696
|
+
if k == "pow2":
|
|
697
|
+
return "(Pow2)"
|
|
698
|
+
if k == "pow2Min":
|
|
699
|
+
return f"(Pow2Min {self.dag(n['args'][0])})"
|
|
700
|
+
if k == "clamp":
|
|
701
|
+
return f"(Clamp {self.dag(n['args'][0])}, {self.dag(n['args'][1])})"
|
|
702
|
+
if k in self._CMP:
|
|
703
|
+
return f"({self._CMP[k]} {self.dag(n['args'][0])}, {self.dag(n['args'][1])})"
|
|
704
|
+
if k == "not":
|
|
705
|
+
return f"(CoreNot {self.dag(n['args'][0])})"
|
|
706
|
+
if k in ("all", "any"):
|
|
707
|
+
c = "CoreAll" if k == "all" else "CoreAny"
|
|
708
|
+
return f"({c} " + ", ".join(self.dag(a) for a in n["args"]) + ")"
|
|
709
|
+
if k == "in":
|
|
710
|
+
return f"(CoreIn {self.dag(n['args'][0])}, {self.dag(n['args'][1])})"
|
|
711
|
+
if k == "inPairs":
|
|
712
|
+
a = n["args"]
|
|
713
|
+
return f"(CoreInPairs {self.dag(a[0])}, {self.dag(a[1])}, {self.dag(a[2])})"
|
|
714
|
+
if k == "inTriples":
|
|
715
|
+
a = n["args"]
|
|
716
|
+
return (f"(CoreInTriples {self.dag(a[0])}, {self.dag(a[1])}, "
|
|
717
|
+
f"{self.dag(a[2])}, {self.dag(a[3])})")
|
|
718
|
+
if k == "inMemDesc":
|
|
719
|
+
a = n["args"]
|
|
720
|
+
return (f"(CoreInMemDesc {self.dag(a[0])}, {self.dag(a[1])}, "
|
|
721
|
+
f"{self.dag(a[2])}, {self.dag(a[3])})")
|
|
722
|
+
if k == "select":
|
|
723
|
+
return (f"(CoreSelect {self.dag(n['args'][0])}, "
|
|
724
|
+
f"{self.dag(n['args'][1])}, {self.dag(n['args'][2])})")
|
|
725
|
+
if k == "nary":
|
|
726
|
+
return self._pred_dag(n)
|
|
727
|
+
raise ValueError(f"unknown node kind {k!r}")
|
|
728
|
+
|
|
729
|
+
def rule(self, r):
|
|
730
|
+
op, pred = r["op"], r["predicate"]
|
|
731
|
+
is_true = (pred.get("kind") == "leaf" and pred.get("name") == "true")
|
|
732
|
+
if op in self._OPS:
|
|
733
|
+
# An unconditional terminal is the `CoreAlways*` def: the schema
|
|
734
|
+
# serializes an unset guard as the true leaf, while
|
|
735
|
+
# `CoreLibcall<(CoreTrue)>` would serialize as {"kind":"true"}.
|
|
736
|
+
return self._ALWAYS[op] if is_true else f"{self._OPS[op]}<{self.dag(pred)}>"
|
|
737
|
+
if op in self._LATTICE:
|
|
738
|
+
if op == "widen" and r["to"].get("kind") == "int":
|
|
739
|
+
# dag params cannot take a bare int; the schema gives widen one
|
|
740
|
+
# dedicated class for the int target (CoreWidenPow2OrMinSize).
|
|
741
|
+
return (f"CoreWidenPow2OrMinSize<{self.dag(r['on'])}, "
|
|
742
|
+
f"{r['to']['value']}>")
|
|
743
|
+
c = self._LATTICE[op]
|
|
744
|
+
on, to = self.dag(r["on"]), self.dag(r["to"])
|
|
745
|
+
if is_true:
|
|
746
|
+
return f"{c}<{on}, {to}>"
|
|
747
|
+
return f"{c}If<{on}, {to}, {self.dag(pred)}>"
|
|
748
|
+
if op == "scalarize":
|
|
749
|
+
on = self.dag(r["on"])
|
|
750
|
+
if is_true:
|
|
751
|
+
return f"CoreScalarize<{on}>"
|
|
752
|
+
return f"CoreScalarizeIf<{on}, {self.dag(pred)}>"
|
|
753
|
+
raise ValueError(r)
|
|
754
|
+
|
|
755
|
+
def emit(self):
|
|
756
|
+
rendered = [(rs, [self.rule(r) for r in rs["rules"]])
|
|
757
|
+
for rs in self.rulesets]
|
|
758
|
+
lines = ['include "llvm/Target/LegalizerInfo.td"',
|
|
759
|
+
'include "llvm/Target/Target.td"', ""]
|
|
760
|
+
for s, name in self.features.items():
|
|
761
|
+
lines.append(f'def {name} : LegalizeFeature<"{s}">;')
|
|
762
|
+
for text, name in self.pred_dags.items():
|
|
763
|
+
lines.append(f'def {name} : LegalizePredicateDag<{text}>;')
|
|
764
|
+
for tname, name in self.types.items():
|
|
765
|
+
lines.append(f'def {name} : LegalizeNamedType<"{_llt(tname)}">;')
|
|
766
|
+
for (cond, then, els), name in self.selects.items():
|
|
767
|
+
lines.append(f'def {name} : LegalizeSelectType<[{cond}], [{then}], {els}>;')
|
|
768
|
+
lines.append("")
|
|
769
|
+
for rs, texts in rendered:
|
|
770
|
+
op = ", ".join(f'"{o}"' for o in rs["opcodes"])
|
|
771
|
+
lines.append(f'def {rs["def"]} : LegalizeRuleSet<[{op}]> {{')
|
|
772
|
+
lines.append(" let Rules = [")
|
|
773
|
+
for t in texts:
|
|
774
|
+
lines.append(f" {t},")
|
|
775
|
+
lines.append(" ];")
|
|
776
|
+
lines.append("}")
|
|
777
|
+
lines.append("")
|
|
778
|
+
return "\n".join(lines) + "\n"
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
# ---- static checks (J1 sort / J2b compare / J3a action / J3b slot bound) --
|
|
782
|
+
|
|
783
|
+
COMPARE_SORTS = {"Int", "Kind", "Ordering", "Type"}
|
|
784
|
+
KIND_ORDERING = {"orderingUnordered", "orderingMonotonic", "orderingAcquire",
|
|
785
|
+
"orderingRelease", "orderingAcquireRelease",
|
|
786
|
+
"orderingSequentiallyConsistent"}
|
|
787
|
+
# admissible target sorts per lattice operator (action table, J3a)
|
|
788
|
+
TARGET_SORTS = {"widen": {"Type", "Int"}, "narrow": {"Type", "Int"},
|
|
789
|
+
"more": {"Int", "Type"}, "fewer": {"Int", "Type"},
|
|
790
|
+
"bitcast": {"Type"}}
|
|
791
|
+
|
|
792
|
+
def _tc(node, arity, errs):
|
|
793
|
+
"""Type-check one normal-form node, return its sort (J1 / J2b).
|
|
794
|
+
|
|
795
|
+
The sorts are the ones the evaluator distinguishes at run time
|
|
796
|
+
(gisel/eval.py value()/bool_value()); MemDesc is the only one that never
|
|
797
|
+
reaches a comparison.
|
|
798
|
+
"""
|
|
799
|
+
k = node["kind"]
|
|
800
|
+
args = node.get("args", [])
|
|
801
|
+
|
|
802
|
+
def tc(n):
|
|
803
|
+
return _tc(n, arity, errs)
|
|
804
|
+
|
|
805
|
+
def need(i, sort):
|
|
806
|
+
got = tc(args[i])
|
|
807
|
+
if got != sort:
|
|
808
|
+
errs.append(f"{k}: arg {i} sort mismatch (got {got}, want {sort})")
|
|
809
|
+
return got
|
|
810
|
+
|
|
811
|
+
def need_any(i, sorts):
|
|
812
|
+
got = tc(args[i])
|
|
813
|
+
if got not in sorts:
|
|
814
|
+
errs.append(f"{k}: arg {i} sort mismatch (got {got}, want {sorts})")
|
|
815
|
+
return got
|
|
816
|
+
|
|
817
|
+
if k == "int":
|
|
818
|
+
return "Int"
|
|
819
|
+
if k == "true":
|
|
820
|
+
return "Bool"
|
|
821
|
+
if k == "slot":
|
|
822
|
+
if node["index"] >= arity:
|
|
823
|
+
errs.append(f"(Slot {node['index']}) out of range: opcode has {arity} slot(s)")
|
|
824
|
+
return "Type"
|
|
825
|
+
if k == "type":
|
|
826
|
+
return "Type"
|
|
827
|
+
if k == "set":
|
|
828
|
+
return "Set"
|
|
829
|
+
if k == "leaf":
|
|
830
|
+
return "Bool"
|
|
831
|
+
if k in ("kindVector", "kindScalableVector", "kindScalar", "kindPointer",
|
|
832
|
+
"kindPointerVector"):
|
|
833
|
+
return "Kind"
|
|
834
|
+
if k in KIND_ORDERING:
|
|
835
|
+
return "Ordering"
|
|
836
|
+
if k == "memSlot":
|
|
837
|
+
need(0, "Int")
|
|
838
|
+
return "MemDesc"
|
|
839
|
+
if k == "sameAs":
|
|
840
|
+
need(0, "Int")
|
|
841
|
+
return "Type"
|
|
842
|
+
if k in ("elemOf", "scalarOf", "replace", "ty"):
|
|
843
|
+
need(0, "Type")
|
|
844
|
+
return "Type"
|
|
845
|
+
if k == "sortOf":
|
|
846
|
+
need(0, "Type")
|
|
847
|
+
return "Kind"
|
|
848
|
+
if k in ("numElems", "bitsOf"):
|
|
849
|
+
need(0, "Type")
|
|
850
|
+
return "Int"
|
|
851
|
+
if k == "sizeOf":
|
|
852
|
+
need_any(0, {"Type", "MemDesc"})
|
|
853
|
+
return "Int"
|
|
854
|
+
if k == "sizeInBytesOf":
|
|
855
|
+
need(0, "Type")
|
|
856
|
+
return "Int"
|
|
857
|
+
if k == "alignOf":
|
|
858
|
+
need(0, "MemDesc")
|
|
859
|
+
return "Int"
|
|
860
|
+
if k == "addrSpace":
|
|
861
|
+
need(0, "Type")
|
|
862
|
+
return "Int"
|
|
863
|
+
if k == "memTy":
|
|
864
|
+
need(0, "MemDesc")
|
|
865
|
+
return "Type"
|
|
866
|
+
if k in ("memOrdering", "memFailureOrdering"):
|
|
867
|
+
need(0, "MemDesc")
|
|
868
|
+
return "Ordering"
|
|
869
|
+
if k in ("mul", "urem", "udiv"):
|
|
870
|
+
need(0, "Int"); need(1, "Int")
|
|
871
|
+
return "Int"
|
|
872
|
+
if k == "pow2Ceil":
|
|
873
|
+
need(0, "Int")
|
|
874
|
+
return "Int"
|
|
875
|
+
if k == "intOfSize":
|
|
876
|
+
need(0, "Int")
|
|
877
|
+
return "Type"
|
|
878
|
+
if k == "minVectorSize":
|
|
879
|
+
need(0, "Int")
|
|
880
|
+
return "Int"
|
|
881
|
+
if k in ("vecOf", "scalarOrVecOf"):
|
|
882
|
+
need(0, "Int"); need(1, "Type")
|
|
883
|
+
return "Type"
|
|
884
|
+
if k == "eltTypeTo":
|
|
885
|
+
need(0, "Type"); need(1, "Type")
|
|
886
|
+
return "Type"
|
|
887
|
+
if k in ("eltSizeTo", "divLanes"):
|
|
888
|
+
need(0, "Type"); need(1, "Int")
|
|
889
|
+
return "Type"
|
|
890
|
+
if k == "pow2":
|
|
891
|
+
return "Int"
|
|
892
|
+
if k == "pow2Min":
|
|
893
|
+
need(0, "Int")
|
|
894
|
+
return "Int"
|
|
895
|
+
if k == "select":
|
|
896
|
+
need(0, "Bool")
|
|
897
|
+
a, b = tc(args[1]), tc(args[2])
|
|
898
|
+
if a != b:
|
|
899
|
+
errs.append(f"select: branch sorts differ ({a} vs {b})")
|
|
900
|
+
elif a not in ("Int", "Type"):
|
|
901
|
+
errs.append(f"select: unsupported branch sort {a}")
|
|
902
|
+
return a
|
|
903
|
+
if k == "not":
|
|
904
|
+
sub = node.get("arg", args[0] if args else None)
|
|
905
|
+
if sub is None:
|
|
906
|
+
errs.append("not: missing operand")
|
|
907
|
+
elif tc(sub) != "Bool":
|
|
908
|
+
errs.append("not: operand must be Bool")
|
|
909
|
+
return "Bool"
|
|
910
|
+
if k in ("all", "any", "nary"):
|
|
911
|
+
if k == "nary" and node.get("op") not in ("all_of", "any_of"):
|
|
912
|
+
errs.append(f"nary: unknown op {node.get('op')!r}")
|
|
913
|
+
for i in range(len(args)):
|
|
914
|
+
need(i, "Bool")
|
|
915
|
+
return "Bool"
|
|
916
|
+
if k in ("eq", "ne", "lt", "le", "gt", "ge"):
|
|
917
|
+
a = tc(args[0]); b = tc(args[1])
|
|
918
|
+
if a != b:
|
|
919
|
+
errs.append(f"{k}: operand sorts differ ({a} vs {b})")
|
|
920
|
+
elif a not in COMPARE_SORTS:
|
|
921
|
+
errs.append(f"{k}: comparison over unsupported sort {a}")
|
|
922
|
+
return "Bool"
|
|
923
|
+
if k == "in":
|
|
924
|
+
if tc(args[0]) != "Type":
|
|
925
|
+
errs.append("in: left operand must be Type")
|
|
926
|
+
return "Bool"
|
|
927
|
+
if k == "inPairs":
|
|
928
|
+
need(0, "Type"); need(1, "Type"); need(2, "Set")
|
|
929
|
+
return "Bool"
|
|
930
|
+
if k == "inTriples":
|
|
931
|
+
for i in range(3):
|
|
932
|
+
need(i, "Type")
|
|
933
|
+
need(3, "Set")
|
|
934
|
+
return "Bool"
|
|
935
|
+
if k == "inMemDesc":
|
|
936
|
+
need(0, "Type"); need(1, "Type"); need(2, "MemDesc"); need(3, "Set")
|
|
937
|
+
return "Bool"
|
|
938
|
+
if k in ("isPow2", "isBFloat16", "isFloat16", "isByteSized"):
|
|
939
|
+
# A type predicate: isBFloat16 takes a type, isPow2 a byte count.
|
|
940
|
+
need_any(0, {"Type", "Int"})
|
|
941
|
+
return "Bool"
|
|
942
|
+
raise ValueError(f"typecheck: unknown node kind {k!r}")
|
|
943
|
+
|
|
944
|
+
def check_rule(rule, arity, errs):
|
|
945
|
+
op = rule["op"]
|
|
946
|
+
if op in ("legal", "lower", "custom", "unsupported", "libcall"):
|
|
947
|
+
if _tc(rule["predicate"], arity, errs) != "Bool":
|
|
948
|
+
errs.append(f"{op}: guard must be Bool")
|
|
949
|
+
elif op in TARGET_SORTS:
|
|
950
|
+
_tc(rule["on"], arity, errs)
|
|
951
|
+
ts = _tc(rule["to"], arity, errs)
|
|
952
|
+
if ts not in TARGET_SORTS[op]:
|
|
953
|
+
errs.append(f"{op}: target sort {ts} not in action table")
|
|
954
|
+
_tc(rule["predicate"], arity, errs)
|
|
955
|
+
elif op == "scalarize":
|
|
956
|
+
_tc(rule["on"], arity, errs)
|
|
957
|
+
else:
|
|
958
|
+
errs.append(f"unknown op {op!r}")
|
|
959
|
+
|
|
960
|
+
_TYPE_INDEX_RE = re.compile(r"\b(?:ptype|type)(\d+)\b")
|
|
961
|
+
_DECL_RE = re.compile(r"\b(class|def)\s+([A-Za-z_]\w*)\s*(?::\s*([^{;]*))?([;{])")
|
|
962
|
+
|
|
963
|
+
|
|
964
|
+
def _strip_td_comments(text):
|
|
965
|
+
"""Drop // comments, keeping string literals intact."""
|
|
966
|
+
out = []
|
|
967
|
+
i, n = 0, len(text)
|
|
968
|
+
in_str = False
|
|
969
|
+
while i < n:
|
|
970
|
+
c = text[i]
|
|
971
|
+
if in_str:
|
|
972
|
+
out.append(c)
|
|
973
|
+
if c == "\\" and i + 1 < n:
|
|
974
|
+
out.append(text[i + 1])
|
|
975
|
+
i += 2
|
|
976
|
+
continue
|
|
977
|
+
if c == '"':
|
|
978
|
+
in_str = False
|
|
979
|
+
i += 1
|
|
980
|
+
continue
|
|
981
|
+
if c == '"':
|
|
982
|
+
in_str = True
|
|
983
|
+
out.append(c)
|
|
984
|
+
i += 1
|
|
985
|
+
continue
|
|
986
|
+
if text.startswith("//", i):
|
|
987
|
+
j = text.find("\n", i)
|
|
988
|
+
i = n if j < 0 else j
|
|
989
|
+
continue
|
|
990
|
+
out.append(c)
|
|
991
|
+
i += 1
|
|
992
|
+
return "".join(out)
|
|
993
|
+
|
|
994
|
+
|
|
995
|
+
def _parse_td_defs(text):
|
|
996
|
+
"""name -> (bases, highest type operand index or None, variadic)."""
|
|
997
|
+
text = _strip_td_comments(text)
|
|
998
|
+
recs = {}
|
|
999
|
+
pos = 0
|
|
1000
|
+
while True:
|
|
1001
|
+
m = _DECL_RE.search(text, pos)
|
|
1002
|
+
if not m:
|
|
1003
|
+
break
|
|
1004
|
+
kind, name = m.group(1), m.group(2)
|
|
1005
|
+
bases, term = m.group(3) or "", m.group(4)
|
|
1006
|
+
body = ""
|
|
1007
|
+
end = m.end()
|
|
1008
|
+
if term == "{":
|
|
1009
|
+
depth, j = 0, m.end() - 1
|
|
1010
|
+
while j < len(text):
|
|
1011
|
+
if text[j] == "{":
|
|
1012
|
+
depth += 1
|
|
1013
|
+
elif text[j] == "}":
|
|
1014
|
+
depth -= 1
|
|
1015
|
+
if depth == 0:
|
|
1016
|
+
break
|
|
1017
|
+
j += 1
|
|
1018
|
+
body = text[m.end():j]
|
|
1019
|
+
end = j + 1
|
|
1020
|
+
prev = recs.get(name, ("class", (), None, False))
|
|
1021
|
+
idx = [int(x) for x in _TYPE_INDEX_RE.findall(body)]
|
|
1022
|
+
own = max(idx) if idx else prev[2]
|
|
1023
|
+
recs[name] = (kind, tuple(b.strip() for b in bases.split(",") if b.strip()),
|
|
1024
|
+
own, "variable_ops" in body or prev[3])
|
|
1025
|
+
pos = end
|
|
1026
|
+
return recs
|
|
1027
|
+
|
|
1028
|
+
|
|
1029
|
+
def _max_type_index(recs, name, seen=None):
|
|
1030
|
+
"""Highest type-operand index for `name`, or None when unknowable.
|
|
1031
|
+
|
|
1032
|
+
A base class defined outside this file (GenericInstruction, ...) carries
|
|
1033
|
+
no type operands; a variadic instruction has no fixed arity.
|
|
1034
|
+
"""
|
|
1035
|
+
seen = set() if seen is None else seen
|
|
1036
|
+
if name in seen:
|
|
1037
|
+
return None
|
|
1038
|
+
seen.add(name)
|
|
1039
|
+
rec = recs.get(name)
|
|
1040
|
+
if rec is None:
|
|
1041
|
+
return -1 # external base: no type operands
|
|
1042
|
+
_kind, bases, own, variadic = rec
|
|
1043
|
+
if variadic:
|
|
1044
|
+
return None
|
|
1045
|
+
best = own
|
|
1046
|
+
for b in bases:
|
|
1047
|
+
got = _max_type_index(recs, b, seen)
|
|
1048
|
+
if got is None:
|
|
1049
|
+
return None
|
|
1050
|
+
if best is None or got > best:
|
|
1051
|
+
best = got
|
|
1052
|
+
return -1 if best is None else best
|
|
1053
|
+
|
|
1054
|
+
|
|
1055
|
+
def build_arity_manifest(generic_td_path):
|
|
1056
|
+
"""Per-opcode type-slot count from GenericOpcodes.td.
|
|
1057
|
+
|
|
1058
|
+
Handles both `def G_X : Class { ... }` and `def G_X : Class;`, walking the
|
|
1059
|
+
class chain (`class VectorReduction { type0, type1 }`). `None` means the
|
|
1060
|
+
count is not fixed (variadic intrinsics).
|
|
1061
|
+
|
|
1062
|
+
The previous regex silently attached the *next* def's body to `def X :
|
|
1063
|
+
Class;`; e.g. G_VECREDUCE_FADD inherited G_MEMCPY's type2 and was recorded
|
|
1064
|
+
as arity 3 instead of 2.
|
|
1065
|
+
"""
|
|
1066
|
+
with open(generic_td_path) as f:
|
|
1067
|
+
recs = _parse_td_defs(f.read())
|
|
1068
|
+
out = {}
|
|
1069
|
+
for name, (kind, _, _, _) in recs.items():
|
|
1070
|
+
if kind != "def" or not name.startswith("G_"):
|
|
1071
|
+
continue
|
|
1072
|
+
idx = _max_type_index(recs, name)
|
|
1073
|
+
out[name] = None if idx is None else idx + 1
|
|
1074
|
+
return out
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
def load_arity_manifest():
|
|
1078
|
+
"""The bundled opcode table: lint works without an LLVM source tree."""
|
|
1079
|
+
path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
1080
|
+
"data", "generic_opcodes.json")
|
|
1081
|
+
with open(path) as f:
|
|
1082
|
+
return json.load(f)["opcodes"]
|
|
1083
|
+
|
|
1084
|
+
|
|
1085
|
+
def check_rules(compiled, opcodes, manifest, errs):
|
|
1086
|
+
arities = [manifest.get(o) for o in opcodes]
|
|
1087
|
+
known = [a for a in arities if a is not None]
|
|
1088
|
+
arity = min(known) if known else None # multi-opcode takes the minimum (J3b)
|
|
1089
|
+
for r in compiled:
|
|
1090
|
+
check_rule(r, arity if arity is not None else 10**9, errs)
|
|
1091
|
+
return arity
|
|
1092
|
+
|
|
1093
|
+
|
|
1094
|
+
TBLGEN = "/home/ubuntu/llvm/builds/explore/bin/llvm-tblgen"
|
|
1095
|
+
INC = ["-I/home/ubuntu/llvm/trees/explore/llvm/include",
|
|
1096
|
+
"-I/home/ubuntu/llvm/builds/explore/include",
|
|
1097
|
+
"-I/home/ubuntu/llvm/trees/explore/llvm/lib/Target"]
|
|
1098
|
+
|
|
1099
|
+
|
|
1100
|
+
def roundtrip_corpus(name, core_json_path, target_dir):
|
|
1101
|
+
"""baseline core.json -> reverse-emit .td -> tblgen -> normal form diff."""
|
|
1102
|
+
baseline = json.load(open(core_json_path))
|
|
1103
|
+
rulesets = [{"def": rs["def"], "opcodes": rs["opcodes"], "rules": rs["rules"]}
|
|
1104
|
+
for rs in baseline["rulesets"]]
|
|
1105
|
+
td = TdEmitter(rulesets).emit()
|
|
1106
|
+
d = tempfile.mkdtemp(prefix="dsl-rt-")
|
|
1107
|
+
path = os.path.join(d, "ReverseLegalizerInfo.td")
|
|
1108
|
+
with open(path, "w") as f:
|
|
1109
|
+
f.write(td)
|
|
1110
|
+
out = os.path.join(d, "out.core.json")
|
|
1111
|
+
inc = INC + [f"-I/home/ubuntu/llvm/trees/explore/llvm/lib/Target/{target_dir}"]
|
|
1112
|
+
r = subprocess.run([TBLGEN, "-gen-legalizer-core", *inc, path, "-o", out],
|
|
1113
|
+
capture_output=True, text=True)
|
|
1114
|
+
if r.returncode != 0:
|
|
1115
|
+
print(f"({name}) tblgen failed:\n{r.stderr}")
|
|
1116
|
+
print("emitted .td head:\n" + "\n".join(td.splitlines()[:40]))
|
|
1117
|
+
return 10**9
|
|
1118
|
+
gen = json.load(open(out))
|
|
1119
|
+
brs = {rs["def"]: rs for rs in baseline["rulesets"]}
|
|
1120
|
+
grs = {rs["def"]: rs for rs in gen["rulesets"]}
|
|
1121
|
+
total = diff = 0
|
|
1122
|
+
for defn, br in brs.items():
|
|
1123
|
+
if defn not in grs:
|
|
1124
|
+
print(f" {name}: missing ruleset {defn}")
|
|
1125
|
+
diff += 1
|
|
1126
|
+
continue
|
|
1127
|
+
gr = grs[defn]
|
|
1128
|
+
if br["opcodes"] != gr["opcodes"]:
|
|
1129
|
+
print(f" {name}:{defn} opcodes differ {br['opcodes']} vs {gr['opcodes']}")
|
|
1130
|
+
diff += 1
|
|
1131
|
+
brr, grr = br["rules"], gr["rules"]
|
|
1132
|
+
total += len(brr)
|
|
1133
|
+
if len(brr) != len(grr):
|
|
1134
|
+
print(f" {name}:{defn} rule count {len(brr)} vs {len(grr)}")
|
|
1135
|
+
diff += 1
|
|
1136
|
+
continue
|
|
1137
|
+
for i, (e, g) in enumerate(zip(brr, grr)):
|
|
1138
|
+
if strip(e) != strip(g):
|
|
1139
|
+
if diff < 3:
|
|
1140
|
+
print(f" {name}:{defn}#{i} DIFF")
|
|
1141
|
+
print(" oracle:", json.dumps(strip(e), sort_keys=True)[:400])
|
|
1142
|
+
print(" gen :", json.dumps(strip(g), sort_keys=True)[:400])
|
|
1143
|
+
diff += 1
|
|
1144
|
+
print(f"({name}) round-trip: {total} rules, {diff} diffs "
|
|
1145
|
+
f"{'ALL MATCH' if diff == 0 else 'SOME DIFF'}")
|
|
1146
|
+
return diff
|
|
1147
|
+
|
|
1148
|
+
|
|
1149
|
+
|
|
1150
|
+
# ---- free-form DSL source renderer (normal form -> DSL text) --------------
|
|
1151
|
+
|
|
1152
|
+
KIND_FROM = {"kindVector": "vector", "kindScalableVector": "scalable_vector",
|
|
1153
|
+
"kindScalar": "scalar", "kindPointer": "pointer",
|
|
1154
|
+
"kindPointerVector": "pointer_vector"}
|
|
1155
|
+
ORDERING_FROM = {"orderingUnordered": "ordering_unordered",
|
|
1156
|
+
"orderingMonotonic": "ordering_monotonic",
|
|
1157
|
+
"orderingAcquire": "ordering_acquire",
|
|
1158
|
+
"orderingRelease": "ordering_release",
|
|
1159
|
+
"orderingAcquireRelease": "ordering_acquire_release",
|
|
1160
|
+
"orderingSequentiallyConsistent": "ordering_sequentially_consistent"}
|
|
1161
|
+
UNARY_FROM = {"elemOf": "elem_of", "scalarOf": "scalar_of", "sortOf": "sort_of",
|
|
1162
|
+
"numElems": "num_elems", "bitsOf": "bits_of", "sizeOf": "size_of",
|
|
1163
|
+
"sizeInBytesOf": "size_in_bytes_of", "isByteSized": "is_byte_sized",
|
|
1164
|
+
"addrSpace": "addr_space", "alignOf": "align_of",
|
|
1165
|
+
"memSlot": "mem_slot", "memTy": "mem_ty", "memOrdering": "mem_ordering",
|
|
1166
|
+
"memFailureOrdering": "mem_failure_ordering",
|
|
1167
|
+
"isBFloat16": "is_bfloat16", "isFloat16": "is_float16",
|
|
1168
|
+
"isPow2": "is_pow2", "replace": "replace", "pow2Ceil": "pow2_ceil",
|
|
1169
|
+
"multipleOf": "multiple_of", "sameAs": "same_as",
|
|
1170
|
+
"minVectorSize": "min_vector_size", "intOfSize": "int_of_size",
|
|
1171
|
+
"ty": "ty", "pow2Min": "pow2_min"}
|
|
1172
|
+
BINARY_FROM = {"vecOf": "vec_of", "scalarOrVecOf": "scalar_or_vec_of",
|
|
1173
|
+
"eltTypeTo": "elt_type_to", "eltSizeTo": "elt_size_to",
|
|
1174
|
+
"divLanes": "div_lanes", "mul": "mul", "urem": "urem",
|
|
1175
|
+
"udiv": "udiv", "clamp": "clamp"}
|
|
1176
|
+
CMP_OPS = {"eq": "==", "ne": "!=", "lt": "<", "le": "<=", "gt": ">", "ge": ">="}
|
|
1177
|
+
|
|
1178
|
+
|
|
1179
|
+
def render_type_value(v):
|
|
1180
|
+
if isinstance(v, str):
|
|
1181
|
+
return v
|
|
1182
|
+
sel = v["select"]
|
|
1183
|
+
return (f"select_type({render_pred(sel['cond'])}, "
|
|
1184
|
+
f"{render_type_value(sel['then'])}, "
|
|
1185
|
+
f"{render_type_value(sel['else'])})")
|
|
1186
|
+
|
|
1187
|
+
|
|
1188
|
+
def render_set(n):
|
|
1189
|
+
arity = n.get("arity", 1)
|
|
1190
|
+
elems = n["elems"]
|
|
1191
|
+
if arity == 1:
|
|
1192
|
+
return "[" + ", ".join(render_type_value(e) for e in elems) + "]"
|
|
1193
|
+
if arity == 2 and len(elems[0]) == 4:
|
|
1194
|
+
return ("[" + ", ".join(
|
|
1195
|
+
f"mem_desc({render_type_value(e[0])}, {render_type_value(e[1])}, "
|
|
1196
|
+
f"{render_type_value(e[2])}, {e[3]})" for e in elems) + "]")
|
|
1197
|
+
return ("[" + ", ".join(
|
|
1198
|
+
"[" + ", ".join(render_type_value(x) for x in e) + "]"
|
|
1199
|
+
for e in elems) + "]")
|
|
1200
|
+
|
|
1201
|
+
|
|
1202
|
+
def render_expr(n):
|
|
1203
|
+
k = n["kind"]
|
|
1204
|
+
if k == "int":
|
|
1205
|
+
return str(n["value"])
|
|
1206
|
+
if k == "slot":
|
|
1207
|
+
return f"slot({n['index']})"
|
|
1208
|
+
if k == "type":
|
|
1209
|
+
return render_type_value(n["value"])
|
|
1210
|
+
if k in KIND_FROM:
|
|
1211
|
+
return KIND_FROM[k]
|
|
1212
|
+
if k in ORDERING_FROM:
|
|
1213
|
+
return ORDERING_FROM[k]
|
|
1214
|
+
if k == "pow2":
|
|
1215
|
+
return "pow2"
|
|
1216
|
+
if k in UNARY_FROM:
|
|
1217
|
+
return f"{UNARY_FROM[k]}({render_expr(n['args'][0])})"
|
|
1218
|
+
if k in BINARY_FROM:
|
|
1219
|
+
return (f"{BINARY_FROM[k]}({render_expr(n['args'][0])}, "
|
|
1220
|
+
f"{render_expr(n['args'][1])})")
|
|
1221
|
+
if k == "select":
|
|
1222
|
+
return (f"select({render_pred(n['args'][0])}, "
|
|
1223
|
+
f"{render_expr(n['args'][1])}, {render_expr(n['args'][2])})")
|
|
1224
|
+
raise ValueError(f"cannot render expr kind {k!r}")
|
|
1225
|
+
|
|
1226
|
+
|
|
1227
|
+
def render_pred(n):
|
|
1228
|
+
k = n["kind"]
|
|
1229
|
+
if k == "leaf":
|
|
1230
|
+
if n["name"] == "true":
|
|
1231
|
+
return "true"
|
|
1232
|
+
if n["name"] == "hasFeature":
|
|
1233
|
+
return f'feature("{n["args"][0]}")'
|
|
1234
|
+
raise ValueError(n)
|
|
1235
|
+
if k == "all":
|
|
1236
|
+
if len(n["args"]) == 1:
|
|
1237
|
+
return "all_of(" + render_pred(n["args"][0]) + ")"
|
|
1238
|
+
return "(" + " and ".join(render_pred(a) for a in n["args"]) + ")"
|
|
1239
|
+
if k == "any":
|
|
1240
|
+
if len(n["args"]) == 1:
|
|
1241
|
+
return "any_of(" + render_pred(n["args"][0]) + ")"
|
|
1242
|
+
return "(" + " or ".join(render_pred(a) for a in n["args"]) + ")"
|
|
1243
|
+
if k == "not":
|
|
1244
|
+
return "not " + render_pred(n["args"][0])
|
|
1245
|
+
if k in CMP_OPS:
|
|
1246
|
+
return (f"{render_expr(n['args'][0])} {CMP_OPS[k]} "
|
|
1247
|
+
f"{render_expr(n['args'][1])}")
|
|
1248
|
+
if k == "in":
|
|
1249
|
+
return (f"in({render_expr(n['args'][0])}, "
|
|
1250
|
+
f"{render_set_or_type(n['args'][1])})")
|
|
1251
|
+
if k == "inPairs":
|
|
1252
|
+
a = n["args"]
|
|
1253
|
+
return (f"in_pairs({render_expr(a[0])}, {render_expr(a[1])}, "
|
|
1254
|
+
f"{render_set(a[2])})")
|
|
1255
|
+
if k == "inTriples":
|
|
1256
|
+
a = n["args"]
|
|
1257
|
+
return (f"in_triples({render_expr(a[0])}, {render_expr(a[1])}, "
|
|
1258
|
+
f"{render_expr(a[2])}, {render_set(a[3])})")
|
|
1259
|
+
if k == "inMemDesc":
|
|
1260
|
+
a = n["args"]
|
|
1261
|
+
return (f"in_mem_desc({render_expr(a[0])}, {render_expr(a[1])}, "
|
|
1262
|
+
f"{render_expr(a[2])}, {render_set(a[3])})")
|
|
1263
|
+
if k == "nary":
|
|
1264
|
+
# legacy all_of/any_of predicate-dag -> core and/or (same meaning)
|
|
1265
|
+
op = n["op"]
|
|
1266
|
+
parts = [render_pred_legacy(a) for a in n["args"]]
|
|
1267
|
+
join = " and " if op == "all_of" else " or "
|
|
1268
|
+
return "(" + join.join(parts) + ")"
|
|
1269
|
+
if k in UNARY_FROM:
|
|
1270
|
+
return render_expr(n)
|
|
1271
|
+
raise ValueError(f"cannot render pred kind {k!r}")
|
|
1272
|
+
|
|
1273
|
+
|
|
1274
|
+
def render_pred_legacy(n):
|
|
1275
|
+
k = n["kind"]
|
|
1276
|
+
if k == "leaf":
|
|
1277
|
+
return render_pred(n)
|
|
1278
|
+
if k == "not":
|
|
1279
|
+
return "not " + render_pred_legacy(n["arg"])
|
|
1280
|
+
if k == "nary":
|
|
1281
|
+
op = n["op"]
|
|
1282
|
+
join = " and " if op == "all_of" else " or "
|
|
1283
|
+
return "(" + join.join(render_pred_legacy(a) for a in n["args"]) + ")"
|
|
1284
|
+
raise ValueError(n)
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
def render_set_or_type(n):
|
|
1288
|
+
if n["kind"] == "type":
|
|
1289
|
+
return render_type_value(n["value"])
|
|
1290
|
+
return render_set(n)
|
|
1291
|
+
|
|
1292
|
+
|
|
1293
|
+
def render_rule(r):
|
|
1294
|
+
op = r["op"]
|
|
1295
|
+
pred = r["predicate"]
|
|
1296
|
+
is_true = pred.get("kind") == "leaf" and pred.get("name") == "true"
|
|
1297
|
+
if op in ("legal", "lower", "custom", "unsupported", "libcall"):
|
|
1298
|
+
return f"{op} {render_pred(pred)}"
|
|
1299
|
+
if op in ("widen", "narrow", "more", "fewer", "bitcast"):
|
|
1300
|
+
text = f"{op}({render_expr(r['on'])}, {render_expr(r['to'])})"
|
|
1301
|
+
if not is_true:
|
|
1302
|
+
text += f" when {render_pred(pred)}"
|
|
1303
|
+
return text
|
|
1304
|
+
if op == "scalarize":
|
|
1305
|
+
text = f"scalarize({render_expr(r['on'])})"
|
|
1306
|
+
if not is_true:
|
|
1307
|
+
text += f" when {render_pred(pred)}"
|
|
1308
|
+
return text
|
|
1309
|
+
raise ValueError(r)
|
|
1310
|
+
|
|
1311
|
+
|
|
1312
|
+
def render_ruleset(rs):
|
|
1313
|
+
lines = [f"rule {' '.join(rs['opcodes'])}:"]
|
|
1314
|
+
for r in rs["rules"]:
|
|
1315
|
+
lines.append(f" {render_rule(r)}")
|
|
1316
|
+
return "\n".join(lines)
|
|
1317
|
+
|
|
1318
|
+
|
|
1319
|
+
def normalize_nary(n):
|
|
1320
|
+
"""Legacy nary/not(arg) predicate-dag -> core all/any/not(args) form."""
|
|
1321
|
+
if isinstance(n, dict):
|
|
1322
|
+
k = n.get("kind")
|
|
1323
|
+
if k == "nary":
|
|
1324
|
+
return _node("all" if n["op"] == "all_of" else "any",
|
|
1325
|
+
args=[normalize_nary(a) for a in n["args"]])
|
|
1326
|
+
if k == "not" and "arg" in n and "args" not in n:
|
|
1327
|
+
return _node("not", args=[normalize_nary(n["arg"])])
|
|
1328
|
+
return {kk: normalize_nary(vv) for kk, vv in n.items()}
|
|
1329
|
+
if isinstance(n, list):
|
|
1330
|
+
return [normalize_nary(x) for x in n]
|
|
1331
|
+
return n
|
|
1332
|
+
|
|
1333
|
+
|
|
1334
|
+
def forward_roundtrip(name, core_json_path):
|
|
1335
|
+
"""baseline core.json -> free-form DSL source -> parse -> normal form diff."""
|
|
1336
|
+
baseline = json.load(open(core_json_path))
|
|
1337
|
+
total = diff = 0
|
|
1338
|
+
src_parts = []
|
|
1339
|
+
for rs in baseline["rulesets"]:
|
|
1340
|
+
src_parts.append(render_ruleset(rs))
|
|
1341
|
+
src = "\n\n".join(src_parts) + "\n"
|
|
1342
|
+
ast = Parser(src).parse()
|
|
1343
|
+
compiled = [normalize_nary(r) for r in compile_rulesets(ast)]
|
|
1344
|
+
expect = [normalize_nary(r) for rs in baseline["rulesets"]
|
|
1345
|
+
for r in rs["rules"]]
|
|
1346
|
+
total = len(expect)
|
|
1347
|
+
if len(compiled) != len(expect):
|
|
1348
|
+
print(f"({name}) forward: rule count {len(compiled)} vs {len(expect)}")
|
|
1349
|
+
diff += abs(len(compiled) - len(expect))
|
|
1350
|
+
for i, (e, g) in enumerate(zip(expect, compiled)):
|
|
1351
|
+
if strip(e) != strip(g):
|
|
1352
|
+
if diff < 3:
|
|
1353
|
+
print(f"({name}) forward #{i} DIFF")
|
|
1354
|
+
print(" oracle:", json.dumps(strip(e), sort_keys=True)[:400])
|
|
1355
|
+
print(" gen :", json.dumps(strip(g), sort_keys=True)[:400])
|
|
1356
|
+
diff += 1
|
|
1357
|
+
print(f"({name}) forward round-trip: {total} rules, {diff} diffs "
|
|
1358
|
+
f"{'ALL MATCH' if diff == 0 else 'SOME DIFF'}")
|
|
1359
|
+
return diff
|
|
1360
|
+
|
|
1361
|
+
|
|
1362
|
+
|
|
1363
|
+
def main():
|
|
1364
|
+
src = r'''
|
|
1365
|
+
rule G_ADD G_SUB:
|
|
1366
|
+
legal in(slot(0), [s8, s16, s32])
|
|
1367
|
+
legal feature("Subtarget.is64Bit()") and in(slot(0), s64)
|
|
1368
|
+
legal feature("Subtarget.hasSSE2()") and in(slot(0), [v16s8, v8s16, v4s32, v2s64])
|
|
1369
|
+
legal feature("Subtarget.hasAVX2()") and in(slot(0), [v32s8, v16s16, v8s32, v4s64])
|
|
1370
|
+
legal feature("Subtarget.hasAVX512()") and in(slot(0), [v16s32, v8s64])
|
|
1371
|
+
legal feature("Subtarget.hasAVX512() && Subtarget.hasBWI()") and in(slot(0), [v64s8, v32s16])
|
|
1372
|
+
clamp_min(0, s8, 16)
|
|
1373
|
+
clamp_min(0, s16, 8)
|
|
1374
|
+
clamp_min(0, s32, 4)
|
|
1375
|
+
clamp_min(0, s64, 2)
|
|
1376
|
+
clamp_max(0, s8, select(feature("Subtarget.hasAVX512() && Subtarget.hasBWI()"), 64, select(feature("Subtarget.hasAVX2()"), 32, 16)))
|
|
1377
|
+
clamp_max(0, s16, select(feature("Subtarget.hasAVX512() && Subtarget.hasBWI()"), 32, select(feature("Subtarget.hasAVX2()"), 16, 8)))
|
|
1378
|
+
clamp_max(0, s32, select(feature("Subtarget.hasAVX512()"), 16, select(feature("Subtarget.hasAVX2()"), 8, 4)))
|
|
1379
|
+
clamp_max(0, s64, select(feature("Subtarget.hasAVX512()"), 8, select(feature("Subtarget.hasAVX2()"), 4, 2)))
|
|
1380
|
+
widen(slot(0), pow2_min(32))
|
|
1381
|
+
widen(slot(0), ty(s8))
|
|
1382
|
+
narrow(slot(0), ty(select_type(feature("Subtarget.is64Bit()"), s64, s32)))
|
|
1383
|
+
scalarize(slot(0))
|
|
1384
|
+
'''
|
|
1385
|
+
ast = Parser(src).parse()
|
|
1386
|
+
compiled = compile_rulesets(ast)
|
|
1387
|
+
|
|
1388
|
+
x86 = json.load(open("/home/ubuntu/legalizer-baseline/x86.core.json"))
|
|
1389
|
+
oracle = next(r for r in x86["rulesets"] if r["def"] == "X86_ADD")["rules"]
|
|
1390
|
+
assert len(compiled) == len(oracle), (len(compiled), len(oracle))
|
|
1391
|
+
|
|
1392
|
+
# (a) direct compile -> normal form, vs baseline
|
|
1393
|
+
ok1 = all(strip(e) == g for e, g in zip(oracle, compiled))
|
|
1394
|
+
print(f"(a) direct compile: {len(compiled)} rules "
|
|
1395
|
+
f"{'ALL MATCH' if ok1 else 'SOME DIFF'}")
|
|
1396
|
+
|
|
1397
|
+
# (b) free-form -> .td -> llvm-tblgen -gen-legalizer-core -> normal form
|
|
1398
|
+
td = TdEmitter([{"def": "X86_ADD", "opcodes": ["G_ADD", "G_SUB"],
|
|
1399
|
+
"rules": compiled}]).emit()
|
|
1400
|
+
d = tempfile.mkdtemp(prefix="dsl-td-")
|
|
1401
|
+
path = os.path.join(d, "X86LegalizerInfo.td")
|
|
1402
|
+
with open(path, "w") as f:
|
|
1403
|
+
f.write(td)
|
|
1404
|
+
out = os.path.join(d, "out.core.json")
|
|
1405
|
+
tblgen = "/home/ubuntu/llvm/builds/explore/bin/llvm-tblgen"
|
|
1406
|
+
inc = ["-I/home/ubuntu/llvm/trees/explore/llvm/lib/Target/X86",
|
|
1407
|
+
"-I/home/ubuntu/llvm/builds/explore/include",
|
|
1408
|
+
"-I/home/ubuntu/llvm/trees/explore/llvm/include",
|
|
1409
|
+
"-I/home/ubuntu/llvm/trees/explore/llvm/lib/Target"]
|
|
1410
|
+
r = subprocess.run([tblgen, "-gen-legalizer-core", *inc, path, "-o", out],
|
|
1411
|
+
capture_output=True, text=True)
|
|
1412
|
+
if r.returncode != 0:
|
|
1413
|
+
print("tblgen failed:\n" + r.stderr)
|
|
1414
|
+
print("emitted .td:\n" + td)
|
|
1415
|
+
return 1
|
|
1416
|
+
gen = json.load(open(out))
|
|
1417
|
+
gen_rules = next(x for x in gen["rulesets"] if x["def"] == "X86_ADD")["rules"]
|
|
1418
|
+
assert len(gen_rules) == len(oracle), (len(gen_rules), len(oracle))
|
|
1419
|
+
ok2 = all(strip(e) == strip(g) for e, g in zip(oracle, gen_rules))
|
|
1420
|
+
print(f"(b) .td round-trip: {len(gen_rules)} rules "
|
|
1421
|
+
f"{'ALL MATCH' if ok2 else 'SOME DIFF'}")
|
|
1422
|
+
if not ok2:
|
|
1423
|
+
for i, (e, g) in enumerate(zip(oracle, gen_rules)):
|
|
1424
|
+
if strip(e) != strip(g):
|
|
1425
|
+
print(f" #{i} oracle:", json.dumps(strip(e), sort_keys=True))
|
|
1426
|
+
print(f" #{i} gen :", json.dumps(strip(g), sort_keys=True))
|
|
1427
|
+
break
|
|
1428
|
+
# (c) static checks: positive (X86_ADD) + negative (sort / slot-bound)
|
|
1429
|
+
manifest = build_arity_manifest(
|
|
1430
|
+
"/home/ubuntu/llvm/trees/explore/llvm/include/llvm/Target/GenericOpcodes.td")
|
|
1431
|
+
errs = []
|
|
1432
|
+
arity = check_rules(compiled, ["G_ADD", "G_SUB"], manifest, errs)
|
|
1433
|
+
print(f"(c) static checks: X86_ADD arity={arity} errors={len(errs)}")
|
|
1434
|
+
ok3 = (len(errs) == 0 and arity == 1)
|
|
1435
|
+
|
|
1436
|
+
neg = [
|
|
1437
|
+
("sort mismatch", "rule G_ADD:\n legal slot(0) == pow2"),
|
|
1438
|
+
("slot out of range", "rule G_ADD:\n legal in(slot(1), s8)"),
|
|
1439
|
+
]
|
|
1440
|
+
ok4 = True
|
|
1441
|
+
for label, src in neg:
|
|
1442
|
+
ncomp = compile_rulesets(Parser(src).parse())
|
|
1443
|
+
nerrs = []
|
|
1444
|
+
check_rules(ncomp, ["G_ADD"], manifest, nerrs)
|
|
1445
|
+
got_err = len(nerrs) > 0
|
|
1446
|
+
ok4 &= got_err
|
|
1447
|
+
print(f" negative [{label}]: {'rejected' if got_err else 'MISSED'} -> {nerrs[:1]}")
|
|
1448
|
+
|
|
1449
|
+
print("static checks: " + ("ALL PASS" if (ok3 and ok4) else "FAIL"))
|
|
1450
|
+
|
|
1451
|
+
# (d) full-corpus reverse round-trip
|
|
1452
|
+
d1 = roundtrip_corpus("X86", "/home/ubuntu/legalizer-baseline/x86.core.json", "X86")
|
|
1453
|
+
d2 = roundtrip_corpus("AArch64",
|
|
1454
|
+
"/home/ubuntu/legalizer-baseline/aarch64.core.json",
|
|
1455
|
+
"AArch64")
|
|
1456
|
+
ok5 = (d1 == 0 and d2 == 0)
|
|
1457
|
+
print("full corpus: " + ("ALL PASS" if ok5 else "FAIL"))
|
|
1458
|
+
|
|
1459
|
+
# (e) forward: baseline core.json -> free-form DSL -> parse -> normal form
|
|
1460
|
+
e1 = forward_roundtrip("X86", "/home/ubuntu/legalizer-baseline/x86.core.json")
|
|
1461
|
+
e2 = forward_roundtrip("AArch64",
|
|
1462
|
+
"/home/ubuntu/legalizer-baseline/aarch64.core.json")
|
|
1463
|
+
ok6 = (e1 == 0 and e2 == 0)
|
|
1464
|
+
print("forward DSL: " + ("ALL PASS" if ok6 else "FAIL"))
|
|
1465
|
+
return 0 if (ok1 and ok2 and ok3 and ok4 and ok5 and ok6) else 1
|
|
1466
|
+
|
|
1467
|
+
|
|
1468
|
+
if __name__ == "__main__":
|
|
1469
|
+
sys.exit(main())
|