mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-23 06:15:38 +00:00
839 lines
37 KiB
C++
839 lines
37 KiB
C++
//
|
|
// Copyright (C) 2014-2015 LunarG, Inc.
|
|
// Copyright (C) 2015-2020 Google, Inc.
|
|
// Copyright (C) 2017 ARM Limited.
|
|
// 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.
|
|
|
|
//
|
|
// "Builder" is an interface to fully build SPIR-V IR. Allocate one of
|
|
// these to build (a thread safe) internal SPIR-V representation (IR),
|
|
// and then dump it as a binary stream according to the SPIR-V specification.
|
|
//
|
|
// A Builder has a 1:1 relationship with a SPIR-V module.
|
|
//
|
|
|
|
#pragma once
|
|
#ifndef SpvBuilder_H
|
|
#define SpvBuilder_H
|
|
|
|
#include "Logger.h"
|
|
#include "spirv.hpp"
|
|
#include "spvIR.h"
|
|
|
|
#include <algorithm>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <set>
|
|
#include <sstream>
|
|
#include <stack>
|
|
#include <unordered_map>
|
|
#include <map>
|
|
|
|
namespace spv {
|
|
|
|
typedef enum {
|
|
Spv_1_0 = (1 << 16),
|
|
Spv_1_1 = (1 << 16) | (1 << 8),
|
|
Spv_1_2 = (1 << 16) | (2 << 8),
|
|
Spv_1_3 = (1 << 16) | (3 << 8),
|
|
Spv_1_4 = (1 << 16) | (4 << 8),
|
|
Spv_1_5 = (1 << 16) | (5 << 8),
|
|
} SpvVersion;
|
|
|
|
class Builder {
|
|
public:
|
|
Builder(unsigned int spvVersion, unsigned int userNumber, SpvBuildLogger* logger);
|
|
virtual ~Builder();
|
|
|
|
static const int maxMatrixSize = 4;
|
|
|
|
unsigned int getSpvVersion() const { return spvVersion; }
|
|
|
|
void setSource(spv::SourceLanguage lang, int version)
|
|
{
|
|
source = lang;
|
|
sourceVersion = version;
|
|
}
|
|
spv::Id getStringId(const std::string& str)
|
|
{
|
|
auto sItr = stringIds.find(str);
|
|
if (sItr != stringIds.end())
|
|
return sItr->second;
|
|
spv::Id strId = getUniqueId();
|
|
Instruction* fileString = new Instruction(strId, NoType, OpString);
|
|
const char* file_c_str = str.c_str();
|
|
fileString->addStringOperand(file_c_str);
|
|
strings.push_back(std::unique_ptr<Instruction>(fileString));
|
|
module.mapInstruction(fileString);
|
|
stringIds[file_c_str] = strId;
|
|
return strId;
|
|
}
|
|
void setSourceFile(const std::string& file)
|
|
{
|
|
sourceFileStringId = getStringId(file);
|
|
}
|
|
void setSourceText(const std::string& text) { sourceText = text; }
|
|
void addSourceExtension(const char* ext) { sourceExtensions.push_back(ext); }
|
|
void addModuleProcessed(const std::string& p) { moduleProcesses.push_back(p.c_str()); }
|
|
void setEmitOpLines() { emitOpLines = true; }
|
|
void addExtension(const char* ext) { extensions.insert(ext); }
|
|
void removeExtension(const char* ext)
|
|
{
|
|
extensions.erase(ext);
|
|
}
|
|
void addIncorporatedExtension(const char* ext, SpvVersion incorporatedVersion)
|
|
{
|
|
if (getSpvVersion() < static_cast<unsigned>(incorporatedVersion))
|
|
addExtension(ext);
|
|
}
|
|
void promoteIncorporatedExtension(const char* baseExt, const char* promoExt, SpvVersion incorporatedVersion)
|
|
{
|
|
removeExtension(baseExt);
|
|
addIncorporatedExtension(promoExt, incorporatedVersion);
|
|
}
|
|
void addInclude(const std::string& name, const std::string& text)
|
|
{
|
|
spv::Id incId = getStringId(name);
|
|
includeFiles[incId] = &text;
|
|
}
|
|
Id import(const char*);
|
|
void setMemoryModel(spv::AddressingModel addr, spv::MemoryModel mem)
|
|
{
|
|
addressModel = addr;
|
|
memoryModel = mem;
|
|
}
|
|
|
|
void addCapability(spv::Capability cap) { capabilities.insert(cap); }
|
|
|
|
// To get a new <id> for anything needing a new one.
|
|
Id getUniqueId() { return ++uniqueId; }
|
|
|
|
// To get a set of new <id>s, e.g., for a set of function parameters
|
|
Id getUniqueIds(int numIds)
|
|
{
|
|
Id id = uniqueId + 1;
|
|
uniqueId += numIds;
|
|
return id;
|
|
}
|
|
|
|
// Generate OpLine for non-filename-based #line directives (ie no filename
|
|
// seen yet): Log the current line, and if different than the last one,
|
|
// issue a new OpLine using the new line and current source file name.
|
|
void setLine(int line);
|
|
|
|
// If filename null, generate OpLine for non-filename-based line directives,
|
|
// else do filename-based: Log the current line and file, and if different
|
|
// than the last one, issue a new OpLine using the new line and file
|
|
// name.
|
|
void setLine(int line, const char* filename);
|
|
// Low-level OpLine. See setLine() for a layered helper.
|
|
void addLine(Id fileName, int line, int column);
|
|
|
|
// For creating new types (will return old type if the requested one was already made).
|
|
Id makeVoidType();
|
|
Id makeBoolType();
|
|
Id makePointer(StorageClass, Id pointee);
|
|
Id makeForwardPointer(StorageClass);
|
|
Id makePointerFromForwardPointer(StorageClass, Id forwardPointerType, Id pointee);
|
|
Id makeIntegerType(int width, bool hasSign); // generic
|
|
Id makeIntType(int width) { return makeIntegerType(width, true); }
|
|
Id makeUintType(int width) { return makeIntegerType(width, false); }
|
|
Id makeFloatType(int width);
|
|
Id makeStructType(const std::vector<Id>& members, const char*);
|
|
Id makeStructResultType(Id type0, Id type1);
|
|
Id makeVectorType(Id component, int size);
|
|
Id makeMatrixType(Id component, int cols, int rows);
|
|
Id makeArrayType(Id element, Id sizeId, int stride); // 0 stride means no stride decoration
|
|
Id makeRuntimeArray(Id element);
|
|
Id makeFunctionType(Id returnType, const std::vector<Id>& paramTypes);
|
|
Id makeImageType(Id sampledType, Dim, bool depth, bool arrayed, bool ms, unsigned sampled, ImageFormat format);
|
|
Id makeSamplerType();
|
|
Id makeSampledImageType(Id imageType);
|
|
Id makeCooperativeMatrixType(Id component, Id scope, Id rows, Id cols);
|
|
|
|
// accelerationStructureNV type
|
|
Id makeAccelerationStructureType();
|
|
// rayQueryEXT type
|
|
Id makeRayQueryType();
|
|
|
|
// For querying about types.
|
|
Id getTypeId(Id resultId) const { return module.getTypeId(resultId); }
|
|
Id getDerefTypeId(Id resultId) const;
|
|
Op getOpCode(Id id) const { return module.getInstruction(id)->getOpCode(); }
|
|
Op getTypeClass(Id typeId) const { return getOpCode(typeId); }
|
|
Op getMostBasicTypeClass(Id typeId) const;
|
|
int getNumComponents(Id resultId) const { return getNumTypeComponents(getTypeId(resultId)); }
|
|
int getNumTypeConstituents(Id typeId) const;
|
|
int getNumTypeComponents(Id typeId) const { return getNumTypeConstituents(typeId); }
|
|
Id getScalarTypeId(Id typeId) const;
|
|
Id getContainedTypeId(Id typeId) const;
|
|
Id getContainedTypeId(Id typeId, int) const;
|
|
StorageClass getTypeStorageClass(Id typeId) const { return module.getStorageClass(typeId); }
|
|
ImageFormat getImageTypeFormat(Id typeId) const
|
|
{ return (ImageFormat)module.getInstruction(typeId)->getImmediateOperand(6); }
|
|
|
|
bool isPointer(Id resultId) const { return isPointerType(getTypeId(resultId)); }
|
|
bool isScalar(Id resultId) const { return isScalarType(getTypeId(resultId)); }
|
|
bool isVector(Id resultId) const { return isVectorType(getTypeId(resultId)); }
|
|
bool isMatrix(Id resultId) const { return isMatrixType(getTypeId(resultId)); }
|
|
bool isCooperativeMatrix(Id resultId)const { return isCooperativeMatrixType(getTypeId(resultId)); }
|
|
bool isAggregate(Id resultId) const { return isAggregateType(getTypeId(resultId)); }
|
|
bool isSampledImage(Id resultId) const { return isSampledImageType(getTypeId(resultId)); }
|
|
|
|
bool isBoolType(Id typeId)
|
|
{ return groupedTypes[OpTypeBool].size() > 0 && typeId == groupedTypes[OpTypeBool].back()->getResultId(); }
|
|
bool isIntType(Id typeId) const
|
|
{ return getTypeClass(typeId) == OpTypeInt && module.getInstruction(typeId)->getImmediateOperand(1) != 0; }
|
|
bool isUintType(Id typeId) const
|
|
{ return getTypeClass(typeId) == OpTypeInt && module.getInstruction(typeId)->getImmediateOperand(1) == 0; }
|
|
bool isFloatType(Id typeId) const { return getTypeClass(typeId) == OpTypeFloat; }
|
|
bool isPointerType(Id typeId) const { return getTypeClass(typeId) == OpTypePointer; }
|
|
bool isScalarType(Id typeId) const
|
|
{ return getTypeClass(typeId) == OpTypeFloat || getTypeClass(typeId) == OpTypeInt ||
|
|
getTypeClass(typeId) == OpTypeBool; }
|
|
bool isVectorType(Id typeId) const { return getTypeClass(typeId) == OpTypeVector; }
|
|
bool isMatrixType(Id typeId) const { return getTypeClass(typeId) == OpTypeMatrix; }
|
|
bool isStructType(Id typeId) const { return getTypeClass(typeId) == OpTypeStruct; }
|
|
bool isArrayType(Id typeId) const { return getTypeClass(typeId) == OpTypeArray; }
|
|
#ifdef GLSLANG_WEB
|
|
bool isCooperativeMatrixType(Id typeId)const { return false; }
|
|
#else
|
|
bool isCooperativeMatrixType(Id typeId)const { return getTypeClass(typeId) == OpTypeCooperativeMatrixNV; }
|
|
#endif
|
|
bool isAggregateType(Id typeId) const
|
|
{ return isArrayType(typeId) || isStructType(typeId) || isCooperativeMatrixType(typeId); }
|
|
bool isImageType(Id typeId) const { return getTypeClass(typeId) == OpTypeImage; }
|
|
bool isSamplerType(Id typeId) const { return getTypeClass(typeId) == OpTypeSampler; }
|
|
bool isSampledImageType(Id typeId) const { return getTypeClass(typeId) == OpTypeSampledImage; }
|
|
bool containsType(Id typeId, Op typeOp, unsigned int width) const;
|
|
bool containsPhysicalStorageBufferOrArray(Id typeId) const;
|
|
|
|
bool isConstantOpCode(Op opcode) const;
|
|
bool isSpecConstantOpCode(Op opcode) const;
|
|
bool isConstant(Id resultId) const { return isConstantOpCode(getOpCode(resultId)); }
|
|
bool isConstantScalar(Id resultId) const { return getOpCode(resultId) == OpConstant; }
|
|
bool isSpecConstant(Id resultId) const { return isSpecConstantOpCode(getOpCode(resultId)); }
|
|
unsigned int getConstantScalar(Id resultId) const
|
|
{ return module.getInstruction(resultId)->getImmediateOperand(0); }
|
|
StorageClass getStorageClass(Id resultId) const { return getTypeStorageClass(getTypeId(resultId)); }
|
|
|
|
int getScalarTypeWidth(Id typeId) const
|
|
{
|
|
Id scalarTypeId = getScalarTypeId(typeId);
|
|
assert(getTypeClass(scalarTypeId) == OpTypeInt || getTypeClass(scalarTypeId) == OpTypeFloat);
|
|
return module.getInstruction(scalarTypeId)->getImmediateOperand(0);
|
|
}
|
|
|
|
int getTypeNumColumns(Id typeId) const
|
|
{
|
|
assert(isMatrixType(typeId));
|
|
return getNumTypeConstituents(typeId);
|
|
}
|
|
int getNumColumns(Id resultId) const { return getTypeNumColumns(getTypeId(resultId)); }
|
|
int getTypeNumRows(Id typeId) const
|
|
{
|
|
assert(isMatrixType(typeId));
|
|
return getNumTypeComponents(getContainedTypeId(typeId));
|
|
}
|
|
int getNumRows(Id resultId) const { return getTypeNumRows(getTypeId(resultId)); }
|
|
|
|
Dim getTypeDimensionality(Id typeId) const
|
|
{
|
|
assert(isImageType(typeId));
|
|
return (Dim)module.getInstruction(typeId)->getImmediateOperand(1);
|
|
}
|
|
Id getImageType(Id resultId) const
|
|
{
|
|
Id typeId = getTypeId(resultId);
|
|
assert(isImageType(typeId) || isSampledImageType(typeId));
|
|
return isSampledImageType(typeId) ? module.getInstruction(typeId)->getIdOperand(0) : typeId;
|
|
}
|
|
bool isArrayedImageType(Id typeId) const
|
|
{
|
|
assert(isImageType(typeId));
|
|
return module.getInstruction(typeId)->getImmediateOperand(3) != 0;
|
|
}
|
|
|
|
// For making new constants (will return old constant if the requested one was already made).
|
|
Id makeBoolConstant(bool b, bool specConstant = false);
|
|
Id makeInt8Constant(int i, bool specConstant = false)
|
|
{ return makeIntConstant(makeIntType(8), (unsigned)i, specConstant); }
|
|
Id makeUint8Constant(unsigned u, bool specConstant = false)
|
|
{ return makeIntConstant(makeUintType(8), u, specConstant); }
|
|
Id makeInt16Constant(int i, bool specConstant = false)
|
|
{ return makeIntConstant(makeIntType(16), (unsigned)i, specConstant); }
|
|
Id makeUint16Constant(unsigned u, bool specConstant = false)
|
|
{ return makeIntConstant(makeUintType(16), u, specConstant); }
|
|
Id makeIntConstant(int i, bool specConstant = false)
|
|
{ return makeIntConstant(makeIntType(32), (unsigned)i, specConstant); }
|
|
Id makeUintConstant(unsigned u, bool specConstant = false)
|
|
{ return makeIntConstant(makeUintType(32), u, specConstant); }
|
|
Id makeInt64Constant(long long i, bool specConstant = false)
|
|
{ return makeInt64Constant(makeIntType(64), (unsigned long long)i, specConstant); }
|
|
Id makeUint64Constant(unsigned long long u, bool specConstant = false)
|
|
{ return makeInt64Constant(makeUintType(64), u, specConstant); }
|
|
Id makeFloatConstant(float f, bool specConstant = false);
|
|
Id makeDoubleConstant(double d, bool specConstant = false);
|
|
Id makeFloat16Constant(float f16, bool specConstant = false);
|
|
Id makeFpConstant(Id type, double d, bool specConstant = false);
|
|
|
|
// Turn the array of constants into a proper spv constant of the requested type.
|
|
Id makeCompositeConstant(Id type, const std::vector<Id>& comps, bool specConst = false);
|
|
|
|
// Methods for adding information outside the CFG.
|
|
Instruction* addEntryPoint(ExecutionModel, Function*, const char* name);
|
|
void addExecutionMode(Function*, ExecutionMode mode, int value1 = -1, int value2 = -1, int value3 = -1);
|
|
void addName(Id, const char* name);
|
|
void addMemberName(Id, int member, const char* name);
|
|
void addDecoration(Id, Decoration, int num = -1);
|
|
void addDecoration(Id, Decoration, const char*);
|
|
void addDecorationId(Id id, Decoration, Id idDecoration);
|
|
void addMemberDecoration(Id, unsigned int member, Decoration, int num = -1);
|
|
void addMemberDecoration(Id, unsigned int member, Decoration, const char*);
|
|
|
|
// At the end of what block do the next create*() instructions go?
|
|
void setBuildPoint(Block* bp) { buildPoint = bp; }
|
|
Block* getBuildPoint() const { return buildPoint; }
|
|
|
|
// Make the entry-point function. The returned pointer is only valid
|
|
// for the lifetime of this builder.
|
|
Function* makeEntryPoint(const char*);
|
|
|
|
// Make a shader-style function, and create its entry block if entry is non-zero.
|
|
// Return the function, pass back the entry.
|
|
// The returned pointer is only valid for the lifetime of this builder.
|
|
Function* makeFunctionEntry(Decoration precision, Id returnType, const char* name,
|
|
const std::vector<Id>& paramTypes, const std::vector<std::vector<Decoration>>& precisions, Block **entry = 0);
|
|
|
|
// Create a return. An 'implicit' return is one not appearing in the source
|
|
// code. In the case of an implicit return, no post-return block is inserted.
|
|
void makeReturn(bool implicit, Id retVal = 0);
|
|
|
|
// Generate all the code needed to finish up a function.
|
|
void leaveFunction();
|
|
|
|
// Create a discard.
|
|
void makeDiscard();
|
|
|
|
// Create a global or function local or IO variable.
|
|
Id createVariable(StorageClass, Id type, const char* name = 0, Id initializer = NoResult);
|
|
|
|
// Create an intermediate with an undefined value.
|
|
Id createUndefined(Id type);
|
|
|
|
// Store into an Id and return the l-value
|
|
void createStore(Id rValue, Id lValue, spv::MemoryAccessMask memoryAccess = spv::MemoryAccessMaskNone,
|
|
spv::Scope scope = spv::ScopeMax, unsigned int alignment = 0);
|
|
|
|
// Load from an Id and return it
|
|
Id createLoad(Id lValue, spv::MemoryAccessMask memoryAccess = spv::MemoryAccessMaskNone,
|
|
spv::Scope scope = spv::ScopeMax, unsigned int alignment = 0);
|
|
|
|
// Create an OpAccessChain instruction
|
|
Id createAccessChain(StorageClass, Id base, const std::vector<Id>& offsets);
|
|
|
|
// Create an OpArrayLength instruction
|
|
Id createArrayLength(Id base, unsigned int member);
|
|
|
|
// Create an OpCooperativeMatrixLengthNV instruction
|
|
Id createCooperativeMatrixLength(Id type);
|
|
|
|
// Create an OpCompositeExtract instruction
|
|
Id createCompositeExtract(Id composite, Id typeId, unsigned index);
|
|
Id createCompositeExtract(Id composite, Id typeId, const std::vector<unsigned>& indexes);
|
|
Id createCompositeInsert(Id object, Id composite, Id typeId, unsigned index);
|
|
Id createCompositeInsert(Id object, Id composite, Id typeId, const std::vector<unsigned>& indexes);
|
|
|
|
Id createVectorExtractDynamic(Id vector, Id typeId, Id componentIndex);
|
|
Id createVectorInsertDynamic(Id vector, Id typeId, Id component, Id componentIndex);
|
|
|
|
void createNoResultOp(Op);
|
|
void createNoResultOp(Op, Id operand);
|
|
void createNoResultOp(Op, const std::vector<Id>& operands);
|
|
void createNoResultOp(Op, const std::vector<IdImmediate>& operands);
|
|
void createControlBarrier(Scope execution, Scope memory, MemorySemanticsMask);
|
|
void createMemoryBarrier(unsigned executionScope, unsigned memorySemantics);
|
|
Id createUnaryOp(Op, Id typeId, Id operand);
|
|
Id createBinOp(Op, Id typeId, Id operand1, Id operand2);
|
|
Id createTriOp(Op, Id typeId, Id operand1, Id operand2, Id operand3);
|
|
Id createOp(Op, Id typeId, const std::vector<Id>& operands);
|
|
Id createOp(Op, Id typeId, const std::vector<IdImmediate>& operands);
|
|
Id createFunctionCall(spv::Function*, const std::vector<spv::Id>&);
|
|
Id createSpecConstantOp(Op, Id typeId, const std::vector<spv::Id>& operands, const std::vector<unsigned>& literals);
|
|
|
|
// Take an rvalue (source) and a set of channels to extract from it to
|
|
// make a new rvalue, which is returned.
|
|
Id createRvalueSwizzle(Decoration precision, Id typeId, Id source, const std::vector<unsigned>& channels);
|
|
|
|
// Take a copy of an lvalue (target) and a source of components, and set the
|
|
// source components into the lvalue where the 'channels' say to put them.
|
|
// An updated version of the target is returned.
|
|
// (No true lvalue or stores are used.)
|
|
Id createLvalueSwizzle(Id typeId, Id target, Id source, const std::vector<unsigned>& channels);
|
|
|
|
// If both the id and precision are valid, the id
|
|
// gets tagged with the requested precision.
|
|
// The passed in id is always the returned id, to simplify use patterns.
|
|
Id setPrecision(Id id, Decoration precision)
|
|
{
|
|
if (precision != NoPrecision && id != NoResult)
|
|
addDecoration(id, precision);
|
|
|
|
return id;
|
|
}
|
|
|
|
// Can smear a scalar to a vector for the following forms:
|
|
// - promoteScalar(scalar, vector) // smear scalar to width of vector
|
|
// - promoteScalar(vector, scalar) // smear scalar to width of vector
|
|
// - promoteScalar(pointer, scalar) // smear scalar to width of what pointer points to
|
|
// - promoteScalar(scalar, scalar) // do nothing
|
|
// Other forms are not allowed.
|
|
//
|
|
// Generally, the type of 'scalar' does not need to be the same type as the components in 'vector'.
|
|
// The type of the created vector is a vector of components of the same type as the scalar.
|
|
//
|
|
// Note: One of the arguments will change, with the result coming back that way rather than
|
|
// through the return value.
|
|
void promoteScalar(Decoration precision, Id& left, Id& right);
|
|
|
|
// Make a value by smearing the scalar to fill the type.
|
|
// vectorType should be the correct type for making a vector of scalarVal.
|
|
// (No conversions are done.)
|
|
Id smearScalar(Decoration precision, Id scalarVal, Id vectorType);
|
|
|
|
// Create a call to a built-in function.
|
|
Id createBuiltinCall(Id resultType, Id builtins, int entryPoint, const std::vector<Id>& args);
|
|
|
|
// List of parameters used to create a texture operation
|
|
struct TextureParameters {
|
|
Id sampler;
|
|
Id coords;
|
|
Id bias;
|
|
Id lod;
|
|
Id Dref;
|
|
Id offset;
|
|
Id offsets;
|
|
Id gradX;
|
|
Id gradY;
|
|
Id sample;
|
|
Id component;
|
|
Id texelOut;
|
|
Id lodClamp;
|
|
Id granularity;
|
|
Id coarse;
|
|
bool nonprivate;
|
|
bool volatil;
|
|
};
|
|
|
|
// Select the correct texture operation based on all inputs, and emit the correct instruction
|
|
Id createTextureCall(Decoration precision, Id resultType, bool sparse, bool fetch, bool proj, bool gather,
|
|
bool noImplicit, const TextureParameters&, ImageOperandsMask);
|
|
|
|
// Emit the OpTextureQuery* instruction that was passed in.
|
|
// Figure out the right return value and type, and return it.
|
|
Id createTextureQueryCall(Op, const TextureParameters&, bool isUnsignedResult);
|
|
|
|
Id createSamplePositionCall(Decoration precision, Id, Id);
|
|
|
|
Id createBitFieldExtractCall(Decoration precision, Id, Id, Id, bool isSigned);
|
|
Id createBitFieldInsertCall(Decoration precision, Id, Id, Id, Id);
|
|
|
|
// Reduction comparison for composites: For equal and not-equal resulting in a scalar.
|
|
Id createCompositeCompare(Decoration precision, Id, Id, bool /* true if for equal, false if for not-equal */);
|
|
|
|
// OpCompositeConstruct
|
|
Id createCompositeConstruct(Id typeId, const std::vector<Id>& constituents);
|
|
|
|
// vector or scalar constructor
|
|
Id createConstructor(Decoration precision, const std::vector<Id>& sources, Id resultTypeId);
|
|
|
|
// matrix constructor
|
|
Id createMatrixConstructor(Decoration precision, const std::vector<Id>& sources, Id constructee);
|
|
|
|
// Helper to use for building nested control flow with if-then-else.
|
|
class If {
|
|
public:
|
|
If(Id condition, unsigned int ctrl, Builder& builder);
|
|
~If() {}
|
|
|
|
void makeBeginElse();
|
|
void makeEndIf();
|
|
|
|
private:
|
|
If(const If&);
|
|
If& operator=(If&);
|
|
|
|
Builder& builder;
|
|
Id condition;
|
|
unsigned int control;
|
|
Function* function;
|
|
Block* headerBlock;
|
|
Block* thenBlock;
|
|
Block* elseBlock;
|
|
Block* mergeBlock;
|
|
};
|
|
|
|
// Make a switch statement. A switch has 'numSegments' of pieces of code, not containing
|
|
// any case/default labels, all separated by one or more case/default labels. Each possible
|
|
// case value v is a jump to the caseValues[v] segment. The defaultSegment is also in this
|
|
// number space. How to compute the value is given by 'condition', as in switch(condition).
|
|
//
|
|
// The SPIR-V Builder will maintain the stack of post-switch merge blocks for nested switches.
|
|
//
|
|
// Use a defaultSegment < 0 if there is no default segment (to branch to post switch).
|
|
//
|
|
// Returns the right set of basic blocks to start each code segment with, so that the caller's
|
|
// recursion stack can hold the memory for it.
|
|
//
|
|
void makeSwitch(Id condition, unsigned int control, int numSegments, const std::vector<int>& caseValues,
|
|
const std::vector<int>& valueToSegment, int defaultSegment, std::vector<Block*>& segmentBB);
|
|
|
|
// Add a branch to the innermost switch's merge block.
|
|
void addSwitchBreak();
|
|
|
|
// Move to the next code segment, passing in the return argument in makeSwitch()
|
|
void nextSwitchSegment(std::vector<Block*>& segmentBB, int segment);
|
|
|
|
// Finish off the innermost switch.
|
|
void endSwitch(std::vector<Block*>& segmentBB);
|
|
|
|
struct LoopBlocks {
|
|
LoopBlocks(Block& head, Block& body, Block& merge, Block& continue_target) :
|
|
head(head), body(body), merge(merge), continue_target(continue_target) { }
|
|
Block &head, &body, &merge, &continue_target;
|
|
private:
|
|
LoopBlocks();
|
|
LoopBlocks& operator=(const LoopBlocks&) = delete;
|
|
};
|
|
|
|
// Start a new loop and prepare the builder to generate code for it. Until
|
|
// closeLoop() is called for this loop, createLoopContinue() and
|
|
// createLoopExit() will target its corresponding blocks.
|
|
LoopBlocks& makeNewLoop();
|
|
|
|
// Create a new block in the function containing the build point. Memory is
|
|
// owned by the function object.
|
|
Block& makeNewBlock();
|
|
|
|
// Add a branch to the continue_target of the current (innermost) loop.
|
|
void createLoopContinue();
|
|
|
|
// Add an exit (e.g. "break") from the innermost loop that we're currently
|
|
// in.
|
|
void createLoopExit();
|
|
|
|
// Close the innermost loop that you're in
|
|
void closeLoop();
|
|
|
|
//
|
|
// Access chain design for an R-Value vs. L-Value:
|
|
//
|
|
// There is a single access chain the builder is building at
|
|
// any particular time. Such a chain can be used to either to a load or
|
|
// a store, when desired.
|
|
//
|
|
// Expressions can be r-values, l-values, or both, or only r-values:
|
|
// a[b.c].d = .... // l-value
|
|
// ... = a[b.c].d; // r-value, that also looks like an l-value
|
|
// ++a[b.c].d; // r-value and l-value
|
|
// (x + y)[2]; // r-value only, can't possibly be l-value
|
|
//
|
|
// Computing an r-value means generating code. Hence,
|
|
// r-values should only be computed when they are needed, not speculatively.
|
|
//
|
|
// Computing an l-value means saving away information for later use in the compiler,
|
|
// no code is generated until the l-value is later dereferenced. It is okay
|
|
// to speculatively generate an l-value, just not okay to speculatively dereference it.
|
|
//
|
|
// The base of the access chain (the left-most variable or expression
|
|
// from which everything is based) can be set either as an l-value
|
|
// or as an r-value. Most efficient would be to set an l-value if one
|
|
// is available. If an expression was evaluated, the resulting r-value
|
|
// can be set as the chain base.
|
|
//
|
|
// The users of this single access chain can save and restore if they
|
|
// want to nest or manage multiple chains.
|
|
//
|
|
|
|
struct AccessChain {
|
|
Id base; // for l-values, pointer to the base object, for r-values, the base object
|
|
std::vector<Id> indexChain;
|
|
Id instr; // cache the instruction that generates this access chain
|
|
std::vector<unsigned> swizzle; // each std::vector element selects the next GLSL component number
|
|
Id component; // a dynamic component index, can coexist with a swizzle,
|
|
// done after the swizzle, NoResult if not present
|
|
Id preSwizzleBaseType; // dereferenced type, before swizzle or component is applied;
|
|
// NoType unless a swizzle or component is present
|
|
bool isRValue; // true if 'base' is an r-value, otherwise, base is an l-value
|
|
unsigned int alignment; // bitwise OR of alignment values passed in. Accumulates worst alignment.
|
|
// Only tracks base and (optional) component selection alignment.
|
|
|
|
// Accumulate whether anything in the chain of structures has coherent decorations.
|
|
struct CoherentFlags {
|
|
CoherentFlags() { clear(); }
|
|
#ifdef GLSLANG_WEB
|
|
void clear() { }
|
|
bool isVolatile() const { return false; }
|
|
CoherentFlags operator |=(const CoherentFlags &other) { return *this; }
|
|
#else
|
|
bool isVolatile() const { return volatil; }
|
|
bool anyCoherent() const {
|
|
return coherent || devicecoherent || queuefamilycoherent || workgroupcoherent ||
|
|
subgroupcoherent || shadercallcoherent;
|
|
}
|
|
|
|
unsigned coherent : 1;
|
|
unsigned devicecoherent : 1;
|
|
unsigned queuefamilycoherent : 1;
|
|
unsigned workgroupcoherent : 1;
|
|
unsigned subgroupcoherent : 1;
|
|
unsigned shadercallcoherent : 1;
|
|
unsigned nonprivate : 1;
|
|
unsigned volatil : 1;
|
|
unsigned isImage : 1;
|
|
|
|
void clear() {
|
|
coherent = 0;
|
|
devicecoherent = 0;
|
|
queuefamilycoherent = 0;
|
|
workgroupcoherent = 0;
|
|
subgroupcoherent = 0;
|
|
shadercallcoherent = 0;
|
|
nonprivate = 0;
|
|
volatil = 0;
|
|
isImage = 0;
|
|
}
|
|
|
|
CoherentFlags operator |=(const CoherentFlags &other) {
|
|
coherent |= other.coherent;
|
|
devicecoherent |= other.devicecoherent;
|
|
queuefamilycoherent |= other.queuefamilycoherent;
|
|
workgroupcoherent |= other.workgroupcoherent;
|
|
subgroupcoherent |= other.subgroupcoherent;
|
|
shadercallcoherent |= other.shadercallcoherent;
|
|
nonprivate |= other.nonprivate;
|
|
volatil |= other.volatil;
|
|
isImage |= other.isImage;
|
|
return *this;
|
|
}
|
|
#endif
|
|
};
|
|
CoherentFlags coherentFlags;
|
|
};
|
|
|
|
//
|
|
// the SPIR-V builder maintains a single active chain that
|
|
// the following methods operate on
|
|
//
|
|
|
|
// for external save and restore
|
|
AccessChain getAccessChain() { return accessChain; }
|
|
void setAccessChain(AccessChain newChain) { accessChain = newChain; }
|
|
|
|
// clear accessChain
|
|
void clearAccessChain();
|
|
|
|
// set new base as an l-value base
|
|
void setAccessChainLValue(Id lValue)
|
|
{
|
|
assert(isPointer(lValue));
|
|
accessChain.base = lValue;
|
|
}
|
|
|
|
// set new base value as an r-value
|
|
void setAccessChainRValue(Id rValue)
|
|
{
|
|
accessChain.isRValue = true;
|
|
accessChain.base = rValue;
|
|
}
|
|
|
|
// push offset onto the end of the chain
|
|
void accessChainPush(Id offset, AccessChain::CoherentFlags coherentFlags, unsigned int alignment)
|
|
{
|
|
accessChain.indexChain.push_back(offset);
|
|
accessChain.coherentFlags |= coherentFlags;
|
|
accessChain.alignment |= alignment;
|
|
}
|
|
|
|
// push new swizzle onto the end of any existing swizzle, merging into a single swizzle
|
|
void accessChainPushSwizzle(std::vector<unsigned>& swizzle, Id preSwizzleBaseType,
|
|
AccessChain::CoherentFlags coherentFlags, unsigned int alignment);
|
|
|
|
// push a dynamic component selection onto the access chain, only applicable with a
|
|
// non-trivial swizzle or no swizzle
|
|
void accessChainPushComponent(Id component, Id preSwizzleBaseType, AccessChain::CoherentFlags coherentFlags,
|
|
unsigned int alignment)
|
|
{
|
|
if (accessChain.swizzle.size() != 1) {
|
|
accessChain.component = component;
|
|
if (accessChain.preSwizzleBaseType == NoType)
|
|
accessChain.preSwizzleBaseType = preSwizzleBaseType;
|
|
}
|
|
accessChain.coherentFlags |= coherentFlags;
|
|
accessChain.alignment |= alignment;
|
|
}
|
|
|
|
// use accessChain and swizzle to store value
|
|
void accessChainStore(Id rvalue, spv::MemoryAccessMask memoryAccess = spv::MemoryAccessMaskNone,
|
|
spv::Scope scope = spv::ScopeMax, unsigned int alignment = 0);
|
|
|
|
// use accessChain and swizzle to load an r-value
|
|
Id accessChainLoad(Decoration precision, Decoration nonUniform, Id ResultType,
|
|
spv::MemoryAccessMask memoryAccess = spv::MemoryAccessMaskNone, spv::Scope scope = spv::ScopeMax,
|
|
unsigned int alignment = 0);
|
|
|
|
// Return whether or not the access chain can be represented in SPIR-V
|
|
// as an l-value.
|
|
// E.g., a[3].yx cannot be, while a[3].y and a[3].y[x] can be.
|
|
bool isSpvLvalue() const { return accessChain.swizzle.size() <= 1; }
|
|
|
|
// get the direct pointer for an l-value
|
|
Id accessChainGetLValue();
|
|
|
|
// Get the inferred SPIR-V type of the result of the current access chain,
|
|
// based on the type of the base and the chain of dereferences.
|
|
Id accessChainGetInferredType();
|
|
|
|
// Add capabilities, extensions, remove unneeded decorations, etc.,
|
|
// based on the resulting SPIR-V.
|
|
void postProcess();
|
|
|
|
// Prune unreachable blocks in the CFG and remove unneeded decorations.
|
|
void postProcessCFG();
|
|
|
|
#ifndef GLSLANG_WEB
|
|
// Add capabilities, extensions based on instructions in the module.
|
|
void postProcessFeatures();
|
|
// Hook to visit each instruction in a block in a function
|
|
void postProcess(Instruction&);
|
|
// Hook to visit each non-32-bit sized float/int operation in a block.
|
|
void postProcessType(const Instruction&, spv::Id typeId);
|
|
#endif
|
|
|
|
void dump(std::vector<unsigned int>&) const;
|
|
|
|
void createBranch(Block* block);
|
|
void createConditionalBranch(Id condition, Block* thenBlock, Block* elseBlock);
|
|
void createLoopMerge(Block* mergeBlock, Block* continueBlock, unsigned int control,
|
|
const std::vector<unsigned int>& operands);
|
|
|
|
// Sets to generate opcode for specialization constants.
|
|
void setToSpecConstCodeGenMode() { generatingOpCodeForSpecConst = true; }
|
|
// Sets to generate opcode for non-specialization constants (normal mode).
|
|
void setToNormalCodeGenMode() { generatingOpCodeForSpecConst = false; }
|
|
// Check if the builder is generating code for spec constants.
|
|
bool isInSpecConstCodeGenMode() { return generatingOpCodeForSpecConst; }
|
|
|
|
protected:
|
|
Id makeIntConstant(Id typeId, unsigned value, bool specConstant);
|
|
Id makeInt64Constant(Id typeId, unsigned long long value, bool specConstant);
|
|
Id findScalarConstant(Op typeClass, Op opcode, Id typeId, unsigned value);
|
|
Id findScalarConstant(Op typeClass, Op opcode, Id typeId, unsigned v1, unsigned v2);
|
|
Id findCompositeConstant(Op typeClass, Id typeId, const std::vector<Id>& comps);
|
|
Id findStructConstant(Id typeId, const std::vector<Id>& comps);
|
|
Id collapseAccessChain();
|
|
void remapDynamicSwizzle();
|
|
void transferAccessChainSwizzle(bool dynamic);
|
|
void simplifyAccessChainSwizzle();
|
|
void createAndSetNoPredecessorBlock(const char*);
|
|
void createSelectionMerge(Block* mergeBlock, unsigned int control);
|
|
void dumpSourceInstructions(std::vector<unsigned int>&) const;
|
|
void dumpSourceInstructions(const spv::Id fileId, const std::string& text, std::vector<unsigned int>&) const;
|
|
void dumpInstructions(std::vector<unsigned int>&, const std::vector<std::unique_ptr<Instruction> >&) const;
|
|
void dumpModuleProcesses(std::vector<unsigned int>&) const;
|
|
spv::MemoryAccessMask sanitizeMemoryAccessForStorageClass(spv::MemoryAccessMask memoryAccess, StorageClass sc)
|
|
const;
|
|
|
|
unsigned int spvVersion; // the version of SPIR-V to emit in the header
|
|
SourceLanguage source;
|
|
int sourceVersion;
|
|
spv::Id sourceFileStringId;
|
|
std::string sourceText;
|
|
int currentLine;
|
|
const char* currentFile;
|
|
bool emitOpLines;
|
|
std::set<std::string> extensions;
|
|
std::vector<const char*> sourceExtensions;
|
|
std::vector<const char*> moduleProcesses;
|
|
AddressingModel addressModel;
|
|
MemoryModel memoryModel;
|
|
std::set<spv::Capability> capabilities;
|
|
int builderNumber;
|
|
Module module;
|
|
Block* buildPoint;
|
|
Id uniqueId;
|
|
Function* entryPointFunction;
|
|
bool generatingOpCodeForSpecConst;
|
|
AccessChain accessChain;
|
|
|
|
// special blocks of instructions for output
|
|
std::vector<std::unique_ptr<Instruction> > strings;
|
|
std::vector<std::unique_ptr<Instruction> > imports;
|
|
std::vector<std::unique_ptr<Instruction> > entryPoints;
|
|
std::vector<std::unique_ptr<Instruction> > executionModes;
|
|
std::vector<std::unique_ptr<Instruction> > names;
|
|
std::vector<std::unique_ptr<Instruction> > decorations;
|
|
std::vector<std::unique_ptr<Instruction> > constantsTypesGlobals;
|
|
std::vector<std::unique_ptr<Instruction> > externals;
|
|
std::vector<std::unique_ptr<Function> > functions;
|
|
|
|
// not output, internally used for quick & dirty canonical (unique) creation
|
|
|
|
// map type opcodes to constant inst.
|
|
std::unordered_map<unsigned int, std::vector<Instruction*>> groupedConstants;
|
|
// map struct-id to constant instructions
|
|
std::unordered_map<unsigned int, std::vector<Instruction*>> groupedStructConstants;
|
|
// map type opcodes to type instructions
|
|
std::unordered_map<unsigned int, std::vector<Instruction*>> groupedTypes;
|
|
|
|
// stack of switches
|
|
std::stack<Block*> switchMerges;
|
|
|
|
// Our loop stack.
|
|
std::stack<LoopBlocks> loops;
|
|
|
|
// map from strings to their string ids
|
|
std::unordered_map<std::string, spv::Id> stringIds;
|
|
|
|
// map from include file name ids to their contents
|
|
std::map<spv::Id, const std::string*> includeFiles;
|
|
|
|
// The stream for outputting warnings and errors.
|
|
SpvBuildLogger* logger;
|
|
}; // end Builder class
|
|
|
|
}; // end spv namespace
|
|
|
|
#endif // SpvBuilder_H
|