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/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:]))