[Piglit] [PATCH v2 07/13] piglit-dispatch: Code generation scripts.

Paul Berry stereotype441 at gmail.com
Tue Mar 13 17:22:10 PDT 2012


This patch contains the code generation scripts that convert the files
gl.spec, gl.tm, and enumext.spec into C code, as well as the CMake
directives to run the scripts.

Code generation occurs in two phases: first the script
glapi/parse_glspec.py converts the .spec and .tm files into an
intermediate JSON format, which is stored in glapi/glapi.json.  Then,
the script tests/util/gen_dispatch.py converts the JSON file into two
files, tests/util/generated_dispatch.c and
tests/util/generated_dispatch.h.

There are two motivations for breaking code generation into two phases:
(1) there are other code generation tasks we may add in the future
(e.g. generating the tests/util/piglit-util-enum.c file), and it would
be nice not to have to rewrite any parsing code when we do so.  (2) if
the GL consortium ever decides to change the format of gl.spec, gl.tm,
and enumext.spec, then we only have to change parse_glspec.py.

These code generation scripts can be run by invoking "make
piglit_dispatch_gen" from the toplevel source directory.

v2: Add clarifying comments, remove a bogus "print" statement, and
raise an exception if an unrecognized multiplicity is encountered.
Also, handle "reference" multiplicity.
---
 CMakeLists.txt              |    3 +
 cmake/piglit_dispatch.cmake |   45 ++++
 cmake/piglit_glapi.cmake    |   40 +++
 glapi/parse_glspec.py       |  469 ++++++++++++++++++++++++++++++++
 tests/util/gen_dispatch.py  |  616 +++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1173 insertions(+), 0 deletions(-)
 create mode 100644 cmake/piglit_dispatch.cmake
 create mode 100644 cmake/piglit_glapi.cmake
 create mode 100644 glapi/parse_glspec.py
 create mode 100644 tests/util/gen_dispatch.py

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3d7bf35..5d814d2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -169,6 +169,9 @@ configure_file(
 	"${piglit_BINARY_DIR}/tests/util/config.h"
 )
 
+include(cmake/piglit_glapi.cmake)
+include(cmake/piglit_dispatch.cmake)
+
 include_directories(src)
 add_subdirectory(cmake/target_api)
 add_subdirectory(generated_tests)
