#!/usr/bin/env python # 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_mapi_table_initializer(self, prefix): """Return the array initializer for mapi_table_fill.""" entries = [self._c_function(ent, prefix) for ent in self.entries if not ent.alias] pre = self.indent + '(mapi_proc) ' return pre + (',\n' + pre).join(entries) def c_mapi_table_spec(self): """Return the spec for mapi_init.""" specv1 = [] line = '"1' for ent in self.entries: if not ent.alias: line += '\\0"\n' specv1.append(line) line = '"' line += '%s\\0' % ent.name line += '";' specv1.append(line) return self.indent + self.indent.join(specv1) 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_private_declarations(self, prefix): """Return the declarations of private functions.""" decls = [self._c_decl(ent, prefix) + ';' for ent in self.entries if not ent.alias] return "\n".join(decls) 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 mapi_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 */' def output_for_app(self): print self.c_notice() print print self.c_private_declarations(self.prefix_app) print print '#ifdef API_TMP_DEFINE_SPEC' print print 'static const char %s_spec[] =' % (self.prefix_app) print self.c_mapi_table_spec() print print 'static const mapi_proc %s_procs[] = {' % (self.prefix_app) print self.c_mapi_table_initializer(self.prefix_app) print '};' print print '#endif /* API_TMP_DEFINE_SPEC */' 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_app = '_mesa_' 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'] modes = ['lib', 'app'] parser = OptionParser(usage='usage: %prog [options] <filename>') parser.add_option('-p', '--printer', dest='printer', help='printer to use: %s' % (", ".join(printers))) parser.add_option('-m', '--mode', dest='mode', help='target user: %s' % (", ".join(modes))) options, args = parser.parse_args() if not args or options.printer not in printers or \ options.mode not in modes: 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) if options.mode == 'lib': printer.output_for_lib() else: printer.output_for_app() if __name__ == '__main__': main()