// | |
//Copyright (C) 2014 LunarG, Inc. | |
// | |
//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. | |
// | |
// Author: John Kessenich, LunarG | |
// | |
// | |
// "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 "spirv.h" | |
#include "spvIR.h" | |
#include <algorithm> | |
#include <stack> | |
#include <map> | |
namespace spv { | |
class Builder { | |
public: | |
Builder(unsigned int userNumber); | |
virtual ~Builder(); | |
static const int maxMatrixSize = 4; | |
void setSource(spv::SourceLanguage lang, int version) | |
{ | |
source = lang; | |
sourceVersion = version; | |
} | |
void addSourceExtension(const char* ext) { extensions.push_back(ext); } | |
Id import(const char*); | |
void setMemoryModel(spv::AddressingModel addr, spv::MemoryModel mem) | |
{ | |
addressModel = addr; | |
memoryModel = mem; | |
} | |
// 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; | |
} | |
// For creating new types (will return old type if the requested one was already made). | |
Id makeVoidType(); | |
Id makeBoolType(); | |
Id makePointer(StorageClass, Id type); | |
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(std::vector<Id>& members, const char*); | |
Id makeVectorType(Id component, int size); | |
Id makeMatrixType(Id component, int cols, int rows); | |
Id makeArrayType(Id element, unsigned size); | |
Id makeFunctionType(Id returnType, std::vector<Id>& paramTypes); | |
enum samplerContent : unsigned { | |
samplerContentTexture, | |
samplerContentImage, | |
samplerContentTextureFilter | |
}; | |
Id makeSampler(Id sampledType, Dim, samplerContent, bool arrayed, bool shadow, bool ms); | |
// 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 getNumTypeComponents(Id typeId) const; | |
Id getScalarTypeId(Id typeId) const; | |
Id getContainedTypeId(Id typeId) const; | |
Id getContainedTypeId(Id typeId, int) const; | |
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 isAggregate(Id resultId) const { return isAggregateType(getTypeId(resultId)); } | |
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; } | |
bool isAggregateType(Id typeId) const { return isArrayType(typeId) || isStructType(typeId); } | |
bool isSamplerType(Id typeId) const { return getTypeClass(typeId) == OpTypeSampler; } | |
bool isConstantScalar(Id resultId) const { return getOpCode(resultId) == OpConstant; } | |
unsigned int getConstantScalar(Id resultId) const { return module.getInstruction(resultId)->getImmediateOperand(0); } | |
int getTypeNumColumns(Id typeId) const | |
{ | |
assert(isMatrixType(typeId)); | |
return getNumTypeComponents(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 getDimensionality(Id resultId) const | |
{ | |
assert(isSamplerType(getTypeId(resultId))); | |
return (Dim)module.getInstruction(getTypeId(resultId))->getImmediateOperand(1); | |
} | |
bool isArrayedSampler(Id resultId) const | |
{ | |
assert(isSamplerType(getTypeId(resultId))); | |
return module.getInstruction(getTypeId(resultId))->getImmediateOperand(3) != 0; | |
} | |
// For making new constants (will return old constant if the requested one was already made). | |
Id makeBoolConstant(bool b); | |
Id makeIntConstant(Id typeId, unsigned value); | |
Id makeIntConstant(int i) { return makeIntConstant(makeIntType(32), (unsigned)i); } | |
Id makeUintConstant(unsigned u) { return makeIntConstant(makeUintType(32), u); } | |
Id makeFloatConstant(float f); | |
Id makeDoubleConstant(double d); | |
// Turn the array of constants into a proper spv constant of the requested type. | |
Id makeCompositeConstant(Id type, std::vector<Id>& comps); | |
// Methods for adding information outside the CFG. | |
void addEntryPoint(ExecutionModel, Function*); | |
void addExecutionMode(Function*, ExecutionMode mode, int value = -1); | |
void addName(Id, const char* name); | |
void addMemberName(Id, int member, const char* name); | |
void addLine(Id target, Id fileName, int line, int column); | |
void addDecoration(Id, Decoration, int num = -1); | |
void addMemberDecoration(Id, unsigned int member, Decoration, int num = -1); | |
// 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 main function. | |
Function* makeMain(); | |
// Return from main. Implicit denotes a return at the very end of main. | |
void makeMainReturn(bool implicit = false) { makeReturn(implicit, 0, true); } | |
// Close the main function. | |
void closeMain(); | |
// Make a shader-style function, and create its entry block if entry is non-zero. | |
// Return the function, pass back the entry. | |
Function* makeFunctionEntry(Id returnType, const char* name, std::vector<Id>& paramTypes, Block **entry = 0); | |
// Create a return. Pass whether it is a return form main, and the return | |
// value (if applicable). In the case of an implicit return, no post-return | |
// block is inserted. | |
void makeReturn(bool implicit = false, Id retVal = 0, bool isMain = false); | |
// Generate all the code needed to finish up a function. | |
void leaveFunction(bool main); | |
// Create a discard. | |
void makeDiscard(); | |
// Create a global or function local or IO variable. | |
Id createVariable(StorageClass, Id type, const char* name = 0); | |
// Store into an Id and return the l-value | |
void createStore(Id rValue, Id lValue); | |
// Load from an Id and return it | |
Id createLoad(Id lValue); | |
// Create an OpAccessChain instruction | |
Id createAccessChain(StorageClass, Id base, std::vector<Id>& offsets); | |
// Create an OpCompositeExtract instruction | |
Id createCompositeExtract(Id composite, Id typeId, unsigned index); | |
Id createCompositeExtract(Id composite, Id typeId, std::vector<unsigned>& indexes); | |
Id createCompositeInsert(Id object, Id composite, Id typeId, unsigned index); | |
Id createCompositeInsert(Id object, Id composite, Id typeId, std::vector<unsigned>& indexes); | |
void createNoResultOp(Op); | |
void createNoResultOp(Op, Id operand); | |
void createControlBarrier(unsigned executionScope); | |
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 createTernaryOp(Op, Id typeId, Id operand1, Id operand2, Id operand3); | |
Id createFunctionCall(spv::Function*, std::vector<spv::Id>&); | |
// Take an rvalue (source) and a set of channels to extract from it to | |
// make a new rvalue, which is returned. | |
Id createRvalueSwizzle(Id typeId, Id source, 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 update version of the target is returned. | |
// (No true lvalue or stores are used.) | |
Id createLvalueSwizzle(Id typeId, Id target, Id source, std::vector<unsigned>& channels); | |
// If the value passed in is an instruction and the precision is not EMpNone, | |
// it gets tagged with the requested precision. | |
void setPrecision(Id /* value */, Decoration /* precision */) | |
{ | |
// TODO | |
} | |
// 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. | |
// | |
// 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 | |
Id smearScalar(Decoration precision, Id scalarVal, Id); | |
// Create a call to a built-in function. | |
Id createBuiltinCall(Decoration precision, Id resultType, Id builtins, int entryPoint, 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 gradX; | |
Id gradY; | |
}; | |
// Select the correct texture operation based on all inputs, and emit the correct instruction | |
Id createTextureCall(Decoration precision, Id resultType, bool proj, const TextureParameters&); | |
// Emit the OpTextureQuery* instruction that was passed in. | |
// Figure out the right return value and type, and return it. | |
Id createTextureQueryCall(Op, const TextureParameters&); | |
Id createSamplePositionCall(Decoration precision, Id, Id); | |
Id createBitFieldExtractCall(Decoration precision, Id, Id, Id, bool isSigned); | |
Id createBitFieldInsertCall(Decoration precision, Id, Id, Id, Id); | |
// Reduction comparision for composites: For equal and not-equal resulting in a scalar. | |
Id createCompare(Decoration precision, Id, Id, bool /* true if for equal, fales if for not-equal */); | |
// OpCompositeConstruct | |
Id createCompositeConstruct(Id typeId, 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, Builder& builder); | |
~If() {} | |
void makeBeginElse(); | |
void makeEndIf(); | |
private: | |
If(const If&); | |
If& operator=(If&); | |
Builder& builder; | |
Id condition; | |
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, int numSegments, std::vector<int>& caseValues, std::vector<int>& valueToSegment, int defaultSegment, | |
std::vector<Block*>& segmentBB); // return argument | |
// 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); | |
// Start the beginning of a new loop. | |
void makeNewLoop(); | |
// Add the branch at the end of the loop header, and leave the build position | |
// in the first block of the body. | |
// 'condition' is true if should exit the loop | |
void createLoopHeaderBranch(Id condition); | |
// Add a back-edge (e.g "continue") for the innermost loop that you're in | |
void createLoopBackEdge(bool implicit=false); | |
// Add an exit (e.g. "break") for the innermost loop that you're 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; // the instruction that generates this access chain | |
std::vector<unsigned> swizzle; | |
Id component; // a dynamic component index | |
int swizzleTargetWidth; | |
Id resultType; // dereferenced type, to be inclusive of swizzles, which can't have a pointer | |
bool isRValue; | |
}; | |
// | |
// the SPIR-V builder maintains a single active chain that | |
// the following methods operated 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; | |
accessChain.resultType = getTypeId(rValue); | |
} | |
// push offset onto the end of the chain | |
void accessChainPush(Id offset, Id newType) | |
{ | |
accessChain.indexChain.push_back(offset); | |
accessChain.resultType = newType; | |
} | |
// push new swizzle onto the end of any existing swizzle, merging into a single swizzle | |
void accessChainPushSwizzle(std::vector<unsigned>& swizzle, int width, Id type); | |
// push a variable component selection onto the access chain; supporting only one, so unsided | |
void accessChainPushComponent(Id component) { accessChain.component = component; } | |
// use accessChain and swizzle to store value | |
void accessChainStore(Id rvalue); | |
// use accessChain and swizzle to load an r-value | |
Id accessChainLoad(Decoration precision); | |
// get the direct pointer for an l-value | |
Id accessChainGetLValue(); | |
void dump(std::vector<unsigned int>&) const; | |
protected: | |
Id findScalarConstant(Op typeClass, Id typeId, unsigned value) const; | |
Id findScalarConstant(Op typeClass, Id typeId, unsigned v1, unsigned v2) const; | |
Id findCompositeConstant(Op typeClass, std::vector<Id>& comps) const; | |
Id collapseAccessChain(); | |
void simplifyAccessChainSwizzle(); | |
void createAndSetNoPredecessorBlock(const char*); | |
void createBranch(Block* block); | |
void createMerge(Op, Block*, unsigned int control); | |
void createConditionalBranch(Id condition, Block* thenBlock, Block* elseBlock); | |
void dumpInstructions(std::vector<unsigned int>&, const std::vector<Instruction*>&) const; | |
SourceLanguage source; | |
int sourceVersion; | |
std::vector<const char*> extensions; | |
AddressingModel addressModel; | |
MemoryModel memoryModel; | |
int builderNumber; | |
Module module; | |
Block* buildPoint; | |
Id uniqueId; | |
Function* mainFunction; | |
Block* stageExit; | |
AccessChain accessChain; | |
// special blocks of instructions for output | |
std::vector<Instruction*> imports; | |
std::vector<Instruction*> entryPoints; | |
std::vector<Instruction*> executionModes; | |
std::vector<Instruction*> names; | |
std::vector<Instruction*> lines; | |
std::vector<Instruction*> decorations; | |
std::vector<Instruction*> constantsTypesGlobals; | |
std::vector<Instruction*> externals; | |
// not output, internally used for quick & dirty canonical (unique) creation | |
std::vector<Instruction*> groupedConstants[OpConstant]; // all types appear before OpConstant | |
std::vector<Instruction*> groupedTypes[OpConstant]; | |
// stack of switches | |
std::stack<Block*> switchMerges; | |
// Data that needs to be kept in order to properly handle loops. | |
struct Loop { | |
Block* header; | |
Block* merge; | |
Function* function; | |
}; | |
// Our loop stack. | |
std::stack<Loop> loops; | |
}; // end Builder class | |
void MissingFunctionality(const char*); | |
void ValidationError(const char* error); | |
}; // end spv namespace | |
#endif // SpvBuilder_H |