diff --git a/cmake/piglit_dispatch.cmake b/cmake/piglit_dispatch.cmake
new file mode 100644
index 0000000..e2c0ef9
--- /dev/null
+++ b/cmake/piglit_dispatch.cmake
@@ -0,0 +1,45 @@
+# Copyright 2012 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.
+
+# Note: we're outputting the generated files to a subdirectory of
+# ${CMAKE_SOURCE_DIR} so that we can check them back in to source
+# control.
+set(piglit_dispatch_gen_output_dir ${CMAKE_SOURCE_DIR}/tests/util)
+
+set(piglit_dispatch_gen_outputs
+	${piglit_dispatch_gen_output_dir}/generated_dispatch.c
+	${piglit_dispatch_gen_output_dir}/generated_dispatch.h
+	)
+
+set(piglit_dispatch_gen_inputs
+	${CMAKE_SOURCE_DIR}/tests/util/gen_dispatch.py
+	${CMAKE_SOURCE_DIR}/glapi/glapi.json
+	)
+
+add_custom_command(
+	OUTPUT ${piglit_dispatch_gen_outputs}
+	DEPENDS ${piglit_dispatch_gen_inputs}
+	COMMAND ${python} ${piglit_dispatch_gen_inputs} ${piglit_dispatch_gen_outputs}
+	)
+
+add_custom_target(piglit_dispatch_gen
+	DEPENDS ${piglit_dispatch_gen_outputs}
+	)
diff --git a/cmake/piglit_glapi.cmake b/cmake/piglit_glapi.cmake
new file mode 100644
index 0000000..e22403d
--- /dev/null
+++ b/cmake/piglit_glapi.cmake
@@ -0,0 +1,40 @@
+# Copyright 2012 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.
+
+# Note: we're outputting the generated file to a subdirectory of
+# ${CMAKE_SOURCE_DIR} so that we can check it back in to source
+# control.
+set(piglit_glapi_dir ${CMAKE_SOURCE_DIR}/glapi)
+
+set(piglit_glapi_output ${piglit_glapi_dir}/glapi.json)
+
+set(piglit_glapi_inputs
+	${piglit_glapi_dir}/parse_glspec.py
+	${piglit_glapi_dir}/gl.tm
+	${piglit_glapi_dir}/gl.spec
+	${piglit_glapi_dir}/enumext.spec
+	)
+
+add_custom_command(
+	OUTPUT ${piglit_glapi_output}
+	DEPENDS ${piglit_glapi_inputs}
+	COMMAND ${python} ${piglit_glapi_inputs} ${piglit_glapi_output}
+	)
diff --git a/glapi/parse_glspec.py b/glapi/parse_glspec.py
new file mode 100644
index 0000000..ac624eb
--- /dev/null
+++ b/glapi/parse_glspec.py
@@ -0,0 +1,469 @@
+# Copyright 2012 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.
+
+# This script generates a JSON description of the GL API based on
+# source files published by www.opengl.org.
+#
+# The source file "gl.tm" is a CSV file mapping from abstract type
+# names (in column 0) to C types (in column 3).  All other columns are
+# ignored.
+#
+#
+# The source file "gl.spec" consists of a record for each API
+# function, which looks like this:
+#
+# # Function name (excluding the "gl" prefix), followed by the names
+# # of all function parameters:
+# GetVertexAttribdvARB(index, pname, params)
+#
+#         # Property/value pairs follow.  Order is irrelevant.
+#
+#         # "return" specifies the return type (as a reference to
+#         # column 0 of gl.tm).
+#         return          void
+#
+#         # "param" specifies the type of a single function parameter
+#         # (as a reference to column 0 of gl.tm).  In addition, it
+#         # specifies whether the parameter is passed as an input
+#         # value, as an input or output array, or an input or output
+#         # reference.  Input arrays and references get translated
+#         # into const pointers, and Output arrays and references get
+#         # translated into non-const pointers.  Note that for arrays,
+#         # the size of the array appears in brackets after the word
+#         # "array".  This value is ignored.
+#         param           index           UInt32 in value
+#         param           pname           VertexAttribPropertyARB in value
+#         param           params          Float64 out array [4]
+#
+#         # "category" specifies which extension (or GL version) this
+#         # function was introduced in.  For extensions, the category
+#         # name is the extension name (without the "GL_" prefix).
+#         # For GL versions, the category name looks like
+#         # e.g. "VERSION_1_0" or "VERSION_1_0_DEPRECATED" (for
+#         # deprecated features).
+#         category        ARB_vertex_program
+#
+#         # "alias" specifies the name of a function that is
+#         # behaviorally indistinguishable from this function (if
+#         # any).
+#         alias           GetVertexAttribdv
+#
+#         # Other property/value pairs are ignored.
+#
+# # Lines in any other format are ignored.
+#
+#
+# The source file "enumext.spec" consists of lines of the form <enum
+# name> = <value>, e.g.:
+#
+#         FRONT                                      = 0x0404
+#
+# The enum name is the name without the "GL_" prefix.
+#
+# It is also permissible for the value to be a reference to an enum
+# that appeared earlier, e.g.:
+#
+#         DRAW_FRAMEBUFFER_BINDING                   = GL_FRAMEBUFFER_BINDING
+#
+# Note that when this happens the value *does* include the "GL_"
+# prefix.
+#
+#
+# The JSON output format is as follows:
+# {
+#   "categories": {
+#     <category name>: {
+#       "kind": <"GL" for a GL version, "extension" for an extension>,
+#       "gl_10x_version": <For a GL version, version number times 10>,
+#       "extension_name" <For an extension, name of the extension>
+#     }, ...
+#   },
+#   "enums": {
+#     <enum name, without "GL_" prefix>: {
+#       "value_int": <value integer>
+#       "value_str": <value string>
+#     }, ...
+#   },
+#   "functions": {
+#     <function name, without "gl" prefix>: {
+#       "category": <category in which this function appears>,
+#       "param_names": <list of param names>,
+#       "param_types": <list of param types>,
+#       "return_type": <type, or "void" if no return>
+#     }, ...
+#   },
+#   "function_alias_sets": {
+#     <list of synonymous function names>, ...
+#   },
+# }
+
+
+
+import collections
+import csv
+import json
+import re
+import sys
+
+
+GLSPEC_HEADER_REGEXP = re.compile(r'^(\w+)\((.*)\)$')
+GLSPEC_ATTRIBUTE_REGEXP = re.compile(r'^\s+(\w+)\s+(.*)$')
+GL_VERSION_REGEXP = re.compile('^VERSION_([0-9])_([0-9])(_DEPRECATED)?$')
+ENUM_REGEXP = re.compile(r'^\s+(\w+)\s+=\s+(\w+)$')
+
+
+# Convert a type into a canonical form that is consistent about its
+# use of spaces.
+#
+# Example input: 'const       void**'
+# Example output: 'const void * *'
+def normalize_type(typ):
+    tokens = [token for token in typ.replace('*', ' * ').split(' ')
+              if token != '']
+    return ' '.join(tokens)
+
+
+# Interpret an enumerated value, which may be in hex or decimal, and
+# may include a type suffix such as "ull".
+#
+# Example input: '0xFFFFFFFFul'
+# Example output: 4294967295
+def decode_enum_value(value_str):
+    for suffix in ('u', 'ul', 'ull'):
+        if value_str.endswith(suffix):
+            value_str = value_str[:-len(suffix)]
+            break
+    return int(value_str, 0)
+
+
+# Convert an object to a form that can be serialized to JSON.  Python
+# "set" objects are converted to lists.
+def jsonize(obj):
+    if type(obj) in (set, frozenset):
+        return sorted(obj)
+    else:
+        raise Exception('jsonize failed for {0}'.format(type(obj)))
+
+
+# Iterate through the lines of a file, discarding end-of-line comments
+# delimited by "#".  Blank lines are discarded, as well as any
+# whitespace at the end of a line.
+def filter_comments(f):
+    for line in f:
+        if '#' in line:
+            line = line[:line.find('#')]
+        line = line.rstrip()
+        if line != '':
+            yield line.rstrip()
+
+
+# Convert a category name from the form used in the gl.spec file to
+# the form we want to output in JSON.  E.g.:
+#
+# - "2.1" is converted into { 'kind': 'GL', 'gl_10x_version': 21 }
+#
+# - "FOO" is converted into { 'kind': 'extension', 'extension_name': 'GL_FOO' }
+def translate_category(category_name):
+    m = GL_VERSION_REGEXP.match(category_name)
+    if m:
+        ones = int(m.group(1))
+        tenths = int(m.group(2))
+        return '{0}.{1}'.format(ones, tenths), {
+            'kind': 'GL',
+            'gl_10x_version': 10 * ones + tenths
+            }
+    else:
+        extension_name = 'GL_' + category_name
+        return extension_name, {
+            'kind': 'extension',
+            'extension_name': extension_name
+            }
+
+
+# Data structure keeping track of which function names are known, and
+# which names are synonymous with which other names.
+class SynonymMap(object):
+    def __init__(self):
+	# __name_to_synonyms maps from a function name to the set of
+	# all names that are synonymous with it (including itself).
+	self.__name_to_synonyms = {}
+
+    # Add a single function name which is not (yet) known to be
+    # synonymous with any other name.  No effect if the function name
+    # is already known.
+    def add_singleton(self, name):
+	if name not in self.__name_to_synonyms:
+	    self.__name_to_synonyms[name] = frozenset([name])
+	return self.__name_to_synonyms[name]
+
+    # Add a pair of function names, and note that they are synonymous.
+    # Synonymity is transitive, so if either of the two function names
+    # previously had known synonyms, all synonyms are combined into a
+    # single set.
+    def add_alias(self, name, alias):
+	name_ss = self.add_singleton(name)
+	alias_ss = self.add_singleton(alias)
+	combined_set = name_ss | alias_ss
+	for n in combined_set:
+	    self.__name_to_synonyms[n] = combined_set
+
+    # Get a set of sets of synonymous functions.
+    def get_synonym_sets(self):
+        return frozenset(self.__name_to_synonyms.values())
+
+
+# In-memory representation of the GL API.
+class Api(object):
+    def __init__(self):
+	# Api.type_translation is a dict mapping abstract type names
+	# to C types.  It is based on the data in the gl.tm file.  For
+	# example, the dict entry for String is:
+	#
+	# 'String': 'const GLubyte *'
+        self.type_translation = {}
+
+	# Api.enums is a dict mapping enum names (without the 'GL_'
+	# prefix) to a dict containing (a) the enum value expressed as
+	# an integer, and (b) the enum value expressed as a C literal.
+	# It is based on the data in the gl.spec file.  For example,
+	# the dict entry for GL_CLIENT_ALL_ATTRIB_BITS is:
+	#
+	# 'CLIENT_ALL_ATTRIB_BITS': { 'value_int': 4294967295,
+	#                             'value_str': "0xFFFFFFFF" }
+        self.enums = {}
+
+	# Api.functions is a dict mapping function names (without the
+	# 'gl' prefix) to a dict containing (a) the name of the
+	# category the function is in, (b) the function call parameter
+	# names, (c) the function call parameter types, and (d) the
+	# function return type.  It is based on the data in the
+	# gl.spec file, cross-referenced against the type translations
+	# from the gl.tm file.  For example, the dict entry for
+	# glAreTexturesResident is:
+	#
+	# 'AreTexturesResident': {
+	#    'category': '1.1',
+	#    'param_names': ['n', 'textures', 'residences'],
+	#    'param_types': ['GLsizei', 'const GLuint *', 'GLboolean *'],
+	#    'return_type': ['GLboolean'] }
+        self.functions = {}
+
+	# Api.synonyms is a SynonymMap object which records which
+	# function names are aliases of each other.  It is based on
+	# the "alias" declarations from the gl.spec file.
+        self.synonyms = SynonymMap()
+
+	# Api.categories is a dict mapping category names to a dict
+	# describing the category.  For categories representing a GL
+	# version, the dict entry looks like this:
+	#
+	# '2.1': { 'kind': 'GL', 'gl_10x_version': 21 }
+	#
+	# For categories representing an extension, the dict entry
+	# looks like this:
+	#
+	# 'GL_ARB_sync': { 'kind': 'extension',
+	#                  'extension_name': 'GL_ARB_sync' }
+	self.categories = {}
+
+    # Convert each line in the gl.tm file into a key/value pair in
+    # self.type_translation, mapping an abstract type name to a C
+    # type.
+    def read_gl_tm(self, f):
+        for line in csv.reader(filter_comments(f)):
+            name = line[0].strip()
+            typ = line[3].strip()
+            if typ == '*':
+                # gl.tm uses "*" to represent void (for void used as a
+                # return value).
+                typ = 'void'
+            self.type_translation[name] = normalize_type(typ)
+
+    # Group the lines in the gl.spec file into triples (function_name,
+    # param_names, attributes).  For example, the following gl.spec
+    # input:
+    #
+    # Foo(bar, baz):
+    #     x  value1
+    #     y  value2 other_info
+    #     y  value3 more_info
+    #
+    # Produces this output triple:
+    #
+    # ('Foo', ['bar', 'baz'],
+    #  {'x': ['value1'], 'y': ['value2 other_info', 'value3 more_info']})
+    @staticmethod
+    def group_gl_spec_functions(f):
+        function_name = None
+        param_names = None
+        attributes = None
+        for line in filter_comments(f):
+            m = GLSPEC_HEADER_REGEXP.match(line)
+            if m:
+                if function_name:
+                    yield function_name, param_names, attributes
+                function_name = m.group(1)
+                if m.group(2) == '':
+                    param_names = []
+                else:
+                    param_names = [n.strip() for n in m.group(2).split(',')]
+                attributes = collections.defaultdict(list)
+                continue
+            m = GLSPEC_ATTRIBUTE_REGEXP.match(line)
+            if m:
+                attribute_type, attribute_data = m.groups()
+                attributes[attribute_type].append(attribute_data)
+                continue
+            continue
+        if function_name:
+            yield function_name, param_names, attributes
+
+    # Process the data in gl.spec, and populate self.functions,
+    # self.synonyms, and self.categories based on it.
+    def read_gl_spec(self, f):
+        for name, param_names, attributes in self.group_gl_spec_functions(f):
+            if name in self.functions:
+                raise Exception(
+                    'Function {0!r} appears more than once'.format(name))
+            param_name_to_index = dict(
+                (param_name, index)
+                for index, param_name in enumerate(param_names))
+            param_types = [None] * len(param_names)
+            if len(attributes['param']) != len(param_names):
+                raise Exception(
+                    'Function {0!r} has a different number of parameters and '
+                    'param declarations'.format(name))
+            for param_datum in attributes['param']:
+                param_datum_tokens = param_datum.split()
+                param_name = param_datum_tokens[0]
+                param_index = param_name_to_index[param_name]
+                param_base_type = self.type_translation[param_datum_tokens[1]]
+                param_dir = param_datum_tokens[2]
+                param_multiplicity = param_datum_tokens[3]
+                if param_types[param_index] is not None:
+                    raise Exception(
+                        'Function {0!r} contains more than one param '
+                        'declaration for parameter {1!r}'.format(
+                            name, param_name))
+                if param_multiplicity == 'value':
+                    assert param_dir == 'in'
+                    param_type = param_base_type
+                elif param_multiplicity in ('array', 'reference'):
+                    if param_dir == 'in':
+                        # Note: technically this is not correct if
+                        # param_base_type is a pointer type (e.g. to
+                        # make an "in array" of "void *" we should
+                        # produce "void * const *", not "const void *
+                        # *").  However, the scripts used by the GL
+                        # consortium to produce glext.h from gl.spec
+                        # produce "const void * *", and fortunately
+                        # the only ill effect of this is that clients
+                        # have to do a little more typecasting than
+                        # they should.  So to avoid confusing people,
+                        # we're going to make the same mistake, so
+                        # that the resulting function signatures match
+                        # those in glext.h.
+                        param_type = normalize_type(
+                            'const {0} *'.format(param_base_type))
+                    elif param_dir == 'out':
+                        param_type = normalize_type(
+                            '{0} *'.format(param_base_type))
+                    else:
+                        raise Exception(
+                            'Function {0!r} parameter {1!r} uses unrecognized '
+                            'direction {2!r}'.format(
+                                name, param_name, param_dir))
+		else:
+		    raise Exception(
+			'Function {0!r} parameter {1!r} uses unrecognized '
+			'multiplicity {2!r}'.format(
+			    name, param_name, param_multiplicity))
+                param_types[param_index] = param_type
+            if len(attributes['return']) != 1:
+                raise Exception(
+                    'Function {0!r} contains {1} return attributes'.format(
+                        name, len(attributes['return'])))
+            if len(attributes['category']) != 1:
+                raise Exception(
+                    'Function {0!r} contains {1} category attributes'.format(
+                        name, len(attributes['category'])))
+            category, additional_data = translate_category(
+                attributes['category'][0])
+            if category not in self.categories:
+                self.categories[category] = additional_data
+            self.functions[name] = {
+                'return_type': self.type_translation[attributes['return'][0]],
+                'param_names': param_names,
+                'param_types': param_types,
+		'category': category,
+                }
+            self.synonyms.add_singleton(name)
+            for alias in attributes['alias']:
+                self.synonyms.add_alias(name, alias)
+
+    # Convert each line in the enumext.spec file into a key/value pair
+    # in self.enums, mapping an enum name to a dict.  For example, the
+    # following enumext.spec input:
+    #
+    # CLIENT_ALL_ATTRIB_BITS = 0xFFFFFFFF # ClientAttribMask
+    #
+    # Produces the dict entry:
+    #
+    # 'CLIENT_ALL_ATTRIB_BITS': { 'value_int': 4294967295,
+    #                             'value_str': "0xFFFFFFFF" }
+    def read_enumext_spec(self, f):
+        for line in filter_comments(f):
+            m = ENUM_REGEXP.match(line)
+            if m:
+                name, value = m.groups()
+                if value.startswith('GL_'):
+                    value_rhs = value[3:]
+                    value_int = self.enums[value_rhs]['value_int']
+                else:
+                    value_int = decode_enum_value(value)
+                self.enums[name] = {
+                    'value_str': value,
+                    'value_int': value_int
+                    }
+
+    # Convert the stored API into JSON.  To make diffing easier, all
+    # dictionaries are sorted by key, and all sets are sorted by set
+    # element.
+    def to_json(self):
+        return json.dumps({
+                'categories': self.categories,
+                'enums': self.enums,
+                'functions': self.functions,
+                'function_alias_sets':
+                    self.synonyms.get_synonym_sets(),
+                }, indent = 2, sort_keys = True, default = jsonize)
+
+
+if __name__ == '__main__':
+    api = Api()
+    with open(sys.argv[1]) as f:
+        api.read_gl_tm(f)
+    with open(sys.argv[2]) as f:
+        api.read_gl_spec(f)
+    with open(sys.argv[3]) as f:
+        api.read_enumext_spec(f)
+    with open(sys.argv[4], 'w') as f:
+	f.write(api.to_json())
diff --git a/tests/util/gen_dispatch.py b/tests/util/gen_dispatch.py
new file mode 100644
index 0000000..f733b30
--- /dev/null
+++ b/tests/util/gen_dispatch.py
@@ -0,0 +1,616 @@
+# Copyright 2012 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.
+
+# This script generates a C file (and corresponding header) allowing
+# Piglit to dispatch calls to OpenGL based on a JSON description of
+# the GL API (and extensions).
+#
+# Invoke this script with 3 command line arguments: the JSON input
+# filename, the C output filename, and the header outpit filename.
+#
+#
+# The input looks like this:
+#
+# {
+#   "categories": {
+#     <category name>: {
+#       "kind": <"GL" for a GL version, "extension" for an extension>,
+#       "gl_10x_version": <For a GL version, version number times 10>,
+#       "extension_name" <For an extension, name of the extension>
+#     }, ...
+#   },
+#   "enums": {
+#     <enum name, without "GL_" prefix>: {
+#       "value_int": <value integer>
+#       "value_str": <value string>
+#     }, ...
+#   },
+#   "functions": {
+#     <function name, without "gl" prefix>: {
+#       "category": <category in which this function appears>,
+#       "param_names": <list of param names>,
+#       "param_types": <list of param types>,
+#       "return_type": <type, or "void" if no return>
+#     }, ...
+#   },
+#   "function_alias_sets": {
+#     <list of synonymous function names>, ...
+#   },
+# }
+#
+#
+# The generated header consists of the following:
+#
+# - A typedef for each function, of the form that would normally
+#   appear in gl.h or glext.h, e.g.:
+#
+#   typedef GLvoid * (APIENTRY *PFNGLMAPBUFFERPROC)(GLenum, GLenum);
+#   typedef GLvoid * (APIENTRY *PFNGLMAPBUFFERARBPROC)(GLenum, GLenum);
+#
+# - A set of extern declarations for "dispatch function pointers".
+#   There is one dispatch function pointer for each set of synonymous
+#   functions in the GL API, e.g.:
+#
+#   extern PFNGLMAPBUFFERPROC __piglit_dispatch_glMapBuffer;
+#
+# - A set of #defines mapping each function name to the corresponding
+#   dispatch function pointer, e.g.:
+#
+#   #define glMapBuffer __piglit_dispatch_glMapBuffer
+#   #define glMapBufferARB __piglit_dispatch_glMapBuffer
+#
+# - A #define for each enum in the GL API, e.g.:
+#
+#   #define GL_FRONT 0x0404
+#
+# - A #define for each extension, e.g.:
+#
+#   #define GL_ARB_vertex_buffer_object
+#
+#
+# The generated C file consists of the following:
+#
+# - A resolve function corresponding to each set of synonymous
+#   functions in the GL API.  The resolve function determines which of
+#   the synonymous names the implementation supports (by consulting
+#   the current GL version and/or the extension string), and calls
+#   either __get_core_proc() or __get_ext_proc() to get the function
+#   pointer.  It stores the result in the dispatch function pointer,
+#   and then returns it as a generic void(void) function pointer.  If
+#   the implementation does not support any of the synonymous names,
+#   it calls __unsupported().  E.g.:
+#
+#   /* glMapBuffer (GL 1.5) */
+#   /* glMapbufferARB (GL_ARB_vertex_buffer_object) */
+#   static piglit_dispatch_function_ptr resolve_glMapBuffer()
+#   {
+#     if (__check_version(15))
+#       __piglit_dispatch_glMapBuffer = (PFNGLMAPBUFFERPROC) __get_core_proc("glMapBuffer");
+#     else if (__check_extension("GL_ARB_vertex_buffer_object"))
+#       __piglit_dispatch_glMapBuffer = (PFNGLMAPBUFFERARBPROC) __get_ext_proc("glMapBufferARB");
+#     else
+#       __unsupported("MapBuffer");
+#     return (piglit_dispatch_function_ptr) __piglit_dispatch_glMapBuffer;
+#   }
+#
+# - A stub function corresponding to each set of synonymous functions
+#   in the GL API.  The stub function first calls
+#   __check_initialized().  Then it calls the resolve function to
+#   ensure that the dispatch function pointer is set.  Finally, it
+#   dispatches to the GL function through the dispatch function
+#   pointer.  E.g.:
+#
+#   static GLvoid * stub_glMapBuffer(GLenum target, GLenum access)
+#   {
+#     __check_initialized();
+#     resolve_glMapBuffer();
+#     return __piglit_dispatch_glMapBuffer(target, access);
+#   }
+#
+# - A declaration for each dispatch function pointer, e.g.:
+#
+#   PFNGLMAPBUFFERPROC __piglit_dispatch_glMapBuffer = stub_glMapBuffer;
+#
+# - An initialization function, initialize_dispatch_pointers(), which
+#   resets each dispatch pointer to the corresponding stub function.
+#
+# - A table function_names, containing the name of each function in
+#   alphabetical order (including the "gl" prefix).
+#
+# - A table function_resolvers, containing a pointer to the resolve
+#   function corresponding to each entry in function_names.
+
+import collections
+import json
+import os.path
+import sys
+
+
+# Generate a top-of-file comment cautioning that the file is
+# auto-generated and should not be manually edited.
+def generated_boilerplate():
+    return """\
+/**
+ * This file was automatically generated by the script {0!r}.
+ *
+ * DO NOT EDIT!
+ *
+ * To regenerate, run "make piglit_dispatch_gen" from the toplevel directory.
+ *
+ * Copyright 2012 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.
+ */
+""".format(os.path.basename(sys.argv[0]))
+
+
+# Internal representation of a category.
+#
+# - For a category representing a GL version, Category.kind is 'GL'
+#   and Category.gl_10x_version is 10 times the GL version (e.g. 21
+#   for OpenGL version 2.1).
+#
+# - For a category representing an extension, Category.kind is
+#   'extension' and Category.extension_name is the extension name
+#   (including the 'GL_' prefix).
+class Category(object):
+    def __init__(self, json_data):
+	self.kind = json_data['kind']
+	if 'gl_10x_version' in json_data:
+	    self.gl_10x_version = json_data['gl_10x_version']
+	if 'extension_name' in json_data:
+	    self.extension_name = json_data['extension_name']
+
+    # Generate a human-readable representation of the category (for
+    # use in generated comments).
+    def __str__(self):
+	if self.kind == 'GL':
+	    return 'GL {0}.{1}'.format(
+		self.gl_10x_version // 10, self.gl_10x_version % 10)
+	elif self.kind == 'extension':
+	    return self.extension_name
+	else:
+	    raise Exception(
+		'Unexpected category kind {0!r}'.format(self.kind))
+
+
+# Internal representation of a GL function.
+#
+# - Function.name is the name of the function, without the 'gl'
+#   prefix.
+#
+# - Function.param_names is a list containing the name of each
+#   function parameter.
+#
+# - Function.param_types is a list containing the type of each
+#   function parameter.
+#
+# - Function.return_type is the return type of the function, or 'void'
+#   if the function has no return.
+#
+# - Function.category is a Category object describing the extension or
+#   GL version the function is defined in.
+class Function(object):
+    def __init__(self, name, json_data):
+	self.name = name
+	self.param_names = json_data['param_names']
+	self.param_types = json_data['param_types']
+	self.return_type = json_data['return_type']
+	self.category = json_data['category']
+
+    # Name of the function, with the 'gl' prefix.
+    @property
+    def gl_name(self):
+	return 'gl' + self.name
+
+    # Name of the function signature typedef corresponding to this
+    # function.  E.g. for the glGetString function, this is
+    # 'PFNGLGETSTRINGPROC'.
+    @property
+    def typedef_name(self):
+	return 'pfn{0}proc'.format(self.gl_name).upper()
+
+    # Generate a string representing the function signature in C.
+    #
+    # - name is inserted before the opening paren--use '' to generate
+    #   an anonymous function type signature.
+    #
+    # - If anonymous_args is True, then the signature contains only
+    #   the types of the arguments, not the names.
+    def c_form(self, name, anonymous_args):
+	if self.param_types:
+	    if anonymous_args:
+		param_decls = ', '.join(self.param_types)
+	    else:
+		param_decls = ', '.join(
+		    '{0} {1}'.format(*p)
+		    for p in zip(self.param_types, self.param_names))
+	else:
+	    param_decls = 'void'
+        return '{rettype} {name}({param_decls})'.format(
+	    rettype = self.return_type, name = name,
+	    param_decls = param_decls)
+
+
+# Internal representation of an enum.
+#
+# - Enum.value_int is the value of the enum, as a Python integer.
+#
+# - Enum.value_str is the value of the enum, as a string suitable for
+#   emitting as C code.
+class Enum(object):
+    def __init__(self, json_data):
+	self.value_int = json_data['value_int']
+	self.value_str = json_data['value_str']
+
+
+# Data structure keeping track of a set of synonymous functions.  Such
+# a set is called a "dispatch set" because it corresponds to a single
+# dispatch pointer.
+#
+# - DispatchSet.cat_fn_pairs is a list of pairs (category, function)
+#   for each category this function is defined in.  The list is sorted
+#   by category, with categories of kind 'GL' appearing first.
+class DispatchSet(object):
+    # Initialize a dispatch set given a list of synonymous function
+    # names.
+    #
+    # - all_functions is a dict mapping all possible function names to
+    #   the Function object describing them.
+    #
+    # - all_categories is a dict mapping all possible category names
+    #   to the Category object describing them.
+    def __init__(self, synonym_set, all_functions, all_categories):
+	self.cat_fn_pairs = []
+	for function_name in synonym_set:
+	    function = all_functions[function_name]
+	    category_name = function.category
+	    category = all_categories[category_name]
+	    self.cat_fn_pairs.append((category, function))
+	# Sort by category, with GL categories preceding extensions.
+	self.cat_fn_pairs.sort(key = self.__sort_key)
+
+    # The first Function object in DispatchSet.functions.  This
+    # "primary" function is used to name the dispatch pointer, the
+    # stub function, and the resolve function.
+    @property
+    def primary_function(self):
+	return self.cat_fn_pairs[0][1]
+
+    # The name of the dispatch pointer that should be generated for
+    # this dispatch set.
+    @property
+    def dispatch_name(self):
+	return '__piglit_dispatch_' + self.primary_function.gl_name
+
+    # The name of the stub function that should be generated for this
+    # dispatch set.
+    @property
+    def stub_name(self):
+	return 'stub_' + self.primary_function.gl_name
+
+    # The name of the resolve function that should be generated for
+    # this dispatch set.
+    @property
+    def resolve_name(self):
+	return 'resolve_' + self.primary_function.gl_name
+
+    @staticmethod
+    def __sort_key(cat_fn_pair):
+	if cat_fn_pair[0].kind == 'GL':
+	    return 0, cat_fn_pair[0].gl_10x_version
+	elif cat_fn_pair[0].kind == 'extension':
+	    return 1, cat_fn_pair[0].extension_name
+	else:
+	    raise Exception(
+		'Unexpected category kind {0!r}'.format(cat_fn_pair[0].kind))
+
+
+# Data structure keeping track of all of the known functions and
+# enums, and synonym relationships that exist between the functions.
+#
+# - Api.enums is a dict mapping enum name to an Enum object.
+#
+# - Api.functions is a dict mapping function name to a Function object.
+#
+# - Api.function_alias_sets is a list of lists, where each constituent
+#   list is a list of function names that are aliases of each other.
+#
+# - Api.categories is a dict mapping category name to a Category
+#   object.
+class Api(object):
+    def __init__(self, json_data):
+	self.enums = dict(
+	    (key, Enum(value))
+	    for key, value in json_data['enums'].items())
+	self.functions = dict(
+	    (key, Function(key, value))
+	    for key, value in json_data['functions'].items())
+	self.function_alias_sets = json_data['function_alias_sets']
+	self.categories = dict(
+	    (key, Category(value))
+	    for key, value in json_data['categories'].items())
+
+    # Generate a list of (name, value) pairs representing all enums in
+    # the API.  The resulting list is sorted by enum value.
+    def compute_unique_enums(self):
+	enums_by_value = [(enum.value_int, (name, enum.value_str))
+			  for name, enum in self.enums.items()]
+	enums_by_value.sort()
+	return [item[1] for item in enums_by_value]
+
+    # A list of all of the extension names declared in the API, as
+    # Python strings, sorted alphabetically.
+    @property
+    def extensions(self):
+	return sorted(
+	    [category_name
+	     for category_name, category in self.categories.items()
+	     if category.kind == 'extension'])
+
+    # Generate a list of DispatchSet objects representing all sets of
+    # synonymous functions in the API.  The resulting list is sorted
+    # by DispatchSet.stub_name.
+    def compute_dispatch_sets(self):
+	sets = [DispatchSet(synonym_set, self.functions, self.categories)
+		for synonym_set in self.function_alias_sets]
+	sets.sort(key = lambda ds: ds.stub_name)
+
+	return sets
+
+    # Generate a list of Function objects representing all functions
+    # in the API.  The resulting list is sorted by function name.
+    def compute_unique_functions(self):
+	return [self.functions[key] for key in sorted(self.functions.keys())]
+
+
+# Read the given input file and return an Api object containing the
+# data in it.
+def read_api(filename):
+    with open(filename, 'r') as f:
+	return Api(json.load(f))
+
+
+# Generate the resolve function for a given DispatchSet.
+def generate_resolve_function(ds):
+    f0 = ds.primary_function
+
+    # First figure out all the conditions we want to check in order to
+    # figure out which function to dispatch to, and the code we will
+    # execute in each case.
+    condition_code_pairs = []
+    for category, f in ds.cat_fn_pairs:
+	if category.kind == 'GL':
+	    getter = '__get_core_proc'
+	    if category.gl_10x_version == 10:
+		# Function has always been available--no need to check
+		# a condition.
+		condition = 'true'
+	    else:
+		condition = '__check_version({0})'.format(
+		    category.gl_10x_version)
+	elif category.kind == 'extension':
+	    getter = '__get_ext_proc'
+	    condition = '__check_extension("{0}")'.format(
+		category.extension_name)
+	else:
+	    raise Exception(
+		'Unexpected category type {0!r}'.format(category.kind))
+
+	if f.name == 'TexImage3DEXT':
+	    # Special case: glTexImage3DEXT has a slightly different
+	    # type than glTexImage3D (argument 3 is a GLenum rather
+	    # than a GLint).  This is not a problem, since GLenum and
+	    # GLint are treated identically by function calling
+	    # conventions.  So when calling get_proc_address() on
+	    # glTexImage3DEXT, cast the result to PFNGLTEXIMAGE3DPROC
+	    # to avoid a warning.
+	    typedef_name = 'PFNGLTEXIMAGE3DPROC'
+	else:
+	    typedef_name = f.typedef_name
+
+	code = '{0} = ({1}) {2}("{3}");'.format(
+	    ds.dispatch_name, typedef_name, getter, f.gl_name)
+
+	condition_code_pairs.append((condition, code))
+
+    # Finally, if none of the previous conditions were satisfied, then
+    # the given dispatch set is not supported by the implementation,
+    # so we want to call the __unsupported() function.
+    condition_code_pairs.append(
+	('true', '__unsupported("{0}");'.format(f0.name)))
+
+    # Start the resolve function
+    resolve_fn = 'static piglit_dispatch_function_ptr {0}()\n'.format(
+	ds.resolve_name)
+    resolve_fn += '{\n'
+
+    # Output code that checks each condition in turn and executes the
+    # appropriate case.  To make the generated code more palatable
+    # (and to avoid compiler warnings), we convert "if (true) FOO;" to
+    # "FOO;" and "else if (true) FOO;" to "else FOO;".
+    if condition_code_pairs[0][0] == 'true':
+	resolve_fn += '\t{0}\n'.format(condition_code_pairs[0][1])
+    else:
+	resolve_fn += '\tif ({0})\n\t\t{1}\n'.format(*condition_code_pairs[0])
+	for i in xrange(1, len(condition_code_pairs)):
+	    if condition_code_pairs[i][0] == 'true':
+		resolve_fn += '\telse\n\t\t{0}\n'.format(
+		    condition_code_pairs[i][1])
+		break
+	    else:
+		resolve_fn += '\telse if ({0})\n\t\t{1}\n'.format(
+		    *condition_code_pairs[i])
+
+    # Output code to return the dispatch function.
+    resolve_fn += '\treturn (piglit_dispatch_function_ptr) {0};\n'.format(
+	ds.dispatch_name)
+    resolve_fn += '}\n'
+    return resolve_fn
+
+
+# Generate the stub function for a given DispatchSet.
+def generate_stub_function(ds):
+    f0 = ds.primary_function
+
+    # Start the stub function
+    stub_fn = 'static {0}\n'.format(
+	f0.c_form(ds.stub_name, anonymous_args = False))
+    stub_fn += '{\n'
+    stub_fn += '\t__check_initialized();\n'
+    stub_fn += '\t{0}();\n'.format(ds.resolve_name)
+
+    # Output the call to the dispatch function.
+    stub_fn += '\t{0}{1}({2});\n'.format(
+	'return ' if f0.return_type != 'void' else '',
+	ds.dispatch_name, ', '.join(f0.param_names))
+    stub_fn += '}\n'
+    return stub_fn
+
+
+# Generate the initialize_dispatch_pointers() function, which sets
+# each dispatch pointer to point to the corresponding stub function.
+def generate_dispatch_pointer_initializer(dispatch_sets):
+    result = []
+    result.append('static void\n')
+    result.append('initialize_dispatch_pointers()\n')
+    result.append('{\n')
+    for ds in dispatch_sets:
+	result.append(
+	    '\t{0} = {1};\n'.format(ds.dispatch_name, ds.stub_name))
+    result.append('}\n')
+    return ''.join(result)
+
+
+# Generate the function_names and function_resolvers tables.
+def generate_function_names_and_resolvers(dispatch_sets):
+    name_resolver_pairs = []
+    for ds in dispatch_sets:
+	for _, f in ds.cat_fn_pairs:
+	    name_resolver_pairs.append((f.gl_name, ds.resolve_name))
+    name_resolver_pairs.sort()
+    result = []
+    result.append('static const char * const function_names[] = {\n')
+    for name, _ in name_resolver_pairs:
+	result.append('\t"{0}",\n'.format(name))
+    result.append('};\n')
+    result.append('\n')
+    result.append('static const piglit_dispatch_resolver_ptr '
+		  'function_resolvers[] = {\n')
+    for _, resolver in name_resolver_pairs:
+	result.append('\t{0},\n'.format(resolver))
+    result.append('};\n')
+    return ''.join(result)
+
+
+# Generate the C source and header files for the API.
+def generate_code(api):
+    c_contents = [generated_boilerplate()]
+    h_contents = [generated_boilerplate()]
+
+    unique_functions = api.compute_unique_functions()
+
+    # Emit typedefs for each name
+    for f in unique_functions:
+	h_contents.append(
+	    'typedef {0};\n'.format(
+		f.c_form('(APIENTRY *{0})'.format(f.typedef_name),
+			 anonymous_args = True)))
+
+    dispatch_sets = api.compute_dispatch_sets()
+
+    for ds in dispatch_sets:
+	f0 = ds.primary_function
+
+	# Emit comment block
+	comments = '\n'
+	for cat, f in ds.cat_fn_pairs:
+	    comments += '/* {0} ({1}) */\n'.format(f.gl_name, cat)
+	c_contents.append(comments)
+	h_contents.append(comments)
+
+	# Emit extern declaration of dispatch pointer
+	h_contents.append(
+	    'extern {0} {1};\n'.format(f0.typedef_name, ds.dispatch_name))
+
+	# Emit defines aliasing each GL function to the dispatch
+	# pointer
+	for _, f in ds.cat_fn_pairs:
+	    h_contents.append(
+		'#define {0} {1}\n'.format(f.gl_name, ds.dispatch_name))
+
+	# Emit resolve function
+	c_contents.append(generate_resolve_function(ds))
+
+	# Emit stub function
+	c_contents.append(generate_stub_function(ds))
+
+	# Emit initializer for dispatch pointer
+	c_contents.append(
+	    '{0} {1} = {2};\n'.format(
+		f0.typedef_name, ds.dispatch_name, ds.stub_name))
+
+    # Emit dispatch pointer initialization function
+    c_contents.append(generate_dispatch_pointer_initializer(dispatch_sets))
+
+    c_contents.append('\n')
+
+    # Emit function_names and function_resolvers tables.
+    c_contents.append(generate_function_names_and_resolvers(dispatch_sets))
+
+    # Emit enum #defines
+    for name, value in api.compute_unique_enums():
+	h_contents.append('#define GL_{0} {1}\n'.format(name, value))
+
+    # Emit extension #defines
+    h_contents.append('\n')
+    for ext in api.extensions:
+	h_contents.append('#define {0}\n'.format(ext))
+
+    return ''.join(c_contents), ''.join(h_contents)
+
+
+if __name__ == '__main__':
+    file_to_parse = sys.argv[1]
+    api = read_api(file_to_parse)
+
+    c_contents, h_contents = generate_code(api)
+    with open(sys.argv[2], 'w') as f:
+	f.write(c_contents)
+    with open(sys.argv[3], 'w') as f:
+	f.write(h_contents)
-- 
1.7.7.6



More information about the Piglit mailing list