1#!/usr/bin/env python3
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
17import errno
18import json
19import os.path
20import re
21
22# Prefix for all C variables generated by this script.
23PYGEN_VARIABLE_PREFIX = 'pygen_variable'
24
25# Extensions to recognize, but which don't necessarily come from the SPIR-V
26# core or KHR grammar files.  Get this list from the SPIR-V registry web page.
27# NOTE: Only put things on this list if it is not in those grammar files.
28EXTENSIONS_FROM_SPIRV_REGISTRY_AND_NOT_FROM_GRAMMARS = """
29SPV_AMD_gcn_shader
30SPV_AMD_gpu_shader_half_float
31SPV_AMD_gpu_shader_int16
32SPV_AMD_shader_trinary_minmax
33SPV_KHR_non_semantic_info
34"""
35
36OUTPUT_LANGUAGE = 'c'
37
38def make_path_to_file(f):
39    """Makes all ancestor directories to the given file, if they don't yet
40    exist.
41
42    Arguments:
43        f: The file whose ancestor directories are to be created.
44    """
45    dir = os.path.dirname(os.path.abspath(f))
46    try:
47        os.makedirs(dir)
48    except OSError as e:
49        if e.errno == errno.EEXIST and os.path.isdir(dir):
50            pass
51        else:
52            raise
53
54
55def convert_min_required_version(version):
56    """Converts the minimal required SPIR-V version encoded in the grammar to
57    the symbol in SPIRV-Tools."""
58    if version is None:
59        return 'SPV_SPIRV_VERSION_WORD(1, 0)'
60    if version == 'None':
61        return '0xffffffffu'
62    return 'SPV_SPIRV_VERSION_WORD({})'.format(version.replace('.', ','))
63
64
65def convert_max_required_version(version):
66    """Converts the maximum required SPIR-V version encoded in the grammar to
67    the symbol in SPIRV-Tools."""
68    if version is None:
69        return '0xffffffffu'
70    return 'SPV_SPIRV_VERSION_WORD({})'.format(version.replace('.', ','))
71
72
73def compose_capability_list(caps):
74    """Returns a string containing a braced list of capabilities as enums.
75
76    Arguments:
77      - caps: a sequence of capability names
78
79    Returns:
80      a string containing the braced list of SpvCapability* or spv::Capability:: enums named by caps.
81    """
82    base_string = 'SpvCapability'
83    global OUTPUT_LANGUAGE
84    if OUTPUT_LANGUAGE == 'c++':
85        base_string = 'spv::Capability::'
86
87    return '{' + ', '.join([(base_string + '{}').format(c) for c in caps]) + '}'
88
89
90def get_capability_array_name(caps):
91    """Returns the name of the array containing all the given capabilities.
92
93    Args:
94      - caps: a sequence of capability names
95    """
96    if not caps:
97        return 'nullptr'
98    return '{}_caps_{}'.format(PYGEN_VARIABLE_PREFIX, ''.join(caps))
99
100
101def generate_capability_arrays(caps):
102    """Returns the arrays of capabilities.
103
104    Arguments:
105      - caps: a sequence of sequence of capability names
106    """
107    caps = sorted(set([tuple(c) for c in caps if c]))
108    cap_str = 'SpvCapability'
109    global OUTPUT_LANGUAGE
110    if OUTPUT_LANGUAGE == 'c++':
111        cap_str = 'spv::Capability'
112    arrays = [
113        'static const ' + cap_str + ' {}[] = {};'.format(
114            get_capability_array_name(c), compose_capability_list(c))
115        for c in caps]
116    return '\n'.join(arrays)
117
118
119def compose_extension_list(exts):
120    """Returns a string containing a braced list of extensions as enums.
121
122    Arguments:
123      - exts: a sequence of extension names
124
125    Returns:
126      a string containing the braced list of extensions named by exts.
127    """
128    return '{' + ', '.join(
129        ['spvtools::Extension::k{}'.format(e) for e in exts]) + '}'
130
131
132def get_extension_array_name(extensions):
133    """Returns the name of the array containing all the given extensions.
134
135    Args:
136      - extensions: a sequence of extension names
137    """
138    if not extensions:
139        return 'nullptr'
140    else:
141        return '{}_exts_{}'.format(
142            PYGEN_VARIABLE_PREFIX, ''.join(extensions))
143
144
145def generate_extension_arrays(extensions):
146    """Returns the arrays of extensions.
147
148    Arguments:
149      - caps: a sequence of sequence of extension names
150    """
151    extensions = sorted(set([tuple(e) for e in extensions if e]))
152    arrays = [
153        'static const spvtools::Extension {}[] = {};'.format(
154            get_extension_array_name(e), compose_extension_list(e))
155        for e in extensions]
156    return '\n'.join(arrays)
157
158
159def convert_operand_kind(operand_tuple):
160    """Returns the corresponding operand type used in spirv-tools for the given
161    operand kind and quantifier used in the JSON grammar.
162
163    Arguments:
164      - operand_tuple: a tuple of two elements:
165          - operand kind: used in the JSON grammar
166          - quantifier: '', '?', or '*'
167
168    Returns:
169      a string of the enumerant name in spv_operand_type_t
170    """
171    kind, quantifier = operand_tuple
172    # The following cases are where we differ between the JSON grammar and
173    # spirv-tools.
174    if kind == 'IdResultType':
175        kind = 'TypeId'
176    elif kind == 'IdResult':
177        kind = 'ResultId'
178    elif kind == 'IdMemorySemantics' or kind == 'MemorySemantics':
179        kind = 'MemorySemanticsId'
180    elif kind == 'IdScope' or kind == 'Scope':
181        kind = 'ScopeId'
182    elif kind == 'IdRef':
183        kind = 'Id'
184
185    elif kind == 'ImageOperands':
186        kind = 'Image'
187    elif kind == 'Dim':
188        kind = 'Dimensionality'
189    elif kind == 'ImageFormat':
190        kind = 'SamplerImageFormat'
191    elif kind == 'KernelEnqueueFlags':
192        kind = 'KernelEnqFlags'
193
194    elif kind == 'LiteralExtInstInteger':
195        kind = 'ExtensionInstructionNumber'
196    elif kind == 'LiteralSpecConstantOpInteger':
197        kind = 'SpecConstantOpNumber'
198    elif kind == 'LiteralContextDependentNumber':
199        kind = 'TypedLiteralNumber'
200
201    elif kind == 'PairLiteralIntegerIdRef':
202        kind = 'LiteralIntegerId'
203    elif kind == 'PairIdRefLiteralInteger':
204        kind = 'IdLiteralInteger'
205    elif kind == 'PairIdRefIdRef':  # Used by OpPhi in the grammar
206        kind = 'Id'
207
208    if kind == 'FPRoundingMode':
209        kind = 'FpRoundingMode'
210    elif kind == 'FPFastMathMode':
211        kind = 'FpFastMathMode'
212
213    if quantifier == '?':
214        kind = 'Optional{}'.format(kind)
215    elif quantifier == '*':
216        kind = 'Variable{}'.format(kind)
217
218    return 'SPV_OPERAND_TYPE_{}'.format(
219        re.sub(r'([a-z])([A-Z])', r'\1_\2', kind).upper())
220
221
222class InstInitializer(object):
223    """Instances holds a SPIR-V instruction suitable for printing as the
224    initializer for spv_opcode_desc_t."""
225
226    def __init__(self, opname, caps, exts, operands, version, lastVersion):
227        """Initialization.
228
229        Arguments:
230          - opname: opcode name (with the 'Op' prefix)
231          - caps: a sequence of capability names required by this opcode
232          - exts: a sequence of names of extensions enabling this enumerant
233          - operands: a sequence of (operand-kind, operand-quantifier) tuples
234          - version: minimal SPIR-V version required for this opcode
235          - lastVersion: last version of SPIR-V that includes this opcode
236        """
237
238        assert opname.startswith('Op')
239        self.opname = opname[2:]  # Remove the "Op" prefix.
240        self.num_caps = len(caps)
241        self.caps_mask = get_capability_array_name(caps)
242        self.num_exts = len(exts)
243        self.exts = get_extension_array_name(exts)
244        self.operands = [convert_operand_kind(o) for o in operands]
245
246        self.fix_syntax()
247
248        operands = [o[0] for o in operands]
249        self.ref_type_id = 'IdResultType' in operands
250        self.def_result_id = 'IdResult' in operands
251
252        self.version = convert_min_required_version(version)
253        self.lastVersion = convert_max_required_version(lastVersion)
254
255    def fix_syntax(self):
256        """Fix an instruction's syntax, adjusting for differences between the
257        officially released grammar and how SPIRV-Tools uses the grammar.
258
259        Fixes:
260            - ExtInst should not end with SPV_OPERAND_VARIABLE_ID.
261            https://github.com/KhronosGroup/SPIRV-Tools/issues/233
262        """
263        if (self.opname == 'ExtInst'
264                and self.operands[-1] == 'SPV_OPERAND_TYPE_VARIABLE_ID'):
265            self.operands.pop()
266
267    def __str__(self):
268        global OUTPUT_LANGUAGE
269        base_str = 'SpvOp'
270        if OUTPUT_LANGUAGE == 'c++':
271            base_str = 'spv::Op::Op'
272
273        template = ['{{"{opname}"', base_str + '{opname}',
274                    '{num_caps}', '{caps_mask}',
275                    '{num_operands}', '{{{operands}}}',
276                    '{def_result_id}', '{ref_type_id}',
277                    '{num_exts}', '{exts}',
278                    '{min_version}', '{max_version}}}']
279        return ', '.join(template).format(
280            opname=self.opname,
281            num_caps=self.num_caps,
282            caps_mask=self.caps_mask,
283            num_operands=len(self.operands),
284            operands=', '.join(self.operands),
285            def_result_id=(1 if self.def_result_id else 0),
286            ref_type_id=(1 if self.ref_type_id else 0),
287            num_exts=self.num_exts,
288            exts=self.exts,
289            min_version=self.version,
290            max_version=self.lastVersion)
291
292
293class ExtInstInitializer(object):
294    """Instances holds a SPIR-V extended instruction suitable for printing as
295    the initializer for spv_ext_inst_desc_t."""
296
297    def __init__(self, opname, opcode, caps, operands):
298        """Initialization.
299
300        Arguments:
301          - opname: opcode name
302          - opcode: enumerant value for this opcode
303          - caps: a sequence of capability names required by this opcode
304          - operands: a sequence of (operand-kind, operand-quantifier) tuples
305        """
306        self.opname = opname
307        self.opcode = opcode
308        self.num_caps = len(caps)
309        self.caps_mask = get_capability_array_name(caps)
310        self.operands = [convert_operand_kind(o) for o in operands]
311        self.operands.append('SPV_OPERAND_TYPE_NONE')
312
313    def __str__(self):
314        template = ['{{"{opname}"', '{opcode}', '{num_caps}', '{caps_mask}',
315                    '{{{operands}}}}}']
316        return ', '.join(template).format(
317            opname=self.opname,
318            opcode=self.opcode,
319            num_caps=self.num_caps,
320            caps_mask=self.caps_mask,
321            operands=', '.join(self.operands))
322
323
324def generate_instruction(inst, is_ext_inst):
325    """Returns the C initializer for the given SPIR-V instruction.
326
327    Arguments:
328      - inst: a dict containing information about a SPIR-V instruction
329      - is_ext_inst: a bool indicating whether |inst| is an extended
330                     instruction.
331
332    Returns:
333      a string containing the C initializer for spv_opcode_desc_t or
334      spv_ext_inst_desc_t
335    """
336    opname = inst.get('opname')
337    opcode = inst.get('opcode')
338    caps = inst.get('capabilities', [])
339    exts = inst.get('extensions', [])
340    operands = inst.get('operands', {})
341    operands = [(o['kind'], o.get('quantifier', '')) for o in operands]
342    min_version = inst.get('version', None)
343    max_version = inst.get('lastVersion', None)
344
345    assert opname is not None
346
347    if is_ext_inst:
348        return str(ExtInstInitializer(opname, opcode, caps, operands))
349    else:
350        return str(InstInitializer(opname, caps, exts, operands, min_version, max_version))
351
352
353def generate_instruction_table(inst_table):
354    """Returns the info table containing all SPIR-V instructions, sorted by
355    opcode, and prefixed by capability arrays.
356
357    Note:
358      - the built-in sorted() function is guaranteed to be stable.
359        https://docs.python.org/3/library/functions.html#sorted
360
361    Arguments:
362      - inst_table: a list containing all SPIR-V instructions.
363    """
364    inst_table = sorted(inst_table, key=lambda k: (k['opcode'], k['opname']))
365
366    caps_arrays = generate_capability_arrays(
367        [inst.get('capabilities', []) for inst in inst_table])
368    exts_arrays = generate_extension_arrays(
369        [inst.get('extensions', []) for inst in inst_table])
370
371    insts = [generate_instruction(inst, False) for inst in inst_table]
372    insts = ['static const spv_opcode_desc_t kOpcodeTableEntries[] = {{\n'
373             '  {}\n}};'.format(',\n  '.join(insts))]
374
375    return '{}\n\n{}\n\n{}'.format(caps_arrays, exts_arrays, '\n'.join(insts))
376
377
378def generate_extended_instruction_table(json_grammar, set_name, operand_kind_prefix=""):
379    """Returns the info table containing all SPIR-V extended instructions,
380    sorted by opcode, and prefixed by capability arrays.
381
382    Arguments:
383      - inst_table: a list containing all SPIR-V instructions.
384      - set_name: the name of the extended instruction set.
385      - operand_kind_prefix: the prefix, if any, to add to the front
386        of operand kind names.
387    """
388    if operand_kind_prefix:
389        prefix_operand_kind_names(operand_kind_prefix, json_grammar)
390
391    inst_table = json_grammar["instructions"]
392    set_name = set_name.replace(".", "_")
393
394    inst_table = sorted(inst_table, key=lambda k: k['opcode'])
395    caps = [inst.get('capabilities', []) for inst in inst_table]
396    caps_arrays = generate_capability_arrays(caps)
397    insts = [generate_instruction(inst, True) for inst in inst_table]
398    insts = ['static const spv_ext_inst_desc_t {}_entries[] = {{\n'
399             '  {}\n}};'.format(set_name, ',\n  '.join(insts))]
400
401    return '{}\n\n{}'.format(caps_arrays, '\n'.join(insts))
402
403
404class EnumerantInitializer(object):
405    """Prints an enumerant as the initializer for spv_operand_desc_t."""
406
407    def __init__(self, enumerant, value, caps, exts, parameters, version, lastVersion):
408        """Initialization.
409
410        Arguments:
411          - enumerant: enumerant name
412          - value: enumerant value
413          - caps: a sequence of capability names required by this enumerant
414          - exts: a sequence of names of extensions enabling this enumerant
415          - parameters: a sequence of (operand-kind, operand-quantifier) tuples
416          - version: minimal SPIR-V version required for this opcode
417          - lastVersion: last SPIR-V version this opode appears
418        """
419        self.enumerant = enumerant
420        self.value = value
421        self.num_caps = len(caps)
422        self.caps = get_capability_array_name(caps)
423        self.num_exts = len(exts)
424        self.exts = get_extension_array_name(exts)
425        self.parameters = [convert_operand_kind(p) for p in parameters]
426        self.version = convert_min_required_version(version)
427        self.lastVersion = convert_max_required_version(lastVersion)
428
429    def __str__(self):
430        template = ['{{"{enumerant}"', '{value}', '{num_caps}',
431                    '{caps}', '{num_exts}', '{exts}',
432                    '{{{parameters}}}', '{min_version}',
433                    '{max_version}}}']
434        return ', '.join(template).format(
435            enumerant=self.enumerant,
436            value=self.value,
437            num_caps=self.num_caps,
438            caps=self.caps,
439            num_exts=self.num_exts,
440            exts=self.exts,
441            parameters=', '.join(self.parameters),
442            min_version=self.version,
443            max_version=self.lastVersion)
444
445
446def generate_enum_operand_kind_entry(entry, extension_map):
447    """Returns the C initializer for the given operand enum entry.
448
449    Arguments:
450      - entry: a dict containing information about an enum entry
451      - extension_map: a dict mapping enum value to list of extensions
452
453    Returns:
454      a string containing the C initializer for spv_operand_desc_t
455    """
456    enumerant = entry.get('enumerant')
457    value = entry.get('value')
458    caps = entry.get('capabilities', [])
459    if value in extension_map:
460        exts = extension_map[value]
461    else:
462        exts = []
463    params = entry.get('parameters', [])
464    params = [p.get('kind') for p in params]
465    params = zip(params, [''] * len(params))
466    version = entry.get('version', None)
467    max_version = entry.get('lastVersion', None)
468
469    assert enumerant is not None
470    assert value is not None
471
472    return str(EnumerantInitializer(
473        enumerant, value, caps, exts, params, version, max_version))
474
475
476def generate_enum_operand_kind(enum, synthetic_exts_list):
477    """Returns the C definition for the given operand kind.
478    It's a static const named array of spv_operand_desc_t.
479
480    Also appends to |synthetic_exts_list| a list of extension lists
481    used.
482    """
483    kind = enum.get('kind')
484    assert kind is not None
485
486    # Sort all enumerants according to their values, but otherwise
487    # preserve their order so the first name listed in the grammar
488    # as the preferred name for disassembly.
489    if enum.get('category') == 'ValueEnum':
490        def functor(k): return (k['value'])
491    else:
492        def functor(k): return (int(k['value'], 16))
493    entries = sorted(enum.get('enumerants', []), key=functor)
494
495    # SubgroupEqMask and SubgroupEqMaskKHR are the same number with
496    # same semantics, but one has no extension list while the other
497    # does.  Both should have the extension list.
498    # So create a mapping from enum value to the union of the extensions
499    # across all those grammar entries.  Preserve order.
500    extension_map = {}
501    for e in entries:
502        value = e.get('value')
503        extension_map[value] = []
504    for e in entries:
505        value = e.get('value')
506        exts = e.get('extensions', [])
507        for ext in exts:
508            if ext not in extension_map[value]:
509                extension_map[value].append(ext)
510    synthetic_exts_list.extend(extension_map.values())
511
512    name = '{}_{}Entries'.format(PYGEN_VARIABLE_PREFIX, kind)
513    entries = ['  {}'.format(generate_enum_operand_kind_entry(e, extension_map))
514               for e in entries]
515
516    template = ['static const spv_operand_desc_t {name}[] = {{',
517                '{entries}', '}};']
518    entries = '\n'.join(template).format(
519        name=name,
520        entries=',\n'.join(entries))
521
522    return kind, name, entries
523
524
525def generate_operand_kind_table(enums):
526    """Returns the info table containing all SPIR-V operand kinds."""
527    # We only need to output info tables for those operand kinds that are enums.
528    enums = [e for e in enums if e.get('category') in ['ValueEnum', 'BitEnum']]
529
530    caps = [entry.get('capabilities', [])
531            for enum in enums
532            for entry in enum.get('enumerants', [])]
533    caps_arrays = generate_capability_arrays(caps)
534
535    exts = [entry.get('extensions', [])
536            for enum in enums
537            for entry in enum.get('enumerants', [])]
538    enums = [generate_enum_operand_kind(e, exts) for e in enums]
539    exts_arrays = generate_extension_arrays(exts)
540
541    # We have a few operand kinds that require their optional counterpart to
542    # exist in the operand info table.
543    optional_enums = ['ImageOperands', 'AccessQualifier', 'MemoryAccess', 'PackedVectorFormat', 'CooperativeMatrixOperands']
544    optional_enums = [e for e in enums if e[0] in optional_enums]
545    enums.extend(optional_enums)
546
547    enum_kinds, enum_names, enum_entries = zip(*enums)
548    # Mark the last few as optional ones.
549    enum_quantifiers = [''] * (len(enums) - len(optional_enums)) + ['?'] * len(optional_enums)
550    # And we don't want redefinition of them.
551    enum_entries = enum_entries[:-len(optional_enums)]
552    enum_kinds = [convert_operand_kind(e)
553                  for e in zip(enum_kinds, enum_quantifiers)]
554    table_entries = zip(enum_kinds, enum_names, enum_names)
555    table_entries = ['  {{{}, ARRAY_SIZE({}), {}}}'.format(*e)
556                     for e in table_entries]
557
558    template = [
559        'static const spv_operand_desc_group_t {p}_OperandInfoTable[] = {{',
560        '{enums}', '}};']
561    table = '\n'.join(template).format(
562        p=PYGEN_VARIABLE_PREFIX, enums=',\n'.join(table_entries))
563
564    return '\n\n'.join((caps_arrays,) + (exts_arrays,) + enum_entries + (table,))
565
566
567def get_extension_list(instructions, operand_kinds):
568    """Returns extensions as an alphabetically sorted list of strings."""
569
570    things_with_an_extensions_field = [item for item in instructions]
571
572    enumerants = sum([item.get('enumerants', [])
573                      for item in operand_kinds], [])
574
575    things_with_an_extensions_field.extend(enumerants)
576
577    extensions = sum([item.get('extensions', [])
578                      for item in things_with_an_extensions_field
579                      if item.get('extensions')], [])
580
581    for item in EXTENSIONS_FROM_SPIRV_REGISTRY_AND_NOT_FROM_GRAMMARS.split():
582            # If it's already listed in a grammar, then don't put it in the
583            # special exceptions list.
584        assert item not in extensions, 'Extension %s is already in a grammar file' % item
585
586    extensions.extend(
587        EXTENSIONS_FROM_SPIRV_REGISTRY_AND_NOT_FROM_GRAMMARS.split())
588
589    # Validator would ignore type declaration unique check. Should only be used
590    # for legacy autogenerated test files containing multiple instances of the
591    # same type declaration, if fixing the test by other methods is too
592    # difficult. Shouldn't be used for any other reasons.
593    extensions.append('SPV_VALIDATOR_ignore_type_decl_unique')
594
595    return sorted(set(extensions))
596
597
598def get_capabilities(operand_kinds):
599    """Returns capabilities as a list of JSON objects, in order of
600    appearance."""
601    enumerants = sum([item.get('enumerants', []) for item in operand_kinds
602                      if item.get('kind') in ['Capability']], [])
603    return enumerants
604
605
606def generate_extension_enum(extensions):
607    """Returns enumeration containing extensions declared in the grammar."""
608    return ',\n'.join(['k' + extension for extension in extensions])
609
610
611def generate_extension_to_string_mapping(extensions):
612    """Returns mapping function from extensions to corresponding strings."""
613    function = 'const char* ExtensionToString(Extension extension) {\n'
614    function += '  switch (extension) {\n'
615    template = '    case Extension::k{extension}:\n' \
616        '      return "{extension}";\n'
617    function += ''.join([template.format(extension=extension)
618                         for extension in extensions])
619    function += '  }\n\n  return "";\n}'
620    return function
621
622
623def generate_string_to_extension_mapping(extensions):
624    """Returns mapping function from strings to corresponding extensions."""
625
626    function = '''
627    bool GetExtensionFromString(const char* str, Extension* extension) {{
628        static const char* known_ext_strs[] = {{ {strs} }};
629        static const Extension known_ext_ids[] = {{ {ids} }};
630        const auto b = std::begin(known_ext_strs);
631        const auto e = std::end(known_ext_strs);
632        const auto found = std::equal_range(
633            b, e, str, [](const char* str1, const char* str2) {{
634                return std::strcmp(str1, str2) < 0;
635            }});
636        if (found.first == e || found.first == found.second) return false;
637
638        *extension = known_ext_ids[found.first - b];
639        return true;
640    }}
641    '''.format(strs=', '.join(['"{}"'.format(e) for e in extensions]),
642               ids=', '.join(['Extension::k{}'.format(e) for e in extensions]))
643
644    return function
645
646
647def generate_capability_to_string_mapping(operand_kinds):
648    """Returns mapping function from capabilities to corresponding strings.
649
650    We take care to avoid emitting duplicate values.
651    """
652    cap_str = 'SpvCapability'
653    cap_join = ''
654    global OUTPUT_LANGUAGE
655    if OUTPUT_LANGUAGE == 'c++':
656        cap_str = 'spv::Capability'
657        cap_join = '::'
658
659    function = 'const char* CapabilityToString(' + cap_str + ' capability) {\n'
660    function += '  switch (capability) {\n'
661    template = '    case ' + cap_str + cap_join + '{capability}:\n' \
662        '      return "{capability}";\n'
663    emitted = set()  # The values of capabilities we already have emitted
664    for capability in get_capabilities(operand_kinds):
665        value = capability.get('value')
666        if value not in emitted:
667            emitted.add(value)
668            function += template.format(capability=capability.get('enumerant'))
669    function += '    case ' + cap_str + cap_join + 'Max:\n' \
670        '      assert(0 && "Attempting to convert ' + cap_str + cap_join + 'Max to string");\n' \
671        '      return "";\n'
672    function += '  }\n\n  return "";\n}'
673    return function
674
675
676def generate_all_string_enum_mappings(extensions, operand_kinds):
677    """Returns all string-to-enum / enum-to-string mapping tables."""
678    tables = []
679    tables.append(generate_extension_to_string_mapping(extensions))
680    tables.append(generate_string_to_extension_mapping(extensions))
681    tables.append(generate_capability_to_string_mapping(operand_kinds))
682    return '\n\n'.join(tables)
683
684
685def precondition_operand_kinds(operand_kinds):
686    """For operand kinds that have the same number, make sure they all have the
687    same extension list."""
688
689    # Map operand kind and value to list of the union of extensions
690    # for same-valued enumerants.
691    exts = {}
692    for kind_entry in operand_kinds:
693        kind = kind_entry.get('kind')
694        for enum_entry in kind_entry.get('enumerants', []):
695            value = enum_entry.get('value')
696            key = kind + '.' + str(value)
697            if key in exts:
698                exts[key].extend(enum_entry.get('extensions', []))
699            else:
700                exts[key] = enum_entry.get('extensions', [])
701            exts[key] = sorted(set(exts[key]))
702
703    # Now make each entry the same list.
704    for kind_entry in operand_kinds:
705        kind = kind_entry.get('kind')
706        for enum_entry in kind_entry.get('enumerants', []):
707            value = enum_entry.get('value')
708            key = kind + '.' + str(value)
709            if len(exts[key]) > 0:
710                enum_entry['extensions'] = exts[key]
711
712    return operand_kinds
713
714
715def prefix_operand_kind_names(prefix, json_dict):
716    """Modifies json_dict, by prefixing all the operand kind names
717    with the given prefix.  Also modifies their uses in the instructions
718    to match.
719    """
720
721    old_to_new = {}
722    for operand_kind in json_dict["operand_kinds"]:
723        old_name = operand_kind["kind"]
724        new_name = prefix + old_name
725        operand_kind["kind"] = new_name
726        old_to_new[old_name] = new_name
727
728    for instruction in json_dict["instructions"]:
729        for operand in instruction.get("operands", []):
730            replacement = old_to_new.get(operand["kind"])
731            if replacement is not None:
732                operand["kind"] = replacement
733
734
735def main():
736    import argparse
737    parser = argparse.ArgumentParser(description='Generate SPIR-V info tables')
738
739    parser.add_argument('--spirv-core-grammar', metavar='<path>',
740                        type=str, required=False,
741                        help='input JSON grammar file for core SPIR-V '
742                        'instructions')
743    parser.add_argument('--extinst-debuginfo-grammar', metavar='<path>',
744                        type=str, required=False, default=None,
745                        help='input JSON grammar file for DebugInfo extended '
746                        'instruction set')
747    parser.add_argument('--extinst-cldebuginfo100-grammar', metavar='<path>',
748                        type=str, required=False, default=None,
749                        help='input JSON grammar file for OpenCL.DebugInfo.100 '
750                        'extended instruction set')
751    parser.add_argument('--extinst-glsl-grammar', metavar='<path>',
752                        type=str, required=False, default=None,
753                        help='input JSON grammar file for GLSL extended '
754                        'instruction set')
755    parser.add_argument('--extinst-opencl-grammar', metavar='<path>',
756                        type=str, required=False, default=None,
757                        help='input JSON grammar file for OpenCL extended '
758                        'instruction set')
759    parser.add_argument('--output-language',
760                        type=str, required=False, default='c',
761                        choices=['c','c++'],
762                        help='specify output language type')
763
764    parser.add_argument('--core-insts-output', metavar='<path>',
765                        type=str, required=False, default=None,
766                        help='output file for core SPIR-V instructions')
767    parser.add_argument('--glsl-insts-output', metavar='<path>',
768                        type=str, required=False, default=None,
769                        help='output file for GLSL extended instruction set')
770    parser.add_argument('--opencl-insts-output', metavar='<path>',
771                        type=str, required=False, default=None,
772                        help='output file for OpenCL extended instruction set')
773    parser.add_argument('--operand-kinds-output', metavar='<path>',
774                        type=str, required=False, default=None,
775                        help='output file for operand kinds')
776    parser.add_argument('--extension-enum-output', metavar='<path>',
777                        type=str, required=False, default=None,
778                        help='output file for extension enumeration')
779    parser.add_argument('--enum-string-mapping-output', metavar='<path>',
780                        type=str, required=False, default=None,
781                        help='output file for enum-string mappings')
782    parser.add_argument('--extinst-vendor-grammar', metavar='<path>',
783                        type=str, required=False, default=None,
784                        help='input JSON grammar file for vendor extended '
785                        'instruction set'),
786    parser.add_argument('--vendor-insts-output', metavar='<path>',
787                        type=str, required=False, default=None,
788                        help='output file for vendor extended instruction set')
789    parser.add_argument('--vendor-operand-kind-prefix', metavar='<string>',
790                        type=str, required=False, default=None,
791                        help='prefix for operand kinds (to disambiguate operand type enums)')
792    args = parser.parse_args()
793
794    global OUTPUT_LANGUAGE
795    OUTPUT_LANGUAGE = args.output_language
796
797    # The GN build system needs this because it doesn't handle quoting
798    # empty string arguments well.
799    if args.vendor_operand_kind_prefix == "...nil...":
800        args.vendor_operand_kind_prefix = ""
801
802    if (args.core_insts_output is None) != \
803            (args.operand_kinds_output is None):
804        print('error: --core-insts-output and --operand-kinds-output '
805              'should be specified together.')
806        exit(1)
807    if args.operand_kinds_output and not (args.spirv_core_grammar and
808         args.extinst_debuginfo_grammar and
809         args.extinst_cldebuginfo100_grammar):
810        print('error: --operand-kinds-output requires --spirv-core-grammar '
811              'and --extinst-debuginfo-grammar '
812              'and --extinst-cldebuginfo100-grammar')
813        exit(1)
814    if (args.glsl_insts_output is None) != \
815            (args.extinst_glsl_grammar is None):
816        print('error: --glsl-insts-output and --extinst-glsl-grammar '
817              'should be specified together.')
818        exit(1)
819    if (args.opencl_insts_output is None) != \
820            (args.extinst_opencl_grammar is None):
821        print('error: --opencl-insts-output and --extinst-opencl-grammar '
822              'should be specified together.')
823        exit(1)
824    if (args.vendor_insts_output is None) != \
825            (args.extinst_vendor_grammar is None):
826        print('error: --vendor-insts-output and '
827              '--extinst-vendor-grammar should be specified together.')
828        exit(1)
829    if all([args.core_insts_output is None,
830            args.glsl_insts_output is None,
831            args.opencl_insts_output is None,
832            args.vendor_insts_output is None,
833            args.extension_enum_output is None,
834            args.enum_string_mapping_output is None]):
835        print('error: at least one output should be specified.')
836        exit(1)
837
838    if args.spirv_core_grammar is not None:
839        with open(args.spirv_core_grammar) as json_file:
840            core_grammar = json.loads(json_file.read())
841            with open(args.extinst_debuginfo_grammar) as debuginfo_json_file:
842                debuginfo_grammar = json.loads(debuginfo_json_file.read())
843                with open(args.extinst_cldebuginfo100_grammar) as cldebuginfo100_json_file:
844                    cldebuginfo100_grammar = json.loads(cldebuginfo100_json_file.read())
845                    prefix_operand_kind_names("CLDEBUG100_", cldebuginfo100_grammar)
846                    instructions = []
847                    instructions.extend(core_grammar['instructions'])
848                    instructions.extend(debuginfo_grammar['instructions'])
849                    instructions.extend(cldebuginfo100_grammar['instructions'])
850                    operand_kinds = []
851                    operand_kinds.extend(core_grammar['operand_kinds'])
852                    operand_kinds.extend(debuginfo_grammar['operand_kinds'])
853                    operand_kinds.extend(cldebuginfo100_grammar['operand_kinds'])
854                    extensions = get_extension_list(instructions, operand_kinds)
855                    operand_kinds = precondition_operand_kinds(operand_kinds)
856        if args.core_insts_output is not None:
857            make_path_to_file(args.core_insts_output)
858            make_path_to_file(args.operand_kinds_output)
859            with open(args.core_insts_output, 'w') as f:
860                f.write(generate_instruction_table(
861                    core_grammar['instructions']))
862            with open(args.operand_kinds_output, 'w') as f:
863                f.write(generate_operand_kind_table(operand_kinds))
864        if args.extension_enum_output is not None:
865            make_path_to_file(args.extension_enum_output)
866            with open(args.extension_enum_output, 'w') as f:
867                f.write(generate_extension_enum(extensions))
868        if args.enum_string_mapping_output is not None:
869            make_path_to_file(args.enum_string_mapping_output)
870            with open(args.enum_string_mapping_output, 'w') as f:
871                f.write(generate_all_string_enum_mappings(
872                    extensions, operand_kinds))
873
874    if args.extinst_glsl_grammar is not None:
875        with open(args.extinst_glsl_grammar) as json_file:
876            grammar = json.loads(json_file.read())
877            make_path_to_file(args.glsl_insts_output)
878            with open(args.glsl_insts_output, 'w') as f:
879                f.write(generate_extended_instruction_table(
880                    grammar, 'glsl'))
881
882    if args.extinst_opencl_grammar is not None:
883        with open(args.extinst_opencl_grammar) as json_file:
884            grammar = json.loads(json_file.read())
885            make_path_to_file(args.opencl_insts_output)
886            with open(args.opencl_insts_output, 'w') as f:
887                f.write(generate_extended_instruction_table(
888                    grammar, 'opencl'))
889
890    if args.extinst_vendor_grammar is not None:
891        with open(args.extinst_vendor_grammar) as json_file:
892            grammar = json.loads(json_file.read())
893            make_path_to_file(args.vendor_insts_output)
894            name = args.extinst_vendor_grammar
895            start = name.find('extinst.') + len('extinst.')
896            name = name[start:-len('.grammar.json')].replace('-', '_')
897            with open(args.vendor_insts_output, 'w') as f:
898                f.write(generate_extended_instruction_table(
899                    grammar, name, args.vendor_operand_kind_prefix))
900
901
902if __name__ == '__main__':
903    main()
904