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