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