| #!/usr/bin/python3 -i |
| # |
| # Copyright (c) 2021-2022 The Khronos Group Inc. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| # Author: Spencer Fricke <s.fricke@samsung.com> |
| |
| import os,re,sys,string,json |
| import xml.etree.ElementTree as etree |
| from generator import * |
| from collections import namedtuple |
| from common_codegen import * |
| |
| # This is a workaround to use a Python 2.7 and 3.x compatible syntax |
| from io import open |
| |
| class SpirvGrammarHelperOutputGeneratorOptions(GeneratorOptions): |
| def __init__(self, |
| conventions = None, |
| filename = None, |
| directory = '.', |
| genpath = None, |
| apiname = 'vulkan', |
| profile = None, |
| versions = '.*', |
| emitversions = '.*', |
| defaultExtensions = 'vulkan', |
| addExtensions = None, |
| removeExtensions = None, |
| emitExtensions = None, |
| emitSpirv = None, |
| sortProcedure = regSortFeatures, |
| genFuncPointers = True, |
| protectFile = True, |
| protectFeature = True, |
| apicall = 'VKAPI_ATTR ', |
| apientry = 'VKAPI_CALL ', |
| apientryp = 'VKAPI_PTR *', |
| indentFuncProto = True, |
| indentFuncPointer = False, |
| alignFuncParam = 48, |
| expandEnumerants = False, |
| grammar = None): |
| GeneratorOptions.__init__(self, |
| conventions = conventions, |
| filename = filename, |
| directory = directory, |
| genpath = genpath, |
| apiname = apiname, |
| profile = profile, |
| versions = versions, |
| emitversions = emitversions, |
| defaultExtensions = defaultExtensions, |
| addExtensions = addExtensions, |
| removeExtensions = removeExtensions, |
| emitExtensions = emitExtensions, |
| emitSpirv = emitSpirv, |
| sortProcedure = sortProcedure) |
| self.genFuncPointers = genFuncPointers |
| self.protectFile = protectFile |
| self.protectFeature = protectFeature |
| self.apicall = apicall |
| self.apientry = apientry |
| self.apientryp = apientryp |
| self.indentFuncProto = indentFuncProto |
| self.indentFuncPointer = indentFuncPointer |
| self.alignFuncParam = alignFuncParam |
| self.expandEnumerants = expandEnumerants |
| self.grammar = grammar |
| # |
| # SpirvGrammarHelperOutputGenerator - Generate SPIR-V grammar helper |
| # for SPIR-V opcodes, enums, etc |
| class SpirvGrammarHelperOutputGenerator(OutputGenerator): |
| def __init__(self, |
| errFile = sys.stderr, |
| warnFile = sys.stderr, |
| diagFile = sys.stdout): |
| OutputGenerator.__init__(self, errFile, warnFile, diagFile) |
| self.headerFile = False # Header file generation flag |
| self.sourceFile = False # Source file generation flag |
| |
| self.opcodes = dict() |
| self.atomicsOps = [] |
| self.groupOps = [] |
| self.imageGatherOps = [] |
| self.imageSampleOps = [] |
| self.imageFetchOps = [] |
| # Need range to be large as largest possible operand index |
| self.imageOperandsParamCount = [[] for i in range(3)] |
| |
| # Lots of switch statements share same ending |
| self.commonBoolSwitch = ''' found = true; |
| break; |
| default: |
| break; |
| } |
| return found; |
| }\n |
| ''' |
| |
| def commonParamSwitch(self, variableName): |
| return ''' break; |
| default: |
| break; |
| }} |
| return {}; |
| }}\n |
| '''.format(variableName) |
| |
| # |
| # Called at beginning of processing as file is opened |
| def beginFile(self, genOpts): |
| OutputGenerator.beginFile(self, genOpts) |
| self.parseGrammar(genOpts.grammar) |
| |
| self.headerFile = (genOpts.filename == 'spirv_grammar_helper.h') |
| self.sourceFile = (genOpts.filename == 'spirv_grammar_helper.cpp') |
| if not self.headerFile and not self.sourceFile: |
| print("Error: Output Filenames have changed, update generator source.\n") |
| sys.exit(1) |
| |
| # File Comment |
| file_comment = '// *** THIS FILE IS GENERATED - DO NOT EDIT ***\n' |
| file_comment += '// See spirv_gramar_generator.py for modifications\n' |
| write(file_comment, file=self.outFile) |
| # Copyright Statement |
| copyright = '' |
| copyright += '\n' |
| copyright += '/***************************************************************************\n' |
| copyright += ' *\n' |
| copyright += ' * Copyright (c) 2021-2022 The Khronos Group Inc.\n' |
| copyright += ' *\n' |
| copyright += ' * Licensed under the Apache License, Version 2.0 (the "License");\n' |
| copyright += ' * you may not use this file except in compliance with the License.\n' |
| copyright += ' * You may obtain a copy of the License at\n' |
| copyright += ' *\n' |
| copyright += ' * http://www.apache.org/licenses/LICENSE-2.0\n' |
| copyright += ' *\n' |
| copyright += ' * Unless required by applicable law or agreed to in writing, software\n' |
| copyright += ' * distributed under the License is distributed on an "AS IS" BASIS,\n' |
| copyright += ' * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n' |
| copyright += ' * See the License for the specific language governing permissions and\n' |
| copyright += ' * limitations under the License.\n' |
| copyright += ' *\n' |
| copyright += ' * Author: Spencer Fricke <s.fricke@samsung.com>\n' |
| copyright += ' *\n' |
| copyright += ' * This file is related to anything that is found in the SPIR-V grammar\n' |
| copyright += ' * file found in the SPIRV-Headers. Mainly used for SPIR-V util functions.\n' |
| copyright += ' *\n' |
| copyright += ' ****************************************************************************/\n' |
| write(copyright, file=self.outFile) |
| |
| if self.sourceFile: |
| write('#include "vk_layer_data.h"', file=self.outFile) |
| write('#include "spirv_grammar_helper.h"', file=self.outFile) |
| elif self.headerFile: |
| write('#pragma once', file=self.outFile) |
| write('#include <cstdint>', file=self.outFile) |
| write('#include <spirv/unified1/spirv.hpp>', file=self.outFile) |
| write('', file=self.outFile) |
| # |
| # Write generated file content to output file |
| def endFile(self): |
| write(self.instructionTable(), file=self.outFile) |
| write(self.atomicOperation(), file=self.outFile) |
| write(self.groupOperation(), file=self.outFile) |
| write(self.imageOperation(), file=self.outFile) |
| write(self.parameterHelper(), file=self.outFile) |
| write(self.stringHelper(), file=self.outFile) |
| # Finish processing in superclass |
| OutputGenerator.endFile(self) |
| # |
| # Takes the SPIR-V Grammar JSON and parses it |
| # Emulates the gen*() functions the vk.xml calls |
| # |
| # In the future, IF more then this generator wants to use the grammar |
| # it would be better to move the file opening to lvl_genvk.py |
| def parseGrammar(self, grammar): |
| with open(grammar, 'r') as jsonFile: |
| data = json.load(jsonFile) |
| instructions = data['instructions'] |
| operandKinds = data['operand_kinds'] |
| |
| # Build list from json of all capabilities that are only for kernel |
| # This needs to be done before loop instructions |
| kernelCapability = ['Kernel'] |
| # some SPV_INTEL_* are not allowed in Vulkan and are just adding unused opcodes |
| # TODO bring in vk.xml to cross check valid extensions/capabilities instead of starting another hardcoded list |
| kernelCapability.append('ArbitraryPrecisionIntegersINTEL') |
| kernelCapability.append('ArbitraryPrecisionFixedPointINTEL') |
| kernelCapability.append('ArbitraryPrecisionFloatingPointINTEL') |
| kernelCapability.append('SubgroupAvcMotionEstimationINTEL') |
| kernelCapability.append('SubgroupAvcMotionEstimationIntraINTEL') |
| kernelCapability.append('SubgroupAvcMotionEstimationChromaINTEL') |
| |
| for operandKind in operandKinds: |
| if operandKind['kind'] == 'Capability': |
| for enum in operandKind['enumerants']: |
| if 'capabilities' in enum and len(enum['capabilities']) == 1 and enum['capabilities'][0] == 'Kernel': |
| kernelCapability.append(enum['enumerant']) |
| if operandKind['kind'] == 'ImageOperands': |
| values = [] # prevent alias from being duplicatd |
| for enum in operandKind['enumerants']: |
| count = 0 if 'parameters' not in enum else len(enum['parameters']) |
| if enum['value'] not in values: |
| self.imageOperandsParamCount[count].append(enum['enumerant']) |
| values.append(enum['value']) |
| |
| for instruction in instructions: |
| opname = instruction['opname'] |
| if 'capabilities' in instruction: |
| notSupported = True |
| for capability in instruction['capabilities']: |
| if capability not in kernelCapability: |
| notSupported = False |
| break |
| if notSupported: |
| continue # If just 'Kernel' capabilites then it's ment for OpenCL and skip instruction |
| |
| # Nice side effect of using a dict here is alias opcodes will be last in the grammar file |
| # ex: OpTypeAccelerationStructureNV will be replaced by OpTypeAccelerationStructureKHR |
| self.opcodes[instruction['opcode']] = { |
| 'name' : opname, |
| 'hasType' : "false", |
| 'hasResult' : "false", |
| 'memoryScopePosition' : 0, |
| 'executionScopePosition' : 0, |
| 'imageOperandsPosition' : 0, |
| } |
| |
| if instruction['class'] == 'Atomic': |
| self.atomicsOps.append(opname) |
| if instruction['class'] == 'Non-Uniform': |
| self.groupOps.append(opname) |
| if re.search("OpImage.*Gather", opname) is not None: |
| self.imageGatherOps.append(opname) |
| if re.search("OpImageFetch.*", opname) is not None: |
| self.imageFetchOps.append(opname) |
| if re.search("OpImageSample.*", opname) is not None: |
| self.imageSampleOps.append(opname) |
| if 'operands' in instruction: |
| for index, operand in enumerate(instruction['operands']): |
| if operand['kind'] == 'IdResultType': |
| self.opcodes[instruction['opcode']]['hasType'] = "true" |
| if operand['kind'] == 'IdResult': |
| self.opcodes[instruction['opcode']]['hasResult'] = "true" |
| # some instructions have both types of IdScope |
| # OpReadClockKHR has the wrong 'name' as 'Scope' |
| if operand['kind'] == 'IdScope': |
| if operand['name'] == '\'Execution\'' or operand['name'] == '\'Scope\'': |
| self.opcodes[instruction['opcode']]['executionScopePosition'] = index + 1 |
| elif operand['name'] == '\'Memory\'': |
| self.opcodes[instruction['opcode']]['memoryScopePosition'] = index + 1 |
| else: |
| print("Error: unknown operand {} not handled correctly\n".format(opname)) |
| sys.exit(1) |
| if operand['kind'] == 'ImageOperands': |
| self.opcodes[instruction['opcode']]['imageOperandsPosition'] = index + 1 |
| |
| # |
| # Generate table for each opcode instruction |
| def instructionTable(self): |
| output = '' |
| if self.sourceFile: |
| output += '// All information related to each SPIR-V opcode instruction\n' |
| output += 'struct InstructionInfo {\n' |
| output += ' const char* name;\n' |
| output += ' bool has_type; // always operand 0 if present\n' |
| output += ' bool has_result; // always operand 1 if present\n' |
| output += ' uint32_t memory_scope_position; // operand ID position or zero if not present\n' |
| output += ' uint32_t execution_scope_position; // operand ID position or zero if not present\n' |
| output += ' uint32_t image_operands_position; // operand ID position or zero if not present\n' |
| output += '};\n' |
| output += '\n' |
| output += '// Static table to replace having many large switch statement functions for looking up each part\n' |
| output += '// of a given SPIR-V opcode instruction\n' |
| output += '//\n' |
| output += '// clang-format off\n' |
| output += 'static const layer_data::unordered_map<uint32_t, InstructionInfo> kInstructionTable {\n' |
| for opcode, info in sorted(self.opcodes.items()): |
| output += f' {{spv::{info["name"]}, {{"{info["name"]}", {info["hasType"]}, {info["hasResult"]}, {info["memoryScopePosition"]}, {info["executionScopePosition"]}, {info["imageOperandsPosition"]}}}}},\n' |
| output += '};\n' |
| output += '// clang-format on\n' |
| return output; |
| # |
| # Generate functions for numeric based functions |
| def atomicOperation(self): |
| output = '' |
| if self.headerFile: |
| output += 'bool AtomicOperation(uint32_t opcode);\n' |
| elif self.sourceFile: |
| output += '// Any non supported operation will be covered with VUID 01090\n' |
| output += 'bool AtomicOperation(uint32_t opcode) {\n' |
| output += ' bool found = false;\n' |
| output += ' switch (opcode) {\n' |
| for f in self.atomicsOps: |
| output += ' case spv::{}:\n'.format(f) |
| output += self.commonBoolSwitch |
| |
| return output; |
| # |
| # Generate functions for numeric based functions |
| def groupOperation(self): |
| output = '' |
| if self.headerFile: |
| output += 'bool GroupOperation(uint32_t opcode);\n' |
| elif self.sourceFile: |
| output += '// Any non supported operation will be covered with VUID 01090\n' |
| output += 'bool GroupOperation(uint32_t opcode) {\n' |
| output += ' bool found = false;\n' |
| output += ' switch (opcode) {\n' |
| for f in self.groupOps: |
| output += ' case spv::{}:\n'.format(f) |
| output += self.commonBoolSwitch |
| |
| return output; |
| # |
| # Generate functions for image operations |
| def imageOperation(self): |
| output = '' |
| if self.headerFile: |
| output += 'bool ImageGatherOperation(uint32_t opcode);\n' |
| output += 'bool ImageFetchOperation(uint32_t opcode);\n' |
| output += 'bool ImageSampleOperation(uint32_t opcode);\n' |
| elif self.sourceFile: |
| output += 'bool ImageGatherOperation(uint32_t opcode) {\n' |
| output += ' bool found = false;\n' |
| output += ' switch (opcode) {\n' |
| for f in self.imageGatherOps: |
| output += ' case spv::{}:\n'.format(f) |
| output += self.commonBoolSwitch |
| output += '\n' |
| |
| output += 'bool ImageFetchOperation(uint32_t opcode) {\n' |
| output += ' bool found = false;\n' |
| output += ' switch (opcode) {\n' |
| for f in self.imageFetchOps: |
| output += ' case spv::{}:\n'.format(f) |
| output += self.commonBoolSwitch |
| output += '\n' |
| |
| output += 'bool ImageSampleOperation(uint32_t opcode) {\n' |
| output += ' bool found = false;\n' |
| output += ' switch (opcode) {\n' |
| for f in self.imageSampleOps: |
| output += ' case spv::{}:\n'.format(f) |
| output += self.commonBoolSwitch |
| |
| return output; |
| # |
| # Generate functions for operand parameter switch cases |
| def parameterHelper(self): |
| output = '' |
| if self.headerFile: |
| output += 'bool OpcodeHasType(uint32_t opcode);\n' |
| output += 'bool OpcodeHasResult(uint32_t opcode);\n' |
| output += 'uint32_t OpcodeResultWord(uint32_t opcode);\n' |
| output += '\n' |
| output += 'uint32_t OpcodeMemoryScopePosition(uint32_t opcode);\n' |
| output += 'uint32_t OpcodeExecutionScopePosition(uint32_t opcode);\n' |
| output += 'uint32_t OpcodeImageOperandsPosition(uint32_t opcode);\n' |
| output += '\n' |
| output += 'uint32_t ImageOperandsParamCount(uint32_t opcode);\n' |
| elif self.sourceFile: |
| output += 'bool OpcodeHasType(uint32_t opcode) {\n' |
| output += ' bool has_type = false;\n' |
| output += ' auto format_info = kInstructionTable.find(opcode);\n' |
| output += ' if (format_info != kInstructionTable.end()) {\n' |
| output += ' has_type = format_info->second.has_type;\n' |
| output += ' }\n' |
| output += ' return has_type;\n' |
| output += '}\n\n' |
| |
| output += 'bool OpcodeHasResult(uint32_t opcode) {\n' |
| output += ' bool has_result = false;\n' |
| output += ' auto format_info = kInstructionTable.find(opcode);\n' |
| output += ' if (format_info != kInstructionTable.end()) {\n' |
| output += ' has_result = format_info->second.has_result;\n' |
| output += ' }\n' |
| output += ' return has_result;\n' |
| output += '}\n\n' |
| |
| output += '// Helper to get the word position of the result operand.\n' |
| output += '// Will either be 1 or 2 if it has a result.\n' |
| output += '// Will be 0 if there is no result.\n' |
| output += 'uint32_t OpcodeResultWord(uint32_t opcode) {\n' |
| output += ' uint32_t position = 0;\n' |
| output += ' if (OpcodeHasResult(opcode)) {\n' |
| output += ' position = 1;\n' |
| output += ' position += OpcodeHasType(opcode) ? 1 : 0;\n' |
| output += ' }\n' |
| output += ' return position;\n' |
| output += '}\n\n' |
| |
| output += '// Return operand position of Memory Scope <ID> or zero if there is none\n' |
| output += 'uint32_t OpcodeMemoryScopePosition(uint32_t opcode) {\n' |
| output += ' uint32_t position = 0;\n' |
| output += ' auto format_info = kInstructionTable.find(opcode);\n' |
| output += ' if (format_info != kInstructionTable.end()) {\n' |
| output += ' position = format_info->second.memory_scope_position;\n' |
| output += ' }\n' |
| output += ' return position;\n' |
| output += '}\n\n' |
| |
| output += '// Return operand position of Execution Scope <ID> or zero if there is none\n' |
| output += 'uint32_t OpcodeExecutionScopePosition(uint32_t opcode) {\n' |
| output += ' uint32_t position = 0;\n' |
| output += ' auto format_info = kInstructionTable.find(opcode);\n' |
| output += ' if (format_info != kInstructionTable.end()) {\n' |
| output += ' position = format_info->second.execution_scope_position;\n' |
| output += ' }\n' |
| output += ' return position;\n' |
| output += '}\n\n' |
| |
| output += '// Return operand position of Image Operands <ID> or zero if there is none\n' |
| output += 'uint32_t OpcodeImageOperandsPosition(uint32_t opcode) {\n' |
| output += ' uint32_t position = 0;\n' |
| output += ' auto format_info = kInstructionTable.find(opcode);\n' |
| output += ' if (format_info != kInstructionTable.end()) {\n' |
| output += ' position = format_info->second.image_operands_position;\n' |
| output += ' }\n' |
| output += ' return position;\n' |
| output += '}\n\n' |
| |
| output += '// Return number of optional parameter from ImageOperands\n' |
| output += 'uint32_t ImageOperandsParamCount(uint32_t image_operand) {\n' |
| output += ' uint32_t count = 0;\n' |
| output += ' switch (image_operand) {\n' |
| for index, operands in enumerate(self.imageOperandsParamCount): |
| for operand in operands: |
| if operand == 'None': # not sure why header is not consistent with this |
| output += ' case spv::ImageOperandsMask{}:\n'.format(operand) |
| else: |
| output += ' case spv::ImageOperands{}Mask:\n'.format(operand) |
| if len(operands) != 0: |
| output += ' return {};\n'.format(index) |
| output += self.commonParamSwitch('count') |
| |
| return output; |
| # |
| # Generate functions for getting strings to give better error messages |
| def stringHelper(self): |
| output = '' |
| if self.headerFile: |
| output = 'const char* string_SpvOpcode(uint32_t opcode);\n' |
| elif self.sourceFile: |
| output = 'const char* string_SpvOpcode(uint32_t opcode) {\n' |
| output += ' auto format_info = kInstructionTable.find(opcode);\n' |
| output += ' if (format_info != kInstructionTable.end()) {\n' |
| output += ' return format_info->second.name;\n' |
| output += ' } else {\n' |
| output += ' return \"Unhandled Opcode\";\n' |
| output += ' }\n' |
| output += '};' |
| return output |