# Mesa 3-D graphics library
#
# Copyright (C) 2010 LunarG Inc.
#
# 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 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.
#
# Authors:
# Chia-I Wu <olv@lunarg.com>
import sys
# make it possible to import glapi
import os
GLAPI = os.path.join(".", os.path.dirname(sys.argv[0]), "glapi/gen")
sys.path.append(GLAPI)
import re
from optparse import OptionParser
import gl_XML
import glX_XML
# number of dynamic entries
ABI_NUM_DYNAMIC_ENTRIES = 256
class ABIEntry(object):
"""Represent an ABI entry."""
_match_c_param = re.compile(
'^(?P<type>[\w\s*]+?)(?P<name>\w+)(\[(?P<array>\d+)\])?$')
def __init__(self, cols, attrs, xml_data = None):
self._parse(cols)
self.slot = attrs['slot']
self.hidden = attrs['hidden']
self.alias = attrs['alias']
self.handcode = attrs['handcode']
self.xml_data = xml_data
def c_prototype(self):
return '%s %s(%s)' % (self.c_return(), self.name, self.c_params())
def c_return(self):
ret = self.ret
if not ret:
ret = 'void'
return ret
def c_params(self):
"""Return the parameter list used in the entry prototype."""
c_params = []
for t, n, a in self.params:
sep = '' if t.endswith('*') else ' '
arr = '[%d]' % a if a else ''
c_params.append(t + sep + n + arr)
if not c_params:
c_params.append('void')
return ", ".join(c_params)
def c_args(self):
"""Return the argument list used in the entry invocation."""
c_args = []
for t, n, a in self.params:
c_args.append(n)
return ", ".join(c_args)
def _parse(self, cols):
ret = cols.pop(0)
if ret == 'void':
ret = None
name = cols.pop(0)
params = []
if not cols:
raise Exception(cols)
elif len(cols) == 1 and cols[0] == 'void':
pass
else:
for val in cols:
params.append(self._parse_param(val))
self.ret = ret
self.name = name
self.params = params
def _parse_param(self, c_param):
m = self._match_c_param.match(c_param)
if not m:
raise Exception('unrecognized param ' + c_param)
c_type = m.group('type').strip()
c_name = m.group('name')
c_array = m.group('array')
c_array = int(c_array) if c_array else 0
return (c_type, c_name, c_array)
def __str__(self):
return self.c_prototype()
def __cmp__(self, other):
# compare slot, alias, and then name
res = cmp(self.slot, other.slot)
if not res:
if not self.alias:
res = -1
elif not other.alias:
res = 1
if not res:
res = cmp(self.name, other.name)
return res
def abi_parse_xml(xml):
"""Parse a GLAPI XML file for ABI entries."""
api = gl_XML.parse_GL_API(xml, glX_XML.glx_item_factory())
entry_dict = {}
for func in api.functionIterateByOffset():
# make sure func.name appear first
entry_points = func.entry_points[:]
entry_points.remove(func.name)
entry_points.insert(0, func.name)
for name in entry_points:
attrs = {
'slot': func.offset,
'hidden': not func.is_static_entry_point(name),
'alias': None if name == func.name else func.name,
'handcode': bool(func.has_different_protocol(name)),
}
# post-process attrs
if attrs['alias']:
try:
alias = entry_dict[attrs['alias']]
except KeyError:
raise Exception('failed to alias %s' % attrs['alias'])
if alias.alias:
raise Exception('recursive alias %s' % ent.name)
attrs['alias'] = alias
if attrs['handcode']:
attrs['handcode'] = func.static_glx_name(name)
else:
attrs['handcode'] = None
if entry_dict.has_key(name):
raise Exception('%s is duplicated' % (name))
cols = []
cols.append(func.return_type)
cols.append(name)
params = func.get_parameter_string(name)
cols.extend([p.strip() for p in params.split(',')])
ent = ABIEntry(cols, attrs, func)
entry_dict[ent.name] = ent
entries = entry_dict.values()
entries.sort()
return entries
def abi_parse_line(line):
cols = [col.strip() for col in line.split(',')]
attrs = {
'slot': -1,
'hidden': False,
'alias': None,
'handcode': None,
}
# extract attributes from the first column
vals = cols[0].split(':')
while len(vals) > 1:
val = vals.pop(0)
if val.startswith('slot='):
attrs['slot'] = int(val[5:])
elif val == 'hidden':
attrs['hidden'] = True
elif val.startswith('alias='):
attrs['alias'] = val[6:]
elif val.startswith('handcode='):
attrs['handcode'] = val[9:]
elif not val:
pass
else:
raise Exception('unknown attribute %s' % val)
cols[0] = vals[0]
return (attrs, cols)
def abi_parse(filename):
"""Parse a CSV file for ABI entries."""
fp = open(filename) if filename != '-' else sys.stdin
lines = [line.strip() for line in fp.readlines()
if not line.startswith('#') and line.strip()]
entry_dict = {}
next_slot = 0
for line in lines:
attrs, cols = abi_parse_line(line)
# post-process attributes
if attrs['alias']:
try:
alias = entry_dict[attrs['alias']]
except KeyError:
raise Exception('failed to alias %s' % attrs['alias'])
if alias.alias:
raise Exception('recursive alias %s' % ent.name)
slot = alias.slot
attrs['alias'] = alias
else:
slot = next_slot
next_slot += 1
if attrs['slot'] < 0:
attrs['slot'] = slot
elif attrs['slot'] != slot:
raise Exception('invalid slot in %s' % (line))
ent = ABIEntry(cols, attrs)
if entry_dict.has_key(ent.name):
raise Exception('%s is duplicated' % (ent.name))
entry_dict[ent.name] = ent
entries = entry_dict.values()
entries.sort()
return entries
def abi_sanity_check(entries):
if not entries:
return
all_names = []
last_slot = entries[-1].slot
i = 0
for slot in xrange(last_slot + 1):
if entries[i].slot != slot:
raise Exception('entries are not ordered by slots')
if entries[i].alias:
raise Exception('first entry of slot %d aliases %s'
% (slot, entries[i].alias.name))
handcode = None
while i < len(entries) and entries[i].slot == slot:
ent = entries[i]
if not handcode and ent.handcode:
handcode = ent.handcode
elif ent.handcode != handcode:
raise Exception('two aliases with handcode %s != %s',
ent.handcode, handcode)
if ent.name in all_names:
raise Exception('%s is duplicated' % (ent.name))
if ent.alias and ent.alias.name not in all_names:
raise Exception('failed to alias %s' % (ent.alias.name))
all_names.append(ent.name)
i += 1
if i < len(entries):
raise Exception('there are %d invalid entries' % (len(entries) - 1))
class ABIPrinter(object):
"""MAPI Printer"""
def __init__(self, entries):
self.entries = entries
# sort entries by their names
self.entries_sorted_by_names = self.entries[:]
self.entries_sorted_by_names.sort(lambda x, y: cmp(x.name, y.name))
self.indent = ' ' * 3
self.noop_warn = 'noop_warn'
self.noop_generic = 'noop_generic'
self.current_get = 'entry_current_get'
self.api_defines = []
self.api_headers = ['"KHR/khrplatform.h"']
self.api_call = 'KHRONOS_APICALL'
self.api_entry = 'KHRONOS_APIENTRY'
self.api_attrs = 'KHRONOS_APIATTRIBUTES'
self.c_header = ''
self.lib_need_table_size = True
self.lib_need_noop_array = True
self.lib_need_stubs = True
self.lib_need_all_entries = True
self.lib_need_non_hidden_entries = False
def c_notice(self):
return '/* This file is automatically generated by mapi_abi.py. Do not modify. */'
def c_public_includes(self):
"""Return includes of the client API headers."""
defines = ['#define ' + d for d in self.api_defines]
includes = ['#include ' + h for h in self.api_headers]
return "\n".join(defines + includes)
def need_entry_point(self, ent):
"""Return True if an entry point is needed for the entry."""
# non-handcode hidden aliases may share the entry they alias
use_alias = (ent.hidden and ent.alias and not ent.handcode)
return not use_alias
def c_public_declarations(self, prefix):
"""Return the declarations of public entry points."""
decls = []
for ent in self.entries:
if not self.need_entry_point(ent):
continue
export = self.api_call if not ent.hidden else ''
decls.append(self._c_decl(ent, prefix, True, export) + ';')
return "\n".join(decls)
def c_mapi_table(self):
"""Return defines of the dispatch table size."""
num_static_entries = self.entries[-1].slot + 1
return ('#define MAPI_TABLE_NUM_STATIC %d\n' + \
'#define MAPI_TABLE_NUM_DYNAMIC %d') % (
num_static_entries, ABI_NUM_DYNAMIC_ENTRIES)
def _c_function(self, ent, prefix, mangle=False, stringify=False):
"""Return the function name of an entry."""
formats = {
True: { True: '%s_STR(%s)', False: '%s(%s)' },
False: { True: '"%s%s"', False: '%s%s' },
}
fmt = formats[prefix.isupper()][stringify]
name = ent.name
if mangle and ent.hidden:
name = '_dispatch_stub_' + str(ent.slot)
return fmt % (prefix, name)
def _c_function_call(self, ent, prefix):
"""Return the function name used for calling."""
if ent.handcode:
# _c_function does not handle this case
formats = { True: '%s(%s)', False: '%s%s' }
fmt = formats[prefix.isupper()]
name = fmt % (prefix, ent.handcode)
elif self.need_entry_point(ent):
name = self._c_function(ent, prefix, True)
else:
name = self._c_function(ent.alias, prefix, True)
return name
def _c_decl(self, ent, prefix, mangle=False, export=''):
"""Return the C declaration for the entry."""
decl = '%s %s %s(%s)' % (ent.c_return(), self.api_entry,
self._c_function(ent, prefix, mangle), ent.c_params())
if export:
decl = export + ' ' + decl
if self.api_attrs:
decl += ' ' + self.api_attrs
return decl
def _c_cast(self, ent):
"""Return the C cast for the entry."""
cast = '%s (%s *)(%s)' % (
ent.c_return(), self.api_entry, ent.c_params())
return cast
def c_public_dispatches(self, prefix, no_hidden):
"""Return the public dispatch functions."""
dispatches = []
for ent in self.entries:
if ent.hidden and no_hidden:
continue
if not self.need_entry_point(ent):
continue
export = self.api_call if not ent.hidden else ''
proto = self._c_decl(ent, prefix, True, export)
cast = self._c_cast(ent)
ret = ''
if ent.ret:
ret = 'return '
stmt1 = self.indent
stmt1 += 'const struct _glapi_table *_tbl = %s();' % (
self.current_get)
stmt2 = self.indent
stmt2 += 'mapi_func _func = ((const mapi_func *) _tbl)[%d];' % (
ent.slot)
stmt3 = self.indent
stmt3 += '%s((%s) _func)(%s);' % (ret, cast, ent.c_args())
disp = '%s\n{\n%s\n%s\n%s\n}' % (proto, stmt1, stmt2, stmt3)
if ent.handcode:
disp = '#if 0\n' + disp + '\n#endif'
dispatches.append(disp)
return '\n\n'.join(dispatches)
def c_public_initializer(self, prefix):
"""Return the initializer for public dispatch functions."""
names = []
for ent in self.entries:
if ent.alias:
continue
name = '%s(mapi_func) %s' % (self.indent,
self._c_function_call(ent, prefix))
names.append(name)
return ',\n'.join(names)
def c_stub_string_pool(self):
"""Return the string pool for use by stubs."""
# sort entries by their names
sorted_entries = self.entries[:]
sorted_entries.sort(lambda x, y: cmp(x.name, y.name))
pool = []
offsets = {}
count = 0
for ent in sorted_entries:
offsets[ent] = count
pool.append('%s' % (ent.name))
count += len(ent.name) + 1
pool_str = self.indent + '"' + \
('\\0"\n' + self.indent + '"').join(pool) + '";'
return (pool_str, offsets)
def c_stub_initializer(self, prefix, pool_offsets):
"""Return the initializer for struct mapi_stub array."""
stubs = []
for ent in self.entries_sorted_by_names:
stubs.append('%s{ (void *) %d, %d, NULL }' % (
self.indent, pool_offsets[ent], ent.slot))
return ',\n'.join(stubs)
def c_noop_functions(self, prefix, warn_prefix):
"""Return the noop functions."""
noops = []
for ent in self.entries:
if ent.alias:
continue
proto = self._c_decl(ent, prefix, False, 'static')
stmt1 = self.indent;
space = ''
for t, n, a in ent.params:
stmt1 += "%s(void) %s;" % (space, n)
space = ' '
if ent.params:
stmt1 += '\n';
stmt1 += self.indent + '%s(%s);' % (self.noop_warn,
self._c_function(ent, warn_prefix, False, True))
if ent.ret:
stmt2 = self.indent + 'return (%s) 0;' % (ent.ret)
noop = '%s\n{\n%s\n%s\n}' % (proto, stmt1, stmt2)
else:
noop = '%s\n{\n%s\n}' % (proto, stmt1)
noops.append(noop)
return '\n\n'.join(noops)
def c_noop_initializer(self, prefix, use_generic):
"""Return an initializer for the noop dispatch table."""
entries = [self._c_function(ent, prefix)
for ent in self.entries if not ent.alias]
if use_generic:
entries = [self.noop_generic] * len(entries)
entries.extend([self.noop_generic] * ABI_NUM_DYNAMIC_ENTRIES)
pre = self.indent + '(mapi_func) '
return pre + (',\n' + pre).join(entries)
def c_asm_gcc(self, prefix, no_hidden):
asm = []
for ent in self.entries:
if ent.hidden and no_hidden:
continue
if not self.need_entry_point(ent):
continue
name = self._c_function(ent, prefix, True, True)
if ent.handcode:
asm.append('#if 0')
if ent.hidden:
asm.append('".hidden "%s"\\n"' % (name))
if ent.alias and not (ent.alias.hidden and no_hidden):
asm.append('".globl "%s"\\n"' % (name))
asm.append('".set "%s", "%s"\\n"' % (name,
self._c_function(ent.alias, prefix, True, True)))
else:
asm.append('STUB_ASM_ENTRY(%s)"\\n"' % (name))
asm.append('"\\t"STUB_ASM_CODE("%d")"\\n"' % (ent.slot))
if ent.handcode:
asm.append('#endif')
asm.append('')
return "\n".join(asm)
def output_for_lib(self):
print self.c_notice()
if self.c_header:
print
print self.c_header
print
print '#ifdef MAPI_TMP_DEFINES'
print self.c_public_includes()
print
print self.c_public_declarations(self.prefix_lib)
print '#undef MAPI_TMP_DEFINES'
print '#endif /* MAPI_TMP_DEFINES */'
if self.lib_need_table_size:
print
print '#ifdef MAPI_TMP_TABLE'
print self.c_mapi_table()
print '#undef MAPI_TMP_TABLE'
print '#endif /* MAPI_TMP_TABLE */'
if self.lib_need_noop_array:
print
print '#ifdef MAPI_TMP_NOOP_ARRAY'
print '#ifdef DEBUG'
print
print self.c_noop_functions(self.prefix_noop, self.prefix_warn)
print
print 'const mapi_func table_%s_array[] = {' % (self.prefix_noop)
print self.c_noop_initializer(self.prefix_noop, False)
print '};'
print
print '#else /* DEBUG */'
print
print 'const mapi_func table_%s_array[] = {' % (self.prefix_noop)
print self.c_noop_initializer(self.prefix_noop, True)
print '};'
print
print '#endif /* DEBUG */'
print '#undef MAPI_TMP_NOOP_ARRAY'
print '#endif /* MAPI_TMP_NOOP_ARRAY */'
if self.lib_need_stubs:
pool, pool_offsets = self.c_stub_string_pool()
print
print '#ifdef MAPI_TMP_PUBLIC_STUBS'
print 'static const char public_string_pool[] ='
print pool
print
print 'static const struct mapi_stub public_stubs[] = {'
print self.c_stub_initializer(self.prefix_lib, pool_offsets)
print '};'
print '#undef MAPI_TMP_PUBLIC_STUBS'
print '#endif /* MAPI_TMP_PUBLIC_STUBS */'
if self.lib_need_all_entries:
print
print '#ifdef MAPI_TMP_PUBLIC_ENTRIES'
print self.c_public_dispatches(self.prefix_lib, False)
print
print 'static const mapi_func public_entries[] = {'
print self.c_public_initializer(self.prefix_lib)
print '};'
print '#undef MAPI_TMP_PUBLIC_ENTRIES'
print '#endif /* MAPI_TMP_PUBLIC_ENTRIES */'
print
print '#ifdef MAPI_TMP_STUB_ASM_GCC'
print '__asm__('
print self.c_asm_gcc(self.prefix_lib, False)
print ');'
print '#undef MAPI_TMP_STUB_ASM_GCC'
print '#endif /* MAPI_TMP_STUB_ASM_GCC */'
if self.lib_need_non_hidden_entries:
all_hidden = True
for ent in self.entries:
if not ent.hidden:
all_hidden = False
break
if not all_hidden:
print
print '#ifdef MAPI_TMP_PUBLIC_ENTRIES_NO_HIDDEN'
print self.c_public_dispatches(self.prefix_lib, True)
print
print '/* does not need public_entries */'
print '#undef MAPI_TMP_PUBLIC_ENTRIES_NO_HIDDEN'
print '#endif /* MAPI_TMP_PUBLIC_ENTRIES_NO_HIDDEN */'
print
print '#ifdef MAPI_TMP_STUB_ASM_GCC_NO_HIDDEN'
print '__asm__('
print self.c_asm_gcc(self.prefix_lib, True)
print ');'
print '#undef MAPI_TMP_STUB_ASM_GCC_NO_HIDDEN'
print '#endif /* MAPI_TMP_STUB_ASM_GCC_NO_HIDDEN */'
class GLAPIPrinter(ABIPrinter):
"""OpenGL API Printer"""
def __init__(self, entries):
for ent in entries:
self._override_for_api(ent)
super(GLAPIPrinter, self).__init__(entries)
self.api_defines = ['GL_GLEXT_PROTOTYPES']
self.api_headers = ['"GL/gl.h"', '"GL/glext.h"']
self.api_call = 'GLAPI'
self.api_entry = 'APIENTRY'
self.api_attrs = ''
self.lib_need_table_size = False
self.lib_need_noop_array = False
self.lib_need_stubs = False
self.lib_need_all_entries = False
self.lib_need_non_hidden_entries = True
self.prefix_lib = 'GLAPI_PREFIX'
self.prefix_noop = 'noop'
self.prefix_warn = self.prefix_lib
self.c_header = self._get_c_header()
def _override_for_api(self, ent):
"""Override attributes of an entry if necessary for this
printer."""
# By default, no override is necessary.
pass
def _get_c_header(self):
header = """#ifndef _GLAPI_TMP_H_
#define _GLAPI_TMP_H_
#ifdef USE_MGL_NAMESPACE
#define GLAPI_PREFIX(func) mgl##func
#define GLAPI_PREFIX_STR(func) "mgl"#func
#else
#define GLAPI_PREFIX(func) gl##func
#define GLAPI_PREFIX_STR(func) "gl"#func
#endif /* USE_MGL_NAMESPACE */
typedef int GLclampx;
#endif /* _GLAPI_TMP_H_ */"""
return header
class ES1APIPrinter(GLAPIPrinter):
"""OpenGL ES 1.x API Printer"""
def __init__(self, entries):
super(ES1APIPrinter, self).__init__(entries)
self.prefix_lib = 'gl'
self.prefix_warn = 'gl'
def _override_for_api(self, ent):
if ent.xml_data is None:
raise Exception('ES2 API printer requires XML input')
ent.hidden = (ent.name not in \
ent.xml_data.entry_points_for_api_version('es1')) \
or ent.hidden
ent.handcode = False
def _get_c_header(self):
header = """#ifndef _GLAPI_TMP_H_
#define _GLAPI_TMP_H_
typedef int GLclampx;
#endif /* _GLAPI_TMP_H_ */"""
return header
class ES2APIPrinter(GLAPIPrinter):
"""OpenGL ES 2.x API Printer"""
def __init__(self, entries):
super(ES2APIPrinter, self).__init__(entries)
self.prefix_lib = 'gl'
self.prefix_warn = 'gl'
def _override_for_api(self, ent):
if ent.xml_data is None:
raise Exception('ES2 API printer requires XML input')
ent.hidden = (ent.name not in \
ent.xml_data.entry_points_for_api_version('es2')) \
or ent.hidden
# This is hella ugly. The same-named function in desktop OpenGL is
# hidden, but it needs to be exposed by libGLESv2 for OpenGL ES 3.0.
# There's no way to express in the XML that a function should be be
# hidden in one API but exposed in another.
if ent.name == 'GetInternalformativ':
ent.hidden = False
ent.handcode = False
def _get_c_header(self):
header = """#ifndef _GLAPI_TMP_H_
#define _GLAPI_TMP_H_
typedef int GLclampx;
#endif /* _GLAPI_TMP_H_ */"""
return header
class SharedGLAPIPrinter(GLAPIPrinter):
"""Shared GLAPI API Printer"""
def __init__(self, entries):
super(SharedGLAPIPrinter, self).__init__(entries)
self.lib_need_table_size = True
self.lib_need_noop_array = True
self.lib_need_stubs = True
self.lib_need_all_entries = True
self.lib_need_non_hidden_entries = False
self.prefix_lib = 'shared'
self.prefix_warn = 'gl'
def _override_for_api(self, ent):
ent.hidden = True
ent.handcode = False
def _get_c_header(self):
header = """#ifndef _GLAPI_TMP_H_
#define _GLAPI_TMP_H_
typedef int GLclampx;
#endif /* _GLAPI_TMP_H_ */"""
return header
def parse_args():
printers = ['glapi', 'es1api', 'es2api', 'shared-glapi']
parser = OptionParser(usage='usage: %prog [options] <filename>')
parser.add_option('-p', '--printer', dest='printer',
help='printer to use: %s' % (", ".join(printers)))
options, args = parser.parse_args()
if not args or options.printer not in printers:
parser.print_help()
sys.exit(1)
return (args[0], options)
def main():
printers = {
'glapi': GLAPIPrinter,
'es1api': ES1APIPrinter,
'es2api': ES2APIPrinter,
'shared-glapi': SharedGLAPIPrinter,
}
filename, options = parse_args()
if filename.endswith('.xml'):
entries = abi_parse_xml(filename)
else:
entries = abi_parse(filename)
abi_sanity_check(entries)
printer = printers[options.printer](entries)
printer.output_for_lib()
if __name__ == '__main__':
main()