Path: blob/main_old/src/compiler/translator/BuildSPIRV.h
1693 views
//1// Copyright 2021 The ANGLE Project Authors. All rights reserved.2// Use of this source code is governed by a BSD-style license that can be3// found in the LICENSE file.4//5// BuildSPIRV: Helper for OutputSPIRV to build SPIR-V.6//78#ifndef COMPILER_TRANSLATOR_BUILDSPIRV_H_9#define COMPILER_TRANSLATOR_BUILDSPIRV_H_1011#include "common/FixedVector.h"12#include "common/PackedEnums.h"13#include "common/bitset_utils.h"14#include "common/hash_utils.h"15#include "common/spirv/spirv_instruction_builder_autogen.h"16#include "compiler/translator/Compiler.h"1718namespace spirv = angle::spirv;1920namespace sh21{22// Helper classes to map types to ids2324// The same GLSL type may map to multiple SPIR-V types when said GLSL type is used differently in25// the shader source, for example used with |invariant| and without, used in an interface block etc.26// This type contains the pieces of information that differentiate SPIR-V types derived from the27// same GLSL type. This is referred to as "SPIR-V type specialization" henceforth.28struct SpirvType;29class SpirvTypeSpec30{31public:32// Some of the properties that specialize SPIR-V types apply to structs or arrays, but not to33// their fields or basic types. When extracting fields, array elements, columns or basic types34// from a type, the following helpers are used to remove any ineffective (and thus incorrect)35// specialization.36void inferDefaults(const TType &type, TCompiler *compiler);37void onArrayElementSelection(bool isElementTypeBlock, bool isElementTypeArray);38void onBlockFieldSelection(const TType &fieldType);39void onMatrixColumnSelection();40void onVectorComponentSelection();4142// If a structure is used in two interface blocks with different layouts, it would have43// to generate two SPIR-V types, as its fields' Offset decorations could be different.44// For non-block types, when used in an interface block as an array, they could generate45// different ArrayStride decorations. As such, the block storage is part of the SPIR-V type46// except for non-block non-array types.47TLayoutBlockStorage blockStorage = EbsUnspecified;4849// If a structure is used in two I/O blocks or output varyings with and without the invariant50// qualifier, it would also have to generate two SPIR-V types, as its fields' Invariant51// decorations would be different.52bool isInvariantBlock = false;5354// Similarly, a structure containing matrices may be used both with the column_major and55// row_major layout qualifier, generating two SPIR-V types with different decorations on its56// fields.57bool isRowMajorQualifiedBlock = false;5859// Arrays when used in an interface block produce a different type which is decorated with an60// ArrayStride. Row-major qualified arrays of matrices can potentially produce a different61// stride from column-major ones.62bool isRowMajorQualifiedArray = false;6364// Bool is disallowed in interface blocks in SPIR-V. This type is emulated with uint. This65// property applies to both blocks with bools in them and the bool type inside the block itself.66bool isOrHasBoolInInterfaceBlock = false;6768// When |patch| is specified on an I/O block, the members of the type itself are decorated with69// it. This is not recursively applied, and since each I/O block has a unique type, this70// doesn't actually result in duplicated types even if it's specializing the type.71bool isPatchIOBlock = false;72};7374struct SpirvType75{76// If struct or interface block, the type is identified by the pointer. Note that both77// TStructure and TInterfaceBlock inherit from TFieldListCollection, and their difference is78// irrelevant as far as SPIR-V type is concerned.79const TFieldListCollection *block = nullptr;8081// Otherwise, it's a basic type + column, row and array dimensions, or it's an image82// declaration.83//84// Notes:85//86// - `precision` turns into a RelaxedPrecision decoration on the variable and instructions.87// - `precise` turns into a NoContraction decoration on the instructions.88// - `readonly`, `writeonly`, `coherent`, `volatile` and `restrict` only apply to memory object89// declarations90// - `invariant` only applies to variable or members of a block91// - `matrixPacking` only applies to members of a struct92TBasicType type = EbtFloat;9394uint8_t primarySize = 1;95uint8_t secondarySize = 1;9697TSpan<const unsigned int> arraySizes;9899// Only useful for image types.100TLayoutImageInternalFormat imageInternalFormat = EiifUnspecified;101102// For sampled images (i.e. GLSL samplers), there are two type ids; one is the OpTypeImage that103// declares the image itself, and one OpTypeSampledImage. `isSamplerBaseImage` distinguishes104// between these two types. Note that for the former, the basic type is still Ebt*Sampler* to105// distinguish it from storage images (which have a basic type of Ebt*Image*).106bool isSamplerBaseImage = false;107108// Anything that can cause the same GLSL type to produce different SPIR-V types.109SpirvTypeSpec typeSpec;110};111112struct SpirvIdAndIdList113{114spirv::IdRef id;115spirv::IdRefList idList;116117bool operator==(const SpirvIdAndIdList &other) const118{119return id == other.id && idList == other.idList;120}121};122123struct SpirvIdAndStorageClass124{125spirv::IdRef id;126spv::StorageClass storageClass;127128bool operator==(const SpirvIdAndStorageClass &other) const129{130return id == other.id && storageClass == other.storageClass;131}132};133134struct SpirvTypeHash135{136size_t operator()(const sh::SpirvType &type) const137{138// Block storage must only affect the type if it's a block type or array type (in a block).139ASSERT(type.typeSpec.blockStorage == sh::EbsUnspecified || type.block != nullptr ||140!type.arraySizes.empty());141142// Invariant must only affect the type if it's a block type.143ASSERT(!type.typeSpec.isInvariantBlock || type.block != nullptr);144145// Row-major block must only affect the type if it's a block type.146ASSERT(!type.typeSpec.isRowMajorQualifiedBlock || type.block != nullptr);147148// Patch must only affect the type if it's a block type.149ASSERT(!type.typeSpec.isPatchIOBlock || type.block != nullptr);150151// Row-major array must only affect the type if it's an array of non-square matrices in152// an std140 or std430 block.153ASSERT(!type.typeSpec.isRowMajorQualifiedArray ||154(type.block == nullptr && !type.arraySizes.empty() && type.secondarySize > 1 &&155type.primarySize != type.secondarySize &&156type.typeSpec.blockStorage != sh::EbsUnspecified));157158size_t result = 0;159160if (!type.arraySizes.empty())161{162result = angle::ComputeGenericHash(type.arraySizes.data(),163type.arraySizes.size() * sizeof(type.arraySizes[0]));164}165166if (type.block != nullptr)167{168return result ^ angle::ComputeGenericHash(&type.block, sizeof(type.block)) ^169static_cast<size_t>(type.typeSpec.isInvariantBlock) ^170(static_cast<size_t>(type.typeSpec.isRowMajorQualifiedBlock) << 1) ^171(static_cast<size_t>(type.typeSpec.isRowMajorQualifiedArray) << 2) ^172(static_cast<size_t>(type.typeSpec.isPatchIOBlock) << 3) ^173(type.typeSpec.blockStorage << 4);174}175176static_assert(sh::EbtLast < 256, "Basic type doesn't fit in uint8_t");177static_assert(sh::EbsLast < 8, "Block storage doesn't fit in 3 bits");178static_assert(sh::EiifLast < 32, "Image format doesn't fit in 5 bits");179ASSERT(type.primarySize > 0 && type.primarySize <= 4);180ASSERT(type.secondarySize > 0 && type.secondarySize <= 4);181182const uint8_t properties[4] = {183static_cast<uint8_t>(type.type),184static_cast<uint8_t>((type.primarySize - 1) | (type.secondarySize - 1) << 2 |185type.isSamplerBaseImage << 4),186static_cast<uint8_t>(type.typeSpec.blockStorage | type.imageInternalFormat << 3),187// Padding because ComputeGenericHash expects a key size divisible by 4188};189190return result ^ angle::ComputeGenericHash(properties, sizeof(properties));191}192};193194struct SpirvIdAndIdListHash195{196size_t operator()(const SpirvIdAndIdList &key) const197{198return angle::ComputeGenericHash(key.idList.data(),199key.idList.size() * sizeof(key.idList[0])) ^200key.id;201}202};203204struct SpirvIdAndStorageClassHash205{206size_t operator()(const SpirvIdAndStorageClass &key) const207{208ASSERT(key.storageClass < 16);209return key.storageClass | key.id << 4;210}211};212213// Data tracked per SPIR-V type (keyed by SpirvType).214struct SpirvTypeData215{216// The SPIR-V id corresponding to the type.217spirv::IdRef id;218};219220// Decorations to be applied to variable or intermediate ids which are not part of the SPIR-V type221// and are not specific enough (like DescriptorSet) to be handled automatically. Currently, these222// are:223//224// RelaxedPrecision: used to implement |lowp| and |mediump|225// NoContraction: used to implement |precise|. TODO: support this. It requires the precise226// property to be promoted through the nodes in the AST, which currently isn't.227// http://anglebug.com/4889228// Invariant: used to implement |invariant|, which is applied to output variables.229//230// Note that Invariant applies to variables and NoContraction to arithmetic instructions, so they231// are mutually exclusive and a maximum of 2 decorations are possible. FixedVector::push_back will232// ASSERT if the given size is ever not enough.233using SpirvDecorations = angle::FixedVector<spv::Decoration, 2>;234235// A block of code. SPIR-V produces forward references to blocks, such as OpBranchConditional236// specifying the id of the if and else blocks, each of those referencing the id of the block after237// the else. Additionally, local variable declarations are accumulated at the top of the first238// block in a function. For these reasons, each block of SPIR-V is generated separately and239// assembled at the end of the function, allowing prior blocks to be modified when necessary.240struct SpirvBlock241{242// Id of the block243spirv::IdRef labelId;244245// Local variable declarations. Only the first block of a function is allowed to contain any246// instructions here.247spirv::Blob localVariables;248249// Everything *after* OpLabel (which itself is not generated until blocks are assembled) and250// local variables.251spirv::Blob body;252253// Whether the block is terminated. Useful for functions without return, asserting that code is254// not added after return/break/continue etc (i.e. dead code, which should really be removed255// earlier by a transformation, but could also be hacked by returning a bogus block to contain256// all the "garbage" to throw away), last switch case without a break, etc.257bool isTerminated = false;258};259260// Conditional code, constituting ifs, switches and loops.261struct SpirvConditional262{263// The id of blocks that make up the conditional.264//265// - For if, there are three blocks: the then, else and merge blocks266// - For loops, there are four blocks: the condition, body, continue and merge blocks267// - For switch, there are a number of blocks based on the cases.268//269// In all cases, the merge block is the last block in this list. When the conditional is done270// with, that's the block that will be made "current" and future instructions written to. The271// merge block is also the branch target of "break" instructions.272//273// For loops, the continue target block is the one before last block in this list.274std::vector<spirv::IdRef> blockIds;275276// Up to which block is already generated. Used by nextConditionalBlock() to generate a block277// and give it an id pre-determined in blockIds.278size_t nextBlockToWrite = 0;279280// Used to determine if continue will affect this (i.e. it's a loop).281bool isContinuable = false;282// Used to determine if break will affect this (i.e. it's a loop or switch).283bool isBreakable = false;284};285286// List of known extensions287enum class SPIRVExtensions288{289// GL_OVR_multiview / SPV_KHR_multiview290MultiviewOVR = 0,291292InvalidEnum = 1,293EnumCount = 1,294};295296// Helper class to construct SPIR-V297class SPIRVBuilder : angle::NonCopyable298{299public:300SPIRVBuilder(TCompiler *compiler,301ShCompileOptions compileOptions,302ShHashFunction64 hashFunction,303NameMap &nameMap);304305spirv::IdRef getNewId(const SpirvDecorations &decorations);306SpirvType getSpirvType(const TType &type, const SpirvTypeSpec &typeSpec) const;307const SpirvTypeData &getTypeData(const TType &type, const SpirvTypeSpec &typeSpec);308const SpirvTypeData &getTypeDataOverrideTypeSpec(const TType &type,309const SpirvTypeSpec &typeSpec);310const SpirvTypeData &getSpirvTypeData(const SpirvType &type, const TSymbol *block);311spirv::IdRef getBasicTypeId(TBasicType basicType, size_t size);312spirv::IdRef getTypePointerId(spirv::IdRef typeId, spv::StorageClass storageClass);313spirv::IdRef getFunctionTypeId(spirv::IdRef returnTypeId, const spirv::IdRefList ¶mTypeIds);314315// Decorations that may apply to intermediate instructions (in addition to variables).316// |precise| is only applicable to arithmetic nodes.317SpirvDecorations getDecorations(const TType &type);318SpirvDecorations getArithmeticDecorations(const TType &type, bool isPrecise);319320// Extended instructions321spirv::IdRef getExtInstImportIdStd();322323spirv::Blob *getSpirvDebug() { return &mSpirvDebug; }324spirv::Blob *getSpirvDecorations() { return &mSpirvDecorations; }325spirv::Blob *getSpirvTypeAndConstantDecls() { return &mSpirvTypeAndConstantDecls; }326spirv::Blob *getSpirvTypePointerDecls() { return &mSpirvTypePointerDecls; }327spirv::Blob *getSpirvFunctionTypeDecls() { return &mSpirvFunctionTypeDecls; }328spirv::Blob *getSpirvVariableDecls() { return &mSpirvVariableDecls; }329spirv::Blob *getSpirvFunctions() { return &mSpirvFunctions; }330spirv::Blob *getSpirvCurrentFunctionBlock()331{332ASSERT(!mSpirvCurrentFunctionBlocks.empty() &&333!mSpirvCurrentFunctionBlocks.back().isTerminated);334return &mSpirvCurrentFunctionBlocks.back().body;335}336spirv::IdRef getSpirvCurrentFunctionBlockId()337{338ASSERT(!mSpirvCurrentFunctionBlocks.empty() &&339!mSpirvCurrentFunctionBlocks.back().isTerminated);340return mSpirvCurrentFunctionBlocks.back().labelId;341}342bool isCurrentFunctionBlockTerminated() const343{344ASSERT(!mSpirvCurrentFunctionBlocks.empty());345return mSpirvCurrentFunctionBlocks.back().isTerminated;346}347void terminateCurrentFunctionBlock()348{349ASSERT(!mSpirvCurrentFunctionBlocks.empty());350mSpirvCurrentFunctionBlocks.back().isTerminated = true;351}352const SpirvConditional *getCurrentConditional() { return &mConditionalStack.back(); }353354bool isInvariantOutput(const TType &type) const;355356void addCapability(spv::Capability capability);357void addExecutionMode(spv::ExecutionMode executionMode);358void addExtension(SPIRVExtensions extension);359void setEntryPointId(spirv::IdRef id);360void addEntryPointInterfaceVariableId(spirv::IdRef id);361void writePerVertexBuiltIns(const TType &type, spirv::IdRef typeId);362void writeInterfaceVariableDecorations(const TType &type, spirv::IdRef variableId);363void writeBranchConditional(spirv::IdRef conditionValue,364spirv::IdRef trueBlock,365spirv::IdRef falseBlock,366spirv::IdRef mergeBlock);367void writeBranchConditionalBlockEnd();368void writeLoopHeader(spirv::IdRef branchToBlock,369spirv::IdRef continueBlock,370spirv::IdRef mergeBlock);371void writeLoopConditionEnd(spirv::IdRef conditionValue,372spirv::IdRef branchToBlock,373spirv::IdRef mergeBlock);374void writeLoopContinueEnd(spirv::IdRef headerBlock);375void writeLoopBodyEnd(spirv::IdRef continueBlock);376void writeSwitch(spirv::IdRef conditionValue,377spirv::IdRef defaultBlock,378const spirv::PairLiteralIntegerIdRefList &targetPairList,379spirv::IdRef mergeBlock);380void writeSwitchCaseBlockEnd();381382spirv::IdRef getBoolConstant(bool value);383spirv::IdRef getUintConstant(uint32_t value);384spirv::IdRef getIntConstant(int32_t value);385spirv::IdRef getFloatConstant(float value);386spirv::IdRef getUvecConstant(uint32_t value, int size);387spirv::IdRef getIvecConstant(int32_t value, int size);388spirv::IdRef getVecConstant(float value, int size);389spirv::IdRef getCompositeConstant(spirv::IdRef typeId, const spirv::IdRefList &values);390spirv::IdRef getNullConstant(spirv::IdRef typeId);391392// Helpers to start and end a function.393void startNewFunction(spirv::IdRef functionId, const TFunction *func);394void assembleSpirvFunctionBlocks();395396// Helper to declare a variable. Function-local variables must be placed in the first block of397// the current function.398spirv::IdRef declareVariable(spirv::IdRef typeId,399spv::StorageClass storageClass,400const SpirvDecorations &decorations,401spirv::IdRef *initializerId,402const char *name);403// Helper to declare specialization constants.404spirv::IdRef declareSpecConst(TBasicType type, int id, const char *name);405406// Helpers for conditionals.407void startConditional(size_t blockCount, bool isContinuable, bool isBreakable);408void nextConditionalBlock();409void endConditional();410bool isInLoop() const;411spirv::IdRef getBreakTargetId() const;412spirv::IdRef getContinueTargetId() const;413414// TODO: remove name hashing once translation through glslang is removed. That is necessary to415// avoid name collision between ANGLE's internal symbols and user-defined ones when compiling416// the generated GLSL, but is irrelevant when generating SPIR-V directly. Currently, the SPIR-V417// transformer relies on the "mapped" names, which should also be changed when this hashing is418// removed.419ImmutableString hashName(const TSymbol *symbol);420ImmutableString hashTypeName(const TType &type);421ImmutableString hashFieldName(const TField *field);422ImmutableString hashFunctionName(const TFunction *func);423424spirv::Blob getSpirv();425426private:427SpirvTypeData declareType(const SpirvType &type, const TSymbol *block);428429uint32_t calculateBaseAlignmentAndSize(const SpirvType &type, uint32_t *sizeInStorageBlockOut);430uint32_t calculateSizeAndWriteOffsetDecorations(const SpirvType &type,431spirv::IdRef typeId,432uint32_t blockBaseAlignment);433void writeMemberDecorations(const SpirvType &type, spirv::IdRef typeId);434void writeInterpolationDecoration(TQualifier qualifier, spirv::IdRef id, uint32_t fieldIndex);435436// Helpers for type declaration.437void getImageTypeParameters(TBasicType type,438spirv::IdRef *sampledTypeOut,439spv::Dim *dimOut,440spirv::LiteralInteger *depthOut,441spirv::LiteralInteger *arrayedOut,442spirv::LiteralInteger *multisampledOut,443spirv::LiteralInteger *sampledOut);444spv::ImageFormat getImageFormat(TLayoutImageInternalFormat imageInternalFormat);445446spirv::IdRef getBasicConstantHelper(uint32_t value,447TBasicType type,448angle::HashMap<uint32_t, spirv::IdRef> *constants);449spirv::IdRef getNullVectorConstantHelper(TBasicType type, int size);450spirv::IdRef getVectorConstantHelper(spirv::IdRef valueId, TBasicType type, int size);451452uint32_t nextUnusedBinding();453uint32_t nextUnusedInputLocation(uint32_t consumedCount);454uint32_t nextUnusedOutputLocation(uint32_t consumedCount);455456void writeExecutionModes(spirv::Blob *blob);457void writeExtensions(spirv::Blob *blob);458void writeSourceExtensions(spirv::Blob *blob);459460ANGLE_MAYBE_UNUSED TCompiler *mCompiler;461ShCompileOptions mCompileOptions;462gl::ShaderType mShaderType;463464// Capabilities the shader is using. Accumulated as the instructions are generated. The Shader465// capability is unconditionally generated, so it's not tracked.466std::set<spv::Capability> mCapabilities;467// Execution modes the shader is using. Most execution modes are automatically derived from468// shader metadata, but some are only discovered while traversing the tree. Only the latter469// execution modes are stored here.470angle::BitSet<32> mExecutionModes;471// Extensions used by the shader.472angle::PackedEnumBitSet<SPIRVExtensions> mExtensions;473474// The list of interface variables and the id of main() populated as the instructions are475// generated. Used for the OpEntryPoint instruction.476spirv::IdRefList mEntryPointInterfaceList;477spirv::IdRef mEntryPointId;478479// Id of imported instructions, if used.480spirv::IdRef mExtInstImportIdStd;481482// Current ID bound, used to allocate new ids.483spirv::IdRef mNextAvailableId;484485// A map from the AST type to the corresponding SPIR-V ID and associated data. Note that TType486// includes a lot of information that pertains to the variable that has the type, not the type487// itself. SpirvType instead contains only information that can identify the type itself.488angle::HashMap<SpirvType, SpirvTypeData, SpirvTypeHash> mTypeMap;489490// Various sections of SPIR-V. Each section grows as SPIR-V is generated, and the final result491// is obtained by stitching the sections together. This puts the instructions in the order492// required by the spec.493spirv::Blob mSpirvDebug;494spirv::Blob mSpirvDecorations;495spirv::Blob mSpirvTypeAndConstantDecls;496spirv::Blob mSpirvTypePointerDecls;497spirv::Blob mSpirvFunctionTypeDecls;498spirv::Blob mSpirvVariableDecls;499spirv::Blob mSpirvFunctions;500// A list of blocks created for the current function. These are assembled by501// assembleSpirvFunctionBlocks() when the function is entirely visited. Local variables need to502// be inserted at the beginning of the first function block, so the entire SPIR-V of the503// function cannot be obtained until it's fully visited.504//505// The last block in this list is the one currently being written to.506std::vector<SpirvBlock> mSpirvCurrentFunctionBlocks;507508// List of constants that are already defined (for reuse).509spirv::IdRef mBoolConstants[2];510angle::HashMap<uint32_t, spirv::IdRef> mUintConstants;511angle::HashMap<uint32_t, spirv::IdRef> mIntConstants;512angle::HashMap<uint32_t, spirv::IdRef> mFloatConstants;513angle::HashMap<SpirvIdAndIdList, spirv::IdRef, SpirvIdAndIdListHash> mCompositeConstants;514// Keyed by typeId, returns the null constant corresponding to that type.515std::vector<spirv::IdRef> mNullConstants;516517// List of type pointers that are already defined.518// TODO: if all users call getTypeData(), move to SpirvTypeData. http://anglebug.com/4889519angle::HashMap<SpirvIdAndStorageClass, spirv::IdRef, SpirvIdAndStorageClassHash>520mTypePointerIdMap;521522// List of function types that are already defined.523angle::HashMap<SpirvIdAndIdList, spirv::IdRef, SpirvIdAndIdListHash> mFunctionTypeIdMap;524525// Stack of conditionals. When an if, loop or switch is visited, a new conditional scope is526// added. When the conditional construct is entirely visited, it's popped. As the blocks of527// the conditional constructs are visited, ids are consumed from the top of the stack. When528// break or continue is visited, the stack is traversed backwards until a loop or switch is529// found.530std::vector<SpirvConditional> mConditionalStack;531532// name hashing.533ShHashFunction64 mHashFunction;534NameMap &mNameMap;535536// Every resource that requires set & binding layout qualifiers is assigned set 0 and an537// arbitrary binding. Every input/output that requires a location layout qualifier is assigned538// an arbitrary location as well.539//540// The link-time SPIR-V transformer modifies set, binding and location decorations in SPIR-V541// directly.542uint32_t mNextUnusedBinding;543uint32_t mNextUnusedInputLocation;544uint32_t mNextUnusedOutputLocation;545};546} // namespace sh547548#endif // COMPILER_TRANSLATOR_BUILDSPIRV_H_549550551