1
2# Mesa 3-D graphics library
3#
4# Copyright (C) 2010 LunarG Inc.
5#
6# Permission is hereby granted, free of charge, to any person obtaining a
7# copy of this software and associated documentation files (the "Software"),
8# to deal in the Software without restriction, including without limitation
9# the rights to use, copy, modify, merge, publish, distribute, sublicense,
10# and/or sell copies of the Software, and to permit persons to whom the
11# Software is furnished to do so, subject to the following conditions:
12#
13# The above copyright notice and this permission notice shall be included
14# in all copies or substantial portions of the Software.
15#
16# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22# DEALINGS IN THE SOFTWARE.
23#
24# Authors:
25#    Chia-I Wu <olv@lunarg.com>
26
27import sys
28# make it possible to import glapi
29import os
30GLAPI = os.path.join(".", os.path.dirname(__file__), "glapi", "gen")
31sys.path.insert(0, GLAPI)
32
33from operator import attrgetter
34import re
35from optparse import OptionParser
36import gl_XML
37import glX_XML
38
39
40# number of dynamic entries
41ABI_NUM_DYNAMIC_ENTRIES = 256
42
43class ABIEntry(object):
44    """Represent an ABI entry."""
45
46    _match_c_param = re.compile(
47            '^(?P<type>[\w\s*]+?)(?P<name>\w+)(\[(?P<array>\d+)\])?$')
48
49    def __init__(self, cols, attrs, xml_data = None):
50        self._parse(cols)
51
52        self.slot = attrs['slot']
53        self.hidden = attrs['hidden']
54        self.alias = attrs['alias']
55        self.handcode = attrs['handcode']
56        self.xml_data = xml_data
57
58    def c_prototype(self):
59        return '%s %s(%s)' % (self.c_return(), self.name, self.c_params())
60
61    def c_return(self):
62        ret = self.ret
63        if not ret:
64            ret = 'void'
65
66        return ret
67
68    def c_params(self):
69        """Return the parameter list used in the entry prototype."""
70        c_params = []
71        for t, n, a in self.params:
72            sep = '' if t.endswith('*') else ' '
73            arr = '[%d]' % a if a else ''
74            c_params.append(t + sep + n + arr)
75        if not c_params:
76            c_params.append('void')
77
78        return ", ".join(c_params)
79
80    def c_args(self):
81        """Return the argument list used in the entry invocation."""
82        c_args = []
83        for t, n, a in self.params:
84            c_args.append(n)
85
86        return ", ".join(c_args)
87
88    def _parse(self, cols):
89        ret = cols.pop(0)
90        if ret == 'void':
91            ret = None
92
93        name = cols.pop(0)
94
95        params = []
96        if not cols:
97            raise Exception(cols)
98        elif len(cols) == 1 and cols[0] == 'void':
99            pass
100        else:
101            for val in cols:
102                params.append(self._parse_param(val))
103
104        self.ret = ret
105        self.name = name
106        self.params = params
107
108    def _parse_param(self, c_param):
109        m = self._match_c_param.match(c_param)
110        if not m:
111            raise Exception('unrecognized param ' + c_param)
112
113        c_type = m.group('type').strip()
114        c_name = m.group('name')
115        c_array = m.group('array')
116        c_array = int(c_array) if c_array else 0
117
118        return (c_type, c_name, c_array)
119
120    def __str__(self):
121        return self.c_prototype()
122
123    def __lt__(self, other):
124        # compare slot, alias, and then name
125        if self.slot == other.slot:
126            if not self.alias:
127                return True
128            elif not other.alias:
129                return False
130
131            return self.name < other.name
132
133        return self.slot < other.slot
134
135
136def abi_parse_xml(xml):
137    """Parse a GLAPI XML file for ABI entries."""
138    api = gl_XML.parse_GL_API(xml, glX_XML.glx_item_factory())
139
140    entry_dict = {}
141    for func in api.functionIterateByOffset():
142        # make sure func.name appear first
143        entry_points = func.entry_points[:]
144        entry_points.remove(func.name)
145        entry_points.insert(0, func.name)
146
147        for name in entry_points:
148            attrs = {
149                    'slot': func.offset,
150                    'hidden': not func.is_static_entry_point(name),
151                    'alias': None if name == func.name else func.name,
152                    'handcode': bool(func.has_different_protocol(name)),
153            }
154
155            # post-process attrs
156            if attrs['alias']:
157                try:
158                    alias = entry_dict[attrs['alias']]
159                except KeyError:
160                    raise Exception('failed to alias %s' % attrs['alias'])
161                if alias.alias:
162                    raise Exception('recursive alias %s' % ent.name)
163                attrs['alias'] = alias
164            if attrs['handcode']:
165                attrs['handcode'] = func.static_glx_name(name)
166            else:
167                attrs['handcode'] = None
168
169            if name in entry_dict:
170                raise Exception('%s is duplicated' % (name))
171
172            cols = []
173            cols.append(func.return_type)
174            cols.append(name)
175            params = func.get_parameter_string(name)
176            cols.extend([p.strip() for p in params.split(',')])
177
178            ent = ABIEntry(cols, attrs, func)
179            entry_dict[ent.name] = ent
180
181    entries = sorted(entry_dict.values())
182
183    return entries
184
185def abi_sanity_check(entries):
186    if not entries:
187        return
188
189    all_names = []
190    last_slot = entries[-1].slot
191    i = 0
192    for slot in range(last_slot + 1):
193        if entries[i].slot != slot:
194            raise Exception('entries are not ordered by slots')
195        if entries[i].alias:
196            raise Exception('first entry of slot %d aliases %s'
197                    % (slot, entries[i].alias.name))
198        handcode = None
199        while i < len(entries) and entries[i].slot == slot:
200            ent = entries[i]
201            if not handcode and ent.handcode:
202                handcode = ent.handcode
203            elif ent.handcode != handcode:
204                raise Exception('two aliases with handcode %s != %s',
205                        ent.handcode, handcode)
206
207            if ent.name in all_names:
208                raise Exception('%s is duplicated' % (ent.name))
209            if ent.alias and ent.alias.name not in all_names:
210                raise Exception('failed to alias %s' % (ent.alias.name))
211            all_names.append(ent.name)
212            i += 1
213    if i < len(entries):
214        raise Exception('there are %d invalid entries' % (len(entries) - 1))
215
216class ABIPrinter(object):
217    """MAPI Printer"""
218
219    def __init__(self, entries):
220        self.entries = entries
221
222        # sort entries by their names
223        self.entries_sorted_by_names = sorted(self.entries, key=attrgetter('name'))
224
225        self.indent = ' ' * 3
226        self.noop_warn = 'noop_warn'
227        self.noop_generic = 'noop_generic'
228        self.current_get = 'entry_current_get'
229
230        self.api_defines = []
231        self.api_headers = ['"KHR/khrplatform.h"']
232        self.api_call = 'KHRONOS_APICALL'
233        self.api_entry = 'KHRONOS_APIENTRY'
234        self.api_attrs = 'KHRONOS_APIATTRIBUTES'
235
236        self.c_header = ''
237
238        self.lib_need_table_size = True
239        self.lib_need_noop_array = True
240        self.lib_need_stubs = True
241        self.lib_need_all_entries = True
242        self.lib_need_non_hidden_entries = False
243
244    def c_notice(self):
245        return '/* This file is automatically generated by mapi_abi.py.  Do not modify. */'
246
247    def c_public_includes(self):
248        """Return includes of the client API headers."""
249        defines = ['#define ' + d for d in self.api_defines]
250        includes = ['#include ' + h for h in self.api_headers]
251        return "\n".join(defines + includes)
252
253    def need_entry_point(self, ent):
254        """Return True if an entry point is needed for the entry."""
255        # non-handcode hidden aliases may share the entry they alias
256        use_alias = (ent.hidden and ent.alias and not ent.handcode)
257        return not use_alias
258
259    def c_public_declarations(self, prefix):
260        """Return the declarations of public entry points."""
261        decls = []
262        for ent in self.entries:
263            if not self.need_entry_point(ent):
264                continue
265            export = self.api_call if not ent.hidden else ''
266            if not ent.hidden or not self.lib_need_non_hidden_entries:
267                decls.append(self._c_decl(ent, prefix, True, export) + ';')
268
269        return "\n".join(decls)
270
271    def c_mapi_table(self):
272        """Return defines of the dispatch table size."""
273        num_static_entries = self.entries[-1].slot + 1
274        return ('#define MAPI_TABLE_NUM_STATIC %d\n' + \
275                '#define MAPI_TABLE_NUM_DYNAMIC %d') % (
276                        num_static_entries, ABI_NUM_DYNAMIC_ENTRIES)
277
278    def _c_function(self, ent, prefix, mangle=False, stringify=False):
279        """Return the function name of an entry."""
280        formats = {
281                True: { True: '%s_STR(%s)', False: '%s(%s)' },
282                False: { True: '"%s%s"', False: '%s%s' },
283        }
284        fmt = formats[prefix.isupper()][stringify]
285        name = ent.name
286        if mangle and ent.hidden:
287            name = '_dispatch_stub_' + str(ent.slot)
288        return fmt % (prefix, name)
289
290    def _c_function_call(self, ent, prefix):
291        """Return the function name used for calling."""
292        if ent.handcode:
293            # _c_function does not handle this case
294            formats = { True: '%s(%s)', False: '%s%s' }
295            fmt = formats[prefix.isupper()]
296            name = fmt % (prefix, ent.handcode)
297        elif self.need_entry_point(ent):
298            name = self._c_function(ent, prefix, True)
299        else:
300            name = self._c_function(ent.alias, prefix, True)
301        return name
302
303    def _c_decl(self, ent, prefix, mangle=False, export=''):
304        """Return the C declaration for the entry."""
305        decl = '%s %s %s(%s)' % (ent.c_return(), self.api_entry,
306                self._c_function(ent, prefix, mangle), ent.c_params())
307        if export:
308            decl = export + ' ' + decl
309        if self.api_attrs:
310            decl += ' ' + self.api_attrs
311
312        return decl
313
314    def _c_cast(self, ent):
315        """Return the C cast for the entry."""
316        cast = '%s (%s *)(%s)' % (
317                ent.c_return(), self.api_entry, ent.c_params())
318
319        return cast
320
321    def c_public_dispatches(self, prefix, no_hidden):
322        """Return the public dispatch functions."""
323        dispatches = []
324        for ent in self.entries:
325            if ent.hidden and no_hidden:
326                continue
327
328            if not self.need_entry_point(ent):
329                continue
330
331            export = self.api_call if not ent.hidden else ''
332
333            proto = self._c_decl(ent, prefix, True, export)
334            cast = self._c_cast(ent)
335
336            ret = ''
337            if ent.ret:
338                ret = 'return '
339            stmt1 = self.indent
340            stmt1 += 'const struct _glapi_table *_tbl = %s();' % (
341                    self.current_get)
342            stmt2 = self.indent
343            stmt2 += 'mapi_func _func = ((const mapi_func *) _tbl)[%d];' % (
344                    ent.slot)
345            stmt3 = self.indent
346            stmt3 += '%s((%s) _func)(%s);' % (ret, cast, ent.c_args())
347
348            disp = '%s\n{\n%s\n%s\n%s\n}' % (proto, stmt1, stmt2, stmt3)
349
350            if ent.handcode:
351                disp = '#if 0\n' + disp + '\n#endif'
352
353            dispatches.append(disp)
354
355        return '\n\n'.join(dispatches)
356
357    def c_public_initializer(self, prefix):
358        """Return the initializer for public dispatch functions."""
359        names = []
360        for ent in self.entries:
361            if ent.alias:
362                continue
363
364            name = '%s(mapi_func) %s' % (self.indent,
365                    self._c_function_call(ent, prefix))
366            names.append(name)
367
368        return ',\n'.join(names)
369
370    def c_stub_string_pool(self):
371        """Return the string pool for use by stubs."""
372        # sort entries by their names
373        sorted_entries = sorted(self.entries, key=attrgetter('name'))
374
375        pool = []
376        offsets = {}
377        count = 0
378        for ent in sorted_entries:
379            offsets[ent] = count
380            pool.append('%s' % (ent.name))
381            count += len(ent.name) + 1
382
383        pool_str =  self.indent + '"' + \
384                ('\\0"\n' + self.indent + '"').join(pool) + '";'
385        return (pool_str, offsets)
386
387    def c_stub_initializer(self, prefix, pool_offsets):
388        """Return the initializer for struct mapi_stub array."""
389        stubs = []
390        for ent in self.entries_sorted_by_names:
391            stubs.append('%s{ (void *) %d, %d, NULL }' % (
392                self.indent, pool_offsets[ent], ent.slot))
393
394        return ',\n'.join(stubs)
395
396    def c_noop_functions(self, prefix, warn_prefix):
397        """Return the noop functions."""
398        noops = []
399        for ent in self.entries:
400            if ent.alias:
401                continue
402
403            proto = self._c_decl(ent, prefix, False, 'static')
404
405            stmt1 = self.indent;
406            space = ''
407            for t, n, a in ent.params:
408                stmt1 += "%s(void) %s;" % (space, n)
409                space = ' '
410
411            if ent.params:
412                stmt1 += '\n';
413
414            stmt1 += self.indent + '%s(%s);' % (self.noop_warn,
415                    self._c_function(ent, warn_prefix, False, True))
416
417            if ent.ret:
418                stmt2 = self.indent + 'return (%s) 0;' % (ent.ret)
419                noop = '%s\n{\n%s\n%s\n}' % (proto, stmt1, stmt2)
420            else:
421                noop = '%s\n{\n%s\n}' % (proto, stmt1)
422
423            noops.append(noop)
424
425        return '\n\n'.join(noops)
426
427    def c_noop_initializer(self, prefix, use_generic):
428        """Return an initializer for the noop dispatch table."""
429        entries = [self._c_function(ent, prefix)
430                for ent in self.entries if not ent.alias]
431        if use_generic:
432            entries = [self.noop_generic] * len(entries)
433
434        entries.extend([self.noop_generic] * ABI_NUM_DYNAMIC_ENTRIES)
435
436        pre = self.indent + '(mapi_func) '
437        return pre + (',\n' + pre).join(entries)
438
439    def c_asm_gcc(self, prefix, no_hidden):
440        asm = []
441
442        for ent in self.entries:
443            if ent.hidden and no_hidden:
444                continue
445
446            if not self.need_entry_point(ent):
447                continue
448
449            name = self._c_function(ent, prefix, True, True)
450
451            if ent.handcode:
452                asm.append('#if 0')
453
454            if ent.hidden:
455                asm.append('".hidden "%s"\\n"' % (name))
456
457            if ent.alias and not (ent.alias.hidden and no_hidden):
458                asm.append('".globl "%s"\\n"' % (name))
459                asm.append('".set "%s", "%s"\\n"' % (name,
460                    self._c_function(ent.alias, prefix, True, True)))
461            else:
462                asm.append('STUB_ASM_ENTRY(%s)"\\n"' % (name))
463                asm.append('"\\t"STUB_ASM_CODE("%d")"\\n"' % (ent.slot))
464
465            if ent.handcode:
466                asm.append('#endif')
467            asm.append('')
468
469        return "\n".join(asm)
470
471    def output_for_lib(self):
472        print(self.c_notice())
473
474        if self.c_header:
475            print()
476            print(self.c_header)
477
478        print()
479        print('#ifdef MAPI_TMP_DEFINES')
480        print(self.c_public_includes())
481        print()
482        print('#ifdef MemoryBarrier')
483        print('#undef MemoryBarrier')
484        print('#endif')
485        print()
486        print(self.c_public_declarations(self.prefix_lib))
487        print('#undef MAPI_TMP_DEFINES')
488        print('#endif /* MAPI_TMP_DEFINES */')
489
490        if self.lib_need_table_size:
491            print()
492            print('#ifdef MAPI_TMP_TABLE')
493            print(self.c_mapi_table())
494            print('#undef MAPI_TMP_TABLE')
495            print('#endif /* MAPI_TMP_TABLE */')
496
497        if self.lib_need_noop_array:
498            print()
499            print('#ifdef MAPI_TMP_NOOP_ARRAY')
500            print('#ifdef DEBUG')
501            print()
502            print(self.c_noop_functions(self.prefix_noop, self.prefix_warn))
503            print()
504            print('const mapi_func table_%s_array[] = {' % (self.prefix_noop))
505            print(self.c_noop_initializer(self.prefix_noop, False))
506            print('};')
507            print()
508            print('#else /* DEBUG */')
509            print()
510            print('const mapi_func table_%s_array[] = {' % (self.prefix_noop))
511            print(self.c_noop_initializer(self.prefix_noop, True))
512            print('};')
513            print()
514            print('#endif /* DEBUG */')
515            print('#undef MAPI_TMP_NOOP_ARRAY')
516            print('#endif /* MAPI_TMP_NOOP_ARRAY */')
517
518        if self.lib_need_stubs:
519            pool, pool_offsets = self.c_stub_string_pool()
520            print()
521            print('#ifdef MAPI_TMP_PUBLIC_STUBS')
522            print('static const char public_string_pool[] =')
523            print(pool)
524            print()
525            print('static const struct mapi_stub public_stubs[] = {')
526            print(self.c_stub_initializer(self.prefix_lib, pool_offsets))
527            print('};')
528            print('#undef MAPI_TMP_PUBLIC_STUBS')
529            print('#endif /* MAPI_TMP_PUBLIC_STUBS */')
530
531        if self.lib_need_all_entries:
532            print()
533            print('#ifdef MAPI_TMP_PUBLIC_ENTRIES')
534            print(self.c_public_dispatches(self.prefix_lib, False))
535            print()
536            print('static const mapi_func public_entries[] = {')
537            print(self.c_public_initializer(self.prefix_lib))
538            print('};')
539            print('#undef MAPI_TMP_PUBLIC_ENTRIES')
540            print('#endif /* MAPI_TMP_PUBLIC_ENTRIES */')
541
542            print()
543            print('#ifdef MAPI_TMP_STUB_ASM_GCC')
544            print('__asm__(')
545            print(self.c_asm_gcc(self.prefix_lib, False))
546            print(');')
547            print('#undef MAPI_TMP_STUB_ASM_GCC')
548            print('#endif /* MAPI_TMP_STUB_ASM_GCC */')
549
550        if self.lib_need_non_hidden_entries:
551            all_hidden = True
552            for ent in self.entries:
553                if not ent.hidden:
554                    all_hidden = False
555                    break
556            if not all_hidden:
557                print()
558                print('#ifdef MAPI_TMP_PUBLIC_ENTRIES_NO_HIDDEN')
559                print(self.c_public_dispatches(self.prefix_lib, True))
560                print()
561                print('/* does not need public_entries */')
562                print('#undef MAPI_TMP_PUBLIC_ENTRIES_NO_HIDDEN')
563                print('#endif /* MAPI_TMP_PUBLIC_ENTRIES_NO_HIDDEN */')
564
565                print()
566                print('#ifdef MAPI_TMP_STUB_ASM_GCC_NO_HIDDEN')
567                print('__asm__(')
568                print(self.c_asm_gcc(self.prefix_lib, True))
569                print(');')
570                print('#undef MAPI_TMP_STUB_ASM_GCC_NO_HIDDEN')
571                print('#endif /* MAPI_TMP_STUB_ASM_GCC_NO_HIDDEN */')
572
573class GLAPIPrinter(ABIPrinter):
574    """OpenGL API Printer"""
575
576    def __init__(self, entries):
577        for ent in entries:
578            self._override_for_api(ent)
579        super(GLAPIPrinter, self).__init__(entries)
580
581        self.api_defines = ['GL_GLEXT_PROTOTYPES']
582        self.api_headers = ['"GL/gl.h"', '"GL/glext.h"']
583        self.api_call = 'GLAPI'
584        self.api_entry = 'APIENTRY'
585        self.api_attrs = ''
586
587        self.lib_need_table_size = False
588        self.lib_need_noop_array = False
589        self.lib_need_stubs = False
590        self.lib_need_all_entries = False
591        self.lib_need_non_hidden_entries = True
592
593        self.prefix_lib = 'GLAPI_PREFIX'
594        self.prefix_noop = 'noop'
595        self.prefix_warn = self.prefix_lib
596
597        self.c_header = self._get_c_header()
598
599    def _override_for_api(self, ent):
600        """Override attributes of an entry if necessary for this
601        printer."""
602        # By default, no override is necessary.
603        pass
604
605    def _get_c_header(self):
606        header = """#ifndef _GLAPI_TMP_H_
607#define _GLAPI_TMP_H_
608#define GLAPI_PREFIX(func)  gl##func
609#define GLAPI_PREFIX_STR(func)  "gl"#func
610
611typedef int GLclampx;
612#endif /* _GLAPI_TMP_H_ */"""
613
614        return header
615
616class SharedGLAPIPrinter(GLAPIPrinter):
617    """Shared GLAPI API Printer"""
618
619    def __init__(self, entries):
620        super(SharedGLAPIPrinter, self).__init__(entries)
621
622        self.lib_need_table_size = True
623        self.lib_need_noop_array = True
624        self.lib_need_stubs = True
625        self.lib_need_all_entries = True
626        self.lib_need_non_hidden_entries = False
627
628        self.prefix_lib = 'shared'
629        self.prefix_warn = 'gl'
630
631    def _override_for_api(self, ent):
632        ent.hidden = True
633        ent.handcode = False
634
635    def _get_c_header(self):
636        header = """#ifndef _GLAPI_TMP_H_
637#define _GLAPI_TMP_H_
638typedef int GLclampx;
639#endif /* _GLAPI_TMP_H_ */"""
640
641        return header
642
643def parse_args():
644    printers = ['glapi', 'es1api', 'es2api', 'shared-glapi']
645
646    parser = OptionParser(usage='usage: %prog [options] <xml_file>')
647    parser.add_option('-p', '--printer', dest='printer',
648            help='printer to use: %s' % (", ".join(printers)))
649
650    options, args = parser.parse_args()
651    if not args or options.printer not in printers:
652        parser.print_help()
653        sys.exit(1)
654
655    if not args[0].endswith('.xml'):
656        parser.print_help()
657        sys.exit(1)
658
659    return (args[0], options)
660
661def main():
662    printers = {
663        'glapi': GLAPIPrinter,
664        'shared-glapi': SharedGLAPIPrinter,
665    }
666
667    filename, options = parse_args()
668
669    entries = abi_parse_xml(filename)
670    abi_sanity_check(entries)
671
672    printer = printers[options.printer](entries)
673    printer.output_for_lib()
674
675if __name__ == '__main__':
676    main()
677