More automatic extension support

Update grammar table generation:
- Get extensions from instructions, not just operand-kinds
- Don't explicitly list extensions that come from the SPIR-V core
  grammar or from a KHR extended instruction set grammar.

This makes it easier to support new extensions since the recommended
extension strategy is to add instructions to the core grammar file.

Also, test the validator has trivial support for passing through
the extensions SPV_NV_shader_subgroup_partitioned and
SPV_EXT_descriptor_indexing.
This commit is contained in:
David Neto 2018-04-05 18:04:27 -04:00
parent 43ca2112b8
commit 082b8b08f1
2 changed files with 43 additions and 86 deletions

View File

@ -62,7 +62,8 @@ INSTANTIATE_TEST_CASE_P(
"SPV_KHR_shader_atomic_counter_ops", "SPV_EXT_shader_stencil_export",
"SPV_EXT_shader_viewport_index_layer",
"SPV_AMD_shader_image_load_store_lod", "SPV_AMD_shader_fragment_mask",
"SPV_GOOGLE_decorate_string", "SPV_GOOGLE_hlsl_functionality1"));
"SPV_GOOGLE_decorate_string", "SPV_GOOGLE_hlsl_functionality1",
"SPV_NV_shader_subgroup_partitioned", "SPV_EXT_descriptor_indexing"));
INSTANTIATE_TEST_CASE_P(FailSilently, ValidateUnknownExtensions,
Values("ERROR_unknown_extension", "SPV_KHR_",

View File

@ -25,39 +25,13 @@ import re
PYGEN_VARIABLE_PREFIX = 'pygen_variable'
# Extensions to recognize, but which don't necessarily come from the SPIR-V
# core grammar. Get this list from the SPIR-V registery web page.
EXTENSIONS_FROM_SPIRV_REGISTRY = """
SPV_AMD_shader_explicit_vertex_parameter
SPV_AMD_shader_trinary_minmax
# core or KHR grammar files. Get this list from the SPIR-V registery 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_KHR_shader_ballot
SPV_AMD_shader_ballot
SPV_AMD_gpu_shader_half_float
SPV_KHR_shader_draw_parameters
SPV_KHR_subgroup_vote
SPV_KHR_16bit_storage
SPV_KHR_device_group
SPV_KHR_multiview
SPV_NVX_multiview_per_view_attributes
SPV_NV_viewport_array2
SPV_NV_stereo_view_rendering
SPV_NV_sample_mask_override_coverage
SPV_NV_geometry_shader_passthrough
SPV_AMD_texture_gather_bias_lod
SPV_KHR_storage_buffer_storage_class
SPV_KHR_variable_pointers
SPV_AMD_gpu_shader_int16
SPV_KHR_post_depth_coverage
SPV_KHR_shader_atomic_counter_ops
SPV_EXT_shader_stencil_export
SPV_EXT_shader_viewport_index_layer
SPV_AMD_shader_image_load_store_lod
SPV_AMD_shader_fragment_mask
SPV_EXT_fragment_fully_covered
SPV_AMD_gpu_shader_half_float_fetch
SPV_GOOGLE_decorate_string
SPV_GOOGLE_hlsl_functionality1
SPV_NV_shader_subgroup_partitioned
SPV_AMD_shader_trinary_minmax
"""
@ -242,6 +216,7 @@ class InstInitializer(object):
- operands: a sequence of (operand-kind, operand-quantifier) tuples
- version: minimal SPIR-V version required for this opcode
"""
assert opname.startswith('Op')
self.opname = opname[2:] # Remove the "Op" prefix.
self.num_caps = len(caps)
@ -524,15 +499,25 @@ def generate_operand_kind_table(enums):
return '\n\n'.join((caps_arrays,) + (exts_arrays,) + enum_entries + (table,))
def get_extension_list(operands):
def get_extension_list(instructions, operand_kinds):
"""Returns extensions as an alphabetically sorted list of strings."""
enumerants = sum([item.get('enumerants', []) for item in operands
if item.get('category') in ['ValueEnum']], [])
extensions = sum([item.get('extensions', []) for item in enumerants
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')], [])
extensions.extend(EXTENSIONS_FROM_SPIRV_REGISTRY.split())
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
@ -543,55 +528,22 @@ def get_extension_list(operands):
return sorted(set(extensions))
def get_capabilities(operands):
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 operands
enumerants = sum([item.get('enumerants', []) for item in operand_kinds
if item.get('kind') in ['Capability']], [])
return enumerants
def generate_extension_enum(operands):
def generate_extension_enum(extensions):
"""Returns enumeration containing extensions declared in the grammar."""
extensions = get_extension_list(operands)
return ',\n'.join(['k' + extension for extension in extensions])
def generate_extension_to_string_table(operands):
"""Returns extension to string mapping table."""
extensions = get_extension_list(operands)
entry_template = ' {{Extension::k{extension},\n "{extension}"}}'
table_entries = [entry_template.format(extension=extension)
for extension in extensions]
table_template = '{{\n{enums}\n}}'
return table_template.format(enums=',\n'.join(table_entries))
def generate_string_to_extension_table(operands):
"""Returns string to extension mapping table."""
extensions = get_extension_list(operands)
entry_template = ' {{"{extension}",\n Extension::k{extension}}}'
table_entries = [entry_template.format(extension=extension)
for extension in extensions]
table_template = '{{\n{enums}\n}}'
return table_template.format(enums=',\n'.join(table_entries))
def generate_capability_to_string_table(operands):
"""Returns capability to string mapping table."""
capabilities = [item.get('enumerant')
for item in get_capabilities(operands)]
entry_template = ' {{SpvCapability{capability},\n "{capability}"}}'
table_entries = [entry_template.format(capability=capability)
for capability in capabilities]
table_template = '{{\n{enums}\n}}'
return table_template.format(enums=',\n'.join(table_entries))
def generate_extension_to_string_mapping(operands):
def generate_extension_to_string_mapping(extensions):
"""Returns mapping function from extensions to corresponding strings."""
extensions = get_extension_list(operands)
function = 'const char* ExtensionToString(Extension extension) {\n'
function += ' switch (extension) {\n'
template = ' case Extension::k{extension}:\n' \
@ -602,9 +554,8 @@ def generate_extension_to_string_mapping(operands):
return function
def generate_string_to_extension_mapping(operands):
def generate_string_to_extension_mapping(extensions):
"""Returns mapping function from strings to corresponding extensions."""
extensions = get_extension_list(operands) # Already sorted
function = '''
bool GetExtensionFromString(const char* str, Extension* extension) {{
@ -627,7 +578,7 @@ def generate_string_to_extension_mapping(operands):
return function
def generate_capability_to_string_mapping(operands):
def generate_capability_to_string_mapping(operand_kinds):
"""Returns mapping function from capabilities to corresponding strings.
We take care to avoid emitting duplicate values.
"""
@ -636,7 +587,7 @@ def generate_capability_to_string_mapping(operands):
template = ' case SpvCapability{capability}:\n' \
' return "{capability}";\n'
emitted = set() # The values of capabilities we already have emitted
for capability in get_capabilities(operands):
for capability in get_capabilities(operand_kinds):
value = capability.get('value')
if value not in emitted:
emitted.add(value)
@ -648,12 +599,12 @@ def generate_capability_to_string_mapping(operands):
return function
def generate_all_string_enum_mappings(operands):
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(operands))
tables.append(generate_string_to_extension_mapping(operands))
tables.append(generate_capability_to_string_mapping(operands))
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)
@ -740,25 +691,30 @@ def main():
if args.spirv_core_grammar is not None:
with open(args.spirv_core_grammar) as json_file:
grammar = json.loads(json_file.read())
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())
operand_kinds = grammar['operand_kinds']
instructions = []
instructions.extend(core_grammar['instructions'])
instructions.extend(debuginfo_grammar['instructions'])
operand_kinds = []
operand_kinds.extend(core_grammar['operand_kinds'])
operand_kinds.extend(debuginfo_grammar['operand_kinds'])
extensions = get_extension_list(instructions, 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)
print(generate_instruction_table(grammar['instructions']),
print(generate_instruction_table(core_grammar['instructions']),
file=open(args.core_insts_output, 'w'))
print(generate_operand_kind_table(operand_kinds),
file=open(args.operand_kinds_output, 'w'))
if args.extension_enum_output is not None:
make_path_to_file(args.extension_enum_output)
print(generate_extension_enum(grammar['operand_kinds']),
print(generate_extension_enum(extensions),
file=open(args.extension_enum_output, 'w'))
if args.enum_string_mapping_output is not None:
make_path_to_file(args.enum_string_mapping_output)
print(generate_all_string_enum_mappings(operand_kinds),
print(generate_all_string_enum_mappings(extensions, operand_kinds),
file=open(args.enum_string_mapping_output, 'w'))
if args.extinst_glsl_grammar is not None: