From: Ian Romanick <ian.d.roman...@intel.com> v2: Clean ups. Remove some functions that never ended up being used.
v3: After updating spirv.core.grammar.json, fix the handling of ShaderViewportMaskNV. See the comment around line 71 of spirv_capabilities_h.py. v4: Many Python style changes based on feedback from Dylan. Signed-off-by: Ian Romanick <ian.d.roman...@intel.com> --- src/compiler/Makefile.sources | 2 + src/compiler/Makefile.spirv.am | 8 + src/compiler/glsl/meson.build | 5 +- src/compiler/spirv/.gitignore | 2 + src/compiler/spirv/meson.build | 14 ++ src/compiler/spirv/spirv_capabilities_h.py | 365 +++++++++++++++++++++++++++++ 6 files changed, 394 insertions(+), 2 deletions(-) create mode 100644 src/compiler/spirv/spirv_capabilities_h.py diff --git a/src/compiler/Makefile.sources b/src/compiler/Makefile.sources index 2ab8e16..1d67cba 100644 --- a/src/compiler/Makefile.sources +++ b/src/compiler/Makefile.sources @@ -287,6 +287,8 @@ NIR_FILES = \ nir/nir_worklist.h SPIRV_GENERATED_FILES = \ + spirv/spirv_capabilities.cpp \ + spirv/spirv_capabilities.h \ spirv/spirv_info.c SPIRV_FILES = \ diff --git a/src/compiler/Makefile.spirv.am b/src/compiler/Makefile.spirv.am index 9841004..4bc684a 100644 --- a/src/compiler/Makefile.spirv.am +++ b/src/compiler/Makefile.spirv.am @@ -20,6 +20,14 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. +spirv/spirv_capabilities.cpp: spirv/spirv_capabilities_h.py spirv/spirv.core.grammar.json + $(MKDIR_GEN) + $(PYTHON_GEN) $(srcdir)/spirv/spirv_capabilities_h.py $(srcdir)/spirv/spirv.core.grammar.json $@ || ($(RM) $@; false) + +spirv/spirv_capabilities.h: spirv/spirv_capabilities_h.py spirv/spirv.core.grammar.json + $(MKDIR_GEN) + $(PYTHON_GEN) $(srcdir)/spirv/spirv_capabilities_h.py $(srcdir)/spirv/spirv.core.grammar.json $@ || ($(RM) $@; false) + spirv/spirv_info.c: spirv/spirv_info_c.py spirv/spirv.core.grammar.json $(MKDIR_GEN) $(PYTHON_GEN) $(srcdir)/spirv/spirv_info_c.py $(srcdir)/spirv/spirv.core.grammar.json $@ || ($(RM) $@; false) diff --git a/src/compiler/glsl/meson.build b/src/compiler/glsl/meson.build index 5b505c0..6e43f80 100644 --- a/src/compiler/glsl/meson.build +++ b/src/compiler/glsl/meson.build @@ -198,11 +198,12 @@ files_libglsl_standalone = files( libglsl = static_library( 'glsl', [files_libglsl, glsl_parser, glsl_lexer_cpp, ir_expression_operation_h, - ir_expression_operation_strings_h, ir_expression_operation_constant_h], + ir_expression_operation_strings_h, ir_expression_operation_constant_h, + spirv_capabilities_cpp, spirv_capabilities_h], c_args : [c_vis_args, c_msvc_compat_args, no_override_init_args], cpp_args : [cpp_vis_args, cpp_msvc_compat_args], link_with : [libnir, libglcpp], - include_directories : [inc_common, inc_compiler, inc_nir], + include_directories : [inc_common, inc_compiler, inc_nir, inc_spirv], build_by_default : false, ) diff --git a/src/compiler/spirv/.gitignore b/src/compiler/spirv/.gitignore index f723c31..6b5ef0a 100644 --- a/src/compiler/spirv/.gitignore +++ b/src/compiler/spirv/.gitignore @@ -1 +1,3 @@ +/spirv_capabilities.cpp +/spirv_capabilities.h /spirv_info.c diff --git a/src/compiler/spirv/meson.build b/src/compiler/spirv/meson.build index 8f1a02e..8b6071a 100644 --- a/src/compiler/spirv/meson.build +++ b/src/compiler/spirv/meson.build @@ -24,3 +24,17 @@ spirv_info_c = custom_target( output : 'spirv_info.c', command : [prog_python2, '@INPUT0@', '@INPUT1@', '@OUTPUT@'], ) + +spirv_capabilities_cpp = custom_target( + 'spirv_capabilities.cpp', + input : files('spirv_capabilities_h.py', 'spirv.core.grammar.json'), + output : 'spirv_capabilities.cpp', + command : [prog_python2, '@INPUT0@', '--gen-cpp', '@OUTPUT@', '@INPUT1@'], +) + +spirv_capabilities_h = custom_target( + 'spirv_capabilities.h', + input : files('spirv_capabilities_h.py', 'spirv.core.grammar.json'), + output : 'spirv_capabilities.h', + command : [prog_python2, '@INPUT0@', '--gen-h', '@OUTPUT@', '@INPUT1@'], +) diff --git a/src/compiler/spirv/spirv_capabilities_h.py b/src/compiler/spirv/spirv_capabilities_h.py new file mode 100644 index 0000000..78b1166 --- /dev/null +++ b/src/compiler/spirv/spirv_capabilities_h.py @@ -0,0 +1,365 @@ +COPYRIGHT = """\ +/* + * Copyright (C) 2017 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +""" + +import argparse +import json +from sys import stdout, stderr, exit +from mako.template import Template + +def parse_args(): + p = argparse.ArgumentParser() + p.add_argument("json", help="SPIR-V definition JSON") + p.add_argument("--gen-cpp", help="Name of the C++ file to generate") + p.add_argument("--gen-h", help="Name of the header file to generate") + return p.parse_args() + +def collect_data(spirv): + for x in spirv["operand_kinds"]: + # There should only be one set of Capability enums in the JSON. + if x["kind"] == "Capability": + operands = x + break + + # There are some duplicate values in some of the tables (thanks guys!), so + # filter them out. + + # by_value is a dictionary that maps values of enumerants to tuples of + # enumerant names and capabilities. + by_value = {} + + # by_name is a dictionary that maps names of enumerants to tuples of + # values and required capabilities. + by_name = {} + + for x in operands["enumerants"]: + caps = x.get("capabilities", []) + + if x["value"] not in by_value: + by_value[x["value"]] = (x["enumerant"], caps) + + by_name[x["enumerant"]] = (x["value"], caps) + + # Recall that there are some duplicate values in the table. These + # duplicate values also appear in the "capabilities" list for some + # enumerants. Filter out the duplicates there as well. + for capability, (_, dependencies) in by_name.iteritems(): + for dependency in dependencies: + dep_value, _ = by_name[dependency] + real_dependency, _ = by_value[dep_value] + + # In most cases where there is a duplicate capability, things that + # depend on one will also depend on the others. + # StorageBuffer16BitAccess and StorageUniformBufferBlock16 have + # the same value, and StorageUniform16 depends on both. + # + # There are exceptions. ShaderViewportIndexLayerEXT and + # ShaderViewportIndexLayerNV have the same value, but + # ShaderViewportMaskNV only depends on ShaderViewportIndexLayerNV. + # + # That's the only case so far, so emit a warning for other cases + # that have more than one dependency. That way we can double + # check that they are handled correctly. + + if real_dependency != dependency: + if real_dependency not in by_name[capability][1]: + if len(by_name[capability][1]) > 1: + print("Warning! Removed {} from {}, but no name with the same value is in the dependency list.".format(dependency, capability)) + else: + if len(by_name[capability][1]) == 1: + stderr.write("Error! Cannot remove {} from {} because it is the only dependency.\n".format(dependency, capability)) + exit(1) + + by_name[capability][1].remove(dependency) + + # The table generated from this data and the C code that uses it + # assumes that each capability has a single dependency. That is + # currently the case, but it may change in the future. + if len(by_name[capability][1]) > 1: + stderr.write("Error! Too many dependencies left for {}. {}\n".format(capability, by_name[capability][1])) + exit(1) + + for cap_value in by_value: + name, _ = by_value[cap_value] + by_value[cap_value] = (name, by_name[name][1]) + + return (by_name, by_value) + +TEMPLATE_H = Template(COPYRIGHT + """\ +#ifndef SPIRV_CAPABILITIES_H +#define SPIRV_CAPABILITIES_H + +#include <stdint.h> +#include "spirv.h" +#include "util/bitset.h" +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) +#endif + +#define NO_DEPENDENCY ((${"uint16_t" if len(all_values) > 256 else "uint8_t"}) ~0) + +class spirv_capability_set; + +/** + * Iterator for the enabled capabilities in a spirv_capability_set + * + * Roughly, this is a wrapper for the bitset iterator functions. Dereferencing + * the iterator results in the \c SpvCapability where as the bitset iterator + * functions provide the index in the bitset. The mapping is handled by + * \c spirv_capability_set. + */ +class spirv_capability_set_iterator { +public: + spirv_capability_set_iterator(const spirv_capability_set *_s); + inline bool operator==(const spirv_capability_set_iterator &that) const; + inline bool operator!=(const spirv_capability_set_iterator &that) const; + spirv_capability_set_iterator &operator++(); + SpvCapability operator*() const; + +private: + /** Capability set being iterated. */ + const spirv_capability_set *s; + + /** Current index in the set. */ + unsigned i; + + /** Temporary storage used by \c __bitset_next_set */ + BITSET_WORD tmp; + + /** + * Construct an iterator starting a specific index + * + * Used by \c spirv_capability_set::end(). + */ + spirv_capability_set_iterator(const spirv_capability_set *_s, unsigned _i); + + friend class spirv_capability_set; +}; + +/** + * Set of SPIR-V capabilities + * + * All SPIR-V capability values are mapped to a compacted bitset. The mapping + * of implemented in the (private) methods \c ::capability_as_index() and + * \c ::index_as_capability(). Once all of the capabilities for a particular + * SPIR-V program have been enabled (by repeatedly calling \c ::enable()), the + * set can be reduced to the minimum set by \c ::reduce(). + */ +class spirv_capability_set { +public: + /** + * Construct a capability set with no capabilities enabled. + */ + spirv_capability_set() + { + BITSET_ZERO(capabilities); + } + + /** + * Enable the specified SPIR-V capability + */ + inline void enable(SpvCapability cap) + { + BITSET_SET(capabilities, capability_as_index(cap)); + } + + /** + * Determine whether or not the specified SPIR-V capability is enabled + */ + inline bool is_enabled(SpvCapability cap) + { + return BITSET_TEST(capabilities, capability_as_index(cap)); + } + + /** + * Reduce a set of SPIR-V capabilities to the minimal set. + * + * Many SPIR-V capabilities imply other capabilities. For example, + * \c SpvCapabilityShader implies \c SpvCapabilityMatrix, so only the + * former needs to be set. This method removes all the redundant + * capabilities so that the minimal set of \c SpvOpCapability instructions + * can be written to the output. + */ + void reduce() + { + for (unsigned i = 0; i < ${len(all_values)}; ++i) { + if (BITSET_TEST(capabilities, i)) { + /* Walk back up the dependency chain clearing all the bits along + * the way. This is necessary because some of the dependencies + * might not be set, so we cannot rely on the dependency-of-a- + * dependency to be cleared automatically. + */ + unsigned dep = dependency_map[i]; + while (dep != NO_DEPENDENCY) { + BITSET_CLEAR(capabilities, dep); + dep = dependency_map[dep]; + } + } + } + } + + spirv_capability_set_iterator begin() const + { + return spirv_capability_set_iterator(this); + } + + spirv_capability_set_iterator end() const + { + return spirv_capability_set_iterator(this, ${len(all_values)}); + } + +private: + /** + * Map a SPIR-V capability to its location in the bitfield. + */ + static unsigned capability_as_index(SpvCapability cap) + { + if (cap <= SpvCapability${by_value[[x for x in all_values if x < 4096][-1]][0]}) + return unsigned(cap); + + switch (cap) { + % for x in all_values: + % if x >= 4096: + case SpvCapability${by_value[x][0]}: return ${all_values.index(x)}; + % endif + % endfor + default: + unreachable("Invalid capability."); + } + } + + /** + * Map a location in the bitfield to the SPIR-V capability it represents. + */ + static SpvCapability index_as_capability(unsigned i) + { + if (i <= ${[x for x in all_values if x < 4096][-1]}) + return SpvCapability(i); + + switch (i) { + % for x in all_values: + % if x >= 4096: + case ${all_values.index(x)}: return SpvCapability${by_value[x][0]}; + % endif + % endfor + default: + unreachable("Invalid capability index."); + } + } + + BITSET_DECLARE(capabilities, ${len(all_values)}); + + static const ${"uint16_t" if len(all_values) > 256 else "uint8_t"} dependency_map[${len(all_values)}]; + + friend class spirv_capability_set_iterator; +}; + +inline spirv_capability_set_iterator::spirv_capability_set_iterator(const spirv_capability_set *_s) + : s(_s), i(0), tmp(_s->capabilities[0]) +{ + i = __bitset_next_set(i, &tmp, s->capabilities, ${len(all_values)}); +} + +inline spirv_capability_set_iterator::spirv_capability_set_iterator( + const spirv_capability_set *_s, unsigned _i) + : s(_s), i(_i) +{ + tmp = s->capabilities[unsigned(i / BITSET_WORDBITS)]; + tmp &= ~((1u << (i % BITSET_WORDBITS))- 1); +} + +inline bool spirv_capability_set_iterator::operator==(const spirv_capability_set_iterator &that) const +{ + return s == that.s && i == that.i; +} + +inline bool spirv_capability_set_iterator::operator!=(const spirv_capability_set_iterator &that) const +{ + return !(*this == that); +} + +inline spirv_capability_set_iterator &spirv_capability_set_iterator::operator++() +{ + i = __bitset_next_set(i, &tmp, s->capabilities, ${len(all_values)}); + return *this; +} + +inline SpvCapability spirv_capability_set_iterator::operator*() const +{ + return spirv_capability_set::index_as_capability(i); +} + +#undef NO_DEPENDENCY + +#endif /* SPIRV_CAPABILITIES_H */ +""") + + +TEMPLATE_CPP = Template("""\ +/* DO NOT EDIT - This file is generated automatically by spirv_capabilities_h.py script */ + +""" + COPYRIGHT + """\ +#include "util/macros.h" /* for unreachable() */ +#include "spirv_capabilities.h" + +#define NO_DEPENDENCY ((${"uint16_t" if len(all_values) > 256 else "uint8_t"}) ~0) + +const ${"uint16_t" if len(all_values) > 256 else "uint8_t"} spirv_capability_set::dependency_map[${len(all_values)}] = { + % for x in all_values: + % if x not in by_value or len(by_value[x][1]) == 0: + NO_DEPENDENCY${"," if all_values[-1] != x else ""} + % else: + ${all_values.index(by_name[by_value[x][1][0]][0])}${"," if all_values[-1] != x else ""} + % endif + % endfor +}; +""") + +if __name__ == "__main__": + pargs = parse_args() + + if not pargs.gen_cpp and not pargs.gen_h: + stderr.write("At least one of --gen-cpp or --gen-h must be supplied.\n") + exit(1) + + with open(pargs.json, 'r') as f: + spirv_info = json.load(f) + + by_name, by_value = collect_data(spirv_info) + + # Assume the "core" values will be fairly tightly packed. + max_core_value = max([x for _, (x, _) in by_name.items() if int(x) < 4096]) + all_values = [x for x in xrange(max_core_value + 1)] + [cap_value for cap_value, _ in sorted(by_value.items()) if cap_value >= 4096] + + if pargs.gen_cpp: + with open(pargs.gen_cpp, 'w') as f: + f.write(TEMPLATE_CPP.render(by_name=by_name, + by_value=by_value, + all_values=all_values)) + + if pargs.gen_h: + with open(pargs.gen_h, 'w') as f: + f.write(TEMPLATE_H.render(by_name=by_name, + by_value=by_value, + all_values=all_values)) -- 2.9.5 _______________________________________________ mesa-dev mailing list mesa-dev@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/mesa-dev