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