[Piglit] [PATCH 10/15] piglit-dispatch: Code generation script

Paul Berry stereotype441 at gmail.com
Fri Mar 2 15:40:28 PST 2012


This is the first in a series of patches that replaces GLEW with a new
mechanism called "piglit-dispatch".  This patch adds the script that
will be used by piglit-dispatch to build its generated code.

The input to the code generation script is a set of XML files
describing the GL API, in the same form as used by Mesa.  For the time
being, these files can be found at
git://github.com/stereotype441/glapi.git.
---
 tests/util/gen_dispatch.py |  620 ++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 620 insertions(+), 0 deletions(-)
 create mode 100644 tests/util/gen_dispatch.py

diff --git a/tests/util/gen_dispatch.py b/tests/util/gen_dispatch.py
new file mode 100644
index 0000000..ef11853
--- /dev/null
+++ b/tests/util/gen_dispatch.py
@@ -0,0 +1,620 @@
+# 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 an XML description of
+# the GL api (and extensions).
+#
+# Invoke this script with 3 command line arguments: the XML input
+# filename, the C output filename, and the header outpit filename.
+#
+#
+# The input looks like this:
+#
+# <OpenGLAPI>
+#
+#   <!-- A category name is either the name of an extension
+#        (e.g. "GL_ARB_vertex_buffer_object") or a GL version (e.g.
+#        "1.0").
+#        -->
+#   <category name="GL_ARB_vertex_buffer_object">
+#
+#     <!-- Enum names omit the "GL_" prefix -->
+#     <enum name="FRONT" value="0x0404"/>
+#
+#     <!-- Function names omit the "gl" prefix.  So, for example the
+#          following defines a function
+#
+#          GLvoid *glMapBuffer(GLenum target, GLenum access);
+#
+#          The alias attribute declares that this function is
+#          synonymous to another function defined elsewhere.  So the
+#          following declaration specifies that "glMapBuffer" is
+#          synonymous with "glMapBufferARB".
+#
+#          Note: a return type of "void" is assumed if there is no
+#          <return> element.
+#          -->
+#     <function name="MapBuffer" alias="MapBufferARB">
+#         <param name="target" type="GLenum"/>
+#         <param name="access" type="GLenum"/>
+#         <return type="GLvoid *"/>
+#     </function>
+#
+#   </category>
+#
+# </OpenGLAPI>
+#
+# The input file may refer to other files in the same directory using
+# XInclude syntax (http://www.w3.org/TR/xinclude/).
+#
+#
+# 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 * (*PFNGLMAPBUFFERPROC)(GLenum, GLenum);
+#   typedef GLvoid * (*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 stub function corresponding to each set of synonymous functions
+#   in the GL API.  The stub function first calls
+#   __check_initialized().  Then it 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
+#   calls it.  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 GLvoid * stub_glMapBuffer(GLenum target, GLenum access)
+#   {
+#     __check_initialized();
+#     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_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.
+
+import collections
+import os.path
+import sys
+import xml.etree.ElementTree
+import xml.etree.ElementInclude
+
+
+# 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.
+ */
+""".format(os.path.basename(sys.argv[0]))
+
+
+# Internal representation of a category.
+#
+# - For a category representing a GL version, Category.typ is 'GL' and
+#   Category.data is 10 times the GL version (e.g. 21 for OpenGL
+#   version 2.1).
+#
+# - For a category representing an extension, Category.typ is
+#   'extension' and Category.data is the extension name (including the
+#   'GL_' prefix).
+class Category(collections.namedtuple('Category', 'typ data')):
+    # Generate a human-readable representation of the category (for
+    # use in generated comments).
+    def __str__(self):
+	if self.typ == 'GL':
+	    return 'GL {0}.{1}'.format(self.data // 10, self.data % 10)
+	elif self.typ == 'extension':
+	    return self.data
+	else:
+	    raise Exception(
+		'Unexpected category type {0!r}'.format(self.typ))
+
+
+# Convert a category attribute string (from XML) to a Category object.
+def decode_category(category_name):
+    try:
+	gl_version = float(category_name)
+	return Category('GL', int(round(gl_version*10)))
+    except ValueError:
+	return Category('extension', category_name)
+
+
+# Internal representation of a function parameter.
+#
+# - Param.name is the name of the parameter.
+#
+# - Param.typ is the C type of the parameter.
+Param = collections.namedtuple('Param', 'name typ')
+
+
+# Convert a function parameter from XML to a Param object.
+def xml_to_param(param_xml):
+    return Param(param_xml.attrib['name'],
+		 param_xml.attrib['type'])
+
+
+# Internal representation of a function signature.
+#
+# - Signature.rettype is the C type of the function's return value (as
+#   a string), or None if the function returns void.
+#
+# - Signature.params is a tuple of Param objects representing the
+#   function's parameters.
+class Signature(collections.namedtuple('Signature', 'rettype params')):
+    def __new__(self, rettype, params):
+	return super(Signature, self).__new__(self, rettype, tuple(params))
+
+    # 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.params:
+	    if anonymous_args:
+		param_decls = ', '.join(p.typ for p in self.params)
+	    else:
+		param_decls = ', '.join('{0} {1}'.format(p.typ, p.name)
+					for p in self.params)
+	else:
+	    param_decls = 'void'
+        return '{rettype} {name}({param_decls})'.format(
+	    rettype = self.rettype or 'void', name = name,
+	    param_decls = param_decls)
+
+
+# Internal representation of a GL function.
+#
+# - Function.name is the name of the function, without the 'gl'
+#   prefix.
+#
+# - Function.sig is the function signature, a Signature object.
+#
+# - Function.alias is the name of a synonymous function, or None if no
+#   alias was declared.
+#
+# - Function.category is the category in which the function was
+#   declared, a Category object.
+class Function(collections.namedtuple('Function', 'name sig alias 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()
+
+
+# Convert a function from XML to a Function object.
+def xml_to_function(func_xml, category):
+    name = func_xml.attrib['name']
+    alias = func_xml.get('alias')
+    params = []
+    rettype = None
+    for param in func_xml.findall('param'):
+	if param.get('padding') != 'true':
+	    params.append(xml_to_param(param))
+    ret = func_xml.findall('return')
+    if len(ret) > 1:
+	raise Exception('Too many <return> elements')
+    if len(ret) == 1:
+	rettype = ret[0].attrib['type']
+    return Function(name, Signature(rettype, params), alias, category)
+
+
+# Internal representation of an enum.
+#
+# - Enum.name is the name of the enum, without the 'GL_' prefix.
+#
+# - Enum.value is the value of the enum, a string in either decimal or
+#   hex format.
+Enum = collections.namedtuple('Enum', 'name value')
+
+
+# Convert an enum from XML to an Enum object.
+def xml_to_enum(enum_xml):
+    return Enum(enum_xml.attrib['name'], enum_xml.attrib['value'])
+
+
+# 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] = set([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 python set object representing all the function names that
+    # are synonymous to the given name, including the name itself.
+    def get_synonyms_for(self, name):
+	return self.__name_to_synonyms[name]
+
+
+# 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.
+class DispatchSet(object):
+    # Initialize a dispatch set given a list of Function objects.
+    def __init__(self, functions):
+	# Sort functions by category, with GL categories preceding
+	# extensions.
+	self.__functions = tuple(
+	    sorted(functions, key = self.__function_category_sort_key))
+
+    # A tuple containing all of the Function objects in this dispatch
+    # set.  The functions are sorted so that core GL functions appear
+    # before extensions.
+    @property
+    def functions(self):
+	return self.__functions
+
+    # The first Function object in DispatchSet.functions.  This
+    # "primary" function is used to name the dispatch pointer and the
+    # stub function.
+    @property
+    def primary_function(self):
+	return self.__functions[0]
+
+    # 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
+
+    @staticmethod
+    def __function_category_sort_key(fn):
+	if fn.category.typ == 'GL':
+	    return 0, fn.category.data
+	elif fn.category.typ == 'extension':
+	    return 1, fn.category.data
+	else:
+	    raise Exception(
+		'Unexpected category type {0!r}'.format(category.typ))
+
+
+# Data structure keeping track of all of the known functions and
+# enums, and synonym relationships that exist between the functions.
+class Api(object):
+    def __init__(self):
+	self.__functions = []
+	self.__enums = []
+	self.__synonyms = SynonymMap()
+	self.__function_map = collections.defaultdict(list)
+	self.__extensions = set()
+
+    # A list of Enum objects representing all the enums in the API.
+    @property
+    def enums(self):
+	return self.__enums
+
+    # A SynonymMap object recording which function names are
+    # synonymous with which other names.
+    @property
+    def synonyms(self):
+	return self.__synonyms
+
+    # An unordered set of all of the extension names declared in the
+    # API, as Python strings.
+    @property
+    def extensions(self):
+	return self.__extensions
+
+    # Add a single Function object to the API, updating synonyms
+    # appropriately.
+    def add_function(self, fn):
+	self.__functions.append(fn)
+	self.__function_map[fn.name].append(fn)
+	if fn.alias:
+	    self.__synonyms.add_alias(fn.name, fn.alias)
+	else:
+	    self.__synonyms.add_singleton(fn.name)
+
+    # 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 = []
+	remaining_names = set(f.name for f in self.__functions)
+	while remaining_names:
+	    name = remaining_names.pop()
+	    synonym_set = self.__synonyms.get_synonyms_for(name)
+	    functions = []
+	    for name in synonym_set:
+		for function in self.__function_map[name]:
+		    functions.append(function)
+	    sets.append(DispatchSet(functions))
+	    for n in synonym_set:
+		remaining_names.discard(n)
+	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):
+	functions = []
+	names_used = set()
+	for f in self.__functions:
+	    if f.name in names_used:
+		continue
+	    functions.append(f)
+	functions.sort(key = lambda f: f.name)
+	return functions
+
+
+# Read the given input file and return an Api object containing the
+# data in it.
+def read_xml(filename):
+    api = Api()
+    os.chdir(os.path.dirname(filename))
+    root = xml.etree.ElementTree.parse(filename).getroot()
+    xml.etree.ElementInclude.include(root)
+
+    for category_xml in root.findall('.//category'):
+	category = decode_category(category_xml.attrib['name'])
+	if category.typ == 'extension':
+	    api.extensions.add(category.data)
+	for func in category_xml.findall('function'):
+	    api.add_function(xml_to_function(func, category))
+	for enum in category_xml.findall('enum'):
+	    api.enums.append(xml_to_enum(enum))
+
+    return api
+
+
+# Generate the stub function for a given DispatchSet.
+def generate_stub_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 f in ds.functions:
+	if f.category.typ == 'GL':
+	    getter = '__get_core_proc'
+	    if f.category.data == 10:
+		# Function has always been available--no need to check
+		# a condition.
+		condition = 'true'
+	    else:
+		condition = '__check_version({0})'.format(
+		    f.category.data)
+	elif f.category.typ == 'extension':
+	    getter = '__get_ext_proc'
+	    condition = '__check_extension("{0}")'.format(
+		f.category.data)
+	else:
+	    raise Exception(
+		'Unexpected category type {0!r}'.format(f.category.type))
+
+	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 stub function
+    stub_fn = 'static {0}\n'.format(
+	f0.sig.c_form(ds.stub_name, anonymous_args = False))
+    stub_fn += '{\n'
+    stub_fn += '\t__check_initialized();\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':
+	stub_fn += '\t{0}\n'.format(condition_code_pairs[0][1])
+    else:
+	stub_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':
+		stub_fn += '\telse\n\t\t{0}\n'.format(
+		    condition_code_pairs[i][1])
+		break
+	    else:
+		stub_fn += '\telse if ({0})\n\t\t{1}\n'.format(
+		    *condition_code_pairs[i])
+
+    # Output the call to the dispatch function.
+    stub_fn += '\t{0}{1}({2});\n'.format(
+	'return ' if f0.sig.rettype else '',
+	ds.dispatch_name,
+	', '.join(p.name for p in f0.sig.params))
+    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 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.sig.c_form('(*{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 f in ds.functions:
+	    comments += '/* {0} ({1}) */\n'.format(f.gl_name, f.category)
+	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.functions:
+	    h_contents.append(
+		'#define {0} {1}\n'.format(f.gl_name, ds.dispatch_name))
+
+	# 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))
+
+    # Emit enum #defines
+    for en in api.enums:
+	h_contents.append('#define GL_{s.name} {s.value}\n'.format(s = en))
+
+    # Emit extension #defines
+    h_contents.append('\n')
+    for ext in sorted(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_xml(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