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