blob: 70f23d519c02e63a4bf07564b4e68af191e79908 [file] [log] [blame]
/*
* Copyright 2016 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SKSL_SPIRVCODEGENERATOR
#define SKSL_SPIRVCODEGENERATOR
#include "include/private/SkSLDefines.h"
#include "include/private/SkSLLayout.h"
#include "include/private/SkSLModifiers.h"
#include "include/private/SkTArray.h"
#include "include/private/SkTHash.h"
#include "src/sksl/SkSLMemoryLayout.h"
#include "src/sksl/SkSLStringStream.h"
#include "src/sksl/codegen/SkSLCodeGenerator.h"
#include "src/sksl/ir/SkSLFunctionDeclaration.h"
#include "src/sksl/ir/SkSLFunctionDefinition.h"
#include "src/sksl/ir/SkSLInterfaceBlock.h"
#include "src/sksl/ir/SkSLSymbolTable.h"
#include "src/sksl/ir/SkSLType.h"
#include "src/sksl/ir/SkSLVariable.h"
#include "src/sksl/spirv.h"
#include <cstdint>
#include <memory>
#include <string_view>
#include <vector>
template <typename T> class SkSpan;
namespace SkSL {
class AnyConstructor;
class BinaryExpression;
class Block;
class ConstructorCompound;
class ConstructorCompoundCast;
class ConstructorDiagonalMatrix;
class ConstructorMatrixResize;
class ConstructorScalarCast;
class ConstructorSplat;
class Context;
class DoStatement;
class Expression;
class FieldAccess;
class ForStatement;
class FunctionCall;
class IfStatement;
class Literal;
class Operator;
class OutputStream;
class Position;
class PostfixExpression;
class PrefixExpression;
class ProgramElement;
class ReturnStatement;
class Statement;
class SwitchStatement;
class TernaryExpression;
class VarDeclaration;
class VariableReference;
enum class ProgramKind : int8_t;
enum IntrinsicKind : int8_t;
struct IndexExpression;
struct Program;
struct Swizzle;
/**
* Converts a Program into a SPIR-V binary.
*/
class SPIRVCodeGenerator : public CodeGenerator {
public:
// We reserve an impossible SpvId as a sentinel. (NA meaning none, n/a, etc.)
static constexpr SpvId NA = (SpvId)-1;
class LValue {
public:
virtual ~LValue() {}
// returns a pointer to the lvalue, if possible. If the lvalue cannot be directly referenced
// by a pointer (e.g. vector swizzles), returns NA.
virtual SpvId getPointer() { return NA; }
// Returns true if a valid pointer returned by getPointer represents a memory object
// (see https://github.com/KhronosGroup/SPIRV-Tools/issues/2892). Has no meaning if
// getPointer() returns NA.
virtual bool isMemoryObjectPointer() const { return true; }
// Applies a swizzle to the components of the LValue, if possible. This is used to create
// LValues that are swizzes-of-swizzles. Non-swizzle LValues can just return false.
virtual bool applySwizzle(const ComponentArray& components, const Type& newType) {
return false;
}
virtual SpvId load(OutputStream& out) = 0;
virtual void store(SpvId value, OutputStream& out) = 0;
};
SPIRVCodeGenerator(const Context* context, const Program* program, OutputStream* out)
: INHERITED(context, program, out)
, fDefaultLayout(MemoryLayout::Standard::k140)
, fCapabilities(0)
, fIdCount(1)
, fCurrentBlock(0)
, fSynthetics(/*builtin=*/true) {}
bool generateCode() override;
private:
enum IntrinsicOpcodeKind {
kGLSL_STD_450_IntrinsicOpcodeKind,
kSPIRV_IntrinsicOpcodeKind,
kSpecial_IntrinsicOpcodeKind,
kInvalid_IntrinsicOpcodeKind,
};
enum SpecialIntrinsic {
kAtan_SpecialIntrinsic,
kClamp_SpecialIntrinsic,
kMatrixCompMult_SpecialIntrinsic,
kMax_SpecialIntrinsic,
kMin_SpecialIntrinsic,
kMix_SpecialIntrinsic,
kMod_SpecialIntrinsic,
kDFdy_SpecialIntrinsic,
kSaturate_SpecialIntrinsic,
kSampledImage_SpecialIntrinsic,
kSmoothStep_SpecialIntrinsic,
kStep_SpecialIntrinsic,
kSubpassLoad_SpecialIntrinsic,
kTexture_SpecialIntrinsic,
kTextureGrad_SpecialIntrinsic,
kTextureLod_SpecialIntrinsic,
};
enum class Precision {
kDefault,
kRelaxed,
};
struct TempVar {
SpvId spvId;
const Type* type;
std::unique_ptr<SPIRVCodeGenerator::LValue> lvalue;
};
/**
* Pass in the type to automatically add a RelaxedPrecision decoration for the id when
* appropriate, or null to never add one.
*/
SpvId nextId(const Type* type);
SpvId nextId(Precision precision);
SpvId getType(const Type& type);
SpvId getType(const Type& type, const MemoryLayout& layout);
SpvId getFunctionType(const FunctionDeclaration& function);
SpvId getFunctionParameterType(const Type& parameterType);
SpvId getPointerType(const Type& type, SpvStorageClass_ storageClass);
SpvId getPointerType(const Type& type, const MemoryLayout& layout,
SpvStorageClass_ storageClass);
SkTArray<SpvId> getAccessChain(const Expression& expr, OutputStream& out);
void writeLayout(const Layout& layout, SpvId target, Position pos);
void writeFieldLayout(const Layout& layout, SpvId target, int member);
SpvId writeStruct(const Type& type, const MemoryLayout& memoryLayout);
void writeProgramElement(const ProgramElement& pe, OutputStream& out);
SpvId writeInterfaceBlock(const InterfaceBlock& intf, bool appendRTFlip = true);
SpvId writeFunctionStart(const FunctionDeclaration& f, OutputStream& out);
SpvId writeFunctionDeclaration(const FunctionDeclaration& f, OutputStream& out);
SpvId writeFunction(const FunctionDefinition& f, OutputStream& out);
void writeGlobalVar(ProgramKind kind, const VarDeclaration& v);
void writeVarDeclaration(const VarDeclaration& var, OutputStream& out);
SpvId writeVariableReference(const VariableReference& ref, OutputStream& out);
int findUniformFieldIndex(const Variable& var) const;
std::unique_ptr<LValue> getLValue(const Expression& value, OutputStream& out);
SpvId writeExpression(const Expression& expr, OutputStream& out);
SpvId writeIntrinsicCall(const FunctionCall& c, OutputStream& out);
SpvId writeFunctionCallArgument(const FunctionCall& call,
int argIndex,
std::vector<TempVar>* tempVars,
OutputStream& out);
void copyBackTempVars(const std::vector<TempVar>& tempVars, OutputStream& out);
SpvId writeFunctionCall(const FunctionCall& c, OutputStream& out);
void writeGLSLExtendedInstruction(const Type& type, SpvId id, SpvId floatInst,
SpvId signedInst, SpvId unsignedInst,
const SkTArray<SpvId>& args, OutputStream& out);
/**
* Promotes an expression to a vector. If the expression is already a vector with vectorSize
* columns, returns it unmodified. If the expression is a scalar, either promotes it to a
* vector (if vectorSize > 1) or returns it unmodified (if vectorSize == 1). Asserts if the
* expression is already a vector and it does not have vectorSize columns.
*/
SpvId vectorize(const Expression& expr, int vectorSize, OutputStream& out);
/**
* Given a list of potentially mixed scalars and vectors, promotes the scalars to match the
* size of the vectors and returns the ids of the written expressions. e.g. given (float, vec2),
* returns (vec2(float), vec2). It is an error to use mismatched vector sizes, e.g. (float,
* vec2, vec3).
*/
SkTArray<SpvId> vectorize(const ExpressionArray& args, OutputStream& out);
SpvId writeSpecialIntrinsic(const FunctionCall& c, SpecialIntrinsic kind, OutputStream& out);
SpvId writeScalarToMatrixSplat(const Type& matrixType, SpvId scalarId, OutputStream& out);
SpvId writeFloatConstructor(const AnyConstructor& c, OutputStream& out);
SpvId castScalarToFloat(SpvId inputId, const Type& inputType, const Type& outputType,
OutputStream& out);
SpvId writeIntConstructor(const AnyConstructor& c, OutputStream& out);
SpvId castScalarToSignedInt(SpvId inputId, const Type& inputType, const Type& outputType,
OutputStream& out);
SpvId writeUIntConstructor(const AnyConstructor& c, OutputStream& out);
SpvId castScalarToUnsignedInt(SpvId inputId, const Type& inputType, const Type& outputType,
OutputStream& out);
SpvId writeBooleanConstructor(const AnyConstructor& c, OutputStream& out);
SpvId castScalarToBoolean(SpvId inputId, const Type& inputType, const Type& outputType,
OutputStream& out);
SpvId castScalarToType(SpvId inputExprId, const Type& inputType, const Type& outputType,
OutputStream& out);
/**
* Writes a potentially-different-sized copy of a matrix. Entries which do not exist in the
* source matrix are filled with zero; entries which do not exist in the destination matrix are
* ignored.
*/
SpvId writeMatrixCopy(SpvId src, const Type& srcType, const Type& dstType, OutputStream& out);
void addColumnEntry(const Type& columnType, SkTArray<SpvId>* currentColumn,
SkTArray<SpvId>* columnIds, int rows, SpvId entry, OutputStream& out);
SpvId writeConstructorCompound(const ConstructorCompound& c, OutputStream& out);
SpvId writeMatrixConstructor(const ConstructorCompound& c, OutputStream& out);
SpvId writeVectorConstructor(const ConstructorCompound& c, OutputStream& out);
SpvId writeCompositeConstructor(const AnyConstructor& c, OutputStream& out);
SpvId writeConstructorDiagonalMatrix(const ConstructorDiagonalMatrix& c, OutputStream& out);
SpvId writeConstructorMatrixResize(const ConstructorMatrixResize& c, OutputStream& out);
SpvId writeConstructorScalarCast(const ConstructorScalarCast& c, OutputStream& out);
SpvId writeConstructorSplat(const ConstructorSplat& c, OutputStream& out);
SpvId writeConstructorCompoundCast(const ConstructorCompoundCast& c, OutputStream& out);
SpvId writeFieldAccess(const FieldAccess& f, OutputStream& out);
SpvId writeSwizzle(const Swizzle& swizzle, OutputStream& out);
/**
* Folds the potentially-vector result of a logical operation down to a single bool. If
* operandType is a vector type, assumes that the intermediate result in id is a bvec of the
* same dimensions, and applys all() to it to fold it down to a single bool value. Otherwise,
* returns the original id value.
*/
SpvId foldToBool(SpvId id, const Type& operandType, SpvOp op, OutputStream& out);
SpvId writeMatrixComparison(const Type& operandType, SpvId lhs, SpvId rhs, SpvOp_ floatOperator,
SpvOp_ intOperator, SpvOp_ vectorMergeOperator,
SpvOp_ mergeOperator, OutputStream& out);
SpvId writeStructComparison(const Type& structType, SpvId lhs, Operator op, SpvId rhs,
OutputStream& out);
SpvId writeArrayComparison(const Type& structType, SpvId lhs, Operator op, SpvId rhs,
OutputStream& out);
// Used by writeStructComparison and writeArrayComparison to logically combine field-by-field
// comparisons into an overall comparison result.
// - `a.x == b.x` merged with `a.y == b.y` generates `(a.x == b.x) && (a.y == b.y)`
// - `a.x != b.x` merged with `a.y != b.y` generates `(a.x != b.x) || (a.y != b.y)`
SpvId mergeComparisons(SpvId comparison, SpvId allComparisons, Operator op, OutputStream& out);
SpvId writeComponentwiseMatrixUnary(const Type& operandType,
SpvId operand,
SpvOp_ op,
OutputStream& out);
SpvId writeComponentwiseMatrixBinary(const Type& operandType, SpvId lhs, SpvId rhs,
SpvOp_ op, OutputStream& out);
SpvId writeBinaryOperation(const Type& resultType, const Type& operandType, SpvId lhs,
SpvId rhs, SpvOp_ ifFloat, SpvOp_ ifInt, SpvOp_ ifUInt,
SpvOp_ ifBool, OutputStream& out);
SpvId writeReciprocal(const Type& type, SpvId value, OutputStream& out);
SpvId writeBinaryExpression(const Type& leftType, SpvId lhs, Operator op,
const Type& rightType, SpvId rhs, const Type& resultType,
OutputStream& out);
SpvId writeBinaryExpression(const BinaryExpression& b, OutputStream& out);
SpvId writeTernaryExpression(const TernaryExpression& t, OutputStream& out);
SpvId writeIndexExpression(const IndexExpression& expr, OutputStream& out);
SpvId writeLogicalAnd(const Expression& left, const Expression& right, OutputStream& out);
SpvId writeLogicalOr(const Expression& left, const Expression& right, OutputStream& out);
SpvId writePrefixExpression(const PrefixExpression& p, OutputStream& out);
SpvId writePostfixExpression(const PostfixExpression& p, OutputStream& out);
SpvId writeLiteral(const Literal& f);
SpvId writeLiteral(double value, const Type& type);
void writeStatement(const Statement& s, OutputStream& out);
void writeBlock(const Block& b, OutputStream& out);
void writeIfStatement(const IfStatement& stmt, OutputStream& out);
void writeForStatement(const ForStatement& f, OutputStream& out);
void writeDoStatement(const DoStatement& d, OutputStream& out);
void writeSwitchStatement(const SwitchStatement& s, OutputStream& out);
void writeReturnStatement(const ReturnStatement& r, OutputStream& out);
void writeCapabilities(OutputStream& out);
void writeInstructions(const Program& program, OutputStream& out);
void writeOpCode(SpvOp_ opCode, int length, OutputStream& out);
void writeWord(int32_t word, OutputStream& out);
void writeString(std::string_view s, OutputStream& out);
void writeInstruction(SpvOp_ opCode, OutputStream& out);
void writeInstruction(SpvOp_ opCode, std::string_view string, OutputStream& out);
void writeInstruction(SpvOp_ opCode, int32_t word1, OutputStream& out);
void writeInstruction(SpvOp_ opCode, int32_t word1, std::string_view string,
OutputStream& out);
void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, std::string_view string,
OutputStream& out);
void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, OutputStream& out);
void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3,
OutputStream& out);
void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4,
OutputStream& out);
void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4,
int32_t word5, OutputStream& out);
void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4,
int32_t word5, int32_t word6, OutputStream& out);
void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4,
int32_t word5, int32_t word6, int32_t word7, OutputStream& out);
void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4,
int32_t word5, int32_t word6, int32_t word7, int32_t word8,
OutputStream& out);
// This form of writeInstruction can deduplicate redundant ops.
struct Word;
// 8 Words is enough for nearly all instructions (except variable-length instructions like
// OpAccessChain or OpConstantComposite).
using Words = SkSTArray<8, Word, true>;
SpvId writeInstruction(SpvOp_ opCode, const SkTArray<Word, true>& words, OutputStream& out);
struct Instruction {
SpvId fOp;
int32_t fResultKind;
SkSTArray<8, int32_t> fWords;
bool operator==(const Instruction& that) const;
struct Hash;
};
static Instruction BuildInstructionKey(SpvOp_ opCode, const SkTArray<Word, true>& words);
// The writeOpXxxxx calls will simplify and deduplicate ops where possible.
SpvId writeOpConstantTrue(const Type& type);
SpvId writeOpConstantFalse(const Type& type);
SpvId writeOpConstant(const Type& type, int32_t valueBits);
SpvId writeOpConstantComposite(const Type& type, const SkTArray<SpvId>& values);
SpvId writeOpCompositeConstruct(const Type& type, const SkTArray<SpvId>&, OutputStream& out);
SpvId writeOpCompositeExtract(const Type& type, SpvId base, int component, OutputStream& out);
SpvId writeOpCompositeExtract(const Type& type, SpvId base, int componentA, int componentB,
OutputStream& out);
SpvId writeOpLoad(SpvId type, Precision precision, SpvId pointer, OutputStream& out);
void writeOpStore(SpvStorageClass_ storageClass, SpvId pointer, SpvId value, OutputStream& out);
// Converts the provided SpvId(s) into an array of scalar OpConstants, if it can be done.
bool toConstants(SpvId value, SkTArray<SpvId>* constants);
bool toConstants(SkSpan<const SpvId> values, SkTArray<SpvId>* constants);
// Extracts the requested component SpvId from a composite instruction, if it can be done.
Instruction* resultTypeForInstruction(const Instruction& instr);
int numComponentsForVecInstruction(const Instruction& instr);
SpvId toComponent(SpvId id, int component);
struct ConditionalOpCounts {
int numReachableOps;
int numStoreOps;
};
ConditionalOpCounts getConditionalOpCounts();
void pruneConditionalOps(ConditionalOpCounts ops);
enum StraightLineLabelType {
// Use "BranchlessBlock" for blocks which are never explicitly branched-to at all. This
// happens at the start of a function, or when we find unreachable code.
kBranchlessBlock,
// Use "BranchIsOnPreviousLine" when writing a label that comes immediately after its
// associated branch. Example usage:
// - SPIR-V does not implicitly fall through from one block to the next, so you may need to
// use an OpBranch to explicitly jump to the next block, even when they are adjacent in
// the code.
// - The block immediately following an OpBranchConditional or OpSwitch.
kBranchIsOnPreviousLine,
};
enum BranchingLabelType {
// Use "BranchIsAbove" for labels which are referenced by OpBranch or OpBranchConditional
// ops that are above the label in the code--i.e., the branch skips forward in the code.
kBranchIsAbove,
// Use "BranchIsBelow" for labels which are referenced by OpBranch or OpBranchConditional
// ops below the label in the code--i.e., the branch jumps backward in the code.
kBranchIsBelow,
// Use "BranchesOnBothSides" for labels which have branches coming from both directions.
kBranchesOnBothSides,
};
void writeLabel(SpvId label, StraightLineLabelType type, OutputStream& out);
void writeLabel(SpvId label, BranchingLabelType type, ConditionalOpCounts ops,
OutputStream& out);
bool isDead(const Variable& var) const;
MemoryLayout memoryLayoutForStorageClass(SpvStorageClass_ storageClass);
MemoryLayout memoryLayoutForVariable(const Variable&) const;
struct EntrypointAdapter {
std::unique_ptr<FunctionDefinition> entrypointDef;
std::unique_ptr<FunctionDeclaration> entrypointDecl;
Layout fLayout;
Modifiers fModifiers;
};
EntrypointAdapter writeEntrypointAdapter(const FunctionDeclaration& main);
struct UniformBuffer {
std::unique_ptr<InterfaceBlock> fInterfaceBlock;
std::unique_ptr<Variable> fInnerVariable;
std::unique_ptr<Type> fStruct;
};
void writeUniformBuffer(std::shared_ptr<SymbolTable> topLevelSymbolTable);
void addRTFlipUniform(Position pos);
const MemoryLayout fDefaultLayout;
uint64_t fCapabilities;
SpvId fIdCount;
SpvId fGLSLExtendedInstructions;
struct Intrinsic {
IntrinsicOpcodeKind opKind;
int32_t floatOp;
int32_t signedOp;
int32_t unsignedOp;
int32_t boolOp;
};
Intrinsic getIntrinsic(IntrinsicKind) const;
SkTHashMap<const FunctionDeclaration*, SpvId> fFunctionMap;
SkTHashMap<const Variable*, SpvId> fVariableMap;
SkTHashMap<const Type*, SpvId> fStructMap;
StringStream fGlobalInitializersBuffer;
StringStream fConstantBuffer;
StringStream fVariableBuffer;
StringStream fNameBuffer;
StringStream fDecorationBuffer;
// These caches map SpvIds to Instructions, and vice-versa. This enables us to deduplicate code
// (by detecting an Instruction we've already issued and reusing the SpvId), and to introspect
// and simplify code we've already emitted (by taking a SpvId from an Instruction and following
// it back to its source).
SkTHashMap<Instruction, SpvId, Instruction::Hash> fOpCache; // maps instruction -> SpvId
SkTHashMap<SpvId, Instruction> fSpvIdCache; // maps SpvId -> instruction
SkTHashMap<SpvId, SpvId> fStoreCache; // maps ptr SpvId -> value SpvId
// "Reachable" ops are instructions which can safely be accessed from the current block.
// For instance, if our SPIR-V contains `%3 = OpFAdd %1 %2`, we would be able to access and
// reuse that computation on following lines. However, if that Add operation occurred inside an
// `if` block, then its SpvId becomes inaccessible once we complete the if statement (since
// depending on the if condition, we may or may not have actually done that computation). The
// same logic applies to other control-flow blocks as well. Once an instruction becomes
// unreachable, we remove it from both op-caches.
SkTArray<SpvId> fReachableOps;
// The "store-ops" list contains a running list of all the pointers in the store cache. If a
// store occurs inside of a conditional block, once that block exits, we no longer know what is
// stored in that particular SpvId. At that point, we must remove any associated entry from the
// store cache.
SkTArray<SpvId> fStoreOps;
// label of the current block, or 0 if we are not in a block
SpvId fCurrentBlock;
SkTArray<SpvId> fBreakTarget;
SkTArray<SpvId> fContinueTarget;
bool fWroteRTFlip = false;
// holds variables synthesized during output, for lifetime purposes
SymbolTable fSynthetics;
// Holds a list of uniforms that were declared as globals at the top-level instead of in an
// interface block.
UniformBuffer fUniformBuffer;
std::vector<const VarDeclaration*> fTopLevelUniforms;
SkTHashMap<const Variable*, int> fTopLevelUniformMap; // <var, UniformBuffer field index>
SkTHashSet<const Variable*> fSPIRVBonusVariables;
SpvId fUniformBufferId = NA;
friend class PointerLValue;
friend class SwizzleLValue;
using INHERITED = CodeGenerator;
};
} // namespace SkSL
#endif