1#!/usr/bin/env python 2# Copyright (c) 2016 Google Inc. 3 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15"""Generates various info tables from SPIR-V JSON grammar.""" 16 17from __future__ import print_function 18 19import errno 20import functools 21import json 22import os.path 23import re 24 25# Prefix for all C variables generated by this script. 26PYGEN_VARIABLE_PREFIX = 'pygen_variable' 27 28# Extensions to recognize, but which don't come from the SPIRV-V core grammar. 29NONSTANDARD_EXTENSIONS = [ 30 # TODO(dneto): Vendor extension names should really be derived from the 31 # content of .json files. 32 'SPV_AMD_gcn_shader', 33 'SPV_AMD_shader_ballot', 34 'SPV_AMD_shader_explicit_vertex_parameter', 35 'SPV_AMD_shader_trinary_minmax', 36 # The following don't have an extended instruction set grammar file. 37 'SPV_AMD_gpu_shader_half_float', 38 'SPV_AMD_texture_gather_bias_lod', 39 'SPV_AMD_gpu_shader_int16', 40 # Validator would ignore type declaration unique check. Should only be used 41 # for legacy autogenerated test files containing multiple instances of the 42 # same type declaration, if fixing the test by other methods is too 43 # difficult. Shouldn't be used for any other reasons. 44 'SPV_VALIDATOR_ignore_type_decl_unique', 45] 46 47def make_path_to_file(f): 48 """Makes all ancestor directories to the given file, if they 49 don't yet exist. 50 51 Arguments: 52 f: The file whose ancestor directories are to be created. 53 """ 54 dir = os.path.dirname(os.path.abspath(f)) 55 try: 56 os.makedirs(dir) 57 except OSError as e: 58 if e.errno == errno.EEXIST and os.path.isdir(dir): 59 pass 60 else: 61 raise 62 63def compose_capability_list(caps): 64 """Returns a string containing a braced list of capabilities as enums. 65 66 Arguments: 67 - caps: a sequence of capability names 68 69 Returns: 70 a string containing the braced list of SpvCapability* enums named by caps. 71 """ 72 return "{" + ", ".join(['SpvCapability{}'.format(c) for c in caps]) + "}" 73 74 75def compose_extension_list(exts): 76 """Returns a string containing a braced list of extensions as enums. 77 78 Arguments: 79 - exts: a sequence of extension names 80 81 Returns: 82 a string containing the braced list of extensions named by exts. 83 """ 84 return "{" + ", ".join(['libspirv::Extension::k{}'.format(e) for e in exts]) + "}" 85 86 87def convert_operand_kind(operand_tuple): 88 """Returns the corresponding operand type used in spirv-tools for 89 the given operand kind and quantifier used in the JSON grammar. 90 91 Arguments: 92 - operand_tuple: a tuple of two elements: 93 - operand kind: used in the JSON grammar 94 - quantifier: '', '?', or '*' 95 96 Returns: 97 a string of the enumerant name in spv_operand_type_t 98 """ 99 kind, quantifier = operand_tuple 100 # The following cases are where we differ between the JSON grammar and 101 # spirv-tools. 102 if kind == 'IdResultType': 103 kind = 'TypeId' 104 elif kind == 'IdResult': 105 kind = 'ResultId' 106 elif kind == 'IdMemorySemantics' or kind == 'MemorySemantics': 107 kind = 'MemorySemanticsId' 108 elif kind == 'IdScope' or kind == 'Scope': 109 kind = 'ScopeId' 110 elif kind == 'IdRef': 111 kind = 'Id' 112 113 elif kind == 'ImageOperands': 114 kind = 'Image' 115 elif kind == 'Dim': 116 kind = 'Dimensionality' 117 elif kind == 'ImageFormat': 118 kind = 'SamplerImageFormat' 119 elif kind == 'KernelEnqueueFlags': 120 kind = 'KernelEnqFlags' 121 122 elif kind == 'LiteralExtInstInteger': 123 kind = 'ExtensionInstructionNumber' 124 elif kind == 'LiteralSpecConstantOpInteger': 125 kind = 'SpecConstantOpNumber' 126 elif kind == 'LiteralContextDependentNumber': 127 kind = 'TypedLiteralNumber' 128 129 elif kind == 'PairLiteralIntegerIdRef': 130 kind = 'LiteralIntegerId' 131 elif kind == 'PairIdRefLiteralInteger': 132 kind = 'IdLiteralInteger' 133 elif kind == 'PairIdRefIdRef': # Used by OpPhi in the grammar 134 kind = 'Id' 135 136 if kind == 'FPRoundingMode': 137 kind = 'FpRoundingMode' 138 elif kind == 'FPFastMathMode': 139 kind = 'FpFastMathMode' 140 141 if quantifier == '?': 142 kind = 'Optional{}'.format(kind) 143 elif quantifier == '*': 144 kind = 'Variable{}'.format(kind) 145 146 return 'SPV_OPERAND_TYPE_{}'.format( 147 re.sub(r'([a-z])([A-Z])', r'\1_\2', kind).upper()) 148 149 150class InstInitializer(object): 151 """Instances holds a SPIR-V instruction suitable for printing as 152 the initializer for spv_opcode_desc_t.""" 153 154 def __init__(self, opname, caps, operands): 155 """Initialization. 156 157 Arguments: 158 - opname: opcode name (with the 'Op' prefix) 159 - caps: a sequence of capability names required by this opcode 160 - operands: a sequence of (operand-kind, operand-quantifier) tuples 161 """ 162 assert opname.startswith('Op') 163 self.opname = opname[2:] # Remove the "Op" prefix. 164 self.caps_mask = compose_capability_list(caps) 165 self.operands = [convert_operand_kind(o) for o in operands] 166 167 self.fix_syntax() 168 169 operands = [o[0] for o in operands] 170 self.ref_type_id = 'IdResultType' in operands 171 self.def_result_id = 'IdResult' in operands 172 173 174 def fix_syntax(self): 175 """Fix an instruction's syntax, adjusting for differences between 176 the officially released grammar and how SPIRV-Tools uses the grammar. 177 178 Fixes: 179 - ExtInst should not end with SPV_OPERAND_VARIABLE_ID. 180 https://github.com/KhronosGroup/SPIRV-Tools/issues/233 181 """ 182 if (self.opname == 'ExtInst' 183 and self.operands[-1] == 'SPV_OPERAND_TYPE_VARIABLE_ID'): 184 self.operands.pop() 185 186 187 def __str__(self): 188 template = ['{{"{opname}"', 'SpvOp{opname}', '{caps_mask}', 189 '{num_operands}', '{{{operands}}}', 190 '{def_result_id}', '{ref_type_id}}}'] 191 return ', '.join(template).format( 192 opname=self.opname, 193 caps_mask=self.caps_mask, 194 num_operands=len(self.operands), 195 operands=', '.join(self.operands), 196 def_result_id=(1 if self.def_result_id else 0), 197 ref_type_id=(1 if self.ref_type_id else 0)) 198 199 200class ExtInstInitializer(object): 201 """Instances holds a SPIR-V extended instruction suitable for printing as 202 the initializer for spv_ext_inst_desc_t.""" 203 204 def __init__(self, opname, opcode, caps, operands): 205 """Initialization. 206 207 Arguments: 208 - opname: opcode name 209 - opcode: enumerant value for this opcode 210 - caps: a sequence of capability names required by this opcode 211 - operands: a sequence of (operand-kind, operand-quantifier) tuples 212 """ 213 self.opname = opname 214 self.opcode = opcode 215 self.caps_mask = compose_capability_list(caps) 216 self.operands = [convert_operand_kind(o) for o in operands] 217 self.operands.append('SPV_OPERAND_TYPE_NONE') 218 219 def __str__(self): 220 template = ['{{"{opname}"', '{opcode}', '{caps_mask}', 221 '{{{operands}}}}}'] 222 return ', '.join(template).format( 223 opname=self.opname, 224 opcode=self.opcode, 225 caps_mask=self.caps_mask, 226 operands=', '.join(self.operands)) 227 228 229def generate_instruction(inst, is_ext_inst): 230 """Returns the C initializer for the given SPIR-V instruction. 231 232 Arguments: 233 - inst: a dict containing information about a SPIR-V instruction 234 - is_ext_inst: a bool indicating whether |inst| is an extended 235 instruction. 236 237 Returns: 238 a string containing the C initializer for spv_opcode_desc_t or 239 spv_ext_inst_desc_t 240 """ 241 opname = inst.get('opname') 242 opcode = inst.get('opcode') 243 caps = inst.get('capabilities', []) 244 operands = inst.get('operands', {}) 245 operands = [(o['kind'], o.get('quantifier', '')) for o in operands] 246 247 assert opname is not None 248 249 if is_ext_inst: 250 return str(ExtInstInitializer(opname, opcode, caps, operands)) 251 else: 252 return str(InstInitializer(opname, caps, operands)) 253 254 255def generate_instruction_table(inst_table, is_ext_inst): 256 """Returns the info table containing all SPIR-V instructions. 257 258 Arguments: 259 - inst_table: a dict containing all SPIR-V instructions. 260 - is_ext_inst: a bool indicating whether |inst_table| is for 261 an extended instruction set. 262 """ 263 return ',\n'.join([generate_instruction(inst, is_ext_inst) 264 for inst in inst_table]) 265 266 267class EnumerantInitializer(object): 268 """Prints an enumerant as the initializer for spv_operand_desc_t.""" 269 270 def __init__(self, enumerant, value, caps, exts, parameters): 271 """Initialization. 272 273 Arguments: 274 - enumerant: enumerant name 275 - value: enumerant value 276 - caps: a sequence of capability names required by this enumerant 277 - exts: a sequence of names of extensions enabling this enumerant 278 - parameters: a sequence of (operand-kind, operand-quantifier) tuples 279 """ 280 self.enumerant = enumerant 281 self.value = value 282 self.caps = compose_capability_list(caps) 283 self.exts = compose_extension_list(exts) 284 self.parameters = [convert_operand_kind(p) for p in parameters] 285 286 def __str__(self): 287 template = ['{{"{enumerant}"', '{value}', 288 '{caps}', '{exts}', '{{{parameters}}}}}'] 289 return ', '.join(template).format( 290 enumerant=self.enumerant, 291 value=self.value, 292 caps=self.caps, 293 exts=self.exts, 294 parameters=', '.join(self.parameters)) 295 296 297def generate_enum_operand_kind_entry(entry): 298 """Returns the C initializer for the given operand enum entry. 299 300 Arguments: 301 - entry: a dict containing information about an enum entry 302 303 Returns: 304 a string containing the C initializer for spv_operand_desc_t 305 """ 306 enumerant = entry.get('enumerant') 307 value = entry.get('value') 308 caps = entry.get('capabilities', []) 309 exts = entry.get('extensions', []) 310 params = entry.get('parameters', []) 311 params = [p.get('kind') for p in params] 312 params = zip(params, [''] * len(params)) 313 314 assert enumerant is not None 315 assert value is not None 316 317 return str(EnumerantInitializer(enumerant, value, caps, exts, params)) 318 319 320def generate_enum_operand_kind(enum): 321 """Returns the C definition for the given operand kind.""" 322 kind = enum.get('kind') 323 assert kind is not None 324 325 name = '{}_{}Entries'.format(PYGEN_VARIABLE_PREFIX, kind) 326 entries = [' {}'.format(generate_enum_operand_kind_entry(e)) 327 for e in enum.get('enumerants', [])] 328 329 template = ['static const spv_operand_desc_t {name}[] = {{', 330 '{entries}', '}};'] 331 entries = '\n'.join(template).format( 332 name=name, 333 entries=',\n'.join(entries)) 334 335 return kind, name, entries 336 337 338def generate_operand_kind_table(enums): 339 """Returns the info table containing all SPIR-V operand kinds.""" 340 # We only need to output info tables for those operand kinds that are enums. 341 enums = [generate_enum_operand_kind(e) 342 for e in enums 343 if e.get('category') in ['ValueEnum', 'BitEnum']] 344 # We have three operand kinds that requires their optional counterpart to 345 # exist in the operand info table. 346 three_optional_enums = ['ImageOperands', 'AccessQualifier', 'MemoryAccess'] 347 three_optional_enums = [e for e in enums if e[0] in three_optional_enums] 348 enums.extend(three_optional_enums) 349 350 enum_kinds, enum_names, enum_entries = zip(*enums) 351 # Mark the last three as optional ones. 352 enum_quantifiers = [''] * (len(enums) - 3) + ['?'] * 3 353 # And we don't want redefinition of them. 354 enum_entries = enum_entries[:-3] 355 enum_kinds = [convert_operand_kind(e) 356 for e in zip(enum_kinds, enum_quantifiers)] 357 table_entries = zip(enum_kinds, enum_names, enum_names) 358 table_entries = [' {{{}, ARRAY_SIZE({}), {}}}'.format(*e) 359 for e in table_entries] 360 361 template = [ 362 'static const spv_operand_desc_group_t {p}_OperandInfoTable[] = {{', 363 '{enums}', '}};'] 364 table = '\n'.join(template).format( 365 p=PYGEN_VARIABLE_PREFIX, enums=',\n'.join(table_entries)) 366 367 return '\n\n'.join(enum_entries + (table,)) 368 369 370def get_extension_list(operands): 371 """Returns extensions as an alphabetically sorted list of strings.""" 372 enumerants = sum([item.get('enumerants', []) for item in operands 373 if item.get('category') in ['ValueEnum']], []) 374 375 extensions = sum([item.get('extensions', []) for item in enumerants 376 if item.get('extensions')], []) 377 378 extensions.extend(NONSTANDARD_EXTENSIONS) 379 380 return sorted(set(extensions)) 381 382 383def get_capabilities(operands): 384 """Returns capabilities as a list of JSON objects, in order of 385 appearance. 386 """ 387 enumerants = sum([item.get('enumerants', []) for item in operands 388 if item.get('kind') in ['Capability']], []) 389 return enumerants 390 391 392def generate_extension_enum(operands): 393 """Returns enumeration containing extensions declared in the grammar.""" 394 extensions = get_extension_list(operands) 395 return ',\n'.join(['k' + extension for extension in extensions]) 396 397 398def generate_extension_to_string_table(operands): 399 """Returns extension to string mapping table.""" 400 extensions = get_extension_list(operands) 401 entry_template = ' {{Extension::k{extension},\n "{extension}"}}' 402 table_entries = [entry_template.format(extension=extension) 403 for extension in extensions] 404 table_template = '{{\n{enums}\n}}' 405 return table_template.format(enums=',\n'.join(table_entries)) 406 407 408def generate_string_to_extension_table(operands): 409 """Returns string to extension mapping table.""" 410 extensions = get_extension_list(operands) 411 entry_template = ' {{"{extension}",\n Extension::k{extension}}}' 412 table_entries = [entry_template.format(extension=extension) 413 for extension in extensions] 414 table_template = '{{\n{enums}\n}}' 415 return table_template.format(enums=',\n'.join(table_entries)) 416 417 418def generate_capability_to_string_table(operands): 419 """Returns capability to string mapping table.""" 420 capabilities = [item.get('enumerant') 421 for item in get_capabilities(operands)] 422 entry_template = ' {{SpvCapability{capability},\n "{capability}"}}' 423 table_entries = [entry_template.format(capability=capability) 424 for capability in capabilities] 425 table_template = '{{\n{enums}\n}}' 426 return table_template.format(enums=',\n'.join(table_entries)) 427 428 429def generate_extension_to_string_mapping(operands): 430 """Returns mapping function from extensions to corresponding strings.""" 431 extensions = get_extension_list(operands) 432 function = 'std::string ExtensionToString(Extension extension) {\n' 433 function += ' switch (extension) {\n' 434 template = ' case Extension::k{extension}:\n' \ 435 ' return "{extension}";\n' 436 function += ''.join([template.format(extension=extension) 437 for extension in extensions]) 438 function += ' };\n\n return "";\n}' 439 return function 440 441 442def generate_string_to_extension_mapping(operands): 443 """Returns mapping function from strings to corresponding extensions.""" 444 function = 'bool GetExtensionFromString(' \ 445 'const std::string& str, Extension* extension) {\n ' \ 446 'static const std::unordered_map<std::string, Extension> mapping =\n' 447 function += generate_string_to_extension_table(operands) 448 function += ';\n\n' 449 function += ' const auto it = mapping.find(str);\n\n' \ 450 ' if (it == mapping.end()) return false;\n\n' \ 451 ' *extension = it->second;\n return true;\n}' 452 return function 453 454 455def generate_capability_to_string_mapping(operands): 456 """Returns mapping function from capabilities to corresponding strings. 457 We take care to avoid emitting duplicate values. 458 """ 459 function = 'std::string CapabilityToString(SpvCapability capability) {\n' 460 function += ' switch (capability) {\n' 461 template = ' case SpvCapability{capability}:\n' \ 462 ' return "{capability}";\n' 463 emitted = set() # The values of capabilities we already have emitted 464 for capability in get_capabilities(operands): 465 value = capability.get('value') 466 if value not in emitted: 467 emitted.add(value) 468 function += template.format(capability=capability.get('enumerant')) 469 function += ' case SpvCapabilityMax:\n' \ 470 ' assert(0 && "Attempting to convert SpvCapabilityMax to string");\n' \ 471 ' return "";\n' 472 function += ' };\n\n return "";\n}' 473 return function 474 475 476def generate_all_string_enum_mappings(operands): 477 """Returns all string-to-enum / enum-to-string mapping tables.""" 478 tables = [] 479 tables.append(generate_extension_to_string_mapping(operands)) 480 tables.append(generate_string_to_extension_mapping(operands)) 481 tables.append(generate_capability_to_string_mapping(operands)) 482 return '\n\n'.join(tables) 483 484 485def main(): 486 import argparse 487 parser = argparse.ArgumentParser(description='Generate SPIR-V info tables') 488 parser.add_argument('--spirv-core-grammar', metavar='<path>', 489 type=str, required=False, 490 help='input JSON grammar file for core SPIR-V ' 491 'instructions') 492 parser.add_argument('--extinst-glsl-grammar', metavar='<path>', 493 type=str, required=False, default=None, 494 help='input JSON grammar file for GLSL extended ' 495 'instruction set') 496 parser.add_argument('--extinst-opencl-grammar', metavar='<path>', 497 type=str, required=False, default=None, 498 help='input JSON grammar file for OpenCL extended ' 499 'instruction set') 500 parser.add_argument('--core-insts-output', metavar='<path>', 501 type=str, required=False, default=None, 502 help='output file for core SPIR-V instructions') 503 parser.add_argument('--glsl-insts-output', metavar='<path>', 504 type=str, required=False, default=None, 505 help='output file for GLSL extended instruction set') 506 parser.add_argument('--opencl-insts-output', metavar='<path>', 507 type=str, required=False, default=None, 508 help='output file for OpenCL extended instruction set') 509 parser.add_argument('--operand-kinds-output', metavar='<path>', 510 type=str, required=False, default=None, 511 help='output file for operand kinds') 512 parser.add_argument('--extension-enum-output', metavar='<path>', 513 type=str, required=False, default=None, 514 help='output file for extension enumeration') 515 parser.add_argument('--enum-string-mapping-output', metavar='<path>', 516 type=str, required=False, default=None, 517 help='output file for enum-string mappings') 518 parser.add_argument('--extinst-vendor-grammar', metavar='<path>', 519 type=str, required=False, default=None, 520 help='input JSON grammar file for vendor extended ' 521 'instruction set'), 522 parser.add_argument('--vendor-insts-output', metavar='<path>', 523 type=str, required=False, default=None, 524 help='output file for vendor extended instruction set') 525 args = parser.parse_args() 526 527 if (args.core_insts_output is None) != \ 528 (args.operand_kinds_output is None): 529 print('error: --core-insts-output and --operand_kinds_output ' 530 'should be specified together.') 531 exit(1) 532 if (args.glsl_insts_output is None) != \ 533 (args.extinst_glsl_grammar is None): 534 print('error: --glsl-insts-output and --extinst-glsl-grammar ' 535 'should be specified together.') 536 exit(1) 537 if (args.opencl_insts_output is None) != \ 538 (args.extinst_opencl_grammar is None): 539 print('error: --opencl-insts-output and --extinst-opencl-grammar ' 540 'should be specified together.') 541 exit(1) 542 if (args.vendor_insts_output is None) != \ 543 (args.extinst_vendor_grammar is None): 544 print('error: --vendor-insts-output and ' 545 '--extinst-vendor-grammar should be specified together.') 546 exit(1) 547 if all([args.core_insts_output is None, 548 args.glsl_insts_output is None, 549 args.opencl_insts_output is None, 550 args.vendor_insts_output is None, 551 args.extension_enum_output is None, 552 args.enum_string_mapping_output is None]): 553 print('error: at least one output should be specified.') 554 exit(1) 555 556 if args.spirv_core_grammar is not None: 557 with open(args.spirv_core_grammar) as json_file: 558 grammar = json.loads(json_file.read()) 559 if args.core_insts_output is not None: 560 make_path_to_file(args.core_insts_output) 561 make_path_to_file(args.operand_kinds_output) 562 print(generate_instruction_table(grammar['instructions'], False), 563 file=open(args.core_insts_output, 'w')) 564 print(generate_operand_kind_table(grammar['operand_kinds']), 565 file=open(args.operand_kinds_output, 'w')) 566 if args.extension_enum_output is not None: 567 make_path_to_file(args.extension_enum_output) 568 print(generate_extension_enum(grammar['operand_kinds']), 569 file=open(args.extension_enum_output, 'w')) 570 if args.enum_string_mapping_output is not None: 571 make_path_to_file(args.enum_string_mapping_output) 572 print(generate_all_string_enum_mappings( 573 grammar['operand_kinds']), 574 file=open(args.enum_string_mapping_output, 'w')) 575 576 if args.extinst_glsl_grammar is not None: 577 with open(args.extinst_glsl_grammar) as json_file: 578 grammar = json.loads(json_file.read()) 579 make_path_to_file(args.glsl_insts_output) 580 print(generate_instruction_table(grammar['instructions'], True), 581 file=open(args.glsl_insts_output, 'w')) 582 583 if args.extinst_opencl_grammar is not None: 584 with open(args.extinst_opencl_grammar) as json_file: 585 grammar = json.loads(json_file.read()) 586 make_path_to_file(args.opencl_insts_output) 587 print(generate_instruction_table(grammar['instructions'], True), 588 file=open(args.opencl_insts_output, 'w')) 589 590 if args.extinst_vendor_grammar is not None: 591 with open(args.extinst_vendor_grammar) as json_file: 592 grammar = json.loads(json_file.read()) 593 make_path_to_file(args.vendor_insts_output) 594 print(generate_instruction_table(grammar['instructions'], True), 595 file=open(args.vendor_insts_output, 'w')) 596 597 598if __name__ == '__main__': 599 main() 600