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/td.py
ADDED
|
@@ -0,0 +1,915 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Keep a target legalizer's .td generated from a free-form .gisel source.
|
|
3
|
+
|
|
4
|
+
The .gisel is the source of truth for the 1075 rules; the .td keeps the
|
|
5
|
+
TableGen scaffolding (feature / named-type / select-type / type-set records and
|
|
6
|
+
rule-set metadata) that the emitter turns into the C++ fragments. The rule
|
|
7
|
+
bodies are rendered by `dsl_spike` (the same grammar the forward round-trip
|
|
8
|
+
uses), and the generator resolves the canonical type/feature names back to the
|
|
9
|
+
target's original record names, so the generated .td produces byte-identical
|
|
10
|
+
-gen-legalizer / -gen-legalizer-table / -gen-legalizer-check artifacts and a
|
|
11
|
+
-gen-legalizer-core normal form that differs only in origin/line.
|
|
12
|
+
|
|
13
|
+
gisel_td.py bootstrap --td X.td --core X.core.json --out X.gisel
|
|
14
|
+
gisel_td.py gen X.gisel --out X.td
|
|
15
|
+
gisel_td.py check X.gisel --td X.td
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import json
|
|
20
|
+
import re
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
from . import dsl as D
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ---- TableGen scaffolding extraction (bootstrap only) ---------------------
|
|
28
|
+
|
|
29
|
+
SCAFFOLD_CLASSES = {"LegalizeFeature", "LegalizePredicateDag",
|
|
30
|
+
"LegalizeNamedType", "LegalizeSelectType",
|
|
31
|
+
"LegalizeCoreTypeSet", "LegalizeNamedTypeList"}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _scan_def(text, start):
|
|
35
|
+
"""Return the index just past the `def`/`defm` statement at `start`."""
|
|
36
|
+
i, depth, instr = start, 0, False
|
|
37
|
+
while i < len(text):
|
|
38
|
+
c = text[i]
|
|
39
|
+
if instr:
|
|
40
|
+
if c == '"' and text[i - 1] != "\\":
|
|
41
|
+
instr = False
|
|
42
|
+
elif text.startswith("//", i):
|
|
43
|
+
j = text.find("\n", i)
|
|
44
|
+
i = j if j >= 0 else len(text)
|
|
45
|
+
continue
|
|
46
|
+
elif text.startswith("/*", i):
|
|
47
|
+
j = text.find("*/", i)
|
|
48
|
+
i = j + 2 if j >= 0 else len(text)
|
|
49
|
+
continue
|
|
50
|
+
else:
|
|
51
|
+
if c == '"':
|
|
52
|
+
instr = True
|
|
53
|
+
elif c in "<[({":
|
|
54
|
+
depth += 1
|
|
55
|
+
elif c in ">])}":
|
|
56
|
+
depth -= 1
|
|
57
|
+
if c == "}" and depth == 0:
|
|
58
|
+
j = i + 1
|
|
59
|
+
while j < len(text) and text[j] in " \t\r\n":
|
|
60
|
+
j += 1
|
|
61
|
+
if j < len(text) and text[j] == ";":
|
|
62
|
+
j += 1
|
|
63
|
+
return j
|
|
64
|
+
elif c == ";" and depth == 0:
|
|
65
|
+
return i + 1
|
|
66
|
+
i += 1
|
|
67
|
+
raise ValueError("unterminated def at %d" % start)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _match_list(text, start):
|
|
71
|
+
depth, i = 0, start
|
|
72
|
+
while i < len(text):
|
|
73
|
+
if text[i] == "[":
|
|
74
|
+
depth += 1
|
|
75
|
+
elif text[i] == "]":
|
|
76
|
+
depth -= 1
|
|
77
|
+
if depth == 0:
|
|
78
|
+
return text[start:i + 1]
|
|
79
|
+
i += 1
|
|
80
|
+
raise ValueError("unbalanced list at %d" % start)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _def_string(body, cls):
|
|
84
|
+
m = re.search(r"%s<\s*\"([^\"]*)\"\s*>" % cls, body, re.S)
|
|
85
|
+
return m.group(1) if m else None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def parse_scaffolding(td_path):
|
|
89
|
+
"""Ordered scaffolding declarations and rule-set metadata from a .td."""
|
|
90
|
+
text = Path(td_path).read_text()
|
|
91
|
+
includes = [ln.strip() for ln in text.splitlines()
|
|
92
|
+
if ln.strip().startswith("include ")]
|
|
93
|
+
decls = [] # ordered [(kind, name, payload)]
|
|
94
|
+
rulesets = [] # ordered [(name, opcodes, comments)]
|
|
95
|
+
for m in re.finditer(r"(?m)^(def|defm)\s+([A-Za-z0-9_]+)\s*:\s*"
|
|
96
|
+
r"([A-Za-z0-9_]+)", text):
|
|
97
|
+
kw, name, cls = m.group(1), m.group(2), m.group(3)
|
|
98
|
+
end = _scan_def(text, m.start())
|
|
99
|
+
body = text[m.start():end]
|
|
100
|
+
if kw == "defm":
|
|
101
|
+
rulesets.append(_defm_meta(name, body))
|
|
102
|
+
elif cls in SCAFFOLD_CLASSES:
|
|
103
|
+
decls.append(_scaffold_decl(cls, name, body))
|
|
104
|
+
elif cls == "LegalizeRuleSet":
|
|
105
|
+
rulesets.append(_ruleset_meta(name, body))
|
|
106
|
+
return {"includes": includes, "decls": decls, "rulesets": rulesets}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _scaffold_decl(cls, name, body):
|
|
110
|
+
if cls == "LegalizeFeature":
|
|
111
|
+
return ("feature", name, _def_string(body, cls))
|
|
112
|
+
if cls == "LegalizeNamedType":
|
|
113
|
+
return ("type", name, _def_string(body, cls))
|
|
114
|
+
if cls == "LegalizePredicateDag":
|
|
115
|
+
m = re.search(r"LegalizePredicateDag<\s*(.*?)\s*>\s*;", body, re.S)
|
|
116
|
+
return ("pred", name, m.group(1).strip())
|
|
117
|
+
if cls == "LegalizeSelectType":
|
|
118
|
+
m = re.search(r"LegalizeSelectType<\s*(\[[^\]]*\])\s*,\s*"
|
|
119
|
+
r"(\[[^\]]*\])\s*,\s*([A-Za-z0-9_]+)\s*>", body, re.S)
|
|
120
|
+
conds = re.findall(r"[A-Za-z0-9_]+", m.group(1))
|
|
121
|
+
types = re.findall(r"[A-Za-z0-9_]+", m.group(2))
|
|
122
|
+
return ("select", name, (conds, types, m.group(3)))
|
|
123
|
+
if cls == "LegalizeNamedTypeList":
|
|
124
|
+
m = re.search(r"LegalizeNamedTypeList<\s*(\[[^\]]*\])\s*>", body, re.S)
|
|
125
|
+
return ("type_list", name, re.findall(r"[A-Za-z0-9_]+", m.group(1)))
|
|
126
|
+
m = re.search(r"LegalizeCoreTypeSet<\s*(.+?)\s*>", body, re.S)
|
|
127
|
+
inner = m.group(1)
|
|
128
|
+
if inner.startswith("["):
|
|
129
|
+
return ("type_set", name, re.findall(r"[A-Za-z0-9_]+", inner))
|
|
130
|
+
ref = re.fullmatch(r"([A-Za-z_][A-Za-z0-9_]*)\.Elems", inner.strip())
|
|
131
|
+
return ("type_set", name, ("ref", ref.group(1)))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _type_set_lists(decls):
|
|
135
|
+
"""name -> element list, resolving `X = Y.Elems` references."""
|
|
136
|
+
raw = {n: p for k, n, p in decls if k in ("type_set", "type_list")}
|
|
137
|
+
out = {}
|
|
138
|
+
|
|
139
|
+
def resolve(name, stack=()):
|
|
140
|
+
if name in out:
|
|
141
|
+
return out[name]
|
|
142
|
+
if name in stack or name not in raw:
|
|
143
|
+
raise ValueError(f"unresolved type-set ref {name!r}")
|
|
144
|
+
p = raw[name]
|
|
145
|
+
out[name] = resolve(p[1], stack + (name,)) if isinstance(p, tuple) else p
|
|
146
|
+
return out[name]
|
|
147
|
+
|
|
148
|
+
for n in raw:
|
|
149
|
+
resolve(n)
|
|
150
|
+
return out
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _ruleset_meta(name, body):
|
|
154
|
+
opcodes, comments, legacy = [], [], []
|
|
155
|
+
m = re.search(r"LegalizeRuleSet<\s*\[([^\]]*)\]", body)
|
|
156
|
+
if m:
|
|
157
|
+
opcodes = re.findall(r'"([^"]*)"', m.group(1))
|
|
158
|
+
cm = re.search(r"let\s+Comments\s*=\s*\[", body)
|
|
159
|
+
if cm:
|
|
160
|
+
raw = _match_list(body, cm.start() + cm.group(0).index("["))
|
|
161
|
+
comments = re.findall(r'"([^"]*)"', raw)
|
|
162
|
+
legacy = [int(x) for x in re.findall(r"ImmIdx<\s*(\d+)\s*>", body)]
|
|
163
|
+
return (name, opcodes, comments, legacy)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _defm_meta(name, body):
|
|
167
|
+
opcodes, comments = [], []
|
|
168
|
+
cm = re.search(r'"([^"]*)"\s*,\s*\[([^\]]*)\]', body)
|
|
169
|
+
if cm:
|
|
170
|
+
opcodes = [cm.group(1)]
|
|
171
|
+
comments = re.findall(r'"([^"]*)"', cm.group(2))
|
|
172
|
+
else:
|
|
173
|
+
m2 = re.search(r'"([^"]*)"', body)
|
|
174
|
+
if m2:
|
|
175
|
+
opcodes = [m2.group(1)]
|
|
176
|
+
legacy = [int(x) for x in re.findall(r"ImmIdx<\s*(\d+)\s*>", body)]
|
|
177
|
+
return (name, opcodes, comments, legacy)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# ---- canonical type / predicate reconstruction ----------------------------
|
|
181
|
+
|
|
182
|
+
def normalize_llt(expr, named_exprs):
|
|
183
|
+
"""Port of normalizeLlt() in LegalizerInfoEmitter.cpp."""
|
|
184
|
+
t = expr.strip()
|
|
185
|
+
if not any(c in t for c in "(){} \t\n") and t in named_exprs:
|
|
186
|
+
return normalize_llt(named_exprs[t], named_exprs)
|
|
187
|
+
m = re.fullmatch(r"LLT::scalar\((\d+)\)", t)
|
|
188
|
+
if m:
|
|
189
|
+
return "s" + m.group(1)
|
|
190
|
+
m = re.fullmatch(r"LLT::integer\((\d+)\)", t)
|
|
191
|
+
if m:
|
|
192
|
+
return "i" + m.group(1)
|
|
193
|
+
kinds = {"LLT::bfloat16()": "bf16", "LLT::float16()": "f16",
|
|
194
|
+
"LLT::float32()": "f32", "LLT::float64()": "f64",
|
|
195
|
+
"LLT::float128()": "f128"}
|
|
196
|
+
if t in kinds:
|
|
197
|
+
return kinds[t]
|
|
198
|
+
m = re.fullmatch(r"LLT::pointer\((\d+),\s*(.+)\)", t, re.S)
|
|
199
|
+
if m:
|
|
200
|
+
as_, bits = m.group(1), m.group(2).strip()
|
|
201
|
+
return f"p{as_}:{bits}" if bits.isdigit() else f"p{as_}:*"
|
|
202
|
+
for head, prefix in (("LLT::fixed_vector(", "v"),
|
|
203
|
+
("LLT::scalable_vector(", "nxv")):
|
|
204
|
+
if t.startswith(head) and t.endswith(")"):
|
|
205
|
+
inner = t[len(head):-1]
|
|
206
|
+
comma = inner.find(",")
|
|
207
|
+
if comma < 0:
|
|
208
|
+
return ""
|
|
209
|
+
lanes = inner[:comma].strip()
|
|
210
|
+
elem = inner[comma + 1:].strip()
|
|
211
|
+
elem = ("s" + elem) if elem.isdigit() else normalize_llt(elem, named_exprs)
|
|
212
|
+
if not lanes.isdigit() or not elem:
|
|
213
|
+
return ""
|
|
214
|
+
return prefix + lanes + elem
|
|
215
|
+
return ""
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _parse_pred_dag(dag, feat_names):
|
|
219
|
+
"""TableGen predicate dag -> legacy predicateJson node."""
|
|
220
|
+
def walk(s):
|
|
221
|
+
s = s.strip()
|
|
222
|
+
if s.startswith("("):
|
|
223
|
+
inner = s[1:-1].strip()
|
|
224
|
+
op_m = re.match(r"([A-Za-z_][A-Za-z0-9_]*)", inner)
|
|
225
|
+
op = op_m.group(1)
|
|
226
|
+
rest = inner[op_m.end():].lstrip(",").strip()
|
|
227
|
+
kids = _split_top(rest)
|
|
228
|
+
if op == "not":
|
|
229
|
+
return {"kind": "not", "arg": walk(kids[0])}
|
|
230
|
+
return {"kind": "nary",
|
|
231
|
+
"op": "all_of" if op == "all_of" else "any_of",
|
|
232
|
+
"args": [walk(k) for k in kids]}
|
|
233
|
+
if s in feat_names:
|
|
234
|
+
return {"kind": "leaf", "name": "hasFeature", "args": [feat_names[s]]}
|
|
235
|
+
raise ValueError("unknown predicate leaf %r" % s)
|
|
236
|
+
return walk(dag.strip())
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _split_top(s):
|
|
240
|
+
out, depth, cur = [], 0, []
|
|
241
|
+
for c in s:
|
|
242
|
+
if c == "(":
|
|
243
|
+
depth += 1
|
|
244
|
+
elif c == ")":
|
|
245
|
+
depth -= 1
|
|
246
|
+
if c == "," and depth == 0:
|
|
247
|
+
out.append("".join(cur).strip())
|
|
248
|
+
cur = []
|
|
249
|
+
else:
|
|
250
|
+
cur.append(c)
|
|
251
|
+
if "".join(cur).strip():
|
|
252
|
+
out.append("".join(cur).strip())
|
|
253
|
+
return out
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _pred_dsl(node, feat_of):
|
|
257
|
+
"""predicateJson node -> free-form `A and not B` text (record names)."""
|
|
258
|
+
k = node["kind"]
|
|
259
|
+
if k == "leaf" and node["name"] == "hasFeature":
|
|
260
|
+
return feat_of[node["args"][0]]
|
|
261
|
+
if k == "not":
|
|
262
|
+
inner = node["arg"] if "arg" in node else node["args"][0]
|
|
263
|
+
return f"not {_pred_dsl(inner, feat_of)}"
|
|
264
|
+
if k in ("nary", "all", "any"):
|
|
265
|
+
op = node.get("op", "all_of" if k == "all" else "any_of")
|
|
266
|
+
join = " and " if op == "all_of" else " or "
|
|
267
|
+
return "(" + join.join(_pred_dsl(a, feat_of) for a in node["args"]) + ")"
|
|
268
|
+
raise ValueError(f"unsupported predicate node {node!r}")
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# ---- names: canonical -> original record name -----------------------------
|
|
272
|
+
|
|
273
|
+
class Names:
|
|
274
|
+
def __init__(self):
|
|
275
|
+
self.feature = {} # C++ cond string -> record name
|
|
276
|
+
self.type = {} # canonical type -> record name
|
|
277
|
+
self.select = {} # canonical select JSON -> record name
|
|
278
|
+
self.type_set = {} # tuple of record names -> set record name
|
|
279
|
+
self.records = set() # every declared record name
|
|
280
|
+
|
|
281
|
+
def select_value(self, value):
|
|
282
|
+
return self.select.get(json.dumps(value, sort_keys=True))
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def collect_named_sets(decls):
|
|
286
|
+
"""Named type sets, expanded to their member record names.
|
|
287
|
+
|
|
288
|
+
`decodeCoreSet` in the C++ emitter expands a named set, so `in s64` and
|
|
289
|
+
`in [s64]` render identically. The normal form keeps the name, so every
|
|
290
|
+
backend that emits a set has to expand it here rather than re-deriving the
|
|
291
|
+
expansion on its own.
|
|
292
|
+
"""
|
|
293
|
+
raw = {n: p for k, n, p in decls if k == "type_set"}
|
|
294
|
+
lists = {n: p for k, n, p in decls if k == "type_list"}
|
|
295
|
+
out = {}
|
|
296
|
+
|
|
297
|
+
def resolve(name, seen=()):
|
|
298
|
+
if name in out:
|
|
299
|
+
return out[name]
|
|
300
|
+
if name in lists:
|
|
301
|
+
out[name] = list(lists[name])
|
|
302
|
+
return out[name]
|
|
303
|
+
p = raw.get(name)
|
|
304
|
+
if p is None:
|
|
305
|
+
return None
|
|
306
|
+
if isinstance(p, tuple) and p[0] == "ref":
|
|
307
|
+
out[name] = list(resolve(p[1], seen + (name,)) or [])
|
|
308
|
+
else:
|
|
309
|
+
out[name] = list(p)
|
|
310
|
+
return out[name]
|
|
311
|
+
|
|
312
|
+
for name in raw:
|
|
313
|
+
resolve(name)
|
|
314
|
+
return out
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def build_names(decls):
|
|
318
|
+
"""Resolve canonical names back to the target's record names."""
|
|
319
|
+
names = Names()
|
|
320
|
+
type_expr = {n: p for k, n, p in decls if k == "type"}
|
|
321
|
+
feat_of = {n: p for k, n, p in decls if k == "feature"}
|
|
322
|
+
dag_node = {n: _bool_node(parse_bool(p), feat_of) for k, n, p in decls
|
|
323
|
+
if k == "pred"}
|
|
324
|
+
canon_of = {}
|
|
325
|
+
for name, expr in type_expr.items():
|
|
326
|
+
canon = normalize_llt(expr, type_expr)
|
|
327
|
+
if canon:
|
|
328
|
+
names.type[canon] = name
|
|
329
|
+
canon_of[name] = canon
|
|
330
|
+
for name, cond in feat_of.items():
|
|
331
|
+
names.feature[cond] = name
|
|
332
|
+
names.records = set(type_expr) | set(feat_of) | set(dag_node) | {
|
|
333
|
+
n for k, n, _ in decls}
|
|
334
|
+
resolved_sets = _type_set_lists(decls)
|
|
335
|
+
for k, name, payload in decls:
|
|
336
|
+
if k == "type_set":
|
|
337
|
+
names.type_set[tuple(resolved_sets[name])] = name
|
|
338
|
+
for k, name, payload in decls:
|
|
339
|
+
if k != "select":
|
|
340
|
+
continue
|
|
341
|
+
conds, types, otherwise = payload
|
|
342
|
+
|
|
343
|
+
def cond_node(rec):
|
|
344
|
+
if rec in feat_of:
|
|
345
|
+
return {"kind": "leaf", "name": "hasFeature",
|
|
346
|
+
"args": [feat_of[rec]]}
|
|
347
|
+
if rec in dag_node:
|
|
348
|
+
return dag_node[rec]
|
|
349
|
+
raise ValueError("unknown select cond %r" % rec)
|
|
350
|
+
|
|
351
|
+
acc = canon_of[otherwise]
|
|
352
|
+
for i in range(len(conds) - 1, -1, -1):
|
|
353
|
+
acc = {"select": {"cond": cond_node(conds[i]),
|
|
354
|
+
"then": canon_of[types[i]], "else": acc}}
|
|
355
|
+
names.select[json.dumps(acc, sort_keys=True)] = name
|
|
356
|
+
return names
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
# ---- .gisel: parse --------------------------------------------------------
|
|
360
|
+
|
|
361
|
+
DECL_RE = re.compile(r"^(feature|pred|type|select|type_set|type_list)\s+"
|
|
362
|
+
r"([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$")
|
|
363
|
+
RULESET_RE = re.compile(r"^ruleset\s+([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(.*)$")
|
|
364
|
+
NOTE_RE = re.compile(r'^note\s+"(.*)"$')
|
|
365
|
+
IMM_RE = re.compile(r"^imm_idx\(\s*(\d+)\s*\)$")
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _bool_tokens(text):
|
|
369
|
+
return re.findall(r"[A-Za-z_][A-Za-z0-9_]*|[()]", text)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
class _BoolParser:
|
|
373
|
+
def __init__(self, text):
|
|
374
|
+
self.toks = _bool_tokens(text)
|
|
375
|
+
self.text = text
|
|
376
|
+
self.pos = 0
|
|
377
|
+
|
|
378
|
+
def peek(self):
|
|
379
|
+
return self.toks[self.pos] if self.pos < len(self.toks) else None
|
|
380
|
+
|
|
381
|
+
def take(self, want=None):
|
|
382
|
+
t = self.peek()
|
|
383
|
+
if t is None or (want is not None and t != want):
|
|
384
|
+
raise ValueError(f"expected {want!r}, got {t!r} in {self.text!r}")
|
|
385
|
+
self.pos += 1
|
|
386
|
+
return t
|
|
387
|
+
|
|
388
|
+
def parse_or(self):
|
|
389
|
+
left = self.parse_and()
|
|
390
|
+
while self.peek() == "or":
|
|
391
|
+
self.take()
|
|
392
|
+
left = ("or", left, self.parse_and())
|
|
393
|
+
return left
|
|
394
|
+
|
|
395
|
+
def parse_and(self):
|
|
396
|
+
left = self.parse_unary()
|
|
397
|
+
while self.peek() == "and":
|
|
398
|
+
self.take()
|
|
399
|
+
left = ("and", left, self.parse_unary())
|
|
400
|
+
return left
|
|
401
|
+
|
|
402
|
+
def parse_unary(self):
|
|
403
|
+
if self.peek() == "not":
|
|
404
|
+
self.take()
|
|
405
|
+
return ("not", self.parse_unary())
|
|
406
|
+
if self.peek() == "(":
|
|
407
|
+
self.take()
|
|
408
|
+
node = self.parse_or()
|
|
409
|
+
self.take(")")
|
|
410
|
+
return node
|
|
411
|
+
return ("name", self.take())
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def parse_bool(text):
|
|
415
|
+
p = _BoolParser(text)
|
|
416
|
+
node = p.parse_or()
|
|
417
|
+
if p.peek() is not None:
|
|
418
|
+
raise ValueError(f"trailing tokens in predicate {text!r}")
|
|
419
|
+
return node
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _parse_sel(text):
|
|
423
|
+
"""`if C then A else B` chain -> (conds, types, otherwise)."""
|
|
424
|
+
p = _BoolParser(text)
|
|
425
|
+
conds, types = [], []
|
|
426
|
+
while p.peek() == "if":
|
|
427
|
+
p.take()
|
|
428
|
+
cond = p.parse_or()
|
|
429
|
+
p.take("then")
|
|
430
|
+
then = p.parse_or()
|
|
431
|
+
p.take("else")
|
|
432
|
+
if cond[0] != "name" or then[0] != "name":
|
|
433
|
+
raise ValueError(f"select needs record names: {text!r}")
|
|
434
|
+
conds.append(cond[1])
|
|
435
|
+
types.append(then[1])
|
|
436
|
+
els = p.parse_unary()
|
|
437
|
+
if els[0] != "name" or p.peek() is not None:
|
|
438
|
+
raise ValueError(f"select: bad tail {els!r} in {text!r}")
|
|
439
|
+
return conds, types, els[1]
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _bool_dag_text(node):
|
|
443
|
+
if node[0] == "name":
|
|
444
|
+
return node[1]
|
|
445
|
+
if node[0] == "not":
|
|
446
|
+
return f"(not {_bool_dag_text(node[1])})"
|
|
447
|
+
if node[0] == "and":
|
|
448
|
+
return f"(all_of {_bool_dag_text(node[1])}, {_bool_dag_text(node[2])})"
|
|
449
|
+
if node[0] == "or":
|
|
450
|
+
return f"(any_of {_bool_dag_text(node[1])}, {_bool_dag_text(node[2])})"
|
|
451
|
+
raise ValueError(node)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def _bool_node(node, feat_of):
|
|
455
|
+
if node[0] == "name":
|
|
456
|
+
return {"kind": "leaf", "name": "hasFeature", "args": [feat_of[node[1]]]}
|
|
457
|
+
if node[0] == "not":
|
|
458
|
+
return {"kind": "not", "arg": _bool_node(node[1], feat_of)}
|
|
459
|
+
return {"kind": "nary",
|
|
460
|
+
"op": "all_of" if node[0] == "and" else "any_of",
|
|
461
|
+
"args": [_bool_node(a, feat_of) for a in node[1:]]}
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _bracket_depth(text):
|
|
465
|
+
depth = 0
|
|
466
|
+
instr = False
|
|
467
|
+
prev = ""
|
|
468
|
+
for c in text:
|
|
469
|
+
if instr:
|
|
470
|
+
if c == '"' and prev != "\\":
|
|
471
|
+
instr = False
|
|
472
|
+
elif c == '"':
|
|
473
|
+
instr = True
|
|
474
|
+
elif c in "([{":
|
|
475
|
+
depth += 1
|
|
476
|
+
elif c in ")]}":
|
|
477
|
+
depth -= 1
|
|
478
|
+
prev = c
|
|
479
|
+
return depth
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def _join_continuations(lines):
|
|
483
|
+
"""Join a statement that a human wrapped inside brackets (free format).
|
|
484
|
+
|
|
485
|
+
`lines` are (source line, raw text); the result keeps the line the
|
|
486
|
+
statement started on, so a parse error inside a wrapped rule still points
|
|
487
|
+
at the first physical line of that rule.
|
|
488
|
+
"""
|
|
489
|
+
out, buf, start = [], None, None
|
|
490
|
+
for ln, raw in lines:
|
|
491
|
+
st = raw.strip()
|
|
492
|
+
if buf is None:
|
|
493
|
+
buf, start = st, ln
|
|
494
|
+
else:
|
|
495
|
+
buf += " " + st
|
|
496
|
+
if _bracket_depth(buf) <= 0:
|
|
497
|
+
out.append((start, buf))
|
|
498
|
+
buf = None
|
|
499
|
+
if buf is not None:
|
|
500
|
+
out.append((start, buf))
|
|
501
|
+
return out
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def _wrap_statement(text, width=88, indent=8):
|
|
505
|
+
"""Greedy wrap at `, ` for a long rule; the parser joins it back."""
|
|
506
|
+
if len(text) <= width:
|
|
507
|
+
return [text]
|
|
508
|
+
out, cur = [], ""
|
|
509
|
+
for part in re.split(r"(?<=, )", text):
|
|
510
|
+
if cur and len(cur) + len(part) > width:
|
|
511
|
+
out.append(cur.rstrip())
|
|
512
|
+
cur = " " * indent
|
|
513
|
+
cur += part
|
|
514
|
+
out.append(cur.rstrip())
|
|
515
|
+
return out
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def parse_gisel(text):
|
|
519
|
+
includes, decls, rulesets = [], [], []
|
|
520
|
+
lines = text.splitlines()
|
|
521
|
+
i = 0
|
|
522
|
+
while i < len(lines):
|
|
523
|
+
line = lines[i].rstrip()
|
|
524
|
+
i += 1
|
|
525
|
+
s = line.strip()
|
|
526
|
+
if not s or s.startswith("//"):
|
|
527
|
+
continue
|
|
528
|
+
if s.startswith("include "):
|
|
529
|
+
includes.append(s)
|
|
530
|
+
continue
|
|
531
|
+
m = DECL_RE.match(s)
|
|
532
|
+
if m:
|
|
533
|
+
kind, name, body = m.group(1), m.group(2), m.group(3).strip()
|
|
534
|
+
if kind == "select":
|
|
535
|
+
decls.append((kind, name, _parse_sel(body)))
|
|
536
|
+
elif kind == "type_list":
|
|
537
|
+
decls.append((kind, name,
|
|
538
|
+
re.findall(r"[A-Za-z0-9_]+", body)))
|
|
539
|
+
elif kind == "type_set":
|
|
540
|
+
ref = re.fullmatch(r"([A-Za-z_][A-Za-z0-9_]*)\.Elems", body)
|
|
541
|
+
if ref:
|
|
542
|
+
decls.append((kind, name, ("ref", ref.group(1))))
|
|
543
|
+
else:
|
|
544
|
+
decls.append((kind, name,
|
|
545
|
+
re.findall(r"[A-Za-z0-9_]+", body)))
|
|
546
|
+
else:
|
|
547
|
+
decls.append((kind, name, body))
|
|
548
|
+
continue
|
|
549
|
+
m = RULESET_RE.match(s)
|
|
550
|
+
if not m:
|
|
551
|
+
raise ValueError(f"line {i}: unrecognized source line {s!r}")
|
|
552
|
+
name, opcodes = m.group(1), m.group(2).split()
|
|
553
|
+
ruleset_line = i # i is the 1-based line just consumed
|
|
554
|
+
comments, body, legacy, raw_body = [], [], [], []
|
|
555
|
+
while i < len(lines):
|
|
556
|
+
raw = lines[i]
|
|
557
|
+
if raw.strip() and not raw.startswith((" ", "\t")):
|
|
558
|
+
break
|
|
559
|
+
i += 1
|
|
560
|
+
st = raw.strip()
|
|
561
|
+
if not st or st.startswith("//"):
|
|
562
|
+
continue
|
|
563
|
+
raw_body.append((i, raw))
|
|
564
|
+
for ln, st in _join_continuations(raw_body):
|
|
565
|
+
nm = NOTE_RE.match(st)
|
|
566
|
+
im = IMM_RE.match(st)
|
|
567
|
+
if nm:
|
|
568
|
+
comments.append(nm.group(1))
|
|
569
|
+
elif im:
|
|
570
|
+
legacy.append(int(im.group(1)))
|
|
571
|
+
else:
|
|
572
|
+
body.append((ln, st))
|
|
573
|
+
snippet = ["rule " + " ".join(opcodes) + ":"]
|
|
574
|
+
snippet += [" " + b.strip() for _, b in body]
|
|
575
|
+
line_map = [ruleset_line] + [ln for ln, _ in body]
|
|
576
|
+
ast = D.Parser("\n".join(snippet), line_map=line_map).parse()
|
|
577
|
+
compiled = D.compile_rulesets(ast)
|
|
578
|
+
rulesets.append({"def": name, "opcodes": opcodes, "comments": comments,
|
|
579
|
+
"legacy": legacy, "rules": compiled})
|
|
580
|
+
return {"includes": includes, "decls": decls, "rulesets": rulesets}
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
GISE_HEADER = """\
|
|
584
|
+
//===- {target}LegalizerInfo.gisel - free-form legalizer rules --*- gisel -*-===//
|
|
585
|
+
//
|
|
586
|
+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
587
|
+
// See https://llvm.org/LICENSE.txt for license information.
|
|
588
|
+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
589
|
+
//
|
|
590
|
+
//===----------------------------------------------------------------------===//
|
|
591
|
+
//
|
|
592
|
+
// Source of truth for {target}LegalizerInfo.td. That .td is generated; do not
|
|
593
|
+
// edit it. Regenerate and check with:
|
|
594
|
+
//
|
|
595
|
+
// llvm-dev/tools/gisel_td.py gen <this file> --out {target}LegalizerInfo.td
|
|
596
|
+
// llvm-dev/tools/gisel_td.py check <this file> --td {target}LegalizerInfo.td
|
|
597
|
+
//
|
|
598
|
+
// The rule body is one statement per line; the indentation after `ruleset`
|
|
599
|
+
// groups statements into one rule set. The vocabulary is the core IR:
|
|
600
|
+
//
|
|
601
|
+
// legal <pred> terminal: legal
|
|
602
|
+
// lower/custom/unsupported/libcall <pred>
|
|
603
|
+
// widen|narrow|more|fewer|bitcast(<obs>, <target>) [when <pred>]
|
|
604
|
+
// scalarize(<obs>) [when <pred>]
|
|
605
|
+
// clamp_min(<idx>, <elt>, <lanes>) / clamp_max(<idx>, <elt>, <lanes>)
|
|
606
|
+
// imm_idx(<n>) coverage marker, not a core rule
|
|
607
|
+
//
|
|
608
|
+
// Observations: slot(i), elem_of, scalar_of, sort_of, num_elems, bits_of,
|
|
609
|
+
// size_of, size_in_bytes_of, is_byte_sized, addr_space, align_of, mem_slot,
|
|
610
|
+
// mem_ty, mem_ordering, mem_failure_ordering, is_pow2, is_bfloat16, is_float16.
|
|
611
|
+
// Constructors: pow2, pow2_min(n), multiple_of(n), pow2_ceil(n), clamp(l,h),
|
|
612
|
+
// ty(t), same_as(k), min_vector_size(n), int_of_size(n), elt_type_to(t,t),
|
|
613
|
+
// elt_size_to(t,n), div_lanes(t,n), vec_of(n,t), scalar_or_vec_of(n,t),
|
|
614
|
+
// replace(t), select(p,a,b), mul(a,b), urem(a,b), udiv(a,b).
|
|
615
|
+
// Predicates: feature("<C++ bool expression>"), true, A and B, A or B, not A,
|
|
616
|
+
// a == b (and !=, <, <=, >, >=), in(slot(t)|[t,...]), in_pairs, in_triples,
|
|
617
|
+
// in_mem_desc.
|
|
618
|
+
//
|
|
619
|
+
// The declarations below are the TableGen scaffolding the rules name: the
|
|
620
|
+
// feature booleans (whose record names become the C++ `bool`s), the named LLT
|
|
621
|
+
// constants, the select types that cache a subtarget choice, and the type
|
|
622
|
+
// lists/sets a rule can use as one operand. A rule body never spells the
|
|
623
|
+
// target prefix; the generator resolves `s8`, `v2s64`, ... to the named
|
|
624
|
+
// records.
|
|
625
|
+
//
|
|
626
|
+
//===----------------------------------------------------------------------===//
|
|
627
|
+
|
|
628
|
+
"""
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
# ---- .gisel: bootstrap from a hand-written .td + core JSON ----------------
|
|
632
|
+
|
|
633
|
+
def bootstrap(td_path, core_path, title):
|
|
634
|
+
scaf = parse_scaffolding(td_path)
|
|
635
|
+
core = json.loads(Path(core_path).read_text())
|
|
636
|
+
meta = {name: (opcodes, comments, legacy)
|
|
637
|
+
for name, opcodes, comments, legacy in scaf["rulesets"]}
|
|
638
|
+
out = GISE_HEADER.format(target=title).rstrip("\n").splitlines()
|
|
639
|
+
out += scaf["includes"]
|
|
640
|
+
out.append("")
|
|
641
|
+
feat_of = {n: p for k, n, p in scaf["decls"] if k == "feature"}
|
|
642
|
+
rec_of = {p: n for n, p in feat_of.items()}
|
|
643
|
+
gdecls = []
|
|
644
|
+
for kind, name, payload in scaf["decls"]:
|
|
645
|
+
if kind == "pred":
|
|
646
|
+
payload = _pred_dsl(_parse_pred_dag(payload, feat_of), rec_of)
|
|
647
|
+
gdecls.append((kind, name, payload))
|
|
648
|
+
for kind, name, payload in gdecls:
|
|
649
|
+
if kind == "feature":
|
|
650
|
+
out.append(f"feature {name} = {payload}")
|
|
651
|
+
elif kind == "pred":
|
|
652
|
+
out.append(f"pred {name} = {payload}")
|
|
653
|
+
elif kind == "type":
|
|
654
|
+
out.append(f"type {name} = {payload}")
|
|
655
|
+
elif kind == "select":
|
|
656
|
+
conds, types, els = payload
|
|
657
|
+
expr = els
|
|
658
|
+
for c, t in zip(reversed(conds), reversed(types)):
|
|
659
|
+
expr = f"if {c} then {t} else {expr}"
|
|
660
|
+
out.append(f"select {name} = {expr}")
|
|
661
|
+
elif kind == "type_list":
|
|
662
|
+
out.append(f"type_list {name} = [" + ", ".join(payload) + "]")
|
|
663
|
+
elif isinstance(payload, tuple):
|
|
664
|
+
out.append(f"type_set {name} = {payload[1]}.Elems")
|
|
665
|
+
else:
|
|
666
|
+
out.append(f"type_set {name} = [" + ", ".join(payload) + "]")
|
|
667
|
+
out.append("")
|
|
668
|
+
names = build_names(gdecls)
|
|
669
|
+
_orig_type_value = D.render_type_value
|
|
670
|
+
_orig_set = D.render_set
|
|
671
|
+
|
|
672
|
+
def _type_value(v):
|
|
673
|
+
if isinstance(v, str):
|
|
674
|
+
return names.type.get(v, v)
|
|
675
|
+
named = names.select_value(v)
|
|
676
|
+
return named if named is not None else _orig_type_value(v)
|
|
677
|
+
|
|
678
|
+
def _set(n):
|
|
679
|
+
if n.get("arity", 1) == 1:
|
|
680
|
+
recs = tuple(names.type.get(e, e) for e in n["elems"])
|
|
681
|
+
if recs in names.type_set:
|
|
682
|
+
return names.type_set[recs]
|
|
683
|
+
return _orig_set(n)
|
|
684
|
+
|
|
685
|
+
def _rule(r):
|
|
686
|
+
sugar = _clamp_sugar(r)
|
|
687
|
+
return sugar if sugar is not None else D.render_rule(r)
|
|
688
|
+
|
|
689
|
+
D.render_type_value = _type_value
|
|
690
|
+
D.render_set = _set
|
|
691
|
+
for rs in core["rulesets"]:
|
|
692
|
+
opcodes, comments, legacy = meta[rs["def"]]
|
|
693
|
+
assert opcodes == rs["opcodes"], (rs["def"], opcodes, rs["opcodes"])
|
|
694
|
+
out.append(f"ruleset {rs['def']}: " + " ".join(opcodes))
|
|
695
|
+
for c in comments:
|
|
696
|
+
out.append(f" note {json.dumps(c)}")
|
|
697
|
+
for r in rs["rules"]:
|
|
698
|
+
out += [" " + ln for ln in _wrap_statement(_rule(r))]
|
|
699
|
+
for idx in legacy:
|
|
700
|
+
out.append(f" imm_idx({idx})")
|
|
701
|
+
out.append("")
|
|
702
|
+
return "\n".join(out).rstrip() + "\n"
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def _clamp_sugar(r):
|
|
706
|
+
"""Render a clamp_min/clamp_max rule as the sugar it was written as.
|
|
707
|
+
|
|
708
|
+
Only fires when the rule is exactly what the clamp class expands to, so a
|
|
709
|
+
hand-written `more(replace(vec_of(...)))` is never silently rewritten.
|
|
710
|
+
"""
|
|
711
|
+
if r["op"] not in ("more", "fewer"):
|
|
712
|
+
return None
|
|
713
|
+
to = r.get("to") or {}
|
|
714
|
+
if to.get("kind") != "replace" or len(to.get("args", [])) != 1:
|
|
715
|
+
return None
|
|
716
|
+
ctor = to["args"][0]
|
|
717
|
+
want = "vecOf" if r["op"] == "more" else "scalarOrVecOf"
|
|
718
|
+
if ctor.get("kind") != want or len(ctor.get("args", [])) != 2:
|
|
719
|
+
return None
|
|
720
|
+
lanes, elem = ctor["args"]
|
|
721
|
+
if lanes.get("kind") not in ("numElems", "int"):
|
|
722
|
+
return None
|
|
723
|
+
if elem.get("kind") != "elemOf":
|
|
724
|
+
return None
|
|
725
|
+
if lanes["kind"] == "numElems":
|
|
726
|
+
ty = lanes["args"][0]
|
|
727
|
+
if ty.get("kind") != "ty":
|
|
728
|
+
return None
|
|
729
|
+
sl = elem["args"][0]
|
|
730
|
+
if sl.get("kind") != "slot":
|
|
731
|
+
return None
|
|
732
|
+
guard = r.get("predicate") or {}
|
|
733
|
+
if guard.get("kind") != "all" or len(guard.get("args", [])) != 4:
|
|
734
|
+
return None
|
|
735
|
+
elem_eq = guard["args"][2]
|
|
736
|
+
if elem_eq.get("kind") != "eq" or len(elem_eq.get("args", [])) != 2:
|
|
737
|
+
return None
|
|
738
|
+
elt = elem_eq["args"][1]
|
|
739
|
+
if elt.get("kind") != "ty" or elt["args"][0].get("kind") != "type":
|
|
740
|
+
return None
|
|
741
|
+
idx = sl["index"]
|
|
742
|
+
op = "clamp_min" if r["op"] == "more" else "clamp_max"
|
|
743
|
+
expected = D.compile_stmt({"op": op, "idx": idx,
|
|
744
|
+
"elt": elt["args"][0]["value"], "lanes": lanes})
|
|
745
|
+
if D.strip(expected) != D.strip(r):
|
|
746
|
+
return None
|
|
747
|
+
return (f"{op}({idx}, {D.render_type_value(elt['args'][0]['value'])}, "
|
|
748
|
+
f"{D.render_expr(lanes)})")
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
# ---- .gitd: emit ----------------------------------------------------------
|
|
752
|
+
|
|
753
|
+
class NamedTdEmitter(D.TdEmitter):
|
|
754
|
+
"""TdEmitter that reuses the target's original record names."""
|
|
755
|
+
|
|
756
|
+
def __init__(self, names):
|
|
757
|
+
self.names = names
|
|
758
|
+
|
|
759
|
+
def _feat(self, s):
|
|
760
|
+
return self.names.feature[s]
|
|
761
|
+
|
|
762
|
+
def _type(self, name):
|
|
763
|
+
if name in self.names.type:
|
|
764
|
+
return self.names.type[name]
|
|
765
|
+
if name in self.names.records:
|
|
766
|
+
return name
|
|
767
|
+
raise KeyError(f"unknown type name {name!r}")
|
|
768
|
+
|
|
769
|
+
def _type_value(self, v):
|
|
770
|
+
if isinstance(v, str):
|
|
771
|
+
return self._type(v)
|
|
772
|
+
name = self.names.select_value(v)
|
|
773
|
+
if name is None:
|
|
774
|
+
raise KeyError(f"no named select for {json.dumps(v, sort_keys=True)}")
|
|
775
|
+
return name
|
|
776
|
+
|
|
777
|
+
def _select_cond(self, cond):
|
|
778
|
+
raise AssertionError("select conditions are resolved by _type_value")
|
|
779
|
+
|
|
780
|
+
def _set(self, n):
|
|
781
|
+
arity = n.get("arity", 1)
|
|
782
|
+
elems = n["elems"]
|
|
783
|
+
if arity == 1:
|
|
784
|
+
recs = [self._type_value(e) for e in elems]
|
|
785
|
+
# A named type set keeps the .td readable; the emitter expands it
|
|
786
|
+
# either way, so the artifact is the same.
|
|
787
|
+
key = tuple(recs)
|
|
788
|
+
if key in self.names.type_set:
|
|
789
|
+
return self.names.type_set[key]
|
|
790
|
+
return "[" + ", ".join(recs) + "]"
|
|
791
|
+
if arity == 2 and len(elems[0]) == 4:
|
|
792
|
+
return ("[" + ", ".join(
|
|
793
|
+
f"LegalizeMemDesc<{self._type_value(e[0])}, "
|
|
794
|
+
f"{self._type_value(e[1])}, {self._type_value(e[2])}, {e[3]}>"
|
|
795
|
+
for e in elems) + "]")
|
|
796
|
+
return ("[" + ", ".join(
|
|
797
|
+
"[" + ", ".join(self._type_value(x) for x in e) + "]"
|
|
798
|
+
for e in elems) + "]")
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
def _emit_decl(kind, name, payload):
|
|
802
|
+
if kind == "feature":
|
|
803
|
+
return f'def {name} : LegalizeFeature<"{payload}">;'
|
|
804
|
+
if kind == "pred":
|
|
805
|
+
dag = _bool_dag_text(parse_bool(payload))
|
|
806
|
+
return f"def {name} : LegalizePredicateDag<{dag}>;"
|
|
807
|
+
if kind == "type":
|
|
808
|
+
return f'def {name} : LegalizeNamedType<"{payload}">;'
|
|
809
|
+
if kind == "type_list":
|
|
810
|
+
return f"def {name} : LegalizeNamedTypeList<[{', '.join(payload)}]>;"
|
|
811
|
+
if kind == "select":
|
|
812
|
+
conds, types, els = payload
|
|
813
|
+
return (f"def {name} : LegalizeSelectType<"
|
|
814
|
+
f"[{', '.join(conds)}], [{', '.join(types)}], {els}>;")
|
|
815
|
+
if isinstance(payload, tuple):
|
|
816
|
+
return f"def {name} : LegalizeCoreTypeSet<{payload[1]}.Elems>;"
|
|
817
|
+
return f"def {name} : LegalizeCoreTypeSet<[{', '.join(payload)}]>;"
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
def target_name(gs, source_name):
|
|
821
|
+
"""The target a source belongs to: the first rule set's def prefix.
|
|
822
|
+
|
|
823
|
+
Deriving it from the rule sets rather than the file name keeps generated
|
|
824
|
+
artifacts identical when a source is renamed or copied, and lets `check`
|
|
825
|
+
work on a scratch copy. `X86_IMPLICIT_DEF` -> `X86`.
|
|
826
|
+
"""
|
|
827
|
+
for rs in gs.get("rulesets", []):
|
|
828
|
+
d = rs["def"]
|
|
829
|
+
if "_" in d:
|
|
830
|
+
return d.split("_", 1)[0]
|
|
831
|
+
return Path(source_name).stem
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
def emit_td(gs, source_name):
|
|
835
|
+
names = build_names(gs["decls"])
|
|
836
|
+
emitter = NamedTdEmitter(names)
|
|
837
|
+
target = target_name(gs, source_name)
|
|
838
|
+
out = ["//===- " + target + "LegalizerInfo.td - generated legalizer rules "
|
|
839
|
+
"-*- tablegen -*-===//",
|
|
840
|
+
"//",
|
|
841
|
+
"// Part of the LLVM Project, under the Apache License v2.0 with LLVM",
|
|
842
|
+
"// Exceptions. See https://llvm.org/LICENSE.txt for license information.",
|
|
843
|
+
"// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception",
|
|
844
|
+
"//",
|
|
845
|
+
"//===----------------------------------------------------------------------===//",
|
|
846
|
+
"//",
|
|
847
|
+
f"// Generated from {target}LegalizerInfo.gisel by llvm-dev/tools/gisel_td.py.",
|
|
848
|
+
"// Do not edit: edit the source and re-run the generator.",
|
|
849
|
+
"",
|
|
850
|
+
"//===----------------------------------------------------------------------===//"]
|
|
851
|
+
out += gs["includes"]
|
|
852
|
+
out += ["//===----------------------------------------------------------------------===//", ""]
|
|
853
|
+
for kind, name, payload in gs["decls"]:
|
|
854
|
+
out.append(_emit_decl(kind, name, payload))
|
|
855
|
+
out.append("")
|
|
856
|
+
for rs in gs["rulesets"]:
|
|
857
|
+
opcodes = ", ".join(json.dumps(o) for o in rs["opcodes"])
|
|
858
|
+
out.append(f"def {rs['def']} : LegalizeRuleSet<[{opcodes}]> {{")
|
|
859
|
+
if rs["comments"]:
|
|
860
|
+
out.append(" let Comments = [")
|
|
861
|
+
out += [f" {json.dumps(c)}," for c in rs["comments"]]
|
|
862
|
+
out.append(" ];")
|
|
863
|
+
out.append(" let Rules = [")
|
|
864
|
+
rendered = [emitter.rule(r) for r in rs["rules"]]
|
|
865
|
+
rendered += [f"ImmIdx<{i}>" for i in rs.get("legacy", [])]
|
|
866
|
+
out += [f" {t}," for t in rendered]
|
|
867
|
+
out.append(" ];")
|
|
868
|
+
out.append("}")
|
|
869
|
+
out.append("")
|
|
870
|
+
return "\n".join(out).rstrip() + "\n"
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
# ---- CLI ------------------------------------------------------------------
|
|
874
|
+
|
|
875
|
+
def main(argv):
|
|
876
|
+
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
877
|
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
878
|
+
b = sub.add_parser("bootstrap")
|
|
879
|
+
b.add_argument("--td", required=True)
|
|
880
|
+
b.add_argument("--core", required=True)
|
|
881
|
+
b.add_argument("--out", required=True)
|
|
882
|
+
b.add_argument("--title", default="Target")
|
|
883
|
+
g = sub.add_parser("gen")
|
|
884
|
+
g.add_argument("gisel")
|
|
885
|
+
g.add_argument("--out", required=True)
|
|
886
|
+
c = sub.add_parser("check")
|
|
887
|
+
c.add_argument("gisel")
|
|
888
|
+
c.add_argument("--td", required=True)
|
|
889
|
+
args = ap.parse_args(argv)
|
|
890
|
+
if args.cmd == "bootstrap":
|
|
891
|
+
text = bootstrap(args.td, args.core, args.title)
|
|
892
|
+
Path(args.out).write_text(text)
|
|
893
|
+
print(f"{args.out}: {len(text.splitlines())} lines")
|
|
894
|
+
return 0
|
|
895
|
+
if args.cmd == "gen":
|
|
896
|
+
gs = parse_gisel(Path(args.gisel).read_text())
|
|
897
|
+
Path(args.out).write_text(emit_td(gs, args.gisel))
|
|
898
|
+
print(f"{args.out}: {len(gs['rulesets'])} rule sets, "
|
|
899
|
+
f"{sum(len(r['rules']) for r in gs['rulesets'])} rules")
|
|
900
|
+
return 0
|
|
901
|
+
gs = parse_gisel(Path(args.gisel).read_text())
|
|
902
|
+
got = emit_td(gs, args.gisel)
|
|
903
|
+
want = Path(args.td).read_text()
|
|
904
|
+
if got == want:
|
|
905
|
+
print(f"{args.td}: up to date")
|
|
906
|
+
return 0
|
|
907
|
+
import difflib
|
|
908
|
+
sys.stderr.writelines(difflib.unified_diff(
|
|
909
|
+
want.splitlines(True), got.splitlines(True), args.td, "<generated>"))
|
|
910
|
+
print(f"{args.td}: STALE (re-run `gisel_td.py gen`)", file=sys.stderr)
|
|
911
|
+
return 1
|
|
912
|
+
|
|
913
|
+
|
|
914
|
+
if __name__ == "__main__":
|
|
915
|
+
sys.exit(main(sys.argv[1:]))
|