glslang/SPIRV/SpvBuilder.h

564 lines
24 KiB
C++

//
//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);
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 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 updated 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 for the loop test, based on the given condition.
// The true branch goes to the block that remains inside the loop, and
// the false branch goes to the loop's merge block. The builder insertion
// point will be placed at the start of the inside-the-loop block.
void createLoopTestBranch(Id condition);
// Finish generating the loop header block in the case where the loop test
// is at the bottom of the loop. It will include the LoopMerge instruction
// and a branch to the rest of the body. The loop header block must be
// separate from the rest of the body to make room for the the two kinds
// of *Merge instructions that might have to occur just before a branch:
// the loop header must have a LoopMerge as its second-last instruction,
// and the body might begin with a conditional branch, which must have its
// own SelectionMerge instruction.
// Also create the basic block that will contain the loop test, but don't
// insert it into the function yet. Any "continue" constructs in this loop
// will branch to the loop test block. The builder insertion point will be
// placed at the start of the body block.
void endLoopHeaderWithoutTest();
// Generate a branch to the loop test block. This can only be called if
// the loop test is at the bottom of the loop. The builder insertion point
// is left at the start of the test block.
void createBranchToLoopTest();
// Add a branch to the test of the current (innermost) loop.
void createLoopContinue();
// 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, can coexist with a swizzle, done after the swizzle
Id resultType; // dereferenced type, to be exclusive of swizzles
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;
accessChain.resultType = getContainedTypeId(getTypeId(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);
// 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 mergeAccessChainSwizzle();
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 {
// The header is the first block generated for the loop.
// It dominates all the blocks in the loop, i.e. it is always
// executed before any others.
// If the loop test is executed before the body (as in "while" and
// "for" loops), then the header begins with the test code.
// Otherwise, the loop is a "do-while" loop and the header contains the
// start of the body of the loop (if the body exists).
Block* header;
// The merge block marks the end of the loop. Control is transferred
// to the merge block when either the loop test fails, or when a
// nested "break" is encountered.
Block* merge;
// If not NULL, the test block is the basic block containing the loop
// test and the conditional branch back to the header or the merge
// block. This is created for "do-while" loops, and is the target of
// any "continue" constructs that might exist.
Block* test;
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