diff --git a/tools/spirv/CMakeLists.txt b/tools/spirv/CMakeLists.txt index af51f86..5775510 100644 --- a/tools/spirv/CMakeLists.txt +++ b/tools/spirv/CMakeLists.txt @@ -12,6 +12,7 @@ include_directories(../..) set(SOURCES main.cpp + assembler_table.cpp disassemble.cpp header.cpp doc.cpp diff --git a/tools/spirv/OclDoc.cpp b/tools/spirv/OclDoc.cpp index 1f25221..ff2c7c6 100644 --- a/tools/spirv/OclDoc.cpp +++ b/tools/spirv/OclDoc.cpp @@ -28,11 +28,14 @@ #include "headers/spirv.hpp" #include "headers/OpenCL.std.h" -#include "doc.h" #include "OclDoc.h" +#include "assembler_table.h" +#include "doc.h" + #include #include +#include #include #include @@ -2187,4 +2190,31 @@ void PrintOclCommonDoc() PrintOclDoc(SPIROpenCLCommonVersion); } +void PrintOclInstructionsTable(std::ostream& out) { + ParameterizeBuiltins(SPIROpenCLCommonVersion); + + out << R"( +// OpenCL extended instructions table, one instruction per line. +// All instructions have a result type and a result ID. +// Fields in this file are: +// - name +// - extended instruction index +// - EmptyList, or List of operand classes. +)"; + for (int i = 0; i < OclExtInstCeiling; ++i) { + const BuiltInFunctionParameters& inst = BuiltInDesc[i]; + // Skip gaps in the enum space, if any. + if (0 == strcmp("unknown", inst.opName)) continue; + assert(inst.hasType()); + assert(inst.hasResult()); + out << "ExtInst(" << inst.opName << ", " << i << ", "; + std::vector classes_info; + for (const auto& operand_class : inst.operands.classes()) { + classes_info.push_back({operand_class, false /* not optional */}); + } + PrintOperandClasses(classes_info, out); + out << ")" << std::endl; + } +} + }; // end namespace spv diff --git a/tools/spirv/OclDoc.h b/tools/spirv/OclDoc.h index 8568880..7cf8a31 100644 --- a/tools/spirv/OclDoc.h +++ b/tools/spirv/OclDoc.h @@ -35,6 +35,8 @@ #include #include "headers/spirv.hpp" +#include "doc.h" + namespace spv { void OclGetDebugNames(const char** names); @@ -189,6 +191,8 @@ public: int getImageType(int op) { return Type[op]; } AccessQualifier getAccessQualifier(int op) { return accessQualifier[op]; } + const std::vector& classes() const { return opClass; } + protected: std::vector opClass; std::vector desc; @@ -307,4 +311,7 @@ protected: // Print out the OpenCL common (all spec revisions) documentation. void PrintOclCommonDoc(); +// Prints the OpenCL instructions table, for consumption by SPIR-V Tools. +void PrintOclInstructionsTable(std::ostream& out); + }; // end namespace spv diff --git a/tools/spirv/assembler_table.cpp b/tools/spirv/assembler_table.cpp new file mode 100644 index 0000000..8dd8fa1 --- /dev/null +++ b/tools/spirv/assembler_table.cpp @@ -0,0 +1,229 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and/or associated documentation files (the +// "Materials"), to deal in the Materials without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Materials, and to +// permit persons to whom the Materials are furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Materials. +// +// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS +// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS +// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT +// https://www.khronos.org/registry/ +// +// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + +#include "assembler_table.h" + +#include +#include +#include + +#include "doc.h" + +namespace spv { + +namespace { + +// Returns true if the given instruction can vary in width. +bool IsVariableLength(const InstructionParameters& inst) { + const OperandParameters& operands = inst.operands; + for (int i = 0; i < operands.getNum() ; i++) { + switch (operands.getClass(i)) { + case spv::OperandVariableIds: + case spv::OperandOptionalLiteral: + case spv::OperandOptionalLiteralString: + case spv::OperandVariableLiterals: + case spv::OperandVariableIdLiteral: + case spv::OperandVariableLiteralId: + case spv::OperandLiteralString: + return true; + default: + break; + } + if (operands.isOptional(i)) return true; + } + return false; +} + +// Returns a string for the given operand class, or nullptr if it's invalid. +const char* GetOperandClassString(OperandClass operandClass) { + switch (operandClass) { +#define CASE(X) case X: return #X; + CASE(OperandNone) + CASE(OperandId) + CASE(OperandVariableIds) + CASE(OperandOptionalLiteral) + CASE(OperandOptionalLiteralString) + CASE(OperandVariableLiterals) + CASE(OperandVariableIdLiteral) + CASE(OperandVariableLiteralId) + CASE(OperandLiteralNumber) + CASE(OperandLiteralString) + CASE(OperandSource) + CASE(OperandExecutionModel) + CASE(OperandAddressing) + CASE(OperandMemory) + CASE(OperandExecutionMode) + CASE(OperandStorage) + CASE(OperandDimensionality) + CASE(OperandSamplerAddressingMode) + CASE(OperandSamplerFilterMode) + CASE(OperandSamplerImageFormat) + CASE(OperandImageChannelOrder) + CASE(OperandImageChannelDataType) + CASE(OperandImageOperands) + CASE(OperandFPFastMath) + CASE(OperandFPRoundingMode) + CASE(OperandLinkageType) + CASE(OperandAccessQualifier) + CASE(OperandFuncParamAttr) + CASE(OperandDecoration) + CASE(OperandBuiltIn) + CASE(OperandSelect) + CASE(OperandLoop) + CASE(OperandFunction) + CASE(OperandMemorySemantics) + CASE(OperandMemoryAccess) + CASE(OperandScope) + CASE(OperandGroupOperation) + CASE(OperandKernelEnqueueFlags) + CASE(OperandKernelProfilingInfo) + CASE(OperandCapability) + CASE(OperandOpcode) +#undef CASE + + case OperandCount: + default: + break; + } + return nullptr; +} +} // anonymous namespace + +// Prints a listing of the operand kinds for the given instruction. +// If the list is empty, then emit just "EmptyList", +// otherwise the output looks like a call to the "List" macro. +void PrintOperandClasses(const std::vector& classes, std::ostream& out) { + std::stringstream contents; + int numPrinted = 0; + for (auto class_info : classes) { + if (const char* name = GetOperandClassString(class_info.value)) { + if (numPrinted) contents << ", "; + switch (class_info.value) { + case OperandId: + case OperandImageOperands: + case OperandLiteralString: + case OperandMemoryAccess: + case OperandAccessQualifier: + if (class_info.is_optional) { + contents << "OperandOptional" << (name + strlen("Operand")); + } else { + contents << name; + } + break; + default: + contents << name; + break; + } + numPrinted++; + if (class_info.value == OperandImageOperands) { + // There are Id and/or VariableIds after this. Skip them + // because the bits in an OperandImageOperands determine + // exactly the number of Ids required to follow. + break; + } + } + } + + if (numPrinted) + out << "List(" << contents.str() << ")"; + else + out << "EmptyList"; +} +} // namespace spv + +namespace spv { + +// Prints a listing of the operand kinds for the given instruction. +// If the list is empty, then emit just "EmptyList", +// otherwise the output looks like a call to the "List" macro. +void PrintOperandClassesForInstruction(const InstructionParameters& inst, + std::ostream& out) { + std::vector result; + + const OperandParameters& operands = inst.operands; + for (int i = 0; i < operands.getNum(); i++) { + result.push_back({operands.getClass(i), operands.isOptional(i)}); + } + PrintOperandClasses(result, out); +} + +// Prints the table entry for the given instruction with the given opcode. +void PrintInstructionDesc(int opcode, const InstructionParameters& inst, std::ostream& out) { + const char* name = OpcodeString(opcode); + // There can be gaps in the listing. + // All valid operations have a name beginning with "Op". + if (strlen(name) > 2 && name[0] == 'O' && name[1] == 'p') { + out << "Instruction(" + << name + 2 << ", "; // Skip the "Op" part. + out << (inst.hasResult() ? 1 : 0) << ", "; + out << (inst.hasType() ? 1 : 0) << ", "; + out << inst.operands.getNum() << ", "; + + // Emit the capability, if any. + // The SPIR-V tools doesn't handle the concept of depending on more than + // one capability. So call it out separately. Currently the biggest case + // is 2. This is a big hack. + out << inst.capabilities.size() << ", "; + assert(inst.capabilities.size() < 3); + if (inst.capabilities.size() == 1) { + out << "Capability(" << CapabilityString(inst.capabilities[0]) << "), "; + } else if (inst.capabilities.size() == 2) { + out << "Capability2(" << CapabilityString(inst.capabilities[0]) << "," + << CapabilityString(inst.capabilities[1]) << "), "; + } else { + out << "Capability(None), "; + } + + out << (IsVariableLength(inst) ? 1 : 0) << ", "; + PrintOperandClassesForInstruction(inst, out); + out << ")" << std::endl; + } +} + +void PrintAssemblerTable(std::ostream& out) { + out << "// Instruction fields are:\n" + << "// name - skips the \"Op\" prefix\n" + << "// {0|1} - whether the instruction generates a result Id\n" + << "// {0|1} - whether the instruction encodes the type of the result Id\n" + << "// numLogicalOperands - does not include result id or type id\n" + << "// numCapabilities - we only handle 0 or 1 required capabilities\n" + << "// Capability() - capability required to use this instruction. Might be None.\n" + << "// There can be Capability2(a,b) for dependence on two capabilities.\n" + << "// {0|1} - whether the instruction is variable number of logical operands\n" + << "// EmptyList or List(...) - list of classes of logical operands\n" + << "// Example use:\n" + << "// #define EmptyList {}\n" + << "// #define List(...) {__VA_ARGS__}\n" + << "// #define Capability(C) Capability##C\n" + << "// #define CapabilityNone -1\n" + << "// #define Instruction(Name,HasResult,HasType,NumLogicalOperands,CapabiltyRequired,IsVariable,LogicalArgsList)\n"; + + for (int i = 0; i < spv::OpcodeCeiling ; i++ ) { + PrintInstructionDesc(i, InstructionDesc[i], out); + } +} + +} diff --git a/tools/spirv/assembler_table.h b/tools/spirv/assembler_table.h new file mode 100644 index 0000000..16ea384 --- /dev/null +++ b/tools/spirv/assembler_table.h @@ -0,0 +1,55 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and/or associated documentation files (the +// "Materials"), to deal in the Materials without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Materials, and to +// permit persons to whom the Materials are furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Materials. +// +// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS +// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS +// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT +// https://www.khronos.org/registry/ +// +// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + +#pragma once +#ifndef ASSEMBLER_TABLE_H +#define ASSEMBLER_TABLE_H + +#include +#include + +#include "doc.h" + +namespace spv { + + // Relevant facts about an operand class value. + struct OperandClassInfo { + OperandClass value; + bool is_optional; + }; + + // Prints the tables used to define the structure of instructions. + // Assumes that parameterization has already occurred + void PrintAssemblerTable(std::ostream& out); + + // Prints a listing of the operand kinds. + // If the list is empty, then emit just "EmptyList", + // otherwise the output looks like a call to the "List" macro. + void PrintOperandClasses(const std::vector& classes, std::ostream& out); + +}; // end namespace spv + +#endif // ASSEMBLER_TABLE_H diff --git a/tools/spirv/doc.h b/tools/spirv/doc.h index 4c11d3b..6e1718a 100644 --- a/tools/spirv/doc.h +++ b/tools/spirv/doc.h @@ -32,6 +32,9 @@ //ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE //POSSIBILITY OF SUCH DAMAGE. +#ifndef DOC_H_ +#define DOC_H_ + // // Author: John Kessenich, LunarG // @@ -81,6 +84,7 @@ const char* KernelEnqueueFlagsString(int); const char* KernelProfilingInfoString(int); const char* CapabilityString(int); const char* OpcodeString(int); +const char* OperandClassString(int); // For grouping opcodes into subsections enum OpcodeClass { @@ -157,6 +161,10 @@ enum OperandClass { OperandOpcode, + // The operand class enum is not part of the spec, so + // it should come after OperandOpcode. + OperandOperandClass, + OperandCount }; @@ -259,3 +267,5 @@ const char* AccessQualifierString(int attr); void PrintOperands(const OperandParameters& operands, int reservedOperands); }; // end namespace spv + +#endif // DOC_H_ diff --git a/tools/spirv/main.cpp b/tools/spirv/main.cpp index d7312f9..e1413bb 100644 --- a/tools/spirv/main.cpp +++ b/tools/spirv/main.cpp @@ -46,6 +46,7 @@ namespace spv { #include "headers/OpenCL.std.h" // This tool's includes +#include "assembler_table.h" #include "disassemble.h" #include "header.h" #include "doc.h" @@ -65,6 +66,8 @@ enum TOptions { EOptionDisassemble = 0x004, EOptionPrintHeader = 0x008, EOptionPrintOclBuiltinsAsciidoc = 0x010, + EOptionPrintAssemblerTable = 0x020, + EOptionPrintOclInstructionsTable = 0x040, }; std::string Filename; @@ -86,9 +89,11 @@ void Usage() " -H print header in all supported languages to files in current directory\n" " -p print documentation\n" " -s [version] prints the SPIR-V extended instructions documentation\n" - " 'CL12': OpenCL 1.2 extended instructions documentation\n" - " 'CL20': OpenCL 2.0 extended instructions documentation\n" - " 'CL21': OpenCL 2.1 extended instructions documentation\n" + " 'OpenCL': OpenCL 1.2, 2.0, 2.1 extended instructions documentation\n" + " 'GLSL': GLSL extended instructions documentation\n" + " -a print table for the assembler\n" + " -C print OpenCL instructions for the assembler\n" + " This is incompatibile with -s OpenCL\n" ); } @@ -155,6 +160,12 @@ bool ProcessArguments(int argc, char* argv[]) } return true; } + case 'a': + Options |= EOptionPrintAssemblerTable; + break; + case 'C': + Options |= EOptionPrintOclInstructionsTable; + break; default: return false; } @@ -162,6 +173,9 @@ bool ProcessArguments(int argc, char* argv[]) Filename = std::string(argv[0]); } } + if ((Options & EOptionPrintOclBuiltinsAsciidoc) && + (Options & EOptionPrintOclInstructionsTable)) + return false; return true; } @@ -220,5 +234,11 @@ int main(int argc, char* argv[]) if (Options & EOptionPrintHeader) spv::PrintHeader(Language, std::cout); + if (Options & EOptionPrintAssemblerTable) + spv::PrintAssemblerTable(std::cout); + + if (Options & EOptionPrintOclInstructionsTable) + spv::PrintOclInstructionsTable(std::cout); + return 0; }