| // | |
| // 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" | |
| #ifndef GLSLANG_WEB | |
| #include "hex_float.h" | |
| #endif | |
| #ifndef _WIN32 | |
| #include <cstdio> | |
| #endif | |
| namespace spv { | |
| Builder::Builder(unsigned int spvVersion, unsigned int magicNumber, SpvBuildLogger* buildLogger) : | |
| spvVersion(spvVersion), | |
| source(SourceLanguageUnknown), | |
| sourceVersion(0), | |
| sourceFileStringId(NoResult), | |
| currentLine(0), | |
| currentFile(nullptr), | |
| emitOpLines(false), | |
| addressModel(AddressingModelLogical), | |
| memoryModel(MemoryModelGLSL450), | |
| builderNumber(magicNumber), | |
| buildPoint(0), | |
| uniqueId(0), | |
| entryPointFunction(0), | |
| generatingOpCodeForSpecConst(false), | |
| logger(buildLogger) | |
| { | |
| clearAccessChain(); | |
| } | |
| Builder::~Builder() | |
| { | |
| } | |
| Id Builder::import(const char* name) | |
| { | |
| Instruction* import = new Instruction(getUniqueId(), NoType, OpExtInstImport); | |
| import->addStringOperand(name); | |
| module.mapInstruction(import); | |
| imports.push_back(std::unique_ptr<Instruction>(import)); | |
| return import->getResultId(); | |
| } | |
| // Emit instruction for non-filename-based #line directives (ie. no filename | |
| // seen yet): emit an OpLine if we've been asked to emit OpLines and the line | |
| // number has changed since the last time, and is a valid line number. | |
| void Builder::setLine(int lineNum) | |
| { | |
| if (lineNum != 0 && lineNum != currentLine) { | |
| currentLine = lineNum; | |
| if (emitOpLines) | |
| addLine(sourceFileStringId, currentLine, 0); | |
| } | |
| } | |
| // If no filename, do non-filename-based #line emit. Else do filename-based emit. | |
| // Emit OpLine if we've been asked to emit OpLines and the line number or filename | |
| // has changed since the last time, and line number is valid. | |
| void Builder::setLine(int lineNum, const char* filename) | |
| { | |
| if (filename == nullptr) { | |
| setLine(lineNum); | |
| return; | |
| } | |
| if ((lineNum != 0 && lineNum != currentLine) || currentFile == nullptr || | |
| strncmp(filename, currentFile, strlen(currentFile) + 1) != 0) { | |
| currentLine = lineNum; | |
| currentFile = filename; | |
| if (emitOpLines) { | |
| spv::Id strId = getStringId(filename); | |
| addLine(strId, currentLine, 0); | |
| } | |
| } | |
| } | |
| void Builder::addLine(Id fileName, int lineNum, int column) | |
| { | |
| Instruction* line = new Instruction(OpLine); | |
| line->addIdOperand(fileName); | |
| line->addImmediateOperand(lineNum); | |
| line->addImmediateOperand(column); | |
| buildPoint->addInstruction(std::unique_ptr<Instruction>(line)); | |
| } | |
| // For creating new groupedTypes (will return old type if the requested one was already made). | |
| Id Builder::makeVoidType() | |
| { | |
| Instruction* type; | |
| if (groupedTypes[OpTypeVoid].size() == 0) { | |
| type = new Instruction(getUniqueId(), NoType, OpTypeVoid); | |
| groupedTypes[OpTypeVoid].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| } else | |
| type = groupedTypes[OpTypeVoid].back(); | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeBoolType() | |
| { | |
| Instruction* type; | |
| if (groupedTypes[OpTypeBool].size() == 0) { | |
| type = new Instruction(getUniqueId(), NoType, OpTypeBool); | |
| groupedTypes[OpTypeBool].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| } else | |
| type = groupedTypes[OpTypeBool].back(); | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeSamplerType() | |
| { | |
| Instruction* type; | |
| if (groupedTypes[OpTypeSampler].size() == 0) { | |
| type = new Instruction(getUniqueId(), NoType, OpTypeSampler); | |
| groupedTypes[OpTypeSampler].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| } else | |
| type = groupedTypes[OpTypeSampler].back(); | |
| return type->getResultId(); | |
| } | |
| Id Builder::makePointer(StorageClass storageClass, Id pointee) | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[OpTypePointer].size(); ++t) { | |
| type = groupedTypes[OpTypePointer][t]; | |
| if (type->getImmediateOperand(0) == (unsigned)storageClass && | |
| type->getIdOperand(1) == pointee) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, OpTypePointer); | |
| type->addImmediateOperand(storageClass); | |
| type->addIdOperand(pointee); | |
| groupedTypes[OpTypePointer].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| 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, OpTypeForwardPointer); | |
| type->addImmediateOperand(storageClass); | |
| 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[OpTypePointer].size(); ++t) { | |
| type = groupedTypes[OpTypePointer][t]; | |
| if (type->getImmediateOperand(0) == (unsigned)storageClass && | |
| type->getIdOperand(1) == pointee) | |
| return type->getResultId(); | |
| } | |
| type = new Instruction(forwardPointerType, NoType, OpTypePointer); | |
| type->addImmediateOperand(storageClass); | |
| type->addIdOperand(pointee); | |
| groupedTypes[OpTypePointer].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeIntegerType(int width, bool hasSign) | |
| { | |
| #ifdef GLSLANG_WEB | |
| assert(width == 32); | |
| width = 32; | |
| #endif | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[OpTypeInt].size(); ++t) { | |
| type = groupedTypes[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, OpTypeInt); | |
| type->addImmediateOperand(width); | |
| type->addImmediateOperand(hasSign ? 1 : 0); | |
| groupedTypes[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(CapabilityInt64); | |
| break; | |
| default: | |
| break; | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeFloatType(int width) | |
| { | |
| #ifdef GLSLANG_WEB | |
| assert(width == 32); | |
| width = 32; | |
| #endif | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[OpTypeFloat].size(); ++t) { | |
| type = groupedTypes[OpTypeFloat][t]; | |
| if (type->getImmediateOperand(0) == (unsigned)width) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, OpTypeFloat); | |
| type->addImmediateOperand(width); | |
| groupedTypes[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(CapabilityFloat64); | |
| break; | |
| default: | |
| break; | |
| } | |
| return type->getResultId(); | |
| } | |
| // 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. | |
| Id Builder::makeStructType(const std::vector<Id>& members, const char* name) | |
| { | |
| // 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, OpTypeStruct); | |
| for (int op = 0; op < (int)members.size(); ++op) | |
| type->addIdOperand(members[op]); | |
| groupedTypes[OpTypeStruct].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| addName(type->getResultId(), name); | |
| 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[OpTypeStruct].size(); ++t) { | |
| type = groupedTypes[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[OpTypeVector].size(); ++t) { | |
| type = groupedTypes[OpTypeVector][t]; | |
| if (type->getIdOperand(0) == component && | |
| type->getImmediateOperand(1) == (unsigned)size) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, OpTypeVector); | |
| type->addIdOperand(component); | |
| type->addImmediateOperand(size); | |
| groupedTypes[OpTypeVector].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| 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[OpTypeMatrix].size(); ++t) { | |
| type = groupedTypes[OpTypeMatrix][t]; | |
| if (type->getIdOperand(0) == column && | |
| type->getImmediateOperand(1) == (unsigned)cols) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, OpTypeMatrix); | |
| type->addIdOperand(column); | |
| type->addImmediateOperand(cols); | |
| groupedTypes[OpTypeMatrix].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeCooperativeMatrixType(Id component, Id scope, Id rows, Id cols) | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[OpTypeCooperativeMatrixNV].size(); ++t) { | |
| type = groupedTypes[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, OpTypeCooperativeMatrixNV); | |
| type->addIdOperand(component); | |
| type->addIdOperand(scope); | |
| type->addIdOperand(rows); | |
| type->addIdOperand(cols); | |
| groupedTypes[OpTypeCooperativeMatrixNV].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[OpTypeArray].size(); ++t) { | |
| type = groupedTypes[OpTypeArray][t]; | |
| if (type->getIdOperand(0) == element && | |
| type->getIdOperand(1) == sizeId) | |
| return type->getResultId(); | |
| } | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, OpTypeArray); | |
| type->addIdOperand(element); | |
| type->addIdOperand(sizeId); | |
| groupedTypes[OpTypeArray].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeRuntimeArray(Id element) | |
| { | |
| Instruction* type = new Instruction(getUniqueId(), NoType, OpTypeRuntimeArray); | |
| type->addIdOperand(element); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| 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[OpTypeFunction].size(); ++t) { | |
| type = groupedTypes[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) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, OpTypeFunction); | |
| type->addIdOperand(returnType); | |
| for (int p = 0; p < (int)paramTypes.size(); ++p) | |
| type->addIdOperand(paramTypes[p]); | |
| groupedTypes[OpTypeFunction].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeImageType(Id sampledType, Dim dim, bool depth, bool arrayed, bool ms, unsigned sampled, | |
| ImageFormat format) | |
| { | |
| assert(sampled == 1 || sampled == 2); | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[OpTypeImage].size(); ++t) { | |
| type = groupedTypes[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, OpTypeImage); | |
| 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[OpTypeImage].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| #ifndef GLSLANG_WEB | |
| // deal with capabilities | |
| switch (dim) { | |
| case DimBuffer: | |
| if (sampled == 1) | |
| addCapability(CapabilitySampledBuffer); | |
| else | |
| addCapability(CapabilityImageBuffer); | |
| break; | |
| case Dim1D: | |
| if (sampled == 1) | |
| addCapability(CapabilitySampled1D); | |
| else | |
| addCapability(CapabilityImage1D); | |
| break; | |
| case DimCube: | |
| if (arrayed) { | |
| if (sampled == 1) | |
| addCapability(CapabilitySampledCubeArray); | |
| else | |
| addCapability(CapabilityImageCubeArray); | |
| } | |
| break; | |
| case DimRect: | |
| if (sampled == 1) | |
| addCapability(CapabilitySampledRect); | |
| else | |
| addCapability(CapabilityImageRect); | |
| break; | |
| case DimSubpassData: | |
| addCapability(CapabilityInputAttachment); | |
| 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::DimSubpassData) | |
| addCapability(CapabilityStorageImageMultisample); | |
| if (arrayed) | |
| addCapability(CapabilityImageMSArray); | |
| } | |
| } | |
| #endif | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeSampledImageType(Id imageType) | |
| { | |
| // try to find it | |
| Instruction* type; | |
| for (int t = 0; t < (int)groupedTypes[OpTypeSampledImage].size(); ++t) { | |
| type = groupedTypes[OpTypeSampledImage][t]; | |
| if (type->getIdOperand(0) == imageType) | |
| return type->getResultId(); | |
| } | |
| // not found, make it | |
| type = new Instruction(getUniqueId(), NoType, OpTypeSampledImage); | |
| type->addIdOperand(imageType); | |
| groupedTypes[OpTypeSampledImage].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| return type->getResultId(); | |
| } | |
| #ifndef GLSLANG_WEB | |
| Id Builder::makeAccelerationStructureType() | |
| { | |
| Instruction *type; | |
| if (groupedTypes[OpTypeAccelerationStructureKHR].size() == 0) { | |
| type = new Instruction(getUniqueId(), NoType, OpTypeAccelerationStructureKHR); | |
| groupedTypes[OpTypeAccelerationStructureKHR].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| } else { | |
| type = groupedTypes[OpTypeAccelerationStructureKHR].back(); | |
| } | |
| return type->getResultId(); | |
| } | |
| Id Builder::makeRayQueryType() | |
| { | |
| Instruction *type; | |
| if (groupedTypes[OpTypeRayQueryProvisionalKHR].size() == 0) { | |
| type = new Instruction(getUniqueId(), NoType, OpTypeRayQueryProvisionalKHR); | |
| groupedTypes[OpTypeRayQueryProvisionalKHR].push_back(type); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(type)); | |
| module.mapInstruction(type); | |
| } else { | |
| type = groupedTypes[OpTypeRayQueryProvisionalKHR].back(); | |
| } | |
| return type->getResultId(); | |
| } | |
| #endif | |
| 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 OpTypeVector: | |
| case OpTypeMatrix: | |
| case OpTypeArray: | |
| case OpTypeRuntimeArray: | |
| return getMostBasicTypeClass(instr->getIdOperand(0)); | |
| case OpTypePointer: | |
| return getMostBasicTypeClass(instr->getIdOperand(1)); | |
| default: | |
| return typeClass; | |
| } | |
| } | |
| int Builder::getNumTypeConstituents(Id typeId) const | |
| { | |
| Instruction* instr = module.getInstruction(typeId); | |
| switch (instr->getOpCode()) | |
| { | |
| case OpTypeBool: | |
| case OpTypeInt: | |
| case OpTypeFloat: | |
| case OpTypePointer: | |
| return 1; | |
| case OpTypeVector: | |
| case OpTypeMatrix: | |
| return instr->getImmediateOperand(1); | |
| case OpTypeArray: | |
| { | |
| Id lengthId = instr->getIdOperand(1); | |
| return module.getInstruction(lengthId)->getImmediateOperand(0); | |
| } | |
| case OpTypeStruct: | |
| return instr->getNumOperands(); | |
| case 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 OpTypeVoid: | |
| case OpTypeBool: | |
| case OpTypeInt: | |
| case OpTypeFloat: | |
| case OpTypeStruct: | |
| return instr->getResultId(); | |
| case OpTypeVector: | |
| case OpTypeMatrix: | |
| case OpTypeArray: | |
| case OpTypeRuntimeArray: | |
| case OpTypePointer: | |
| 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 OpTypeVector: | |
| case OpTypeMatrix: | |
| case OpTypeArray: | |
| case OpTypeRuntimeArray: | |
| case OpTypeCooperativeMatrixNV: | |
| return instr->getIdOperand(0); | |
| case OpTypePointer: | |
| return instr->getIdOperand(1); | |
| case OpTypeStruct: | |
| return instr->getIdOperand(member); | |
| default: | |
| assert(0); | |
| return NoResult; | |
| } | |
| } | |
| // 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 OpTypeInt: | |
| case OpTypeFloat: | |
| return typeClass == typeOp && instr.getImmediateOperand(0) == width; | |
| case OpTypeStruct: | |
| for (int m = 0; m < instr.getNumOperands(); ++m) { | |
| if (containsType(instr.getIdOperand(m), typeOp, width)) | |
| return true; | |
| } | |
| return false; | |
| case OpTypePointer: | |
| return false; | |
| case OpTypeVector: | |
| case OpTypeMatrix: | |
| case OpTypeArray: | |
| case OpTypeRuntimeArray: | |
| return containsType(getContainedTypeId(typeId), typeOp, width); | |
| default: | |
| return typeClass == typeOp; | |
| } | |
| } | |
| // return true if the type is a pointer to PhysicalStorageBufferEXT or an | |
| // array of such pointers. These require restrict/aliased decorations. | |
| bool Builder::containsPhysicalStorageBufferOrArray(Id typeId) const | |
| { | |
| const Instruction& instr = *module.getInstruction(typeId); | |
| Op typeClass = instr.getOpCode(); | |
| switch (typeClass) | |
| { | |
| case OpTypePointer: | |
| return getTypeStorageClass(typeId) == StorageClassPhysicalStorageBufferEXT; | |
| case OpTypeArray: | |
| return containsPhysicalStorageBufferOrArray(getContainedTypeId(typeId)); | |
| 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) | |
| { | |
| Instruction* constant; | |
| for (int i = 0; i < (int)groupedConstants[typeClass].size(); ++i) { | |
| constant = groupedConstants[typeClass][i]; | |
| if (constant->getOpCode() == opcode && | |
| constant->getTypeId() == typeId && | |
| constant->getImmediateOperand(0) == value) | |
| return constant->getResultId(); | |
| } | |
| return 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) | |
| { | |
| Instruction* constant; | |
| for (int i = 0; i < (int)groupedConstants[typeClass].size(); ++i) { | |
| constant = groupedConstants[typeClass][i]; | |
| if (constant->getOpCode() == opcode && | |
| constant->getTypeId() == typeId && | |
| constant->getImmediateOperand(0) == v1 && | |
| constant->getImmediateOperand(1) == v2) | |
| return constant->getResultId(); | |
| } | |
| return 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 OpUndef: | |
| case OpConstantTrue: | |
| case OpConstantFalse: | |
| case OpConstant: | |
| case OpConstantComposite: | |
| case OpConstantSampler: | |
| case OpConstantNull: | |
| case OpSpecConstantTrue: | |
| case OpSpecConstantFalse: | |
| case OpSpecConstant: | |
| case OpSpecConstantComposite: | |
| case OpSpecConstantOp: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| // Return true if consuming 'opcode' means consuming a specialization constant. | |
| bool Builder::isSpecConstantOpCode(Op opcode) const | |
| { | |
| switch (opcode) { | |
| case OpSpecConstantTrue: | |
| case OpSpecConstantFalse: | |
| case OpSpecConstant: | |
| case OpSpecConstantComposite: | |
| case OpSpecConstantOp: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| Id Builder::makeBoolConstant(bool b, bool specConstant) | |
| { | |
| Id typeId = makeBoolType(); | |
| Instruction* constant; | |
| Op opcode = specConstant ? (b ? OpSpecConstantTrue : OpSpecConstantFalse) : (b ? OpConstantTrue : 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 = 0; | |
| for (int i = 0; i < (int)groupedConstants[OpTypeBool].size(); ++i) { | |
| constant = groupedConstants[OpTypeBool][i]; | |
| if (constant->getTypeId() == typeId && constant->getOpCode() == opcode) | |
| existing = constant->getResultId(); | |
| } | |
| if (existing) | |
| return existing; | |
| } | |
| // Make it | |
| Instruction* c = new Instruction(getUniqueId(), typeId, opcode); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(c)); | |
| groupedConstants[OpTypeBool].push_back(c); | |
| module.mapInstruction(c); | |
| return c->getResultId(); | |
| } | |
| Id Builder::makeIntConstant(Id typeId, unsigned value, bool specConstant) | |
| { | |
| Op opcode = specConstant ? OpSpecConstant : 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(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)); | |
| groupedConstants[OpTypeInt].push_back(c); | |
| module.mapInstruction(c); | |
| return c->getResultId(); | |
| } | |
| Id Builder::makeInt64Constant(Id typeId, unsigned long long value, bool specConstant) | |
| { | |
| Op opcode = specConstant ? OpSpecConstant : 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(OpTypeInt, opcode, typeId, op1, op2); | |
| if (existing) | |
| return existing; | |
| } | |
| Instruction* c = new Instruction(getUniqueId(), typeId, opcode); | |
| c->addImmediateOperand(op1); | |
| c->addImmediateOperand(op2); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(c)); | |
| groupedConstants[OpTypeInt].push_back(c); | |
| module.mapInstruction(c); | |
| return c->getResultId(); | |
| } | |
| Id Builder::makeFloatConstant(float f, bool specConstant) | |
| { | |
| Op opcode = specConstant ? OpSpecConstant : 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(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)); | |
| groupedConstants[OpTypeFloat].push_back(c); | |
| module.mapInstruction(c); | |
| return c->getResultId(); | |
| } | |
| Id Builder::makeDoubleConstant(double d, bool specConstant) | |
| { | |
| #ifdef GLSLANG_WEB | |
| assert(0); | |
| return NoResult; | |
| #else | |
| Op opcode = specConstant ? OpSpecConstant : 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(OpTypeFloat, opcode, typeId, op1, op2); | |
| if (existing) | |
| return existing; | |
| } | |
| Instruction* c = new Instruction(getUniqueId(), typeId, opcode); | |
| c->addImmediateOperand(op1); | |
| c->addImmediateOperand(op2); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(c)); | |
| groupedConstants[OpTypeFloat].push_back(c); | |
| module.mapInstruction(c); | |
| return c->getResultId(); | |
| #endif | |
| } | |
| Id Builder::makeFloat16Constant(float f16, bool specConstant) | |
| { | |
| #ifdef GLSLANG_WEB | |
| assert(0); | |
| return NoResult; | |
| #else | |
| Op opcode = specConstant ? OpSpecConstant : 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(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)); | |
| groupedConstants[OpTypeFloat].push_back(c); | |
| module.mapInstruction(c); | |
| return c->getResultId(); | |
| #endif | |
| } | |
| Id Builder::makeFpConstant(Id type, double d, bool specConstant) | |
| { | |
| #ifdef GLSLANG_WEB | |
| const int width = 32; | |
| assert(width == getScalarTypeWidth(type)); | |
| #else | |
| const int width = getScalarTypeWidth(type); | |
| #endif | |
| 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::findCompositeConstant(Op typeClass, Id typeId, const std::vector<Id>& comps) | |
| { | |
| Instruction* constant = 0; | |
| bool found = false; | |
| for (int i = 0; i < (int)groupedConstants[typeClass].size(); ++i) { | |
| constant = groupedConstants[typeClass][i]; | |
| if (constant->getTypeId() != typeId) | |
| continue; | |
| // same contents? | |
| bool mismatch = false; | |
| for (int op = 0; op < constant->getNumOperands(); ++op) { | |
| if (constant->getIdOperand(op) != comps[op]) { | |
| mismatch = true; | |
| break; | |
| } | |
| } | |
| if (! mismatch) { | |
| found = true; | |
| break; | |
| } | |
| } | |
| return found ? constant->getResultId() : NoResult; | |
| } | |
| Id Builder::findStructConstant(Id typeId, const std::vector<Id>& comps) | |
| { | |
| Instruction* constant = 0; | |
| bool found = false; | |
| for (int i = 0; i < (int)groupedStructConstants[typeId].size(); ++i) { | |
| constant = groupedStructConstants[typeId][i]; | |
| // same contents? | |
| bool mismatch = false; | |
| for (int op = 0; op < constant->getNumOperands(); ++op) { | |
| if (constant->getIdOperand(op) != comps[op]) { | |
| mismatch = true; | |
| break; | |
| } | |
| } | |
| if (! mismatch) { | |
| found = true; | |
| break; | |
| } | |
| } | |
| return found ? constant->getResultId() : NoResult; | |
| } | |
| // Comments in header | |
| Id Builder::makeCompositeConstant(Id typeId, const std::vector<Id>& members, bool specConstant) | |
| { | |
| Op opcode = specConstant ? OpSpecConstantComposite : OpConstantComposite; | |
| assert(typeId); | |
| Op typeClass = getTypeClass(typeId); | |
| switch (typeClass) { | |
| case OpTypeVector: | |
| case OpTypeArray: | |
| case OpTypeMatrix: | |
| case OpTypeCooperativeMatrixNV: | |
| if (! specConstant) { | |
| Id existing = findCompositeConstant(typeClass, typeId, members); | |
| if (existing) | |
| return existing; | |
| } | |
| break; | |
| case OpTypeStruct: | |
| if (! specConstant) { | |
| Id existing = findStructConstant(typeId, members); | |
| if (existing) | |
| return existing; | |
| } | |
| break; | |
| default: | |
| assert(0); | |
| return makeFloatConstant(0.0); | |
| } | |
| Instruction* c = new Instruction(getUniqueId(), typeId, opcode); | |
| for (int op = 0; op < (int)members.size(); ++op) | |
| c->addIdOperand(members[op]); | |
| constantsTypesGlobals.push_back(std::unique_ptr<Instruction>(c)); | |
| if (typeClass == OpTypeStruct) | |
| groupedStructConstants[typeId].push_back(c); | |
| else | |
| groupedConstants[typeClass].push_back(c); | |
| module.mapInstruction(c); | |
| return c->getResultId(); | |
| } | |
| Instruction* Builder::addEntryPoint(ExecutionModel model, Function* function, const char* name) | |
| { | |
| Instruction* entryPoint = new Instruction(OpEntryPoint); | |
| 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) | |
| { | |
| Instruction* instr = new Instruction(OpExecutionMode); | |
| 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::addName(Id id, const char* string) | |
| { | |
| Instruction* name = new Instruction(OpName); | |
| 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(OpMemberName); | |
| 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::DecorationMax) | |
| return; | |
| Instruction* dec = new Instruction(OpDecorate); | |
| dec->addIdOperand(id); | |
| dec->addImmediateOperand(decoration); | |
| if (num >= 0) | |
| dec->addImmediateOperand(num); | |
| decorations.push_back(std::unique_ptr<Instruction>(dec)); | |
| } | |
| void Builder::addDecoration(Id id, Decoration decoration, const char* s) | |
| { | |
| if (decoration == spv::DecorationMax) | |
| return; | |
| Instruction* dec = new Instruction(OpDecorateStringGOOGLE); | |
| dec->addIdOperand(id); | |
| dec->addImmediateOperand(decoration); | |
| dec->addStringOperand(s); | |
| decorations.push_back(std::unique_ptr<Instruction>(dec)); | |
| } | |
| void Builder::addDecorationId(Id id, Decoration decoration, Id idDecoration) | |
| { | |
| if (decoration == spv::DecorationMax) | |
| return; | |
| Instruction* dec = new Instruction(OpDecorateId); | |
| dec->addIdOperand(id); | |
| dec->addImmediateOperand(decoration); | |
| dec->addIdOperand(idDecoration); | |
| decorations.push_back(std::unique_ptr<Instruction>(dec)); | |
| } | |
| void Builder::addMemberDecoration(Id id, unsigned int member, Decoration decoration, int num) | |
| { | |
| if (decoration == spv::DecorationMax) | |
| return; | |
| Instruction* dec = new Instruction(OpMemberDecorate); | |
| dec->addIdOperand(id); | |
| dec->addImmediateOperand(member); | |
| dec->addImmediateOperand(decoration); | |
| if (num >= 0) | |
| dec->addImmediateOperand(num); | |
| decorations.push_back(std::unique_ptr<Instruction>(dec)); | |
| } | |
| void Builder::addMemberDecoration(Id id, unsigned int member, Decoration decoration, const char *s) | |
| { | |
| if (decoration == spv::DecorationMax) | |
| return; | |
| Instruction* dec = new Instruction(OpMemberDecorateStringGOOGLE); | |
| dec->addIdOperand(id); | |
| dec->addImmediateOperand(member); | |
| dec->addImmediateOperand(decoration); | |
| dec->addStringOperand(s); | |
| decorations.push_back(std::unique_ptr<Instruction>(dec)); | |
| } | |
| // Comments in header | |
| Function* Builder::makeEntryPoint(const char* entryPoint) | |
| { | |
| assert(! entryPointFunction); | |
| Block* entry; | |
| std::vector<Id> params; | |
| std::vector<std::vector<Decoration>> decorations; | |
| entryPointFunction = makeFunctionEntry(NoPrecision, makeVoidType(), entryPoint, params, decorations, &entry); | |
| return entryPointFunction; | |
| } | |
| // Comments in header | |
| Function* Builder::makeFunctionEntry(Decoration precision, Id returnType, const char* name, | |
| 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()); | |
| Function* function = new Function(getUniqueId(), returnType, typeId, firstParamId, module); | |
| // Set up the precisions | |
| setPrecision(function->getId(), 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]); | |
| if (decorations[p][d] == DecorationRelaxedPrecision) | |
| function->addReducedPrecisionParam(p); | |
| } | |
| } | |
| // CFG | |
| if (entry) { | |
| *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; | |
| } | |
| // Comments in header | |
| void Builder::makeReturn(bool implicit, Id retVal) | |
| { | |
| if (retVal) { | |
| Instruction* inst = new Instruction(NoResult, NoType, OpReturnValue); | |
| inst->addIdOperand(retVal); | |
| buildPoint->addInstruction(std::unique_ptr<Instruction>(inst)); | |
| } else | |
| buildPoint->addInstruction(std::unique_ptr<Instruction>(new Instruction(NoResult, NoType, OpReturn))); | |
| if (! implicit) | |
| createAndSetNoPredecessorBlock("post-return"); | |
| } | |
| // 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())); | |
| } | |
| } | |
| } | |
| // Comments in header | |
| void Builder::makeDiscard() | |
| { | |
| buildPoint->addInstruction(std::unique_ptr<Instruction>(new Instruction(OpKill))); | |
| createAndSetNoPredecessorBlock("post-discard"); | |
| } | |
| // Comments in header | |
| Id Builder::createVariable(StorageClass storageClass, Id type, const char* name, Id initializer) | |
| { | |
| Id pointerType = makePointer(storageClass, type); | |
| Instruction* inst = new Instruction(getUniqueId(), pointerType, OpVariable); | |
| inst->addImmediateOperand(storageClass); | |
| if (initializer != NoResult) | |
| inst->addIdOperand(initializer); | |
| switch (storageClass) { | |
| case StorageClassFunction: | |
| // 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); | |
| return inst->getResultId(); | |
| } | |
| // Comments in header | |
| Id Builder::createUndefined(Id type) | |
| { | |
| Instruction* inst = new Instruction(getUniqueId(), type, OpUndef); | |
| buildPoint->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::StorageClassUniform: | |
| case spv::StorageClassWorkgroup: | |
| case spv::StorageClassStorageBuffer: | |
| case spv::StorageClassPhysicalStorageBufferEXT: | |
| break; | |
| default: | |
| memoryAccess = spv::MemoryAccessMask(memoryAccess & | |
| ~(spv::MemoryAccessMakePointerAvailableKHRMask | | |
| spv::MemoryAccessMakePointerVisibleKHRMask | | |
| spv::MemoryAccessNonPrivatePointerKHRMask)); | |
| break; | |
| } | |
| return memoryAccess; | |
| } | |
| // Comments in header | |
| void Builder::createStore(Id rValue, Id lValue, spv::MemoryAccessMask memoryAccess, spv::Scope scope, | |
| unsigned int alignment) | |
| { | |
| Instruction* store = new Instruction(OpStore); | |
| store->addIdOperand(lValue); | |
| store->addIdOperand(rValue); | |
| memoryAccess = sanitizeMemoryAccessForStorageClass(memoryAccess, getStorageClass(lValue)); | |
| if (memoryAccess != MemoryAccessMaskNone) { | |
| store->addImmediateOperand(memoryAccess); | |
| if (memoryAccess & spv::MemoryAccessAlignedMask) { | |
| store->addImmediateOperand(alignment); | |
| } | |
| if (memoryAccess & spv::MemoryAccessMakePointerAvailableKHRMask) { | |
| store->addIdOperand(makeUintConstant(scope)); | |
| } | |
| } | |
| buildPoint->addInstruction(std::unique_ptr<Instruction>(store)); | |
| } | |
| // Comments in header | |
| Id Builder::createLoad(Id lValue, spv::MemoryAccessMask memoryAccess, spv::Scope scope, unsigned int alignment) | |
| { | |
| Instruction* load = new Instruction(getUniqueId(), getDerefTypeId(lValue), OpLoad); | |
| load->addIdOperand(lValue); | |
| memoryAccess = sanitizeMemoryAccessForStorageClass(memoryAccess, getStorageClass(lValue)); | |
| if (memoryAccess != MemoryAccessMaskNone) { | |
| load->addImmediateOperand(memoryAccess); | |
| if (memoryAccess & spv::MemoryAccessAlignedMask) { | |
| load->addImmediateOperand(alignment); | |
| } | |
| if (memoryAccess & spv::MemoryAccessMakePointerVisibleKHRMask) { | |
| load->addIdOperand(makeUintConstant(scope)); | |
| } | |
| } | |
| buildPoint->addInstruction(std::unique_ptr<Instruction>(load)); | |
| return load->getResultId(); | |
| } | |
| // Comments in header | |
| Id Builder::createAccessChain(StorageClass storageClass, Id base, const std::vector<Id>& offsets) | |
| { | |
| // Figure out the final resulting type. | |
| spv::Id typeId = getTypeId(base); | |
| assert(isPointerType(typeId) && offsets.size() > 0); | |
| typeId = getContainedTypeId(typeId); | |
| for (int i = 0; i < (int)offsets.size(); ++i) { | |
| if (isStructType(typeId)) { | |
| assert(isConstantScalar(offsets[i])); | |
| typeId = getContainedTypeId(typeId, getConstantScalar(offsets[i])); | |
| } else | |
| typeId = getContainedTypeId(typeId, offsets[i]); | |
| } | |
| typeId = makePointer(storageClass, typeId); | |
| // Make the instruction | |
| Instruction* chain = new Instruction(getUniqueId(), typeId, OpAccessChain); | |
| chain->addIdOperand(base); | |
| for (int i = 0; i < (int)offsets.size(); ++i) | |
| chain->addIdOperand(offsets[i]); | |
| buildPoint->addInstruction(std::unique_ptr<Instruction>(chain)); | |
| return chain->getResultId(); | |
| } | |
| Id Builder::createArrayLength(Id base, unsigned int member) | |
| { | |
| spv::Id intType = makeUintType(32); | |
| Instruction* length = new Instruction(getUniqueId(), intType, OpArrayLength); | |
| length->addIdOperand(base); | |
| length->addImmediateOperand(member); | |
| buildPoint->addInstruction(std::unique_ptr<Instruction>(length)); | |
| return length->getResultId(); | |
| } | |
| Id Builder::createCooperativeMatrixLength(Id type) | |
| { | |
| spv::Id intType = makeUintType(32); | |
| // Generate code for spec constants if in spec constant operation | |
| // generation mode. | |
| if (generatingOpCodeForSpecConst) { | |
| return createSpecConstantOp(OpCooperativeMatrixLengthNV, intType, std::vector<Id>(1, type), std::vector<Id>()); | |
| } | |
| Instruction* length = new Instruction(getUniqueId(), intType, OpCooperativeMatrixLengthNV); | |
| length->addIdOperand(type); | |
| buildPoint->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(OpCompositeExtract, typeId, std::vector<Id>(1, composite), | |
| std::vector<Id>(1, index)); | |
| } | |
| Instruction* extract = new Instruction(getUniqueId(), typeId, OpCompositeExtract); | |
| extract->addIdOperand(composite); | |
| extract->addImmediateOperand(index); | |
| buildPoint->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(OpCompositeExtract, typeId, std::vector<Id>(1, composite), indexes); | |
| } | |
| Instruction* extract = new Instruction(getUniqueId(), typeId, OpCompositeExtract); | |
| extract->addIdOperand(composite); | |
| for (int i = 0; i < (int)indexes.size(); ++i) | |
| extract->addImmediateOperand(indexes[i]); | |
| buildPoint->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, OpCompositeInsert); | |
| insert->addIdOperand(object); | |
| insert->addIdOperand(composite); | |
| insert->addImmediateOperand(index); | |
| buildPoint->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, OpCompositeInsert); | |
| insert->addIdOperand(object); | |
| insert->addIdOperand(composite); | |
| for (int i = 0; i < (int)indexes.size(); ++i) | |
| insert->addImmediateOperand(indexes[i]); | |
| buildPoint->addInstruction(std::unique_ptr<Instruction>(insert)); | |
| return insert->getResultId(); | |
| } | |
| Id Builder::createVectorExtractDynamic(Id vector, Id typeId, Id componentIndex) | |
| { | |
| Instruction* extract = new Instruction(getUniqueId(), typeId, OpVectorExtractDynamic); | |
| extract->addIdOperand(vector); | |
| extract->addIdOperand(componentIndex); | |
| buildPoint->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, OpVectorInsertDynamic); | |
| insert->addIdOperand(vector); | |
| insert->addIdOperand(component); | |
| insert->addIdOperand(componentIndex); | |
| buildPoint->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); | |
| buildPoint->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); | |
| buildPoint->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); | |
| for (auto it = operands.cbegin(); it != operands.cend(); ++it) { | |
| op->addIdOperand(*it); | |
| } | |
| buildPoint->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); | |
| for (auto it = operands.cbegin(); it != operands.cend(); ++it) { | |
| if (it->isId) | |
| op->addIdOperand(it->word); | |
| else | |
| op->addImmediateOperand(it->word); | |
| } | |
| buildPoint->addInstruction(std::unique_ptr<Instruction>(op)); | |
| } | |
| void Builder::createControlBarrier(Scope execution, Scope memory, MemorySemanticsMask semantics) | |
| { | |
| Instruction* op = new Instruction(OpControlBarrier); | |
| op->addIdOperand(makeUintConstant(execution)); | |
| op->addIdOperand(makeUintConstant(memory)); | |
| op->addIdOperand(makeUintConstant(semantics)); | |
| buildPoint->addInstruction(std::unique_ptr<Instruction>(op)); | |
| } | |
| void Builder::createMemoryBarrier(unsigned executionScope, unsigned memorySemantics) | |
| { | |
| Instruction* op = new Instruction(OpMemoryBarrier); | |
| op->addIdOperand(makeUintConstant(executionScope)); | |
| op->addIdOperand(makeUintConstant(memorySemantics)); | |
| buildPoint->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); | |
| buildPoint->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->addIdOperand(left); | |
| op->addIdOperand(right); | |
| buildPoint->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->addIdOperand(op1); | |
| op->addIdOperand(op2); | |
| op->addIdOperand(op3); | |
| buildPoint->addInstruction(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); | |
| for (auto it = operands.cbegin(); it != operands.cend(); ++it) | |
| op->addIdOperand(*it); | |
| buildPoint->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); | |
| for (auto it = operands.cbegin(); it != operands.cend(); ++it) { | |
| if (it->isId) | |
| op->addIdOperand(it->word); | |
| else | |
| op->addImmediateOperand(it->word); | |
| } | |
| buildPoint->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, OpSpecConstantOp); | |
| 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)); | |
| return op->getResultId(); | |
| } | |
| Id Builder::createFunctionCall(spv::Function* function, const std::vector<spv::Id>& args) | |
| { | |
| Instruction* op = new Instruction(getUniqueId(), function->getReturnType(), OpFunctionCall); | |
| op->addIdOperand(function->getId()); | |
| for (int a = 0; a < (int)args.size(); ++a) | |
| op->addIdOperand(args[a]); | |
| buildPoint->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(OpVectorShuffle, typeId, operands, channels), precision); | |
| } | |
| Instruction* swizzle = new Instruction(getUniqueId(), typeId, OpVectorShuffle); | |
| assert(isVector(source)); | |
| swizzle->addIdOperand(source); | |
| swizzle->addIdOperand(source); | |
| for (int i = 0; i < (int)channels.size(); ++i) | |
| swizzle->addImmediateOperand(channels[i]); | |
| buildPoint->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, OpVectorShuffle); | |
| assert(isVector(target)); | |
| swizzle->addIdOperand(target); | |
| assert(getNumComponents(source) == (int)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 | |
| for (int i = 0; i < numTargetComponents; ++i) | |
| swizzle->addImmediateOperand(components[i]); | |
| buildPoint->addInstruction(std::unique_ptr<Instruction>(swizzle)); | |
| return swizzle->getResultId(); | |
| } | |
| // Comments in header | |
| void Builder::promoteScalar(Decoration precision, Id& left, Id& right) | |
| { | |
| int direction = getNumComponents(right) - getNumComponents(left); | |
| if (direction > 0) | |
| left = smearScalar(precision, left, makeVectorType(getTypeId(left), getNumComponents(right))); | |
| else if (direction < 0) | |
| right = smearScalar(precision, right, makeVectorType(getTypeId(right), getNumComponents(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) | |
| 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 { | |
| smear = new Instruction(getUniqueId(), vectorType, OpCompositeConstruct); | |
| for (int c = 0; c < numComponents; ++c) | |
| smear->addIdOperand(scalar); | |
| buildPoint->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, OpExtInst); | |
| inst->addIdOperand(builtins); | |
| inst->addImmediateOperand(entryPoint); | |
| for (int arg = 0; arg < (int)args.size(); ++arg) | |
| inst->addIdOperand(args[arg]); | |
| buildPoint->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) | |
| { | |
| static const int maxTextureArgs = 10; | |
| Id texArgs[maxTextureArgs] = {}; | |
| // | |
| // Set up the fixed arguments | |
| // | |
| int numArgs = 0; | |
| bool explicitLod = false; | |
| texArgs[numArgs++] = parameters.sampler; | |
| texArgs[numArgs++] = parameters.coords; | |
| if (parameters.Dref != NoResult) | |
| texArgs[numArgs++] = parameters.Dref; | |
| if (parameters.component != NoResult) | |
| texArgs[numArgs++] = parameters.component; | |
| #ifndef GLSLANG_WEB | |
| if (parameters.granularity != NoResult) | |
| texArgs[numArgs++] = parameters.granularity; | |
| if (parameters.coarse != NoResult) | |
| texArgs[numArgs++] = parameters.coarse; | |
| #endif | |
| // | |
| // Set up the optional arguments | |
| // | |
| int optArgNum = numArgs; // track which operand, if it exists, is the mask of optional arguments | |
| ++numArgs; // speculatively make room for the mask operand | |
| ImageOperandsMask mask = ImageOperandsMaskNone; // the mask operand | |
| if (parameters.bias) { | |
| mask = (ImageOperandsMask)(mask | ImageOperandsBiasMask); | |
| texArgs[numArgs++] = parameters.bias; | |
| } | |
| if (parameters.lod) { | |
| mask = (ImageOperandsMask)(mask | ImageOperandsLodMask); | |
| texArgs[numArgs++] = parameters.lod; | |
| explicitLod = true; | |
| } else if (parameters.gradX) { | |
| mask = (ImageOperandsMask)(mask | ImageOperandsGradMask); | |
| texArgs[numArgs++] = parameters.gradX; | |
| texArgs[numArgs++] = 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 | ImageOperandsLodMask); | |
| texArgs[numArgs++] = makeFloatConstant(0.0); | |
| explicitLod = true; | |
| } | |
| if (parameters.offset) { | |
| if (isConstant(parameters.offset)) | |
| mask = (ImageOperandsMask)(mask | ImageOperandsConstOffsetMask); | |
| else { | |
| addCapability(CapabilityImageGatherExtended); | |
| mask = (ImageOperandsMask)(mask | ImageOperandsOffsetMask); | |
| } | |
| texArgs[numArgs++] = parameters.offset; | |
| } | |
| if (parameters.offsets) { | |
| addCapability(CapabilityImageGatherExtended); | |
| mask = (ImageOperandsMask)(mask | ImageOperandsConstOffsetsMask); | |
| texArgs[numArgs++] = parameters.offsets; | |
| } | |
| #ifndef GLSLANG_WEB | |
| if (parameters.sample) { | |
| mask = (ImageOperandsMask)(mask | ImageOperandsSampleMask); | |
| texArgs[numArgs++] = parameters.sample; | |
| } | |
| if (parameters.lodClamp) { | |
| // capability if this bit is used | |
| addCapability(CapabilityMinLod); | |
| mask = (ImageOperandsMask)(mask | ImageOperandsMinLodMask); | |
| texArgs[numArgs++] = parameters.lodClamp; | |
| } | |
| if (parameters.nonprivate) { | |
| mask = mask | ImageOperandsNonPrivateTexelKHRMask; | |
| } | |
| if (parameters.volatil) { | |
| mask = mask | ImageOperandsVolatileTexelKHRMask; | |
| } | |
| #endif | |
| mask = mask | signExtensionMask; | |
| if (mask == ImageOperandsMaskNone) | |
| --numArgs; // undo speculative reservation for the mask argument | |
| else | |
| texArgs[optArgNum] = mask; | |
| // | |
| // Set up the instruction | |
| // | |
| Op opCode = OpNop; // All paths below need to set this | |
| if (fetch) { | |
| if (sparse) | |
| opCode = OpImageSparseFetch; | |
| else | |
| opCode = OpImageFetch; | |
| #ifndef GLSLANG_WEB | |
| } else if (parameters.granularity && parameters.coarse) { | |
| opCode = OpImageSampleFootprintNV; | |
| } else if (gather) { | |
| if (parameters.Dref) | |
| if (sparse) | |
| opCode = OpImageSparseDrefGather; | |
| else | |
| opCode = OpImageDrefGather; | |
| else | |
| if (sparse) | |
| opCode = OpImageSparseGather; | |
| else | |
| opCode = OpImageGather; | |
| #endif | |
| } else if (explicitLod) { | |
| if (parameters.Dref) { | |
| if (proj) | |
| if (sparse) | |
| opCode = OpImageSparseSampleProjDrefExplicitLod; | |
| else | |
| opCode = OpImageSampleProjDrefExplicitLod; | |
| else | |
| if (sparse) | |
| opCode = OpImageSparseSampleDrefExplicitLod; | |
| else | |
| opCode = OpImageSampleDrefExplicitLod; | |
| } else { | |
| if (proj) | |
| if (sparse) | |
| opCode = OpImageSparseSampleProjExplicitLod; | |
| else | |
| opCode = OpImageSampleProjExplicitLod; | |
| else | |
| if (sparse) | |
| opCode = OpImageSparseSampleExplicitLod; | |
| else | |
| opCode = OpImageSampleExplicitLod; | |
| } | |
| } else { | |
| if (parameters.Dref) { | |
| if (proj) | |
| if (sparse) | |
| opCode = OpImageSparseSampleProjDrefImplicitLod; | |
| else | |
| opCode = OpImageSampleProjDrefImplicitLod; | |
| else | |
| if (sparse) | |
| opCode = OpImageSparseSampleDrefImplicitLod; | |
| else | |
| opCode = OpImageSampleDrefImplicitLod; | |
| } else { | |
| if (proj) | |
| if (sparse) | |
| opCode = OpImageSparseSampleProjImplicitLod; | |
| else | |
| opCode = OpImageSampleProjImplicitLod; | |
| else | |
| if (sparse) | |
| opCode = OpImageSparseSampleImplicitLod; | |
| else | |
| opCode = 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 OpImageSampleDrefImplicitLod: | |
| case OpImageSampleDrefExplicitLod: | |
| case OpImageSampleProjDrefImplicitLod: | |
| case 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); | |
| for (int op = 0; op < optArgNum; ++op) | |
| textureInst->addIdOperand(texArgs[op]); | |
| if (optArgNum < numArgs) | |
| textureInst->addImmediateOperand(texArgs[optArgNum]); | |
| for (int op = optArgNum + 1; op < numArgs; ++op) | |
| textureInst->addIdOperand(texArgs[op]); | |
| setPrecision(textureInst->getResultId(), precision); | |
| buildPoint->addInstruction(std::unique_ptr<Instruction>(textureInst)); | |
| Id resultId = textureInst->getResultId(); | |
| if (sparse) { | |
| // set capability | |
| addCapability(CapabilitySparseResidency); | |
| // 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 OpImageQuerySize: | |
| case OpImageQuerySizeLod: | |
| { | |
| int numComponents = 0; | |
| switch (getTypeDimensionality(getImageType(parameters.sampler))) { | |
| case Dim1D: | |
| case DimBuffer: | |
| numComponents = 1; | |
| break; | |
| case Dim2D: | |
| case DimCube: | |
| case DimRect: | |
| case DimSubpassData: | |
| numComponents = 2; | |
| break; | |
| case 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 OpImageQueryLod: | |
| resultType = makeVectorType(getScalarTypeId(getTypeId(parameters.coords)), 2); | |
| break; | |
| case OpImageQueryLevels: | |
| case 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); | |
| buildPoint->addInstruction(std::unique_ptr<Instruction>(query)); | |
| addCapability(CapabilityImageQuery); | |
| 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 OpTypeFloat: | |
| op = equal ? OpFOrdEqual : OpFUnordNotEqual; | |
| break; | |
| case OpTypeInt: | |
| default: | |
| op = equal ? OpIEqual : OpINotEqual; | |
| break; | |
| case OpTypeBool: | |
| op = equal ? OpLogicalEqual : 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 ? OpAll : 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 ? OpLogicalAnd : 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) == (int)constituents.size())); | |
| 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); })); | |
| } | |
| Instruction* op = new Instruction(getUniqueId(), typeId, OpCompositeConstruct); | |
| for (int c = 0; c < (int)constituents.size(); ++c) | |
| op->addIdOperand(constituents[c]); | |
| buildPoint->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) | |
| return smearScalar(precision, sources[0], resultTypeId); | |
| // 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) | |
| 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; | |
| int col = 0; | |
| 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])) | |
| 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); | |
| } | |
| // Comments in header | |
| Id Builder::createMatrixConstructor(Decoration precision, const std::vector<Id>& sources, Id resultTypeId) | |
| { | |
| Id componentTypeId = getScalarTypeId(resultTypeId); | |
| int numCols = getTypeNumColumns(resultTypeId); | |
| int numRows = getTypeNumRows(resultTypeId); | |
| Instruction* instr = module.getInstruction(componentTypeId); | |
| #ifdef GLSLANG_WEB | |
| const unsigned bitCount = 32; | |
| assert(bitCount == instr->getImmediateOperand(0)); | |
| #else | |
| const unsigned bitCount = instr->getImmediateOperand(0); | |
| #endif | |
| // 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 (int row = 0; row < numRows; ++row) | |
| channels.push_back(row); | |
| std::vector<Id> matrixColumns; | |
| for (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); | |
| } | |
| // 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 = (bitCount == 64 ? makeDoubleConstant(1.0) : makeFloatConstant(1.0)); | |
| Id zero = (bitCount == 64 ? makeDoubleConstant(0.0) : 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]; | |
| int minCols = std::min(numCols, getNumColumns(matrix)); | |
| int minRows = std::min(numRows, getNumRows(matrix)); | |
| for (int col = 0; col < minCols; ++col) { | |
| std::vector<unsigned> indexes; | |
| indexes.push_back(col); | |
| for (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 | |
| int row = 0; | |
| int col = 0; | |
| for (int arg = 0; arg < (int)sources.size(); ++arg) { | |
| Id argComp = sources[arg]; | |
| for (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++; | |
| } | |
| } | |
| } | |
| } | |
| // 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 (int col = 0; col < numCols; ++col) { | |
| std::vector<Id> vectorComponents; | |
| for (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, unsigned int ctrl, Builder& gb) : | |
| builder(gb), | |
| condition(cond), | |
| control(ctrl), | |
| elseBlock(0) | |
| { | |
| 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(); | |
| 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(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(mergeBlock); | |
| // Go back to the headerBlock and make the flow control split | |
| builder.setBuildPoint(headerBlock); | |
| builder.createSelectionMerge(mergeBlock, control); | |
| 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, unsigned int 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, OpSwitch); | |
| 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); | |
| } | |
| buildPoint->addInstruction(std::unique_ptr<Instruction>(switchInst)); | |
| // push the merge block | |
| switchMerges.push(mergeBlock); | |
| } | |
| // Comments in header | |
| void Builder::addSwitchBreak() | |
| { | |
| // branch to the top of the merge block stack | |
| createBranch(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(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(); | |
| 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(&loops.top().continue_target); | |
| // Set up a block for dead code. | |
| createAndSetNoPredecessorBlock("post-loop-continue"); | |
| } | |
| void Builder::createLoopExit() | |
| { | |
| createBranch(&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; | |
| } | |
| // 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, spv::MemoryAccessMask memoryAccess, spv::Scope scope, unsigned int alignment) | |
| { | |
| assert(accessChain.isRValue == false); | |
| transferAccessChainSwizzle(true); | |
| Id base = collapseAccessChain(); | |
| Id source = rvalue; | |
| // dynamic component should be gone | |
| assert(accessChain.component == NoResult); | |
| // If swizzle still 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. | |
| if (accessChain.swizzle.size() > 0) { | |
| Id tempBaseId = createLoad(base); | |
| source = createLvalueSwizzle(getTypeId(tempBaseId), tempBaseId, source, accessChain.swizzle); | |
| } | |
| // take LSB of alignment | |
| alignment = alignment & ~(alignment & (alignment-1)); | |
| if (getStorageClass(base) == StorageClassPhysicalStorageBufferEXT) { | |
| memoryAccess = (spv::MemoryAccessMask)(memoryAccess | spv::MemoryAccessAlignedMask); | |
| } | |
| createStore(source, base, memoryAccess, scope, alignment); | |
| } | |
| // Comments in header | |
| Id Builder::accessChainLoad(Decoration precision, Decoration 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); | |
| } else { | |
| Id lValue = NoResult; | |
| if (spvVersion >= Spv_1_4) { | |
| // 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(StorageClassFunction, getTypeId(accessChain.base), "indexable", | |
| accessChain.base); | |
| addDecoration(lValue, DecorationNonWritable); | |
| } else { | |
| lValue = createVariable(StorageClassFunction, 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()); | |
| } | |
| setPrecision(id, 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) == StorageClassPhysicalStorageBufferEXT) { | |
| memoryAccess = (spv::MemoryAccessMask)(memoryAccess | spv::MemoryAccessAlignedMask); | |
| } | |
| // 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, nonUniform); | |
| id = createLoad(id, memoryAccess, scope, alignment); | |
| setPrecision(id, precision); | |
| addDecoration(id, 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, 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? | |
| if (accessChain.base == NoResult) | |
| 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, OpCapability); | |
| capInst.addImmediateOperand(*it); | |
| capInst.dump(out); | |
| } | |
| for (auto it = extensions.cbegin(); it != extensions.cend(); ++it) { | |
| Instruction extInst(0, 0, OpExtension); | |
| extInst.addStringOperand(it->c_str()); | |
| extInst.dump(out); | |
| } | |
| dumpInstructions(out, imports); | |
| Instruction memInst(0, 0, 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, 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) | |
| return accessChain.base; | |
| // emit the access chain | |
| StorageClass storageClass = (StorageClass)module.getStorageClass(getTypeId(accessChain.base)); | |
| 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) > (int)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(Block* block) | |
| { | |
| Instruction* branch = new Instruction(OpBranch); | |
| branch->addIdOperand(block->getId()); | |
| buildPoint->addInstruction(std::unique_ptr<Instruction>(branch)); | |
| block->addPredecessor(buildPoint); | |
| } | |
| void Builder::createSelectionMerge(Block* mergeBlock, unsigned int control) | |
| { | |
| Instruction* merge = new Instruction(OpSelectionMerge); | |
| merge->addIdOperand(mergeBlock->getId()); | |
| merge->addImmediateOperand(control); | |
| buildPoint->addInstruction(std::unique_ptr<Instruction>(merge)); | |
| } | |
| void Builder::createLoopMerge(Block* mergeBlock, Block* continueBlock, unsigned int control, | |
| const std::vector<unsigned int>& operands) | |
| { | |
| Instruction* merge = new Instruction(OpLoopMerge); | |
| merge->addIdOperand(mergeBlock->getId()); | |
| merge->addIdOperand(continueBlock->getId()); | |
| merge->addImmediateOperand(control); | |
| for (int op = 0; op < (int)operands.size(); ++op) | |
| merge->addImmediateOperand(operands[op]); | |
| buildPoint->addInstruction(std::unique_ptr<Instruction>(merge)); | |
| } | |
| void Builder::createConditionalBranch(Id condition, Block* thenBlock, Block* elseBlock) | |
| { | |
| Instruction* branch = new Instruction(OpBranchConditional); | |
| branch->addIdOperand(condition); | |
| branch->addIdOperand(thenBlock->getId()); | |
| branch->addIdOperand(elseBlock->getId()); | |
| buildPoint->addInstruction(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 (source != SourceLanguageUnknown) { | |
| // OpSource Language Version File Source | |
| Instruction sourceInst(NoResult, NoType, OpSource); | |
| sourceInst.addImmediateOperand(source); | |
| 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(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 | |
| { | |
| dumpSourceInstructions(sourceFileStringId, sourceText, out); | |
| for (auto iItr = includeFiles.begin(); iItr != includeFiles.end(); ++iItr) | |
| dumpSourceInstructions(iItr->first, *iItr->second, out); | |
| } | |
| void Builder::dumpInstructions(std::vector<unsigned int>& out, | |
| const std::vector<std::unique_ptr<Instruction> >& instructions) const | |
| { | |
| for (int i = 0; i < (int)instructions.size(); ++i) { | |
| instructions[i]->dump(out); | |
| } | |
| } | |
| void Builder::dumpModuleProcesses(std::vector<unsigned int>& out) const | |
| { | |
| for (int i = 0; i < (int)moduleProcesses.size(); ++i) { | |
| Instruction moduleProcessed(OpModuleProcessed); | |
| moduleProcessed.addStringOperand(moduleProcesses[i]); | |
| moduleProcessed.dump(out); | |
| } | |
| } | |
| }; // end spv namespace |