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