SPIRV-Tools/utils/generate_grammar_tables.py
LU-JOHN 7c778973e5
Allow fmt arg to printf to be an array of i8 in non-constant space (#5677)
* In spirv-val allow format arg to printf to be an array of i8 in Generic space

Signed-off-by: Lu, John <john.lu@intel.com>

* Allow more addr spaces for printf format string

Signed-off-by: Lu, John <john.lu@intel.com>

* Update printf format arg testcase

Signed-off-by: Lu, John <john.lu@intel.com>

* Apply clang-format

Signed-off-by: Lu, John <john.lu@intel.com>

* Reorder code for clarity

Signed-off-by: Lu, John <john.lu@intel.com>

* Only allow other addr spaces if extension is seen

Signed-off-by: Lu, John <john.lu@intel.com>

* Add test to check printf format with extension

Signed-off-by: Lu, John <john.lu@intel.com>

* Add extension correctly

Signed-off-by: Lu, John <john.lu@intel.com>

---------

Signed-off-by: Lu, John <john.lu@intel.com>
2024-07-16 09:20:04 -04:00

909 lines
35 KiB
Python
Executable File

#!/usr/bin/env python3
# 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
SPV_EXT_relaxed_printf_string_address_space
"""
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]
if len(entries) == 0:
# Insert a dummy entry. Otherwise the array is empty and compilation
# will fail in MSVC.
entries = [' {"place holder", 0, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(999,0), 0}']
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', 'CooperativeMatrixOperands', 'RawAccessChainOperands', 'FPEncoding']
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()