1#!/usr/bin/env python3
2# Copyright (c) 2017-2020 Google LLC
3#
4# Permission is hereby granted, free of charge, to any person obtaining a
5# copy of this software and/or associated documentation files (the
6# "Materials"), to deal in the Materials without restriction, including
7# without limitation the rights to use, copy, modify, merge, publish,
8# distribute, sublicense, and/or sell copies of the Materials, and to
9# permit persons to whom the Materials are furnished to do so, subject to
10# the following conditions:
11#
12# The above copyright notice and this permission notice shall be included
13# in all copies or substantial portions of the Materials.
14#
15# MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
16# KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
17# SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
18#    https://www.khronos.org/registry/
19#
20# THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
25# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
26# MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
27
28"""Generates a C language headers from a SPIR-V JSON grammar file"""
29
30import errno
31import json
32import os.path
33import re
34
35DEFAULT_COPYRIGHT="""Copyright (c) 2020 The Khronos Group Inc.
36
37Permission is hereby granted, free of charge, to any person obtaining a
38copy of this software and/or associated documentation files (the
39"Materials"), to deal in the Materials without restriction, including
40without limitation the rights to use, copy, modify, merge, publish,
41distribute, sublicense, and/or sell copies of the Materials, and to
42permit persons to whom the Materials are furnished to do so, subject to
43the following conditions:
44
45The above copyright notice and this permission notice shall be included
46in all copies or substantial portions of the Materials.
47
48MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
49KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
50SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
51   https://www.khronos.org/registry/
52
53THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
54EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
55MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
56IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
57CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
58TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
59MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
60""".split('\n')
61
62def make_path_to_file(f):
63    """Makes all ancestor directories to the given file, if they
64    don't yet exist.
65
66    Arguments:
67        f: The file whose ancestor directories are to be created.
68    """
69    dir = os.path.dirname(os.path.abspath(f))
70    try:
71        os.makedirs(dir)
72    except OSError as e:
73        if e.errno == errno.EEXIST and os.path.isdir(dir):
74            pass
75        else:
76            raise
77
78class ExtInstGrammar:
79    """The grammar for an extended instruction set"""
80
81    def __init__(self, name, copyright, instructions, operand_kinds, version = None, revision = None):
82       self.name = name
83       self.copyright = copyright
84       self.instructions = instructions
85       self.operand_kinds = operand_kinds
86       self.version = version
87       self.revision = revision
88
89
90class LangGenerator:
91    """A language-specific generator"""
92
93    def __init__(self):
94        self.upper_case_initial = re.compile('^[A-Z]')
95        pass
96
97    def comment_prefix(self):
98        return ""
99
100    def namespace_prefix(self):
101        return ""
102
103    def uses_guards(self):
104        return False
105
106    def cpp_guard_preamble(self):
107        return ""
108
109    def cpp_guard_postamble(self):
110        return ""
111
112    def enum_value(self, prefix, name, value):
113        if self.upper_case_initial.match(name):
114            use_name = name
115        else:
116            use_name = '_' + name
117
118        return "    {}{} = {},".format(prefix, use_name, value)
119
120    def generate(self, grammar):
121        """Returns a string that is the language-specific header for the given grammar"""
122
123        parts = []
124        if grammar.copyright:
125            parts.extend(["{}{}".format(self.comment_prefix(), f) for f in grammar.copyright])
126        parts.append('')
127
128        guard = 'SPIRV_UNIFIED1_{}_H_'.format(grammar.name)
129        if self.uses_guards:
130            parts.append('#ifndef {}'.format(guard))
131            parts.append('#define {}'.format(guard))
132        parts.append('')
133
134        parts.append(self.cpp_guard_preamble())
135
136        if grammar.version:
137            parts.append(self.const_definition(grammar.name, 'Version', grammar.version))
138
139        if grammar.revision is not None:
140            parts.append(self.const_definition(grammar.name, 'Revision', grammar.revision))
141
142        parts.append('')
143
144        if grammar.instructions:
145            parts.append(self.enum_prefix(grammar.name, 'Instructions'))
146            for inst in grammar.instructions:
147                parts.append(self.enum_value(grammar.name, inst['opname'], inst['opcode']))
148            parts.append(self.enum_end(grammar.name, 'Instructions'))
149            parts.append('')
150
151        if grammar.operand_kinds:
152            for kind in grammar.operand_kinds:
153                parts.append(self.enum_prefix(grammar.name, kind['kind']))
154                for e in kind['enumerants']:
155                    parts.append(self.enum_value(grammar.name, e['enumerant'], e['value']))
156                parts.append(self.enum_end(grammar.name, kind['kind']))
157            parts.append('')
158
159        parts.append(self.cpp_guard_postamble())
160
161        if self.uses_guards:
162            parts.append('#endif // {}'.format(guard))
163
164        # Ensre the file ends in an end of line
165        parts.append('')
166
167        return '\n'.join(parts)
168
169
170class CLikeGenerator(LangGenerator):
171    def uses_guards(self):
172        return True
173
174    def comment_prefix(self):
175        return "// "
176
177    def const_definition(self, prefix, var, value):
178        # Use an anonymous enum.  Don't use a static const int variable because
179        # that can bloat binary size.
180        return 'enum {0}{1}{2}{3} = {4},{1}{2}{3}_BitWidthPadding = 0x7fffffff{5};'.format(
181               '{', '\n    ', prefix, var, value, '\n}')
182
183    def enum_prefix(self, prefix, name):
184        return 'enum {}{} {}'.format(prefix, name, '{')
185
186    def enum_end(self, prefix, enum):
187        return '    {}{}Max = 0x7fffffff\n{};\n'.format(prefix, enum, '}')
188
189    def cpp_guard_preamble(self):
190        return '#ifdef __cplusplus\nextern "C" {\n#endif\n'
191
192    def cpp_guard_postamble(self):
193        return '#ifdef __cplusplus\n}\n#endif\n'
194
195
196class CGenerator(CLikeGenerator):
197    pass
198
199
200def main():
201    import argparse
202    parser = argparse.ArgumentParser(description='Generate language headers from a JSON grammar')
203
204    parser.add_argument('--extinst-name',
205                        type=str, required=True,
206                        help='The name to use in tokens')
207    parser.add_argument('--extinst-grammar', metavar='<path>',
208                        type=str, required=True,
209                        help='input JSON grammar file for extended instruction set')
210    parser.add_argument('--extinst-output-base', metavar='<path>',
211                        type=str, required=True,
212                        help='Basename of the language-specific output file.')
213    args = parser.parse_args()
214
215    with open(args.extinst_grammar) as json_file:
216        grammar_json = json.loads(json_file.read())
217        if 'copyright' in grammar_json:
218          copyright = grammar_json['copyright']
219        else:
220          copyright = DEFAULT_COPYRIGHT
221        if 'version' in grammar_json:
222          version = grammar_json['version']
223        else:
224          version = 0
225        if 'operand_kinds' in grammar_json:
226          operand_kinds = grammar_json['operand_kinds']
227        else:
228          operand_kinds = []
229
230        grammar = ExtInstGrammar(name = args.extinst_name,
231                                 copyright = copyright,
232                                 instructions = grammar_json['instructions'],
233                                 operand_kinds = operand_kinds,
234                                 version = version,
235                                 revision = grammar_json['revision'])
236        make_path_to_file(args.extinst_output_base)
237        with open(args.extinst_output_base + '.h', 'w') as f:
238            f.write(CGenerator().generate(grammar))
239
240
241if __name__ == '__main__':
242    main()
243