2 # -*- coding: utf-8 -*-
4 # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5 # See https://llvm.org/LICENSE.txt for license information.
6 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
8 # Script for updating SPIR-V dialect by scraping information from SPIR-V
9 # HTML and JSON specs from the Internet.
11 # For example, to define the enum attribute for SPIR-V memory model:
13 # ./gen_spirv_dialect.py --base-td-path /path/to/SPIRVBase.td \
14 # --new-enum MemoryModel
16 # The 'operand_kinds' dict of spirv.core.grammar.json contains all supported
17 # SPIR-V enum classes.
26 SPIRV_HTML_SPEC_URL
= (
27 "https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html"
29 SPIRV_JSON_SPEC_URL
= "https://raw.githubusercontent.com/KhronosGroup/SPIRV-Headers/master/include/spirv/unified1/spirv.core.grammar.json"
31 SPIRV_CL_EXT_HTML_SPEC_URL
= "https://www.khronos.org/registry/SPIR-V/specs/unified1/OpenCL.ExtendedInstructionSet.100.html"
32 SPIRV_CL_EXT_JSON_SPEC_URL
= "https://raw.githubusercontent.com/KhronosGroup/SPIRV-Headers/master/include/spirv/unified1/extinst.opencl.std.100.grammar.json"
34 AUTOGEN_OP_DEF_SEPARATOR
= "\n// -----\n\n"
35 AUTOGEN_ENUM_SECTION_MARKER
= "enum section. Generated from SPIR-V spec; DO NOT MODIFY!"
36 AUTOGEN_OPCODE_SECTION_MARKER
= (
37 "opcode section. Generated from SPIR-V spec; DO NOT MODIFY!"
41 def get_spirv_doc_from_html_spec(url
, settings
):
42 """Extracts instruction documentation from SPIR-V HTML spec.
45 - A dict mapping from instruction opcode to documentation.
48 url
= SPIRV_HTML_SPEC_URL
50 response
= requests
.get(url
)
51 spec
= response
.content
53 from bs4
import BeautifulSoup
55 spirv
= BeautifulSoup(spec
, "html.parser")
59 if settings
.gen_cl_ops
:
60 section_anchor
= spirv
.find("h2", {"id": "_binary_form"})
61 for section
in section_anchor
.parent
.find_all("div", {"class": "sect2"}):
62 for table
in section
.find_all("table"):
63 inst_html
= table
.tbody
.tr
.td
64 opname
= inst_html
.a
["id"]
65 # Ignore the first line, which is just the opname.
66 doc
[opname
] = inst_html
.text
.split("\n", 1)[1].strip()
68 section_anchor
= spirv
.find("h3", {"id": "_instructions_3"})
69 for section
in section_anchor
.parent
.find_all("div", {"class": "sect3"}):
70 for table
in section
.find_all("table"):
71 inst_html
= table
.tbody
.tr
.td
.p
72 opname
= inst_html
.a
["id"]
73 # Ignore the first line, which is just the opname.
74 doc
[opname
] = inst_html
.text
.split("\n", 1)[1].strip()
79 def get_spirv_grammar_from_json_spec(url
):
80 """Extracts operand kind and instruction grammar from SPIR-V JSON spec.
83 - A list containing all operand kinds' grammar
84 - A list containing all instructions' grammar
86 response
= requests
.get(SPIRV_JSON_SPEC_URL
)
87 spec
= response
.content
91 spirv
= json
.loads(spec
)
94 return spirv
["operand_kinds"], spirv
["instructions"]
96 response_ext
= requests
.get(url
)
97 spec_ext
= response_ext
.content
98 spirv_ext
= json
.loads(spec_ext
)
100 return spirv
["operand_kinds"], spirv_ext
["instructions"]
103 def split_list_into_sublists(items
):
104 """Split the list of items into multiple sublists.
106 This is to make sure the string composed from each sublist won't exceed
110 - items: a list of strings
117 chunk_len
+= len(item
) + 2
119 chuncks
.append(chunk
)
121 chunk_len
= len(item
) + 2
125 chuncks
.append(chunk
)
130 def uniquify_enum_cases(lst
):
131 """Prunes duplicate enum cases from the list.
134 - lst: List whose elements are to be uniqued. Assumes each element is a
135 (symbol, value) pair and elements already sorted according to value.
138 - A list with all duplicates removed. The elements are sorted according to
139 value and, for each value, uniqued according to symbol.
141 - A map from deduplicated cases to the uniqued case.
145 duplicated_cases
= {}
147 # First sort according to the value
148 cases
.sort(key
=lambda x
: x
[1])
150 # Then group them according to the value
151 for _
, groups
in itertools
.groupby(cases
, key
=lambda x
: x
[1]):
152 # For each value, sort according to the enumerant symbol.
153 sorted_group
= sorted(groups
, key
=lambda x
: x
[0])
154 # Keep the "smallest" case, which is typically the symbol without extension
155 # suffix. But we have special cases that we want to fix.
156 case
= sorted_group
[0]
157 for i
in range(1, len(sorted_group
)):
158 duplicated_cases
[sorted_group
[i
][0]] = case
[0]
159 if case
[0] == "HlslSemanticGOOGLE":
160 assert len(sorted_group
) == 2, "unexpected new variant for HlslSemantic"
161 case
= sorted_group
[1]
162 duplicated_cases
[sorted_group
[0][0]] = case
[0]
163 uniqued_cases
.append(case
)
165 return uniqued_cases
, duplicated_cases
168 def toposort(dag
, sort_fn
):
169 """Topologically sorts the given dag.
172 - dag: a dict mapping from a node to its incoming nodes.
173 - sort_fn: a function for sorting nodes in the same batch.
176 A list containing topologically sorted nodes.
179 # Returns the next batch of nodes without incoming edges
180 def get_next_batch(dag
):
182 no_prev_nodes
= set(node
for node
, prev
in dag
.items() if not prev
)
183 if not no_prev_nodes
:
185 yield sorted(no_prev_nodes
, key
=sort_fn
)
187 node
: (prev
- no_prev_nodes
)
188 for node
, prev
in dag
.items()
189 if node
not in no_prev_nodes
191 assert not dag
, "found cyclic dependency"
194 for batch
in get_next_batch(dag
):
195 sorted_nodes
.extend(batch
)
200 def toposort_capabilities(all_cases
, capability_mapping
):
201 """Returns topologically sorted capability (symbol, value) pairs.
204 - all_cases: all capability cases (containing symbol, value, and implied
206 - capability_mapping: mapping from duplicated capability symbols to the
207 canonicalized symbol chosen for SPIRVBase.td.
210 A list containing topologically sorted capability (symbol, value) pairs.
214 for case
in all_cases
:
215 # Get the current capability.
216 cur
= case
["enumerant"]
217 name_to_value
[cur
] = case
["value"]
218 # Ignore duplicated symbols.
219 if cur
in capability_mapping
:
222 # Get capabilities implied by the current capability.
223 prev
= case
.get("capabilities", [])
224 uniqued_prev
= set([capability_mapping
.get(c
, c
) for c
in prev
])
225 dag
[cur
] = uniqued_prev
227 sorted_caps
= toposort(dag
, lambda x
: name_to_value
[x
])
228 # Attach the capability's value as the second component of the pair.
229 return [(c
, name_to_value
[c
]) for c
in sorted_caps
]
232 def get_capability_mapping(operand_kinds
):
233 """Returns the capability mapping from duplicated cases to canonicalized ones.
236 - operand_kinds: all operand kinds' grammar spec
239 - A map mapping from duplicated capability symbols to the canonicalized
240 symbol chosen for SPIRVBase.td.
242 # Find the operand kind for capability
244 for kind
in operand_kinds
:
245 if kind
["kind"] == "Capability":
248 kind_cases
= [(case
["enumerant"], case
["value"]) for case
in cap_kind
["enumerants"]]
249 _
, capability_mapping
= uniquify_enum_cases(kind_cases
)
251 return capability_mapping
254 def get_availability_spec(enum_case
, capability_mapping
, for_op
, for_cap
):
255 """Returns the availability specification string for the given enum case.
258 - enum_case: the enum case to generate availability spec for. It may contain
259 'version', 'lastVersion', 'extensions', or 'capabilities'.
260 - capability_mapping: mapping from duplicated capability symbols to the
261 canonicalized symbol chosen for SPIRVBase.td.
262 - for_op: bool value indicating whether this is the availability spec for an
264 - for_cap: bool value indicating whether this is the availability spec for
265 capabilities themselves.
268 - A `let availability = [...];` string if with availability spec or
269 empty string if without availability spec
271 assert not (for_op
and for_cap
), "cannot set both for_op and for_cap"
273 DEFAULT_MIN_VERSION
= "MinVersion<SPIRV_V_1_0>"
274 DEFAULT_MAX_VERSION
= "MaxVersion<SPIRV_V_1_6>"
275 DEFAULT_CAP
= "Capability<[]>"
276 DEFAULT_EXT
= "Extension<[]>"
278 min_version
= enum_case
.get("version", "")
279 if min_version
== "None":
282 min_version
= "MinVersion<SPIRV_V_{}>".format(min_version
.replace(".", "_"))
283 # TODO: delete this once ODS can support dialect-specific content
284 # and we can use omission to mean no requirements.
285 if for_op
and not min_version
:
286 min_version
= DEFAULT_MIN_VERSION
288 max_version
= enum_case
.get("lastVersion", "")
290 max_version
= "MaxVersion<SPIRV_V_{}>".format(max_version
.replace(".", "_"))
291 # TODO: delete this once ODS can support dialect-specific content
292 # and we can use omission to mean no requirements.
293 if for_op
and not max_version
:
294 max_version
= DEFAULT_MAX_VERSION
296 exts
= enum_case
.get("extensions", [])
298 exts
= "Extension<[{}]>".format(", ".join(sorted(set(exts
))))
299 # We need to strip the minimal version requirement if this symbol is
300 # available via an extension, which means *any* SPIR-V version can support
301 # it as long as the extension is provided. The grammar's 'version' field
302 # under such case should be interpreted as this symbol is introduced as
303 # a core symbol since the given version, rather than a minimal version
305 min_version
= DEFAULT_MIN_VERSION
if for_op
else ""
306 # TODO: delete this once ODS can support dialect-specific content
307 # and we can use omission to mean no requirements.
308 if for_op
and not exts
:
311 caps
= enum_case
.get("capabilities", [])
314 canonicalized_caps
= []
316 if c
in capability_mapping
:
317 canonicalized_caps
.append(capability_mapping
[c
])
319 canonicalized_caps
.append(c
)
321 "SPIRV_C_{}".format(c
) for c
in sorted(set(canonicalized_caps
))
324 # If this is generating the availability for capabilities, we need to
325 # put the capability "requirements" in implies field because now
326 # the "capabilities" field in the source grammar means so.
328 implies
= "list<I32EnumAttrCase> implies = [{}];".format(
329 ", ".join(prefixed_caps
)
332 caps
= "Capability<[{}]>".format(", ".join(prefixed_caps
))
334 # TODO: delete this once ODS can support dialect-specific content
335 # and we can use omission to mean no requirements.
336 if for_op
and not caps
:
340 # Compose availability spec if any of the requirements is not empty.
341 # For ops, because we have a default in SPIRV_Op class, omit if the spec
343 if (min_version
or max_version
or caps
or exts
) and not (
345 and min_version
== DEFAULT_MIN_VERSION
346 and max_version
== DEFAULT_MAX_VERSION
347 and caps
== DEFAULT_CAP
348 and exts
== DEFAULT_EXT
350 joined_spec
= ",\n ".join(
351 [e
for e
in [min_version
, max_version
, exts
, caps
] if e
]
353 avail
= "{} availability = [\n {}\n ];".format(
354 "let" if for_op
else "list<Availability>", joined_spec
357 return "{}{}{}".format(implies
, "\n " if implies
and avail
else "", avail
)
360 def gen_operand_kind_enum_attr(operand_kind
, capability_mapping
):
361 """Generates the TableGen EnumAttr definition for the given operand kind.
364 - The operand kind's name
365 - A string containing the TableGen EnumAttr definition
367 if "enumerants" not in operand_kind
:
370 # Returns a symbol for the given case in the given kind. This function
371 # handles Dim specially to avoid having numbers as the start of symbols,
372 # which does not play well with C++ and the MLIR parser.
373 def get_case_symbol(kind_name
, case_name
):
374 if kind_name
== "Dim":
375 if case_name
== "1D" or case_name
== "2D" or case_name
== "3D":
376 return "Dim{}".format(case_name
)
379 kind_name
= operand_kind
["kind"]
380 is_bit_enum
= operand_kind
["category"] == "BitEnum"
381 kind_acronym
= "".join([c
for c
in kind_name
if c
>= "A" and c
<= "Z"])
383 name_to_case_dict
= {}
384 for case
in operand_kind
["enumerants"]:
385 name_to_case_dict
[case
["enumerant"]] = case
387 if kind_name
== "Capability":
388 # Special treatment for capability cases: we need to sort them topologically
389 # because a capability can refer to another via the 'implies' field.
390 kind_cases
= toposort_capabilities(
391 operand_kind
["enumerants"], capability_mapping
395 (case
["enumerant"], case
["value"]) for case
in operand_kind
["enumerants"]
397 kind_cases
, _
= uniquify_enum_cases(kind_cases
)
398 max_len
= max([len(symbol
) for (symbol
, _
) in kind_cases
])
400 # Generate the definition for each enum case
401 case_category
= "I32Bit" if is_bit_enum
else "I32"
403 "def SPIRV_{acronym}_{case_name} {colon:>{offset}} "
404 '{category}EnumAttrCase{suffix}<"{symbol}"{case_value_part}>{avail}'
407 for case_pair
in kind_cases
:
410 value
= int(case_pair
[1], base
=16)
412 value
= int(case_pair
[1])
413 avail
= get_availability_spec(
414 name_to_case_dict
[name
],
417 kind_name
== "Capability",
425 value
= ", {}".format(int(math
.log2(value
)))
428 value
= ", {}".format(value
)
430 case_def
= fmt_str
.format(
431 category
=case_category
,
433 acronym
=kind_acronym
,
435 symbol
=get_case_symbol(kind_name
, name
),
436 case_value_part
=value
,
437 avail
=" {{\n {}\n}}".format(avail
) if avail
else ";",
439 offset
=(max_len
+ 1 - len(name
)),
441 case_defs
.append(case_def
)
442 case_defs
= "\n".join(case_defs
)
444 # Generate the list of enum case names
445 fmt_str
= "SPIRV_{acronym}_{symbol}"
447 fmt_str
.format(acronym
=kind_acronym
, symbol
=case
[0]) for case
in kind_cases
450 # Split them into sublists and concatenate into multiple lines
451 case_names
= split_list_into_sublists(case_names
)
452 case_names
= ["{:6}".format("") + ", ".join(sublist
) for sublist
in case_names
]
453 case_names
= ",\n".join(case_names
)
455 # Generate the enum attribute definition
456 kind_category
= "Bit" if is_bit_enum
else "I32"
457 enum_attr
= """def SPIRV_{name}Attr :
458 SPIRV_{category}EnumAttr<"{name}", "valid SPIR-V {name}", "{snake_name}", [
462 snake_name
=snake_casify(kind_name
),
463 category
=kind_category
,
466 return kind_name
, case_defs
+ "\n\n" + enum_attr
469 def gen_opcode(instructions
):
470 """Generates the TableGen definition to map opname to opcode
473 - A string containing the TableGen SPIRV_OpCode definition
476 max_len
= max([len(inst
["opname"]) for inst
in instructions
])
478 "def SPIRV_OC_{name} {colon:>{offset}} " 'I32EnumAttrCase<"{name}", {value}>;'
483 value
=inst
["opcode"],
485 offset
=(max_len
+ 1 - len(inst
["opname"])),
487 for inst
in instructions
489 opcode_str
= "\n".join(opcode_defs
)
491 decl_fmt_str
= "SPIRV_OC_{name}"
492 opcode_list
= [decl_fmt_str
.format(name
=inst
["opname"]) for inst
in instructions
]
493 opcode_list
= split_list_into_sublists(opcode_list
)
494 opcode_list
= ["{:6}".format("") + ", ".join(sublist
) for sublist
in opcode_list
]
495 opcode_list
= ",\n".join(opcode_list
)
497 "def SPIRV_OpcodeAttr :\n"
498 ' SPIRV_I32EnumAttr<"{name}", "valid SPIR-V instructions", '
501 " ]>;".format(name
="Opcode", lst
=opcode_list
)
503 return opcode_str
+ "\n\n" + enum_attr
506 def map_cap_to_opnames(instructions
):
507 """Maps capabilities to instructions enabled by those capabilities
510 - instructions: a list containing a subset of SPIR-V instructions' grammar
512 - A map with keys representing capabilities and values of lists of
513 instructions enabled by the corresponding key
517 for inst
in instructions
:
518 caps
= inst
["capabilities"] if "capabilities" in inst
else ["0_core_0"]
520 if cap
not in cap_to_inst
:
521 cap_to_inst
[cap
] = []
522 cap_to_inst
[cap
].append(inst
["opname"])
527 def gen_instr_coverage_report(path
, instructions
):
528 """Dumps to standard output a YAML report of current instruction coverage
531 - path: the path to SPIRBase.td
532 - instructions: a list containing all SPIR-V instructions' grammar
534 with
open(path
, "r") as f
:
537 content
= content
.split(AUTOGEN_OPCODE_SECTION_MARKER
)
539 existing_opcodes
= [k
[11:] for k
in re
.findall("def SPIRV_OC_\w+", content
[1])]
540 existing_instructions
= list(
541 filter(lambda inst
: (inst
["opname"] in existing_opcodes
), instructions
)
544 instructions_opnames
= [inst
["opname"] for inst
in instructions
]
546 remaining_opcodes
= list(set(instructions_opnames
) - set(existing_opcodes
))
547 remaining_instructions
= list(
548 filter(lambda inst
: (inst
["opname"] in remaining_opcodes
), instructions
)
551 rem_cap_to_instr
= map_cap_to_opnames(remaining_instructions
)
552 ex_cap_to_instr
= map_cap_to_opnames(existing_instructions
)
556 # Calculate coverage for each capability
557 for cap
in rem_cap_to_instr
:
558 if cap
not in ex_cap_to_instr
:
559 rem_cap_to_cov
[cap
] = 0.0
561 rem_cap_to_cov
[cap
] = len(ex_cap_to_instr
[cap
]) / (
562 len(ex_cap_to_instr
[cap
]) + len(rem_cap_to_instr
[cap
])
567 # Merge the 3 maps into one report
568 for cap
in rem_cap_to_instr
:
570 report
[cap
]["Supported Instructions"] = (
571 ex_cap_to_instr
[cap
] if cap
in ex_cap_to_instr
else []
573 report
[cap
]["Unsupported Instructions"] = rem_cap_to_instr
[cap
]
574 report
[cap
]["Coverage"] = "{}%".format(int(rem_cap_to_cov
[cap
] * 100))
576 print(yaml
.dump(report
))
579 def update_td_opcodes(path
, instructions
, filter_list
):
580 """Updates SPIRBase.td with new generated opcode cases.
583 - path: the path to SPIRBase.td
584 - instructions: a list containing all SPIR-V instructions' grammar
585 - filter_list: a list containing new opnames to add
588 with
open(path
, "r") as f
:
591 content
= content
.split(AUTOGEN_OPCODE_SECTION_MARKER
)
592 assert len(content
) == 3
594 # Extend opcode list with existing list
595 prefix
= "def SPIRV_OC_"
597 k
[len(prefix
) :] for k
in re
.findall(prefix
+ "\w+", content
[1])
599 filter_list
.extend(existing_opcodes
)
600 filter_list
= list(set(filter_list
))
602 # Generate the opcode for all instructions in SPIR-V
603 filter_instrs
= list(
604 filter(lambda inst
: (inst
["opname"] in filter_list
), instructions
)
606 # Sort instruction based on opcode
607 filter_instrs
.sort(key
=lambda inst
: inst
["opcode"])
608 opcode
= gen_opcode(filter_instrs
)
610 # Substitute the opcode
613 + AUTOGEN_OPCODE_SECTION_MARKER
617 + AUTOGEN_OPCODE_SECTION_MARKER
621 with
open(path
, "w") as f
:
625 def update_td_enum_attrs(path
, operand_kinds
, filter_list
):
626 """Updates SPIRBase.td with new generated enum definitions.
629 - path: the path to SPIRBase.td
630 - operand_kinds: a list containing all operand kinds' grammar
631 - filter_list: a list containing new enums to add
633 with
open(path
, "r") as f
:
636 content
= content
.split(AUTOGEN_ENUM_SECTION_MARKER
)
637 assert len(content
) == 3
639 # Extend filter list with existing enum definitions
640 existing_kinds
= [k
[8:-4] for k
in re
.findall("def SPIRV_\w+Attr", content
[1])]
641 filter_list
.extend(existing_kinds
)
643 capability_mapping
= get_capability_mapping(operand_kinds
)
645 # Generate definitions for all enums in filter list
647 gen_operand_kind_enum_attr(kind
, capability_mapping
)
648 for kind
in operand_kinds
649 if kind
["kind"] in filter_list
651 # Sort alphabetically according to enum name
652 defs
.sort(key
=lambda enum
: enum
[0])
653 # Only keep the definitions from now on
654 # Put Capability's definition at the very beginning because capability cases
655 # will be referenced later
656 defs
= [enum
[1] for enum
in defs
if enum
[0] == "Capability"] + [
657 enum
[1] for enum
in defs
if enum
[0] != "Capability"
660 # Substitute the old section
663 + AUTOGEN_ENUM_SECTION_MARKER
667 + AUTOGEN_ENUM_SECTION_MARKER
671 with
open(path
, "w") as f
:
675 def snake_casify(name
):
676 """Turns the given name to follow snake_case convention."""
677 return re
.sub(r
"(?<!^)(?=[A-Z])", "_", name
).lower()
680 def map_spec_operand_to_ods_argument(operand
):
681 """Maps an operand in SPIR-V JSON spec to an op argument in ODS.
684 - A dict containing the operand's kind, quantifier, and name
687 - A string containing both the type and name for the argument
689 kind
= operand
["kind"]
690 quantifier
= operand
.get("quantifier", "")
692 # These instruction "operands" are for encoding the results; they should
693 # not be handled here.
694 assert kind
!= "IdResultType", 'unexpected to handle "IdResultType" kind'
695 assert kind
!= "IdResult", 'unexpected to handle "IdResult" kind'
699 arg_type
= "SPIRV_Type"
700 elif quantifier
== "?":
701 arg_type
= "Optional<SPIRV_Type>"
703 arg_type
= "Variadic<SPIRV_Type>"
704 elif kind
== "IdMemorySemantics" or kind
== "IdScope":
705 # TODO: Need to further constrain 'IdMemorySemantics'
706 # and 'IdScope' given that they should be generated from OpConstant.
707 assert quantifier
== "", (
708 "unexpected to have optional/variadic memory " "semantics or scope <id>"
710 arg_type
= "SPIRV_" + kind
[2:] + "Attr"
711 elif kind
== "LiteralInteger":
714 elif quantifier
== "?":
715 arg_type
= "OptionalAttr<I32Attr>"
717 arg_type
= "OptionalAttr<I32ArrayAttr>"
719 kind
== "LiteralString"
720 or kind
== "LiteralContextDependentNumber"
721 or kind
== "LiteralExtInstInteger"
722 or kind
== "LiteralSpecConstantOpInteger"
723 or kind
== "PairLiteralIntegerIdRef"
724 or kind
== "PairIdRefLiteralInteger"
725 or kind
== "PairIdRefIdRef"
727 assert False, '"{}" kind unimplemented'.format(kind
)
729 # The rest are all enum operands that we represent with op attributes.
730 assert quantifier
!= "*", "unexpected to have variadic enum attribute"
731 arg_type
= "SPIRV_{}Attr".format(kind
)
732 if quantifier
== "?":
733 arg_type
= "OptionalAttr<{}>".format(arg_type
)
735 name
= operand
.get("name", "")
736 name
= snake_casify(name
) if name
else kind
.lower()
738 return "{}:${}".format(arg_type
, name
)
741 def get_description(text
, appendix
):
742 """Generates the description for the given SPIR-V instruction.
745 - text: Textual description of the operation as string.
746 - appendix: Additional contents to attach in description as string,
747 includking IR examples, and others.
750 - A string that corresponds to the description of the Tablegen op.
752 fmt_str
= "{text}\n\n <!-- End of AutoGen section -->\n{appendix}\n "
753 return fmt_str
.format(text
=text
, appendix
=appendix
)
756 def get_op_definition(
757 instruction
, opname
, doc
, existing_info
, capability_mapping
, settings
759 """Generates the TableGen op definition for the given SPIR-V instruction.
762 - instruction: the instruction's SPIR-V JSON grammar
763 - doc: the instruction's SPIR-V HTML doc
764 - existing_info: a dict containing potential manually specified sections for
766 - capability_mapping: mapping from duplicated capability symbols to the
767 canonicalized symbol chosen for SPIRVBase.td
770 - A string containing the TableGen op definition
772 if settings
.gen_cl_ops
:
774 "def SPIRV_{opname}Op : "
775 'SPIRV_{inst_category}<"{opname_src}", {opcode}, <<Insert result type>> > '
776 "{{\n let summary = {summary};\n\n let description = "
777 "[{{\n{description}}}];{availability}\n"
781 "def SPIRV_{vendor_name}{opname_src}Op : "
782 'SPIRV_{inst_category}<"{opname_src}"{category_args}, [{traits}]> '
783 "{{\n let summary = {summary};\n\n let description = "
784 "[{{\n{description}}}];{availability}\n"
788 inst_category
= existing_info
.get("inst_category", "Op")
789 if inst_category
== "Op":
791 "\n let arguments = (ins{args});\n\n" " let results = (outs{results});\n"
793 elif inst_category
.endswith("VendorOp"):
794 vendor_name
= inst_category
.split("VendorOp")[0].upper()
795 assert len(vendor_name
) != 0, "Invalid instruction category"
797 fmt_str
+= "{extras}" "}}\n"
799 opname_src
= instruction
["opname"]
800 if opname
.startswith("Op"):
801 opname_src
= opname_src
[2:]
802 if len(vendor_name
) > 0:
803 assert opname_src
.endswith(
805 ), "op name does not match the instruction category"
806 opname_src
= opname_src
[: -len(vendor_name
)]
808 category_args
= existing_info
.get("category_args", "")
811 summary
, text
= doc
.split("\n", 1)
815 wrapper
= textwrap
.TextWrapper(
816 width
=76, initial_indent
=" ", subsequent_indent
=" "
819 # Format summary. If the summary can fit in the same line, we print it out
820 # as a "-quoted string; otherwise, wrap the lines using "[{...}]".
821 summary
= summary
.strip()
822 if len(summary
) + len(' let summary = "";') <= 80:
823 summary
= '"{}"'.format(summary
)
825 summary
= "[{{\n{}\n }}]".format(wrapper
.fill(summary
))
828 text
= text
.split("\n")
829 text
= [wrapper
.fill(line
) for line
in text
if line
]
830 text
= "\n\n".join(text
)
832 operands
= instruction
.get("operands", [])
835 avail
= get_availability_spec(instruction
, capability_mapping
, True, False)
837 avail
= "\n\n {0}".format(avail
)
841 if len(operands
) > 0 and operands
[0]["kind"] == "IdResultType":
842 results
= "\n SPIRV_Type:$result\n "
843 operands
= operands
[1:]
844 if "results" in existing_info
:
845 results
= existing_info
["results"]
847 # Ignore the operand standing for the result <id>
848 if len(operands
) > 0 and operands
[0]["kind"] == "IdResult":
849 operands
= operands
[1:]
852 arguments
= existing_info
.get("arguments", None)
853 if arguments
is None:
854 arguments
= [map_spec_operand_to_ods_argument(o
) for o
in operands
]
855 arguments
= ",\n ".join(arguments
)
857 # Prepend and append whitespace for formatting
858 arguments
= "\n {}\n ".format(arguments
)
860 description
= existing_info
.get("description", None)
861 if description
is None:
871 description
= get_description(text
, assembly
)
873 return fmt_str
.format(
875 opname_src
=opname_src
,
876 opcode
=instruction
["opcode"],
877 category_args
=category_args
,
878 inst_category
=inst_category
,
879 vendor_name
=vendor_name
,
880 traits
=existing_info
.get("traits", ""),
882 description
=description
,
886 extras
=existing_info
.get("extras", ""),
890 def get_string_between(base
, start
, end
):
891 """Extracts a substring with a specified start and end from a string.
894 - base: string to extract from.
895 - start: string to use as the start of the substring.
896 - end: string to use as the end of the substring.
899 - The substring if found
900 - The part of the base after end of the substring. Is the base string itself
901 if the substring wasnt found.
903 split
= base
.split(start
, 1)
905 rest
= split
[1].split(end
, 1)
906 assert len(rest
) == 2, (
907 'cannot find end "{end}" while extracting substring '
908 "starting with {start}".format(start
=start
, end
=end
)
910 return rest
[0].rstrip(end
), rest
[1]
914 def get_string_between_nested(base
, start
, end
):
915 """Extracts a substring with a nested start and end from a string.
918 - base: string to extract from.
919 - start: string to use as the start of the substring.
920 - end: string to use as the end of the substring.
923 - The substring if found
924 - The part of the base after end of the substring. Is the base string itself
925 if the substring wasn't found.
927 split
= base
.split(start
, 1)
929 # Handle nesting delimiters
933 while unmatched_start
> 0 and index
< len(rest
):
934 if rest
[index
:].startswith(end
):
936 if unmatched_start
== 0:
939 elif rest
[index
:].startswith(start
):
945 assert index
< len(rest
), (
946 'cannot find end "{end}" while extracting substring '
947 'starting with "{start}"'.format(start
=start
, end
=end
)
949 return rest
[:index
], rest
[index
+ len(end
) :]
953 def extract_td_op_info(op_def
):
954 """Extracts potentially manually specified sections in op's definition.
956 Arguments: - A string containing the op's TableGen definition
959 - A dict containing potential manually specified sections
962 opname
= [o
[8:-2] for o
in re
.findall("def SPIRV_\w+Op", op_def
)]
963 assert len(opname
) == 1, "more than one ops in the same section!"
966 # Get instruction category
967 inst_category
= [o
[4:] for o
in re
.findall("SPIRV_\w+Op", op_def
.split(":", 1)[1])]
968 assert len(inst_category
) <= 1, "more than one ops in the same section!"
969 inst_category
= inst_category
[0] if len(inst_category
) == 1 else "Op"
972 op_tmpl_params
, _
= get_string_between_nested(op_def
, "<", ">")
973 opstringname
, rest
= get_string_between(op_tmpl_params
, '"', '"')
974 category_args
= rest
.split("[", 1)[0]
977 traits
, _
= get_string_between_nested(rest
, "[", "]")
980 description
, rest
= get_string_between(op_def
, "let description = [{\n", "}];\n")
983 args
, rest
= get_string_between(rest
, " let arguments = (ins", ");\n")
986 results
, rest
= get_string_between(rest
, " let results = (outs", ");\n")
988 extras
= rest
.strip(" }\n")
990 extras
= "\n {}\n".format(extras
)
993 # Prefix with 'Op' to make it consistent with SPIR-V spec
994 "opname": "Op{}".format(opname
),
995 "inst_category": inst_category
,
996 "category_args": category_args
,
998 "description": description
,
1005 def update_td_op_definitions(
1006 path
, instructions
, docs
, filter_list
, inst_category
, capability_mapping
, settings
1008 """Updates SPIRVOps.td with newly generated op definition.
1011 - path: path to SPIRVOps.td
1012 - instructions: SPIR-V JSON grammar for all instructions
1013 - docs: SPIR-V HTML doc for all instructions
1014 - filter_list: a list containing new opnames to include
1015 - capability_mapping: mapping from duplicated capability symbols to the
1016 canonicalized symbol chosen for SPIRVBase.td.
1019 - A string containing all the TableGen op definitions
1021 with
open(path
, "r") as f
:
1024 # Split the file into chunks, each containing one op.
1025 ops
= content
.split(AUTOGEN_OP_DEF_SEPARATOR
)
1030 # For each existing op, extract the manually-written sections out to retain
1031 # them when re-generating the ops. Also append the existing ops to filter
1033 name_op_map
= {} # Map from opname to its existing ODS definition
1036 info_dict
= extract_td_op_info(op
)
1037 opname
= info_dict
["opname"]
1038 name_op_map
[opname
] = op
1039 op_info_dict
[opname
] = info_dict
1040 filter_list
.append(opname
)
1041 filter_list
= sorted(list(set(filter_list
)))
1045 if settings
.gen_cl_ops
:
1046 fix_opname
= lambda src
: src
.replace("CL", "").lower()
1048 fix_opname
= lambda src
: src
1050 for opname
in filter_list
:
1051 # Find the grammar spec for this op
1053 fixed_opname
= fix_opname(opname
)
1055 inst
for inst
in instructions
if inst
["opname"] == fixed_opname
1063 op_info_dict
.get(opname
, {"inst_category": inst_category
}),
1068 except StopIteration:
1069 # This is an op added by us; use the existing ODS definition.
1070 op_defs
.append(name_op_map
[opname
])
1072 # Substitute the old op definitions
1073 op_defs
= [header
] + op_defs
+ [footer
]
1074 content
= AUTOGEN_OP_DEF_SEPARATOR
.join(op_defs
)
1076 with
open(path
, "w") as f
:
1080 if __name__
== "__main__":
1083 cli_parser
= argparse
.ArgumentParser(
1084 description
="Update SPIR-V dialect definitions using SPIR-V spec"
1087 cli_parser
.add_argument(
1089 dest
="base_td_path",
1092 help="Path to SPIRVBase.td",
1094 cli_parser
.add_argument(
1099 help="Path to SPIRVOps.td",
1102 cli_parser
.add_argument(
1107 help="SPIR-V enum to be added to SPIRVBase.td",
1109 cli_parser
.add_argument(
1115 help="update SPIR-V opcodes in SPIRVBase.td",
1117 cli_parser
.add_argument(
1123 help="SPIR-V instruction to be added to ops file",
1125 cli_parser
.add_argument(
1127 dest
="inst_category",
1130 help="SPIR-V instruction category used for choosing "
1131 "the TableGen base class to define this op",
1133 cli_parser
.add_argument(
1136 help="Generate OpenCL Extended Instruction Set op",
1137 action
="store_true",
1139 cli_parser
.set_defaults(gen_cl_ops
=False)
1140 cli_parser
.add_argument(
1141 "--gen-inst-coverage", dest
="gen_inst_coverage", action
="store_true"
1143 cli_parser
.set_defaults(gen_inst_coverage
=False)
1145 args
= cli_parser
.parse_args()
1148 ext_html_url
= SPIRV_CL_EXT_HTML_SPEC_URL
1149 ext_json_url
= SPIRV_CL_EXT_JSON_SPEC_URL
1154 operand_kinds
, instructions
= get_spirv_grammar_from_json_spec(ext_json_url
)
1156 # Define new enum attr
1157 if args
.new_enum
is not None:
1158 assert args
.base_td_path
is not None
1159 filter_list
= [args
.new_enum
] if args
.new_enum
else []
1160 update_td_enum_attrs(args
.base_td_path
, operand_kinds
, filter_list
)
1163 if args
.new_opcodes
is not None:
1164 assert args
.base_td_path
is not None
1165 update_td_opcodes(args
.base_td_path
, instructions
, args
.new_opcodes
)
1168 if args
.new_inst
is not None:
1169 assert args
.op_td_path
is not None
1170 docs
= get_spirv_doc_from_html_spec(ext_html_url
, args
)
1171 capability_mapping
= get_capability_mapping(operand_kinds
)
1172 update_td_op_definitions(
1181 print("Done. Note that this script just generates a template; ", end
="")
1182 print("please read the spec and update traits, arguments, and ", end
="")
1183 print("results accordingly.")
1185 if args
.gen_inst_coverage
:
1186 gen_instr_coverage_report(args
.base_td_path
, instructions
)