| // | |
| // Copyright (C) 2014-2015 LunarG, Inc. | |
| // Copyright (C) 2015-2018 Google, Inc. | |
| // Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. | |
| // | |
| // All rights reserved. | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions | |
| // are met: | |
| // | |
| // Redistributions of source code must retain the above copyright | |
| // notice, this list of conditions and the following disclaimer. | |
| // | |
| // Redistributions in binary form must reproduce the above | |
| // copyright notice, this list of conditions and the following | |
| // disclaimer in the documentation and/or other materials provided | |
| // with the distribution. | |
| // | |
| // Neither the name of 3Dlabs Inc. Ltd. nor the names of its | |
| // contributors may be used to endorse or promote products derived | |
| // from this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
| // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
| // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | |
| // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | |
| // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
| // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
| // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
| // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
| // POSSIBILITY OF SUCH DAMAGE. | |
| // | |
| // Helper for making SPIR-V IR. Generally, this is documented in the header | |
| // SpvBuilder.h. | |
| // | |
| #include <cassert> | |
| #include <cstdlib> | |
| #include <unordered_set> | |
| #include <algorithm> | |
| #include "SpvBuilder.h" | |
| #include "spvUtil.h" | |
| #include "hex_float.h" | |
| #ifndef _WIN32 | |
| #include <cstdio> | |
| #endif | |
| namespace spv { | |
| Builder::Builder(unsigned int spvVersion, unsigned int magicNumber, SpvBuildLogger* buildLogger) : | |
| spvVersion(spvVersion), | |
| sourceLang(SourceLanguage::Unknown), | |
| sourceVersion(0), | |
| addressModel(AddressingModel::Logical), | |
| memoryModel(MemoryModel::GLSL450), | |
| builderNumber(magicNumber), | |
| buildPoint(nullptr), | |
| uniqueId(0), | |
| entryPointFunction(nullptr), | |
| generatingOpCodeForSpecConst(false), | |
| descHeapByteArrayType(NoResult), | |
| logger(buildLogger) | |
| { | |
| clearAccessChain(); | |
| } | |
| Builder::~Builder() | |
| { | |
| } | |
| Id Builder::import(const char* name) | |
| { | |
| Instruction* import = new Instruction(getUniqueId(), NoType, Op::OpExtInstImport); | |
| import->addStringOperand(name); | |
| module.mapInstruction(import); | |
| imports.push_back(std::unique_ptr<Instruction>(import)); | |
| return import->getResultId(); | |
| } | |
| // For creating new groupedTypes (will return old type if the requested one was already made). | |
| Id Builder::makeVoidType() | |
| { | |
| Instruction* type; | |
| if (groupedTypes[enumCast(Op::OpTypeVoid)].size() == 0) { | |
| Id typeId = getUniqueId(); | |
| type = new Instruction(typeId, NoType, Op::OpTypeVoid); | |
| groupedTypes[enumCast(Op::OpTypeVoid)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| // Core OpTypeVoid used for debug void type | |
| if (emitNonSemanticShaderDebugInfo) | |
| debugTypeIdLookup[typeId] = typeId; | |
| } else | |
| type = groupedTypes[enumCast(Op::OpTypeVoid)].back(); | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeBoolType() | |
| { | |
| Instruction* type; | |
| if (groupedTypes[enumCast(Op::OpTypeBool)].size() == 0) { | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypeBool); | |
| groupedTypes[enumCast(Op::OpTypeBool)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| if (emitNonSemanticShaderDebugInfo) { | |
| auto const debugResultId = makeBoolDebugType(32); | |
| debugTypeIdLookup[type->getResultId()] = debugResultId; | |
| } | |
| } else | |
| type = groupedTypes[enumCast(Op::OpTypeBool)].back(); | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeSamplerType(const char* debugName) | |
| { | |
| Instruction* type; | |
| if (groupedTypes[enumCast(Op::OpTypeSampler)].size() == 0) { | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypeSampler); | |
| groupedTypes[enumCast(Op::OpTypeSampler)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| } else | |
| type = groupedTypes[enumCast(Op::OpTypeSampler)].back(); | |
| if (emitNonSemanticShaderDebugInfo) | |
| { | |
| auto const debugResultId = makeOpaqueDebugType(debugName); | |
| debugTypeIdLookup[type->getResultId()] = debugResultId; | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makePointer(StorageClass storageClass, Id pointee) | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[enumCast(Op::OpTypePointer)].size(); ++t) { | |
| type = groupedTypes[enumCast(Op::OpTypePointer)][t]; | |
| if (type->getImmediateOperand(0) == (unsigned)storageClass && | |
| type->getIdOperand(1) == pointee) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypePointer); | |
| type->reserveOperands(2); | |
| type->addImmediateOperand(storageClass); | |
| type->addIdOperand(pointee); | |
| groupedTypes[enumCast(Op::OpTypePointer)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| if (emitNonSemanticShaderDebugInfo) { | |
| const Id debugResultId = makePointerDebugType(storageClass, pointee); | |
| debugTypeIdLookup[type->getResultId()] = debugResultId; | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeForwardPointer(StorageClass storageClass) | |
| { | |
| // Caching/uniquifying doesn't work here, because we don't know the | |
| // pointee type and there can be multiple forward pointers of the same | |
| // storage type. Somebody higher up in the stack must keep track. | |
| Instruction* type = new Instruction(getUniqueId(), NoType, Op::OpTypeForwardPointer); | |
| type->addImmediateOperand(storageClass); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| if (emitNonSemanticShaderDebugInfo) { | |
| const Id debugResultId = makeForwardPointerDebugType(storageClass); | |
| debugTypeIdLookup[type->getResultId()] = debugResultId; | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeUntypedPointer(StorageClass storageClass, bool setBufferPointer) | |
| { | |
| // try to find it | |
| Instruction* type; | |
| // both typeBufferEXT and UntypedPointer only contains storage class info. | |
| spv::Op typeOp = setBufferPointer ? Op::OpTypeBufferEXT : Op::OpTypeUntypedPointerKHR; | |
| for (int t = 0; t < (int)groupedTypes[enumCast(typeOp)].size(); ++t) { | |
| type = groupedTypes[enumCast(typeOp)][t]; | |
| if (type->getImmediateOperand(0) == (unsigned)storageClass) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, typeOp); | |
| type->addImmediateOperand(storageClass); | |
| groupedTypes[enumCast(typeOp)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return type->getResultId(); | |
| } | |
| Id Builder::makePointerFromForwardPointer(StorageClass storageClass, Id forwardPointerType, Id pointee) | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[enumCast(Op::OpTypePointer)].size(); ++t) { | |
| type = groupedTypes[enumCast(Op::OpTypePointer)][t]; | |
| if (type->getImmediateOperand(0) == (unsigned)storageClass && | |
| type->getIdOperand(1) == pointee) | |
| return type->getResultId(); | |
| } | |
| type = new Instruction(forwardPointerType, NoType, Op::OpTypePointer); | |
| type->reserveOperands(2); | |
| type->addImmediateOperand(storageClass); | |
| type->addIdOperand(pointee); | |
| groupedTypes[enumCast(Op::OpTypePointer)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| // If we are emitting nonsemantic debuginfo, we need to patch the debug pointer type | |
| // that was emitted alongside the forward pointer, now that we have a pointee debug | |
| // type for it to point to. | |
| if (emitNonSemanticShaderDebugInfo) { | |
| Instruction *debugForwardPointer = module.getInstruction(getDebugType(forwardPointerType)); | |
| assert(getDebugType(pointee)); | |
| debugForwardPointer->setIdOperand(2, getDebugType(pointee)); | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeIntegerType(int width, bool hasSign) | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[enumCast(Op::OpTypeInt)].size(); ++t) { | |
| type = groupedTypes[enumCast(Op::OpTypeInt)][t]; | |
| if (type->getImmediateOperand(0) == (unsigned)width && | |
| type->getImmediateOperand(1) == (hasSign ? 1u : 0u)) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypeInt); | |
| type->reserveOperands(2); | |
| type->addImmediateOperand(width); | |
| type->addImmediateOperand(hasSign ? 1 : 0); | |
| groupedTypes[enumCast(Op::OpTypeInt)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| // deal with capabilities | |
| switch (width) { | |
| case 8: | |
| case 16: | |
| // these are currently handled by storage-type declarations and post processing | |
| break; | |
| case 64: | |
| addCapability(Capability::Int64); | |
| break; | |
| default: | |
| break; | |
| } | |
| if (emitNonSemanticShaderDebugInfo) | |
| { | |
| auto const debugResultId = makeIntegerDebugType(width, hasSign); | |
| debugTypeIdLookup[type->getResultId()] = debugResultId; | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeFloatType(int width) | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[enumCast(Op::OpTypeFloat)].size(); ++t) { | |
| type = groupedTypes[enumCast(Op::OpTypeFloat)][t]; | |
| if (type->getNumOperands() != 1) { | |
| continue; | |
| } | |
| if (type->getImmediateOperand(0) == (unsigned)width) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypeFloat); | |
| type->addImmediateOperand(width); | |
| groupedTypes[enumCast(Op::OpTypeFloat)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| // deal with capabilities | |
| switch (width) { | |
| case 16: | |
| // currently handled by storage-type declarations and post processing | |
| break; | |
| case 64: | |
| addCapability(Capability::Float64); | |
| break; | |
| default: | |
| break; | |
| } | |
| if (emitNonSemanticShaderDebugInfo) | |
| { | |
| auto const debugResultId = makeFloatDebugType(width); | |
| debugTypeIdLookup[type->getResultId()] = debugResultId; | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeBFloat16Type() | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[enumCast(Op::OpTypeFloat)].size(); ++t) { | |
| type = groupedTypes[enumCast(Op::OpTypeFloat)][t]; | |
| if (type->getNumOperands() != 2) { | |
| continue; | |
| } | |
| if (type->getImmediateOperand(0) == (unsigned)16 && | |
| type->getImmediateOperand(1) == FPEncoding::BFloat16KHR) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypeFloat); | |
| type->addImmediateOperand(16); | |
| type->addImmediateOperand(FPEncoding::BFloat16KHR); | |
| groupedTypes[enumCast(Op::OpTypeFloat)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| addExtension(spv::E_SPV_KHR_bfloat16); | |
| addCapability(Capability::BFloat16TypeKHR); | |
| if (emitNonSemanticShaderDebugInfo) { | |
| auto const debugResultId = makeFloatDebugType(16, makeUintConstant((unsigned)FPEncoding::BFloat16KHR)); | |
| debugTypeIdLookup[type->getResultId()] = debugResultId; | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeFloatE5M2Type() | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[enumCast(Op::OpTypeFloat)].size(); ++t) { | |
| type = groupedTypes[enumCast(Op::OpTypeFloat)][t]; | |
| if (type->getNumOperands() != 2) { | |
| continue; | |
| } | |
| if (type->getImmediateOperand(0) == (unsigned)8 && | |
| type->getImmediateOperand(1) == FPEncoding::Float8E5M2EXT) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypeFloat); | |
| type->addImmediateOperand(8); | |
| type->addImmediateOperand(FPEncoding::Float8E5M2EXT); | |
| groupedTypes[enumCast(Op::OpTypeFloat)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| addExtension(spv::E_SPV_EXT_float8); | |
| addCapability(Capability::Float8EXT); | |
| if (emitNonSemanticShaderDebugInfo) { | |
| auto const debugResultId = makeFloatDebugType(8, makeUintConstant((unsigned)FPEncoding::Float8E5M2EXT)); | |
| debugTypeIdLookup[type->getResultId()] = debugResultId; | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeFloatE4M3Type() | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[enumCast(Op::OpTypeFloat)].size(); ++t) { | |
| type = groupedTypes[enumCast(Op::OpTypeFloat)][t]; | |
| if (type->getNumOperands() != 2) { | |
| continue; | |
| } | |
| if (type->getImmediateOperand(0) == (unsigned)8 && | |
| type->getImmediateOperand(1) == FPEncoding::Float8E4M3EXT) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypeFloat); | |
| type->addImmediateOperand(8); | |
| type->addImmediateOperand(FPEncoding::Float8E4M3EXT); | |
| groupedTypes[enumCast(Op::OpTypeFloat)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| addExtension(spv::E_SPV_EXT_float8); | |
| addCapability(Capability::Float8EXT); | |
| if (emitNonSemanticShaderDebugInfo) { | |
| auto const debugResultId = makeFloatDebugType(8, makeUintConstant((unsigned)FPEncoding::Float8E4M3EXT)); | |
| debugTypeIdLookup[type->getResultId()] = debugResultId; | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeFloatOcpMicroscalingType(uint32_t width, FPEncoding encoding, Capability cap) | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[enumCast(Op::OpTypeFloat)].size(); ++t) { | |
| type = groupedTypes[enumCast(Op::OpTypeFloat)][t]; | |
| if (type->getNumOperands() != 2) { | |
| continue; | |
| } | |
| if (type->getImmediateOperand(0) == (unsigned)width && | |
| type->getImmediateOperand(1) == encoding) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypeFloat); | |
| type->addImmediateOperand(width); | |
| type->addImmediateOperand(encoding); | |
| groupedTypes[enumCast(Op::OpTypeFloat)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| addExtension(spv::E_SPV_EXT_ocp_microscaling_types); | |
| addCapability(cap); | |
| if (emitNonSemanticShaderDebugInfo) | |
| { | |
| auto const debugResultId = makeFloatDebugType(width, makeUintConstant((unsigned)encoding)); | |
| debugTypeIdLookup[type->getResultId()] = debugResultId; | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeFloatE2M1Type() | |
| { | |
| return makeFloatOcpMicroscalingType(4, FPEncoding::Float4E2M1EXT, Capability::Float4EXT); | |
| } | |
| Id Builder::makeFloatE3M2Type() | |
| { | |
| return makeFloatOcpMicroscalingType(6, FPEncoding::Float6E3M2EXT, Capability::Float6EXT); | |
| } | |
| Id Builder::makeFloatE2M3Type() | |
| { | |
| return makeFloatOcpMicroscalingType(6, FPEncoding::Float6E2M3EXT, Capability::Float6EXT); | |
| } | |
| Id Builder::makeFloatUE8M0Type() | |
| { | |
| return makeFloatOcpMicroscalingType(8, FPEncoding::Float8UnsignedE8M0EXT, Capability::Float8UnsignedE8M0EXT); | |
| } | |
| Id Builder::makeFloatMXINT8Type() | |
| { | |
| return makeFloatOcpMicroscalingType(8, FPEncoding::MXInt8EXT, Capability::MXInt8EXT); | |
| } | |
| // Make a struct without checking for duplication. | |
| // See makeStructResultType() for non-decorated structs | |
| // needed as the result of some instructions, which does | |
| // check for duplicates. | |
| // For compiler-generated structs, debug info is ignored. | |
| Id Builder::makeStructType(const std::vector<Id>& members, const std::vector<spv::StructMemberDebugInfo>& memberDebugInfo, | |
| const char* name, bool const compilerGenerated) | |
| { | |
| // Don't look for previous one, because in the general case, | |
| // structs can be duplicated except for decorations. | |
| // not found, make it | |
| Instruction* type = new Instruction(getUniqueId(), NoType, Op::OpTypeStruct); | |
| for (int op = 0; op < (int)members.size(); ++op) | |
| type->addIdOperand(members[op]); | |
| groupedTypes[enumCast(Op::OpTypeStruct)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| addName(type->getResultId(), name); | |
| if (emitNonSemanticShaderDebugInfo && !compilerGenerated) { | |
| assert(members.size() == memberDebugInfo.size()); | |
| auto const debugResultId = | |
| makeCompositeDebugType(members, memberDebugInfo, name, NonSemanticShaderDebugInfoStructure); | |
| debugTypeIdLookup[type->getResultId()] = debugResultId; | |
| } | |
| return type->getResultId(); | |
| } | |
| // Make a struct for the simple results of several instructions, | |
| // checking for duplication. | |
| Id Builder::makeStructResultType(Id type0, Id type1) | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[enumCast(Op::OpTypeStruct)].size(); ++t) { | |
| type = groupedTypes[enumCast(Op::OpTypeStruct)][t]; | |
| if (type->getNumOperands() != 2) | |
| continue; | |
| if (type->getIdOperand(0) != type0 || | |
| type->getIdOperand(1) != type1) | |
| continue; | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| std::vector<spv::Id> members; | |
| members.push_back(type0); | |
| members.push_back(type1); | |
| return makeStructType(members, {}, "ResType"); | |
| } | |
| Id Builder::makeVectorType(Id component, int size) | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[enumCast(Op::OpTypeVector)].size(); ++t) { | |
| type = groupedTypes[enumCast(Op::OpTypeVector)][t]; | |
| if (type->getIdOperand(0) == component && | |
| type->getImmediateOperand(1) == (unsigned)size) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypeVector); | |
| type->reserveOperands(2); | |
| type->addIdOperand(component); | |
| type->addImmediateOperand(size); | |
| groupedTypes[enumCast(Op::OpTypeVector)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| if (emitNonSemanticShaderDebugInfo) | |
| { | |
| auto const debugResultId = makeVectorDebugType(component, size); | |
| debugTypeIdLookup[type->getResultId()] = debugResultId; | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeMatrixType(Id component, int cols, int rows) | |
| { | |
| assert(cols <= maxMatrixSize && rows <= maxMatrixSize); | |
| Id column = makeVectorType(component, rows); | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[enumCast(Op::OpTypeMatrix)].size(); ++t) { | |
| type = groupedTypes[enumCast(Op::OpTypeMatrix)][t]; | |
| if (type->getIdOperand(0) == column && | |
| type->getImmediateOperand(1) == (unsigned)cols) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypeMatrix); | |
| type->reserveOperands(2); | |
| type->addIdOperand(column); | |
| type->addImmediateOperand(cols); | |
| groupedTypes[enumCast(Op::OpTypeMatrix)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| if (emitNonSemanticShaderDebugInfo) | |
| { | |
| auto const debugResultId = makeMatrixDebugType(column, cols); | |
| debugTypeIdLookup[type->getResultId()] = debugResultId; | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeCooperativeMatrixTypeKHR(Id component, Id scope, Id rows, Id cols, Id use) | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[enumCast(Op::OpTypeCooperativeMatrixKHR)].size(); ++t) { | |
| type = groupedTypes[enumCast(Op::OpTypeCooperativeMatrixKHR)][t]; | |
| if (type->getIdOperand(0) == component && | |
| type->getIdOperand(1) == scope && | |
| type->getIdOperand(2) == rows && | |
| type->getIdOperand(3) == cols && | |
| type->getIdOperand(4) == use) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypeCooperativeMatrixKHR); | |
| type->reserveOperands(5); | |
| type->addIdOperand(component); | |
| type->addIdOperand(scope); | |
| type->addIdOperand(rows); | |
| type->addIdOperand(cols); | |
| type->addIdOperand(use); | |
| groupedTypes[enumCast(Op::OpTypeCooperativeMatrixKHR)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| if (emitNonSemanticShaderDebugInfo) { | |
| auto const debugResultId = makeCooperativeMatrixDebugTypeKHR(component, scope, rows, cols, use); | |
| debugTypeIdLookup[type->getResultId()] = debugResultId; | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeCooperativeMatrixTypeNV(Id component, Id scope, Id rows, Id cols) | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[enumCast(Op::OpTypeCooperativeMatrixNV)].size(); ++t) { | |
| type = groupedTypes[enumCast(Op::OpTypeCooperativeMatrixNV)][t]; | |
| if (type->getIdOperand(0) == component && type->getIdOperand(1) == scope && type->getIdOperand(2) == rows && | |
| type->getIdOperand(3) == cols) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypeCooperativeMatrixNV); | |
| type->reserveOperands(4); | |
| type->addIdOperand(component); | |
| type->addIdOperand(scope); | |
| type->addIdOperand(rows); | |
| type->addIdOperand(cols); | |
| groupedTypes[enumCast(Op::OpTypeCooperativeMatrixNV)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeCooperativeMatrixTypeWithSameShape(Id component, Id otherType) | |
| { | |
| Instruction* instr = module.getInstruction(otherType); | |
| if (instr->getOpCode() == Op::OpTypeCooperativeMatrixNV) { | |
| return makeCooperativeMatrixTypeNV(component, instr->getIdOperand(1), instr->getIdOperand(2), instr->getIdOperand(3)); | |
| } else { | |
| assert(instr->getOpCode() == Op::OpTypeCooperativeMatrixKHR); | |
| return makeCooperativeMatrixTypeKHR(component, instr->getIdOperand(1), instr->getIdOperand(2), instr->getIdOperand(3), instr->getIdOperand(4)); | |
| } | |
| } | |
| Id Builder::makeCooperativeVectorTypeNV(Id componentType, Id components) | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[enumCast(Op::OpTypeCooperativeVectorNV)].size(); ++t) { | |
| type = groupedTypes[enumCast(Op::OpTypeCooperativeVectorNV)][t]; | |
| if (type->getIdOperand(0) == componentType && | |
| type->getIdOperand(1) == components) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypeCooperativeVectorNV); | |
| type->addIdOperand(componentType); | |
| type->addIdOperand(components); | |
| groupedTypes[enumCast(Op::OpTypeCooperativeVectorNV)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| if (emitNonSemanticShaderDebugInfo) { | |
| auto const debugResultId = makeVectorIdDebugType(componentType, components); | |
| debugTypeIdLookup[type->getResultId()] = debugResultId; | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeTensorTypeARM(Id elementType, Id rank) | |
| { | |
| // See if an OpTypeTensorARM with same element type and rank already exists. | |
| for (int t = 0; t < (int)groupedTypes[enumCast(Op::OpTypeTensorARM)].size(); ++t) { | |
| const Instruction *type = groupedTypes[enumCast(Op::OpTypeTensorARM)][t]; | |
| if (type->getIdOperand(0) == elementType && type->getIdOperand(1) == rank) | |
| return type->getResultId(); | |
| } | |
| // Not found, make it. | |
| std::unique_ptr<Instruction> type(new Instruction(getUniqueId(), NoType, Op::OpTypeTensorARM)); | |
| type->addIdOperand(elementType); | |
| type->addIdOperand(rank); | |
| groupedTypes[enumCast(Op::OpTypeTensorARM)].push_back(type.get()); | |
| module.mapInstruction(type.get()); | |
| Id resultID = type->getResultId(); | |
| constantsTypesGlobals.push_back(std::move(type)); | |
| return resultID; | |
| } | |
| Id Builder::makeGenericType(spv::Op opcode, std::vector<spv::IdImmediate>& operands) | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[enumCast(opcode)].size(); ++t) { | |
| type = groupedTypes[enumCast(opcode)][t]; | |
| if (static_cast<size_t>(type->getNumOperands()) != operands.size()) | |
| continue; // Number mismatch, find next | |
| bool match = true; | |
| for (int op = 0; match && op < (int)operands.size(); ++op) { | |
| match = (operands[op].isId ? type->getIdOperand(op) : type->getImmediateOperand(op)) == operands[op].word; | |
| } | |
| if (match) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, opcode); | |
| type->reserveOperands(operands.size()); | |
| for (size_t op = 0; op < operands.size(); ++op) { | |
| if (operands[op].isId) | |
| type->addIdOperand(operands[op].word); | |
| else | |
| type->addImmediateOperand(operands[op].word); | |
| } | |
| groupedTypes[enumCast(opcode)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return type->getResultId(); | |
| } | |
| // TODO: performance: track arrays per stride | |
| // If a stride is supplied (non-zero) make an array. | |
| // If no stride (0), reuse previous array types. | |
| // 'size' is an Id of a constant or specialization constant of the array size | |
| Id Builder::makeArrayType(Id element, Id sizeId, int stride) | |
| { | |
| Instruction* type; | |
| if (stride == 0) { | |
| // try to find existing type | |
| for (int t = 0; t < (int)groupedTypes[enumCast(Op::OpTypeArray)].size(); ++t) { | |
| type = groupedTypes[enumCast(Op::OpTypeArray)][t]; | |
| if (type->getIdOperand(0) == element && | |
| type->getIdOperand(1) == sizeId && | |
| explicitlyLaidOut.find(type->getResultId()) == explicitlyLaidOut.end()) | |
| return type->getResultId(); | |
| } | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypeArray); | |
| type->reserveOperands(2); | |
| type->addIdOperand(element); | |
| type->addIdOperand(sizeId); | |
| groupedTypes[enumCast(Op::OpTypeArray)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| if (stride != 0) { | |
| explicitlyLaidOut.insert(type->getResultId()); | |
| } | |
| if (emitNonSemanticShaderDebugInfo) | |
| { | |
| auto const debugResultId = makeArrayDebugType(element, sizeId); | |
| debugTypeIdLookup[type->getResultId()] = debugResultId; | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeRuntimeArray(Id element) | |
| { | |
| Instruction* type = new Instruction(getUniqueId(), NoType, Op::OpTypeRuntimeArray); | |
| type->addIdOperand(element); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| if (emitNonSemanticShaderDebugInfo) | |
| { | |
| auto const debugResultId = makeArrayDebugType(element, makeUintConstant(0)); | |
| debugTypeIdLookup[type->getResultId()] = debugResultId; | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeFunctionType(Id returnType, const std::vector<Id>& paramTypes) | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[enumCast(Op::OpTypeFunction)].size(); ++t) { | |
| type = groupedTypes[enumCast(Op::OpTypeFunction)][t]; | |
| if (type->getIdOperand(0) != returnType || (int)paramTypes.size() != type->getNumOperands() - 1) | |
| continue; | |
| bool mismatch = false; | |
| for (int p = 0; p < (int)paramTypes.size(); ++p) { | |
| if (paramTypes[p] != type->getIdOperand(p + 1)) { | |
| mismatch = true; | |
| break; | |
| } | |
| } | |
| if (! mismatch) | |
| { | |
| // If compiling HLSL, glslang will create a wrapper function around the entrypoint. Accordingly, a void(void) | |
| // function type is created for the wrapper function. However, nonsemantic shader debug information is disabled | |
| // while creating the HLSL wrapper. Consequently, if we encounter another void(void) function, we need to create | |
| // the associated debug function type if it hasn't been created yet. | |
| if(emitNonSemanticShaderDebugInfo && getDebugType(type->getResultId()) == NoType) { | |
| assert(sourceLang == spv::SourceLanguage::HLSL); | |
| assert(getTypeClass(returnType) == Op::OpTypeVoid && paramTypes.size() == 0); | |
| Id id = makeDebugFunctionType(returnType, {}); | |
| debugTypeIdLookup[type->getResultId()] = id; | |
| } | |
| return type->getResultId(); | |
| } | |
| } | |
| // not found, make it | |
| Id typeId = getUniqueId(); | |
| type = new Instruction(typeId, NoType, Op::OpTypeFunction); | |
| type->reserveOperands(paramTypes.size() + 1); | |
| type->addIdOperand(returnType); | |
| for (int p = 0; p < (int)paramTypes.size(); ++p) | |
| type->addIdOperand(paramTypes[p]); | |
| groupedTypes[enumCast(Op::OpTypeFunction)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| // make debug type and map it | |
| if (emitNonSemanticShaderDebugInfo) { | |
| Id debugTypeId = makeDebugFunctionType(returnType, paramTypes); | |
| debugTypeIdLookup[typeId] = debugTypeId; | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeDebugFunctionType(Id returnType, const std::vector<Id>& paramTypes) | |
| { | |
| assert(getDebugType(returnType) != NoType); | |
| Id typeId = getUniqueId(); | |
| auto type = new Instruction(typeId, makeVoidType(), Op::OpExtInst); | |
| type->reserveOperands(paramTypes.size() + 4); | |
| type->addIdOperand(nonSemanticShaderDebugInfo); | |
| type->addImmediateOperand(NonSemanticShaderDebugInfoDebugTypeFunction); | |
| type->addIdOperand(makeUintConstant(NonSemanticShaderDebugInfoFlagIsPublic)); | |
| type->addIdOperand(getDebugType(returnType)); | |
| for (auto const paramType : paramTypes) { | |
| if (isPointerType(paramType) || isArrayType(paramType)) { | |
| type->addIdOperand(getDebugType(getContainedTypeId(paramType))); | |
| } | |
| else { | |
| type->addIdOperand(getDebugType(paramType)); | |
| } | |
| } | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return typeId; | |
| } | |
| Id Builder::makeImageType(Id sampledType, Dim dim, bool depth, bool arrayed, bool ms, unsigned sampled, | |
| ImageFormat format, const char* debugName) | |
| { | |
| assert(sampled == 1 || sampled == 2); | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[enumCast(Op::OpTypeImage)].size(); ++t) { | |
| type = groupedTypes[enumCast(Op::OpTypeImage)][t]; | |
| if (type->getIdOperand(0) == sampledType && | |
| type->getImmediateOperand(1) == (unsigned int)dim && | |
| type->getImmediateOperand(2) == ( depth ? 1u : 0u) && | |
| type->getImmediateOperand(3) == (arrayed ? 1u : 0u) && | |
| type->getImmediateOperand(4) == ( ms ? 1u : 0u) && | |
| type->getImmediateOperand(5) == sampled && | |
| type->getImmediateOperand(6) == (unsigned int)format) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypeImage); | |
| type->reserveOperands(7); | |
| type->addIdOperand(sampledType); | |
| type->addImmediateOperand( dim); | |
| type->addImmediateOperand( depth ? 1 : 0); | |
| type->addImmediateOperand(arrayed ? 1 : 0); | |
| type->addImmediateOperand( ms ? 1 : 0); | |
| type->addImmediateOperand(sampled); | |
| type->addImmediateOperand((unsigned int)format); | |
| groupedTypes[enumCast(Op::OpTypeImage)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| // deal with capabilities | |
| switch (dim) { | |
| case Dim::Buffer: | |
| if (sampled == 1) | |
| addCapability(Capability::SampledBuffer); | |
| else | |
| addCapability(Capability::ImageBuffer); | |
| break; | |
| case Dim::Dim1D: | |
| if (sampled == 1) | |
| addCapability(Capability::Sampled1D); | |
| else | |
| addCapability(Capability::Image1D); | |
| break; | |
| case Dim::Cube: | |
| if (arrayed) { | |
| if (sampled == 1) | |
| addCapability(Capability::SampledCubeArray); | |
| else | |
| addCapability(Capability::ImageCubeArray); | |
| } | |
| break; | |
| case Dim::Rect: | |
| if (sampled == 1) | |
| addCapability(Capability::SampledRect); | |
| else | |
| addCapability(Capability::ImageRect); | |
| break; | |
| case Dim::SubpassData: | |
| addCapability(Capability::InputAttachment); | |
| break; | |
| default: | |
| break; | |
| } | |
| if (ms) { | |
| if (sampled == 2) { | |
| // Images used with subpass data are not storage | |
| // images, so don't require the capability for them. | |
| if (dim != Dim::SubpassData) | |
| addCapability(Capability::StorageImageMultisample); | |
| if (arrayed) | |
| addCapability(Capability::ImageMSArray); | |
| } | |
| } | |
| if (emitNonSemanticShaderDebugInfo) | |
| { | |
| auto const debugResultId = makeOpaqueDebugType(debugName); | |
| debugTypeIdLookup[type->getResultId()] = debugResultId; | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeSampledImageType(Id imageType, const char* debugName) | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[enumCast(Op::OpTypeSampledImage)].size(); ++t) { | |
| type = groupedTypes[enumCast(Op::OpTypeSampledImage)][t]; | |
| if (type->getIdOperand(0) == imageType) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypeSampledImage); | |
| type->addIdOperand(imageType); | |
| groupedTypes[enumCast(Op::OpTypeSampledImage)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| if (emitNonSemanticShaderDebugInfo) | |
| { | |
| auto const debugResultId = makeOpaqueDebugType(debugName); | |
| debugTypeIdLookup[type->getResultId()] = debugResultId; | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeDebugInfoNone() | |
| { | |
| if (debugInfoNone != 0) | |
| return debugInfoNone; | |
| Instruction* inst = new Instruction(getUniqueId(), makeVoidType(), Op::OpExtInst); | |
| inst->reserveOperands(2); | |
| inst->addIdOperand(nonSemanticShaderDebugInfo); | |
| inst->addImmediateOperand(NonSemanticShaderDebugInfoDebugInfoNone); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(inst)); | |
| module.mapInstruction(inst); | |
| debugInfoNone = inst->getResultId(); | |
| return debugInfoNone; | |
| } | |
| Id Builder::makeBoolDebugType(int const size) | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeBasic].size(); ++t) { | |
| type = groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeBasic][t]; | |
| if (type->getIdOperand(0) == getStringId("bool") && | |
| type->getIdOperand(1) == static_cast<unsigned int>(size) && | |
| type->getIdOperand(2) == NonSemanticShaderDebugInfoBoolean) | |
| return type->getResultId(); | |
| } | |
| type = new Instruction(getUniqueId(), makeVoidType(), Op::OpExtInst); | |
| type->reserveOperands(6); | |
| type->addIdOperand(nonSemanticShaderDebugInfo); | |
| type->addImmediateOperand(NonSemanticShaderDebugInfoDebugTypeBasic); | |
| type->addIdOperand(getStringId("bool")); // name id | |
| type->addIdOperand(makeUintConstant(size)); // size id | |
| type->addIdOperand(makeUintConstant(NonSemanticShaderDebugInfoBoolean)); // encoding id | |
| type->addIdOperand(makeUintConstant(NonSemanticShaderDebugInfoNone)); // flags id | |
| groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeBasic].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeIntegerDebugType(int const width, bool const hasSign) | |
| { | |
| const char* typeName = nullptr; | |
| switch (width) { | |
| case 8: typeName = hasSign ? "int8_t" : "uint8_t"; break; | |
| case 16: typeName = hasSign ? "int16_t" : "uint16_t"; break; | |
| case 64: typeName = hasSign ? "int64_t" : "uint64_t"; break; | |
| default: typeName = hasSign ? "int" : "uint"; | |
| } | |
| auto nameId = getStringId(typeName); | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeBasic].size(); ++t) { | |
| type = groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeBasic][t]; | |
| if (type->getIdOperand(0) == nameId && | |
| type->getIdOperand(1) == static_cast<unsigned int>(width) && | |
| type->getIdOperand(2) == (hasSign ? NonSemanticShaderDebugInfoSigned : NonSemanticShaderDebugInfoUnsigned)) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), makeVoidType(), Op::OpExtInst); | |
| type->reserveOperands(6); | |
| type->addIdOperand(nonSemanticShaderDebugInfo); | |
| type->addImmediateOperand(NonSemanticShaderDebugInfoDebugTypeBasic); | |
| type->addIdOperand(nameId); // name id | |
| type->addIdOperand(makeUintConstant(width)); // size id | |
| if(hasSign == true) { | |
| type->addIdOperand(makeUintConstant(NonSemanticShaderDebugInfoSigned)); // encoding id | |
| } else { | |
| type->addIdOperand(makeUintConstant(NonSemanticShaderDebugInfoUnsigned)); // encoding id | |
| } | |
| type->addIdOperand(makeUintConstant(NonSemanticShaderDebugInfoNone)); // flags id | |
| groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeBasic].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeFloatDebugType(int const width, Id const fpEncoding) | |
| { | |
| if (fpEncoding != NoType) | |
| requireNonSemanticShaderDebugInfoVersion(101); | |
| // Determine the debug type name. FP-encoded variants have distinct names. | |
| // Id comparison works here because makeUintConstant deduplicates: two calls | |
| // with the same value return the same Id. | |
| const char* typeName = nullptr; | |
| if (fpEncoding != NoType) { | |
| if (fpEncoding == makeUintConstant((unsigned)FPEncoding::BFloat16KHR)) | |
| typeName = "bfloat16_t"; | |
| else if (fpEncoding == makeUintConstant((unsigned)FPEncoding::Float8E4M3EXT)) | |
| typeName = "floate4m3_t"; | |
| else if (fpEncoding == makeUintConstant((unsigned)FPEncoding::Float8E5M2EXT)) | |
| typeName = "floate5m2_t"; | |
| else if (fpEncoding == makeUintConstant((unsigned)FPEncoding::Float4E2M1EXT)) | |
| typeName = "floate2m1_t"; | |
| else if (fpEncoding == makeUintConstant((unsigned)FPEncoding::Float6E3M2EXT)) | |
| typeName = "floate3m2_t"; | |
| else if (fpEncoding == makeUintConstant((unsigned)FPEncoding::Float6E2M3EXT)) | |
| typeName = "floate2m3_t"; | |
| else if (fpEncoding == makeUintConstant((unsigned)FPEncoding::Float8UnsignedE8M0EXT)) | |
| typeName = "floatue8m0_t"; | |
| else if (fpEncoding == makeUintConstant((unsigned)FPEncoding::MXInt8EXT)) | |
| typeName = "floatmxint8_t"; | |
| else | |
| typeName = "float"; | |
| } else { | |
| switch (width) { | |
| case 16: typeName = "float16_t"; break; | |
| case 64: typeName = "double"; break; | |
| default: typeName = "float"; break; | |
| } | |
| } | |
| auto nameId = getStringId(typeName); | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeBasic].size(); ++t) { | |
| type = groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeBasic][t]; | |
| if (type->getIdOperand(0) == nameId && | |
| type->getIdOperand(1) == static_cast<unsigned int>(width) && | |
| type->getIdOperand(2) == NonSemanticShaderDebugInfoFloat) { | |
| if (fpEncoding == NoType) { | |
| if (type->getNumOperands() == 6) | |
| return type->getResultId(); | |
| } else { | |
| if (type->getNumOperands() == 7 && type->getIdOperand(6) == fpEncoding) | |
| return type->getResultId(); | |
| } | |
| } | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), makeVoidType(), Op::OpExtInst); | |
| type->reserveOperands(fpEncoding == NoType ? 6 : 7); | |
| type->addIdOperand(nonSemanticShaderDebugInfo); | |
| type->addImmediateOperand(NonSemanticShaderDebugInfoDebugTypeBasic); | |
| type->addIdOperand(nameId); // name id | |
| type->addIdOperand(makeUintConstant(width)); // size id | |
| type->addIdOperand(makeUintConstant(NonSemanticShaderDebugInfoFloat)); // encoding id | |
| type->addIdOperand(makeUintConstant(NonSemanticShaderDebugInfoNone)); // flags id | |
| if (fpEncoding != NoType) | |
| type->addIdOperand(fpEncoding); // optional FPEncoding id (NSDI.101) | |
| groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeBasic].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeSequentialDebugType(Id const baseType, Id const componentCount, NonSemanticShaderDebugInfoInstructions const sequenceType) | |
| { | |
| assert(sequenceType == NonSemanticShaderDebugInfoDebugTypeArray || | |
| sequenceType == NonSemanticShaderDebugInfoDebugTypeVector); | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedDebugTypes[sequenceType].size(); ++t) { | |
| type = groupedDebugTypes[sequenceType][t]; | |
| if (type->getIdOperand(0) == baseType && | |
| type->getIdOperand(1) == makeUintConstant(componentCount)) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), makeVoidType(), Op::OpExtInst); | |
| type->reserveOperands(4); | |
| type->addIdOperand(nonSemanticShaderDebugInfo); | |
| type->addImmediateOperand(sequenceType); | |
| type->addIdOperand(getDebugType(baseType)); // base type | |
| type->addIdOperand(componentCount); // component count | |
| groupedDebugTypes[sequenceType].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeArrayDebugType(Id const baseType, Id const componentCount) | |
| { | |
| return makeSequentialDebugType(baseType, componentCount, NonSemanticShaderDebugInfoDebugTypeArray); | |
| } | |
| Id Builder::makeVectorDebugType(Id const baseType, int const componentCount) | |
| { | |
| return makeSequentialDebugType(baseType, makeUintConstant(componentCount), NonSemanticShaderDebugInfoDebugTypeVector); | |
| } | |
| Id Builder::makeMatrixDebugType(Id const vectorType, int const vectorCount, bool columnMajor) | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeMatrix].size(); ++t) { | |
| type = groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeMatrix][t]; | |
| if (type->getIdOperand(0) == vectorType && | |
| type->getIdOperand(1) == makeUintConstant(vectorCount)) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), makeVoidType(), Op::OpExtInst); | |
| type->reserveOperands(5); | |
| type->addIdOperand(nonSemanticShaderDebugInfo); | |
| type->addImmediateOperand(NonSemanticShaderDebugInfoDebugTypeMatrix); | |
| type->addIdOperand(getDebugType(vectorType)); // vector type id | |
| type->addIdOperand(makeUintConstant(vectorCount)); // component count id | |
| type->addIdOperand(makeBoolConstant(columnMajor)); // column-major id | |
| groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeMatrix].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return type->getResultId(); | |
| } | |
| // DebugTypeVectorIdEXT (NSDI.101 opcode 109): describes OpTypeVectorIdEXT and | |
| // OpTypeCooperativeVectorNV types whose component count is a SPIR-V Id (not a | |
| // literal), including specialization-constant component counts. | |
| Id Builder::makeVectorIdDebugType(Id const componentType, Id const componentCount) | |
| { | |
| requireNonSemanticShaderDebugInfoVersion(101); | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeVectorIdEXT].size(); ++t) { | |
| type = groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeVectorIdEXT][t]; | |
| if (type->getIdOperand(2) == getDebugType(componentType) && | |
| type->getIdOperand(3) == componentCount) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), makeVoidType(), Op::OpExtInst); | |
| type->reserveOperands(4); | |
| type->addIdOperand(nonSemanticShaderDebugInfo); | |
| type->addImmediateOperand(NonSemanticShaderDebugInfoDebugTypeVectorIdEXT); | |
| type->addIdOperand(getDebugType(componentType)); // component debug type | |
| type->addIdOperand(componentCount); // component count (constant instruction Id) | |
| groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeVectorIdEXT].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return type->getResultId(); | |
| } | |
| // DebugTypeCooperativeMatrixKHR (NSDI.101 opcode 110): describes | |
| // OpTypeCooperativeMatrixKHR types. | |
| Id Builder::makeCooperativeMatrixDebugTypeKHR(Id const componentType, Id const scope, | |
| Id const rows, Id const cols, Id const use) | |
| { | |
| requireNonSemanticShaderDebugInfoVersion(101); | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeCooperativeMatrixKHR].size(); ++t) { | |
| type = groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeCooperativeMatrixKHR][t]; | |
| if (type->getIdOperand(2) == getDebugType(componentType) && | |
| type->getIdOperand(3) == scope && | |
| type->getIdOperand(4) == rows && | |
| type->getIdOperand(5) == cols && | |
| type->getIdOperand(6) == use) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), makeVoidType(), Op::OpExtInst); | |
| type->reserveOperands(7); | |
| type->addIdOperand(nonSemanticShaderDebugInfo); | |
| type->addImmediateOperand(NonSemanticShaderDebugInfoDebugTypeCooperativeMatrixKHR); | |
| type->addIdOperand(getDebugType(componentType)); // component debug type | |
| type->addIdOperand(scope); // scope | |
| type->addIdOperand(rows); // rows | |
| type->addIdOperand(cols); // columns | |
| type->addIdOperand(use); // use (MatrixA, MatrixB, Accumulator) | |
| groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeCooperativeMatrixKHR].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeMemberDebugType(Id const memberType, StructMemberDebugInfo const& debugTypeLoc) | |
| { | |
| assert(getDebugType(memberType) != NoType); | |
| Instruction* type = new Instruction(getUniqueId(), makeVoidType(), Op::OpExtInst); | |
| type->reserveOperands(10); | |
| type->addIdOperand(nonSemanticShaderDebugInfo); | |
| type->addImmediateOperand(NonSemanticShaderDebugInfoDebugTypeMember); | |
| type->addIdOperand(getStringId(debugTypeLoc.name)); // name id | |
| type->addIdOperand(debugTypeLoc.debugTypeOverride != 0 ? debugTypeLoc.debugTypeOverride | |
| : getDebugType(memberType)); // type id | |
| type->addIdOperand(makeDebugSource(currentFileId)); // source id | |
| type->addIdOperand(makeUintConstant(debugTypeLoc.line)); // line id TODO: currentLine is always zero | |
| type->addIdOperand(makeUintConstant(debugTypeLoc.column)); // TODO: column id | |
| type->addIdOperand(makeUintConstant(0)); // TODO: offset id | |
| type->addIdOperand(makeUintConstant(0)); // TODO: size id | |
| type->addIdOperand(makeUintConstant(NonSemanticShaderDebugInfoFlagIsPublic)); // flags id | |
| groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeMember].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeCompositeDebugType(std::vector<Id> const& memberTypes, std::vector<StructMemberDebugInfo> const& memberDebugInfo, | |
| char const* const name, NonSemanticShaderDebugInfoDebugCompositeType const tag) | |
| { | |
| // Create the debug member types. | |
| std::vector<Id> memberDebugTypes; | |
| assert(memberTypes.size() == memberDebugInfo.size()); | |
| for (size_t i = 0; i < memberTypes.size(); i++) { | |
| if (getDebugType(memberTypes[i]) != NoType) { | |
| memberDebugTypes.emplace_back(makeMemberDebugType(memberTypes[i], memberDebugInfo[i])); | |
| } | |
| } | |
| // Create The structure debug type. | |
| Instruction* type = new Instruction(getUniqueId(), makeVoidType(), Op::OpExtInst); | |
| type->reserveOperands(memberDebugTypes.size() + 11); | |
| type->addIdOperand(nonSemanticShaderDebugInfo); | |
| type->addImmediateOperand(NonSemanticShaderDebugInfoDebugTypeComposite); | |
| type->addIdOperand(getStringId(name)); // name id | |
| type->addIdOperand(makeUintConstant(tag)); // tag id | |
| type->addIdOperand(makeDebugSource(currentFileId)); // source id | |
| type->addIdOperand(makeUintConstant(currentLine)); // line id TODO: currentLine always zero? | |
| type->addIdOperand(makeUintConstant(0)); // TODO: column id | |
| type->addIdOperand(makeDebugCompilationUnit()); // scope id | |
| type->addIdOperand(getStringId(name)); // linkage name id | |
| type->addIdOperand(makeUintConstant(0)); // TODO: size id | |
| type->addIdOperand(makeUintConstant(NonSemanticShaderDebugInfoFlagIsPublic)); // flags id | |
| for(auto const memberDebugType : memberDebugTypes) { | |
| type->addIdOperand(memberDebugType); | |
| } | |
| groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeComposite].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return type->getResultId(); | |
| } | |
| // The NonSemantic Shader Debug Info doesn't really have a dedicated opcode for opaque types. Instead, we use DebugTypeComposite. | |
| // To represent a source language opaque type, this instruction must have no Members operands, Size operand must be | |
| // DebugInfoNone, and Name must start with @ to avoid clashes with user defined names. | |
| Id Builder::makeOpaqueDebugType(char const* const name) | |
| { | |
| // Create The structure debug type. | |
| Instruction* type = new Instruction(getUniqueId(), makeVoidType(), Op::OpExtInst); | |
| type->reserveOperands(11); | |
| type->addIdOperand(nonSemanticShaderDebugInfo); | |
| type->addImmediateOperand(NonSemanticShaderDebugInfoDebugTypeComposite); | |
| type->addIdOperand(getStringId(name)); // name id | |
| type->addIdOperand(makeUintConstant(NonSemanticShaderDebugInfoStructure)); // tag id | |
| type->addIdOperand(makeDebugSource(currentFileId)); // source id | |
| type->addIdOperand(makeUintConstant(currentLine)); // line id TODO: currentLine always zero? | |
| type->addIdOperand(makeUintConstant(0)); // TODO: column id | |
| type->addIdOperand(makeDebugCompilationUnit()); // scope id | |
| // Prepend '@' to opaque types. | |
| type->addIdOperand(getStringId('@' + std::string(name))); // linkage name id | |
| type->addIdOperand(makeDebugInfoNone()); // size id | |
| type->addIdOperand(makeUintConstant(NonSemanticShaderDebugInfoFlagIsPublic)); // flags id | |
| groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypeComposite].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return type->getResultId(); | |
| } | |
| Id Builder::makePointerDebugType(StorageClass storageClass, Id const baseType) | |
| { | |
| const Id debugBaseType = getDebugType(baseType); | |
| if (!debugBaseType) { | |
| return makeDebugInfoNone(); | |
| } | |
| const Id scID = makeUintConstant(storageClass); | |
| for (Instruction* otherType : groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypePointer]) { | |
| if (otherType->getIdOperand(2) == debugBaseType && | |
| otherType->getIdOperand(3) == scID) { | |
| return otherType->getResultId(); | |
| } | |
| } | |
| Instruction* type = new Instruction(getUniqueId(), makeVoidType(), Op::OpExtInst); | |
| type->reserveOperands(5); | |
| type->addIdOperand(nonSemanticShaderDebugInfo); | |
| type->addImmediateOperand(NonSemanticShaderDebugInfoDebugTypePointer); | |
| type->addIdOperand(debugBaseType); | |
| type->addIdOperand(scID); | |
| type->addIdOperand(makeUintConstant(0)); | |
| groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypePointer].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return type->getResultId(); | |
| } | |
| // Emit a OpExtInstWithForwardRefsKHR nonsemantic instruction for a pointer debug type | |
| // where we don't have the pointee yet. Since we don't have the pointee yet, it just | |
| // points to itself and we rely on patching it later. | |
| Id Builder::makeForwardPointerDebugType(StorageClass storageClass) | |
| { | |
| const Id scID = makeUintConstant(storageClass); | |
| this->addExtension(spv::E_SPV_KHR_relaxed_extended_instruction); | |
| Instruction *type = new Instruction(getUniqueId(), makeVoidType(), Op::OpExtInstWithForwardRefsKHR); | |
| type->addIdOperand(nonSemanticShaderDebugInfo); | |
| type->addImmediateOperand(NonSemanticShaderDebugInfoDebugTypePointer); | |
| type->addIdOperand(type->getResultId()); | |
| type->addIdOperand(scID); | |
| type->addIdOperand(makeUintConstant(0)); | |
| groupedDebugTypes[NonSemanticShaderDebugInfoDebugTypePointer].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeDebugSource(const Id fileName) { | |
| if (debugSourceId.find(fileName) != debugSourceId.end()) | |
| return debugSourceId[fileName]; | |
| spv::Id resultId = getUniqueId(); | |
| Instruction* sourceInst = new Instruction(resultId, makeVoidType(), Op::OpExtInst); | |
| sourceInst->reserveOperands(3); | |
| sourceInst->addIdOperand(nonSemanticShaderDebugInfo); | |
| sourceInst->addImmediateOperand(NonSemanticShaderDebugInfoDebugSource); | |
| sourceInst->addIdOperand(fileName); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(sourceInst)); | |
| module.mapInstruction(sourceInst); | |
| if (emitNonSemanticShaderDebugSource) { | |
| const int maxWordCount = 0xFFFF; | |
| const int opSourceWordCount = 4; | |
| const int nonNullBytesPerInstruction = 4 * (maxWordCount - opSourceWordCount) - 1; | |
| auto processDebugSource = [&](std::string source) { | |
| if (source.size() > 0) { | |
| int nextByte = 0; | |
| while ((int)source.size() - nextByte > 0) { | |
| auto subString = source.substr(nextByte, nonNullBytesPerInstruction); | |
| auto sourceId = getStringId(subString); | |
| if (nextByte == 0) { | |
| // DebugSource | |
| sourceInst->addIdOperand(sourceId); | |
| } else { | |
| // DebugSourceContinued | |
| Instruction* sourceContinuedInst = new Instruction(getUniqueId(), makeVoidType(), Op::OpExtInst); | |
| sourceContinuedInst->reserveOperands(2); | |
| sourceContinuedInst->addIdOperand(nonSemanticShaderDebugInfo); | |
| sourceContinuedInst->addImmediateOperand(NonSemanticShaderDebugInfoDebugSourceContinued); | |
| sourceContinuedInst->addIdOperand(sourceId); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(sourceContinuedInst)); | |
| module.mapInstruction(sourceContinuedInst); | |
| } | |
| nextByte += nonNullBytesPerInstruction; | |
| } | |
| } else { | |
| auto sourceId = getStringId(source); | |
| sourceInst->addIdOperand(sourceId); | |
| } | |
| }; | |
| if (fileName == mainFileId) { | |
| processDebugSource(sourceText); | |
| } else { | |
| auto incItr = includeFiles.find(fileName); | |
| if (incItr != includeFiles.end()) { | |
| processDebugSource(*incItr->second); | |
| } else { | |
| // We omit the optional source text item if not available in glslang | |
| } | |
| } | |
| } | |
| debugSourceId[fileName] = resultId; | |
| return resultId; | |
| } | |
| Id Builder::makeDebugCompilationUnit() { | |
| if (nonSemanticShaderCompilationUnitId != 0) | |
| return nonSemanticShaderCompilationUnitId; | |
| spv::Id resultId = getUniqueId(); | |
| Instruction* sourceInst = new Instruction(resultId, makeVoidType(), Op::OpExtInst); | |
| sourceInst->reserveOperands(6); | |
| sourceInst->addIdOperand(nonSemanticShaderDebugInfo); | |
| sourceInst->addImmediateOperand(NonSemanticShaderDebugInfoDebugCompilationUnit); | |
| sourceInst->addIdOperand(makeUintConstant(1)); // TODO(greg-lunarg): Get rid of magic number | |
| sourceInst->addIdOperand(makeUintConstant(4)); // TODO(greg-lunarg): Get rid of magic number | |
| sourceInst->addIdOperand(makeDebugSource(mainFileId)); | |
| sourceInst->addIdOperand(makeUintConstant(sourceLang)); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(sourceInst)); | |
| module.mapInstruction(sourceInst); | |
| nonSemanticShaderCompilationUnitId = resultId; | |
| // In the case of non-semantic shader debug info, preserve source text for every include | |
| // even if no debug scope or line record ends up referencing that file. | |
| if (emitNonSemanticShaderDebugSource) { | |
| for (const auto& includeFile : includeFiles) { | |
| makeDebugSource(includeFile.first); | |
| } | |
| } | |
| // We can reasonably assume that makeDebugCompilationUnit will be called before any of | |
| // debug-scope stack. Function scopes and lexical scopes will occur afterward. | |
| assert(currentDebugScopeId.empty()); | |
| currentDebugScopeId.push(nonSemanticShaderCompilationUnitId); | |
| return resultId; | |
| } | |
| Id Builder::createDebugGlobalVariable(Id const type, char const*const name, Id const variable) | |
| { | |
| assert(type != 0); | |
| Instruction* inst = new Instruction(getUniqueId(), makeVoidType(), Op::OpExtInst); | |
| inst->reserveOperands(11); | |
| inst->addIdOperand(nonSemanticShaderDebugInfo); | |
| inst->addImmediateOperand(NonSemanticShaderDebugInfoDebugGlobalVariable); | |
| inst->addIdOperand(getStringId(name)); // name id | |
| inst->addIdOperand(type); // type id | |
| inst->addIdOperand(makeDebugSource(currentFileId)); // source id | |
| inst->addIdOperand(makeUintConstant(currentLine)); // line id TODO: currentLine always zero? | |
| inst->addIdOperand(makeUintConstant(0)); // TODO: column id | |
| inst->addIdOperand(makeDebugCompilationUnit()); // scope id | |
| inst->addIdOperand(getStringId(name)); // linkage name id | |
| inst->addIdOperand(variable); // variable id | |
| inst->addIdOperand(makeUintConstant(NonSemanticShaderDebugInfoFlagIsDefinition)); // flags id | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(inst)); | |
| module.mapInstruction(inst); | |
| return inst->getResultId(); | |
| } | |
| Id Builder::createDebugLocalVariable(Id type, char const*const name, size_t const argNumber) | |
| { | |
| assert(name != nullptr); | |
| assert(!currentDebugScopeId.empty()); | |
| Instruction* inst = new Instruction(getUniqueId(), makeVoidType(), Op::OpExtInst); | |
| inst->reserveOperands(9); | |
| inst->addIdOperand(nonSemanticShaderDebugInfo); | |
| inst->addImmediateOperand(NonSemanticShaderDebugInfoDebugLocalVariable); | |
| inst->addIdOperand(getStringId(name)); // name id | |
| inst->addIdOperand(type); // type id | |
| inst->addIdOperand(makeDebugSource(currentFileId)); // source id | |
| inst->addIdOperand(makeUintConstant(currentLine)); // line id | |
| inst->addIdOperand(makeUintConstant(0)); // TODO: column id | |
| inst->addIdOperand(currentDebugScopeId.top()); // scope id | |
| inst->addIdOperand(makeUintConstant(NonSemanticShaderDebugInfoFlagIsLocal)); // flags id | |
| if(argNumber != 0) { | |
| inst->addIdOperand(makeUintConstant(static_cast<unsigned int>(argNumber))); | |
| } | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(inst)); | |
| module.mapInstruction(inst); | |
| return inst->getResultId(); | |
| } | |
| Id Builder::makeDebugExpression() | |
| { | |
| if (debugExpression != 0) | |
| return debugExpression; | |
| Instruction* inst = new Instruction(getUniqueId(), makeVoidType(), Op::OpExtInst); | |
| inst->reserveOperands(2); | |
| inst->addIdOperand(nonSemanticShaderDebugInfo); | |
| inst->addImmediateOperand(NonSemanticShaderDebugInfoDebugExpression); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(inst)); | |
| module.mapInstruction(inst); | |
| debugExpression = inst->getResultId(); | |
| return debugExpression; | |
| } | |
| Id Builder::makeDebugDeclare(Id const debugLocalVariable, Id const pointer) | |
| { | |
| Instruction* inst = new Instruction(getUniqueId(), makeVoidType(), Op::OpExtInst); | |
| inst->reserveOperands(5); | |
| inst->addIdOperand(nonSemanticShaderDebugInfo); | |
| inst->addImmediateOperand(NonSemanticShaderDebugInfoDebugDeclare); | |
| inst->addIdOperand(debugLocalVariable); // debug local variable id | |
| inst->addIdOperand(pointer); // pointer to local variable id | |
| inst->addIdOperand(makeDebugExpression()); // expression id | |
| addInstruction(std::unique_ptr<Instruction>(inst)); | |
| return inst->getResultId(); | |
| } | |
| Id Builder::makeDebugValue(Id const debugLocalVariable, Id const value) | |
| { | |
| Instruction* inst = new Instruction(getUniqueId(), makeVoidType(), Op::OpExtInst); | |
| inst->reserveOperands(5); | |
| inst->addIdOperand(nonSemanticShaderDebugInfo); | |
| inst->addImmediateOperand(NonSemanticShaderDebugInfoDebugValue); | |
| inst->addIdOperand(debugLocalVariable); // debug local variable id | |
| inst->addIdOperand(value); // value of local variable id | |
| inst->addIdOperand(makeDebugExpression()); // expression id | |
| addInstruction(std::unique_ptr<Instruction>(inst)); | |
| return inst->getResultId(); | |
| } | |
| Id Builder::makeAccelerationStructureType() | |
| { | |
| Instruction *type; | |
| if (groupedTypes[enumCast(Op::OpTypeAccelerationStructureKHR)].size() == 0) { | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypeAccelerationStructureKHR); | |
| groupedTypes[enumCast(Op::OpTypeAccelerationStructureKHR)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| if (emitNonSemanticShaderDebugInfo) { | |
| spv::Id debugType = makeOpaqueDebugType("accelerationStructure"); | |
| debugTypeIdLookup[type->getResultId()] = debugType; | |
| } | |
| } else { | |
| type = groupedTypes[enumCast(Op::OpTypeAccelerationStructureKHR)].back(); | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeRayQueryType() | |
| { | |
| Instruction *type; | |
| if (groupedTypes[enumCast(Op::OpTypeRayQueryKHR)].size() == 0) { | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypeRayQueryKHR); | |
| groupedTypes[enumCast(Op::OpTypeRayQueryKHR)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| if (emitNonSemanticShaderDebugInfo) { | |
| spv::Id debugType = makeOpaqueDebugType("rayQuery"); | |
| debugTypeIdLookup[type->getResultId()] = debugType; | |
| } | |
| } else { | |
| type = groupedTypes[enumCast(Op::OpTypeRayQueryKHR)].back(); | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeHitObjectEXTType() | |
| { | |
| Instruction *type; | |
| if (groupedTypes[enumCast(Op::OpTypeHitObjectEXT)].size() == 0) { | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypeHitObjectEXT); | |
| groupedTypes[enumCast(Op::OpTypeHitObjectEXT)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| } else { | |
| type = groupedTypes[enumCast(Op::OpTypeHitObjectEXT)].back(); | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeHitObjectNVType() | |
| { | |
| Instruction *type; | |
| if (groupedTypes[enumCast(Op::OpTypeHitObjectNV)].size() == 0) { | |
| type = new Instruction(getUniqueId(), NoType, Op::OpTypeHitObjectNV); | |
| groupedTypes[enumCast(Op::OpTypeHitObjectNV)].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| if (emitNonSemanticShaderDebugInfo) { | |
| spv::Id debugType = makeOpaqueDebugType("hitObjectNV"); | |
| debugTypeIdLookup[type->getResultId()] = debugType; | |
| } | |
| } else { | |
| type = groupedTypes[enumCast(Op::OpTypeHitObjectNV)].back(); | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::getDerefTypeId(Id resultId) const | |
| { | |
| Id typeId = getTypeId(resultId); | |
| assert(isPointerType(typeId)); | |
| return module.getInstruction(typeId)->getIdOperand(1); | |
| } | |
| Op Builder::getMostBasicTypeClass(Id typeId) const | |
| { | |
| Instruction* instr = module.getInstruction(typeId); | |
| Op typeClass = instr->getOpCode(); | |
| switch (typeClass) | |
| { | |
| case Op::OpTypeVector: | |
| case Op::OpTypeMatrix: | |
| case Op::OpTypeArray: | |
| case Op::OpTypeRuntimeArray: | |
| return getMostBasicTypeClass(instr->getIdOperand(0)); | |
| case Op::OpTypePointer: | |
| return getMostBasicTypeClass(instr->getIdOperand(1)); | |
| default: | |
| return typeClass; | |
| } | |
| } | |
| unsigned int Builder::getNumTypeConstituents(Id typeId) const | |
| { | |
| Instruction* instr = module.getInstruction(typeId); | |
| switch (instr->getOpCode()) | |
| { | |
| case Op::OpTypeBool: | |
| case Op::OpTypeInt: | |
| case Op::OpTypeFloat: | |
| case Op::OpTypePointer: | |
| return 1; | |
| case Op::OpTypeVector: | |
| case Op::OpTypeMatrix: | |
| return instr->getImmediateOperand(1); | |
| case Op::OpTypeCooperativeVectorNV: | |
| case Op::OpTypeArray: | |
| { | |
| Id lengthId = instr->getIdOperand(1); | |
| return module.getInstruction(lengthId)->getImmediateOperand(0); | |
| } | |
| case Op::OpTypeStruct: | |
| return instr->getNumOperands(); | |
| case Op::OpTypeCooperativeMatrixKHR: | |
| case Op::OpTypeCooperativeMatrixNV: | |
| // has only one constituent when used with OpCompositeConstruct. | |
| return 1; | |
| default: | |
| assert(0); | |
| return 1; | |
| } | |
| } | |
| // Return the lowest-level type of scalar that an homogeneous composite is made out of. | |
| // Typically, this is just to find out if something is made out of ints or floats. | |
| // However, it includes returning a structure, if say, it is an array of structure. | |
| Id Builder::getScalarTypeId(Id typeId) const | |
| { | |
| Instruction* instr = module.getInstruction(typeId); | |
| Op typeClass = instr->getOpCode(); | |
| switch (typeClass) | |
| { | |
| case Op::OpTypeVoid: | |
| case Op::OpTypeBool: | |
| case Op::OpTypeInt: | |
| case Op::OpTypeFloat: | |
| case Op::OpTypeStruct: | |
| return instr->getResultId(); | |
| case Op::OpTypeVector: | |
| case Op::OpTypeMatrix: | |
| case Op::OpTypeArray: | |
| case Op::OpTypeRuntimeArray: | |
| case Op::OpTypePointer: | |
| case Op::OpTypeCooperativeVectorNV: | |
| return getScalarTypeId(getContainedTypeId(typeId)); | |
| default: | |
| assert(0); | |
| return NoResult; | |
| } | |
| } | |
| // Return the type of 'member' of a composite. | |
| Id Builder::getContainedTypeId(Id typeId, int member) const | |
| { | |
| Instruction* instr = module.getInstruction(typeId); | |
| Op typeClass = instr->getOpCode(); | |
| switch (typeClass) | |
| { | |
| case Op::OpTypeVector: | |
| case Op::OpTypeMatrix: | |
| case Op::OpTypeArray: | |
| case Op::OpTypeRuntimeArray: | |
| case Op::OpTypeCooperativeMatrixKHR: | |
| case Op::OpTypeCooperativeMatrixNV: | |
| case Op::OpTypeCooperativeVectorNV: | |
| return instr->getIdOperand(0); | |
| case Op::OpTypePointer: | |
| return instr->getIdOperand(1); | |
| case Op::OpTypeStruct: | |
| return instr->getIdOperand(member); | |
| default: | |
| assert(0); | |
| return NoResult; | |
| } | |
| } | |
| // Figure out the final resulting type of the access chain. | |
| Id Builder::getResultingAccessChainType() const | |
| { | |
| assert(accessChain.base != NoResult); | |
| Id typeId = getTypeId(accessChain.base); | |
| assert(isPointerType(typeId)); | |
| typeId = getContainedTypeId(typeId); | |
| for (int i = 0; i < (int)accessChain.indexChain.size(); ++i) { | |
| if (isStructType(typeId)) { | |
| assert(isConstantScalar(accessChain.indexChain[i])); | |
| typeId = getContainedTypeId(typeId, getConstantScalar(accessChain.indexChain[i])); | |
| } else | |
| typeId = getContainedTypeId(typeId, accessChain.indexChain[i]); | |
| } | |
| return typeId; | |
| } | |
| // Return the immediately contained type of a given composite type. | |
| Id Builder::getContainedTypeId(Id typeId) const | |
| { | |
| return getContainedTypeId(typeId, 0); | |
| } | |
| // Returns true if 'typeId' is or contains a scalar type declared with 'typeOp' | |
| // of width 'width'. The 'width' is only consumed for int and float types. | |
| // Returns false otherwise. | |
| bool Builder::containsType(Id typeId, spv::Op typeOp, unsigned int width) const | |
| { | |
| const Instruction& instr = *module.getInstruction(typeId); | |
| Op typeClass = instr.getOpCode(); | |
| switch (typeClass) | |
| { | |
| case Op::OpTypeInt: | |
| case Op::OpTypeFloat: | |
| return typeClass == typeOp && instr.getImmediateOperand(0) == width; | |
| case Op::OpTypeStruct: | |
| for (int m = 0; m < instr.getNumOperands(); ++m) { | |
| if (containsType(instr.getIdOperand(m), typeOp, width)) | |
| return true; | |
| } | |
| return false; | |
| case Op::OpTypePointer: | |
| return false; | |
| case Op::OpTypeVector: | |
| case Op::OpTypeMatrix: | |
| case Op::OpTypeArray: | |
| case Op::OpTypeRuntimeArray: | |
| return containsType(getContainedTypeId(typeId), typeOp, width); | |
| default: | |
| return typeClass == typeOp; | |
| } | |
| } | |
| // return true if the type is a pointer to PhysicalStorageBufferEXT or an | |
| // contains such a pointer. These require restrict/aliased decorations. | |
| bool Builder::containsPhysicalStorageBufferOrArray(Id typeId) const | |
| { | |
| const Instruction& instr = *module.getInstruction(typeId); | |
| Op typeClass = instr.getOpCode(); | |
| switch (typeClass) | |
| { | |
| case Op::OpTypePointer: | |
| return getTypeStorageClass(typeId) == StorageClass::PhysicalStorageBufferEXT; | |
| case Op::OpTypeArray: | |
| return containsPhysicalStorageBufferOrArray(getContainedTypeId(typeId)); | |
| case Op::OpTypeStruct: | |
| for (int m = 0; m < instr.getNumOperands(); ++m) { | |
| if (containsPhysicalStorageBufferOrArray(instr.getIdOperand(m))) | |
| return true; | |
| } | |
| return false; | |
| default: | |
| return false; | |
| } | |
| } | |
| // See if a scalar constant of this type has already been created, so it | |
| // can be reused rather than duplicated. (Required by the specification). | |
| Id Builder::findScalarConstant(Op typeClass, Op opcode, Id typeId, unsigned value) | |
| { | |
| ScalarConstantKey key{ enumCast(typeClass), enumCast(opcode), typeId, value, 0 }; | |
| auto it = groupedScalarConstantResultIDs.find(key); | |
| return (it != groupedScalarConstantResultIDs.end()) ? it->second : 0; | |
| } | |
| // Version of findScalarConstant (see above) for scalars that take two operands (e.g. a 'double' or 'int64'). | |
| Id Builder::findScalarConstant(Op typeClass, Op opcode, Id typeId, unsigned v1, unsigned v2) | |
| { | |
| ScalarConstantKey key{ enumCast(typeClass), enumCast(opcode), typeId, v1, v2 }; | |
| auto it = groupedScalarConstantResultIDs.find(key); | |
| return (it != groupedScalarConstantResultIDs.end()) ? it->second : 0; | |
| } | |
| // Return true if consuming 'opcode' means consuming a constant. | |
| // "constant" here means after final transform to executable code, | |
| // the value consumed will be a constant, so includes specialization. | |
| bool Builder::isConstantOpCode(Op opcode) const | |
| { | |
| switch (opcode) { | |
| case Op::OpUndef: | |
| case Op::OpConstantTrue: | |
| case Op::OpConstantFalse: | |
| case Op::OpConstant: | |
| case Op::OpConstantDataKHR: | |
| case Op::OpConstantComposite: | |
| case Op::OpConstantCompositeReplicateEXT: | |
| case Op::OpConstantSampler: | |
| case Op::OpConstantNull: | |
| case Op::OpSpecConstantTrue: | |
| case Op::OpSpecConstantFalse: | |
| case Op::OpSpecConstant: | |
| case Op::OpSpecConstantComposite: | |
| case Op::OpSpecConstantCompositeReplicateEXT: | |
| case Op::OpSpecConstantDataKHR: | |
| case Op::OpSpecConstantOp: | |
| case Op::OpConstantSizeOfEXT: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| // Return true if consuming 'opcode' means consuming a specialization constant. | |
| bool Builder::isSpecConstantOpCode(Op opcode) const | |
| { | |
| switch (opcode) { | |
| case Op::OpSpecConstantTrue: | |
| case Op::OpSpecConstantFalse: | |
| case Op::OpSpecConstant: | |
| case Op::OpSpecConstantComposite: | |
| case Op::OpSpecConstantDataKHR: | |
| case Op::OpSpecConstantOp: | |
| case Op::OpSpecConstantCompositeReplicateEXT: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| Id Builder::makeNullConstant(Id typeId) | |
| { | |
| Instruction* constant; | |
| // See if we already made it. | |
| Id existing = NoResult; | |
| for (int i = 0; i < (int)nullConstants.size(); ++i) { | |
| constant = nullConstants[i]; | |
| if (constant->getTypeId() == typeId) | |
| existing = constant->getResultId(); | |
| } | |
| if (existing != NoResult) | |
| return existing; | |
| // Make it | |
| Instruction* c = new Instruction(getUniqueId(), typeId, Op::OpConstantNull); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(c)); | |
| nullConstants.push_back(c); | |
| module.mapInstruction(c); | |
| return c->getResultId(); | |
| } | |
| Id Builder::makeBoolConstant(bool b, bool specConstant) | |
| { | |
| Id typeId = makeBoolType(); | |
| Op opcode = specConstant ? (b ? Op::OpSpecConstantTrue : Op::OpSpecConstantFalse) : (b ? Op::OpConstantTrue : Op::OpConstantFalse); | |
| // See if we already made it. Applies only to regular constants, because specialization constants | |
| // must remain distinct for the purpose of applying a SpecId decoration. | |
| if (!specConstant) { | |
| Id existing = findScalarConstant(Op::OpTypeBool, opcode, typeId, 0); | |
| if (existing) | |
| return existing; | |
| } | |
| // Make it | |
| Instruction* c = new Instruction(getUniqueId(), typeId, opcode); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(c)); | |
| module.mapInstruction(c); | |
| Id resultId = c->getResultId(); | |
| if (!specConstant) { | |
| ScalarConstantKey key{enumCast(Op::OpTypeBool), enumCast(opcode), typeId, 0, 0}; | |
| groupedScalarConstantResultIDs[key] = resultId; | |
| } | |
| return resultId; | |
| } | |
| Id Builder::makeIntConstant(Id typeId, unsigned value, bool specConstant) | |
| { | |
| Op opcode = specConstant ? Op::OpSpecConstant : Op::OpConstant; | |
| // See if we already made it. Applies only to regular constants, because specialization constants | |
| // must remain distinct for the purpose of applying a SpecId decoration. | |
| if (! specConstant) { | |
| Id existing = findScalarConstant(Op::OpTypeInt, opcode, typeId, value); | |
| if (existing) | |
| return existing; | |
| } | |
| Instruction* c = new Instruction(getUniqueId(), typeId, opcode); | |
| c->addImmediateOperand(value); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(c)); | |
| module.mapInstruction(c); | |
| Id resultId = c->getResultId(); | |
| if (!specConstant) { | |
| ScalarConstantKey key{ enumCast(Op::OpTypeInt), enumCast(opcode), typeId, value, 0 }; | |
| groupedScalarConstantResultIDs[key] = resultId; | |
| } | |
| return resultId; | |
| } | |
| Id Builder::makeInt64Constant(Id typeId, unsigned long long value, bool specConstant) | |
| { | |
| Op opcode = specConstant ? Op::OpSpecConstant : Op::OpConstant; | |
| unsigned op1 = value & 0xFFFFFFFF; | |
| unsigned op2 = value >> 32; | |
| // See if we already made it. Applies only to regular constants, because specialization constants | |
| // must remain distinct for the purpose of applying a SpecId decoration. | |
| if (! specConstant) { | |
| Id existing = findScalarConstant(Op::OpTypeInt, opcode, typeId, op1, op2); | |
| if (existing) | |
| return existing; | |
| } | |
| Instruction* c = new Instruction(getUniqueId(), typeId, opcode); | |
| c->reserveOperands(2); | |
| c->addImmediateOperand(op1); | |
| c->addImmediateOperand(op2); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(c)); | |
| module.mapInstruction(c); | |
| Id resultId = c->getResultId(); | |
| if (!specConstant) { | |
| ScalarConstantKey key{ enumCast(Op::OpTypeInt), enumCast(opcode), typeId, op1, op2 }; | |
| groupedScalarConstantResultIDs[key] = resultId; | |
| } | |
| return resultId; | |
| } | |
| Id Builder::makeFloatConstant(float f, bool specConstant) | |
| { | |
| Op opcode = specConstant ? Op::OpSpecConstant : Op::OpConstant; | |
| Id typeId = makeFloatType(32); | |
| union { float fl; unsigned int ui; } u; | |
| u.fl = f; | |
| unsigned value = u.ui; | |
| // See if we already made it. Applies only to regular constants, because specialization constants | |
| // must remain distinct for the purpose of applying a SpecId decoration. | |
| if (! specConstant) { | |
| Id existing = findScalarConstant(Op::OpTypeFloat, opcode, typeId, value); | |
| if (existing) | |
| return existing; | |
| } | |
| Instruction* c = new Instruction(getUniqueId(), typeId, opcode); | |
| c->addImmediateOperand(value); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(c)); | |
| module.mapInstruction(c); | |
| Id resultId = c->getResultId(); | |
| if (!specConstant) { | |
| ScalarConstantKey key{ enumCast(Op::OpTypeFloat), enumCast(opcode), typeId, value, 0 }; | |
| groupedScalarConstantResultIDs[key] = resultId; | |
| } | |
| return resultId; | |
| } | |
| Id Builder::makeDoubleConstant(double d, bool specConstant) | |
| { | |
| Op opcode = specConstant ? Op::OpSpecConstant : Op::OpConstant; | |
| Id typeId = makeFloatType(64); | |
| union { double db; unsigned long long ull; } u; | |
| u.db = d; | |
| unsigned long long value = u.ull; | |
| unsigned op1 = value & 0xFFFFFFFF; | |
| unsigned op2 = value >> 32; | |
| // See if we already made it. Applies only to regular constants, because specialization constants | |
| // must remain distinct for the purpose of applying a SpecId decoration. | |
| if (! specConstant) { | |
| Id existing = findScalarConstant(Op::OpTypeFloat, opcode, typeId, op1, op2); | |
| if (existing) | |
| return existing; | |
| } | |
| Instruction* c = new Instruction(getUniqueId(), typeId, opcode); | |
| c->reserveOperands(2); | |
| c->addImmediateOperand(op1); | |
| c->addImmediateOperand(op2); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(c)); | |
| module.mapInstruction(c); | |
| Id resultId = c->getResultId(); | |
| if (!specConstant) { | |
| ScalarConstantKey key{ enumCast(Op::OpTypeFloat), enumCast(opcode), typeId, op1, op2 }; | |
| groupedScalarConstantResultIDs[key] = resultId; | |
| } | |
| return resultId; | |
| } | |
| Id Builder::makeFloat16Constant(float f16, bool specConstant) | |
| { | |
| Op opcode = specConstant ? Op::OpSpecConstant : Op::OpConstant; | |
| Id typeId = makeFloatType(16); | |
| spvutils::HexFloat<spvutils::FloatProxy<float>> fVal(f16); | |
| spvutils::HexFloat<spvutils::FloatProxy<spvutils::Float16>> f16Val(0); | |
| fVal.castTo(f16Val, spvutils::kRoundToZero); | |
| unsigned value = f16Val.value().getAsFloat().get_value(); | |
| // See if we already made it. Applies only to regular constants, because specialization constants | |
| // must remain distinct for the purpose of applying a SpecId decoration. | |
| if (!specConstant) { | |
| Id existing = findScalarConstant(Op::OpTypeFloat, opcode, typeId, value); | |
| if (existing) | |
| return existing; | |
| } | |
| Instruction* c = new Instruction(getUniqueId(), typeId, opcode); | |
| c->addImmediateOperand(value); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(c)); | |
| module.mapInstruction(c); | |
| Id resultId = c->getResultId(); | |
| if (!specConstant) { | |
| ScalarConstantKey key{ enumCast(Op::OpTypeFloat), enumCast(opcode), typeId, value, 0 }; | |
| groupedScalarConstantResultIDs[key] = resultId; | |
| } | |
| return resultId; | |
| } | |
| Id Builder::makeBFloat16Constant(float bf16, bool specConstant) | |
| { | |
| Op opcode = specConstant ? Op::OpSpecConstant : Op::OpConstant; | |
| Id typeId = makeBFloat16Type(); | |
| union { | |
| float f; | |
| uint32_t u; | |
| } un; | |
| un.f = bf16; | |
| // take high 16b of fp32 value. This is effectively round-to-zero, other than certain NaNs. | |
| unsigned value = un.u >> 16; | |
| // See if we already made it. Applies only to regular constants, because specialization constants | |
| // must remain distinct for the purpose of applying a SpecId decoration. | |
| if (!specConstant) { | |
| Id existing = findScalarConstant(Op::OpTypeFloat, opcode, typeId, value); | |
| if (existing) | |
| return existing; | |
| } | |
| Instruction* c = new Instruction(getUniqueId(), typeId, opcode); | |
| c->addImmediateOperand(value); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(c)); | |
| module.mapInstruction(c); | |
| Id resultId = c->getResultId(); | |
| if (!specConstant) { | |
| ScalarConstantKey key{ enumCast(Op::OpTypeFloat), enumCast(opcode), typeId, value, 0 }; | |
| groupedScalarConstantResultIDs[key] = resultId; | |
| } | |
| return resultId; | |
| } | |
| template <typename SpvUtilsType> | |
| Id Builder::makeFloatConstantHelper(float f, Id typeId, bool specConstant) | |
| { | |
| Op opcode = specConstant ? Op::OpSpecConstant : Op::OpConstant; | |
| spvutils::HexFloat<spvutils::FloatProxy<float>> fVal(f); | |
| spvutils::HexFloat<spvutils::FloatProxy<SpvUtilsType>> smallFloatVal(0); | |
| fVal.castTo(smallFloatVal, spvutils::kRoundToZero); | |
| unsigned value = smallFloatVal.value().getAsFloat().get_value(); | |
| // See if we already made it. Applies only to regular constants, because specialization constants | |
| // must remain distinct for the purpose of applying a SpecId decoration. | |
| if (!specConstant) { | |
| Id existing = findScalarConstant(Op::OpTypeFloat, opcode, typeId, value); | |
| if (existing) | |
| return existing; | |
| } | |
| Instruction* c = new Instruction(getUniqueId(), typeId, opcode); | |
| c->addImmediateOperand(value); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(c)); | |
| module.mapInstruction(c); | |
| Id resultId = c->getResultId(); | |
| if (!specConstant) { | |
| ScalarConstantKey key{enumCast(Op::OpTypeFloat), enumCast(opcode), typeId, value, 0}; | |
| groupedScalarConstantResultIDs[key] = resultId; | |
| } | |
| return resultId; | |
| } | |
| Id Builder::makeFloatE5M2Constant(float fe5m2, bool specConstant) | |
| { | |
| return makeFloatConstantHelper<spvutils::FloatE5M2>(fe5m2, makeFloatE5M2Type(), specConstant); | |
| } | |
| Id Builder::makeFloatE4M3Constant(float fe4m3, bool specConstant) | |
| { | |
| return makeFloatConstantHelper<spvutils::FloatE4M3>(fe4m3, makeFloatE4M3Type(), specConstant); | |
| } | |
| Id Builder::makeFloatE2M1Constant(float fe2m1, bool specConstant) | |
| { | |
| return makeFloatConstantHelper<spvutils::FloatE2M1>(fe2m1, makeFloatE2M1Type(), specConstant); | |
| } | |
| Id Builder::makeFloatE3M2Constant(float fe3m2, bool specConstant) | |
| { | |
| return makeFloatConstantHelper<spvutils::FloatE3M2>(fe3m2, makeFloatE3M2Type(), specConstant); | |
| } | |
| Id Builder::makeFloatE2M3Constant(float fe2m3, bool specConstant) | |
| { | |
| return makeFloatConstantHelper<spvutils::FloatE2M3>(fe2m3, makeFloatE2M3Type(), specConstant); | |
| } | |
| Id Builder::makeFloatUE8M0Constant(float fue8m0, bool specConstant) | |
| { | |
| Op opcode = specConstant ? Op::OpSpecConstant : Op::OpConstant; | |
| Id typeId = makeFloatUE8M0Type(); | |
| fue8m0 = std::max(fue8m0, 0.f); | |
| union { | |
| float f; | |
| uint32_t u; | |
| } un; | |
| un.f = fue8m0; | |
| // extract the exponent, this effectively rounds to zero. | |
| uint32_t value = un.u >> 23; | |
| // See if we already made it. Applies only to regular constants, because specialization constants | |
| // must remain distinct for the purpose of applying a SpecId decoration. | |
| if (!specConstant) { | |
| Id existing = findScalarConstant(Op::OpTypeFloat, opcode, typeId, value); | |
| if (existing) | |
| return existing; | |
| } | |
| Instruction* c = new Instruction(getUniqueId(), typeId, opcode); | |
| c->addImmediateOperand(value); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(c)); | |
| module.mapInstruction(c); | |
| Id resultId = c->getResultId(); | |
| if (!specConstant) { | |
| ScalarConstantKey key{ enumCast(Op::OpTypeFloat), enumCast(opcode), typeId, value, 0 }; | |
| groupedScalarConstantResultIDs[key] = resultId; | |
| } | |
| return resultId; | |
| } | |
| Id Builder::makeFloatMXINT8Constant(float mxint8, bool specConstant) | |
| { | |
| Op opcode = specConstant ? Op::OpSpecConstant : Op::OpConstant; | |
| Id typeId = makeFloatMXINT8Type(); | |
| // clamp and convert to fixed point. | |
| mxint8 = std::min(mxint8, 127.f/64.f); | |
| mxint8 = std::max(mxint8, -127.f/64.f); | |
| mxint8 *= 64; | |
| mxint8 = roundf(mxint8); | |
| uint32_t value = ((int32_t)mxint8) & 0xFF; | |
| // See if we already made it. Applies only to regular constants, because specialization constants | |
| // must remain distinct for the purpose of applying a SpecId decoration. | |
| if (!specConstant) { | |
| Id existing = findScalarConstant(Op::OpTypeFloat, opcode, typeId, value); | |
| if (existing) | |
| return existing; | |
| } | |
| Instruction* c = new Instruction(getUniqueId(), typeId, opcode); | |
| c->addImmediateOperand(value); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(c)); | |
| module.mapInstruction(c); | |
| Id resultId = c->getResultId(); | |
| if (!specConstant) { | |
| ScalarConstantKey key{ enumCast(Op::OpTypeFloat), enumCast(opcode), typeId, value, 0 }; | |
| groupedScalarConstantResultIDs[key] = resultId; | |
| } | |
| return resultId; | |
| } | |
| Id Builder::makeFpConstant(Id type, double d, bool specConstant) | |
| { | |
| const int width = getScalarTypeWidth(type); | |
| assert(isFloatType(type)); | |
| switch (width) { | |
| case 16: | |
| return makeFloat16Constant((float)d, specConstant); | |
| case 32: | |
| return makeFloatConstant((float)d, specConstant); | |
| case 64: | |
| return makeDoubleConstant(d, specConstant); | |
| default: | |
| break; | |
| } | |
| assert(false); | |
| return NoResult; | |
| } | |
| Id Builder::importNonSemanticShaderDebugInfoInstructions() | |
| { | |
| assert(emitNonSemanticShaderDebugInfo == true); | |
| if(nonSemanticShaderDebugInfo == 0) | |
| { | |
| this->addExtension(spv::E_SPV_KHR_non_semantic_info); | |
| // Create the import instruction directly so we can save a pointer for later | |
| // patching by requireNonSemanticShaderDebugInfoVersion(). | |
| std::string importName = "NonSemantic.Shader.DebugInfo." + std::to_string(nonSemanticShaderDebugInfoVersion); | |
| auto* importInst = new Instruction(getUniqueId(), NoType, Op::OpExtInstImport); | |
| importInst->addStringOperand(importName.c_str()); | |
| module.mapInstruction(importInst); | |
| imports.push_back(std::unique_ptr<Instruction>(importInst)); | |
| nonSemanticShaderDebugInfo = importInst->getResultId(); | |
| nonSemanticShaderDebugInfoImportInst = importInst; | |
| } | |
| return nonSemanticShaderDebugInfo; | |
| } | |
| void Builder::requireNonSemanticShaderDebugInfoVersion(unsigned version) | |
| { | |
| if (nonSemanticShaderDebugInfoVersion >= version) | |
| return; | |
| nonSemanticShaderDebugInfoVersion = version; | |
| if (nonSemanticShaderDebugInfoImportInst != nullptr) { | |
| // The import instruction was already emitted with an older version string. | |
| // Rebuild the string operands in place so all referencing OpExtInst instructions | |
| // automatically pick up the new name without needing new IDs. | |
| std::string importName = "NonSemantic.Shader.DebugInfo." + std::to_string(version); | |
| nonSemanticShaderDebugInfoImportInst->clearOperands(); | |
| nonSemanticShaderDebugInfoImportInst->addStringOperand(importName.c_str()); | |
| } | |
| } | |
| Id Builder::findCompositeConstant(Op typeClass, Op opcode, Id typeId, const std::vector<Id>& comps, size_t numMembers) | |
| { | |
| auto it = groupedCompositeConstants.find( | |
| CompositeConstantKey(typeClass, opcode, typeId, comps, numMembers)); | |
| if (it != groupedCompositeConstants.end()) { | |
| const Instruction* constant = (*it).getInstruction(); | |
| return constant->getResultId(); | |
| } | |
| return NoResult; | |
| } | |
| Id Builder::findStructConstant(Id typeId, const std::vector<Id>& comps, size_t numMembers) | |
| { | |
| auto it = groupedStructConstants.find(StructConstantKey(typeId, comps, numMembers)); | |
| if (it != groupedStructConstants.end()) { | |
| const Instruction* constant = (*it).getInstruction(); | |
| return constant->getResultId(); | |
| } | |
| return NoResult; | |
| } | |
| // Comments in header | |
| Id Builder::makeCompositeConstant(Id typeId, const std::vector<Id>& members, bool specConstant) | |
| { | |
| assert(typeId); | |
| Op typeClass = getTypeClass(typeId); | |
| bool replicate = false; | |
| size_t numMembers = members.size(); | |
| if (useReplicatedComposites || typeClass == Op::OpTypeCooperativeVectorNV) { | |
| // use replicate if all members are the same | |
| replicate = numMembers > 0 && | |
| std::equal(members.begin() + 1, members.end(), members.begin()); | |
| if (replicate) { | |
| numMembers = 1; | |
| addCapability(spv::Capability::ReplicatedCompositesEXT); | |
| addExtension(spv::E_SPV_EXT_replicated_composites); | |
| } | |
| } | |
| Op opcode = replicate ? | |
| (specConstant ? Op::OpSpecConstantCompositeReplicateEXT : Op::OpConstantCompositeReplicateEXT) : | |
| (specConstant ? Op::OpSpecConstantComposite : Op::OpConstantComposite); | |
| switch (typeClass) { | |
| case Op::OpTypeVector: | |
| case Op::OpTypeArray: | |
| case Op::OpTypeMatrix: | |
| case Op::OpTypeCooperativeMatrixKHR: | |
| case Op::OpTypeCooperativeMatrixNV: | |
| case Op::OpTypeCooperativeVectorNV: | |
| if (! specConstant) { | |
| Id existing = findCompositeConstant(typeClass, opcode, typeId, members, numMembers); | |
| if (existing) | |
| return existing; | |
| } | |
| break; | |
| case Op::OpTypeStruct: | |
| if (! specConstant) { | |
| Id existing = findStructConstant(typeId, members, numMembers); | |
| if (existing) | |
| return existing; | |
| } | |
| break; | |
| default: | |
| assert(0); | |
| return makeFloatConstant(0.0); | |
| } | |
| Instruction* c = new Instruction(getUniqueId(), typeId, opcode); | |
| c->reserveOperands(members.size()); | |
| for (size_t op = 0; op < numMembers; ++op) | |
| c->addIdOperand(members[op]); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(c)); | |
| if (typeClass == Op::OpTypeStruct) | |
| groupedStructConstants.insert(StructConstantKey(c)); | |
| else | |
| groupedCompositeConstants.insert(CompositeConstantKey(typeClass, c)); | |
| module.mapInstruction(c); | |
| return c->getResultId(); | |
| } | |
| Instruction* Builder::addEntryPoint(ExecutionModel model, Function* function, const char* name) | |
| { | |
| Instruction* entryPoint = new Instruction(Op::OpEntryPoint); | |
| entryPoint->reserveOperands(3); | |
| entryPoint->addImmediateOperand(model); | |
| entryPoint->addIdOperand(function->getId()); | |
| entryPoint->addStringOperand(name); | |
| entryPoints.push_back(std::unique_ptr<Instruction>(entryPoint)); | |
| return entryPoint; | |
| } | |
| // Currently relying on the fact that all 'value' of interest are small non-negative values. | |
| void Builder::addExecutionMode(Function* entryPoint, ExecutionMode mode, int value1, int value2, int value3) | |
| { | |
| // entryPoint can be null if we are in compile-only mode | |
| if (!entryPoint) | |
| return; | |
| Instruction* instr = new Instruction(Op::OpExecutionMode); | |
| instr->reserveOperands(3); | |
| instr->addIdOperand(entryPoint->getId()); | |
| instr->addImmediateOperand(mode); | |
| if (value1 >= 0) | |
| instr->addImmediateOperand(value1); | |
| if (value2 >= 0) | |
| instr->addImmediateOperand(value2); | |
| if (value3 >= 0) | |
| instr->addImmediateOperand(value3); | |
| executionModes.push_back(std::unique_ptr<Instruction>(instr)); | |
| } | |
| void Builder::addExecutionMode(Function* entryPoint, ExecutionMode mode, const std::vector<unsigned>& literals) | |
| { | |
| // entryPoint can be null if we are in compile-only mode | |
| if (!entryPoint) | |
| return; | |
| Instruction* instr = new Instruction(Op::OpExecutionMode); | |
| instr->reserveOperands(literals.size() + 2); | |
| instr->addIdOperand(entryPoint->getId()); | |
| instr->addImmediateOperand(mode); | |
| for (auto literal : literals) | |
| instr->addImmediateOperand(literal); | |
| executionModes.push_back(std::unique_ptr<Instruction>(instr)); | |
| } | |
| void Builder::addExecutionModeId(Function* entryPoint, ExecutionMode mode, const std::vector<Id>& operandIds) | |
| { | |
| // entryPoint can be null if we are in compile-only mode | |
| if (!entryPoint) | |
| return; | |
| Instruction* instr = new Instruction(Op::OpExecutionModeId); | |
| instr->reserveOperands(operandIds.size() + 2); | |
| instr->addIdOperand(entryPoint->getId()); | |
| instr->addImmediateOperand(mode); | |
| for (auto operandId : operandIds) | |
| instr->addIdOperand(operandId); | |
| executionModes.push_back(std::unique_ptr<Instruction>(instr)); | |
| } | |
| void Builder::addName(Id id, const char* string) | |
| { | |
| Instruction* name = new Instruction(Op::OpName); | |
| name->reserveOperands(2); | |
| name->addIdOperand(id); | |
| name->addStringOperand(string); | |
| names.push_back(std::unique_ptr<Instruction>(name)); | |
| } | |
| void Builder::addMemberName(Id id, int memberNumber, const char* string) | |
| { | |
| Instruction* name = new Instruction(Op::OpMemberName); | |
| name->reserveOperands(3); | |
| name->addIdOperand(id); | |
| name->addImmediateOperand(memberNumber); | |
| name->addStringOperand(string); | |
| names.push_back(std::unique_ptr<Instruction>(name)); | |
| } | |
| void Builder::addDecoration(Id id, Decoration decoration, int num) | |
| { | |
| if (decoration == spv::Decoration::Max) | |
| return; | |
| Instruction* dec = new Instruction(Op::OpDecorate); | |
| dec->reserveOperands(2); | |
| dec->addIdOperand(id); | |
| dec->addImmediateOperand(decoration); | |
| if (num >= 0) | |
| dec->addImmediateOperand(num); | |
| decorations.insert(std::unique_ptr<Instruction>(dec)); | |
| } | |
| void Builder::addDecoration(Id id, Decoration decoration, const char* s) | |
| { | |
| if (decoration == spv::Decoration::Max) | |
| return; | |
| Instruction* dec = new Instruction(Op::OpDecorateString); | |
| dec->reserveOperands(3); | |
| dec->addIdOperand(id); | |
| dec->addImmediateOperand(decoration); | |
| dec->addStringOperand(s); | |
| decorations.insert(std::unique_ptr<Instruction>(dec)); | |
| } | |
| void Builder::addDecoration(Id id, Decoration decoration, const std::vector<unsigned>& literals) | |
| { | |
| if (decoration == spv::Decoration::Max) | |
| return; | |
| Instruction* dec = new Instruction(Op::OpDecorate); | |
| dec->reserveOperands(literals.size() + 2); | |
| dec->addIdOperand(id); | |
| dec->addImmediateOperand(decoration); | |
| for (auto literal : literals) | |
| dec->addImmediateOperand(literal); | |
| decorations.insert(std::unique_ptr<Instruction>(dec)); | |
| } | |
| void Builder::addDecoration(Id id, Decoration decoration, const std::vector<const char*>& strings) | |
| { | |
| if (decoration == spv::Decoration::Max) | |
| return; | |
| Instruction* dec = new Instruction(Op::OpDecorateString); | |
| dec->reserveOperands(strings.size() + 2); | |
| dec->addIdOperand(id); | |
| dec->addImmediateOperand(decoration); | |
| for (auto string : strings) | |
| dec->addStringOperand(string); | |
| decorations.insert(std::unique_ptr<Instruction>(dec)); | |
| } | |
| void Builder::addLinkageDecoration(Id id, const char* name, spv::LinkageType linkType) { | |
| Instruction* dec = new Instruction(Op::OpDecorate); | |
| dec->reserveOperands(4); | |
| dec->addIdOperand(id); | |
| dec->addImmediateOperand(spv::Decoration::LinkageAttributes); | |
| dec->addStringOperand(name); | |
| dec->addImmediateOperand(linkType); | |
| decorations.insert(std::unique_ptr<Instruction>(dec)); | |
| } | |
| void Builder::addDecorationId(Id id, Decoration decoration, Id idDecoration) | |
| { | |
| if (decoration == spv::Decoration::Max) | |
| return; | |
| Instruction* dec = new Instruction(Op::OpDecorateId); | |
| dec->reserveOperands(3); | |
| dec->addIdOperand(id); | |
| dec->addImmediateOperand(decoration); | |
| dec->addIdOperand(idDecoration); | |
| decorations.insert(std::unique_ptr<Instruction>(dec)); | |
| } | |
| void Builder::addDecorationId(Id id, Decoration decoration, const std::vector<Id>& operandIds) | |
| { | |
| if(decoration == spv::Decoration::Max) | |
| return; | |
| Instruction* dec = new Instruction(Op::OpDecorateId); | |
| dec->reserveOperands(operandIds.size() + 2); | |
| dec->addIdOperand(id); | |
| dec->addImmediateOperand(decoration); | |
| for (auto operandId : operandIds) | |
| dec->addIdOperand(operandId); | |
| decorations.insert(std::unique_ptr<Instruction>(dec)); | |
| } | |
| void Builder::addMemberDecorationIdEXT(Id id, unsigned int member, Decoration decoration, | |
| const std::vector<unsigned>& operands) | |
| { | |
| if (decoration == spv::Decoration::Max) | |
| return; | |
| Instruction* dec = new Instruction(Op::OpMemberDecorateIdEXT); | |
| dec->reserveOperands(operands.size() + 3); | |
| dec->addIdOperand(id); | |
| dec->addImmediateOperand(member); | |
| dec->addImmediateOperand(decoration); | |
| for (auto operand : operands) | |
| dec->addIdOperand(operand); | |
| decorations.insert(std::unique_ptr<Instruction>(dec)); | |
| } | |
| void Builder::addMemberDecoration(Id id, unsigned int member, Decoration decoration, int num) | |
| { | |
| if (decoration == spv::Decoration::Max) | |
| return; | |
| Instruction* dec = new Instruction(Op::OpMemberDecorate); | |
| dec->reserveOperands(3); | |
| dec->addIdOperand(id); | |
| dec->addImmediateOperand(member); | |
| dec->addImmediateOperand(decoration); | |
| if (num >= 0) | |
| dec->addImmediateOperand(num); | |
| decorations.insert(std::unique_ptr<Instruction>(dec)); | |
| } | |
| void Builder::addMemberDecoration(Id id, unsigned int member, Decoration decoration, const char *s) | |
| { | |
| if (decoration == spv::Decoration::Max) | |
| return; | |
| Instruction* dec = new Instruction(Op::OpMemberDecorateStringGOOGLE); | |
| dec->reserveOperands(4); | |
| dec->addIdOperand(id); | |
| dec->addImmediateOperand(member); | |
| dec->addImmediateOperand(decoration); | |
| dec->addStringOperand(s); | |
| decorations.insert(std::unique_ptr<Instruction>(dec)); | |
| } | |
| void Builder::addMemberDecoration(Id id, unsigned int member, Decoration decoration, const std::vector<unsigned>& literals) | |
| { | |
| if (decoration == spv::Decoration::Max) | |
| return; | |
| Instruction* dec = new Instruction(Op::OpMemberDecorate); | |
| dec->reserveOperands(literals.size() + 3); | |
| dec->addIdOperand(id); | |
| dec->addImmediateOperand(member); | |
| dec->addImmediateOperand(decoration); | |
| for (auto literal : literals) | |
| dec->addImmediateOperand(literal); | |
| decorations.insert(std::unique_ptr<Instruction>(dec)); | |
| } | |
| void Builder::addMemberDecoration(Id id, unsigned int member, Decoration decoration, const std::vector<const char*>& strings) | |
| { | |
| if (decoration == spv::Decoration::Max) | |
| return; | |
| Instruction* dec = new Instruction(Op::OpMemberDecorateString); | |
| dec->reserveOperands(strings.size() + 3); | |
| dec->addIdOperand(id); | |
| dec->addImmediateOperand(member); | |
| dec->addImmediateOperand(decoration); | |
| for (auto string : strings) | |
| dec->addStringOperand(string); | |
| decorations.insert(std::unique_ptr<Instruction>(dec)); | |
| } | |
| void Builder::addInstruction(std::unique_ptr<Instruction> inst) { | |
| // Phis must appear first in their block, don't insert line tracking instructions | |
| // in front of them, just add the OpPhi and return. | |
| if (inst->getOpCode() == Op::OpPhi) { | |
| buildPoint->addInstruction(std::move(inst)); | |
| return; | |
| } | |
| // Optionally insert OpDebugScope | |
| if (emitNonSemanticShaderDebugInfo && dirtyScopeTracker) { | |
| if (buildPoint->updateDebugScope(currentDebugScopeId.top())) { | |
| auto scopeInst = std::make_unique<Instruction>(getUniqueId(), makeVoidType(), Op::OpExtInst); | |
| scopeInst->reserveOperands(3); | |
| scopeInst->addIdOperand(nonSemanticShaderDebugInfo); | |
| scopeInst->addImmediateOperand(NonSemanticShaderDebugInfoDebugScope); | |
| scopeInst->addIdOperand(currentDebugScopeId.top()); | |
| buildPoint->addInstruction(std::move(scopeInst)); | |
| } | |
| dirtyScopeTracker = false; | |
| } | |
| // Insert OpLine/OpDebugLine if the debug source location has changed | |
| if (trackDebugInfo && dirtyLineTracker) { | |
| if (buildPoint->updateDebugSourceLocation(currentLine, 0, currentFileId)) { | |
| if (emitSpirvDebugInfo) { | |
| auto lineInst = std::make_unique<Instruction>(Op::OpLine); | |
| lineInst->reserveOperands(3); | |
| lineInst->addIdOperand(currentFileId); | |
| lineInst->addImmediateOperand(currentLine); | |
| lineInst->addImmediateOperand(0); | |
| buildPoint->addInstruction(std::move(lineInst)); | |
| } | |
| if (emitNonSemanticShaderDebugInfo) { | |
| auto lineInst = std::make_unique<Instruction>(getUniqueId(), makeVoidType(), Op::OpExtInst); | |
| lineInst->reserveOperands(7); | |
| lineInst->addIdOperand(nonSemanticShaderDebugInfo); | |
| lineInst->addImmediateOperand(NonSemanticShaderDebugInfoDebugLine); | |
| lineInst->addIdOperand(makeDebugSource(currentFileId)); | |
| lineInst->addIdOperand(makeUintConstant(currentLine)); | |
| lineInst->addIdOperand(makeUintConstant(currentLine)); | |
| lineInst->addIdOperand(makeUintConstant(0)); | |
| lineInst->addIdOperand(makeUintConstant(0)); | |
| buildPoint->addInstruction(std::move(lineInst)); | |
| } | |
| } | |
| dirtyLineTracker = false; | |
| } | |
| buildPoint->addInstruction(std::move(inst)); | |
| } | |
| void Builder::addInstructionNoDebugInfo(std::unique_ptr<Instruction> inst) { | |
| buildPoint->addInstruction(std::move(inst)); | |
| } | |
| // Comments in header | |
| Function* Builder::makeEntryPoint(const char* entryPoint) | |
| { | |
| assert(! entryPointFunction); | |
| auto const returnType = makeVoidType(); | |
| restoreNonSemanticShaderDebugInfo = emitNonSemanticShaderDebugInfo; | |
| if(sourceLang == spv::SourceLanguage::HLSL) { | |
| emitNonSemanticShaderDebugInfo = false; | |
| } | |
| Block* entry = nullptr; | |
| entryPointFunction = makeFunctionEntry(NoPrecision, returnType, entryPoint, LinkageType::Max, {}, {}, &entry); | |
| emitNonSemanticShaderDebugInfo = restoreNonSemanticShaderDebugInfo; | |
| return entryPointFunction; | |
| } | |
| // Comments in header | |
| Function* Builder::makeFunctionEntry(Decoration precision, Id returnType, const char* name, LinkageType linkType, | |
| const std::vector<Id>& paramTypes, | |
| const std::vector<std::vector<Decoration>>& decorations, Block** entry) | |
| { | |
| // Make the function and initial instructions in it | |
| Id typeId = makeFunctionType(returnType, paramTypes); | |
| Id firstParamId = paramTypes.size() == 0 ? 0 : getUniqueIds((int)paramTypes.size()); | |
| Id funcId = getUniqueId(); | |
| Function* function = new Function(funcId, returnType, typeId, firstParamId, linkType, name, module); | |
| // Set up the precisions | |
| setPrecision(function->getId(), precision); | |
| function->setReturnPrecision(precision); | |
| for (unsigned p = 0; p < (unsigned)decorations.size(); ++p) { | |
| for (int d = 0; d < (int)decorations[p].size(); ++d) { | |
| addDecoration(firstParamId + p, decorations[p][d]); | |
| function->addParamPrecision(p, decorations[p][d]); | |
| } | |
| } | |
| // reset last debug scope | |
| if (emitNonSemanticShaderDebugInfo) { | |
| dirtyScopeTracker = true; | |
| } | |
| // CFG | |
| assert(entry != nullptr); | |
| *entry = new Block(getUniqueId(), *function); | |
| function->addBlock(*entry); | |
| setBuildPoint(*entry); | |
| if (name) | |
| addName(function->getId(), name); | |
| functions.push_back(std::unique_ptr<Function>(function)); | |
| return function; | |
| } | |
| void Builder::setupFunctionDebugInfo(Function* function, const char* name, const std::vector<Id>& paramTypes, | |
| const std::vector<char const*>& paramNames) | |
| { | |
| if (!emitNonSemanticShaderDebugInfo) | |
| return; | |
| Id nameId = getStringId(unmangleFunctionName(name)); | |
| Id funcTypeId = function->getFuncTypeId(); | |
| assert(getDebugType(funcTypeId) != NoType); | |
| Id funcId = function->getId(); | |
| assert(funcId != 0); | |
| // Make the debug function instruction | |
| Id debugFuncId = makeDebugFunction(function, nameId, funcTypeId); | |
| debugFuncIdLookup[funcId] = debugFuncId; | |
| currentDebugScopeId.push(debugFuncId); | |
| // DebugScope and DebugLine for parameter DebugDeclares | |
| assert(paramTypes.size() == paramNames.size()); | |
| if ((int)paramTypes.size() > 0) { | |
| Id firstParamId = function->getParamId(0); | |
| for (size_t p = 0; p < paramTypes.size(); ++p) { | |
| bool passByRef = false; | |
| Id paramTypeId = paramTypes[p]; | |
| // For pointer-typed parameters, they are actually passed by reference and we need unwrap the pointer to get the actual parameter type. | |
| if (isPointerType(paramTypeId) || isArrayType(paramTypeId)) { | |
| passByRef = true; | |
| paramTypeId = getContainedTypeId(paramTypeId); | |
| } | |
| auto const& paramName = paramNames[p]; | |
| auto const debugLocalVariableId = createDebugLocalVariable(getDebugType(paramTypeId), paramName, p + 1); | |
| auto const paramId = static_cast<Id>(firstParamId + p); | |
| if (passByRef) { | |
| makeDebugDeclare(debugLocalVariableId, paramId); | |
| } else { | |
| makeDebugValue(debugLocalVariableId, paramId); | |
| } | |
| } | |
| } | |
| // Clear debug scope stack | |
| if (emitNonSemanticShaderDebugInfo) | |
| currentDebugScopeId.pop(); | |
| } | |
| Id Builder::makeDebugFunction([[maybe_unused]] Function* function, Id nameId, Id funcTypeId) | |
| { | |
| assert(function != nullptr); | |
| assert(nameId != 0); | |
| assert(funcTypeId != 0); | |
| assert(getDebugType(funcTypeId) != NoType); | |
| Id funcId = getUniqueId(); | |
| auto type = new Instruction(funcId, makeVoidType(), Op::OpExtInst); | |
| type->reserveOperands(11); | |
| type->addIdOperand(nonSemanticShaderDebugInfo); | |
| type->addImmediateOperand(NonSemanticShaderDebugInfoDebugFunction); | |
| type->addIdOperand(nameId); | |
| type->addIdOperand(getDebugType(funcTypeId)); | |
| type->addIdOperand(makeDebugSource(currentFileId)); // TODO: This points to file of definition instead of declaration | |
| type->addIdOperand(makeUintConstant(currentLine)); // TODO: This points to line of definition instead of declaration | |
| type->addIdOperand(makeUintConstant(0)); // column | |
| type->addIdOperand(makeDebugCompilationUnit()); // scope | |
| type->addIdOperand(nameId); // linkage name | |
| type->addIdOperand(makeUintConstant(NonSemanticShaderDebugInfoFlagIsPublic)); | |
| type->addIdOperand(makeUintConstant(currentLine)); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return funcId; | |
| } | |
| Id Builder::makeDebugLexicalBlock(uint32_t line, uint32_t column) { | |
| assert(!currentDebugScopeId.empty()); | |
| Id lexId = getUniqueId(); | |
| auto lex = new Instruction(lexId, makeVoidType(), Op::OpExtInst); | |
| lex->reserveOperands(6); | |
| lex->addIdOperand(nonSemanticShaderDebugInfo); | |
| lex->addImmediateOperand(NonSemanticShaderDebugInfoDebugLexicalBlock); | |
| lex->addIdOperand(makeDebugSource(currentFileId)); | |
| lex->addIdOperand(makeUintConstant(line)); | |
| lex->addIdOperand(makeUintConstant(column)); // column | |
| lex->addIdOperand(currentDebugScopeId.top()); // scope | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(lex)); | |
| module.mapInstruction(lex); | |
| return lexId; | |
| } | |
| std::string Builder::unmangleFunctionName(std::string const& name) const | |
| { | |
| assert(name.length() > 0); | |
| if(name.rfind('(') != std::string::npos) { | |
| return name.substr(0, name.rfind('(')); | |
| } else { | |
| return name; | |
| } | |
| } | |
| // Comments in header | |
| void Builder::makeReturn(bool implicit, Id retVal) | |
| { | |
| if (retVal) { | |
| Instruction* inst = new Instruction(NoResult, NoType, Op::OpReturnValue); | |
| inst->addIdOperand(retVal); | |
| addInstruction(std::unique_ptr<Instruction>(inst)); | |
| } else | |
| addInstruction(std::unique_ptr<Instruction>(new Instruction(NoResult, NoType, Op::OpReturn))); | |
| if (! implicit) | |
| createAndSetNoPredecessorBlock("post-return"); | |
| } | |
| // Comments in header | |
| void Builder::enterLexicalBlock(uint32_t line, uint32_t column) | |
| { | |
| if (!emitNonSemanticShaderDebugInfo) { | |
| return; | |
| } | |
| // Generate new lexical scope debug instruction | |
| Id lexId = makeDebugLexicalBlock(line, column); | |
| currentDebugScopeId.push(lexId); | |
| dirtyScopeTracker = true; | |
| } | |
| // Comments in header | |
| void Builder::leaveLexicalBlock() | |
| { | |
| if (!emitNonSemanticShaderDebugInfo) { | |
| return; | |
| } | |
| // Pop current scope from stack and clear current scope | |
| currentDebugScopeId.pop(); | |
| dirtyScopeTracker = true; | |
| } | |
| // Comments in header | |
| void Builder::enterFunction(Function const* function) | |
| { | |
| currentFunction = function; | |
| // Save and disable debugInfo for HLSL entry point function. It is a wrapper | |
| // function with no user code in it. | |
| restoreNonSemanticShaderDebugInfo = emitNonSemanticShaderDebugInfo; | |
| if (sourceLang == spv::SourceLanguage::HLSL && function == entryPointFunction) { | |
| emitNonSemanticShaderDebugInfo = false; | |
| } | |
| if (emitNonSemanticShaderDebugInfo) { | |
| // Initialize scope state | |
| Id funcId = function->getFuncId(); | |
| Id debugFuncId = getDebugFunction(funcId); | |
| currentDebugScopeId.push(debugFuncId); | |
| // Create DebugFunctionDefinition | |
| spv::Id resultId = getUniqueId(); | |
| Instruction* defInst = new Instruction(resultId, makeVoidType(), Op::OpExtInst); | |
| defInst->reserveOperands(4); | |
| defInst->addIdOperand(nonSemanticShaderDebugInfo); | |
| defInst->addImmediateOperand(NonSemanticShaderDebugInfoDebugFunctionDefinition); | |
| defInst->addIdOperand(debugFuncId); | |
| defInst->addIdOperand(funcId); | |
| addInstruction(std::unique_ptr<Instruction>(defInst)); | |
| } | |
| if (auto linkType = function->getLinkType(); linkType != LinkageType::Max) { | |
| Id funcId = function->getFuncId(); | |
| addCapability(Capability::Linkage); | |
| addLinkageDecoration(funcId, function->getExportName(), linkType); | |
| } | |
| } | |
| // Comments in header | |
| void Builder::leaveFunction() | |
| { | |
| Block* block = buildPoint; | |
| Function& function = buildPoint->getParent(); | |
| assert(block); | |
| // If our function did not contain a return, add a return void now. | |
| if (! block->isTerminated()) { | |
| if (function.getReturnType() == makeVoidType()) | |
| makeReturn(true); | |
| else { | |
| makeReturn(true, createUndefined(function.getReturnType())); | |
| } | |
| } | |
| // Clear function scope from debug scope stack | |
| if (emitNonSemanticShaderDebugInfo) | |
| currentDebugScopeId.pop(); | |
| emitNonSemanticShaderDebugInfo = restoreNonSemanticShaderDebugInfo; | |
| // Clear current function record | |
| currentFunction = nullptr; | |
| } | |
| // Comments in header | |
| void Builder::makeStatementTerminator(spv::Op opcode, const char *name) | |
| { | |
| addInstruction(std::unique_ptr<Instruction>(new Instruction(opcode))); | |
| createAndSetNoPredecessorBlock(name); | |
| } | |
| // Comments in header | |
| void Builder::makeStatementTerminator(spv::Op opcode, const std::vector<Id>& operands, const char* name) | |
| { | |
| // It's assumed that the terminator instruction is always of void return type | |
| // However in future if there is a need for non void return type, new helper | |
| // methods can be created. | |
| createNoResultOp(opcode, operands); | |
| createAndSetNoPredecessorBlock(name); | |
| } | |
| void Builder::createConstVariable(Id type, const char* name, Id constant, bool isGlobal) | |
| { | |
| if (emitNonSemanticShaderDebugInfo) { | |
| Id debugType = getDebugType(type); | |
| if (isGlobal) { | |
| createDebugGlobalVariable(debugType, name, constant); | |
| } | |
| else { | |
| auto debugLocal = createDebugLocalVariable(debugType, name); | |
| makeDebugValue(debugLocal, constant); | |
| } | |
| } | |
| } | |
| // Comments in header | |
| Id Builder::createUntypedVariable(Decoration precision, StorageClass storageClass, const char* name, Id dataType, | |
| Id initializer) | |
| { | |
| Id resultUntypedPointerType = makeUntypedPointer(storageClass); | |
| Instruction* inst = new Instruction(getUniqueId(), resultUntypedPointerType, Op::OpUntypedVariableKHR); | |
| inst->addImmediateOperand(storageClass); | |
| if (dataType != NoResult) { | |
| Id dataPointerType = makePointer(storageClass, dataType); | |
| inst->addIdOperand(dataPointerType); | |
| } | |
| if (initializer != NoResult) | |
| inst->addIdOperand(initializer); | |
| switch (storageClass) { | |
| case StorageClass::Function: | |
| // Validation rules require the declaration in the entry block | |
| buildPoint->getParent().addLocalVariable(std::unique_ptr<Instruction>(inst)); | |
| break; | |
| default: | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(inst)); | |
| module.mapInstruction(inst); | |
| break; | |
| } | |
| if (name) | |
| addName(inst->getResultId(), name); | |
| setPrecision(inst->getResultId(), precision); | |
| return inst->getResultId(); | |
| } | |
| // Comments in header | |
| Id Builder::createVariable(Decoration precision, StorageClass storageClass, Id type, const char* name, Id initializer, | |
| bool const compilerGenerated) | |
| { | |
| Id pointerType = makePointer(storageClass, type); | |
| Instruction* inst = new Instruction(getUniqueId(), pointerType, Op::OpVariable); | |
| inst->addImmediateOperand(storageClass); | |
| if (initializer != NoResult) | |
| inst->addIdOperand(initializer); | |
| if (storageClass == StorageClass::Function) { | |
| // Validation rules require the declaration in the entry block | |
| buildPoint->getParent().addLocalVariable(std::unique_ptr<Instruction>(inst)); | |
| } | |
| else { | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(inst)); | |
| module.mapInstruction(inst); | |
| } | |
| if (emitNonSemanticShaderDebugInfo && !compilerGenerated) | |
| { | |
| // For debug info, we prefer respecting how the variable is declared in source code. | |
| // We may emulate some local variables as global variable with private storage in SPIR-V, but we still want to | |
| // treat them as local variables in debug info. | |
| if (storageClass == StorageClass::Function || (currentFunction && storageClass == StorageClass::Private)) { | |
| auto const debugLocalVariableId = createDebugLocalVariable(getDebugType(type), name); | |
| makeDebugDeclare(debugLocalVariableId, inst->getResultId()); | |
| } | |
| else { | |
| createDebugGlobalVariable(getDebugType(type), name, inst->getResultId()); | |
| } | |
| } | |
| if (name) | |
| addName(inst->getResultId(), name); | |
| setPrecision(inst->getResultId(), precision); | |
| return inst->getResultId(); | |
| } | |
| // Comments in header | |
| Id Builder::createUndefined(Id type) | |
| { | |
| Instruction* inst = new Instruction(getUniqueId(), type, Op::OpUndef); | |
| addInstruction(std::unique_ptr<Instruction>(inst)); | |
| return inst->getResultId(); | |
| } | |
| // av/vis/nonprivate are unnecessary and illegal for some storage classes. | |
| spv::MemoryAccessMask Builder::sanitizeMemoryAccessForStorageClass(spv::MemoryAccessMask memoryAccess, StorageClass sc) | |
| const | |
| { | |
| switch (sc) { | |
| case spv::StorageClass::Uniform: | |
| case spv::StorageClass::Workgroup: | |
| case spv::StorageClass::StorageBuffer: | |
| case spv::StorageClass::PhysicalStorageBufferEXT: | |
| break; | |
| default: | |
| memoryAccess = spv::MemoryAccessMask(memoryAccess & | |
| ~(spv::MemoryAccessMask::MakePointerAvailableKHR | | |
| spv::MemoryAccessMask::MakePointerVisibleKHR | | |
| spv::MemoryAccessMask::NonPrivatePointerKHR)); | |
| break; | |
| } | |
| return memoryAccess; | |
| } | |
| // Comments in header | |
| void Builder::createStore(Id rValue, Id lValue, spv::MemoryAccessMask memoryAccess, spv::Scope scope, | |
| unsigned int alignment) | |
| { | |
| Instruction* store = nullptr; | |
| if (isUntypedPointer(lValue)) | |
| store = createDescHeapLoadStoreBaseRemap(lValue, Op::OpStore); | |
| else { | |
| store = new Instruction(Op::OpStore); | |
| store->reserveOperands(2); | |
| store->addIdOperand(lValue); | |
| } | |
| store->addIdOperand(rValue); | |
| memoryAccess = sanitizeMemoryAccessForStorageClass(memoryAccess, getStorageClass(lValue)); | |
| if (memoryAccess != MemoryAccessMask::MaskNone) { | |
| store->addImmediateOperand(memoryAccess); | |
| if (anySet(memoryAccess, spv::MemoryAccessMask::Aligned)) { | |
| store->addImmediateOperand(alignment); | |
| } | |
| if (anySet(memoryAccess, spv::MemoryAccessMask::MakePointerAvailableKHR)) { | |
| store->addIdOperand(makeUintConstant(scope)); | |
| } | |
| } | |
| addInstruction(std::unique_ptr<Instruction>(store)); | |
| } | |
| // Comments in header | |
| Id Builder::createLoad(Id lValue, spv::Decoration precision, spv::MemoryAccessMask memoryAccess, | |
| spv::Scope scope, unsigned int alignment) | |
| { | |
| Instruction* load = nullptr; | |
| if (isUntypedPointer(lValue)) | |
| load = createDescHeapLoadStoreBaseRemap(lValue, Op::OpLoad); | |
| else { | |
| load = new Instruction(getUniqueId(), getDerefTypeId(lValue), Op::OpLoad); | |
| load->addIdOperand(lValue); | |
| } | |
| memoryAccess = sanitizeMemoryAccessForStorageClass(memoryAccess, getStorageClass(lValue)); | |
| if (memoryAccess != MemoryAccessMask::MaskNone) { | |
| load->addImmediateOperand(memoryAccess); | |
| if (anySet(memoryAccess, spv::MemoryAccessMask::Aligned)) { | |
| load->addImmediateOperand(alignment); | |
| } | |
| if (anySet(memoryAccess, spv::MemoryAccessMask::MakePointerVisibleKHR)) { | |
| load->addIdOperand(makeUintConstant(scope)); | |
| } | |
| } | |
| addInstruction(std::unique_ptr<Instruction>(load)); | |
| setPrecision(load->getResultId(), precision); | |
| return load->getResultId(); | |
| } | |
| Instruction* Builder::createDescHeapLoadStoreBaseRemap(Id baseId, Op op) | |
| { | |
| // could only be untypedAccessChain op. | |
| spv::Op instOp = module.getInstruction(baseId)->getOpCode(); | |
| spv::Id baseVal = baseId; | |
| // base type (from run time array) | |
| spv::Id resultTy = getIdOperand(baseId, 0); | |
| // Descriptor heap using run time array. | |
| if (accessChain.descHeapInfo.descStorageClass != StorageClass::Max) | |
| resultTy = getIdOperand(resultTy, 0); | |
| if (instOp != Op::OpUntypedAccessChainKHR) { | |
| assert(false && "Not a untyped load type"); | |
| } | |
| Instruction* inst = nullptr; | |
| if (op == Op::OpStore) | |
| inst = new Instruction(Op::OpStore); | |
| else { | |
| inst = new Instruction(getUniqueId(), resultTy, Op::OpLoad); | |
| accessChain.descHeapInfo.descHeapInstId.push_back(inst); | |
| } | |
| inst->addIdOperand(baseVal); | |
| return inst; | |
| } | |
| spv::Id Builder::getOrCreateDescHeapByteArrayType() | |
| { | |
| if (descHeapByteArrayType != NoResult) | |
| return descHeapByteArrayType; | |
| addCapability(Capability::Int8); | |
| descHeapByteArrayType = makeRuntimeArray(makeUintType(8)); | |
| addDecoration(descHeapByteArrayType, Decoration::ArrayStride, 1); | |
| return descHeapByteArrayType; | |
| } | |
| // Comments in header | |
| Id Builder::createDescHeapAccessChain() | |
| { | |
| std::vector<Id>& heapOffsets = accessChain.descHeapInfo.descHeapIndexChain; | |
| assert(heapOffsets.size() != 0); | |
| Id heapBase = accessChain.base; | |
| Id heapBaseTy = accessChain.descHeapInfo.descHeapBaseTy; | |
| Id heapBaseOffset = accessChain.descHeapInfo.descHeapBaseOffset; | |
| StorageClass storageClass = (StorageClass)accessChain.descHeapInfo.descStorageClass; | |
| if (heapBaseOffset != NoResult) { | |
| const std::pair<Id, Id> cacheKey(heapBase, heapBaseOffset); | |
| auto cachedShiftedBase = descHeapShiftedBaseCache.find(cacheKey); | |
| if (cachedShiftedBase != descHeapShiftedBaseCache.end()) { | |
| heapBase = cachedShiftedBase->second; | |
| } else { | |
| Id shiftedBaseId = getUniqueId(); | |
| const std::vector<Id> shiftedBaseOffsets(1, heapBaseOffset); | |
| heapBase = createUntypedAccessChain(getOrCreateDescHeapByteArrayType(), heapBase, | |
| shiftedBaseOffsets, shiftedBaseId); | |
| descHeapShiftedBaseCache.insert(std::make_pair(cacheKey, heapBase)); | |
| } | |
| } | |
| // First descriptor heap access chain | |
| Id chain = createUntypedAccessChain(heapBaseTy, heapBase, heapOffsets); | |
| // Create OpBufferPointer for loading target buffer descriptor | |
| if (storageClass == spv::StorageClass::Uniform || storageClass == spv::StorageClass::StorageBuffer) { | |
| // Create OpBufferPointer for the descriptor, then use a typed access chain for buffer data. | |
| Id descriptorTy = accessChain.descHeapInfo.descTy; | |
| Id bufferPtrTy = makePointer(storageClass, descriptorTy); | |
| Instruction* bufferDataPtr = new Instruction(getUniqueId(), bufferPtrTy, Op::OpBufferPointerEXT); | |
| bufferDataPtr->addIdOperand(chain); | |
| addInstruction(std::unique_ptr<Instruction>(bufferDataPtr)); | |
| if (accessChain.descHeapInfo.descWriteonly) | |
| addDecoration(bufferDataPtr->getResultId(), Decoration::NonReadable); | |
| if (accessChain.descHeapInfo.descReadonly && storageClass == StorageClass::StorageBuffer) | |
| addDecoration(bufferDataPtr->getResultId(), Decoration::NonWritable); | |
| std::vector<Id>& offsets = accessChain.indexChain; | |
| if (offsets.empty()) | |
| return bufferDataPtr->getResultId(); | |
| // Form a second, typed access chain for accessing buffer data. | |
| Id resultTy = descriptorTy; | |
| for (int i = 0; i < (int)offsets.size(); ++i) { | |
| if (isStructType(resultTy)) { | |
| assert(isConstantScalar(offsets[i])); | |
| resultTy = getContainedTypeId(resultTy, getConstantScalar(offsets[i])); | |
| } else | |
| resultTy = getContainedTypeId(resultTy, offsets[i]); | |
| } | |
| resultTy = makePointer(storageClass, resultTy); | |
| Instruction* bufferChain = new Instruction(getUniqueId(), resultTy, Op::OpAccessChain); | |
| bufferChain->reserveOperands(offsets.size() + 1); | |
| bufferChain->addIdOperand(bufferDataPtr->getResultId()); | |
| for (int i = 0; i < (int)offsets.size(); ++i) | |
| bufferChain->addIdOperand(offsets[i]); | |
| addInstruction(std::unique_ptr<Instruction>(bufferChain)); | |
| return bufferChain->getResultId(); | |
| } | |
| return chain; | |
| } | |
| // Comments in header | |
| Id Builder::createAccessChain(StorageClass storageClass, Id base, const std::vector<Id>& offsets) | |
| { | |
| // Figure out the final resulting type. | |
| Id typeId = getResultingAccessChainType(); | |
| typeId = makePointer(storageClass, typeId); | |
| // Make the instruction | |
| Instruction* chain = new Instruction(getUniqueId(), typeId, Op::OpAccessChain); | |
| chain->reserveOperands(offsets.size() + 1); | |
| chain->addIdOperand(base); | |
| for (int i = 0; i < (int)offsets.size(); ++i) | |
| chain->addIdOperand(offsets[i]); | |
| addInstruction(std::unique_ptr<Instruction>(chain)); | |
| return chain->getResultId(); | |
| } | |
| // Comments in header | |
| Id Builder::createUntypedAccessChain(Id resultType, Id base, const std::vector<Id>& offsets, Id resultId) | |
| { | |
| if (resultId == NoResult) | |
| resultId = getUniqueId(); | |
| Instruction* chain = new Instruction(resultId, makeUntypedPointer(getStorageClass(base)), | |
| Op::OpUntypedAccessChainKHR); | |
| chain->reserveOperands(offsets.size() + 2); | |
| chain->addIdOperand(resultType); | |
| chain->addIdOperand(base); | |
| for (int i = 0; i < (int)offsets.size(); ++i) | |
| chain->addIdOperand(offsets[i]); | |
| addInstruction(std::unique_ptr<Instruction>(chain)); | |
| return chain->getResultId(); | |
| } | |
| Id Builder::createArrayLength(Id base, unsigned int member, unsigned int bits) | |
| { | |
| spv::Id intType = makeUintType(bits); | |
| Instruction* length = new Instruction(getUniqueId(), intType, Op::OpArrayLength); | |
| length->reserveOperands(2); | |
| length->addIdOperand(base); | |
| length->addImmediateOperand(member); | |
| addInstruction(std::unique_ptr<Instruction>(length)); | |
| return length->getResultId(); | |
| } | |
| Id Builder::createCooperativeMatrixLengthKHR(Id type) | |
| { | |
| spv::Id intType = makeUintType(32); | |
| // Generate code for spec constants if in spec constant operation | |
| // generation mode. | |
| if (generatingOpCodeForSpecConst) { | |
| return createSpecConstantOp(Op::OpCooperativeMatrixLengthKHR, intType, std::vector<Id>(1, type), std::vector<Id>()); | |
| } | |
| Instruction* length = new Instruction(getUniqueId(), intType, Op::OpCooperativeMatrixLengthKHR); | |
| length->addIdOperand(type); | |
| addInstruction(std::unique_ptr<Instruction>(length)); | |
| return length->getResultId(); | |
| } | |
| Id Builder::createCooperativeMatrixLengthNV(Id type) | |
| { | |
| spv::Id intType = makeUintType(32); | |
| // Generate code for spec constants if in spec constant operation | |
| // generation mode. | |
| if (generatingOpCodeForSpecConst) { | |
| return createSpecConstantOp(Op::OpCooperativeMatrixLengthNV, intType, std::vector<Id>(1, type), std::vector<Id>()); | |
| } | |
| Instruction* length = new Instruction(getUniqueId(), intType, Op::OpCooperativeMatrixLengthNV); | |
| length->addIdOperand(type); | |
| addInstruction(std::unique_ptr<Instruction>(length)); | |
| return length->getResultId(); | |
| } | |
| Id Builder::createCompositeExtract(Id composite, Id typeId, unsigned index) | |
| { | |
| // Generate code for spec constants if in spec constant operation | |
| // generation mode. | |
| if (generatingOpCodeForSpecConst) { | |
| return createSpecConstantOp(Op::OpCompositeExtract, typeId, std::vector<Id>(1, composite), | |
| std::vector<Id>(1, index)); | |
| } | |
| Instruction* extract = new Instruction(getUniqueId(), typeId, Op::OpCompositeExtract); | |
| extract->reserveOperands(2); | |
| extract->addIdOperand(composite); | |
| extract->addImmediateOperand(index); | |
| addInstruction(std::unique_ptr<Instruction>(extract)); | |
| return extract->getResultId(); | |
| } | |
| Id Builder::createCompositeExtract(Id composite, Id typeId, const std::vector<unsigned>& indexes) | |
| { | |
| // Generate code for spec constants if in spec constant operation | |
| // generation mode. | |
| if (generatingOpCodeForSpecConst) { | |
| return createSpecConstantOp(Op::OpCompositeExtract, typeId, std::vector<Id>(1, composite), indexes); | |
| } | |
| Instruction* extract = new Instruction(getUniqueId(), typeId, Op::OpCompositeExtract); | |
| extract->reserveOperands(indexes.size() + 1); | |
| extract->addIdOperand(composite); | |
| for (int i = 0; i < (int)indexes.size(); ++i) | |
| extract->addImmediateOperand(indexes[i]); | |
| addInstruction(std::unique_ptr<Instruction>(extract)); | |
| return extract->getResultId(); | |
| } | |
| Id Builder::createCompositeInsert(Id object, Id composite, Id typeId, unsigned index) | |
| { | |
| Instruction* insert = new Instruction(getUniqueId(), typeId, Op::OpCompositeInsert); | |
| insert->reserveOperands(3); | |
| insert->addIdOperand(object); | |
| insert->addIdOperand(composite); | |
| insert->addImmediateOperand(index); | |
| addInstruction(std::unique_ptr<Instruction>(insert)); | |
| return insert->getResultId(); | |
| } | |
| Id Builder::createCompositeInsert(Id object, Id composite, Id typeId, const std::vector<unsigned>& indexes) | |
| { | |
| Instruction* insert = new Instruction(getUniqueId(), typeId, Op::OpCompositeInsert); | |
| insert->reserveOperands(indexes.size() + 2); | |
| insert->addIdOperand(object); | |
| insert->addIdOperand(composite); | |
| for (int i = 0; i < (int)indexes.size(); ++i) | |
| insert->addImmediateOperand(indexes[i]); | |
| addInstruction(std::unique_ptr<Instruction>(insert)); | |
| return insert->getResultId(); | |
| } | |
| Id Builder::createVectorExtractDynamic(Id vector, Id typeId, Id componentIndex) | |
| { | |
| Instruction* extract = new Instruction(getUniqueId(), typeId, Op::OpVectorExtractDynamic); | |
| extract->reserveOperands(2); | |
| extract->addIdOperand(vector); | |
| extract->addIdOperand(componentIndex); | |
| addInstruction(std::unique_ptr<Instruction>(extract)); | |
| return extract->getResultId(); | |
| } | |
| Id Builder::createVectorInsertDynamic(Id vector, Id typeId, Id component, Id componentIndex) | |
| { | |
| Instruction* insert = new Instruction(getUniqueId(), typeId, Op::OpVectorInsertDynamic); | |
| insert->reserveOperands(3); | |
| insert->addIdOperand(vector); | |
| insert->addIdOperand(component); | |
| insert->addIdOperand(componentIndex); | |
| addInstruction(std::unique_ptr<Instruction>(insert)); | |
| return insert->getResultId(); | |
| } | |
| // An opcode that has no operands, no result id, and no type | |
| void Builder::createNoResultOp(Op opCode) | |
| { | |
| Instruction* op = new Instruction(opCode); | |
| addInstruction(std::unique_ptr<Instruction>(op)); | |
| } | |
| // An opcode that has one id operand, no result id, and no type | |
| void Builder::createNoResultOp(Op opCode, Id operand) | |
| { | |
| Instruction* op = new Instruction(opCode); | |
| op->addIdOperand(operand); | |
| addInstruction(std::unique_ptr<Instruction>(op)); | |
| } | |
| // An opcode that has one or more operands, no result id, and no type | |
| void Builder::createNoResultOp(Op opCode, const std::vector<Id>& operands) | |
| { | |
| Instruction* op = new Instruction(opCode); | |
| op->reserveOperands(operands.size()); | |
| for (auto id : operands) { | |
| op->addIdOperand(id); | |
| } | |
| addInstruction(std::unique_ptr<Instruction>(op)); | |
| } | |
| // An opcode that has multiple operands, no result id, and no type | |
| void Builder::createNoResultOp(Op opCode, const std::vector<IdImmediate>& operands) | |
| { | |
| Instruction* op = new Instruction(opCode); | |
| op->reserveOperands(operands.size()); | |
| for (auto it = operands.cbegin(); it != operands.cend(); ++it) { | |
| if (it->isId) | |
| op->addIdOperand(it->word); | |
| else | |
| op->addImmediateOperand(it->word); | |
| } | |
| addInstruction(std::unique_ptr<Instruction>(op)); | |
| } | |
| void Builder::createControlBarrier(Scope execution, Scope memory, MemorySemanticsMask semantics) | |
| { | |
| Instruction* op = new Instruction(Op::OpControlBarrier); | |
| op->reserveOperands(3); | |
| op->addIdOperand(makeUintConstant(execution)); | |
| op->addIdOperand(makeUintConstant(memory)); | |
| op->addIdOperand(makeUintConstant(semantics)); | |
| addInstruction(std::unique_ptr<Instruction>(op)); | |
| } | |
| void Builder::createSplitControlBarrier(Op opcode, Scope execution, Scope memory, MemorySemanticsMask semantics) | |
| { | |
| Instruction* op = new Instruction(opcode); | |
| op->reserveOperands(3); | |
| op->addIdOperand(makeUintConstant(execution)); | |
| op->addIdOperand(makeUintConstant(memory)); | |
| op->addIdOperand(makeUintConstant(semantics)); | |
| addInstruction(std::unique_ptr<Instruction>(op)); | |
| } | |
| void Builder::createMemoryBarrier(Scope executionScope, MemorySemanticsMask memorySemantics) | |
| { | |
| Instruction* op = new Instruction(Op::OpMemoryBarrier); | |
| op->reserveOperands(2); | |
| op->addIdOperand(makeUintConstant((unsigned)executionScope)); | |
| op->addIdOperand(makeUintConstant((unsigned)memorySemantics)); | |
| addInstruction(std::unique_ptr<Instruction>(op)); | |
| } | |
| // An opcode that has one operands, a result id, and a type | |
| Id Builder::createUnaryOp(Op opCode, Id typeId, Id operand) | |
| { | |
| // Generate code for spec constants if in spec constant operation | |
| // generation mode. | |
| if (generatingOpCodeForSpecConst) { | |
| return createSpecConstantOp(opCode, typeId, std::vector<Id>(1, operand), std::vector<Id>()); | |
| } | |
| Instruction* op = new Instruction(getUniqueId(), typeId, opCode); | |
| op->addIdOperand(operand); | |
| addInstruction(std::unique_ptr<Instruction>(op)); | |
| return op->getResultId(); | |
| } | |
| Id Builder::createBinOp(Op opCode, Id typeId, Id left, Id right) | |
| { | |
| // Generate code for spec constants if in spec constant operation | |
| // generation mode. | |
| if (generatingOpCodeForSpecConst) { | |
| std::vector<Id> operands(2); | |
| operands[0] = left; operands[1] = right; | |
| return createSpecConstantOp(opCode, typeId, operands, std::vector<Id>()); | |
| } | |
| Instruction* op = new Instruction(getUniqueId(), typeId, opCode); | |
| op->reserveOperands(2); | |
| op->addIdOperand(left); | |
| op->addIdOperand(right); | |
| addInstruction(std::unique_ptr<Instruction>(op)); | |
| return op->getResultId(); | |
| } | |
| Id Builder::createTriOp(Op opCode, Id typeId, Id op1, Id op2, Id op3) | |
| { | |
| // Generate code for spec constants if in spec constant operation | |
| // generation mode. | |
| if (generatingOpCodeForSpecConst) { | |
| std::vector<Id> operands(3); | |
| operands[0] = op1; | |
| operands[1] = op2; | |
| operands[2] = op3; | |
| return createSpecConstantOp( | |
| opCode, typeId, operands, std::vector<Id>()); | |
| } | |
| Instruction* op = new Instruction(getUniqueId(), typeId, opCode); | |
| op->reserveOperands(3); | |
| op->addIdOperand(op1); | |
| op->addIdOperand(op2); | |
| op->addIdOperand(op3); | |
| addInstruction(std::unique_ptr<Instruction>(op)); | |
| return op->getResultId(); | |
| } | |
| Id Builder::createConstData(Op opCode, Id typeId, const std::vector<const char*> operands) | |
| { | |
| Instruction* op = new Instruction(getUniqueId(), typeId, opCode); | |
| op->reserveOperands(operands.size()); | |
| for (auto id : operands) | |
| op->addStringOperand(id); | |
| module.mapInstruction(op); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(op)); | |
| return op->getResultId(); | |
| } | |
| Id Builder::createOp(Op opCode, Id typeId, const std::vector<Id>& operands) | |
| { | |
| Instruction* op = new Instruction(getUniqueId(), typeId, opCode); | |
| op->reserveOperands(operands.size()); | |
| for (auto id : operands) | |
| op->addIdOperand(id); | |
| addInstruction(std::unique_ptr<Instruction>(op)); | |
| return op->getResultId(); | |
| } | |
| Id Builder::createOp(Op opCode, Id typeId, const std::vector<IdImmediate>& operands) | |
| { | |
| Instruction* op = new Instruction(getUniqueId(), typeId, opCode); | |
| op->reserveOperands(operands.size()); | |
| for (auto it = operands.cbegin(); it != operands.cend(); ++it) { | |
| if (it->isId) | |
| op->addIdOperand(it->word); | |
| else | |
| op->addImmediateOperand(it->word); | |
| } | |
| addInstruction(std::unique_ptr<Instruction>(op)); | |
| return op->getResultId(); | |
| } | |
| Id Builder::createSpecConstantOp(Op opCode, Id typeId, const std::vector<Id>& operands, | |
| const std::vector<unsigned>& literals) | |
| { | |
| Instruction* op = new Instruction(getUniqueId(), typeId, Op::OpSpecConstantOp); | |
| op->reserveOperands(operands.size() + literals.size() + 1); | |
| op->addImmediateOperand((unsigned) opCode); | |
| for (auto it = operands.cbegin(); it != operands.cend(); ++it) | |
| op->addIdOperand(*it); | |
| for (auto it = literals.cbegin(); it != literals.cend(); ++it) | |
| op->addImmediateOperand(*it); | |
| module.mapInstruction(op); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(op)); | |
| // OpSpecConstantOp's using 8 or 16 bit types require the associated capability | |
| if (containsType(typeId, Op::OpTypeInt, 8)) | |
| addCapability(Capability::Int8); | |
| if (containsType(typeId, Op::OpTypeInt, 16)) | |
| addCapability(Capability::Int16); | |
| if (containsType(typeId, Op::OpTypeFloat, 16)) | |
| addCapability(Capability::Float16); | |
| return op->getResultId(); | |
| } | |
| Id Builder::createSpecConstantAlignTo(Id value, Id alignment) | |
| { | |
| Id valueModAlignment = createSpecConstantOp(Op::OpUMod, makeUintType(32), {value, alignment}, {}); | |
| Id paddingToAlignment = createSpecConstantOp(Op::OpISub, makeUintType(32), {alignment, valueModAlignment}, {}); | |
| Id padding = createSpecConstantOp(Op::OpUMod, makeUintType(32), {paddingToAlignment, alignment}, {}); | |
| return createSpecConstantOp(Op::OpIAdd, makeUintType(32), {value, padding}, {}); | |
| } | |
| Id Builder::createSpecConstantSelectMax(Id lhs, Id rhs) | |
| { | |
| Id lhsGreater = createSpecConstantOp(Op::OpUGreaterThan, makeBoolType(), {lhs, rhs}, {}); | |
| return createSpecConstantOp(Op::OpSelect, makeUintType(32), {lhsGreater, lhs, rhs}, {}); | |
| } | |
| Id Builder::createFunctionCall(spv::Function* function, const std::vector<spv::Id>& args) | |
| { | |
| Instruction* op = new Instruction(getUniqueId(), function->getReturnType(), Op::OpFunctionCall); | |
| op->reserveOperands(args.size() + 1); | |
| op->addIdOperand(function->getId()); | |
| for (int a = 0; a < (int)args.size(); ++a) | |
| op->addIdOperand(args[a]); | |
| addInstruction(std::unique_ptr<Instruction>(op)); | |
| return op->getResultId(); | |
| } | |
| // Comments in header | |
| Id Builder::createRvalueSwizzle(Decoration precision, Id typeId, Id source, const std::vector<unsigned>& channels) | |
| { | |
| if (channels.size() == 1) | |
| return setPrecision(createCompositeExtract(source, typeId, channels.front()), precision); | |
| if (generatingOpCodeForSpecConst) { | |
| std::vector<Id> operands(2); | |
| operands[0] = operands[1] = source; | |
| return setPrecision(createSpecConstantOp(Op::OpVectorShuffle, typeId, operands, channels), precision); | |
| } | |
| Instruction* swizzle = new Instruction(getUniqueId(), typeId, Op::OpVectorShuffle); | |
| assert(isVector(source)); | |
| swizzle->reserveOperands(channels.size() + 2); | |
| swizzle->addIdOperand(source); | |
| swizzle->addIdOperand(source); | |
| for (int i = 0; i < (int)channels.size(); ++i) | |
| swizzle->addImmediateOperand(channels[i]); | |
| addInstruction(std::unique_ptr<Instruction>(swizzle)); | |
| return setPrecision(swizzle->getResultId(), precision); | |
| } | |
| // Comments in header | |
| Id Builder::createLvalueSwizzle(Id typeId, Id target, Id source, const std::vector<unsigned>& channels) | |
| { | |
| if (channels.size() == 1 && getNumComponents(source) == 1) | |
| return createCompositeInsert(source, target, typeId, channels.front()); | |
| Instruction* swizzle = new Instruction(getUniqueId(), typeId, Op::OpVectorShuffle); | |
| assert(isVector(target)); | |
| swizzle->reserveOperands(2); | |
| swizzle->addIdOperand(target); | |
| assert(getNumComponents(source) == channels.size()); | |
| assert(isVector(source)); | |
| swizzle->addIdOperand(source); | |
| // Set up an identity shuffle from the base value to the result value | |
| unsigned int components[4]; | |
| int numTargetComponents = getNumComponents(target); | |
| for (int i = 0; i < numTargetComponents; ++i) | |
| components[i] = i; | |
| // Punch in the l-value swizzle | |
| for (int i = 0; i < (int)channels.size(); ++i) | |
| components[channels[i]] = numTargetComponents + i; | |
| // finish the instruction with these components selectors | |
| swizzle->reserveOperands(numTargetComponents); | |
| for (int i = 0; i < numTargetComponents; ++i) | |
| swizzle->addImmediateOperand(components[i]); | |
| addInstruction(std::unique_ptr<Instruction>(swizzle)); | |
| return swizzle->getResultId(); | |
| } | |
| // Comments in header | |
| void Builder::promoteScalar(Decoration precision, Id& left, Id& right) | |
| { | |
| // choose direction of promotion (+1 for left to right, -1 for right to left) | |
| int direction = !isScalar(right) - !isScalar(left); | |
| auto const &makeVec = [&](Id component, Id other) { | |
| if (isCooperativeVector(other)) { | |
| return makeCooperativeVectorTypeNV(getTypeId(component), getCooperativeVectorNumComponents(getTypeId(other))); | |
| } else { | |
| return makeVectorType(getTypeId(component), getNumComponents(other)); | |
| } | |
| }; | |
| if (direction > 0) | |
| left = smearScalar(precision, left, makeVec(left, right)); | |
| else if (direction < 0) | |
| right = smearScalar(precision, right, makeVec(right, left)); | |
| return; | |
| } | |
| // Comments in header | |
| Id Builder::smearScalar(Decoration precision, Id scalar, Id vectorType) | |
| { | |
| assert(getNumComponents(scalar) == 1); | |
| assert(getTypeId(scalar) == getScalarTypeId(vectorType)); | |
| int numComponents = getNumTypeComponents(vectorType); | |
| if (numComponents == 1 && !isCooperativeVectorType(vectorType) && !isVectorType(vectorType)) | |
| return scalar; | |
| Instruction* smear = nullptr; | |
| if (generatingOpCodeForSpecConst) { | |
| auto members = std::vector<spv::Id>(numComponents, scalar); | |
| // Sometime even in spec-constant-op mode, the temporary vector created by | |
| // promoting a scalar might not be a spec constant. This should depend on | |
| // the scalar. | |
| // e.g.: | |
| // const vec2 spec_const_result = a_spec_const_vec2 + a_front_end_const_scalar; | |
| // In such cases, the temporary vector created from a_front_end_const_scalar | |
| // is not a spec constant vector, even though the binary operation node is marked | |
| // as 'specConstant' and we are in spec-constant-op mode. | |
| auto result_id = makeCompositeConstant(vectorType, members, isSpecConstant(scalar)); | |
| smear = module.getInstruction(result_id); | |
| } else { | |
| bool replicate = (useReplicatedComposites || isCooperativeVectorType(vectorType)) && (numComponents > 0); | |
| if (replicate) { | |
| numComponents = 1; | |
| addCapability(spv::Capability::ReplicatedCompositesEXT); | |
| addExtension(spv::E_SPV_EXT_replicated_composites); | |
| } | |
| Op opcode = replicate ? Op::OpCompositeConstructReplicateEXT : Op::OpCompositeConstruct; | |
| smear = new Instruction(getUniqueId(), vectorType, opcode); | |
| smear->reserveOperands(numComponents); | |
| for (int c = 0; c < numComponents; ++c) | |
| smear->addIdOperand(scalar); | |
| addInstruction(std::unique_ptr<Instruction>(smear)); | |
| } | |
| return setPrecision(smear->getResultId(), precision); | |
| } | |
| // Comments in header | |
| Id Builder::createBuiltinCall(Id resultType, Id builtins, int entryPoint, const std::vector<Id>& args) | |
| { | |
| Instruction* inst = new Instruction(getUniqueId(), resultType, Op::OpExtInst); | |
| inst->reserveOperands(args.size() + 2); | |
| inst->addIdOperand(builtins); | |
| inst->addImmediateOperand(entryPoint); | |
| for (int arg = 0; arg < (int)args.size(); ++arg) | |
| inst->addIdOperand(args[arg]); | |
| addInstruction(std::unique_ptr<Instruction>(inst)); | |
| return inst->getResultId(); | |
| } | |
| // Accept all parameters needed to create a texture instruction. | |
| // Create the correct instruction based on the inputs, and make the call. | |
| Id Builder::createTextureCall(Decoration precision, Id resultType, bool sparse, bool fetch, bool proj, bool gather, | |
| bool noImplicitLod, const TextureParameters& parameters, ImageOperandsMask signExtensionMask) | |
| { | |
| std::vector<Id> texArgs; | |
| // | |
| // Set up the fixed arguments | |
| // | |
| bool explicitLod = false; | |
| texArgs.push_back(parameters.sampler); | |
| texArgs.push_back(parameters.coords); | |
| if (parameters.Dref != NoResult) | |
| texArgs.push_back(parameters.Dref); | |
| if (parameters.component != NoResult) | |
| texArgs.push_back(parameters.component); | |
| if (gather && parameters.gatherMode != NoResult) | |
| texArgs.push_back(parameters.gatherMode); | |
| if (parameters.granularity != NoResult) | |
| texArgs.push_back(parameters.granularity); | |
| if (parameters.coarse != NoResult) | |
| texArgs.push_back(parameters.coarse); | |
| // | |
| // Set up the optional arguments | |
| // | |
| size_t optArgNum = texArgs.size(); // the position of the mask for the optional arguments, if any. | |
| ImageOperandsMask mask = ImageOperandsMask::MaskNone; // the mask operand | |
| if (parameters.bias) { | |
| mask = (ImageOperandsMask)(mask | ImageOperandsMask::Bias); | |
| texArgs.push_back(parameters.bias); | |
| } | |
| if (parameters.lod) { | |
| mask = (ImageOperandsMask)(mask | ImageOperandsMask::Lod); | |
| texArgs.push_back(parameters.lod); | |
| explicitLod = true; | |
| } else if (parameters.gradX) { | |
| mask = (ImageOperandsMask)(mask | ImageOperandsMask::Grad); | |
| texArgs.push_back(parameters.gradX); | |
| texArgs.push_back(parameters.gradY); | |
| explicitLod = true; | |
| } else if (noImplicitLod && ! fetch && ! gather) { | |
| // have to explicitly use lod of 0 if not allowed to have them be implicit, and | |
| // we would otherwise be about to issue an implicit instruction | |
| mask = (ImageOperandsMask)(mask | ImageOperandsMask::Lod); | |
| texArgs.push_back(makeFloatConstant(0.0)); | |
| explicitLod = true; | |
| } | |
| if (parameters.offset) { | |
| if (isConstant(parameters.offset)) | |
| mask = (ImageOperandsMask)(mask | ImageOperandsMask::ConstOffset); | |
| else { | |
| addCapability(Capability::ImageGatherExtended); | |
| mask = (ImageOperandsMask)(mask | ImageOperandsMask::Offset); | |
| } | |
| texArgs.push_back(parameters.offset); | |
| } | |
| if (parameters.offsets) { | |
| if (!isConstant(parameters.offsets) && sourceLang == spv::SourceLanguage::GLSL) { | |
| mask = (ImageOperandsMask)(mask | ImageOperandsMask::Offsets); | |
| } else { | |
| addCapability(Capability::ImageGatherExtended); | |
| mask = (ImageOperandsMask)(mask | ImageOperandsMask::ConstOffsets); | |
| } | |
| texArgs.push_back(parameters.offsets); | |
| } | |
| if (parameters.sample) { | |
| mask = (ImageOperandsMask)(mask | ImageOperandsMask::Sample); | |
| texArgs.push_back(parameters.sample); | |
| } | |
| if (parameters.lodClamp) { | |
| // capability if this bit is used | |
| addCapability(Capability::MinLod); | |
| mask = (ImageOperandsMask)(mask | ImageOperandsMask::MinLod); | |
| texArgs.push_back(parameters.lodClamp); | |
| } | |
| if (parameters.nonprivate) { | |
| mask = mask | ImageOperandsMask::NonPrivateTexelKHR; | |
| } | |
| if (parameters.volatil) { | |
| mask = mask | ImageOperandsMask::VolatileTexelKHR; | |
| } | |
| if (parameters.nontemporal) { | |
| mask = mask | ImageOperandsMask::Nontemporal; | |
| } | |
| mask = mask | signExtensionMask; | |
| // insert the operand for the mask, if any bits were set. | |
| if (mask != ImageOperandsMask::MaskNone) | |
| texArgs.insert(texArgs.begin() + optArgNum, (Id)mask); | |
| bool isTextureGatherExtended = (gather && parameters.gatherMode != NoResult); | |
| // | |
| // Set up the instruction | |
| // | |
| Op opCode = Op::OpNop; // All paths below need to set this | |
| if (fetch) { | |
| if (sparse) | |
| opCode = Op::OpImageSparseFetch; | |
| else | |
| opCode = Op::OpImageFetch; | |
| } else if (parameters.granularity && parameters.coarse) { | |
| opCode = Op::OpImageSampleFootprintNV; | |
| } else if (gather) { | |
| if (parameters.Dref) | |
| if (sparse) | |
| opCode = Op::OpImageSparseDrefGather; | |
| else | |
| opCode = Op::OpImageDrefGather; | |
| else | |
| if (sparse) | |
| opCode = Op::OpImageSparseGather; | |
| else if (isTextureGatherExtended) | |
| opCode = Op::OpImageGatherQCOM; | |
| else | |
| opCode = Op::OpImageGather; | |
| } else if (explicitLod) { | |
| if (parameters.Dref) { | |
| if (proj) | |
| if (sparse) | |
| opCode = Op::OpImageSparseSampleProjDrefExplicitLod; | |
| else | |
| opCode = Op::OpImageSampleProjDrefExplicitLod; | |
| else | |
| if (sparse) | |
| opCode = Op::OpImageSparseSampleDrefExplicitLod; | |
| else | |
| opCode = Op::OpImageSampleDrefExplicitLod; | |
| } else { | |
| if (proj) | |
| if (sparse) | |
| opCode = Op::OpImageSparseSampleProjExplicitLod; | |
| else | |
| opCode = Op::OpImageSampleProjExplicitLod; | |
| else | |
| if (sparse) | |
| opCode = Op::OpImageSparseSampleExplicitLod; | |
| else | |
| opCode = Op::OpImageSampleExplicitLod; | |
| } | |
| } else { | |
| if (parameters.Dref) { | |
| if (proj) | |
| if (sparse) | |
| opCode = Op::OpImageSparseSampleProjDrefImplicitLod; | |
| else | |
| opCode = Op::OpImageSampleProjDrefImplicitLod; | |
| else | |
| if (sparse) | |
| opCode = Op::OpImageSparseSampleDrefImplicitLod; | |
| else | |
| opCode = Op::OpImageSampleDrefImplicitLod; | |
| } else { | |
| if (proj) | |
| if (sparse) | |
| opCode = Op::OpImageSparseSampleProjImplicitLod; | |
| else | |
| opCode = Op::OpImageSampleProjImplicitLod; | |
| else | |
| if (sparse) | |
| opCode = Op::OpImageSparseSampleImplicitLod; | |
| else | |
| opCode = Op::OpImageSampleImplicitLod; | |
| } | |
| } | |
| // See if the result type is expecting a smeared result. | |
| // This happens when a legacy shadow*() call is made, which | |
| // gets a vec4 back instead of a float. | |
| Id smearedType = resultType; | |
| if (! isScalarType(resultType)) { | |
| switch (opCode) { | |
| case Op::OpImageSampleDrefImplicitLod: | |
| case Op::OpImageSampleDrefExplicitLod: | |
| case Op::OpImageSampleProjDrefImplicitLod: | |
| case Op::OpImageSampleProjDrefExplicitLod: | |
| resultType = getScalarTypeId(resultType); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| Id typeId0 = 0; | |
| Id typeId1 = 0; | |
| if (sparse) { | |
| typeId0 = resultType; | |
| typeId1 = getDerefTypeId(parameters.texelOut); | |
| resultType = makeStructResultType(typeId0, typeId1); | |
| } | |
| // Build the SPIR-V instruction | |
| Instruction* textureInst = new Instruction(getUniqueId(), resultType, opCode); | |
| textureInst->reserveOperands(optArgNum + (texArgs.size() - (optArgNum + 1))); | |
| for (size_t op = 0; op < optArgNum; ++op) | |
| textureInst->addIdOperand(texArgs[op]); | |
| if (optArgNum < texArgs.size()) | |
| textureInst->addImmediateOperand(texArgs[optArgNum]); | |
| for (size_t op = optArgNum + 1; op < texArgs.size(); ++op) | |
| textureInst->addIdOperand(texArgs[op]); | |
| setPrecision(textureInst->getResultId(), precision); | |
| addInstruction(std::unique_ptr<Instruction>(textureInst)); | |
| Id resultId = textureInst->getResultId(); | |
| if (sparse) { | |
| // set capability | |
| addCapability(Capability::SparseResidency); | |
| // Decode the return type that was a special structure | |
| createStore(createCompositeExtract(resultId, typeId1, 1), parameters.texelOut); | |
| resultId = createCompositeExtract(resultId, typeId0, 0); | |
| setPrecision(resultId, precision); | |
| } else { | |
| // When a smear is needed, do it, as per what was computed | |
| // above when resultType was changed to a scalar type. | |
| if (resultType != smearedType) | |
| resultId = smearScalar(precision, resultId, smearedType); | |
| } | |
| return resultId; | |
| } | |
| // Comments in header | |
| Id Builder::createTextureQueryCall(Op opCode, const TextureParameters& parameters, bool isUnsignedResult) | |
| { | |
| // Figure out the result type | |
| Id resultType = 0; | |
| switch (opCode) { | |
| case Op::OpImageQuerySize: | |
| case Op::OpImageQuerySizeLod: | |
| { | |
| int numComponents = 0; | |
| switch (getTypeDimensionality(getImageType(parameters.sampler))) { | |
| case Dim::Dim1D: | |
| case Dim::Buffer: | |
| numComponents = 1; | |
| break; | |
| case Dim::Dim2D: | |
| case Dim::Cube: | |
| case Dim::Rect: | |
| case Dim::SubpassData: | |
| numComponents = 2; | |
| break; | |
| case Dim::Dim3D: | |
| numComponents = 3; | |
| break; | |
| default: | |
| assert(0); | |
| break; | |
| } | |
| if (isArrayedImageType(getImageType(parameters.sampler))) | |
| ++numComponents; | |
| Id intType = isUnsignedResult ? makeUintType(32) : makeIntType(32); | |
| if (numComponents == 1) | |
| resultType = intType; | |
| else | |
| resultType = makeVectorType(intType, numComponents); | |
| break; | |
| } | |
| case Op::OpImageQueryLod: | |
| resultType = makeVectorType(getScalarTypeId(getTypeId(parameters.coords)), 2); | |
| break; | |
| case Op::OpImageQueryLevels: | |
| case Op::OpImageQuerySamples: | |
| resultType = isUnsignedResult ? makeUintType(32) : makeIntType(32); | |
| break; | |
| default: | |
| assert(0); | |
| break; | |
| } | |
| Instruction* query = new Instruction(getUniqueId(), resultType, opCode); | |
| query->addIdOperand(parameters.sampler); | |
| if (parameters.coords) | |
| query->addIdOperand(parameters.coords); | |
| if (parameters.lod) | |
| query->addIdOperand(parameters.lod); | |
| addInstruction(std::unique_ptr<Instruction>(query)); | |
| addCapability(Capability::ImageQuery); | |
| return query->getResultId(); | |
| } | |
| // External comments in header. | |
| // Operates recursively to visit the composite's hierarchy. | |
| Id Builder::createCompositeCompare(Decoration precision, Id value1, Id value2, bool equal) | |
| { | |
| Id boolType = makeBoolType(); | |
| Id valueType = getTypeId(value1); | |
| Id resultId = NoResult; | |
| int numConstituents = getNumTypeConstituents(valueType); | |
| // Scalars and Vectors | |
| if (isScalarType(valueType) || isVectorType(valueType)) { | |
| assert(valueType == getTypeId(value2)); | |
| // These just need a single comparison, just have | |
| // to figure out what it is. | |
| Op op; | |
| switch (getMostBasicTypeClass(valueType)) { | |
| case Op::OpTypeFloat: | |
| op = equal ? Op::OpFOrdEqual : Op::OpFUnordNotEqual; | |
| break; | |
| case Op::OpTypeInt: | |
| default: | |
| op = equal ? Op::OpIEqual : Op::OpINotEqual; | |
| break; | |
| case Op::OpTypeBool: | |
| op = equal ? Op::OpLogicalEqual : Op::OpLogicalNotEqual; | |
| precision = NoPrecision; | |
| break; | |
| } | |
| if (isScalarType(valueType)) { | |
| // scalar | |
| resultId = createBinOp(op, boolType, value1, value2); | |
| } else { | |
| // vector | |
| resultId = createBinOp(op, makeVectorType(boolType, numConstituents), value1, value2); | |
| setPrecision(resultId, precision); | |
| // reduce vector compares... | |
| resultId = createUnaryOp(equal ? Op::OpAll : Op::OpAny, boolType, resultId); | |
| } | |
| return setPrecision(resultId, precision); | |
| } | |
| // Only structs, arrays, and matrices should be left. | |
| // They share in common the reduction operation across their constituents. | |
| assert(isAggregateType(valueType) || isMatrixType(valueType)); | |
| // Compare each pair of constituents | |
| for (int constituent = 0; constituent < numConstituents; ++constituent) { | |
| std::vector<unsigned> indexes(1, constituent); | |
| Id constituentType1 = getContainedTypeId(getTypeId(value1), constituent); | |
| Id constituentType2 = getContainedTypeId(getTypeId(value2), constituent); | |
| Id constituent1 = createCompositeExtract(value1, constituentType1, indexes); | |
| Id constituent2 = createCompositeExtract(value2, constituentType2, indexes); | |
| Id subResultId = createCompositeCompare(precision, constituent1, constituent2, equal); | |
| if (constituent == 0) | |
| resultId = subResultId; | |
| else | |
| resultId = setPrecision(createBinOp(equal ? Op::OpLogicalAnd : Op::OpLogicalOr, boolType, resultId, subResultId), | |
| precision); | |
| } | |
| return resultId; | |
| } | |
| // OpCompositeConstruct | |
| Id Builder::createCompositeConstruct(Id typeId, const std::vector<Id>& constituents) | |
| { | |
| assert(isAggregateType(typeId) || (getNumTypeConstituents(typeId) > 1 && | |
| getNumTypeConstituents(typeId) == constituents.size()) || | |
| ((isCooperativeVectorType(typeId) || isVectorType(typeId)) && constituents.size() == 1)); | |
| if (generatingOpCodeForSpecConst) { | |
| // Sometime, even in spec-constant-op mode, the constant composite to be | |
| // constructed may not be a specialization constant. | |
| // e.g.: | |
| // const mat2 m2 = mat2(a_spec_const, a_front_end_const, another_front_end_const, third_front_end_const); | |
| // The first column vector should be a spec constant one, as a_spec_const is a spec constant. | |
| // The second column vector should NOT be spec constant, as it does not contain any spec constants. | |
| // To handle such cases, we check the constituents of the constant vector to determine whether this | |
| // vector should be created as a spec constant. | |
| return makeCompositeConstant(typeId, constituents, | |
| std::any_of(constituents.begin(), constituents.end(), | |
| [&](spv::Id id) { return isSpecConstant(id); })); | |
| } | |
| bool replicate = false; | |
| size_t numConstituents = constituents.size(); | |
| if (useReplicatedComposites || isCooperativeVectorType(typeId)) { | |
| replicate = numConstituents > 0 && | |
| std::equal(constituents.begin() + 1, constituents.end(), constituents.begin()); | |
| } | |
| if (replicate) { | |
| numConstituents = 1; | |
| addCapability(spv::Capability::ReplicatedCompositesEXT); | |
| addExtension(spv::E_SPV_EXT_replicated_composites); | |
| } | |
| Op opcode = replicate ? Op::OpCompositeConstructReplicateEXT : Op::OpCompositeConstruct; | |
| Instruction* op = new Instruction(getUniqueId(), typeId, opcode); | |
| op->reserveOperands(constituents.size()); | |
| for (size_t c = 0; c < numConstituents; ++c) | |
| op->addIdOperand(constituents[c]); | |
| addInstruction(std::unique_ptr<Instruction>(op)); | |
| return op->getResultId(); | |
| } | |
| // coopmat conversion | |
| Id Builder::createCooperativeMatrixConversion(Id typeId, Id source) | |
| { | |
| Instruction* op = new Instruction(getUniqueId(), typeId, Op::OpCooperativeMatrixConvertNV); | |
| op->addIdOperand(source); | |
| addInstruction(std::unique_ptr<Instruction>(op)); | |
| return op->getResultId(); | |
| } | |
| // coopmat reduce | |
| Id Builder::createCooperativeMatrixReduce(Op opcode, Id typeId, Id source, unsigned int mask, Id func) | |
| { | |
| Instruction* op = new Instruction(getUniqueId(), typeId, opcode); | |
| op->addIdOperand(source); | |
| op->addImmediateOperand(mask); | |
| op->addIdOperand(func); | |
| addInstruction(std::unique_ptr<Instruction>(op)); | |
| return op->getResultId(); | |
| } | |
| // coopmat per-element operation | |
| Id Builder::createCooperativeMatrixPerElementOp(Id typeId, const std::vector<Id>& operands) | |
| { | |
| Instruction* op = new Instruction(getUniqueId(), typeId, spv::Op::OpCooperativeMatrixPerElementOpNV); | |
| // skip operand[0], which is where the result is stored | |
| for (uint32_t i = 1; i < operands.size(); ++i) { | |
| op->addIdOperand(operands[i]); | |
| } | |
| addInstruction(std::unique_ptr<Instruction>(op)); | |
| return op->getResultId(); | |
| } | |
| // Vector or scalar constructor | |
| Id Builder::createConstructor(Decoration precision, const std::vector<Id>& sources, Id resultTypeId) | |
| { | |
| Id result = NoResult; | |
| unsigned int numTargetComponents = getNumTypeComponents(resultTypeId); | |
| unsigned int targetComponent = 0; | |
| // Special case: when calling a vector constructor with a single scalar | |
| // argument, smear the scalar | |
| if (sources.size() == 1 && isScalar(sources[0]) && (numTargetComponents > 1 || isCooperativeVectorType(resultTypeId))) | |
| return smearScalar(precision, sources[0], resultTypeId); | |
| // Special case: 2 vectors of equal size | |
| if (sources.size() == 1 && | |
| (isVector(sources[0]) || isCooperativeVector(sources[0])) && | |
| numTargetComponents == getNumComponents(sources[0])) { | |
| if (isCooperativeVector(sources[0]) != isCooperativeVectorType(resultTypeId)) { | |
| assert(isVector(sources[0]) != isVectorType(resultTypeId)); | |
| return createUnaryOp(spv::Op::OpBitcast, resultTypeId, sources[0]); | |
| } else { | |
| assert(resultTypeId == getTypeId(sources[0])); | |
| return sources[0]; | |
| } | |
| } | |
| // accumulate the arguments for OpCompositeConstruct | |
| std::vector<Id> constituents; | |
| Id scalarTypeId = getScalarTypeId(resultTypeId); | |
| // lambda to store the result of visiting an argument component | |
| const auto latchResult = [&](Id comp) { | |
| if (numTargetComponents > 1 || isVectorType(resultTypeId)) | |
| constituents.push_back(comp); | |
| else | |
| result = comp; | |
| ++targetComponent; | |
| }; | |
| // lambda to visit a vector argument's components | |
| const auto accumulateVectorConstituents = [&](Id sourceArg) { | |
| unsigned int sourceSize = getNumComponents(sourceArg); | |
| unsigned int sourcesToUse = sourceSize; | |
| if (sourcesToUse + targetComponent > numTargetComponents) | |
| sourcesToUse = numTargetComponents - targetComponent; | |
| for (unsigned int s = 0; s < sourcesToUse; ++s) { | |
| std::vector<unsigned> swiz; | |
| swiz.push_back(s); | |
| latchResult(createRvalueSwizzle(precision, scalarTypeId, sourceArg, swiz)); | |
| } | |
| }; | |
| // lambda to visit a matrix argument's components | |
| const auto accumulateMatrixConstituents = [&](Id sourceArg) { | |
| unsigned int sourceSize = getNumColumns(sourceArg) * getNumRows(sourceArg); | |
| unsigned int sourcesToUse = sourceSize; | |
| if (sourcesToUse + targetComponent > numTargetComponents) | |
| sourcesToUse = numTargetComponents - targetComponent; | |
| unsigned int col = 0; | |
| unsigned int row = 0; | |
| for (unsigned int s = 0; s < sourcesToUse; ++s) { | |
| if (row >= getNumRows(sourceArg)) { | |
| row = 0; | |
| col++; | |
| } | |
| std::vector<Id> indexes; | |
| indexes.push_back(col); | |
| indexes.push_back(row); | |
| latchResult(createCompositeExtract(sourceArg, scalarTypeId, indexes)); | |
| row++; | |
| } | |
| }; | |
| // Go through the source arguments, each one could have either | |
| // a single or multiple components to contribute. | |
| for (unsigned int i = 0; i < sources.size(); ++i) { | |
| if (isScalar(sources[i]) || isPointer(sources[i])) | |
| latchResult(sources[i]); | |
| else if (isVector(sources[i]) || isCooperativeVector(sources[i])) | |
| accumulateVectorConstituents(sources[i]); | |
| else if (isMatrix(sources[i])) | |
| accumulateMatrixConstituents(sources[i]); | |
| else | |
| assert(0); | |
| if (targetComponent >= numTargetComponents) | |
| break; | |
| } | |
| // If the result is a vector, make it from the gathered constituents. | |
| if (constituents.size() > 0) { | |
| result = createCompositeConstruct(resultTypeId, constituents); | |
| return setPrecision(result, precision); | |
| } else { | |
| // Precision was set when generating this component. | |
| return result; | |
| } | |
| } | |
| // Comments in header | |
| Id Builder::createMatrixConstructor(Decoration precision, const std::vector<Id>& sources, Id resultTypeId) | |
| { | |
| Id componentTypeId = getScalarTypeId(resultTypeId); | |
| unsigned int numCols = getTypeNumColumns(resultTypeId); | |
| unsigned int numRows = getTypeNumRows(resultTypeId); | |
| Instruction* instr = module.getInstruction(componentTypeId); | |
| const unsigned bitCount = instr->getImmediateOperand(0); | |
| // Optimize matrix constructed from a bigger matrix | |
| if (isMatrix(sources[0]) && getNumColumns(sources[0]) >= numCols && getNumRows(sources[0]) >= numRows) { | |
| // To truncate the matrix to a smaller number of rows/columns, we need to: | |
| // 1. For each column, extract the column and truncate it to the required size using shuffle | |
| // 2. Assemble the resulting matrix from all columns | |
| Id matrix = sources[0]; | |
| Id columnTypeId = getContainedTypeId(resultTypeId); | |
| Id sourceColumnTypeId = getContainedTypeId(getTypeId(matrix)); | |
| std::vector<unsigned> channels; | |
| for (unsigned int row = 0; row < numRows; ++row) | |
| channels.push_back(row); | |
| std::vector<Id> matrixColumns; | |
| for (unsigned int col = 0; col < numCols; ++col) { | |
| std::vector<unsigned> indexes; | |
| indexes.push_back(col); | |
| Id colv = createCompositeExtract(matrix, sourceColumnTypeId, indexes); | |
| setPrecision(colv, precision); | |
| if (numRows != getNumRows(matrix)) { | |
| matrixColumns.push_back(createRvalueSwizzle(precision, columnTypeId, colv, channels)); | |
| } else { | |
| matrixColumns.push_back(colv); | |
| } | |
| } | |
| return setPrecision(createCompositeConstruct(resultTypeId, matrixColumns), precision); | |
| } | |
| // Detect a matrix being constructed from a repeated vector of the correct size. | |
| // Create the composite directly from it. | |
| if (sources.size() == numCols && isVector(sources[0]) && getNumComponents(sources[0]) == numRows && | |
| std::equal(sources.begin() + 1, sources.end(), sources.begin())) { | |
| return setPrecision(createCompositeConstruct(resultTypeId, sources), precision); | |
| } | |
| // Otherwise, will use a two step process | |
| // 1. make a compile-time 2D array of values | |
| // 2. construct a matrix from that array | |
| // Step 1. | |
| // initialize the array to the identity matrix | |
| Id ids[maxMatrixSize][maxMatrixSize]; | |
| Id one, zero; | |
| if (bitCount == 64) { | |
| one = makeDoubleConstant(1.0); | |
| zero = makeDoubleConstant(0.0); | |
| } else if (bitCount == 16) { | |
| one = makeFloat16Constant(1.0); | |
| zero = makeFloat16Constant(0.0); | |
| } else { | |
| one = makeFloatConstant(1.0); | |
| zero = makeFloatConstant(0.0); | |
| } | |
| for (int col = 0; col < 4; ++col) { | |
| for (int row = 0; row < 4; ++row) { | |
| if (col == row) | |
| ids[col][row] = one; | |
| else | |
| ids[col][row] = zero; | |
| } | |
| } | |
| // modify components as dictated by the arguments | |
| if (sources.size() == 1 && isScalar(sources[0])) { | |
| // a single scalar; resets the diagonals | |
| for (int col = 0; col < 4; ++col) | |
| ids[col][col] = sources[0]; | |
| } else if (isMatrix(sources[0])) { | |
| // constructing from another matrix; copy over the parts that exist in both the argument and constructee | |
| Id matrix = sources[0]; | |
| unsigned int minCols = std::min(numCols, getNumColumns(matrix)); | |
| unsigned int minRows = std::min(numRows, getNumRows(matrix)); | |
| for (unsigned int col = 0; col < minCols; ++col) { | |
| std::vector<unsigned> indexes; | |
| indexes.push_back(col); | |
| for (unsigned int row = 0; row < minRows; ++row) { | |
| indexes.push_back(row); | |
| ids[col][row] = createCompositeExtract(matrix, componentTypeId, indexes); | |
| indexes.pop_back(); | |
| setPrecision(ids[col][row], precision); | |
| } | |
| } | |
| } else { | |
| // fill in the matrix in column-major order with whatever argument components are available | |
| unsigned int row = 0; | |
| unsigned int col = 0; | |
| for (unsigned int arg = 0; arg < sources.size() && col < numCols; ++arg) { | |
| Id argComp = sources[arg]; | |
| for (unsigned int comp = 0; comp < getNumComponents(sources[arg]); ++comp) { | |
| if (getNumComponents(sources[arg]) > 1) { | |
| argComp = createCompositeExtract(sources[arg], componentTypeId, comp); | |
| setPrecision(argComp, precision); | |
| } | |
| ids[col][row++] = argComp; | |
| if (row == numRows) { | |
| row = 0; | |
| col++; | |
| } | |
| if (col == numCols) { | |
| // If more components are provided than fit the matrix, discard the rest. | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| // Step 2: Construct a matrix from that array. | |
| // First make the column vectors, then make the matrix. | |
| // make the column vectors | |
| Id columnTypeId = getContainedTypeId(resultTypeId); | |
| std::vector<Id> matrixColumns; | |
| for (unsigned int col = 0; col < numCols; ++col) { | |
| std::vector<Id> vectorComponents; | |
| for (unsigned int row = 0; row < numRows; ++row) | |
| vectorComponents.push_back(ids[col][row]); | |
| Id column = createCompositeConstruct(columnTypeId, vectorComponents); | |
| setPrecision(column, precision); | |
| matrixColumns.push_back(column); | |
| } | |
| // make the matrix | |
| return setPrecision(createCompositeConstruct(resultTypeId, matrixColumns), precision); | |
| } | |
| // Comments in header | |
| Builder::If::If(Id cond, SelectionControlMask ctrl, Builder& gb) : | |
| builder(gb), | |
| condition(cond), | |
| control(ctrl), | |
| elseBlock(nullptr) | |
| { | |
| function = &builder.getBuildPoint()->getParent(); | |
| // make the blocks, but only put the then-block into the function, | |
| // the else-block and merge-block will be added later, in order, after | |
| // earlier code is emitted | |
| thenBlock = new Block(builder.getUniqueId(), *function); | |
| mergeBlock = new Block(builder.getUniqueId(), *function); | |
| // Save the current block, so that we can add in the flow control split when | |
| // makeEndIf is called. | |
| headerBlock = builder.getBuildPoint(); | |
| builder.createSelectionMerge(mergeBlock, control); | |
| function->addBlock(thenBlock); | |
| builder.setBuildPoint(thenBlock); | |
| } | |
| // Comments in header | |
| void Builder::If::makeBeginElse() | |
| { | |
| // Close out the "then" by having it jump to the mergeBlock | |
| builder.createBranch(true, mergeBlock); | |
| // Make the first else block and add it to the function | |
| elseBlock = new Block(builder.getUniqueId(), *function); | |
| function->addBlock(elseBlock); | |
| // Start building the else block | |
| builder.setBuildPoint(elseBlock); | |
| } | |
| // Comments in header | |
| void Builder::If::makeEndIf() | |
| { | |
| // jump to the merge block | |
| builder.createBranch(true, mergeBlock); | |
| // Go back to the headerBlock and make the flow control split | |
| builder.setBuildPoint(headerBlock); | |
| if (elseBlock) | |
| builder.createConditionalBranch(condition, thenBlock, elseBlock); | |
| else | |
| builder.createConditionalBranch(condition, thenBlock, mergeBlock); | |
| // add the merge block to the function | |
| function->addBlock(mergeBlock); | |
| builder.setBuildPoint(mergeBlock); | |
| } | |
| // Comments in header | |
| void Builder::makeSwitch(Id selector, SelectionControlMask control, int numSegments, const std::vector<int>& caseValues, | |
| const std::vector<int>& valueIndexToSegment, int defaultSegment, | |
| std::vector<Block*>& segmentBlocks) | |
| { | |
| Function& function = buildPoint->getParent(); | |
| // make all the blocks | |
| for (int s = 0; s < numSegments; ++s) | |
| segmentBlocks.push_back(new Block(getUniqueId(), function)); | |
| Block* mergeBlock = new Block(getUniqueId(), function); | |
| // make and insert the switch's selection-merge instruction | |
| createSelectionMerge(mergeBlock, control); | |
| // make the switch instruction | |
| Instruction* switchInst = new Instruction(NoResult, NoType, Op::OpSwitch); | |
| switchInst->reserveOperands((caseValues.size() * 2) + 2); | |
| switchInst->addIdOperand(selector); | |
| auto defaultOrMerge = (defaultSegment >= 0) ? segmentBlocks[defaultSegment] : mergeBlock; | |
| switchInst->addIdOperand(defaultOrMerge->getId()); | |
| defaultOrMerge->addPredecessor(buildPoint); | |
| for (int i = 0; i < (int)caseValues.size(); ++i) { | |
| switchInst->addImmediateOperand(caseValues[i]); | |
| switchInst->addIdOperand(segmentBlocks[valueIndexToSegment[i]]->getId()); | |
| segmentBlocks[valueIndexToSegment[i]]->addPredecessor(buildPoint); | |
| } | |
| addInstruction(std::unique_ptr<Instruction>(switchInst)); | |
| // push the merge block | |
| switchMerges.push(mergeBlock); | |
| } | |
| // Comments in header | |
| void Builder::addSwitchBreak(bool implicit) | |
| { | |
| // branch to the top of the merge block stack | |
| createBranch(implicit, switchMerges.top()); | |
| createAndSetNoPredecessorBlock("post-switch-break"); | |
| } | |
| // Comments in header | |
| void Builder::nextSwitchSegment(std::vector<Block*>& segmentBlock, int nextSegment) | |
| { | |
| int lastSegment = nextSegment - 1; | |
| if (lastSegment >= 0) { | |
| // Close out previous segment by jumping, if necessary, to next segment | |
| if (! buildPoint->isTerminated()) | |
| createBranch(true, segmentBlock[nextSegment]); | |
| } | |
| Block* block = segmentBlock[nextSegment]; | |
| block->getParent().addBlock(block); | |
| setBuildPoint(block); | |
| } | |
| // Comments in header | |
| void Builder::endSwitch(std::vector<Block*>& /*segmentBlock*/) | |
| { | |
| // Close out previous segment by jumping, if necessary, to next segment | |
| if (! buildPoint->isTerminated()) | |
| addSwitchBreak(true); | |
| switchMerges.top()->getParent().addBlock(switchMerges.top()); | |
| setBuildPoint(switchMerges.top()); | |
| switchMerges.pop(); | |
| } | |
| Block& Builder::makeNewBlock() | |
| { | |
| Function& function = buildPoint->getParent(); | |
| auto block = new Block(getUniqueId(), function); | |
| function.addBlock(block); | |
| return *block; | |
| } | |
| Builder::LoopBlocks& Builder::makeNewLoop() | |
| { | |
| // This verbosity is needed to simultaneously get the same behavior | |
| // everywhere (id's in the same order), have a syntax that works | |
| // across lots of versions of C++, have no warnings from pedantic | |
| // compilation modes, and leave the rest of the code alone. | |
| Block& head = makeNewBlock(); | |
| Block& body = makeNewBlock(); | |
| Block& merge = makeNewBlock(); | |
| Block& continue_target = makeNewBlock(); | |
| LoopBlocks blocks(head, body, merge, continue_target); | |
| loops.push(blocks); | |
| return loops.top(); | |
| } | |
| void Builder::createLoopContinue() | |
| { | |
| createBranch(false, &loops.top().continue_target); | |
| // Set up a block for dead code. | |
| createAndSetNoPredecessorBlock("post-loop-continue"); | |
| } | |
| void Builder::createLoopExit() | |
| { | |
| createBranch(false, &loops.top().merge); | |
| // Set up a block for dead code. | |
| createAndSetNoPredecessorBlock("post-loop-break"); | |
| } | |
| void Builder::closeLoop() | |
| { | |
| loops.pop(); | |
| } | |
| void Builder::clearAccessChain() | |
| { | |
| accessChain.base = NoResult; | |
| accessChain.indexChain.clear(); | |
| accessChain.instr = NoResult; | |
| accessChain.swizzle.clear(); | |
| accessChain.component = NoResult; | |
| accessChain.preSwizzleBaseType = NoType; | |
| accessChain.isRValue = false; | |
| accessChain.coherentFlags.clear(); | |
| accessChain.alignment = 0; | |
| accessChain.descHeapInfo.descHeapBaseTy = NoResult; | |
| accessChain.descHeapInfo.descHeapBaseOffset = NoResult; | |
| accessChain.descHeapInfo.descHeapIndexChain.clear(); | |
| accessChain.descHeapInfo.descTy = NoResult; | |
| accessChain.descHeapInfo.descStorageClass = StorageClass::Max; | |
| accessChain.descHeapInfo.descReadonly = false; | |
| accessChain.descHeapInfo.descWriteonly = false; | |
| accessChain.descHeapInfo.descHeapInstId.clear(); | |
| } | |
| // Comments in header | |
| void Builder::accessChainPushSwizzle(std::vector<unsigned>& swizzle, Id preSwizzleBaseType, | |
| AccessChain::CoherentFlags coherentFlags, unsigned int alignment) | |
| { | |
| accessChain.coherentFlags |= coherentFlags; | |
| accessChain.alignment |= alignment; | |
| // swizzles can be stacked in GLSL, but simplified to a single | |
| // one here; the base type doesn't change | |
| if (accessChain.preSwizzleBaseType == NoType) | |
| accessChain.preSwizzleBaseType = preSwizzleBaseType; | |
| // if needed, propagate the swizzle for the current access chain | |
| if (accessChain.swizzle.size() > 0) { | |
| std::vector<unsigned> oldSwizzle = accessChain.swizzle; | |
| accessChain.swizzle.resize(0); | |
| for (unsigned int i = 0; i < swizzle.size(); ++i) { | |
| assert(swizzle[i] < oldSwizzle.size()); | |
| accessChain.swizzle.push_back(oldSwizzle[swizzle[i]]); | |
| } | |
| } else | |
| accessChain.swizzle = swizzle; | |
| // determine if we need to track this swizzle anymore | |
| simplifyAccessChainSwizzle(); | |
| } | |
| // Comments in header | |
| void Builder::accessChainStore(Id rvalue, Decoration nonUniform, spv::MemoryAccessMask memoryAccess, spv::Scope scope, unsigned int alignment) | |
| { | |
| assert(accessChain.isRValue == false); | |
| transferAccessChainSwizzle(true); | |
| // MeshShadingEXT outputs don't support loads, so split swizzled stores | |
| bool isMeshOutput = getStorageClass(accessChain.base) == StorageClass::Output && | |
| capabilities.find(spv::Capability::MeshShadingEXT) != capabilities.end(); | |
| // If a swizzle exists and is not full and is not dynamic, then the swizzle will be broken into individual stores. | |
| if (accessChain.swizzle.size() > 0 && | |
| ((getNumTypeComponents(getResultingAccessChainType()) != accessChain.swizzle.size() && accessChain.component == NoResult) || isMeshOutput)) { | |
| for (unsigned int i = 0; i < accessChain.swizzle.size(); ++i) { | |
| accessChain.indexChain.push_back(makeUintConstant(accessChain.swizzle[i])); | |
| accessChain.instr = NoResult; | |
| Id base = collapseAccessChain(); | |
| addDecoration(base, nonUniform); | |
| accessChain.indexChain.pop_back(); | |
| accessChain.instr = NoResult; | |
| // dynamic component should be gone | |
| assert(accessChain.component == NoResult); | |
| Id source = createCompositeExtract(rvalue, getContainedTypeId(getTypeId(rvalue)), i); | |
| // take LSB of alignment | |
| alignment = alignment & ~(alignment & (alignment-1)); | |
| if (getStorageClass(base) == StorageClass::PhysicalStorageBufferEXT) { | |
| memoryAccess = (spv::MemoryAccessMask)(memoryAccess | spv::MemoryAccessMask::Aligned); | |
| } | |
| createStore(source, base, memoryAccess, scope, alignment); | |
| } | |
| } | |
| else { | |
| Id base = collapseAccessChain(); | |
| addDecoration(base, nonUniform); | |
| Id source = rvalue; | |
| // dynamic component should be gone | |
| assert(accessChain.component == NoResult); | |
| // take LSB of alignment | |
| alignment = alignment & ~(alignment & (alignment-1)); | |
| if (getStorageClass(base) == StorageClass::PhysicalStorageBufferEXT) { | |
| memoryAccess = (spv::MemoryAccessMask)(memoryAccess | spv::MemoryAccessMask::Aligned); | |
| } | |
| // If swizzle still exists, it may be out-of-order, we must load the target vector, | |
| // extract and insert elements to perform writeMask and/or swizzle. | |
| if (accessChain.swizzle.size() > 0) { | |
| // The read-modify-write load hits the same pointer as the store, so it needs the | |
| // same memory operands. In particular a PhysicalStorageBuffer access must carry | |
| // Aligned, and postProcess() expects the operand to be present to fix up. | |
| Id tempBaseId = createLoad(base, spv::NoPrecision, memoryAccess, scope, alignment); | |
| source = createLvalueSwizzle(getTypeId(tempBaseId), tempBaseId, source, accessChain.swizzle); | |
| } | |
| createStore(source, base, memoryAccess, scope, alignment); | |
| } | |
| } | |
| // Comments in header | |
| Id Builder::accessChainLoad(Decoration precision, Decoration l_nonUniform, | |
| Decoration r_nonUniform, Id resultType, spv::MemoryAccessMask memoryAccess, | |
| spv::Scope scope, unsigned int alignment) | |
| { | |
| Id id; | |
| if (accessChain.isRValue) { | |
| // transfer access chain, but try to stay in registers | |
| transferAccessChainSwizzle(false); | |
| if (accessChain.indexChain.size() > 0) { | |
| Id swizzleBase = accessChain.preSwizzleBaseType != NoType ? accessChain.preSwizzleBaseType : resultType; | |
| // if all the accesses are constants, we can use OpCompositeExtract | |
| std::vector<unsigned> indexes; | |
| bool constant = true; | |
| for (int i = 0; i < (int)accessChain.indexChain.size(); ++i) { | |
| if (isConstantScalar(accessChain.indexChain[i])) | |
| indexes.push_back(getConstantScalar(accessChain.indexChain[i])); | |
| else { | |
| constant = false; | |
| break; | |
| } | |
| } | |
| if (constant) { | |
| id = createCompositeExtract(accessChain.base, swizzleBase, indexes); | |
| setPrecision(id, precision); | |
| } else if (isVector(accessChain.base) || isCooperativeVector(accessChain.base)) { | |
| assert(accessChain.indexChain.size() == 1); | |
| id = createVectorExtractDynamic(accessChain.base, resultType, accessChain.indexChain[0]); | |
| } else { | |
| Id lValue = NoResult; | |
| if (spvVersion >= Spv_1_4 && isValidInitializer(accessChain.base)) { | |
| // make a new function variable for this r-value, using an initializer, | |
| // and mark it as NonWritable so that downstream it can be detected as a lookup | |
| // table | |
| lValue = createVariable(NoPrecision, StorageClass::Function, getTypeId(accessChain.base), | |
| "indexable", accessChain.base); | |
| addDecoration(lValue, Decoration::NonWritable); | |
| } else { | |
| lValue = createVariable(NoPrecision, StorageClass::Function, getTypeId(accessChain.base), | |
| "indexable"); | |
| // store into it | |
| createStore(accessChain.base, lValue); | |
| } | |
| // move base to the new variable | |
| accessChain.base = lValue; | |
| accessChain.isRValue = false; | |
| // load through the access chain | |
| id = createLoad(collapseAccessChain(), precision); | |
| } | |
| } else | |
| id = accessChain.base; // no precision, it was set when this was defined | |
| } else { | |
| transferAccessChainSwizzle(true); | |
| // take LSB of alignment | |
| alignment = alignment & ~(alignment & (alignment-1)); | |
| if (getStorageClass(accessChain.base) == StorageClass::PhysicalStorageBufferEXT) { | |
| memoryAccess = (spv::MemoryAccessMask)(memoryAccess | spv::MemoryAccessMask::Aligned); | |
| } | |
| // load through the access chain | |
| id = collapseAccessChain(); | |
| // Apply nonuniform both to the access chain and the loaded value. | |
| // Buffer accesses need the access chain decorated, and this is where | |
| // loaded image types get decorated. TODO: This should maybe move to | |
| // createImageTextureFunctionCall. | |
| addDecoration(id, l_nonUniform); | |
| id = createLoad(id, precision, memoryAccess, scope, alignment); | |
| addDecoration(id, r_nonUniform); | |
| } | |
| // Done, unless there are swizzles to do | |
| if (accessChain.swizzle.size() == 0 && accessChain.component == NoResult) | |
| return id; | |
| // Do remaining swizzling | |
| // Do the basic swizzle | |
| if (accessChain.swizzle.size() > 0) { | |
| Id swizzledType = getScalarTypeId(getTypeId(id)); | |
| if (accessChain.swizzle.size() > 1) | |
| swizzledType = makeVectorType(swizzledType, (int)accessChain.swizzle.size()); | |
| id = createRvalueSwizzle(precision, swizzledType, id, accessChain.swizzle); | |
| } | |
| // Do the dynamic component | |
| if (accessChain.component != NoResult) | |
| id = setPrecision(createVectorExtractDynamic(id, resultType, accessChain.component), precision); | |
| addDecoration(id, r_nonUniform); | |
| return id; | |
| } | |
| Id Builder::accessChainGetLValue() | |
| { | |
| assert(accessChain.isRValue == false); | |
| transferAccessChainSwizzle(true); | |
| Id lvalue = collapseAccessChain(); | |
| // If swizzle exists, it is out-of-order or not full, we must load the target vector, | |
| // extract and insert elements to perform writeMask and/or swizzle. This does not | |
| // go with getting a direct l-value pointer. | |
| assert(accessChain.swizzle.size() == 0); | |
| assert(accessChain.component == NoResult); | |
| return lvalue; | |
| } | |
| // comment in header | |
| Id Builder::accessChainGetInferredType() | |
| { | |
| // anything to operate on? | |
| // for untyped pointer, it may be remapped to a descriptor heap. | |
| // for descriptor heap, its base data type will be determined later, | |
| // according to load/store results' types. | |
| if (accessChain.base == NoResult || isUntypedPointer(accessChain.base) || | |
| !accessChain.descHeapInfo.descHeapIndexChain.empty()) | |
| return NoType; | |
| Id type = getTypeId(accessChain.base); | |
| // do initial dereference | |
| if (! accessChain.isRValue) | |
| type = getContainedTypeId(type); | |
| // dereference each index | |
| for (auto it = accessChain.indexChain.cbegin(); it != accessChain.indexChain.cend(); ++it) { | |
| if (isStructType(type)) | |
| type = getContainedTypeId(type, getConstantScalar(*it)); | |
| else | |
| type = getContainedTypeId(type); | |
| } | |
| // dereference swizzle | |
| if (accessChain.swizzle.size() == 1) | |
| type = getContainedTypeId(type); | |
| else if (accessChain.swizzle.size() > 1) | |
| type = makeVectorType(getContainedTypeId(type), (int)accessChain.swizzle.size()); | |
| // dereference component selection | |
| if (accessChain.component) | |
| type = getContainedTypeId(type); | |
| return type; | |
| } | |
| void Builder::dump(std::vector<unsigned int>& out) const | |
| { | |
| // Header, before first instructions: | |
| out.push_back(MagicNumber); | |
| out.push_back(spvVersion); | |
| out.push_back(builderNumber); | |
| out.push_back(uniqueId + 1); | |
| out.push_back(0); | |
| // Capabilities | |
| for (auto it = capabilities.cbegin(); it != capabilities.cend(); ++it) { | |
| Instruction capInst(0, 0, Op::OpCapability); | |
| capInst.addImmediateOperand(*it); | |
| capInst.dump(out); | |
| } | |
| for (auto it = extensions.cbegin(); it != extensions.cend(); ++it) { | |
| Instruction extInst(0, 0, Op::OpExtension); | |
| extInst.addStringOperand(it->c_str()); | |
| extInst.dump(out); | |
| } | |
| dumpInstructions(out, imports); | |
| Instruction memInst(0, 0, Op::OpMemoryModel); | |
| memInst.addImmediateOperand(addressModel); | |
| memInst.addImmediateOperand(memoryModel); | |
| memInst.dump(out); | |
| // Instructions saved up while building: | |
| dumpInstructions(out, entryPoints); | |
| dumpInstructions(out, executionModes); | |
| // Debug instructions | |
| dumpInstructions(out, strings); | |
| dumpSourceInstructions(out); | |
| for (int e = 0; e < (int)sourceExtensions.size(); ++e) { | |
| Instruction sourceExtInst(0, 0, Op::OpSourceExtension); | |
| sourceExtInst.addStringOperand(sourceExtensions[e]); | |
| sourceExtInst.dump(out); | |
| } | |
| dumpInstructions(out, names); | |
| dumpModuleProcesses(out); | |
| // Annotation instructions | |
| dumpInstructions(out, decorations); | |
| dumpInstructions(out, constantsTypesGlobals); | |
| dumpInstructions(out, externals); | |
| // The functions | |
| module.dump(out); | |
| } | |
| // | |
| // Protected methods. | |
| // | |
| // Turn the described access chain in 'accessChain' into an instruction(s) | |
| // computing its address. This *cannot* include complex swizzles, which must | |
| // be handled after this is called. | |
| // | |
| // Can generate code. | |
| Id Builder::collapseAccessChain() | |
| { | |
| assert(accessChain.isRValue == false); | |
| // did we already emit an access chain for this? | |
| if (accessChain.instr != NoResult) | |
| return accessChain.instr; | |
| // If we have a dynamic component, we can still transfer | |
| // that into a final operand to the access chain. We need to remap the | |
| // dynamic component through the swizzle to get a new dynamic component to | |
| // update. | |
| // | |
| // This was not done in transferAccessChainSwizzle() because it might | |
| // generate code. | |
| remapDynamicSwizzle(); | |
| if (accessChain.component != NoResult) { | |
| // transfer the dynamic component to the access chain | |
| accessChain.indexChain.push_back(accessChain.component); | |
| accessChain.component = NoResult; | |
| } | |
| // note that non-trivial swizzling is left pending | |
| // do we have an access chain? | |
| if (accessChain.indexChain.size() == 0 && accessChain.descHeapInfo.descHeapIndexChain.empty()) | |
| return accessChain.base; | |
| // emit the access chain | |
| StorageClass storageClass = (StorageClass)module.getStorageClass(getTypeId(accessChain.base)); | |
| // when descHeap info is set, use another access chain process. | |
| if (isUntypedPointer(accessChain.base) || !accessChain.descHeapInfo.descHeapIndexChain.empty()) { | |
| accessChain.instr = createDescHeapAccessChain(); | |
| } else { | |
| accessChain.instr = createAccessChain(storageClass, accessChain.base, accessChain.indexChain); | |
| } | |
| return accessChain.instr; | |
| } | |
| // For a dynamic component selection of a swizzle. | |
| // | |
| // Turn the swizzle and dynamic component into just a dynamic component. | |
| // | |
| // Generates code. | |
| void Builder::remapDynamicSwizzle() | |
| { | |
| // do we have a swizzle to remap a dynamic component through? | |
| if (accessChain.component != NoResult && accessChain.swizzle.size() > 1) { | |
| // build a vector of the swizzle for the component to map into | |
| std::vector<Id> components; | |
| for (int c = 0; c < (int)accessChain.swizzle.size(); ++c) | |
| components.push_back(makeUintConstant(accessChain.swizzle[c])); | |
| Id mapType = makeVectorType(makeUintType(32), (int)accessChain.swizzle.size()); | |
| Id map = makeCompositeConstant(mapType, components); | |
| // use it | |
| accessChain.component = createVectorExtractDynamic(map, makeUintType(32), accessChain.component); | |
| accessChain.swizzle.clear(); | |
| } | |
| } | |
| // clear out swizzle if it is redundant, that is reselecting the same components | |
| // that would be present without the swizzle. | |
| void Builder::simplifyAccessChainSwizzle() | |
| { | |
| // If the swizzle has fewer components than the vector, it is subsetting, and must stay | |
| // to preserve that fact. | |
| if (getNumTypeComponents(accessChain.preSwizzleBaseType) > accessChain.swizzle.size()) | |
| return; | |
| // if components are out of order, it is a swizzle | |
| for (unsigned int i = 0; i < accessChain.swizzle.size(); ++i) { | |
| if (i != accessChain.swizzle[i]) | |
| return; | |
| } | |
| // otherwise, there is no need to track this swizzle | |
| accessChain.swizzle.clear(); | |
| if (accessChain.component == NoResult) | |
| accessChain.preSwizzleBaseType = NoType; | |
| } | |
| // To the extent any swizzling can become part of the chain | |
| // of accesses instead of a post operation, make it so. | |
| // If 'dynamic' is true, include transferring the dynamic component, | |
| // otherwise, leave it pending. | |
| // | |
| // Does not generate code. just updates the access chain. | |
| void Builder::transferAccessChainSwizzle(bool dynamic) | |
| { | |
| // non existent? | |
| if (accessChain.swizzle.size() == 0 && accessChain.component == NoResult) | |
| return; | |
| // too complex? | |
| // (this requires either a swizzle, or generating code for a dynamic component) | |
| if (accessChain.swizzle.size() > 1) | |
| return; | |
| // single component, either in the swizzle and/or dynamic component | |
| if (accessChain.swizzle.size() == 1) { | |
| assert(accessChain.component == NoResult); | |
| // handle static component selection | |
| accessChain.indexChain.push_back(makeUintConstant(accessChain.swizzle.front())); | |
| accessChain.swizzle.clear(); | |
| accessChain.preSwizzleBaseType = NoType; | |
| } else if (dynamic && accessChain.component != NoResult) { | |
| assert(accessChain.swizzle.size() == 0); | |
| // handle dynamic component | |
| accessChain.indexChain.push_back(accessChain.component); | |
| accessChain.preSwizzleBaseType = NoType; | |
| accessChain.component = NoResult; | |
| } | |
| } | |
| // Utility method for creating a new block and setting the insert point to | |
| // be in it. This is useful for flow-control operations that need a "dummy" | |
| // block proceeding them (e.g. instructions after a discard, etc). | |
| void Builder::createAndSetNoPredecessorBlock(const char* /*name*/) | |
| { | |
| Block* block = new Block(getUniqueId(), buildPoint->getParent()); | |
| block->setUnreachable(); | |
| buildPoint->getParent().addBlock(block); | |
| setBuildPoint(block); | |
| // if (name) | |
| // addName(block->getId(), name); | |
| } | |
| // Comments in header | |
| void Builder::createBranch(bool implicit, Block* block) | |
| { | |
| Instruction* branch = new Instruction(Op::OpBranch); | |
| branch->addIdOperand(block->getId()); | |
| if (implicit) { | |
| addInstructionNoDebugInfo(std::unique_ptr<Instruction>(branch)); | |
| } | |
| else { | |
| addInstruction(std::unique_ptr<Instruction>(branch)); | |
| } | |
| block->addPredecessor(buildPoint); | |
| } | |
| // Create OpConstantSizeOfEXT | |
| Id Builder::createConstantSizeOfEXT(Id typeId) | |
| { | |
| auto constantSize = constantSizeOfEXTIds.find(typeId); | |
| if (constantSize != constantSizeOfEXTIds.end()) | |
| return constantSize->second; | |
| Id resultType = makeUintType(32); | |
| Instruction* inst = new Instruction(getUniqueId(), resultType, Op::OpConstantSizeOfEXT); | |
| inst->addIdOperand(typeId); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(inst)); | |
| module.mapInstruction(inst); | |
| constantSizeOfEXTIds[typeId] = inst->getResultId(); | |
| return inst->getResultId(); | |
| } | |
| void Builder::createSelectionMerge(Block* mergeBlock, SelectionControlMask control) | |
| { | |
| Instruction* merge = new Instruction(Op::OpSelectionMerge); | |
| merge->reserveOperands(2); | |
| merge->addIdOperand(mergeBlock->getId()); | |
| merge->addImmediateOperand(control); | |
| addInstruction(std::unique_ptr<Instruction>(merge)); | |
| } | |
| void Builder::createLoopMerge(Block* mergeBlock, Block* continueBlock, LoopControlMask control, | |
| const std::vector<unsigned int>& operands) | |
| { | |
| Instruction* merge = new Instruction(Op::OpLoopMerge); | |
| merge->reserveOperands(operands.size() + 3); | |
| merge->addIdOperand(mergeBlock->getId()); | |
| merge->addIdOperand(continueBlock->getId()); | |
| merge->addImmediateOperand(control); | |
| for (int op = 0; op < (int)operands.size(); ++op) | |
| merge->addImmediateOperand(operands[op]); | |
| addInstruction(std::unique_ptr<Instruction>(merge)); | |
| } | |
| void Builder::createConditionalBranch(Id condition, Block* thenBlock, Block* elseBlock) | |
| { | |
| Instruction* branch = new Instruction(Op::OpBranchConditional); | |
| branch->reserveOperands(3); | |
| branch->addIdOperand(condition); | |
| branch->addIdOperand(thenBlock->getId()); | |
| branch->addIdOperand(elseBlock->getId()); | |
| // A conditional branch is always attached to a condition expression | |
| addInstructionNoDebugInfo(std::unique_ptr<Instruction>(branch)); | |
| thenBlock->addPredecessor(buildPoint); | |
| elseBlock->addPredecessor(buildPoint); | |
| } | |
| // OpSource | |
| // [OpSourceContinued] | |
| // ... | |
| void Builder::dumpSourceInstructions(const spv::Id fileId, const std::string& text, | |
| std::vector<unsigned int>& out) const | |
| { | |
| const int maxWordCount = 0xFFFF; | |
| const int opSourceWordCount = 4; | |
| const int nonNullBytesPerInstruction = 4 * (maxWordCount - opSourceWordCount) - 1; | |
| if (sourceLang != SourceLanguage::Unknown) { | |
| // OpSource Language Version File Source | |
| Instruction sourceInst(NoResult, NoType, Op::OpSource); | |
| sourceInst.reserveOperands(3); | |
| sourceInst.addImmediateOperand(sourceLang); | |
| sourceInst.addImmediateOperand(sourceVersion); | |
| // File operand | |
| if (fileId != NoResult) { | |
| sourceInst.addIdOperand(fileId); | |
| // Source operand | |
| if (text.size() > 0) { | |
| int nextByte = 0; | |
| std::string subString; | |
| while ((int)text.size() - nextByte > 0) { | |
| subString = text.substr(nextByte, nonNullBytesPerInstruction); | |
| if (nextByte == 0) { | |
| // OpSource | |
| sourceInst.addStringOperand(subString.c_str()); | |
| sourceInst.dump(out); | |
| } else { | |
| // OpSourcContinued | |
| Instruction sourceContinuedInst(Op::OpSourceContinued); | |
| sourceContinuedInst.addStringOperand(subString.c_str()); | |
| sourceContinuedInst.dump(out); | |
| } | |
| nextByte += nonNullBytesPerInstruction; | |
| } | |
| } else | |
| sourceInst.dump(out); | |
| } else | |
| sourceInst.dump(out); | |
| } | |
| } | |
| // Dump an OpSource[Continued] sequence for the source and every include file | |
| void Builder::dumpSourceInstructions(std::vector<unsigned int>& out) const | |
| { | |
| if (emitNonSemanticShaderDebugInfo) return; | |
| dumpSourceInstructions(mainFileId, sourceText, out); | |
| for (auto iItr = includeFiles.begin(); iItr != includeFiles.end(); ++iItr) | |
| dumpSourceInstructions(iItr->first, *iItr->second, out); | |
| } | |
| template <class Range> void Builder::dumpInstructions(std::vector<unsigned int>& out, const Range& instructions) const | |
| { | |
| for (const auto& inst : instructions) { | |
| inst->dump(out); | |
| } | |
| } | |
| void Builder::dumpModuleProcesses(std::vector<unsigned int>& out) const | |
| { | |
| for (int i = 0; i < (int)moduleProcesses.size(); ++i) { | |
| Instruction moduleProcessed(Op::OpModuleProcessed); | |
| moduleProcessed.addStringOperand(moduleProcesses[i]); | |
| moduleProcessed.dump(out); | |
| } | |
| } | |
| bool Builder::DecorationInstructionLessThan::operator()(const std::unique_ptr<Instruction>& lhs, | |
| const std::unique_ptr<Instruction>& rhs) const | |
| { | |
| // Order by the id to which the decoration applies first. This is more intuitive. | |
| assert(lhs->isIdOperand(0) && rhs->isIdOperand(0)); | |
| if (lhs->getIdOperand(0) != rhs->getIdOperand(0)) { | |
| return lhs->getIdOperand(0) < rhs->getIdOperand(0); | |
| } | |
| if (lhs->getOpCode() != rhs->getOpCode()) | |
| return lhs->getOpCode() < rhs->getOpCode(); | |
| // Now compare the operands. | |
| int minSize = std::min(lhs->getNumOperands(), rhs->getNumOperands()); | |
| for (int i = 1; i < minSize; ++i) { | |
| if (lhs->isIdOperand(i) != rhs->isIdOperand(i)) { | |
| return lhs->isIdOperand(i) < rhs->isIdOperand(i); | |
| } | |
| if (lhs->isIdOperand(i)) { | |
| if (lhs->getIdOperand(i) != rhs->getIdOperand(i)) { | |
| return lhs->getIdOperand(i) < rhs->getIdOperand(i); | |
| } | |
| } else { | |
| if (lhs->getImmediateOperand(i) != rhs->getImmediateOperand(i)) { | |
| return lhs->getImmediateOperand(i) < rhs->getImmediateOperand(i); | |
| } | |
| } | |
| } | |
| if (lhs->getNumOperands() != rhs->getNumOperands()) | |
| return lhs->getNumOperands() < rhs->getNumOperands(); | |
| // In this case they are equal. | |
| return false; | |
| } | |
| } // end spv namespace |