| #!/usr/bin/env python |
| # Copyright (c) 2016 Google Inc. |
| |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| """Generates various info tables from SPIR-V JSON grammar.""" |
| |
| import errno |
| import json |
| import os.path |
| import re |
| |
| # Prefix for all C variables generated by this script. |
| PYGEN_VARIABLE_PREFIX = 'pygen_variable' |
| |
| # Extensions to recognize, but which don't necessarily come from the SPIR-V |
| # core or KHR grammar files. Get this list from the SPIR-V registry web page. |
| # NOTE: Only put things on this list if it is not in those grammar files. |
| EXTENSIONS_FROM_SPIRV_REGISTRY_AND_NOT_FROM_GRAMMARS = """ |
| SPV_AMD_gcn_shader |
| SPV_AMD_gpu_shader_half_float |
| SPV_AMD_gpu_shader_int16 |
| SPV_AMD_shader_trinary_minmax |
| SPV_KHR_non_semantic_info |
| """ |
| |
| OUTPUT_LANGUAGE = 'c' |
| |
| def make_path_to_file(f): |
| """Makes all ancestor directories to the given file, if they don't yet |
| exist. |
| |
| Arguments: |
| f: The file whose ancestor directories are to be created. |
| """ |
| dir = os.path.dirname(os.path.abspath(f)) |
| try: |
| os.makedirs(dir) |
| except OSError as e: |
| if e.errno == errno.EEXIST and os.path.isdir(dir): |
| pass |
| else: |
| raise |
| |
| |
| def convert_min_required_version(version): |
| """Converts the minimal required SPIR-V version encoded in the grammar to |
| the symbol in SPIRV-Tools.""" |
| if version is None: |
| return 'SPV_SPIRV_VERSION_WORD(1, 0)' |
| if version == 'None': |
| return '0xffffffffu' |
| return 'SPV_SPIRV_VERSION_WORD({})'.format(version.replace('.', ',')) |
| |
| |
| def convert_max_required_version(version): |
| """Converts the maximum required SPIR-V version encoded in the grammar to |
| the symbol in SPIRV-Tools.""" |
| if version is None: |
| return '0xffffffffu' |
| return 'SPV_SPIRV_VERSION_WORD({})'.format(version.replace('.', ',')) |
| |
| |
| def compose_capability_list(caps): |
| """Returns a string containing a braced list of capabilities as enums. |
| |
| Arguments: |
| - caps: a sequence of capability names |
| |
| Returns: |
| a string containing the braced list of SpvCapability* or spv::Capability:: enums named by caps. |
| """ |
| base_string = 'SpvCapability' |
| global OUTPUT_LANGUAGE |
| if OUTPUT_LANGUAGE == 'c++': |
| base_string = 'spv::Capability::' |
| |
| return '{' + ', '.join([(base_string + '{}').format(c) for c in caps]) + '}' |
| |
| |
| def get_capability_array_name(caps): |
| """Returns the name of the array containing all the given capabilities. |
| |
| Args: |
| - caps: a sequence of capability names |
| """ |
| if not caps: |
| return 'nullptr' |
| return '{}_caps_{}'.format(PYGEN_VARIABLE_PREFIX, ''.join(caps)) |
| |
| |
| def generate_capability_arrays(caps): |
| """Returns the arrays of capabilities. |
| |
| Arguments: |
| - caps: a sequence of sequence of capability names |
| """ |
| caps = sorted(set([tuple(c) for c in caps if c])) |
| cap_str = 'SpvCapability' |
| global OUTPUT_LANGUAGE |
| if OUTPUT_LANGUAGE == 'c++': |
| cap_str = 'spv::Capability' |
| arrays = [ |
| 'static const ' + cap_str + ' {}[] = {};'.format( |
| get_capability_array_name(c), compose_capability_list(c)) |
| for c in caps] |
| return '\n'.join(arrays) |
| |
| |
| def compose_extension_list(exts): |
| """Returns a string containing a braced list of extensions as enums. |
| |
| Arguments: |
| - exts: a sequence of extension names |
| |
| Returns: |
| a string containing the braced list of extensions named by exts. |
| """ |
| return '{' + ', '.join( |
| ['spvtools::Extension::k{}'.format(e) for e in exts]) + '}' |
| |
| |
| def get_extension_array_name(extensions): |
| """Returns the name of the array containing all the given extensions. |
| |
| Args: |
| - extensions: a sequence of extension names |
| """ |
| if not extensions: |
| return 'nullptr' |
| else: |
| return '{}_exts_{}'.format( |
| PYGEN_VARIABLE_PREFIX, ''.join(extensions)) |
| |
| |
| def generate_extension_arrays(extensions): |
| """Returns the arrays of extensions. |
| |
| Arguments: |
| - caps: a sequence of sequence of extension names |
| """ |
| extensions = sorted(set([tuple(e) for e in extensions if e])) |
| arrays = [ |
| 'static const spvtools::Extension {}[] = {};'.format( |
| get_extension_array_name(e), compose_extension_list(e)) |
| for e in extensions] |
| return '\n'.join(arrays) |
| |
| |
| def convert_operand_kind(operand_tuple): |
| """Returns the corresponding operand type used in spirv-tools for the given |
| operand kind and quantifier used in the JSON grammar. |
| |
| Arguments: |
| - operand_tuple: a tuple of two elements: |
| - operand kind: used in the JSON grammar |
| - quantifier: '', '?', or '*' |
| |
| Returns: |
| a string of the enumerant name in spv_operand_type_t |
| """ |
| kind, quantifier = operand_tuple |
| # The following cases are where we differ between the JSON grammar and |
| # spirv-tools. |
| if kind == 'IdResultType': |
| kind = 'TypeId' |
| elif kind == 'IdResult': |
| kind = 'ResultId' |
| elif kind == 'IdMemorySemantics' or kind == 'MemorySemantics': |
| kind = 'MemorySemanticsId' |
| elif kind == 'IdScope' or kind == 'Scope': |
| kind = 'ScopeId' |
| elif kind == 'IdRef': |
| kind = 'Id' |
| |
| elif kind == 'ImageOperands': |
| kind = 'Image' |
| elif kind == 'Dim': |
| kind = 'Dimensionality' |
| elif kind == 'ImageFormat': |
| kind = 'SamplerImageFormat' |
| elif kind == 'KernelEnqueueFlags': |
| kind = 'KernelEnqFlags' |
| |
| elif kind == 'LiteralExtInstInteger': |
| kind = 'ExtensionInstructionNumber' |
| elif kind == 'LiteralSpecConstantOpInteger': |
| kind = 'SpecConstantOpNumber' |
| elif kind == 'LiteralContextDependentNumber': |
| kind = 'TypedLiteralNumber' |
| |
| elif kind == 'PairLiteralIntegerIdRef': |
| kind = 'LiteralIntegerId' |
| elif kind == 'PairIdRefLiteralInteger': |
| kind = 'IdLiteralInteger' |
| elif kind == 'PairIdRefIdRef': # Used by OpPhi in the grammar |
| kind = 'Id' |
| |
| if kind == 'FPRoundingMode': |
| kind = 'FpRoundingMode' |
| elif kind == 'FPFastMathMode': |
| kind = 'FpFastMathMode' |
| |
| if quantifier == '?': |
| kind = 'Optional{}'.format(kind) |
| elif quantifier == '*': |
| kind = 'Variable{}'.format(kind) |
| |
| return 'SPV_OPERAND_TYPE_{}'.format( |
| re.sub(r'([a-z])([A-Z])', r'\1_\2', kind).upper()) |
| |
| |
| class InstInitializer(object): |
| """Instances holds a SPIR-V instruction suitable for printing as the |
| initializer for spv_opcode_desc_t.""" |
| |
| def __init__(self, opname, caps, exts, operands, version, lastVersion): |
| """Initialization. |
| |
| Arguments: |
| - opname: opcode name (with the 'Op' prefix) |
| - caps: a sequence of capability names required by this opcode |
| - exts: a sequence of names of extensions enabling this enumerant |
| - operands: a sequence of (operand-kind, operand-quantifier) tuples |
| - version: minimal SPIR-V version required for this opcode |
| - lastVersion: last version of SPIR-V that includes this opcode |
| """ |
| |
| assert opname.startswith('Op') |
| self.opname = opname[2:] # Remove the "Op" prefix. |
| self.num_caps = len(caps) |
| self.caps_mask = get_capability_array_name(caps) |
| self.num_exts = len(exts) |
| self.exts = get_extension_array_name(exts) |
| self.operands = [convert_operand_kind(o) for o in operands] |
| |
| self.fix_syntax() |
| |
| operands = [o[0] for o in operands] |
| self.ref_type_id = 'IdResultType' in operands |
| self.def_result_id = 'IdResult' in operands |
| |
| self.version = convert_min_required_version(version) |
| self.lastVersion = convert_max_required_version(lastVersion) |
| |
| def fix_syntax(self): |
| """Fix an instruction's syntax, adjusting for differences between the |
| officially released grammar and how SPIRV-Tools uses the grammar. |
| |
| Fixes: |
| - ExtInst should not end with SPV_OPERAND_VARIABLE_ID. |
| https://github.com/KhronosGroup/SPIRV-Tools/issues/233 |
| """ |
| if (self.opname == 'ExtInst' |
| and self.operands[-1] == 'SPV_OPERAND_TYPE_VARIABLE_ID'): |
| self.operands.pop() |
| |
| def __str__(self): |
| global OUTPUT_LANGUAGE |
| base_str = 'SpvOp' |
| if OUTPUT_LANGUAGE == 'c++': |
| base_str = 'spv::Op::Op' |
| |
| template = ['{{"{opname}"', base_str + '{opname}', |
| '{num_caps}', '{caps_mask}', |
| '{num_operands}', '{{{operands}}}', |
| '{def_result_id}', '{ref_type_id}', |
| '{num_exts}', '{exts}', |
| '{min_version}', '{max_version}}}'] |
| return ', '.join(template).format( |
| opname=self.opname, |
| num_caps=self.num_caps, |
| caps_mask=self.caps_mask, |
| num_operands=len(self.operands), |
| operands=', '.join(self.operands), |
| def_result_id=(1 if self.def_result_id else 0), |
| ref_type_id=(1 if self.ref_type_id else 0), |
| num_exts=self.num_exts, |
| exts=self.exts, |
| min_version=self.version, |
| max_version=self.lastVersion) |
| |
| |
| class ExtInstInitializer(object): |
| """Instances holds a SPIR-V extended instruction suitable for printing as |
| the initializer for spv_ext_inst_desc_t.""" |
| |
| def __init__(self, opname, opcode, caps, operands): |
| """Initialization. |
| |
| Arguments: |
| - opname: opcode name |
| - opcode: enumerant value for this opcode |
| - caps: a sequence of capability names required by this opcode |
| - operands: a sequence of (operand-kind, operand-quantifier) tuples |
| """ |
| self.opname = opname |
| self.opcode = opcode |
| self.num_caps = len(caps) |
| self.caps_mask = get_capability_array_name(caps) |
| self.operands = [convert_operand_kind(o) for o in operands] |
| self.operands.append('SPV_OPERAND_TYPE_NONE') |
| |
| def __str__(self): |
| template = ['{{"{opname}"', '{opcode}', '{num_caps}', '{caps_mask}', |
| '{{{operands}}}}}'] |
| return ', '.join(template).format( |
| opname=self.opname, |
| opcode=self.opcode, |
| num_caps=self.num_caps, |
| caps_mask=self.caps_mask, |
| operands=', '.join(self.operands)) |
| |
| |
| def generate_instruction(inst, is_ext_inst): |
| """Returns the C initializer for the given SPIR-V instruction. |
| |
| Arguments: |
| - inst: a dict containing information about a SPIR-V instruction |
| - is_ext_inst: a bool indicating whether |inst| is an extended |
| instruction. |
| |
| Returns: |
| a string containing the C initializer for spv_opcode_desc_t or |
| spv_ext_inst_desc_t |
| """ |
| opname = inst.get('opname') |
| opcode = inst.get('opcode') |
| caps = inst.get('capabilities', []) |
| exts = inst.get('extensions', []) |
| operands = inst.get('operands', {}) |
| operands = [(o['kind'], o.get('quantifier', '')) for o in operands] |
| min_version = inst.get('version', None) |
| max_version = inst.get('lastVersion', None) |
| |
| assert opname is not None |
| |
| if is_ext_inst: |
| return str(ExtInstInitializer(opname, opcode, caps, operands)) |
| else: |
| return str(InstInitializer(opname, caps, exts, operands, min_version, max_version)) |
| |
| |
| def generate_instruction_table(inst_table): |
| """Returns the info table containing all SPIR-V instructions, sorted by |
| opcode, and prefixed by capability arrays. |
| |
| Note: |
| - the built-in sorted() function is guaranteed to be stable. |
| https://docs.python.org/3/library/functions.html#sorted |
| |
| Arguments: |
| - inst_table: a list containing all SPIR-V instructions. |
| """ |
| inst_table = sorted(inst_table, key=lambda k: (k['opcode'], k['opname'])) |
| |
| caps_arrays = generate_capability_arrays( |
| [inst.get('capabilities', []) for inst in inst_table]) |
| exts_arrays = generate_extension_arrays( |
| [inst.get('extensions', []) for inst in inst_table]) |
| |
| insts = [generate_instruction(inst, False) for inst in inst_table] |
| insts = ['static const spv_opcode_desc_t kOpcodeTableEntries[] = {{\n' |
| ' {}\n}};'.format(',\n '.join(insts))] |
| |
| return '{}\n\n{}\n\n{}'.format(caps_arrays, exts_arrays, '\n'.join(insts)) |
| |
| |
| def generate_extended_instruction_table(json_grammar, set_name, operand_kind_prefix=""): |
| """Returns the info table containing all SPIR-V extended instructions, |
| sorted by opcode, and prefixed by capability arrays. |
| |
| Arguments: |
| - inst_table: a list containing all SPIR-V instructions. |
| - set_name: the name of the extended instruction set. |
| - operand_kind_prefix: the prefix, if any, to add to the front |
| of operand kind names. |
| """ |
| if operand_kind_prefix: |
| prefix_operand_kind_names(operand_kind_prefix, json_grammar) |
| |
| inst_table = json_grammar["instructions"] |
| set_name = set_name.replace(".", "_") |
| |
| inst_table = sorted(inst_table, key=lambda k: k['opcode']) |
| caps = [inst.get('capabilities', []) for inst in inst_table] |
| caps_arrays = generate_capability_arrays(caps) |
| insts = [generate_instruction(inst, True) for inst in inst_table] |
| insts = ['static const spv_ext_inst_desc_t {}_entries[] = {{\n' |
| ' {}\n}};'.format(set_name, ',\n '.join(insts))] |
| |
| return '{}\n\n{}'.format(caps_arrays, '\n'.join(insts)) |
| |
| |
| class EnumerantInitializer(object): |
| """Prints an enumerant as the initializer for spv_operand_desc_t.""" |
| |
| def __init__(self, enumerant, value, caps, exts, parameters, version, lastVersion): |
| """Initialization. |
| |
| Arguments: |
| - enumerant: enumerant name |
| - value: enumerant value |
| - caps: a sequence of capability names required by this enumerant |
| - exts: a sequence of names of extensions enabling this enumerant |
| - parameters: a sequence of (operand-kind, operand-quantifier) tuples |
| - version: minimal SPIR-V version required for this opcode |
| - lastVersion: last SPIR-V version this opode appears |
| """ |
| self.enumerant = enumerant |
| self.value = value |
| self.num_caps = len(caps) |
| self.caps = get_capability_array_name(caps) |
| self.num_exts = len(exts) |
| self.exts = get_extension_array_name(exts) |
| self.parameters = [convert_operand_kind(p) for p in parameters] |
| self.version = convert_min_required_version(version) |
| self.lastVersion = convert_max_required_version(lastVersion) |
| |
| def __str__(self): |
| template = ['{{"{enumerant}"', '{value}', '{num_caps}', |
| '{caps}', '{num_exts}', '{exts}', |
| '{{{parameters}}}', '{min_version}', |
| '{max_version}}}'] |
| return ', '.join(template).format( |
| enumerant=self.enumerant, |
| value=self.value, |
| num_caps=self.num_caps, |
| caps=self.caps, |
| num_exts=self.num_exts, |
| exts=self.exts, |
| parameters=', '.join(self.parameters), |
| min_version=self.version, |
| max_version=self.lastVersion) |
| |
| |
| def generate_enum_operand_kind_entry(entry, extension_map): |
| """Returns the C initializer for the given operand enum entry. |
| |
| Arguments: |
| - entry: a dict containing information about an enum entry |
| - extension_map: a dict mapping enum value to list of extensions |
| |
| Returns: |
| a string containing the C initializer for spv_operand_desc_t |
| """ |
| enumerant = entry.get('enumerant') |
| value = entry.get('value') |
| caps = entry.get('capabilities', []) |
| if value in extension_map: |
| exts = extension_map[value] |
| else: |
| exts = [] |
| params = entry.get('parameters', []) |
| params = [p.get('kind') for p in params] |
| params = zip(params, [''] * len(params)) |
| version = entry.get('version', None) |
| max_version = entry.get('lastVersion', None) |
| |
| assert enumerant is not None |
| assert value is not None |
| |
| return str(EnumerantInitializer( |
| enumerant, value, caps, exts, params, version, max_version)) |
| |
| |
| def generate_enum_operand_kind(enum, synthetic_exts_list): |
| """Returns the C definition for the given operand kind. |
| It's a static const named array of spv_operand_desc_t. |
| |
| Also appends to |synthetic_exts_list| a list of extension lists |
| used. |
| """ |
| kind = enum.get('kind') |
| assert kind is not None |
| |
| # Sort all enumerants according to their values, but otherwise |
| # preserve their order so the first name listed in the grammar |
| # as the preferred name for disassembly. |
| if enum.get('category') == 'ValueEnum': |
| def functor(k): return (k['value']) |
| else: |
| def functor(k): return (int(k['value'], 16)) |
| entries = sorted(enum.get('enumerants', []), key=functor) |
| |
| # SubgroupEqMask and SubgroupEqMaskKHR are the same number with |
| # same semantics, but one has no extension list while the other |
| # does. Both should have the extension list. |
| # So create a mapping from enum value to the union of the extensions |
| # across all those grammar entries. Preserve order. |
| extension_map = {} |
| for e in entries: |
| value = e.get('value') |
| extension_map[value] = [] |
| for e in entries: |
| value = e.get('value') |
| exts = e.get('extensions', []) |
| for ext in exts: |
| if ext not in extension_map[value]: |
| extension_map[value].append(ext) |
| synthetic_exts_list.extend(extension_map.values()) |
| |
| name = '{}_{}Entries'.format(PYGEN_VARIABLE_PREFIX, kind) |
| entries = [' {}'.format(generate_enum_operand_kind_entry(e, extension_map)) |
| for e in entries] |
| |
| template = ['static const spv_operand_desc_t {name}[] = {{', |
| '{entries}', '}};'] |
| entries = '\n'.join(template).format( |
| name=name, |
| entries=',\n'.join(entries)) |
| |
| return kind, name, entries |
| |
| |
| def generate_operand_kind_table(enums): |
| """Returns the info table containing all SPIR-V operand kinds.""" |
| # We only need to output info tables for those operand kinds that are enums. |
| enums = [e for e in enums if e.get('category') in ['ValueEnum', 'BitEnum']] |
| |
| caps = [entry.get('capabilities', []) |
| for enum in enums |
| for entry in enum.get('enumerants', [])] |
| caps_arrays = generate_capability_arrays(caps) |
| |
| exts = [entry.get('extensions', []) |
| for enum in enums |
| for entry in enum.get('enumerants', [])] |
| enums = [generate_enum_operand_kind(e, exts) for e in enums] |
| exts_arrays = generate_extension_arrays(exts) |
| |
| # We have a few operand kinds that require their optional counterpart to |
| # exist in the operand info table. |
| optional_enums = ['ImageOperands', 'AccessQualifier', 'MemoryAccess', 'PackedVectorFormat'] |
| optional_enums = [e for e in enums if e[0] in optional_enums] |
| enums.extend(optional_enums) |
| |
| enum_kinds, enum_names, enum_entries = zip(*enums) |
| # Mark the last few as optional ones. |
| enum_quantifiers = [''] * (len(enums) - len(optional_enums)) + ['?'] * len(optional_enums) |
| # And we don't want redefinition of them. |
| enum_entries = enum_entries[:-len(optional_enums)] |
| enum_kinds = [convert_operand_kind(e) |
| for e in zip(enum_kinds, enum_quantifiers)] |
| table_entries = zip(enum_kinds, enum_names, enum_names) |
| table_entries = [' {{{}, ARRAY_SIZE({}), {}}}'.format(*e) |
| for e in table_entries] |
| |
| template = [ |
| 'static const spv_operand_desc_group_t {p}_OperandInfoTable[] = {{', |
| '{enums}', '}};'] |
| table = '\n'.join(template).format( |
| p=PYGEN_VARIABLE_PREFIX, enums=',\n'.join(table_entries)) |
| |
| return '\n\n'.join((caps_arrays,) + (exts_arrays,) + enum_entries + (table,)) |
| |
| |
| def get_extension_list(instructions, operand_kinds): |
| """Returns extensions as an alphabetically sorted list of strings.""" |
| |
| things_with_an_extensions_field = [item for item in instructions] |
| |
| enumerants = sum([item.get('enumerants', []) |
| for item in operand_kinds], []) |
| |
| things_with_an_extensions_field.extend(enumerants) |
| |
| extensions = sum([item.get('extensions', []) |
| for item in things_with_an_extensions_field |
| if item.get('extensions')], []) |
| |
| for item in EXTENSIONS_FROM_SPIRV_REGISTRY_AND_NOT_FROM_GRAMMARS.split(): |
| # If it's already listed in a grammar, then don't put it in the |
| # special exceptions list. |
| assert item not in extensions, 'Extension %s is already in a grammar file' % item |
| |
| extensions.extend( |
| EXTENSIONS_FROM_SPIRV_REGISTRY_AND_NOT_FROM_GRAMMARS.split()) |
| |
| # Validator would ignore type declaration unique check. Should only be used |
| # for legacy autogenerated test files containing multiple instances of the |
| # same type declaration, if fixing the test by other methods is too |
| # difficult. Shouldn't be used for any other reasons. |
| extensions.append('SPV_VALIDATOR_ignore_type_decl_unique') |
| |
| return sorted(set(extensions)) |
| |
| |
| def get_capabilities(operand_kinds): |
| """Returns capabilities as a list of JSON objects, in order of |
| appearance.""" |
| enumerants = sum([item.get('enumerants', []) for item in operand_kinds |
| if item.get('kind') in ['Capability']], []) |
| return enumerants |
| |
| |
| def generate_extension_enum(extensions): |
| """Returns enumeration containing extensions declared in the grammar.""" |
| return ',\n'.join(['k' + extension for extension in extensions]) |
| |
| |
| def generate_extension_to_string_mapping(extensions): |
| """Returns mapping function from extensions to corresponding strings.""" |
| function = 'const char* ExtensionToString(Extension extension) {\n' |
| function += ' switch (extension) {\n' |
| template = ' case Extension::k{extension}:\n' \ |
| ' return "{extension}";\n' |
| function += ''.join([template.format(extension=extension) |
| for extension in extensions]) |
| function += ' }\n\n return "";\n}' |
| return function |
| |
| |
| def generate_string_to_extension_mapping(extensions): |
| """Returns mapping function from strings to corresponding extensions.""" |
| |
| function = ''' |
| bool GetExtensionFromString(const char* str, Extension* extension) {{ |
| static const char* known_ext_strs[] = {{ {strs} }}; |
| static const Extension known_ext_ids[] = {{ {ids} }}; |
| const auto b = std::begin(known_ext_strs); |
| const auto e = std::end(known_ext_strs); |
| const auto found = std::equal_range( |
| b, e, str, [](const char* str1, const char* str2) {{ |
| return std::strcmp(str1, str2) < 0; |
| }}); |
| if (found.first == e || found.first == found.second) return false; |
| |
| *extension = known_ext_ids[found.first - b]; |
| return true; |
| }} |
| '''.format(strs=', '.join(['"{}"'.format(e) for e in extensions]), |
| ids=', '.join(['Extension::k{}'.format(e) for e in extensions])) |
| |
| return function |
| |
| |
| def generate_capability_to_string_mapping(operand_kinds): |
| """Returns mapping function from capabilities to corresponding strings. |
| |
| We take care to avoid emitting duplicate values. |
| """ |
| cap_str = 'SpvCapability' |
| cap_join = '' |
| global OUTPUT_LANGUAGE |
| if OUTPUT_LANGUAGE == 'c++': |
| cap_str = 'spv::Capability' |
| cap_join = '::' |
| |
| function = 'const char* CapabilityToString(' + cap_str + ' capability) {\n' |
| function += ' switch (capability) {\n' |
| template = ' case ' + cap_str + cap_join + '{capability}:\n' \ |
| ' return "{capability}";\n' |
| emitted = set() # The values of capabilities we already have emitted |
| for capability in get_capabilities(operand_kinds): |
| value = capability.get('value') |
| if value not in emitted: |
| emitted.add(value) |
| function += template.format(capability=capability.get('enumerant')) |
| function += ' case ' + cap_str + cap_join + 'Max:\n' \ |
| ' assert(0 && "Attempting to convert ' + cap_str + cap_join + 'Max to string");\n' \ |
| ' return "";\n' |
| function += ' }\n\n return "";\n}' |
| return function |
| |
| |
| def generate_all_string_enum_mappings(extensions, operand_kinds): |
| """Returns all string-to-enum / enum-to-string mapping tables.""" |
| tables = [] |
| tables.append(generate_extension_to_string_mapping(extensions)) |
| tables.append(generate_string_to_extension_mapping(extensions)) |
| tables.append(generate_capability_to_string_mapping(operand_kinds)) |
| return '\n\n'.join(tables) |
| |
| |
| def precondition_operand_kinds(operand_kinds): |
| """For operand kinds that have the same number, make sure they all have the |
| same extension list.""" |
| |
| # Map operand kind and value to list of the union of extensions |
| # for same-valued enumerants. |
| exts = {} |
| for kind_entry in operand_kinds: |
| kind = kind_entry.get('kind') |
| for enum_entry in kind_entry.get('enumerants', []): |
| value = enum_entry.get('value') |
| key = kind + '.' + str(value) |
| if key in exts: |
| exts[key].extend(enum_entry.get('extensions', [])) |
| else: |
| exts[key] = enum_entry.get('extensions', []) |
| exts[key] = sorted(set(exts[key])) |
| |
| # Now make each entry the same list. |
| for kind_entry in operand_kinds: |
| kind = kind_entry.get('kind') |
| for enum_entry in kind_entry.get('enumerants', []): |
| value = enum_entry.get('value') |
| key = kind + '.' + str(value) |
| if len(exts[key]) > 0: |
| enum_entry['extensions'] = exts[key] |
| |
| return operand_kinds |
| |
| |
| def prefix_operand_kind_names(prefix, json_dict): |
| """Modifies json_dict, by prefixing all the operand kind names |
| with the given prefix. Also modifies their uses in the instructions |
| to match. |
| """ |
| |
| old_to_new = {} |
| for operand_kind in json_dict["operand_kinds"]: |
| old_name = operand_kind["kind"] |
| new_name = prefix + old_name |
| operand_kind["kind"] = new_name |
| old_to_new[old_name] = new_name |
| |
| for instruction in json_dict["instructions"]: |
| for operand in instruction.get("operands", []): |
| replacement = old_to_new.get(operand["kind"]) |
| if replacement is not None: |
| operand["kind"] = replacement |
| |
| |
| def main(): |
| import argparse |
| parser = argparse.ArgumentParser(description='Generate SPIR-V info tables') |
| |
| parser.add_argument('--spirv-core-grammar', metavar='<path>', |
| type=str, required=False, |
| help='input JSON grammar file for core SPIR-V ' |
| 'instructions') |
| parser.add_argument('--extinst-debuginfo-grammar', metavar='<path>', |
| type=str, required=False, default=None, |
| help='input JSON grammar file for DebugInfo extended ' |
| 'instruction set') |
| parser.add_argument('--extinst-cldebuginfo100-grammar', metavar='<path>', |
| type=str, required=False, default=None, |
| help='input JSON grammar file for OpenCL.DebugInfo.100 ' |
| 'extended instruction set') |
| parser.add_argument('--extinst-glsl-grammar', metavar='<path>', |
| type=str, required=False, default=None, |
| help='input JSON grammar file for GLSL extended ' |
| 'instruction set') |
| parser.add_argument('--extinst-opencl-grammar', metavar='<path>', |
| type=str, required=False, default=None, |
| help='input JSON grammar file for OpenCL extended ' |
| 'instruction set') |
| parser.add_argument('--output-language', |
| type=str, required=False, default='c', |
| choices=['c','c++'], |
| help='specify output language type') |
| |
| parser.add_argument('--core-insts-output', metavar='<path>', |
| type=str, required=False, default=None, |
| help='output file for core SPIR-V instructions') |
| parser.add_argument('--glsl-insts-output', metavar='<path>', |
| type=str, required=False, default=None, |
| help='output file for GLSL extended instruction set') |
| parser.add_argument('--opencl-insts-output', metavar='<path>', |
| type=str, required=False, default=None, |
| help='output file for OpenCL extended instruction set') |
| parser.add_argument('--operand-kinds-output', metavar='<path>', |
| type=str, required=False, default=None, |
| help='output file for operand kinds') |
| parser.add_argument('--extension-enum-output', metavar='<path>', |
| type=str, required=False, default=None, |
| help='output file for extension enumeration') |
| parser.add_argument('--enum-string-mapping-output', metavar='<path>', |
| type=str, required=False, default=None, |
| help='output file for enum-string mappings') |
| parser.add_argument('--extinst-vendor-grammar', metavar='<path>', |
| type=str, required=False, default=None, |
| help='input JSON grammar file for vendor extended ' |
| 'instruction set'), |
| parser.add_argument('--vendor-insts-output', metavar='<path>', |
| type=str, required=False, default=None, |
| help='output file for vendor extended instruction set') |
| parser.add_argument('--vendor-operand-kind-prefix', metavar='<string>', |
| type=str, required=False, default=None, |
| help='prefix for operand kinds (to disambiguate operand type enums)') |
| args = parser.parse_args() |
| |
| global OUTPUT_LANGUAGE |
| OUTPUT_LANGUAGE = args.output_language |
| |
| # The GN build system needs this because it doesn't handle quoting |
| # empty string arguments well. |
| if args.vendor_operand_kind_prefix == "...nil...": |
| args.vendor_operand_kind_prefix = "" |
| |
| if (args.core_insts_output is None) != \ |
| (args.operand_kinds_output is None): |
| print('error: --core-insts-output and --operand-kinds-output ' |
| 'should be specified together.') |
| exit(1) |
| if args.operand_kinds_output and not (args.spirv_core_grammar and |
| args.extinst_debuginfo_grammar and |
| args.extinst_cldebuginfo100_grammar): |
| print('error: --operand-kinds-output requires --spirv-core-grammar ' |
| 'and --extinst-debuginfo-grammar ' |
| 'and --extinst-cldebuginfo100-grammar') |
| exit(1) |
| if (args.glsl_insts_output is None) != \ |
| (args.extinst_glsl_grammar is None): |
| print('error: --glsl-insts-output and --extinst-glsl-grammar ' |
| 'should be specified together.') |
| exit(1) |
| if (args.opencl_insts_output is None) != \ |
| (args.extinst_opencl_grammar is None): |
| print('error: --opencl-insts-output and --extinst-opencl-grammar ' |
| 'should be specified together.') |
| exit(1) |
| if (args.vendor_insts_output is None) != \ |
| (args.extinst_vendor_grammar is None): |
| print('error: --vendor-insts-output and ' |
| '--extinst-vendor-grammar should be specified together.') |
| exit(1) |
| if all([args.core_insts_output is None, |
| args.glsl_insts_output is None, |
| args.opencl_insts_output is None, |
| args.vendor_insts_output is None, |
| args.extension_enum_output is None, |
| args.enum_string_mapping_output is None]): |
| print('error: at least one output should be specified.') |
| exit(1) |
| |
| if args.spirv_core_grammar is not None: |
| with open(args.spirv_core_grammar) as json_file: |
| core_grammar = json.loads(json_file.read()) |
| with open(args.extinst_debuginfo_grammar) as debuginfo_json_file: |
| debuginfo_grammar = json.loads(debuginfo_json_file.read()) |
| with open(args.extinst_cldebuginfo100_grammar) as cldebuginfo100_json_file: |
| cldebuginfo100_grammar = json.loads(cldebuginfo100_json_file.read()) |
| prefix_operand_kind_names("CLDEBUG100_", cldebuginfo100_grammar) |
| instructions = [] |
| instructions.extend(core_grammar['instructions']) |
| instructions.extend(debuginfo_grammar['instructions']) |
| instructions.extend(cldebuginfo100_grammar['instructions']) |
| operand_kinds = [] |
| operand_kinds.extend(core_grammar['operand_kinds']) |
| operand_kinds.extend(debuginfo_grammar['operand_kinds']) |
| operand_kinds.extend(cldebuginfo100_grammar['operand_kinds']) |
| extensions = get_extension_list(instructions, operand_kinds) |
| operand_kinds = precondition_operand_kinds(operand_kinds) |
| if args.core_insts_output is not None: |
| make_path_to_file(args.core_insts_output) |
| make_path_to_file(args.operand_kinds_output) |
| with open(args.core_insts_output, 'w') as f: |
| f.write(generate_instruction_table( |
| core_grammar['instructions'])) |
| with open(args.operand_kinds_output, 'w') as f: |
| f.write(generate_operand_kind_table(operand_kinds)) |
| if args.extension_enum_output is not None: |
| make_path_to_file(args.extension_enum_output) |
| with open(args.extension_enum_output, 'w') as f: |
| f.write(generate_extension_enum(extensions)) |
| if args.enum_string_mapping_output is not None: |
| make_path_to_file(args.enum_string_mapping_output) |
| with open(args.enum_string_mapping_output, 'w') as f: |
| f.write(generate_all_string_enum_mappings( |
| extensions, operand_kinds)) |
| |
| if args.extinst_glsl_grammar is not None: |
| with open(args.extinst_glsl_grammar) as json_file: |
| grammar = json.loads(json_file.read()) |
| make_path_to_file(args.glsl_insts_output) |
| with open(args.glsl_insts_output, 'w') as f: |
| f.write(generate_extended_instruction_table( |
| grammar, 'glsl')) |
| |
| if args.extinst_opencl_grammar is not None: |
| with open(args.extinst_opencl_grammar) as json_file: |
| grammar = json.loads(json_file.read()) |
| make_path_to_file(args.opencl_insts_output) |
| with open(args.opencl_insts_output, 'w') as f: |
| f.write(generate_extended_instruction_table( |
| grammar, 'opencl')) |
| |
| if args.extinst_vendor_grammar is not None: |
| with open(args.extinst_vendor_grammar) as json_file: |
| grammar = json.loads(json_file.read()) |
| make_path_to_file(args.vendor_insts_output) |
| name = args.extinst_vendor_grammar |
| start = name.find('extinst.') + len('extinst.') |
| name = name[start:-len('.grammar.json')].replace('-', '_') |
| with open(args.vendor_insts_output, 'w') as f: |
| f.write(generate_extended_instruction_table( |
| grammar, name, args.vendor_operand_kind_prefix)) |
| |
| |
| if __name__ == '__main__': |
| main() |