978 lines
41 KiB
Python
Executable File
978 lines
41 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 compressed grammar tables from SPIR-V JSON grammar."""
|
|
|
|
import errno
|
|
import json
|
|
import os.path
|
|
import re
|
|
import sys
|
|
from typing import Dict, List, Tuple, Any
|
|
|
|
# Find modules relative to the directory containing this script.
|
|
# This is needed for hermetic Bazel builds, where the Table files are bundled
|
|
# together with this script, while keeping their relative locations.
|
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
from Table.Context import Context
|
|
from Table.IndexRange import IndexRange
|
|
from Table.Operand import Operand
|
|
|
|
class GrammarError(Exception):
|
|
pass
|
|
|
|
# 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_gpu_shader_half_float
|
|
SPV_AMD_gpu_shader_int16
|
|
SPV_KHR_non_semantic_info
|
|
SPV_EXT_relaxed_printf_string_address_space
|
|
"""
|
|
|
|
class ExtInst():
|
|
"""
|
|
An extended instruction set.
|
|
|
|
Properties:
|
|
prefix: the string prefix for operand enums. Often an empty string.
|
|
file: the location of the JSON grammar file
|
|
name: the name, can be used as an identifier
|
|
enum_name: the enum name, e.g. SPV_EXT_INST_OPENCL_STD
|
|
grammar: the JSON object for the grammar, loaded from the file.
|
|
"""
|
|
def __init__(self,spec: str):
|
|
matches = re.fullmatch('^([^,]*),(.*)',spec)
|
|
if matches is None:
|
|
raise Exception("Invalid prefix and path: {}".format(spec))
|
|
self.prefix = matches[1]
|
|
if self.prefix is None:
|
|
self.prefix = ""
|
|
self.file = matches[2]
|
|
matches = re.match('.*extinst\\.(.*)\\.grammar.json', self.file)
|
|
if matches is None:
|
|
raise Exception("Invalid grammar file name: {}".format(self.file))
|
|
self.name = matches[1].replace('-','_').replace('.','_')
|
|
|
|
self.enum_name = 'SPV_EXT_INST_TYPE_{}'.format(self.name).upper()
|
|
if self.enum_name == "SPV_EXT_INST_TYPE_OPENCL_STD_100":
|
|
# Live with an old decision, by adjusting this name.
|
|
self.enum_name = "SPV_EXT_INST_TYPE_OPENCL_STD"
|
|
if self.enum_name == "SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO":
|
|
# The version-agnostic grammar file name omits "_100", but the
|
|
# public enum in libspirv.h retains the suffix for compatibility.
|
|
self.enum_name = "SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100"
|
|
|
|
self.load()
|
|
|
|
def load(self):
|
|
"""
|
|
Populates self.grammar from the file.
|
|
Applies the self.prefix to operand enums
|
|
"""
|
|
with open(self.file) as json_file:
|
|
self.grammar = json.loads(json_file.read())
|
|
if len(self.prefix) > 0:
|
|
prefix_operand_kind_names(self.prefix, self.grammar)
|
|
|
|
|
|
def convert_min_required_version(version): # (version: str | None) -> str
|
|
"""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): # (version: str | None) -> str
|
|
"""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 c_bool(b: bool) -> str:
|
|
return 'true' if b else 'false'
|
|
|
|
|
|
def ctype(kind: str, quantifier: str) -> str:
|
|
"""Returns the corresponding operand type used in spirv-tools for the given
|
|
operand kind and quantifier used in the JSON grammar.
|
|
|
|
Arguments:
|
|
- kind, e.g. 'IdRef'
|
|
- quantifier, e.g. '', '?', '*'
|
|
|
|
Returns:
|
|
a string of the enumerant name in spv_operand_type_t
|
|
"""
|
|
if kind == '':
|
|
raise Error("operand JSON object missing a 'kind' field")
|
|
# 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())
|
|
|
|
|
|
def convert_operand_kind(obj: Dict[str, str]) -> str:
|
|
"""Returns the corresponding operand type used in spirv-tools for the given
|
|
operand kind and quantifier used in the JSON grammar.
|
|
|
|
Arguments:
|
|
- obj: an instruction operand, having keys:
|
|
- 'kind', e.g. 'IdRef'
|
|
- optionally, a quantifier: '?' or '*'
|
|
|
|
Returns:
|
|
a string of the enumerant name in spv_operand_type_t
|
|
"""
|
|
kind = obj.get('kind', '')
|
|
quantifier = obj.get('quantifier', '')
|
|
return ctype(kind, quantifier)
|
|
|
|
|
|
def to_safe_identifier(s: str) -> str:
|
|
"""
|
|
Returns a new string with all non-letters converted to underscores,
|
|
and prepending 'k'.
|
|
The result should be safe to use as a C identifier.
|
|
"""
|
|
return 'k' + re.sub(r'[^a-zA-Z0-9]', '_', s)
|
|
|
|
|
|
class Grammar():
|
|
"""
|
|
Accumulates string and enum tables.
|
|
The extensions and operand kinds lists are fixed at creation time.
|
|
Prints tables for instructions, operand kinds, and underlying string
|
|
and enum tables.
|
|
Assumes an index range is emitted by printing an IndexRange object.
|
|
"""
|
|
def __init__(self, extensions: List[str], operand_kinds:List[dict], printing_classes: List[str]) -> None:
|
|
self.context = Context()
|
|
self.extensions = sorted(extensions)
|
|
self.operand_kinds = sorted(operand_kinds, key = lambda ok: convert_operand_kind(ok))
|
|
self.printing_classes = sorted([to_safe_identifier(x) for x in printing_classes])
|
|
|
|
# The self.header_ignore_decls are only used to debug the flow.
|
|
# They are copied into the C++ source code where they are more likely
|
|
# to be seen by humans.
|
|
self.header_ignore_decls: List[str] = [self.IndexRangeDecls()]
|
|
|
|
# The self.header_decls content goes into core_tables_header.inc to be
|
|
# included in a .h file.
|
|
self.header_decls: List[str] = []
|
|
# The self.body_decls content goes into core_tables_body.inc to be included
|
|
# in a .cpp file. It includes definitions of static variables and
|
|
# hidden functions.
|
|
self.body_decls: List[str] = []
|
|
|
|
if len(self.operand_kinds) == 0:
|
|
raise Exception("operand_kinds should be a non-empty list")
|
|
if len(self.extensions) == 0:
|
|
raise Exception("extensions should be a non-empty list")
|
|
|
|
self.ComputePrintingClassDecls()
|
|
self.ComputeExtensionDecls()
|
|
|
|
# These operand kinds need to have their optional counterpart to also
|
|
# be represented in the lookup tables, with the same content.
|
|
self.operand_kinds_needing_optional_variant = [
|
|
'ImageOperands',
|
|
'AccessQualifier',
|
|
'MemoryAccess',
|
|
'PackedVectorFormat',
|
|
'CooperativeMatrixOperands',
|
|
'MatrixMultiplyAccumulateOperands',
|
|
'RawAccessChainOperands',
|
|
'FPEncoding',
|
|
'TensorOperands',
|
|
'Capability']
|
|
|
|
def dump(self) -> None:
|
|
self.context.dump()
|
|
|
|
def IndexRangeDecls(self) -> str:
|
|
return """
|
|
struct IndexRange {
|
|
uint32_t first = 0; // index of the first element in the range
|
|
uint32_t count = 0; // number of elements in the range
|
|
};
|
|
constexpr inline IndexRange IR(uint32_t first, uint32_t count) {
|
|
return {first, count};
|
|
}
|
|
"""
|
|
|
|
def ComputePrintingClassDecls(self) -> str:
|
|
parts: List[str] = []
|
|
parts.append("enum class PrintingClass : uint32_t {");
|
|
parts.extend([" {},".format(x) for x in self.printing_classes])
|
|
parts.append("};\n")
|
|
self.header_decls.extend(parts)
|
|
|
|
def ComputeExtensionDecls(self) -> None:
|
|
parts: List[str] = []
|
|
parts.append("enum Extension : uint32_t {");
|
|
parts.extend([" {},".format(to_safe_identifier(x)) for x in self.extensions])
|
|
parts.append("};\n")
|
|
self.header_decls.extend(parts)
|
|
|
|
parts = []
|
|
parts.append("// Returns the name of an extension, as an index into kStrings")
|
|
parts.append("IndexRange ExtensionToIndexRange(Extension extension) {\n switch(extension) {")
|
|
for e in self.extensions:
|
|
parts.append(' case Extension::k{}: return {};'.format(e,self.context.AddString(e)))
|
|
parts.append(" default: break;");
|
|
parts.append(' }\n return {};\n}\n');
|
|
self.body_decls.extend(parts)
|
|
|
|
parts = []
|
|
parts.append("""// Extension names and values, ordered by name
|
|
// The fields in order are:
|
|
// name, indexing into kStrings
|
|
// enum value""")
|
|
parts.append("static const std::array<NameValue,{}> kExtensionNames{{{{".format(len(self.extensions)))
|
|
for e in self.extensions:
|
|
parts.append(' {{{}, static_cast<uint32_t>({})}},'.format(self.context.AddString(e), to_safe_identifier(e)))
|
|
parts.append("}};\n")
|
|
self.body_decls.extend(parts)
|
|
|
|
def ComputeOperandTables(self) -> None:
|
|
"""
|
|
Returns the string for the C definitions of the operand kind tables.
|
|
|
|
An operand kind such as ImageOperands also has an associated
|
|
operand kind that is an 'optional' variant.
|
|
These are represented as two distinct operand kinds in spv_operand_type_t.
|
|
For example, ImageOperands maps to both SPV_OPERAND_TYPE_IMAGE, and also
|
|
to SPV_OPERAND_TYPE_OPTIONAL_IMAGE.
|
|
|
|
The definitions are:
|
|
- kOperandsByValue: a 1-dimensional array of all operand descriptions
|
|
sorted first by operand kind, then by operand value.
|
|
Only non-optional operand kinds are represented here.
|
|
|
|
- kOperandsByValueRangeByKind: a function mapping from operand kind to
|
|
the index range into kOperandByValue.
|
|
This has mappings for both concrete and corresponding optional operand kinds.
|
|
|
|
- kOperandNames: a 1-dimensional array of all operand NameIndex
|
|
entries, sorted first by operand kinds, then by operand name.
|
|
The name part is represented by an index range into the string table.
|
|
The index part is the index of this name's entry into the by-value array.
|
|
This can have more entries than the by-value array, because names
|
|
can have string aliases. For example,the MemorySemantics value 0
|
|
is named both "Relaxed" and "None".
|
|
Only non-optional operand kinds are represented here.
|
|
|
|
- kOperandNamesRangeByKind: a mapping from operand kind to the index
|
|
range into kOperandNames.
|
|
This has mappings for both concrete and corresponding optional operand kinds.
|
|
"""
|
|
|
|
self.header_ignore_decls.append(
|
|
"""
|
|
struct NameIndex {
|
|
// Location of the null-terminated name in the global string table.
|
|
IndexRange name;
|
|
// Index of this name's entry in in the associated by-value table.
|
|
uint32_t index;
|
|
};
|
|
struct NameValue {
|
|
// Location of the null-terminated name in the global string table.
|
|
IndexRange name;
|
|
// Enum value in the binary format.
|
|
uint32_t value;
|
|
};
|
|
// Describes a SPIR-V operand.
|
|
struct OperandDesc {
|
|
uint32_t value;
|
|
IndexRange operands_range; // Indexes kOperandSpans
|
|
IndexRange name_range; // Indexes kStrings
|
|
IndexRange aliases_range; // Indexes kAliasSpans
|
|
IndexRange capabilities_range; // Indexes kCapabilitySpans
|
|
// A set of extensions that enable this feature. If empty then this operand
|
|
// value is in core and its availability is subject to minVersion. The
|
|
// assembler, binary parser, and disassembler ignore this rule, so you can
|
|
// freely process invalid modules.
|
|
IndexRange extensions_range; // Indexes kExtensionSpans
|
|
// Minimal core SPIR-V version required for this feature, if without
|
|
// extensions. ~0u means reserved for future use. ~0u and non-empty
|
|
// extension lists means only available in extensions.
|
|
uint32_t minVersion;
|
|
uint32_t lastVersion;
|
|
utils::Span<spv_operand_type_t> operands() const;
|
|
utils::Span<char> name() const;
|
|
utils::Span<IndexRange> aliases() const;
|
|
utils::Span<spv::Capability> capabilities() const;
|
|
utils::Span<spvtools::Extension> extensions() const;
|
|
OperandDesc(const OperandDesc&) = delete;
|
|
OperandDesc(OperandDesc&&) = delete;
|
|
};
|
|
""")
|
|
|
|
def ShouldEmit(operand_kind_json: Dict[str,any]):
|
|
""" Returns true if we should emit a table for the given
|
|
operand kind.
|
|
"""
|
|
category = operand_kind_json.get('category')
|
|
return category in ['ValueEnum', 'BitEnum']
|
|
|
|
# Populate kOperandsByValue
|
|
operands_by_value: List[str] = []
|
|
operands_by_value_by_kind: Dict[str,IndexRange] = {}
|
|
# Maps the operand kind and value to the index into kOperandsByValue
|
|
index_by_kind_and_value: Dict[Tuple(str,int),int] = {}
|
|
index = 0
|
|
for operand_kind_json in self.operand_kinds:
|
|
kind_key: str = convert_operand_kind(operand_kind_json)
|
|
if ShouldEmit(operand_kind_json):
|
|
operands = [Operand(o) for o in operand_kind_json['enumerants']]
|
|
operand_descs: List[str] = []
|
|
for o in sorted(operands, key = lambda o: o.value):
|
|
suboperands = [convert_operand_kind(p) for p in o.parameters]
|
|
desc = [
|
|
o.value,
|
|
self.context.AddStringList('operand', suboperands),
|
|
str(self.context.AddString(o.enumerant)) + '/* {} */'.format(o.enumerant),
|
|
self.context.AddStringList('alias', o.aliases),
|
|
self.context.AddStringList('capability', o.capabilities),
|
|
self.context.AddStringList('extension', o.extensions),
|
|
convert_min_required_version(o.version),
|
|
convert_max_required_version(o.lastVersion),
|
|
]
|
|
operand_descs.append('{' + ','.join([str(d) for d in desc]) + '}}, // {}'.format(kind_key))
|
|
index_by_kind_and_value[(kind_key,o.value)] = index
|
|
index += 1
|
|
operands_by_value_by_kind[kind_key] = IndexRange(len(operands_by_value), len(operand_descs))
|
|
operands_by_value.extend(operand_descs)
|
|
else:
|
|
pass
|
|
|
|
parts = []
|
|
parts.append("""// Operand descriptions, ordered by (operand kind, operand enum value).
|
|
// The fields in order are:
|
|
// enum value
|
|
// operands, an IndexRange into kOperandSpans
|
|
// name, a character-counting IndexRange into kStrings
|
|
// aliases, an IndexRange into kAliasSpans
|
|
// capabilities, an IndexRange into kCapabilitySpans
|
|
// extensions, as an IndexRange into kExtensionSpans
|
|
// version, first version of SPIR-V that has it
|
|
// lastVersion, last version of SPIR-V that has it""")
|
|
parts.append("static const std::array<OperandDesc, {}> kOperandsByValue{{{{".format(len(operands_by_value)))
|
|
parts.extend([' ' + str(x) for x in operands_by_value])
|
|
parts.append("}};\n")
|
|
self.body_decls.extend(parts)
|
|
|
|
parts = []
|
|
parts.append("""// Maps an operand kind to possible operands for that kind.
|
|
// The result is an IndexRange into kOperandsByValue, and the operands
|
|
// are sorted by value within that span.
|
|
// An optional variant of a kind maps to the details for the corresponding
|
|
// concrete operand kind.""")
|
|
parts.append("IndexRange OperandByValueRangeForKind(spv_operand_type_t type) {\n switch(type) {")
|
|
for kind_key, ir in operands_by_value_by_kind.items():
|
|
parts.append(" case {}: return {};".format(
|
|
kind_key,
|
|
str(operands_by_value_by_kind[kind_key])))
|
|
for kind in self.operand_kinds_needing_optional_variant:
|
|
non_optional_kind = ctype(kind,'')
|
|
if non_optional_kind in operands_by_value_by_kind:
|
|
parts.append(" case {}: return {};".format(
|
|
ctype(kind, '?'),
|
|
str(operands_by_value_by_kind[ctype(kind,'')])))
|
|
else:
|
|
raise GrammarError(
|
|
"error: unknown operand type {}, from JSON grammar operand '{}':".format(non_optional_kind, kind) +
|
|
" consider updating spv_operand_type_t in spirv-tools/libspirv.h")
|
|
|
|
parts.append(" default: break;");
|
|
parts.append(" }\n return IR(0,0);\n}\n")
|
|
self.body_decls.extend(parts)
|
|
|
|
# Populate kOperandNames
|
|
operand_names: List[Tuple[IndexRange,int]] = []
|
|
name_range_for_kind: Dict[str,IndexRange] = {}
|
|
for operand_kind_json in self.operand_kinds:
|
|
kind_key: str = convert_operand_kind(operand_kind_json)
|
|
if ShouldEmit(operand_kind_json):
|
|
operands = [Operand(o) for o in operand_kind_json['enumerants']]
|
|
tuples: List[Tuple[str,int,str]] = []
|
|
for o in operands:
|
|
tuples.append((o.enumerant, o.value, kind_key))
|
|
for a in o.aliases:
|
|
tuples.append((a, o.value, kind_key))
|
|
tuples = sorted(tuples, key = lambda t: t[0])
|
|
ir_tuples = [(self.context.AddString(t[0]),t[1],t[2]) for t in tuples]
|
|
name_range_for_kind[kind_key] = IndexRange(len(operand_names), len(ir_tuples))
|
|
operand_names.extend(ir_tuples)
|
|
else:
|
|
pass
|
|
operand_name_strings: List[str] = []
|
|
for i in range(0, len(operand_names)):
|
|
ir, value, kind_key = operand_names[i]
|
|
index = index_by_kind_and_value[(kind_key,value)]
|
|
operand_name_strings.append('{{{}, {}}}, // {} {} in {}'.format(
|
|
str(ir),index,i,self.context.GetString(ir),kind_key))
|
|
|
|
parts: List[str] = []
|
|
parts.append("""// Operand names and index into kOperandsByValue, ordered by (operand kind, name)
|
|
// The fields in order are:
|
|
// name, either the primary name or an alias, indexing into kStrings
|
|
// index into the kOperandsByValue array""")
|
|
parts.append("static const std::array<NameIndex, {}> kOperandNames{{{{".format(len(operand_name_strings)))
|
|
parts.extend([' ' + str(x) for x in operand_name_strings])
|
|
parts.append("}};\n")
|
|
self.body_decls.extend(parts)
|
|
|
|
parts.append("""// Maps an operand kind to possible names for operands of that kind.
|
|
// The result is an IndexRange into kOperandNames, and the names
|
|
// are sorted by name within that span.
|
|
// An optional variant of a kind maps to the details for the corresponding
|
|
// concrete operand kind.""")
|
|
parts = ["IndexRange OperandNameRangeForKind(spv_operand_type_t type) {\n switch(type) {"]
|
|
for kind_key, ir in name_range_for_kind.items():
|
|
parts.append(" case {}: return {};".format(
|
|
kind_key,
|
|
str(name_range_for_kind[kind_key])))
|
|
for kind in self.operand_kinds_needing_optional_variant:
|
|
parts.append(" case {}: return {};".format(
|
|
ctype(kind, '?'),
|
|
str(name_range_for_kind[ctype(kind,'')])))
|
|
parts.append(" default: break;");
|
|
parts.append(" }\n return IR(0,0);\n}\n")
|
|
self.body_decls.extend(parts)
|
|
|
|
|
|
def ComputeInstructionTables(self, insts) -> None:
|
|
"""
|
|
Creates declarations for instruction tables.
|
|
Populates self.header_ignore_decls, self.body_decls.
|
|
|
|
Params:
|
|
insts: an array of instructions objects using the JSON schema
|
|
"""
|
|
self.header_ignore_decls.append("""
|
|
// Describes an Instruction
|
|
struct InstructionDesc {
|
|
const spv::Op value;
|
|
const bool hasResult;
|
|
const bool hasType;
|
|
const IndexRange operands_range; // Indexes kOperandSpans
|
|
const IndexRange name_range; // Indexes kStrings
|
|
const IndexRange aliases_range; // Indexes kAliasSpans
|
|
const IndexRange capabilities_range; // Indexes kCapbilitySpans
|
|
// A set of extensions that enable this feature. If empty then this operand
|
|
// value is in core and its availability is subject to minVersion. The
|
|
// assembler, binary parser, and disassembler ignore this rule, so you can
|
|
// freely process invalid modules.
|
|
const IndexRange extensions_range; // Indexes kExtensionSpans
|
|
// Minimal core SPIR-V version required for this feature, if without
|
|
// extensions. ~0u means reserved for future use. ~0u and non-empty
|
|
// extension lists means only available in extensions.
|
|
uint32_t minVersion;
|
|
uint32_t lastVersion;
|
|
PrintingClass printingClass; // Section of SPIR-V spec. e.g. kComposite, kImage
|
|
utils::Span<spv_operand_type_t> operands() const;
|
|
utils::Span<char> name() const;
|
|
utils::Span<IndexRange> aliases() const;
|
|
utils::Span<spv::Capability> capabilities() const;
|
|
utils::Span<spvtools::Extension> extensions() const;
|
|
OperandDesc(const OperandDesc&) = delete;
|
|
OperandDesc(OperandDesc&&) = delete;
|
|
};
|
|
""")
|
|
|
|
# Create the array of InstructionDesc
|
|
lines: List[str] = []
|
|
# Maps the opcode name (without "Op" prefix) to its index in the table.
|
|
index_by_opcode: Dict[int,int] = {}
|
|
# Sort by opcode, so lookup can use binary search
|
|
for inst in sorted(insts, key = lambda inst: int(inst['opcode'])):
|
|
parts: List[str] = []
|
|
|
|
opname: str = inst['opname']
|
|
|
|
operand_kinds = [convert_operand_kind(o) for o in inst.get('operands',[])]
|
|
if opname == 'OpExtInst' and operand_kinds[-1] == 'SPV_OPERAND_TYPE_VARIABLE_ID':
|
|
# The published grammar uses 'sequence of ID' at the
|
|
# end of the ExtInst operands. But SPIRV-Tools uses
|
|
# a specific pattern based on the particular opcode.
|
|
# Drop it here.
|
|
# See https://github.com/KhronosGroup/SPIRV-Tools/issues/233
|
|
operand_kinds.pop()
|
|
|
|
hasResult = 'SPV_OPERAND_TYPE_RESULT_ID' in operand_kinds
|
|
hasType = 'SPV_OPERAND_TYPE_TYPE_ID' in operand_kinds
|
|
|
|
# Remove the "Op" prefix from opcode alias names
|
|
aliases = [name[2:] for name in inst.get('aliases',[])]
|
|
|
|
parts.extend([
|
|
'spv::Op::' + opname,
|
|
c_bool(hasResult),
|
|
c_bool(hasType),
|
|
self.context.AddStringList('operand', operand_kinds),
|
|
self.context.AddString(opname[2:]),
|
|
self.context.AddStringList('alias', aliases),
|
|
self.context.AddStringList('capability', inst.get('capabilities',[])),
|
|
self.context.AddStringList('extension', inst.get('extensions',[])),
|
|
convert_min_required_version(inst.get('version', None)),
|
|
convert_max_required_version(inst.get('lastVersion', None)),
|
|
'PrintingClass::' + to_safe_identifier(inst.get('class','@exclude'))
|
|
])
|
|
|
|
index_by_opcode[int(inst['opcode'])] = len(lines)
|
|
lines.append('{{{}}},'.format(', '.join([str(x) for x in parts])))
|
|
parts = []
|
|
parts.append("""// Instruction descriptions, ordered by opcode.
|
|
// The fields in order are:
|
|
// opcode
|
|
// a boolean indicating if the instruction produces a result ID
|
|
// a boolean indicating if the instruction result ID has a type
|
|
// operands, an IndexRange into kOperandSpans
|
|
// opcode name (without the 'Op' prefix), a character-counting IndexRange into kStrings
|
|
// aliases, an IndexRange into kAliasSpans
|
|
// capabilities, an IndexRange into kCapabilitySpans
|
|
// extensions, as an IndexRange into kExtensionSpans
|
|
// version, first version of SPIR-V that has it
|
|
// lastVersion, last version of SPIR-V that has it""")
|
|
parts.append("static const std::array<InstructionDesc, {}> kInstructionDesc{{{{".format(len(lines)));
|
|
parts.extend([' ' + l for l in lines])
|
|
parts.append("}};\n");
|
|
self.body_decls.extend(parts)
|
|
|
|
# Create kInstructionNames.
|
|
opcode_name_entries: List[str] = []
|
|
name_value_pairs: List[Tuple[str,int]] = []
|
|
for i in insts:
|
|
name_value_pairs.append((i['opname'][2:], i['opcode']))
|
|
for a in i.get('aliases',[]):
|
|
name_value_pairs.append((a[2:], i['opcode']))
|
|
name_value_pairs = sorted(name_value_pairs)
|
|
inst_name_strings: List[str] = []
|
|
for i in range(0, len(name_value_pairs)):
|
|
name, value = name_value_pairs[i]
|
|
ir = self.context.AddString(name)
|
|
index = index_by_opcode[value]
|
|
inst_name_strings.append('{{{}, {}}}, // {} {}'.format(str(ir),index,i,name))
|
|
parts: List[str] = []
|
|
parts.append("""// Opcode strings (without the 'Op' prefix) and opcode values, ordered by name.
|
|
// The fields in order are:
|
|
// name, either the primary name or an alias, indexing into kStrings
|
|
// index into kInstructionDesc""")
|
|
parts.append("static const std::array<NameIndex, {}> kInstructionNames{{{{".format(len(inst_name_strings)))
|
|
parts.extend([' ' + str(x) for x in inst_name_strings])
|
|
parts.append("}};\n")
|
|
self.body_decls.extend(parts)
|
|
|
|
|
|
def ComputeExtendedInstructions(self, extinsts) -> None:
|
|
"""
|
|
Generates tables for extended instructions
|
|
|
|
Args:
|
|
self
|
|
extinsts: a list of extinst objects
|
|
"""
|
|
|
|
"""
|
|
ExtInstDesc {
|
|
value: uint32_t
|
|
name: IndexRange
|
|
operands: IndexRange
|
|
capabilities: IndexRange
|
|
}
|
|
|
|
The definitions are:
|
|
- kExtInstByValue: a 1-dimensional array of all operand descriptions
|
|
sorted first by extended instruction enum, then by operand value.
|
|
|
|
- ExtInstByValueRangeForKind: a function mapping from extinst enum to
|
|
the index range into kExtInstByValue.
|
|
|
|
- kExtInstNames: a 1-dimensional array of all extinst name-index pairs,
|
|
sorted first by extinst enum, then by operand name.
|
|
The name part is represented by an index range into the string table.
|
|
The index part is the index of this name's entry in the kExtInstByValue
|
|
array.
|
|
|
|
- kExtInstNamesRangeByKind: a mapping from operand kind to the index
|
|
range into kOperandNames.
|
|
This has mappings for both concrete and corresponding optional operand kinds.
|
|
"""
|
|
|
|
# Create kExtInstByValue
|
|
by_value: List[List[Any]] = []
|
|
by_value_by_kind: Dict[str,IndexRange] = {}
|
|
index_by_kind_and_opcode: Dict[Tuple[str,int],int] = {}
|
|
index = 0
|
|
for e in extinsts:
|
|
insts_in_set = []
|
|
for inst in sorted(e.grammar['instructions'], key = lambda inst: inst['opcode']):
|
|
operands = [convert_operand_kind(o) for o in inst.get('operands',[])]
|
|
inst_parts = [
|
|
inst['opcode'],
|
|
self.context.AddStringList('operand', operands),
|
|
self.context.AddString(inst['opname']),
|
|
self.context.AddStringList('capability', inst.get('capabilities',[])),
|
|
]
|
|
inst_parts = [str(x) for x in inst_parts]
|
|
insts_in_set.append(' {{{}}}, // {} in {}'.format(
|
|
','.join(inst_parts), inst['opname'], e.name))
|
|
index_by_kind_and_opcode[(e.enum_name,int(inst['opcode']))] = index
|
|
index += 1
|
|
by_value_by_kind[e.enum_name] = IndexRange(len(by_value), len(insts_in_set))
|
|
by_value.extend(insts_in_set)
|
|
|
|
parts: List[str] = []
|
|
parts.append("""// Extended instruction descriptions, ordered by (extinst enum, opcode value).
|
|
// The fields in order are:
|
|
// enum value
|
|
// operands, an IndexRange into kOperandSpans
|
|
// name, a character-counting IndexRange into kStrings
|
|
// capabilities, an IndexRange into kCapabilitySpans""")
|
|
parts.append("static const std::array<ExtInstDesc, {}> kExtInstByValue{{{{".format(len(by_value)))
|
|
parts.extend(by_value)
|
|
parts.append('}};\n')
|
|
self.body_decls.extend(parts)
|
|
|
|
# Create kExtInstByValueRangeForKind
|
|
parts = []
|
|
parts.append("""// Maps an extended instruction enum to possible names for operands of that kind.
|
|
// The result is an IndexRange into kOperandNames, and the names
|
|
// are sorted by name within that span.
|
|
// An optional variant of a kind maps to the details for the corresponding
|
|
// concrete operand kind.""")
|
|
parts = ["IndexRange ExtInstByValueRangeForKind(spv_ext_inst_type_t type) {\n switch(type) {"]
|
|
for name, ir in by_value_by_kind.items():
|
|
parts.append(" case {}: return {};".format(name, ir))
|
|
parts.append(" default: break;");
|
|
parts.append(" }\n return IR(0,0);\n}\n")
|
|
self.body_decls.extend(parts)
|
|
|
|
# Create kExtInstNames
|
|
parts = []
|
|
by_name: List[List[Any]] = []
|
|
by_name_by_kind: Dict[str,IndexRange] = {}
|
|
for e in extinsts:
|
|
# Sort by name within a set
|
|
insts_by_name = sorted(e.grammar['instructions'], key = lambda i: i['opname'])
|
|
insts_in_set = []
|
|
for inst in insts_by_name:
|
|
index = index_by_kind_and_opcode[(e.enum_name,int(inst['opcode']))]
|
|
insts_in_set.append(
|
|
' {{{}, {}}}, // {} in {}'.format(
|
|
str(self.context.AddString(inst['opname'])),
|
|
index,
|
|
inst['opname'],
|
|
e.name))
|
|
by_name_by_kind[e.enum_name] = IndexRange(len(by_name), len(insts_in_set))
|
|
by_name.extend(insts_in_set)
|
|
parts.append("""// Extended instruction opcode names sorted by extended instruction kind, then opcode name.
|
|
// The fields in order are:
|
|
// name
|
|
// index into kExtInstByValue""")
|
|
parts.append("static const std::array<NameIndex, {}> kExtInstNames{{{{".format(len(by_name)))
|
|
parts.extend(by_name)
|
|
parts.append('}};\n')
|
|
self.body_decls.extend(parts)
|
|
|
|
# Create kExtInstNameRangeByKind
|
|
parts = []
|
|
parts.append("""// Maps an extended instruction kind to possible names for instructions of that kind.
|
|
// The result is an IndexRange into kExtInstNames, and the names
|
|
// are sorted by name within that span.""")
|
|
parts = ["IndexRange ExtInstNameRangeForKind(spv_ext_inst_type_t type) {\n switch(type) {"]
|
|
for name, ir in by_name_by_kind.items():
|
|
parts.append(" case {}: return {};".format(name, str(ir)))
|
|
parts.append(" default: break;");
|
|
parts.append(" }\n return IR(0,0);\n}\n")
|
|
self.body_decls.extend(parts)
|
|
|
|
def ComputeLeafTables(self) -> None:
|
|
"""
|
|
Generates the tables that the instruction and operand tables point to.
|
|
The tables are:
|
|
- the string table
|
|
- the table of sequences of:
|
|
- capabilities
|
|
- extensions
|
|
- operands
|
|
|
|
This method must be called after computing instruction and operand tables.
|
|
"""
|
|
|
|
def c_str(s: str):
|
|
"""
|
|
Returns the source for a C string literal or the given string, including
|
|
the explicit null at the end
|
|
"""
|
|
return '"{}\\0"'.format(json.dumps(s).strip('"'))
|
|
|
|
parts: List[str] = []
|
|
parts.append("// Array of characters, referenced by IndexRanges elsewhere.")
|
|
parts.append("// Each IndexRange denotes a string.")
|
|
parts.append('static const char kStrings[] =');
|
|
parts.extend([' {} // {}'.format(c_str(s), str(self.context.strings[s])) for s in self.context.string_buffer])
|
|
parts.append(';\n');
|
|
self.body_decls.extend(parts);
|
|
|
|
parts: List[str] = []
|
|
parts.append("""// Array of IndexRanges, where each represents a string by referencing
|
|
// the kStrings table.
|
|
// This array contains all sequences of alias strings used in the grammar.
|
|
// This table is referenced by an IndexRange elsewhere, i.e. by the 'aliases'
|
|
// field of an instruction or operand description.""")
|
|
parts.append('static const IndexRange kAliasSpans[] = {');
|
|
ranges = self.context.range_buffer['alias']
|
|
for i in range(0, len(ranges)):
|
|
ir = ranges[i]
|
|
parts.append(' {}, // {} {}'.format(str(ir), i, self.context.GetString(ir)))
|
|
parts.append('};\n');
|
|
self.body_decls.extend(parts);
|
|
|
|
parts = []
|
|
parts.append("// Array of capabilities, referenced by IndexRanges elsewhere.")
|
|
parts.append("// Contains all sequences of capabilities used in the grammar.")
|
|
parts.append('static const spv::Capability kCapabilitySpans[] = {');
|
|
capability_ranges = self.context.range_buffer['capability']
|
|
for i in range(0, len(capability_ranges)):
|
|
ir = capability_ranges[i]
|
|
cap = self.context.GetString(ir)
|
|
parts.append(' spv::Capability::{}, // {}'.format(cap, i))
|
|
parts.append('};\n');
|
|
self.body_decls.extend(parts);
|
|
|
|
parts = []
|
|
parts.append("// Array of extensions, referenced by IndexRanges elsewhere.")
|
|
parts.append("// Contains all sequences of extensions used in the grammar.")
|
|
parts.append('static const spvtools::Extension kExtensionSpans[] = {');
|
|
ranges = self.context.range_buffer['extension']
|
|
for i in range(0, len(ranges)):
|
|
ir = ranges[i]
|
|
name = self.context.GetString(ir)
|
|
parts.append(' spvtools::Extension::k{}, // {}'.format(name, i))
|
|
parts.append('};\n');
|
|
self.body_decls.extend(parts);
|
|
|
|
parts = []
|
|
parts.append("// Array of operand types, referenced by IndexRanges elsewhere.")
|
|
parts.append("// Contains all sequences of operand types used in the grammar.")
|
|
parts.append('static const spv_operand_type_t kOperandSpans[] = {');
|
|
ranges = self.context.range_buffer['operand']
|
|
for i in range(0, len(ranges)):
|
|
ir = ranges[i]
|
|
name = self.context.GetString(ir)
|
|
parts.append(' {}, // {}'.format(name, i))
|
|
parts.append('};\n');
|
|
self.body_decls.extend(parts)
|
|
|
|
|
|
def make_path_to_file(f: str) -> None:
|
|
"""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 get_extension_list(instructions, operand_kinds):
|
|
"""Returns extensions as an alphabetically sorted list of strings.
|
|
|
|
Args:
|
|
instructions: list of instruction objects, using the JSON grammar file schema
|
|
operand_kinds: list of operand_kind objects, using the JSON grammar file schema
|
|
"""
|
|
|
|
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 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', metavar='<path>',
|
|
type=str, action='append', required=False, default=None,
|
|
help='extended instruction info: an enum prefix, then a comma, then'
|
|
' the file location of the JSON grammar')
|
|
|
|
parser.add_argument('--core-tables-body-output', metavar='<path>',
|
|
type=str, required=False, default=None,
|
|
help='output file for core SPIR-V grammar tables to be included in .cpp')
|
|
parser.add_argument('--core-tables-header-output', metavar='<path>',
|
|
type=str, required=False, default=None,
|
|
help='output file for core SPIR-V grammar tables to be included in .h')
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.spirv_core_grammar is None:
|
|
print('error: missing --spirv-core-grammar ')
|
|
sys.exit(1)
|
|
if (args.core_tables_body_output is None) and (args.core_tables_header_output is None):
|
|
print('error: need at least one of --core-tables-body-output --core-tables-header-output ')
|
|
sys.exit(1)
|
|
if len(args.extinst) < 1:
|
|
print('error: missing --extinst ')
|
|
sys.exit(1)
|
|
|
|
# Load the JSON grammar files.
|
|
extinsts = sorted([ExtInst(e) for e in args.extinst], key = lambda e: e.name)
|
|
with open(args.spirv_core_grammar) as json_file:
|
|
core_grammar = json.loads(json_file.read())
|
|
printing_class: List[str] = [e['tag'] for e in core_grammar['instruction_printing_class']]
|
|
|
|
# Collect all operand kinds and instructions, so we can generate
|
|
# extension lists, capability lists, and alias lists.
|
|
# Make a copy to avoid polluting the instruction list.
|
|
instructions = [x for x in core_grammar['instructions']]
|
|
operand_kinds = [x for x in core_grammar['operand_kinds']]
|
|
for e in extinsts:
|
|
instructions.extend(e.grammar.get('instructions',[]))
|
|
operand_kinds.extend(e.grammar.get('operand_kinds',[]))
|
|
|
|
extensions = get_extension_list(instructions, operand_kinds)
|
|
|
|
g = Grammar(extensions, operand_kinds, printing_class)
|
|
g.ComputeOperandTables()
|
|
g.ComputeInstructionTables(core_grammar['instructions'])
|
|
g.ComputeExtendedInstructions(extinsts)
|
|
g.ComputeLeafTables()
|
|
|
|
if args.core_tables_body_output is not None:
|
|
make_path_to_file(args.core_tables_body_output)
|
|
with open(args.core_tables_body_output, 'w') as f:
|
|
f.write('\n'.join(g.body_decls))
|
|
if args.core_tables_header_output is not None:
|
|
make_path_to_file(args.core_tables_header_output)
|
|
with open(args.core_tables_header_output, 'w') as f:
|
|
f.write('\n'.join(g.header_decls))
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
try:
|
|
main()
|
|
except GrammarError as ge:
|
|
print(ge)
|
|
sys.exit(1)
|