mirror of
https://github.com/bkaradzic/bgfx.git
synced 2026-02-17 20:52:36 +01:00
Updated spirv-tools.
This commit is contained in:
5
3rdparty/spirv-tools/README.md
vendored
5
3rdparty/spirv-tools/README.md
vendored
@@ -349,10 +349,7 @@ option, like so:
|
||||
|
||||
```sh
|
||||
# In <spirv-dir> (the SPIRV-Tools repo root):
|
||||
git clone https://github.com/protocolbuffers/protobuf external/protobuf
|
||||
pushd external/protobuf
|
||||
git checkout v3.7.1
|
||||
popd
|
||||
git clone --depth=1 --branch v3.13.0 https://github.com/protocolbuffers/protobuf external/protobuf
|
||||
|
||||
# In your build directory:
|
||||
cmake [-G <platform-generator>] <spirv-dir> -DSPIRV_BUILD_FUZZER=ON
|
||||
|
||||
@@ -1 +1 @@
|
||||
"v2020.5", "SPIRV-Tools v2020.5 86402fec24134b6d4103b6ad1e689fb5840f720f"
|
||||
"v2020.5", "SPIRV-Tools v2020.5 b2259d7c11768aa4bcafa242d63802e9ca427764"
|
||||
|
||||
@@ -21,4 +21,5 @@
|
||||
{20, "W3C WebGPU Group", "WHLSL Shader Translator", "W3C WebGPU Group WHLSL Shader Translator"},
|
||||
{21, "Google", "Clspv", "Google Clspv"},
|
||||
{22, "Google", "MLIR SPIR-V Serializer", "Google MLIR SPIR-V Serializer"},
|
||||
{23, "Google", "Tint Compiler", "Google Tint Compiler"},
|
||||
{23, "Google", "Tint Compiler", "Google Tint Compiler"},
|
||||
{24, "Google", "ANGLE Shader Compiler", "Google ANGLE Shader Compiler"},
|
||||
@@ -24,6 +24,7 @@
|
||||
//
|
||||
// CreateInstBindlessCheckPass
|
||||
// CreateInstBuffAddrCheckPass
|
||||
// CreateInstDebugPrintfPass
|
||||
//
|
||||
// More detailed documentation of these routines can be found in optimizer.hpp
|
||||
|
||||
@@ -33,7 +34,7 @@ namespace spvtools {
|
||||
//
|
||||
// The following values provide offsets into the output buffer struct
|
||||
// generated by InstrumentPass::GenDebugStreamWrite. This method is utilized
|
||||
// by InstBindlessCheckPass.
|
||||
// by InstBindlessCheckPass, InstBuffAddrCheckPass, and InstDebugPrintfPass.
|
||||
//
|
||||
// The first member of the debug output buffer contains the next available word
|
||||
// in the data stream to be written. Shaders will atomically read and update
|
||||
@@ -138,12 +139,21 @@ static const int kInstValidationOutError = kInstStageOutCnt;
|
||||
// A bindless bounds error will output the index and the bound.
|
||||
static const int kInstBindlessBoundsOutDescIndex = kInstStageOutCnt + 1;
|
||||
static const int kInstBindlessBoundsOutDescBound = kInstStageOutCnt + 2;
|
||||
static const int kInstBindlessBoundsOutCnt = kInstStageOutCnt + 3;
|
||||
static const int kInstBindlessBoundsOutUnused = kInstStageOutCnt + 3;
|
||||
static const int kInstBindlessBoundsOutCnt = kInstStageOutCnt + 4;
|
||||
|
||||
// A bindless uninitialized error will output the index.
|
||||
// A descriptor uninitialized error will output the index.
|
||||
static const int kInstBindlessUninitOutDescIndex = kInstStageOutCnt + 1;
|
||||
static const int kInstBindlessUninitOutUnused = kInstStageOutCnt + 2;
|
||||
static const int kInstBindlessUninitOutCnt = kInstStageOutCnt + 3;
|
||||
static const int kInstBindlessUninitOutUnused2 = kInstStageOutCnt + 3;
|
||||
static const int kInstBindlessUninitOutCnt = kInstStageOutCnt + 4;
|
||||
|
||||
// A buffer out-of-bounds error will output the descriptor
|
||||
// index, the buffer offset and the buffer size
|
||||
static const int kInstBindlessBuffOOBOutDescIndex = kInstStageOutCnt + 1;
|
||||
static const int kInstBindlessBuffOOBOutBuffOff = kInstStageOutCnt + 2;
|
||||
static const int kInstBindlessBuffOOBOutBuffSize = kInstStageOutCnt + 3;
|
||||
static const int kInstBindlessBuffOOBOutCnt = kInstStageOutCnt + 4;
|
||||
|
||||
// A buffer address unalloc error will output the 64-bit pointer in
|
||||
// two 32-bit pieces, lower bits first.
|
||||
@@ -152,7 +162,7 @@ static const int kInstBuffAddrUnallocOutDescPtrHi = kInstStageOutCnt + 2;
|
||||
static const int kInstBuffAddrUnallocOutCnt = kInstStageOutCnt + 3;
|
||||
|
||||
// Maximum Output Record Member Count
|
||||
static const int kInstMaxOutCnt = kInstStageOutCnt + 3;
|
||||
static const int kInstMaxOutCnt = kInstStageOutCnt + 4;
|
||||
|
||||
// Validation Error Codes
|
||||
//
|
||||
@@ -160,6 +170,7 @@ static const int kInstMaxOutCnt = kInstStageOutCnt + 3;
|
||||
static const int kInstErrorBindlessBounds = 0;
|
||||
static const int kInstErrorBindlessUninit = 1;
|
||||
static const int kInstErrorBuffAddrUnallocRef = 2;
|
||||
static const int kInstErrorBindlessBuffOOB = 3;
|
||||
|
||||
// Direct Input Buffer Offsets
|
||||
//
|
||||
@@ -197,7 +208,10 @@ static const int kDebugOutputPrintfStream = 3;
|
||||
// At offset kDebugInputBindlessInitOffset in Data[] is a single uint which
|
||||
// gives an offset to the start of the bindless initialization data. More
|
||||
// specifically, if the following value is zero, we know that the descriptor at
|
||||
// (set = s, binding = b, index = i) is not initialized:
|
||||
// (set = s, binding = b, index = i) is not initialized; if the value is
|
||||
// non-zero, and the descriptor points to a buffer, the value is the length of
|
||||
// the buffer in bytes and can be used to check for out-of-bounds buffer
|
||||
// references:
|
||||
// Data[ i + Data[ b + Data[ s + Data[ kDebugInputBindlessInitOffset ] ] ] ]
|
||||
static const int kDebugInputBindlessInitOffset = 0;
|
||||
|
||||
|
||||
@@ -675,6 +675,13 @@ SPIRV_TOOLS_EXPORT void spvReducerOptionsSetStepLimit(
|
||||
SPIRV_TOOLS_EXPORT void spvReducerOptionsSetFailOnValidationError(
|
||||
spv_reducer_options options, bool fail_on_validation_error);
|
||||
|
||||
// Sets the function that the reducer should target. If set to zero the reducer
|
||||
// will target all functions as well as parts of the module that lie outside
|
||||
// functions. Otherwise the reducer will restrict reduction to the function
|
||||
// with result id |target_function|, which is required to exist.
|
||||
SPIRV_TOOLS_EXPORT void spvReducerOptionsSetTargetFunction(
|
||||
spv_reducer_options options, uint32_t target_function);
|
||||
|
||||
// Creates a fuzzer options object with default options. Returns a valid
|
||||
// options object. The object remains valid until it is passed into
|
||||
// |spvFuzzerOptionsDestroy|.
|
||||
@@ -709,6 +716,11 @@ SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetShrinkerStepLimit(
|
||||
SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableFuzzerPassValidation(
|
||||
spv_fuzzer_options options);
|
||||
|
||||
// Enables all fuzzer passes during a fuzzing run (instead of a random subset
|
||||
// of passes).
|
||||
SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableAllPasses(
|
||||
spv_fuzzer_options options);
|
||||
|
||||
// Encodes the given SPIR-V assembly text to its binary representation. The
|
||||
// length parameter specifies the number of bytes for text. Encoded binary will
|
||||
// be stored into *binary. Any error will be written into *diagnostic if
|
||||
|
||||
@@ -202,6 +202,11 @@ class ReducerOptions {
|
||||
fail_on_validation_error);
|
||||
}
|
||||
|
||||
// See spvReducerOptionsSetTargetFunction.
|
||||
void set_target_function(uint32_t target_function) {
|
||||
spvReducerOptionsSetTargetFunction(options_, target_function);
|
||||
}
|
||||
|
||||
private:
|
||||
spv_reducer_options options_;
|
||||
};
|
||||
@@ -242,6 +247,9 @@ class FuzzerOptions {
|
||||
spvFuzzerOptionsEnableFuzzerPassValidation(options_);
|
||||
}
|
||||
|
||||
// See spvFuzzerOptionsEnableAllPasses.
|
||||
void enable_all_passes() { spvFuzzerOptionsEnableAllPasses(options_); }
|
||||
|
||||
private:
|
||||
spv_fuzzer_options options_;
|
||||
};
|
||||
|
||||
@@ -764,7 +764,7 @@ Optimizer::PassToken CreateCombineAccessChainsPass();
|
||||
// initialization checking, both of which require input buffer support.
|
||||
Optimizer::PassToken CreateInstBindlessCheckPass(
|
||||
uint32_t desc_set, uint32_t shader_id, bool input_length_enable = false,
|
||||
bool input_init_enable = false);
|
||||
bool input_init_enable = false, bool input_buff_oob_enable = false);
|
||||
|
||||
// Create a pass to instrument physical buffer address checking
|
||||
// This pass instruments all physical buffer address references to check that
|
||||
|
||||
17
3rdparty/spirv-tools/source/opcode.cpp
vendored
17
3rdparty/spirv-tools/source/opcode.cpp
vendored
@@ -731,3 +731,20 @@ bool spvOpcodeIsAccessChain(SpvOp opcode) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool spvOpcodeIsBit(SpvOp opcode) {
|
||||
switch (opcode) {
|
||||
case SpvOpShiftRightLogical:
|
||||
case SpvOpShiftRightArithmetic:
|
||||
case SpvOpShiftLeftLogical:
|
||||
case SpvOpBitwiseOr:
|
||||
case SpvOpBitwiseXor:
|
||||
case SpvOpBitwiseAnd:
|
||||
case SpvOpNot:
|
||||
case SpvOpBitReverse:
|
||||
case SpvOpBitCount:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
9
3rdparty/spirv-tools/source/opcode.h
vendored
9
3rdparty/spirv-tools/source/opcode.h
vendored
@@ -134,17 +134,20 @@ bool spvOpcodeIsDebug(SpvOp opcode);
|
||||
// where the order of the operands is irrelevant.
|
||||
bool spvOpcodeIsCommutativeBinaryOperator(SpvOp opcode);
|
||||
|
||||
// Returns true for opcodes that represents linear algebra instructions.
|
||||
// Returns true for opcodes that represent linear algebra instructions.
|
||||
bool spvOpcodeIsLinearAlgebra(SpvOp opcode);
|
||||
|
||||
// Returns true for opcodes that represents an image sample instruction.
|
||||
// Returns true for opcodes that represent image sample instructions.
|
||||
bool spvOpcodeIsImageSample(SpvOp opcode);
|
||||
|
||||
// Returns a vector containing the indices of the memory semantics <id>
|
||||
// operands for |opcode|.
|
||||
std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode);
|
||||
|
||||
// Returns true for opcodes that represents access chain instructions.
|
||||
// Returns true for opcodes that represent access chain instructions.
|
||||
bool spvOpcodeIsAccessChain(SpvOp opcode);
|
||||
|
||||
// Returns true for opcodes that represent bit instructions.
|
||||
bool spvOpcodeIsBit(SpvOp opcode);
|
||||
|
||||
#endif // SOURCE_OPCODE_H_
|
||||
|
||||
@@ -956,6 +956,7 @@ void AggressiveDCEPass::InitExtensions() {
|
||||
"SPV_AMD_gpu_shader_half_float",
|
||||
"SPV_KHR_shader_draw_parameters",
|
||||
"SPV_KHR_subgroup_vote",
|
||||
"SPV_KHR_8bit_storage",
|
||||
"SPV_KHR_16bit_storage",
|
||||
"SPV_KHR_device_group",
|
||||
"SPV_KHR_multiview",
|
||||
|
||||
2
3rdparty/spirv-tools/source/opt/ccp_pass.cpp
vendored
2
3rdparty/spirv-tools/source/opt/ccp_pass.cpp
vendored
@@ -150,7 +150,7 @@ SSAPropagator::PropStatus CCPPass::VisitAssignment(Instruction* instr) {
|
||||
// that CCP has modified the IR, independently of whether the constant is
|
||||
// actually propagated. See
|
||||
// https://github.com/KhronosGroup/SPIRV-Tools/issues/3636 for details.
|
||||
if (folded_inst->result_id() == next_id) created_new_constant_ = true;
|
||||
if (folded_inst->result_id() >= next_id) created_new_constant_ = true;
|
||||
|
||||
return SSAPropagator::kInteresting;
|
||||
}
|
||||
|
||||
@@ -320,9 +320,13 @@ void GraphicsRobustAccessPass::ClampIndicesForAccessChain(
|
||||
maxval_width *= 2;
|
||||
}
|
||||
// Determine the type for |maxval|.
|
||||
uint32_t next_id = context()->module()->IdBound();
|
||||
analysis::Integer signed_type_for_query(maxval_width, true);
|
||||
auto* maxval_type =
|
||||
type_mgr->GetRegisteredType(&signed_type_for_query)->AsInteger();
|
||||
if (next_id != context()->module()->IdBound()) {
|
||||
module_status_.modified = true;
|
||||
}
|
||||
// Access chain indices are treated as signed, so limit the maximum value
|
||||
// of the index so it will always be positive for a signed clamp operation.
|
||||
maxval = std::min(maxval, ((uint64_t(1) << (maxval_width - 1)) - 1));
|
||||
|
||||
@@ -26,13 +26,19 @@ static const int kSpvImageSampledImageIdInIdx = 0;
|
||||
static const int kSpvLoadPtrIdInIdx = 0;
|
||||
static const int kSpvAccessChainBaseIdInIdx = 0;
|
||||
static const int kSpvAccessChainIndex0IdInIdx = 1;
|
||||
static const int kSpvTypePointerTypeIdInIdx = 1;
|
||||
static const int kSpvTypeArrayLengthIdInIdx = 1;
|
||||
static const int kSpvConstantValueInIdx = 0;
|
||||
static const int kSpvVariableStorageClassInIdx = 0;
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// Avoid unused variable warning/error on Linux
|
||||
#ifndef NDEBUG
|
||||
#define USE_ASSERT(x) assert(x)
|
||||
#else
|
||||
#define USE_ASSERT(x) ((void)(x))
|
||||
#endif
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
@@ -48,14 +54,25 @@ uint32_t InstBindlessCheckPass::GenDebugReadLength(
|
||||
uint32_t InstBindlessCheckPass::GenDebugReadInit(uint32_t var_id,
|
||||
uint32_t desc_idx_id,
|
||||
InstructionBuilder* builder) {
|
||||
uint32_t desc_set_base_id =
|
||||
builder->GetUintConstantId(kDebugInputBindlessInitOffset);
|
||||
uint32_t desc_set_idx_id = builder->GetUintConstantId(var2desc_set_[var_id]);
|
||||
uint32_t binding_idx_id = builder->GetUintConstantId(var2binding_[var_id]);
|
||||
uint32_t u_desc_idx_id = GenUintCastCode(desc_idx_id, builder);
|
||||
return GenDebugDirectRead(
|
||||
{desc_set_base_id, desc_set_idx_id, binding_idx_id, u_desc_idx_id},
|
||||
builder);
|
||||
// If desc index checking is not enabled, we know the offset of initialization
|
||||
// entries is 1, so we can avoid loading this value and just add 1 to the
|
||||
// descriptor set.
|
||||
if (!desc_idx_enabled_) {
|
||||
uint32_t desc_set_idx_id =
|
||||
builder->GetUintConstantId(var2desc_set_[var_id] + 1);
|
||||
return GenDebugDirectRead({desc_set_idx_id, binding_idx_id, u_desc_idx_id},
|
||||
builder);
|
||||
} else {
|
||||
uint32_t desc_set_base_id =
|
||||
builder->GetUintConstantId(kDebugInputBindlessInitOffset);
|
||||
uint32_t desc_set_idx_id =
|
||||
builder->GetUintConstantId(var2desc_set_[var_id]);
|
||||
return GenDebugDirectRead(
|
||||
{desc_set_base_id, desc_set_idx_id, binding_idx_id, u_desc_idx_id},
|
||||
builder);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t InstBindlessCheckPass::CloneOriginalReference(
|
||||
@@ -156,13 +173,9 @@ uint32_t InstBindlessCheckPass::GetImageId(Instruction* inst) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Instruction* InstBindlessCheckPass::GetDescriptorTypeInst(
|
||||
Instruction* var_inst) {
|
||||
uint32_t var_type_id = var_inst->type_id();
|
||||
Instruction* var_type_inst = get_def_use_mgr()->GetDef(var_type_id);
|
||||
uint32_t desc_type_id =
|
||||
var_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx);
|
||||
return get_def_use_mgr()->GetDef(desc_type_id);
|
||||
Instruction* InstBindlessCheckPass::GetPointeeTypeInst(Instruction* ptr_inst) {
|
||||
uint32_t pte_ty_id = GetPointeeTypeId(ptr_inst);
|
||||
return get_def_use_mgr()->GetDef(pte_ty_id);
|
||||
}
|
||||
|
||||
bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst,
|
||||
@@ -187,7 +200,7 @@ bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst,
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
Instruction* desc_type_inst = GetDescriptorTypeInst(var_inst);
|
||||
Instruction* desc_type_inst = GetPointeeTypeInst(var_inst);
|
||||
switch (desc_type_inst->opcode()) {
|
||||
case SpvOpTypeArray:
|
||||
case SpvOpTypeRuntimeArray:
|
||||
@@ -195,11 +208,11 @@ bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst,
|
||||
// do not want to instrument loads of descriptors here which are part of
|
||||
// an image-based reference.
|
||||
if (ptr_inst->NumInOperands() < 3) return false;
|
||||
ref->index_id =
|
||||
ref->desc_idx_id =
|
||||
ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
|
||||
break;
|
||||
default:
|
||||
ref->index_id = 0;
|
||||
ref->desc_idx_id = 0;
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
@@ -229,14 +242,14 @@ bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst,
|
||||
ref->ptr_id = desc_load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
|
||||
Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref->ptr_id);
|
||||
if (ptr_inst->opcode() == SpvOp::SpvOpVariable) {
|
||||
ref->index_id = 0;
|
||||
ref->desc_idx_id = 0;
|
||||
ref->var_id = ref->ptr_id;
|
||||
} else if (ptr_inst->opcode() == SpvOp::SpvOpAccessChain) {
|
||||
if (ptr_inst->NumInOperands() != 2) {
|
||||
assert(false && "unexpected bindless index number");
|
||||
return false;
|
||||
}
|
||||
ref->index_id =
|
||||
ref->desc_idx_id =
|
||||
ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
|
||||
ref->var_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
|
||||
Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id);
|
||||
@@ -251,9 +264,150 @@ bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst,
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t InstBindlessCheckPass::FindStride(uint32_t ty_id,
|
||||
uint32_t stride_deco) {
|
||||
uint32_t stride = 0xdeadbeef;
|
||||
bool found = !get_decoration_mgr()->WhileEachDecoration(
|
||||
ty_id, stride_deco, [&stride](const Instruction& deco_inst) {
|
||||
stride = deco_inst.GetSingleWordInOperand(2u);
|
||||
return false;
|
||||
});
|
||||
USE_ASSERT(found && "stride not found");
|
||||
return stride;
|
||||
}
|
||||
|
||||
uint32_t InstBindlessCheckPass::ByteSize(uint32_t ty_id) {
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
const analysis::Type* sz_ty = type_mgr->GetType(ty_id);
|
||||
if (sz_ty->kind() == analysis::Type::kPointer) {
|
||||
// Assuming PhysicalStorageBuffer pointer
|
||||
return 8;
|
||||
}
|
||||
uint32_t size = 1;
|
||||
if (sz_ty->kind() == analysis::Type::kMatrix) {
|
||||
const analysis::Matrix* m_ty = sz_ty->AsMatrix();
|
||||
size = m_ty->element_count() * size;
|
||||
uint32_t stride = FindStride(ty_id, SpvDecorationMatrixStride);
|
||||
if (stride != 0) return size * stride;
|
||||
sz_ty = m_ty->element_type();
|
||||
}
|
||||
if (sz_ty->kind() == analysis::Type::kVector) {
|
||||
const analysis::Vector* v_ty = sz_ty->AsVector();
|
||||
size = v_ty->element_count() * size;
|
||||
sz_ty = v_ty->element_type();
|
||||
}
|
||||
switch (sz_ty->kind()) {
|
||||
case analysis::Type::kFloat: {
|
||||
const analysis::Float* f_ty = sz_ty->AsFloat();
|
||||
size *= f_ty->width();
|
||||
} break;
|
||||
case analysis::Type::kInteger: {
|
||||
const analysis::Integer* i_ty = sz_ty->AsInteger();
|
||||
size *= i_ty->width();
|
||||
} break;
|
||||
default: { assert(false && "unexpected type"); } break;
|
||||
}
|
||||
size /= 8;
|
||||
return size;
|
||||
}
|
||||
|
||||
uint32_t InstBindlessCheckPass::GenLastByteIdx(ref_analysis* ref,
|
||||
InstructionBuilder* builder) {
|
||||
// Find outermost buffer type and its access chain index
|
||||
Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id);
|
||||
Instruction* desc_ty_inst = GetPointeeTypeInst(var_inst);
|
||||
uint32_t buff_ty_id;
|
||||
uint32_t ac_in_idx = 1;
|
||||
switch (desc_ty_inst->opcode()) {
|
||||
case SpvOpTypeArray:
|
||||
case SpvOpTypeRuntimeArray:
|
||||
buff_ty_id = desc_ty_inst->GetSingleWordInOperand(0);
|
||||
++ac_in_idx;
|
||||
break;
|
||||
default:
|
||||
assert(desc_ty_inst->opcode() == SpvOpTypeStruct &&
|
||||
"unexpected descriptor type");
|
||||
buff_ty_id = desc_ty_inst->result_id();
|
||||
break;
|
||||
}
|
||||
// Process remaining access chain indices
|
||||
Instruction* ac_inst = get_def_use_mgr()->GetDef(ref->ptr_id);
|
||||
uint32_t curr_ty_id = buff_ty_id;
|
||||
uint32_t sum_id = 0;
|
||||
while (ac_in_idx < ac_inst->NumInOperands()) {
|
||||
uint32_t curr_idx_id = ac_inst->GetSingleWordInOperand(ac_in_idx);
|
||||
Instruction* curr_idx_inst = get_def_use_mgr()->GetDef(curr_idx_id);
|
||||
Instruction* curr_ty_inst = get_def_use_mgr()->GetDef(curr_ty_id);
|
||||
uint32_t curr_offset_id = 0;
|
||||
switch (curr_ty_inst->opcode()) {
|
||||
case SpvOpTypeArray:
|
||||
case SpvOpTypeRuntimeArray:
|
||||
case SpvOpTypeMatrix: {
|
||||
// Get array/matrix stride and multiply by current index
|
||||
uint32_t stride_deco = (curr_ty_inst->opcode() == SpvOpTypeMatrix)
|
||||
? SpvDecorationMatrixStride
|
||||
: SpvDecorationArrayStride;
|
||||
uint32_t arr_stride = FindStride(curr_ty_id, stride_deco);
|
||||
uint32_t arr_stride_id = builder->GetUintConstantId(arr_stride);
|
||||
Instruction* curr_offset_inst = builder->AddBinaryOp(
|
||||
GetUintId(), SpvOpIMul, arr_stride_id, curr_idx_id);
|
||||
curr_offset_id = curr_offset_inst->result_id();
|
||||
// Get element type for next step
|
||||
curr_ty_id = curr_ty_inst->GetSingleWordInOperand(0);
|
||||
} break;
|
||||
case SpvOpTypeVector: {
|
||||
// Stride is size of component type
|
||||
uint32_t comp_ty_id = curr_ty_inst->GetSingleWordInOperand(0u);
|
||||
uint32_t vec_stride = ByteSize(comp_ty_id);
|
||||
uint32_t vec_stride_id = builder->GetUintConstantId(vec_stride);
|
||||
Instruction* curr_offset_inst = builder->AddBinaryOp(
|
||||
GetUintId(), SpvOpIMul, vec_stride_id, curr_idx_id);
|
||||
curr_offset_id = curr_offset_inst->result_id();
|
||||
// Get element type for next step
|
||||
curr_ty_id = comp_ty_id;
|
||||
} break;
|
||||
case SpvOpTypeStruct: {
|
||||
// Get buffer byte offset for the referenced member
|
||||
assert(curr_idx_inst->opcode() == SpvOpConstant &&
|
||||
"unexpected struct index");
|
||||
uint32_t member_idx = curr_idx_inst->GetSingleWordInOperand(0);
|
||||
uint32_t member_offset = 0xdeadbeef;
|
||||
bool found = !get_decoration_mgr()->WhileEachDecoration(
|
||||
curr_ty_id, SpvDecorationOffset,
|
||||
[&member_idx, &member_offset](const Instruction& deco_inst) {
|
||||
if (deco_inst.GetSingleWordInOperand(1u) != member_idx)
|
||||
return true;
|
||||
member_offset = deco_inst.GetSingleWordInOperand(3u);
|
||||
return false;
|
||||
});
|
||||
USE_ASSERT(found && "member offset not found");
|
||||
curr_offset_id = builder->GetUintConstantId(member_offset);
|
||||
// Get element type for next step
|
||||
curr_ty_id = curr_ty_inst->GetSingleWordInOperand(member_idx);
|
||||
} break;
|
||||
default: { assert(false && "unexpected non-composite type"); } break;
|
||||
}
|
||||
if (sum_id == 0)
|
||||
sum_id = curr_offset_id;
|
||||
else {
|
||||
Instruction* sum_inst =
|
||||
builder->AddBinaryOp(GetUintId(), SpvOpIAdd, sum_id, curr_offset_id);
|
||||
sum_id = sum_inst->result_id();
|
||||
}
|
||||
++ac_in_idx;
|
||||
}
|
||||
// Add in offset of last byte of referenced object
|
||||
uint32_t bsize = ByteSize(curr_ty_id);
|
||||
uint32_t last = bsize - 1;
|
||||
uint32_t last_id = builder->GetUintConstantId(last);
|
||||
Instruction* sum_inst =
|
||||
builder->AddBinaryOp(GetUintId(), SpvOpIAdd, sum_id, last_id);
|
||||
return sum_inst->result_id();
|
||||
}
|
||||
|
||||
void InstBindlessCheckPass::GenCheckCode(
|
||||
uint32_t check_id, uint32_t error_id, uint32_t length_id,
|
||||
uint32_t stage_idx, ref_analysis* ref,
|
||||
uint32_t check_id, uint32_t error_id, uint32_t offset_id,
|
||||
uint32_t length_id, uint32_t stage_idx, ref_analysis* ref,
|
||||
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
|
||||
BasicBlock* back_blk_ptr = &*new_blocks->back();
|
||||
InstructionBuilder builder(
|
||||
@@ -279,9 +433,19 @@ void InstBindlessCheckPass::GenCheckCode(
|
||||
// Gen invalid block
|
||||
new_blk_ptr.reset(new BasicBlock(std::move(invalid_label)));
|
||||
builder.SetInsertPoint(&*new_blk_ptr);
|
||||
uint32_t u_index_id = GenUintCastCode(ref->index_id, &builder);
|
||||
GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], stage_idx,
|
||||
{error_id, u_index_id, length_id}, &builder);
|
||||
uint32_t u_index_id = GenUintCastCode(ref->desc_idx_id, &builder);
|
||||
if (offset_id != 0)
|
||||
GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], stage_idx,
|
||||
{error_id, u_index_id, offset_id, length_id}, &builder);
|
||||
else if (buffer_bounds_enabled_)
|
||||
// So all error modes will use same debug stream write function
|
||||
GenDebugStreamWrite(
|
||||
uid2offset_[ref->ref_inst->unique_id()], stage_idx,
|
||||
{error_id, u_index_id, length_id, builder.GetUintConstantId(0)},
|
||||
&builder);
|
||||
else
|
||||
GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], stage_idx,
|
||||
{error_id, u_index_id, length_id}, &builder);
|
||||
// Remember last invalid block id
|
||||
uint32_t last_invalid_blk_id = new_blk_ptr->GetLabelInst()->result_id();
|
||||
// Gen zero for invalid reference
|
||||
@@ -305,7 +469,7 @@ void InstBindlessCheckPass::GenCheckCode(
|
||||
context()->KillInst(ref->ref_inst);
|
||||
}
|
||||
|
||||
void InstBindlessCheckPass::GenBoundsCheckCode(
|
||||
void InstBindlessCheckPass::GenDescIdxCheckCode(
|
||||
BasicBlock::iterator ref_inst_itr,
|
||||
UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
|
||||
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
|
||||
@@ -318,19 +482,19 @@ void InstBindlessCheckPass::GenBoundsCheckCode(
|
||||
// If index and bound both compile-time constants and index < bound,
|
||||
// return without changing
|
||||
Instruction* var_inst = get_def_use_mgr()->GetDef(ref.var_id);
|
||||
Instruction* desc_type_inst = GetDescriptorTypeInst(var_inst);
|
||||
Instruction* desc_type_inst = GetPointeeTypeInst(var_inst);
|
||||
uint32_t length_id = 0;
|
||||
if (desc_type_inst->opcode() == SpvOpTypeArray) {
|
||||
length_id =
|
||||
desc_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx);
|
||||
Instruction* index_inst = get_def_use_mgr()->GetDef(ref.index_id);
|
||||
Instruction* index_inst = get_def_use_mgr()->GetDef(ref.desc_idx_id);
|
||||
Instruction* length_inst = get_def_use_mgr()->GetDef(length_id);
|
||||
if (index_inst->opcode() == SpvOpConstant &&
|
||||
length_inst->opcode() == SpvOpConstant &&
|
||||
index_inst->GetSingleWordInOperand(kSpvConstantValueInIdx) <
|
||||
length_inst->GetSingleWordInOperand(kSpvConstantValueInIdx))
|
||||
return;
|
||||
} else if (!input_length_enabled_ ||
|
||||
} else if (!desc_idx_enabled_ ||
|
||||
desc_type_inst->opcode() != SpvOpTypeRuntimeArray) {
|
||||
return;
|
||||
}
|
||||
@@ -352,9 +516,9 @@ void InstBindlessCheckPass::GenBoundsCheckCode(
|
||||
// Generate full runtime bounds test code with true branch
|
||||
// being full reference and false branch being debug output and zero
|
||||
// for the referenced value.
|
||||
Instruction* ult_inst =
|
||||
builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, ref.index_id, length_id);
|
||||
GenCheckCode(ult_inst->result_id(), error_id, length_id, stage_idx, &ref,
|
||||
Instruction* ult_inst = builder.AddBinaryOp(GetBoolId(), SpvOpULessThan,
|
||||
ref.desc_idx_id, length_id);
|
||||
GenCheckCode(ult_inst->result_id(), error_id, 0u, length_id, stage_idx, &ref,
|
||||
new_blocks);
|
||||
// Move original block's remaining code into remainder/merge block and add
|
||||
// to new blocks
|
||||
@@ -362,13 +526,30 @@ void InstBindlessCheckPass::GenBoundsCheckCode(
|
||||
MovePostludeCode(ref_block_itr, back_blk_ptr);
|
||||
}
|
||||
|
||||
void InstBindlessCheckPass::GenInitCheckCode(
|
||||
void InstBindlessCheckPass::GenDescInitCheckCode(
|
||||
BasicBlock::iterator ref_inst_itr,
|
||||
UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
|
||||
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
|
||||
// Look for reference through descriptor. If not, return.
|
||||
ref_analysis ref;
|
||||
if (!AnalyzeDescriptorReference(&*ref_inst_itr, &ref)) return;
|
||||
// Determine if we can only do initialization check
|
||||
bool init_check = false;
|
||||
if (ref.desc_load_id != 0 || !buffer_bounds_enabled_) {
|
||||
init_check = true;
|
||||
} else {
|
||||
// For now, only do bounds check for non-aggregate types. Otherwise
|
||||
// just do descriptor initialization check.
|
||||
// TODO(greg-lunarg): Do bounds check for aggregate loads and stores
|
||||
Instruction* ref_ptr_inst = get_def_use_mgr()->GetDef(ref.ptr_id);
|
||||
Instruction* pte_type_inst = GetPointeeTypeInst(ref_ptr_inst);
|
||||
uint32_t pte_type_op = pte_type_inst->opcode();
|
||||
if (pte_type_op == SpvOpTypeArray || pte_type_op == SpvOpTypeRuntimeArray ||
|
||||
pte_type_op == SpvOpTypeStruct)
|
||||
init_check = true;
|
||||
}
|
||||
// If initialization check and not enabled, return
|
||||
if (init_check && !desc_init_enabled_) return;
|
||||
// Move original block's preceding instructions into first new block
|
||||
std::unique_ptr<BasicBlock> new_blk_ptr;
|
||||
MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
|
||||
@@ -376,19 +557,25 @@ void InstBindlessCheckPass::GenInitCheckCode(
|
||||
context(), &*new_blk_ptr,
|
||||
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
|
||||
new_blocks->push_back(std::move(new_blk_ptr));
|
||||
// Read initialization status from debug input buffer. If index id not yet
|
||||
// If initialization check, use reference value of zero.
|
||||
// Else use the index of the last byte referenced.
|
||||
uint32_t ref_id = init_check ? builder.GetUintConstantId(0u)
|
||||
: GenLastByteIdx(&ref, &builder);
|
||||
// Read initialization/bounds from debug input buffer. If index id not yet
|
||||
// set, binding is single descriptor, so set index to constant 0.
|
||||
uint32_t zero_id = builder.GetUintConstantId(0u);
|
||||
if (ref.index_id == 0) ref.index_id = zero_id;
|
||||
uint32_t init_id = GenDebugReadInit(ref.var_id, ref.index_id, &builder);
|
||||
// Generate full runtime non-zero init test code with true branch
|
||||
if (ref.desc_idx_id == 0) ref.desc_idx_id = builder.GetUintConstantId(0u);
|
||||
uint32_t init_id = GenDebugReadInit(ref.var_id, ref.desc_idx_id, &builder);
|
||||
// Generate runtime initialization/bounds test code with true branch
|
||||
// being full reference and false branch being debug output and zero
|
||||
// for the referenced value.
|
||||
Instruction* uneq_inst =
|
||||
builder.AddBinaryOp(GetBoolId(), SpvOpINotEqual, init_id, zero_id);
|
||||
uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessUninit);
|
||||
GenCheckCode(uneq_inst->result_id(), error_id, zero_id, stage_idx, &ref,
|
||||
new_blocks);
|
||||
Instruction* ult_inst =
|
||||
builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, ref_id, init_id);
|
||||
uint32_t error =
|
||||
init_check ? kInstErrorBindlessUninit : kInstErrorBindlessBuffOOB;
|
||||
uint32_t error_id = builder.GetUintConstantId(error);
|
||||
GenCheckCode(ult_inst->result_id(), error_id, init_check ? 0 : ref_id,
|
||||
init_check ? builder.GetUintConstantId(0u) : init_id, stage_idx,
|
||||
&ref, new_blocks);
|
||||
// Move original block's remaining code into remainder/merge block and add
|
||||
// to new blocks
|
||||
BasicBlock* back_blk_ptr = &*new_blocks->back();
|
||||
@@ -400,7 +587,7 @@ void InstBindlessCheckPass::InitializeInstBindlessCheck() {
|
||||
InitializeInstrument();
|
||||
// If runtime array length support enabled, create variable mappings. Length
|
||||
// support is always enabled if descriptor init check is enabled.
|
||||
if (input_length_enabled_)
|
||||
if (desc_idx_enabled_ || buffer_bounds_enabled_)
|
||||
for (auto& anno : get_module()->annotations())
|
||||
if (anno.opcode() == SpvOpDecorate) {
|
||||
if (anno.GetSingleWordInOperand(1u) == SpvDecorationDescriptorSet)
|
||||
@@ -418,19 +605,19 @@ Pass::Status InstBindlessCheckPass::ProcessImpl() {
|
||||
[this](BasicBlock::iterator ref_inst_itr,
|
||||
UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
|
||||
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
|
||||
return GenBoundsCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
|
||||
new_blocks);
|
||||
return GenDescIdxCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
|
||||
new_blocks);
|
||||
};
|
||||
bool modified = InstProcessEntryPointCallTree(pfn);
|
||||
if (input_init_enabled_) {
|
||||
if (desc_init_enabled_ || buffer_bounds_enabled_) {
|
||||
// Perform descriptor initialization check on each entry point function in
|
||||
// module
|
||||
pfn = [this](BasicBlock::iterator ref_inst_itr,
|
||||
UptrVectorIterator<BasicBlock> ref_block_itr,
|
||||
uint32_t stage_idx,
|
||||
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
|
||||
return GenInitCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
|
||||
new_blocks);
|
||||
return GenDescInitCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
|
||||
new_blocks);
|
||||
};
|
||||
modified |= InstProcessEntryPointCallTree(pfn);
|
||||
}
|
||||
|
||||
@@ -28,12 +28,24 @@ namespace opt {
|
||||
// external design may change as the layer evolves.
|
||||
class InstBindlessCheckPass : public InstrumentPass {
|
||||
public:
|
||||
// Preferred Interface
|
||||
// Old interface to support testing pre-buffer-overrun capability
|
||||
InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id,
|
||||
bool input_length_enable, bool input_init_enable)
|
||||
: InstrumentPass(desc_set, shader_id, kInstValidationIdBindless),
|
||||
input_length_enabled_(input_length_enable),
|
||||
input_init_enabled_(input_init_enable) {}
|
||||
bool desc_idx_enable, bool desc_init_enable)
|
||||
: InstrumentPass(desc_set, shader_id, kInstValidationIdBindless, false),
|
||||
desc_idx_enabled_(desc_idx_enable),
|
||||
desc_init_enabled_(desc_init_enable),
|
||||
buffer_bounds_enabled_(false) {}
|
||||
|
||||
// New interface supporting buffer overrun checking
|
||||
InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id,
|
||||
bool desc_idx_enable, bool desc_init_enable,
|
||||
bool buffer_bounds_enable)
|
||||
: InstrumentPass(
|
||||
desc_set, shader_id, kInstValidationIdBindless,
|
||||
desc_idx_enable || desc_init_enable || buffer_bounds_enable),
|
||||
desc_idx_enabled_(desc_idx_enable),
|
||||
desc_init_enabled_(desc_init_enable),
|
||||
buffer_bounds_enabled_(buffer_bounds_enable) {}
|
||||
|
||||
~InstBindlessCheckPass() override = default;
|
||||
|
||||
@@ -46,13 +58,11 @@ class InstBindlessCheckPass : public InstrumentPass {
|
||||
// These functions do bindless checking instrumentation on a single
|
||||
// instruction which references through a descriptor (ie references into an
|
||||
// image or buffer). Refer to Vulkan API for further information on
|
||||
// descriptors. GenBoundsCheckCode checks that an index into a descriptor
|
||||
// array (array of images or buffers) is in-bounds. GenInitCheckCode
|
||||
// descriptors. GenDescIdxCheckCode checks that an index into a descriptor
|
||||
// array (array of images or buffers) is in-bounds. GenDescInitCheckCode
|
||||
// checks that the referenced descriptor has been initialized, if the
|
||||
// SPV_EXT_descriptor_indexing extension is enabled.
|
||||
//
|
||||
// TODO(greg-lunarg): Add support for buffers. Currently only does
|
||||
// checking of references of images.
|
||||
// SPV_EXT_descriptor_indexing extension is enabled, and initialized large
|
||||
// enough to handle the reference, if RobustBufferAccess is disabled.
|
||||
//
|
||||
// The functions are designed to be passed to
|
||||
// InstrumentPass::InstProcessEntryPointCallTree(), which applies the
|
||||
@@ -89,15 +99,15 @@ class InstBindlessCheckPass : public InstrumentPass {
|
||||
//
|
||||
// The Descriptor Array Size is the size of the descriptor array which was
|
||||
// indexed.
|
||||
void GenBoundsCheckCode(BasicBlock::iterator ref_inst_itr,
|
||||
UptrVectorIterator<BasicBlock> ref_block_itr,
|
||||
uint32_t stage_idx,
|
||||
std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
|
||||
void GenDescIdxCheckCode(
|
||||
BasicBlock::iterator ref_inst_itr,
|
||||
UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
|
||||
std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
|
||||
|
||||
void GenInitCheckCode(BasicBlock::iterator ref_inst_itr,
|
||||
UptrVectorIterator<BasicBlock> ref_block_itr,
|
||||
uint32_t stage_idx,
|
||||
std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
|
||||
void GenDescInitCheckCode(
|
||||
BasicBlock::iterator ref_inst_itr,
|
||||
UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
|
||||
std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
|
||||
|
||||
// Generate instructions into |builder| to read length of runtime descriptor
|
||||
// array |var_id| from debug input buffer and return id of value.
|
||||
@@ -118,10 +128,20 @@ class InstBindlessCheckPass : public InstrumentPass {
|
||||
uint32_t load_id;
|
||||
uint32_t ptr_id;
|
||||
uint32_t var_id;
|
||||
uint32_t index_id;
|
||||
uint32_t desc_idx_id;
|
||||
Instruction* ref_inst;
|
||||
} ref_analysis;
|
||||
|
||||
// Return size of type |ty_id| in bytes.
|
||||
uint32_t ByteSize(uint32_t ty_id);
|
||||
|
||||
// Return stride of type |ty_id| with decoration |stride_deco|. Return 0
|
||||
// if not found
|
||||
uint32_t FindStride(uint32_t ty_id, uint32_t stride_deco);
|
||||
|
||||
// Generate index of last byte referenced by buffer reference |ref|
|
||||
uint32_t GenLastByteIdx(ref_analysis* ref, InstructionBuilder* builder);
|
||||
|
||||
// Clone original original reference encapsulated by |ref| into |builder|.
|
||||
// This may generate more than one instruction if neccessary.
|
||||
uint32_t CloneOriginalReference(ref_analysis* ref,
|
||||
@@ -131,8 +151,8 @@ class InstBindlessCheckPass : public InstrumentPass {
|
||||
// references through. Else return 0.
|
||||
uint32_t GetImageId(Instruction* inst);
|
||||
|
||||
// Get descriptor type inst of variable |var_inst|.
|
||||
Instruction* GetDescriptorTypeInst(Instruction* var_inst);
|
||||
// Get pointee type inst of pointer value |ptr_inst|.
|
||||
Instruction* GetPointeeTypeInst(Instruction* ptr_inst);
|
||||
|
||||
// Analyze descriptor reference |ref_inst| and save components into |ref|.
|
||||
// Return true if |ref_inst| is a descriptor reference, false otherwise.
|
||||
@@ -145,22 +165,25 @@ class InstBindlessCheckPass : public InstrumentPass {
|
||||
// writes debug error output utilizing |ref|, |error_id|, |length_id| and
|
||||
// |stage_idx|. Generate merge block for valid and invalid branches. Kill
|
||||
// original reference.
|
||||
void GenCheckCode(uint32_t check_id, uint32_t error_id, uint32_t length_id,
|
||||
uint32_t stage_idx, ref_analysis* ref,
|
||||
void GenCheckCode(uint32_t check_id, uint32_t error_id, uint32_t offset_id,
|
||||
uint32_t length_id, uint32_t stage_idx, ref_analysis* ref,
|
||||
std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
|
||||
|
||||
// Initialize state for instrumenting bindless checking
|
||||
void InitializeInstBindlessCheck();
|
||||
|
||||
// Apply GenBoundsCheckCode to every instruction in module. Then apply
|
||||
// GenInitCheckCode to every instruction in module.
|
||||
// Apply GenDescIdxCheckCode to every instruction in module. Then apply
|
||||
// GenDescInitCheckCode to every instruction in module.
|
||||
Pass::Status ProcessImpl();
|
||||
|
||||
// Enable instrumentation of runtime array length checking
|
||||
bool input_length_enabled_;
|
||||
bool desc_idx_enabled_;
|
||||
|
||||
// Enable instrumentation of descriptor initialization checking
|
||||
bool input_init_enabled_;
|
||||
bool desc_init_enabled_;
|
||||
|
||||
// Enable instrumentation of buffer overrun checking
|
||||
bool buffer_bounds_enabled_;
|
||||
|
||||
// Mapping from variable to descriptor set
|
||||
std::unordered_map<uint32_t, uint32_t> var2desc_set_;
|
||||
|
||||
@@ -281,14 +281,42 @@ void InstrumentPass::GenDebugStreamWrite(
|
||||
(void)builder->AddNaryOp(GetVoidId(), SpvOpFunctionCall, args);
|
||||
}
|
||||
|
||||
bool InstrumentPass::AllConstant(const std::vector<uint32_t>& ids) {
|
||||
for (auto& id : ids) {
|
||||
Instruction* id_inst = context()->get_def_use_mgr()->GetDef(id);
|
||||
if (!spvOpcodeIsConstant(id_inst->opcode())) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t InstrumentPass::GenDebugDirectRead(
|
||||
const std::vector<uint32_t>& offset_ids, InstructionBuilder* builder) {
|
||||
const std::vector<uint32_t>& offset_ids, InstructionBuilder* ref_builder) {
|
||||
// Call debug input function. Pass func_idx and offset ids as args.
|
||||
uint32_t off_id_cnt = static_cast<uint32_t>(offset_ids.size());
|
||||
uint32_t input_func_id = GetDirectReadFunctionId(off_id_cnt);
|
||||
std::vector<uint32_t> args = {input_func_id};
|
||||
(void)args.insert(args.end(), offset_ids.begin(), offset_ids.end());
|
||||
return builder->AddNaryOp(GetUintId(), SpvOpFunctionCall, args)->result_id();
|
||||
// If optimizing direct reads and the call has already been generated,
|
||||
// use its result
|
||||
if (opt_direct_reads_) {
|
||||
uint32_t res_id = call2id_[args];
|
||||
if (res_id != 0) return res_id;
|
||||
}
|
||||
// If the offsets are all constants, the call can be moved to the first block
|
||||
// of the function where its result can be reused. One example where this is
|
||||
// profitable is for uniform buffer references, of which there are often many.
|
||||
InstructionBuilder builder(ref_builder->GetContext(),
|
||||
&*ref_builder->GetInsertPoint(),
|
||||
ref_builder->GetPreservedAnalysis());
|
||||
bool insert_in_first_block = opt_direct_reads_ && AllConstant(offset_ids);
|
||||
if (insert_in_first_block) {
|
||||
Instruction* insert_before = &*curr_func_->begin()->tail();
|
||||
builder.SetInsertPoint(insert_before);
|
||||
}
|
||||
uint32_t res_id =
|
||||
builder.AddNaryOp(GetUintId(), SpvOpFunctionCall, args)->result_id();
|
||||
if (insert_in_first_block) call2id_[args] = res_id;
|
||||
return res_id;
|
||||
}
|
||||
|
||||
bool InstrumentPass::IsSameBlockOp(const Instruction* inst) const {
|
||||
@@ -819,21 +847,52 @@ uint32_t InstrumentPass::GetDirectReadFunctionId(uint32_t param_cnt) {
|
||||
return func_id;
|
||||
}
|
||||
|
||||
void InstrumentPass::SplitBlock(
|
||||
BasicBlock::iterator inst_itr, UptrVectorIterator<BasicBlock> block_itr,
|
||||
std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
|
||||
// Make sure def/use analysis is done before we start moving instructions
|
||||
// out of function
|
||||
(void)get_def_use_mgr();
|
||||
// Move original block's preceding instructions into first new block
|
||||
std::unique_ptr<BasicBlock> first_blk_ptr;
|
||||
MovePreludeCode(inst_itr, block_itr, &first_blk_ptr);
|
||||
InstructionBuilder builder(
|
||||
context(), &*first_blk_ptr,
|
||||
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
|
||||
uint32_t split_blk_id = TakeNextId();
|
||||
std::unique_ptr<Instruction> split_label(NewLabel(split_blk_id));
|
||||
(void)builder.AddBranch(split_blk_id);
|
||||
new_blocks->push_back(std::move(first_blk_ptr));
|
||||
// Move remaining instructions into split block and add to new blocks
|
||||
std::unique_ptr<BasicBlock> split_blk_ptr(
|
||||
new BasicBlock(std::move(split_label)));
|
||||
MovePostludeCode(block_itr, &*split_blk_ptr);
|
||||
new_blocks->push_back(std::move(split_blk_ptr));
|
||||
}
|
||||
|
||||
bool InstrumentPass::InstrumentFunction(Function* func, uint32_t stage_idx,
|
||||
InstProcessFunction& pfn) {
|
||||
curr_func_ = func;
|
||||
call2id_.clear();
|
||||
bool first_block_split = false;
|
||||
bool modified = false;
|
||||
// Compute function index
|
||||
uint32_t function_idx = 0;
|
||||
for (auto fii = get_module()->begin(); fii != get_module()->end(); ++fii) {
|
||||
if (&*fii == func) break;
|
||||
++function_idx;
|
||||
}
|
||||
std::vector<std::unique_ptr<BasicBlock>> new_blks;
|
||||
// Apply instrumentation function to each instruction.
|
||||
// Using block iterators here because of block erasures and insertions.
|
||||
std::vector<std::unique_ptr<BasicBlock>> new_blks;
|
||||
for (auto bi = func->begin(); bi != func->end(); ++bi) {
|
||||
for (auto ii = bi->begin(); ii != bi->end();) {
|
||||
// Generate instrumentation if warranted
|
||||
pfn(ii, bi, stage_idx, &new_blks);
|
||||
// Split all executable instructions out of first block into a following
|
||||
// block. This will allow function calls to be inserted into the first
|
||||
// block without interfering with the instrumentation algorithm.
|
||||
if (opt_direct_reads_ && !first_block_split) {
|
||||
if (ii->opcode() != SpvOpVariable) {
|
||||
SplitBlock(ii, bi, &new_blks);
|
||||
first_block_split = true;
|
||||
}
|
||||
} else {
|
||||
pfn(ii, bi, stage_idx, &new_blks);
|
||||
}
|
||||
// If no new code, continue
|
||||
if (new_blks.size() == 0) {
|
||||
++ii;
|
||||
continue;
|
||||
|
||||
@@ -82,12 +82,15 @@ class InstrumentPass : public Pass {
|
||||
protected:
|
||||
// Create instrumentation pass for |validation_id| which utilizes descriptor
|
||||
// set |desc_set| for debug input and output buffers and writes |shader_id|
|
||||
// into debug output records.
|
||||
InstrumentPass(uint32_t desc_set, uint32_t shader_id, uint32_t validation_id)
|
||||
// into debug output records. |opt_direct_reads| indicates that the pass
|
||||
// will see direct input buffer reads and should prepare to optimize them.
|
||||
InstrumentPass(uint32_t desc_set, uint32_t shader_id, uint32_t validation_id,
|
||||
bool opt_direct_reads = false)
|
||||
: Pass(),
|
||||
desc_set_(desc_set),
|
||||
shader_id_(shader_id),
|
||||
validation_id_(validation_id) {}
|
||||
validation_id_(validation_id),
|
||||
opt_direct_reads_(opt_direct_reads) {}
|
||||
|
||||
// Initialize state for instrumentation of module.
|
||||
void InitializeInstrument();
|
||||
@@ -196,6 +199,9 @@ class InstrumentPass : public Pass {
|
||||
const std::vector<uint32_t>& validation_ids,
|
||||
InstructionBuilder* builder);
|
||||
|
||||
// Return true if all instructions in |ids| are constants or spec constants.
|
||||
bool AllConstant(const std::vector<uint32_t>& ids);
|
||||
|
||||
// Generate in |builder| instructions to read the unsigned integer from the
|
||||
// input buffer specified by the offsets in |offset_ids|. Given offsets
|
||||
// o0, o1, ... oN, and input buffer ibuf, return the id for the value:
|
||||
@@ -284,6 +290,12 @@ class InstrumentPass : public Pass {
|
||||
// if it doesn't exist.
|
||||
uint32_t GetDirectReadFunctionId(uint32_t param_cnt);
|
||||
|
||||
// Split block |block_itr| into two new blocks where the second block
|
||||
// contains |inst_itr| and place in |new_blocks|.
|
||||
void SplitBlock(BasicBlock::iterator inst_itr,
|
||||
UptrVectorIterator<BasicBlock> block_itr,
|
||||
std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
|
||||
|
||||
// Apply instrumentation function |pfn| to every instruction in |func|.
|
||||
// If code is generated for an instruction, replace the instruction's
|
||||
// block with the new blocks that are generated. Continue processing at the
|
||||
@@ -428,6 +440,29 @@ class InstrumentPass : public Pass {
|
||||
|
||||
// Post-instrumentation same-block op ids
|
||||
std::unordered_map<uint32_t, uint32_t> same_block_post_;
|
||||
|
||||
// Map function calls to result id. Clear for every function.
|
||||
// This is for debug input reads with constant arguments that
|
||||
// have been generated into the first block of the function.
|
||||
// This mechanism is used to avoid multiple identical debug
|
||||
// input buffer reads.
|
||||
struct vector_hash_ {
|
||||
std::size_t operator()(const std::vector<uint32_t>& v) const {
|
||||
std::size_t hash = v.size();
|
||||
for (auto& u : v) {
|
||||
hash ^= u + 0x9e3779b9 + (hash << 11) + (hash >> 21);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
std::unordered_map<std::vector<uint32_t>, uint32_t, vector_hash_> call2id_;
|
||||
|
||||
// Function currently being instrumented
|
||||
Function* curr_func_;
|
||||
|
||||
// Optimize direct debug input buffer reads. Specifically, move all such
|
||||
// reads with constant args to first block and reuse them.
|
||||
bool opt_direct_reads_;
|
||||
};
|
||||
|
||||
} // namespace opt
|
||||
|
||||
@@ -369,6 +369,7 @@ void LocalAccessChainConvertPass::InitExtensions() {
|
||||
"SPV_AMD_gpu_shader_half_float",
|
||||
"SPV_KHR_shader_draw_parameters",
|
||||
"SPV_KHR_subgroup_vote",
|
||||
"SPV_KHR_8bit_storage",
|
||||
"SPV_KHR_16bit_storage",
|
||||
"SPV_KHR_device_group",
|
||||
"SPV_KHR_multiview",
|
||||
|
||||
@@ -232,6 +232,7 @@ void LocalSingleBlockLoadStoreElimPass::InitExtensions() {
|
||||
"SPV_AMD_gpu_shader_half_float",
|
||||
"SPV_KHR_shader_draw_parameters",
|
||||
"SPV_KHR_subgroup_vote",
|
||||
"SPV_KHR_8bit_storage",
|
||||
"SPV_KHR_16bit_storage",
|
||||
"SPV_KHR_device_group",
|
||||
"SPV_KHR_multiview",
|
||||
|
||||
@@ -88,6 +88,7 @@ void LocalSingleStoreElimPass::InitExtensionAllowList() {
|
||||
"SPV_AMD_gpu_shader_half_float",
|
||||
"SPV_KHR_shader_draw_parameters",
|
||||
"SPV_KHR_subgroup_vote",
|
||||
"SPV_KHR_8bit_storage",
|
||||
"SPV_KHR_16bit_storage",
|
||||
"SPV_KHR_device_group",
|
||||
"SPV_KHR_multiview",
|
||||
|
||||
@@ -1063,7 +1063,7 @@ LoopPeelingPass::LoopPeelingInfo::HandleInequality(CmpOperator cmp_op,
|
||||
}
|
||||
|
||||
uint32_t cast_iteration = 0;
|
||||
// coherence check: can we fit |iteration| in a uint32_t ?
|
||||
// Integrity check: can we fit |iteration| in a uint32_t ?
|
||||
if (static_cast<uint64_t>(iteration) < std::numeric_limits<uint32_t>::max()) {
|
||||
cast_iteration = static_cast<uint32_t>(iteration);
|
||||
}
|
||||
|
||||
16
3rdparty/spirv-tools/source/opt/optimizer.cpp
vendored
16
3rdparty/spirv-tools/source/opt/optimizer.cpp
vendored
@@ -427,6 +427,12 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) {
|
||||
RegisterPass(CreateDeadBranchElimPass());
|
||||
RegisterPass(CreateBlockMergePass());
|
||||
RegisterPass(CreateAggressiveDCEPass());
|
||||
} else if (pass_name == "inst-buff-oob-check") {
|
||||
RegisterPass(CreateInstBindlessCheckPass(7, 23, false, false, true));
|
||||
RegisterPass(CreateSimplificationPass());
|
||||
RegisterPass(CreateDeadBranchElimPass());
|
||||
RegisterPass(CreateBlockMergePass());
|
||||
RegisterPass(CreateAggressiveDCEPass());
|
||||
} else if (pass_name == "inst-buff-addr-check") {
|
||||
RegisterPass(CreateInstBuffAddrCheckPass(7, 23));
|
||||
RegisterPass(CreateAggressiveDCEPass());
|
||||
@@ -579,8 +585,8 @@ bool Optimizer::Run(const uint32_t* original_binary,
|
||||
|
||||
#ifndef NDEBUG
|
||||
// We do not keep the result id of DebugScope in struct DebugScope.
|
||||
// Instead, we assign random ids for them, which results in coherence
|
||||
// check failures. We want to skip the coherence check when the module
|
||||
// Instead, we assign random ids for them, which results in integrity
|
||||
// check failures. We want to skip the integrity check when the module
|
||||
// contains DebugScope instructions.
|
||||
if (status == opt::Pass::Status::SuccessWithoutChange &&
|
||||
!context->module()->ContainsDebugScope()) {
|
||||
@@ -894,10 +900,12 @@ Optimizer::PassToken CreateUpgradeMemoryModelPass() {
|
||||
Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
|
||||
uint32_t shader_id,
|
||||
bool input_length_enable,
|
||||
bool input_init_enable) {
|
||||
bool input_init_enable,
|
||||
bool input_buff_oob_enable) {
|
||||
return MakeUnique<Optimizer::PassToken::Impl>(
|
||||
MakeUnique<opt::InstBindlessCheckPass>(
|
||||
desc_set, shader_id, input_length_enable, input_init_enable));
|
||||
desc_set, shader_id, input_length_enable, input_init_enable,
|
||||
input_buff_oob_enable));
|
||||
}
|
||||
|
||||
Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set,
|
||||
|
||||
@@ -128,6 +128,19 @@ uint32_t StructuredCFGAnalysis::MergeBlock(uint32_t bb_id) {
|
||||
return merge_inst->GetSingleWordInOperand(kMergeNodeIndex);
|
||||
}
|
||||
|
||||
uint32_t StructuredCFGAnalysis::NestingDepth(uint32_t bb_id) {
|
||||
uint32_t result = 0;
|
||||
|
||||
// Find the merge block of the current merge construct as long as the block is
|
||||
// inside a merge construct, exiting one for each iteration.
|
||||
for (uint32_t merge_block_id = MergeBlock(bb_id); merge_block_id != 0;
|
||||
merge_block_id = MergeBlock(merge_block_id)) {
|
||||
result++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t StructuredCFGAnalysis::LoopMergeBlock(uint32_t bb_id) {
|
||||
uint32_t header_id = ContainingLoop(bb_id);
|
||||
if (header_id == 0) {
|
||||
|
||||
@@ -53,6 +53,11 @@ class StructuredCFGAnalysis {
|
||||
// merge construct.
|
||||
uint32_t MergeBlock(uint32_t bb_id);
|
||||
|
||||
// Returns the nesting depth of the given block, i.e. the number of merge
|
||||
// constructs containing it. Headers and merge blocks are not considered part
|
||||
// of the corresponding merge constructs.
|
||||
uint32_t NestingDepth(uint32_t block_id);
|
||||
|
||||
// Returns the id of the header of the innermost loop construct
|
||||
// that contains |bb_id|. Return |0| if |bb_id| is not contained in any loop
|
||||
// construct.
|
||||
|
||||
@@ -50,6 +50,7 @@ set(SPIRV_TOOLS_REDUCE_SOURCES
|
||||
operand_to_dominating_id_reduction_opportunity_finder.cpp
|
||||
reducer.cpp
|
||||
reduction_opportunity.cpp
|
||||
reduction_opportunity_finder.cpp
|
||||
reduction_pass.cpp
|
||||
reduction_util.cpp
|
||||
remove_block_reduction_opportunity.cpp
|
||||
|
||||
@@ -20,12 +20,10 @@
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
using opt::IRContext;
|
||||
using opt::Instruction;
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>>
|
||||
ConditionalBranchToSimpleConditionalBranchOpportunityFinder::
|
||||
GetAvailableOpportunities(IRContext* context) const {
|
||||
GetAvailableOpportunities(opt::IRContext* context,
|
||||
uint32_t target_function) const {
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> result;
|
||||
|
||||
// Find the opportunities for redirecting all false targets before the
|
||||
@@ -34,12 +32,12 @@ ConditionalBranchToSimpleConditionalBranchOpportunityFinder::
|
||||
// reducer is improved by avoiding contiguous opportunities that disable one
|
||||
// another.
|
||||
for (bool redirect_to_true : {true, false}) {
|
||||
// Consider every function.
|
||||
for (auto& function : *context->module()) {
|
||||
// Consider every relevant function.
|
||||
for (auto* function : GetTargetFunctions(context, target_function)) {
|
||||
// Consider every block in the function.
|
||||
for (auto& block : function) {
|
||||
for (auto& block : *function) {
|
||||
// The terminator must be SpvOpBranchConditional.
|
||||
Instruction* terminator = block.terminator();
|
||||
opt::Instruction* terminator = block.terminator();
|
||||
if (terminator->opcode() != SpvOpBranchConditional) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class ConditionalBranchToSimpleConditionalBranchOpportunityFinder
|
||||
: public ReductionOpportunityFinder {
|
||||
public:
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
|
||||
opt::IRContext* context) const override;
|
||||
opt::IRContext* context, uint32_t target_function) const override;
|
||||
|
||||
std::string GetName() const override;
|
||||
};
|
||||
|
||||
@@ -19,13 +19,10 @@
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
using opt::IRContext;
|
||||
using opt::Instruction;
|
||||
|
||||
ConditionalBranchToSimpleConditionalBranchReductionOpportunity::
|
||||
ConditionalBranchToSimpleConditionalBranchReductionOpportunity(
|
||||
IRContext* context, Instruction* conditional_branch_instruction,
|
||||
bool redirect_to_true)
|
||||
opt::IRContext* context,
|
||||
opt::Instruction* conditional_branch_instruction, bool redirect_to_true)
|
||||
: context_(context),
|
||||
conditional_branch_instruction_(conditional_branch_instruction),
|
||||
redirect_to_true_(redirect_to_true) {}
|
||||
@@ -63,7 +60,8 @@ void ConditionalBranchToSimpleConditionalBranchReductionOpportunity::Apply() {
|
||||
context_->cfg()->block(old_successor_block_id));
|
||||
|
||||
// We have changed the CFG.
|
||||
context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
|
||||
context_->InvalidateAnalysesExceptFor(
|
||||
opt::IRContext::Analysis::kAnalysisNone);
|
||||
}
|
||||
|
||||
} // namespace reduce
|
||||
|
||||
@@ -20,12 +20,8 @@
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
using opt::BasicBlock;
|
||||
using opt::Function;
|
||||
using opt::IRContext;
|
||||
|
||||
MergeBlocksReductionOpportunity::MergeBlocksReductionOpportunity(
|
||||
IRContext* context, Function* function, BasicBlock* block) {
|
||||
opt::IRContext* context, opt::Function* function, opt::BasicBlock* block) {
|
||||
// Precondition: the terminator has to be OpBranch.
|
||||
assert(block->terminator()->opcode() == SpvOpBranch);
|
||||
context_ = context;
|
||||
@@ -49,7 +45,8 @@ bool MergeBlocksReductionOpportunity::PreconditionHolds() {
|
||||
"For a successor to be merged into its predecessor, exactly one "
|
||||
"predecessor must be present.");
|
||||
const uint32_t predecessor_id = predecessors[0];
|
||||
BasicBlock* predecessor_block = context_->get_instr_block(predecessor_id);
|
||||
opt::BasicBlock* predecessor_block =
|
||||
context_->get_instr_block(predecessor_id);
|
||||
return opt::blockmergeutil::CanMergeWithSuccessor(context_,
|
||||
predecessor_block);
|
||||
}
|
||||
@@ -70,7 +67,8 @@ void MergeBlocksReductionOpportunity::Apply() {
|
||||
if (bi->id() == predecessor_id) {
|
||||
opt::blockmergeutil::MergeWithSuccessor(context_, function_, bi);
|
||||
// Block merging changes the control flow graph, so invalidate it.
|
||||
context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
|
||||
context_->InvalidateAnalysesExceptFor(
|
||||
opt::IRContext::Analysis::kAnalysisNone);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,25 +19,23 @@
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
using opt::IRContext;
|
||||
|
||||
std::string MergeBlocksReductionOpportunityFinder::GetName() const {
|
||||
return "MergeBlocksReductionOpportunityFinder";
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>>
|
||||
MergeBlocksReductionOpportunityFinder::GetAvailableOpportunities(
|
||||
IRContext* context) const {
|
||||
opt::IRContext* context, uint32_t target_function) const {
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> result;
|
||||
|
||||
// Consider every block in every function.
|
||||
for (auto& function : *context->module()) {
|
||||
for (auto& block : function) {
|
||||
for (auto* function : GetTargetFunctions(context, target_function)) {
|
||||
for (auto& block : *function) {
|
||||
// See whether it is possible to merge this block with its successor.
|
||||
if (opt::blockmergeutil::CanMergeWithSuccessor(context, &block)) {
|
||||
// It is, so record an opportunity to do this.
|
||||
result.push_back(spvtools::MakeUnique<MergeBlocksReductionOpportunity>(
|
||||
context, &function, &block));
|
||||
context, function, &block));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class MergeBlocksReductionOpportunityFinder
|
||||
std::string GetName() const final;
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
|
||||
opt::IRContext* context) const final;
|
||||
opt::IRContext* context, uint32_t target_function) const final;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
@@ -20,11 +20,9 @@
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
using opt::IRContext;
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>>
|
||||
OperandToConstReductionOpportunityFinder::GetAvailableOpportunities(
|
||||
IRContext* context) const {
|
||||
opt::IRContext* context, uint32_t target_function) const {
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> result;
|
||||
assert(result.empty());
|
||||
|
||||
@@ -37,8 +35,8 @@ OperandToConstReductionOpportunityFinder::GetAvailableOpportunities(
|
||||
// contiguous blocks of opportunities early on, and we want to avoid having a
|
||||
// large block of incompatible opportunities if possible.
|
||||
for (const auto& constant : context->GetConstants()) {
|
||||
for (auto& function : *context->module()) {
|
||||
for (auto& block : function) {
|
||||
for (auto* function : GetTargetFunctions(context, target_function)) {
|
||||
for (auto& block : *function) {
|
||||
for (auto& inst : block) {
|
||||
// We iterate through the operands using an explicit index (rather
|
||||
// than using a lambda) so that we use said index in the construction
|
||||
|
||||
@@ -33,7 +33,7 @@ class OperandToConstReductionOpportunityFinder
|
||||
std::string GetName() const final;
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
|
||||
opt::IRContext* context) const final;
|
||||
opt::IRContext* context, uint32_t target_function) const final;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
@@ -20,13 +20,9 @@
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
using opt::Function;
|
||||
using opt::IRContext;
|
||||
using opt::Instruction;
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>>
|
||||
OperandToDominatingIdReductionOpportunityFinder::GetAvailableOpportunities(
|
||||
IRContext* context) const {
|
||||
opt::IRContext* context, uint32_t target_function) const {
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> result;
|
||||
|
||||
// Go through every instruction in every block, considering it as a potential
|
||||
@@ -42,15 +38,15 @@ OperandToDominatingIdReductionOpportunityFinder::GetAvailableOpportunities(
|
||||
// to prioritise replacing e with its smallest sub-expressions; generalising
|
||||
// this idea to dominating ids this roughly corresponds to more distant
|
||||
// dominators.
|
||||
for (auto& function : *context->module()) {
|
||||
for (auto dominating_block = function.begin();
|
||||
dominating_block != function.end(); ++dominating_block) {
|
||||
for (auto* function : GetTargetFunctions(context, target_function)) {
|
||||
for (auto dominating_block = function->begin();
|
||||
dominating_block != function->end(); ++dominating_block) {
|
||||
for (auto& dominating_inst : *dominating_block) {
|
||||
if (dominating_inst.HasResultId() && dominating_inst.type_id()) {
|
||||
// Consider replacing any operand with matching type in a dominated
|
||||
// instruction with the id generated by this instruction.
|
||||
GetOpportunitiesForDominatingInst(
|
||||
&result, &dominating_inst, dominating_block, &function, context);
|
||||
&result, &dominating_inst, dominating_block, function, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,9 +57,9 @@ OperandToDominatingIdReductionOpportunityFinder::GetAvailableOpportunities(
|
||||
void OperandToDominatingIdReductionOpportunityFinder::
|
||||
GetOpportunitiesForDominatingInst(
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
|
||||
Instruction* candidate_dominator,
|
||||
Function::iterator candidate_dominator_block, Function* function,
|
||||
IRContext* context) const {
|
||||
opt::Instruction* candidate_dominator,
|
||||
opt::Function::iterator candidate_dominator_block,
|
||||
opt::Function* function, opt::IRContext* context) const {
|
||||
assert(candidate_dominator->HasResultId());
|
||||
assert(candidate_dominator->type_id());
|
||||
auto dominator_analysis = context->GetDominatorAnalysis(function);
|
||||
@@ -91,9 +87,8 @@ void OperandToDominatingIdReductionOpportunityFinder::
|
||||
// constant. It is thus not relevant to this pass.
|
||||
continue;
|
||||
}
|
||||
// Coherence check: we should not get here if the argument is a
|
||||
// constant.
|
||||
assert(!context->get_constant_mgr()->GetConstantFromInst(def));
|
||||
assert(!context->get_constant_mgr()->GetConstantFromInst(def) &&
|
||||
"We should not get here if the argument is a constant.");
|
||||
if (def->type_id() != candidate_dominator->type_id()) {
|
||||
// The types need to match.
|
||||
continue;
|
||||
|
||||
@@ -40,7 +40,7 @@ class OperandToDominatingIdReductionOpportunityFinder
|
||||
std::string GetName() const final;
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
|
||||
opt::IRContext* context) const final;
|
||||
opt::IRContext* context, uint32_t target_function) const final;
|
||||
|
||||
private:
|
||||
void GetOpportunitiesForDominatingInst(
|
||||
|
||||
@@ -20,15 +20,13 @@
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
using opt::IRContext;
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>>
|
||||
OperandToUndefReductionOpportunityFinder::GetAvailableOpportunities(
|
||||
IRContext* context) const {
|
||||
opt::IRContext* context, uint32_t target_function) const {
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> result;
|
||||
|
||||
for (auto& function : *context->module()) {
|
||||
for (auto& block : function) {
|
||||
for (auto* function : GetTargetFunctions(context, target_function)) {
|
||||
for (auto& block : *function) {
|
||||
for (auto& inst : block) {
|
||||
// Skip instructions that result in a pointer type.
|
||||
auto type_id = inst.type_id();
|
||||
|
||||
@@ -32,7 +32,7 @@ class OperandToUndefReductionOpportunityFinder
|
||||
std::string GetName() const final;
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
|
||||
opt::IRContext* context) const final;
|
||||
opt::IRContext* context, uint32_t target_function) const final;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
@@ -183,7 +183,8 @@ Reducer::ReductionResultStatus Reducer::RunPasses(
|
||||
consumer_(SPV_MSG_INFO, nullptr, {},
|
||||
("Trying pass " + pass->GetName() + ".").c_str());
|
||||
do {
|
||||
auto maybe_result = pass->TryApplyReduction(*current_binary);
|
||||
auto maybe_result =
|
||||
pass->TryApplyReduction(*current_binary, options->target_function);
|
||||
if (maybe_result.empty()) {
|
||||
// For this round, the pass has no more opportunities (chunks) to
|
||||
// apply, so move on to the next pass.
|
||||
|
||||
34
3rdparty/spirv-tools/source/reduce/reduction_opportunity_finder.cpp
vendored
Normal file
34
3rdparty/spirv-tools/source/reduce/reduction_opportunity_finder.cpp
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "reduction_opportunity_finder.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
std::vector<opt::Function*> ReductionOpportunityFinder::GetTargetFunctions(
|
||||
opt::IRContext* ir_context, uint32_t target_function) {
|
||||
std::vector<opt::Function*> result;
|
||||
for (auto& function : *ir_context->module()) {
|
||||
if (!target_function || function.result_id() == target_function) {
|
||||
result.push_back(&function);
|
||||
}
|
||||
}
|
||||
assert((!target_function || !result.empty()) &&
|
||||
"Requested target function must exist.");
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace reduce
|
||||
} // namespace spvtools
|
||||
@@ -15,6 +15,8 @@
|
||||
#ifndef SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_
|
||||
#define SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "source/opt/ir_context.h"
|
||||
#include "source/reduce/reduction_opportunity.h"
|
||||
|
||||
@@ -29,12 +31,25 @@ class ReductionOpportunityFinder {
|
||||
virtual ~ReductionOpportunityFinder() = default;
|
||||
|
||||
// Finds and returns the reduction opportunities relevant to this pass that
|
||||
// could be applied to the given SPIR-V module.
|
||||
// could be applied to SPIR-V module |context|.
|
||||
//
|
||||
// If |target_function| is non-zero then the available opportunities will be
|
||||
// restricted to only those opportunities that modify the function with result
|
||||
// id |target_function|.
|
||||
virtual std::vector<std::unique_ptr<ReductionOpportunity>>
|
||||
GetAvailableOpportunities(opt::IRContext* context) const = 0;
|
||||
GetAvailableOpportunities(opt::IRContext* context,
|
||||
uint32_t target_function) const = 0;
|
||||
|
||||
// Provides a name for the finder.
|
||||
virtual std::string GetName() const = 0;
|
||||
|
||||
protected:
|
||||
// Requires that |target_function| is zero or the id of a function in
|
||||
// |ir_context|. If |target_function| is zero, returns all the functions in
|
||||
// |ir_context|. Otherwise, returns the function with id |target_function|.
|
||||
// This allows fuzzer passes to restrict attention to a single function.
|
||||
static std::vector<opt::Function*> GetTargetFunctions(
|
||||
opt::IRContext* ir_context, uint32_t target_function);
|
||||
};
|
||||
|
||||
} // namespace reduce
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
std::vector<uint32_t> ReductionPass::TryApplyReduction(
|
||||
const std::vector<uint32_t>& binary) {
|
||||
const std::vector<uint32_t>& binary, uint32_t target_function) {
|
||||
// We represent modules as binaries because (a) attempts at reduction need to
|
||||
// end up in binary form to be passed on to SPIR-V-consuming tools, and (b)
|
||||
// when we apply a reduction step we need to do it on a fresh version of the
|
||||
@@ -34,7 +34,7 @@ std::vector<uint32_t> ReductionPass::TryApplyReduction(
|
||||
assert(context);
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> opportunities =
|
||||
finder_->GetAvailableOpportunities(context.get());
|
||||
finder_->GetAvailableOpportunities(context.get(), target_function);
|
||||
|
||||
// There is no point in having a granularity larger than the number of
|
||||
// opportunities, so reduce the granularity in this case.
|
||||
|
||||
@@ -49,7 +49,12 @@ class ReductionPass {
|
||||
// Returns an empty vector if there are no more chunks left to apply; in this
|
||||
// case, the index will be reset and the granularity lowered for the next
|
||||
// round.
|
||||
std::vector<uint32_t> TryApplyReduction(const std::vector<uint32_t>& binary);
|
||||
//
|
||||
// If |target_function| is non-zero, only reduction opportunities that
|
||||
// simplify the internals of the function with result id |target_function|
|
||||
// will be applied.
|
||||
std::vector<uint32_t> TryApplyReduction(const std::vector<uint32_t>& binary,
|
||||
uint32_t target_function);
|
||||
|
||||
// Notifies the reduction pass whether the binary returned from
|
||||
// TryApplyReduction is interesting, so that the next call to
|
||||
|
||||
@@ -15,17 +15,73 @@
|
||||
#include "source/reduce/reduction_util.h"
|
||||
|
||||
#include "source/opt/ir_context.h"
|
||||
#include "source/util/make_unique.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
using opt::IRContext;
|
||||
using opt::Instruction;
|
||||
|
||||
const uint32_t kTrueBranchOperandIndex = 1;
|
||||
const uint32_t kFalseBranchOperandIndex = 2;
|
||||
|
||||
uint32_t FindOrCreateGlobalUndef(IRContext* context, uint32_t type_id) {
|
||||
uint32_t FindOrCreateGlobalVariable(opt::IRContext* context,
|
||||
uint32_t pointer_type_id) {
|
||||
for (auto& inst : context->module()->types_values()) {
|
||||
if (inst.opcode() != SpvOpVariable) {
|
||||
continue;
|
||||
}
|
||||
if (inst.type_id() == pointer_type_id) {
|
||||
return inst.result_id();
|
||||
}
|
||||
}
|
||||
const uint32_t variable_id = context->TakeNextId();
|
||||
auto variable_inst = MakeUnique<opt::Instruction>(
|
||||
context, SpvOpVariable, pointer_type_id, variable_id,
|
||||
opt::Instruction::OperandList(
|
||||
{{SPV_OPERAND_TYPE_STORAGE_CLASS,
|
||||
{static_cast<uint32_t>(context->get_type_mgr()
|
||||
->GetType(pointer_type_id)
|
||||
->AsPointer()
|
||||
->storage_class())}}}));
|
||||
context->module()->AddGlobalValue(std::move(variable_inst));
|
||||
return variable_id;
|
||||
}
|
||||
|
||||
uint32_t FindOrCreateFunctionVariable(opt::IRContext* context,
|
||||
opt::Function* function,
|
||||
uint32_t pointer_type_id) {
|
||||
// The pointer type of a function variable must have Function storage class.
|
||||
assert(context->get_type_mgr()
|
||||
->GetType(pointer_type_id)
|
||||
->AsPointer()
|
||||
->storage_class() == SpvStorageClassFunction);
|
||||
|
||||
// Go through the instructions in the function's first block until we find a
|
||||
// suitable variable, or go past all the variables.
|
||||
opt::BasicBlock::iterator iter = function->begin()->begin();
|
||||
for (;; ++iter) {
|
||||
// We will either find a suitable variable, or find a non-variable
|
||||
// instruction; we won't exhaust all instructions.
|
||||
assert(iter != function->begin()->end());
|
||||
if (iter->opcode() != SpvOpVariable) {
|
||||
// If we see a non-variable, we have gone through all the variables.
|
||||
break;
|
||||
}
|
||||
if (iter->type_id() == pointer_type_id) {
|
||||
return iter->result_id();
|
||||
}
|
||||
}
|
||||
// At this point, iter refers to the first non-function instruction of the
|
||||
// function's entry block.
|
||||
const uint32_t variable_id = context->TakeNextId();
|
||||
auto variable_inst = MakeUnique<opt::Instruction>(
|
||||
context, SpvOpVariable, pointer_type_id, variable_id,
|
||||
opt::Instruction::OperandList(
|
||||
{{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}}));
|
||||
iter->InsertBefore(std::move(variable_inst));
|
||||
return variable_id;
|
||||
}
|
||||
|
||||
uint32_t FindOrCreateGlobalUndef(opt::IRContext* context, uint32_t type_id) {
|
||||
for (auto& inst : context->module()->types_values()) {
|
||||
if (inst.opcode() != SpvOpUndef) {
|
||||
continue;
|
||||
@@ -34,11 +90,9 @@ uint32_t FindOrCreateGlobalUndef(IRContext* context, uint32_t type_id) {
|
||||
return inst.result_id();
|
||||
}
|
||||
}
|
||||
// TODO(2182): this is adapted from MemPass::Type2Undef. In due course it
|
||||
// would be good to factor out this duplication.
|
||||
const uint32_t undef_id = context->TakeNextId();
|
||||
std::unique_ptr<Instruction> undef_inst(
|
||||
new Instruction(context, SpvOpUndef, type_id, undef_id, {}));
|
||||
auto undef_inst = MakeUnique<opt::Instruction>(
|
||||
context, SpvOpUndef, type_id, undef_id, opt::Instruction::OperandList());
|
||||
assert(undef_id == undef_inst->result_id());
|
||||
context->module()->AddGlobalValue(std::move(undef_inst));
|
||||
return undef_id;
|
||||
@@ -46,8 +100,8 @@ uint32_t FindOrCreateGlobalUndef(IRContext* context, uint32_t type_id) {
|
||||
|
||||
void AdaptPhiInstructionsForRemovedEdge(uint32_t from_id,
|
||||
opt::BasicBlock* to_block) {
|
||||
to_block->ForEachPhiInst([&from_id](Instruction* phi_inst) {
|
||||
Instruction::OperandList new_in_operands;
|
||||
to_block->ForEachPhiInst([&from_id](opt::Instruction* phi_inst) {
|
||||
opt::Instruction::OperandList new_in_operands;
|
||||
// Go through the OpPhi's input operands in (variable, parent) pairs.
|
||||
for (uint32_t index = 0; index < phi_inst->NumInOperands(); index += 2) {
|
||||
// Keep all pairs where the parent is not the block from which the edge
|
||||
|
||||
@@ -26,6 +26,16 @@ namespace reduce {
|
||||
extern const uint32_t kTrueBranchOperandIndex;
|
||||
extern const uint32_t kFalseBranchOperandIndex;
|
||||
|
||||
// Returns a global OpVariable of type |pointer_type_id|, adding one if none
|
||||
// exist.
|
||||
uint32_t FindOrCreateGlobalVariable(opt::IRContext* context,
|
||||
uint32_t pointer_type_id);
|
||||
|
||||
// Returns an OpVariable of type |pointer_type_id| declared in |function|,
|
||||
// adding one if none exist.
|
||||
uint32_t FindOrCreateFunctionVariable(opt::IRContext* context, opt::Function*,
|
||||
uint32_t pointer_type_id);
|
||||
|
||||
// Returns an OpUndef id from the global value list that is of the given type,
|
||||
// adding one if it does not exist.
|
||||
uint32_t FindOrCreateGlobalUndef(opt::IRContext* context, uint32_t type_id);
|
||||
|
||||
@@ -19,11 +19,8 @@
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
using opt::BasicBlock;
|
||||
using opt::Function;
|
||||
|
||||
RemoveBlockReductionOpportunity::RemoveBlockReductionOpportunity(
|
||||
Function* function, BasicBlock* block)
|
||||
opt::Function* function, opt::BasicBlock* block)
|
||||
: function_(function), block_(block) {
|
||||
// precondition:
|
||||
assert(block_->begin() != block_->end() &&
|
||||
|
||||
@@ -19,25 +19,21 @@
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
using opt::Function;
|
||||
using opt::IRContext;
|
||||
using opt::Instruction;
|
||||
|
||||
std::string RemoveBlockReductionOpportunityFinder::GetName() const {
|
||||
return "RemoveBlockReductionOpportunityFinder";
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>>
|
||||
RemoveBlockReductionOpportunityFinder::GetAvailableOpportunities(
|
||||
IRContext* context) const {
|
||||
opt::IRContext* context, uint32_t target_function) const {
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> result;
|
||||
|
||||
// Consider every block in every function.
|
||||
for (auto& function : *context->module()) {
|
||||
for (auto bi = function.begin(); bi != function.end(); ++bi) {
|
||||
if (IsBlockValidOpportunity(context, function, bi)) {
|
||||
result.push_back(spvtools::MakeUnique<RemoveBlockReductionOpportunity>(
|
||||
&function, &*bi));
|
||||
// Consider every block in every relevant function.
|
||||
for (auto* function : GetTargetFunctions(context, target_function)) {
|
||||
for (auto bi = function->begin(); bi != function->end(); ++bi) {
|
||||
if (IsBlockValidOpportunity(context, function, &bi)) {
|
||||
result.push_back(
|
||||
MakeUnique<RemoveBlockReductionOpportunity>(function, &*bi));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,21 +41,22 @@ RemoveBlockReductionOpportunityFinder::GetAvailableOpportunities(
|
||||
}
|
||||
|
||||
bool RemoveBlockReductionOpportunityFinder::IsBlockValidOpportunity(
|
||||
IRContext* context, Function& function, Function::iterator& bi) {
|
||||
assert(bi != function.end() && "Block iterator was out of bounds");
|
||||
opt::IRContext* context, opt::Function* function,
|
||||
opt::Function::iterator* bi) {
|
||||
assert(*bi != function->end() && "Block iterator was out of bounds");
|
||||
|
||||
// Don't remove first block; we don't want to end up with no blocks.
|
||||
if (bi == function.begin()) {
|
||||
if (*bi == function->begin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't remove blocks with references.
|
||||
if (context->get_def_use_mgr()->NumUsers(bi->id()) > 0) {
|
||||
if (context->get_def_use_mgr()->NumUsers((*bi)->id()) > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't remove blocks whose instructions have outside references.
|
||||
if (!BlockInstructionsHaveNoOutsideReferences(context, bi)) {
|
||||
if (!BlockInstructionsHaveNoOutsideReferences(context, *bi)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -67,19 +64,19 @@ bool RemoveBlockReductionOpportunityFinder::IsBlockValidOpportunity(
|
||||
}
|
||||
|
||||
bool RemoveBlockReductionOpportunityFinder::
|
||||
BlockInstructionsHaveNoOutsideReferences(IRContext* context,
|
||||
const Function::iterator& bi) {
|
||||
BlockInstructionsHaveNoOutsideReferences(
|
||||
opt::IRContext* context, const opt::Function::iterator& bi) {
|
||||
// Get all instructions in block.
|
||||
std::unordered_set<uint32_t> instructions_in_block;
|
||||
for (const Instruction& instruction : *bi) {
|
||||
for (const opt::Instruction& instruction : *bi) {
|
||||
instructions_in_block.insert(instruction.unique_id());
|
||||
}
|
||||
|
||||
// For each instruction...
|
||||
for (const Instruction& instruction : *bi) {
|
||||
for (const opt::Instruction& instruction : *bi) {
|
||||
// For each use of the instruction...
|
||||
bool no_uses_outside_block = context->get_def_use_mgr()->WhileEachUser(
|
||||
&instruction, [&instructions_in_block](Instruction* user) -> bool {
|
||||
&instruction, [&instructions_in_block](opt::Instruction* user) -> bool {
|
||||
// If the use is in this block, continue (return true). Otherwise, we
|
||||
// found an outside use; return false (and stop).
|
||||
return instructions_in_block.find(user->unique_id()) !=
|
||||
|
||||
@@ -34,14 +34,14 @@ class RemoveBlockReductionOpportunityFinder
|
||||
std::string GetName() const final;
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
|
||||
opt::IRContext* context) const final;
|
||||
opt::IRContext* context, uint32_t target_function) const final;
|
||||
|
||||
private:
|
||||
// Returns true if the block |bi| in function |function| is a valid
|
||||
// opportunity according to various restrictions.
|
||||
static bool IsBlockValidOpportunity(opt::IRContext* context,
|
||||
opt::Function& function,
|
||||
opt::Function::iterator& bi);
|
||||
opt::Function* function,
|
||||
opt::Function::iterator* bi);
|
||||
|
||||
// Returns true if the instructions (definitions) in block |bi| have no
|
||||
// references, except for references from inside the block itself.
|
||||
|
||||
@@ -21,7 +21,14 @@ namespace reduce {
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>>
|
||||
RemoveFunctionReductionOpportunityFinder::GetAvailableOpportunities(
|
||||
opt::IRContext* context) const {
|
||||
opt::IRContext* context, uint32_t target_function) const {
|
||||
if (target_function) {
|
||||
// If we are targeting a specific function then we are only interested in
|
||||
// opportunities that simplify the internals of that function; removing
|
||||
// whole functions does not fit the bill.
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> result;
|
||||
// Consider each function.
|
||||
for (auto& function : *context->module()) {
|
||||
|
||||
@@ -31,7 +31,7 @@ class RemoveFunctionReductionOpportunityFinder
|
||||
std::string GetName() const final;
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
|
||||
opt::IRContext* context) const final;
|
||||
opt::IRContext* context, uint32_t target_function) const final;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
@@ -19,10 +19,6 @@
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
using opt::BasicBlock;
|
||||
using opt::IRContext;
|
||||
using opt::Instruction;
|
||||
|
||||
namespace {
|
||||
const uint32_t kMergeNodeIndex = 0;
|
||||
const uint32_t kContinueNodeIndex = 1;
|
||||
@@ -34,11 +30,11 @@ std::string RemoveSelectionReductionOpportunityFinder::GetName() const {
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>>
|
||||
RemoveSelectionReductionOpportunityFinder::GetAvailableOpportunities(
|
||||
IRContext* context) const {
|
||||
opt::IRContext* context, uint32_t target_function) const {
|
||||
// Get all loop merge and continue blocks so we can check for these later.
|
||||
std::unordered_set<uint32_t> merge_and_continue_blocks_from_loops;
|
||||
for (auto& function : *context->module()) {
|
||||
for (auto& block : function) {
|
||||
for (auto* function : GetTargetFunctions(context, target_function)) {
|
||||
for (auto& block : *function) {
|
||||
if (auto merge_instruction = block.GetMergeInst()) {
|
||||
if (merge_instruction->opcode() == SpvOpLoopMerge) {
|
||||
uint32_t merge_block_id =
|
||||
@@ -73,8 +69,8 @@ RemoveSelectionReductionOpportunityFinder::GetAvailableOpportunities(
|
||||
}
|
||||
|
||||
bool RemoveSelectionReductionOpportunityFinder::CanOpSelectionMergeBeRemoved(
|
||||
IRContext* context, const BasicBlock& header_block,
|
||||
Instruction* merge_instruction,
|
||||
opt::IRContext* context, const opt::BasicBlock& header_block,
|
||||
opt::Instruction* merge_instruction,
|
||||
std::unordered_set<uint32_t> merge_and_continue_blocks_from_loops) {
|
||||
assert(header_block.GetMergeInst() == merge_instruction &&
|
||||
"CanOpSelectionMergeBeRemoved(...): header block and merge "
|
||||
@@ -122,7 +118,7 @@ bool RemoveSelectionReductionOpportunityFinder::CanOpSelectionMergeBeRemoved(
|
||||
merge_instruction->GetSingleWordOperand(kMergeNodeIndex);
|
||||
for (uint32_t predecessor_block_id :
|
||||
context->cfg()->preds(merge_block_id)) {
|
||||
const BasicBlock* predecessor_block =
|
||||
const opt::BasicBlock* predecessor_block =
|
||||
context->cfg()->block(predecessor_block_id);
|
||||
assert(predecessor_block);
|
||||
bool found_divergent_successor = false;
|
||||
|
||||
@@ -33,7 +33,7 @@ class RemoveSelectionReductionOpportunityFinder
|
||||
std::string GetName() const final;
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
|
||||
opt::IRContext* context) const final;
|
||||
opt::IRContext* context, uint32_t target_function) const final;
|
||||
|
||||
// Returns true if the OpSelectionMerge instruction |merge_instruction| in
|
||||
// block |header_block| can be removed.
|
||||
|
||||
@@ -28,61 +28,72 @@ RemoveUnusedInstructionReductionOpportunityFinder::
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>>
|
||||
RemoveUnusedInstructionReductionOpportunityFinder::GetAvailableOpportunities(
|
||||
opt::IRContext* context) const {
|
||||
opt::IRContext* context, uint32_t target_function) const {
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> result;
|
||||
|
||||
for (auto& inst : context->module()->debugs1()) {
|
||||
if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
|
||||
continue;
|
||||
if (!target_function) {
|
||||
// We are not restricting reduction to a specific function, so we consider
|
||||
// unused instructions defined outside functions.
|
||||
|
||||
for (auto& inst : context->module()->debugs1()) {
|
||||
if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
|
||||
continue;
|
||||
}
|
||||
result.push_back(
|
||||
MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
|
||||
}
|
||||
|
||||
for (auto& inst : context->module()->debugs2()) {
|
||||
if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
|
||||
continue;
|
||||
}
|
||||
result.push_back(
|
||||
MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
|
||||
}
|
||||
|
||||
for (auto& inst : context->module()->debugs3()) {
|
||||
if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
|
||||
continue;
|
||||
}
|
||||
result.push_back(
|
||||
MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
|
||||
}
|
||||
|
||||
for (auto& inst : context->module()->ext_inst_debuginfo()) {
|
||||
if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
|
||||
continue;
|
||||
}
|
||||
result.push_back(
|
||||
MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
|
||||
}
|
||||
|
||||
for (auto& inst : context->module()->types_values()) {
|
||||
if (!remove_constants_and_undefs_ &&
|
||||
spvOpcodeIsConstantOrUndef(inst.opcode())) {
|
||||
continue;
|
||||
}
|
||||
if (!OnlyReferencedByIntimateDecorationOrEntryPointInterface(context,
|
||||
inst)) {
|
||||
continue;
|
||||
}
|
||||
result.push_back(
|
||||
MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
|
||||
}
|
||||
|
||||
for (auto& inst : context->module()->annotations()) {
|
||||
if (context->get_def_use_mgr()->NumUsers(&inst) > 0) {
|
||||
continue;
|
||||
}
|
||||
if (!IsIndependentlyRemovableDecoration(inst)) {
|
||||
continue;
|
||||
}
|
||||
result.push_back(
|
||||
MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
|
||||
}
|
||||
result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
|
||||
}
|
||||
|
||||
for (auto& inst : context->module()->debugs2()) {
|
||||
if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
|
||||
continue;
|
||||
}
|
||||
result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
|
||||
}
|
||||
|
||||
for (auto& inst : context->module()->debugs3()) {
|
||||
if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
|
||||
continue;
|
||||
}
|
||||
result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
|
||||
}
|
||||
|
||||
for (auto& inst : context->module()->ext_inst_debuginfo()) {
|
||||
if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
|
||||
continue;
|
||||
}
|
||||
result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
|
||||
}
|
||||
|
||||
for (auto& inst : context->module()->types_values()) {
|
||||
if (!remove_constants_and_undefs_ &&
|
||||
spvOpcodeIsConstantOrUndef(inst.opcode())) {
|
||||
continue;
|
||||
}
|
||||
if (!OnlyReferencedByIntimateDecorationOrEntryPointInterface(context,
|
||||
inst)) {
|
||||
continue;
|
||||
}
|
||||
result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
|
||||
}
|
||||
|
||||
for (auto& inst : context->module()->annotations()) {
|
||||
if (context->get_def_use_mgr()->NumUsers(&inst) > 0) {
|
||||
continue;
|
||||
}
|
||||
if (!IsIndependentlyRemovableDecoration(inst)) {
|
||||
continue;
|
||||
}
|
||||
result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
|
||||
}
|
||||
|
||||
for (auto& function : *context->module()) {
|
||||
for (auto& block : function) {
|
||||
for (auto* function : GetTargetFunctions(context, target_function)) {
|
||||
for (auto& block : *function) {
|
||||
for (auto& inst : block) {
|
||||
if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
|
||||
continue;
|
||||
|
||||
@@ -38,7 +38,7 @@ class RemoveUnusedInstructionReductionOpportunityFinder
|
||||
std::string GetName() const final;
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
|
||||
opt::IRContext* context) const final;
|
||||
opt::IRContext* context, uint32_t target_function) const final;
|
||||
|
||||
private:
|
||||
// Returns true if and only if the only uses of |inst| are by decorations that
|
||||
|
||||
@@ -24,7 +24,14 @@ namespace reduce {
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>>
|
||||
RemoveUnusedStructMemberReductionOpportunityFinder::GetAvailableOpportunities(
|
||||
opt::IRContext* context) const {
|
||||
opt::IRContext* context, uint32_t target_function) const {
|
||||
if (target_function) {
|
||||
// Removing an unused struct member is a global change, as struct types are
|
||||
// global. We thus do not consider such opportunities if we are targeting
|
||||
// a specific function.
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> result;
|
||||
|
||||
// We track those struct members that are never accessed. We do this by
|
||||
|
||||
@@ -32,7 +32,7 @@ class RemoveUnusedStructMemberReductionOpportunityFinder
|
||||
std::string GetName() const final;
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
|
||||
opt::IRContext* context) const final;
|
||||
opt::IRContext* context, uint32_t target_function) const final;
|
||||
|
||||
private:
|
||||
// A helper method to update |unused_members_to_structs| by removing from it
|
||||
|
||||
@@ -20,20 +20,17 @@
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
using opt::IRContext;
|
||||
using opt::Instruction;
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>>
|
||||
SimpleConditionalBranchToBranchOpportunityFinder::GetAvailableOpportunities(
|
||||
IRContext* context) const {
|
||||
opt::IRContext* context, uint32_t target_function) const {
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> result;
|
||||
|
||||
// Consider every function.
|
||||
for (auto& function : *context->module()) {
|
||||
for (auto* function : GetTargetFunctions(context, target_function)) {
|
||||
// Consider every block in the function.
|
||||
for (auto& block : function) {
|
||||
for (auto& block : *function) {
|
||||
// The terminator must be SpvOpBranchConditional.
|
||||
Instruction* terminator = block.terminator();
|
||||
opt::Instruction* terminator = block.terminator();
|
||||
if (terminator->opcode() != SpvOpBranchConditional) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class SimpleConditionalBranchToBranchOpportunityFinder
|
||||
: public ReductionOpportunityFinder {
|
||||
public:
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
|
||||
opt::IRContext* context) const override;
|
||||
opt::IRContext* context, uint32_t target_function) const override;
|
||||
|
||||
std::string GetName() const override;
|
||||
};
|
||||
|
||||
@@ -19,11 +19,9 @@
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
using namespace opt;
|
||||
|
||||
SimpleConditionalBranchToBranchReductionOpportunity::
|
||||
SimpleConditionalBranchToBranchReductionOpportunity(
|
||||
Instruction* conditional_branch_instruction)
|
||||
opt::Instruction* conditional_branch_instruction)
|
||||
: conditional_branch_instruction_(conditional_branch_instruction) {}
|
||||
|
||||
bool SimpleConditionalBranchToBranchReductionOpportunity::PreconditionHolds() {
|
||||
|
||||
@@ -21,11 +21,6 @@
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
using opt::BasicBlock;
|
||||
using opt::IRContext;
|
||||
using opt::Instruction;
|
||||
using opt::Operand;
|
||||
|
||||
namespace {
|
||||
const uint32_t kMergeNodeIndex = 0;
|
||||
} // namespace
|
||||
@@ -58,14 +53,16 @@ void StructuredLoopToSelectionReductionOpportunity::Apply() {
|
||||
|
||||
// We have made control flow changes that do not preserve the analyses that
|
||||
// were performed.
|
||||
context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
|
||||
context_->InvalidateAnalysesExceptFor(
|
||||
opt::IRContext::Analysis::kAnalysisNone);
|
||||
|
||||
// (4) By changing CFG edges we may have created scenarios where ids are used
|
||||
// without being dominated; we fix instances of this.
|
||||
FixNonDominatedIdUses();
|
||||
|
||||
// Invalidate the analyses we just used.
|
||||
context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
|
||||
context_->InvalidateAnalysesExceptFor(
|
||||
opt::IRContext::Analysis::kAnalysisNone);
|
||||
}
|
||||
|
||||
void StructuredLoopToSelectionReductionOpportunity::RedirectToClosestMergeBlock(
|
||||
@@ -168,13 +165,14 @@ void StructuredLoopToSelectionReductionOpportunity::RedirectEdge(
|
||||
}
|
||||
|
||||
void StructuredLoopToSelectionReductionOpportunity::
|
||||
AdaptPhiInstructionsForAddedEdge(uint32_t from_id, BasicBlock* to_block) {
|
||||
to_block->ForEachPhiInst([this, &from_id](Instruction* phi_inst) {
|
||||
AdaptPhiInstructionsForAddedEdge(uint32_t from_id,
|
||||
opt::BasicBlock* to_block) {
|
||||
to_block->ForEachPhiInst([this, &from_id](opt::Instruction* phi_inst) {
|
||||
// Add to the phi operand an (undef, from_id) pair to reflect the added
|
||||
// edge.
|
||||
auto undef_id = FindOrCreateGlobalUndef(context_, phi_inst->type_id());
|
||||
phi_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {undef_id}));
|
||||
phi_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {from_id}));
|
||||
phi_inst->AddOperand(opt::Operand(SPV_OPERAND_TYPE_ID, {undef_id}));
|
||||
phi_inst->AddOperand(opt::Operand(SPV_OPERAND_TYPE_ID, {from_id}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -227,7 +225,7 @@ void StructuredLoopToSelectionReductionOpportunity::FixNonDominatedIdUses() {
|
||||
continue;
|
||||
}
|
||||
context_->get_def_use_mgr()->ForEachUse(&def, [this, &block, &def](
|
||||
Instruction* use,
|
||||
opt::Instruction* use,
|
||||
uint32_t index) {
|
||||
// Ignore uses outside of blocks, such as in OpDecorate.
|
||||
if (context_->get_instr_block(use) == nullptr) {
|
||||
@@ -245,17 +243,20 @@ void StructuredLoopToSelectionReductionOpportunity::FixNonDominatedIdUses() {
|
||||
case SpvStorageClassFunction:
|
||||
use->SetOperand(
|
||||
index, {FindOrCreateFunctionVariable(
|
||||
context_, enclosing_function_,
|
||||
context_->get_type_mgr()->GetId(pointer_type))});
|
||||
break;
|
||||
default:
|
||||
// TODO(2183) Need to think carefully about whether it makes
|
||||
// sense to add new variables for all storage classes; it's fine
|
||||
// for Private but might not be OK for input/output storage
|
||||
// classes for example.
|
||||
// sense to add new variables for all storage classes; it's
|
||||
// fine for Private but might not be OK for input/output
|
||||
// storage classes for example.
|
||||
use->SetOperand(
|
||||
index, {FindOrCreateGlobalVariable(
|
||||
context_,
|
||||
context_->get_type_mgr()->GetId(pointer_type))});
|
||||
break;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
use->SetOperand(index,
|
||||
@@ -268,9 +269,10 @@ void StructuredLoopToSelectionReductionOpportunity::FixNonDominatedIdUses() {
|
||||
}
|
||||
|
||||
bool StructuredLoopToSelectionReductionOpportunity::
|
||||
DefinitionSufficientlyDominatesUse(Instruction* def, Instruction* use,
|
||||
DefinitionSufficientlyDominatesUse(opt::Instruction* def,
|
||||
opt::Instruction* use,
|
||||
uint32_t use_index,
|
||||
BasicBlock& def_block) {
|
||||
opt::BasicBlock& def_block) {
|
||||
if (use->opcode() == SpvOpPhi) {
|
||||
// A use in a phi doesn't need to be dominated by its definition, but the
|
||||
// associated parent block does need to be dominated by the definition.
|
||||
@@ -282,62 +284,5 @@ bool StructuredLoopToSelectionReductionOpportunity::
|
||||
->Dominates(def, use);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
StructuredLoopToSelectionReductionOpportunity::FindOrCreateGlobalVariable(
|
||||
uint32_t pointer_type_id) {
|
||||
for (auto& inst : context_->module()->types_values()) {
|
||||
if (inst.opcode() != SpvOpVariable) {
|
||||
continue;
|
||||
}
|
||||
if (inst.type_id() == pointer_type_id) {
|
||||
return inst.result_id();
|
||||
}
|
||||
}
|
||||
const uint32_t variable_id = context_->TakeNextId();
|
||||
std::unique_ptr<Instruction> variable_inst(
|
||||
new Instruction(context_, SpvOpVariable, pointer_type_id, variable_id,
|
||||
{{SPV_OPERAND_TYPE_STORAGE_CLASS,
|
||||
{(uint32_t)context_->get_type_mgr()
|
||||
->GetType(pointer_type_id)
|
||||
->AsPointer()
|
||||
->storage_class()}}}));
|
||||
context_->module()->AddGlobalValue(std::move(variable_inst));
|
||||
return variable_id;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
StructuredLoopToSelectionReductionOpportunity::FindOrCreateFunctionVariable(
|
||||
uint32_t pointer_type_id) {
|
||||
// The pointer type of a function variable must have Function storage class.
|
||||
assert(context_->get_type_mgr()
|
||||
->GetType(pointer_type_id)
|
||||
->AsPointer()
|
||||
->storage_class() == SpvStorageClassFunction);
|
||||
|
||||
// Go through the instructions in the function's first block until we find a
|
||||
// suitable variable, or go past all the variables.
|
||||
BasicBlock::iterator iter = enclosing_function_->begin()->begin();
|
||||
for (;; ++iter) {
|
||||
// We will either find a suitable variable, or find a non-variable
|
||||
// instruction; we won't exhaust all instructions.
|
||||
assert(iter != enclosing_function_->begin()->end());
|
||||
if (iter->opcode() != SpvOpVariable) {
|
||||
// If we see a non-variable, we have gone through all the variables.
|
||||
break;
|
||||
}
|
||||
if (iter->type_id() == pointer_type_id) {
|
||||
return iter->result_id();
|
||||
}
|
||||
}
|
||||
// At this point, iter refers to the first non-function instruction of the
|
||||
// function's entry block.
|
||||
const uint32_t variable_id = context_->TakeNextId();
|
||||
std::unique_ptr<Instruction> variable_inst(new Instruction(
|
||||
context_, SpvOpVariable, pointer_type_id, variable_id,
|
||||
{{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}}));
|
||||
iter->InsertBefore(std::move(variable_inst));
|
||||
return variable_id;
|
||||
}
|
||||
|
||||
} // namespace reduce
|
||||
} // namespace spvtools
|
||||
|
||||
@@ -86,20 +86,6 @@ class StructuredLoopToSelectionReductionOpportunity
|
||||
uint32_t use_index,
|
||||
opt::BasicBlock& def_block);
|
||||
|
||||
// Checks whether the global value list has an OpVariable of the given pointer
|
||||
// type, adding one if not, and returns the id of such an OpVariable.
|
||||
//
|
||||
// TODO(2184): This will likely be used by other reduction passes, so should
|
||||
// be factored out in due course.
|
||||
uint32_t FindOrCreateGlobalVariable(uint32_t pointer_type_id);
|
||||
|
||||
// Checks whether the enclosing function has an OpVariable of the given
|
||||
// pointer type, adding one if not, and returns the id of such an OpVariable.
|
||||
//
|
||||
// TODO(2184): This will likely be used by other reduction passes, so should
|
||||
// be factored out in due course.
|
||||
uint32_t FindOrCreateFunctionVariable(uint32_t pointer_type_id);
|
||||
|
||||
opt::IRContext* context_;
|
||||
opt::BasicBlock* loop_construct_header_;
|
||||
opt::Function* enclosing_function_;
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
using opt::IRContext;
|
||||
|
||||
namespace {
|
||||
const uint32_t kMergeNodeIndex = 0;
|
||||
const uint32_t kContinueNodeIndex = 1;
|
||||
@@ -28,12 +26,12 @@ const uint32_t kContinueNodeIndex = 1;
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>>
|
||||
StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities(
|
||||
IRContext* context) const {
|
||||
opt::IRContext* context, uint32_t target_function) const {
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> result;
|
||||
|
||||
std::set<uint32_t> merge_block_ids;
|
||||
for (auto& function : *context->module()) {
|
||||
for (auto& block : function) {
|
||||
for (auto* function : GetTargetFunctions(context, target_function)) {
|
||||
for (auto& block : *function) {
|
||||
auto merge_block_id = block.MergeBlockIdIfAny();
|
||||
if (merge_block_id) {
|
||||
merge_block_ids.insert(merge_block_id);
|
||||
@@ -42,8 +40,8 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities(
|
||||
}
|
||||
|
||||
// Consider each loop construct header in the module.
|
||||
for (auto& function : *context->module()) {
|
||||
for (auto& block : function) {
|
||||
for (auto* function : GetTargetFunctions(context, target_function)) {
|
||||
for (auto& block : *function) {
|
||||
auto loop_merge_inst = block.GetLoopMergeInst();
|
||||
if (!loop_merge_inst) {
|
||||
// This is not a loop construct header.
|
||||
@@ -71,8 +69,8 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities(
|
||||
// so we cautiously do not consider applying a transformation.
|
||||
auto merge_block_id =
|
||||
loop_merge_inst->GetSingleWordInOperand(kMergeNodeIndex);
|
||||
if (!context->GetDominatorAnalysis(&function)->Dominates(
|
||||
block.id(), merge_block_id)) {
|
||||
if (!context->GetDominatorAnalysis(function)->Dominates(block.id(),
|
||||
merge_block_id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -80,7 +78,7 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities(
|
||||
// construct header. If not (e.g. because the loop contains OpReturn,
|
||||
// OpKill or OpUnreachable), we cautiously do not consider applying
|
||||
// a transformation.
|
||||
if (!context->GetPostDominatorAnalysis(&function)->Dominates(
|
||||
if (!context->GetPostDominatorAnalysis(function)->Dominates(
|
||||
merge_block_id, block.id())) {
|
||||
continue;
|
||||
}
|
||||
@@ -89,7 +87,7 @@ StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities(
|
||||
// opportunity to do so.
|
||||
result.push_back(
|
||||
MakeUnique<StructuredLoopToSelectionReductionOpportunity>(
|
||||
context, &block, &function));
|
||||
context, &block, function));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -46,7 +46,7 @@ class StructuredLoopToSelectionReductionOpportunityFinder
|
||||
std::string GetName() const final;
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
|
||||
opt::IRContext* context) const final;
|
||||
opt::IRContext* context, uint32_t target_function) const final;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
@@ -25,7 +25,8 @@ spv_fuzzer_options_t::spv_fuzzer_options_t()
|
||||
replay_range(0),
|
||||
replay_validation_enabled(false),
|
||||
shrinker_step_limit(kDefaultStepLimit),
|
||||
fuzzer_pass_validation_enabled(false) {}
|
||||
fuzzer_pass_validation_enabled(false),
|
||||
all_passes_enabled(false) {}
|
||||
|
||||
SPIRV_TOOLS_EXPORT spv_fuzzer_options spvFuzzerOptionsCreate() {
|
||||
return new spv_fuzzer_options_t();
|
||||
@@ -60,3 +61,8 @@ SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableFuzzerPassValidation(
|
||||
spv_fuzzer_options options) {
|
||||
options->fuzzer_pass_validation_enabled = true;
|
||||
}
|
||||
|
||||
SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableAllPasses(
|
||||
spv_fuzzer_options options) {
|
||||
options->all_passes_enabled = true;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,9 @@ struct spv_fuzzer_options_t {
|
||||
|
||||
// See spvFuzzerOptionsValidateAfterEveryPass.
|
||||
bool fuzzer_pass_validation_enabled;
|
||||
|
||||
// See spvFuzzerOptionsEnableAllPasses.
|
||||
bool all_passes_enabled;
|
||||
};
|
||||
|
||||
#endif // SOURCE_SPIRV_FUZZER_OPTIONS_H_
|
||||
|
||||
@@ -23,7 +23,9 @@ const uint32_t kDefaultStepLimit = 2500;
|
||||
} // namespace
|
||||
|
||||
spv_reducer_options_t::spv_reducer_options_t()
|
||||
: step_limit(kDefaultStepLimit), fail_on_validation_error(false) {}
|
||||
: step_limit(kDefaultStepLimit),
|
||||
fail_on_validation_error(false),
|
||||
target_function(0) {}
|
||||
|
||||
SPIRV_TOOLS_EXPORT spv_reducer_options spvReducerOptionsCreate() {
|
||||
return new spv_reducer_options_t();
|
||||
@@ -42,3 +44,8 @@ SPIRV_TOOLS_EXPORT void spvReducerOptionsSetFailOnValidationError(
|
||||
spv_reducer_options options, bool fail_on_validation_error) {
|
||||
options->fail_on_validation_error = fail_on_validation_error;
|
||||
}
|
||||
|
||||
SPIRV_TOOLS_EXPORT void spvReducerOptionsSetTargetFunction(
|
||||
spv_reducer_options options, uint32_t target_function) {
|
||||
options->target_function = target_function;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@ struct spv_reducer_options_t {
|
||||
|
||||
// See spvReducerOptionsSetFailOnValidationError.
|
||||
bool fail_on_validation_error;
|
||||
|
||||
// See spvReducerOptionsSetTargetFunction.
|
||||
uint32_t target_function;
|
||||
};
|
||||
|
||||
#endif // SOURCE_SPIRV_REDUCER_OPTIONS_H_
|
||||
|
||||
@@ -205,6 +205,14 @@ class BuiltInsValidator {
|
||||
const Decoration& decoration, const Instruction& inst);
|
||||
spv_result_t ValidateWorkgroupSizeAtDefinition(const Decoration& decoration,
|
||||
const Instruction& inst);
|
||||
spv_result_t ValidateBaseInstanceOrVertexAtDefinition(
|
||||
const Decoration& decoration, const Instruction& inst);
|
||||
spv_result_t ValidateDrawIndexAtDefinition(const Decoration& decoration,
|
||||
const Instruction& inst);
|
||||
spv_result_t ValidateViewIndexAtDefinition(const Decoration& decoration,
|
||||
const Instruction& inst);
|
||||
spv_result_t ValidateDeviceIndexAtDefinition(const Decoration& decoration,
|
||||
const Instruction& inst);
|
||||
// Used for GlobalInvocationId, LocalInvocationId, NumWorkgroups, WorkgroupId.
|
||||
spv_result_t ValidateComputeShaderI32Vec3InputAtDefinition(
|
||||
const Decoration& decoration, const Instruction& inst);
|
||||
@@ -339,6 +347,26 @@ class BuiltInsValidator {
|
||||
const Instruction& referenced_inst,
|
||||
const Instruction& referenced_from_inst);
|
||||
|
||||
spv_result_t ValidateBaseInstanceOrVertexAtReference(
|
||||
const Decoration& decoration, const Instruction& built_in_inst,
|
||||
const Instruction& referenced_inst,
|
||||
const Instruction& referenced_from_inst);
|
||||
|
||||
spv_result_t ValidateDrawIndexAtReference(
|
||||
const Decoration& decoration, const Instruction& built_in_inst,
|
||||
const Instruction& referenced_inst,
|
||||
const Instruction& referenced_from_inst);
|
||||
|
||||
spv_result_t ValidateViewIndexAtReference(
|
||||
const Decoration& decoration, const Instruction& built_in_inst,
|
||||
const Instruction& referenced_inst,
|
||||
const Instruction& referenced_from_inst);
|
||||
|
||||
spv_result_t ValidateDeviceIndexAtReference(
|
||||
const Decoration& decoration, const Instruction& built_in_inst,
|
||||
const Instruction& referenced_inst,
|
||||
const Instruction& referenced_from_inst);
|
||||
|
||||
// Used for GlobalInvocationId, LocalInvocationId, NumWorkgroups, WorkgroupId.
|
||||
spv_result_t ValidateComputeShaderI32Vec3InputAtReference(
|
||||
const Decoration& decoration, const Instruction& built_in_inst,
|
||||
@@ -2978,6 +3006,259 @@ spv_result_t BuiltInsValidator::ValidateWorkgroupSizeAtReference(
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
spv_result_t BuiltInsValidator::ValidateBaseInstanceOrVertexAtDefinition(
|
||||
const Decoration& decoration, const Instruction& inst) {
|
||||
if (spvIsVulkanEnv(_.context()->target_env)) {
|
||||
if (spv_result_t error = ValidateI32(
|
||||
decoration, inst,
|
||||
[this, &inst,
|
||||
&decoration](const std::string& message) -> spv_result_t {
|
||||
uint32_t vuid = (decoration.params()[0] == SpvBuiltInBaseInstance)
|
||||
? 4183
|
||||
: 4186;
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, &inst)
|
||||
<< _.VkErrorID(vuid)
|
||||
<< "According to the Vulkan spec BuiltIn "
|
||||
<< _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
|
||||
decoration.params()[0])
|
||||
<< " variable needs to be a 32-bit int scalar. "
|
||||
<< message;
|
||||
})) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
return ValidateBaseInstanceOrVertexAtReference(decoration, inst, inst, inst);
|
||||
}
|
||||
|
||||
spv_result_t BuiltInsValidator::ValidateBaseInstanceOrVertexAtReference(
|
||||
const Decoration& decoration, const Instruction& built_in_inst,
|
||||
const Instruction& referenced_inst,
|
||||
const Instruction& referenced_from_inst) {
|
||||
uint32_t operand = decoration.params()[0];
|
||||
if (spvIsVulkanEnv(_.context()->target_env)) {
|
||||
const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
|
||||
if (storage_class != SpvStorageClassMax &&
|
||||
storage_class != SpvStorageClassInput) {
|
||||
uint32_t vuid = (operand == SpvBuiltInBaseInstance) ? 4182 : 4185;
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
|
||||
<< _.VkErrorID(vuid) << "Vulkan spec allows BuiltIn "
|
||||
<< _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
|
||||
operand)
|
||||
<< " to be only used for variables with Input storage class. "
|
||||
<< GetReferenceDesc(decoration, built_in_inst, referenced_inst,
|
||||
referenced_from_inst)
|
||||
<< " " << GetStorageClassDesc(referenced_from_inst);
|
||||
}
|
||||
|
||||
for (const SpvExecutionModel execution_model : execution_models_) {
|
||||
if (execution_model != SpvExecutionModelVertex) {
|
||||
uint32_t vuid = (operand == SpvBuiltInBaseInstance) ? 4181 : 4184;
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
|
||||
<< _.VkErrorID(vuid) << "Vulkan spec allows BuiltIn "
|
||||
<< _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
|
||||
operand)
|
||||
<< " to be used only with Vertex execution model. "
|
||||
<< GetReferenceDesc(decoration, built_in_inst, referenced_inst,
|
||||
referenced_from_inst, execution_model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (function_id_ == 0) {
|
||||
// Propagate this rule to all dependant ids in the global scope.
|
||||
id_to_at_reference_checks_[referenced_from_inst.id()].push_back(
|
||||
std::bind(&BuiltInsValidator::ValidateBaseInstanceOrVertexAtReference,
|
||||
this, decoration, built_in_inst, referenced_from_inst,
|
||||
std::placeholders::_1));
|
||||
}
|
||||
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
spv_result_t BuiltInsValidator::ValidateDrawIndexAtDefinition(
|
||||
const Decoration& decoration, const Instruction& inst) {
|
||||
if (spvIsVulkanEnv(_.context()->target_env)) {
|
||||
if (spv_result_t error = ValidateI32(
|
||||
decoration, inst,
|
||||
[this, &inst,
|
||||
&decoration](const std::string& message) -> spv_result_t {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, &inst)
|
||||
<< _.VkErrorID(4209)
|
||||
<< "According to the Vulkan spec BuiltIn "
|
||||
<< _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
|
||||
decoration.params()[0])
|
||||
<< " variable needs to be a 32-bit int scalar. "
|
||||
<< message;
|
||||
})) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
return ValidateDrawIndexAtReference(decoration, inst, inst, inst);
|
||||
}
|
||||
|
||||
spv_result_t BuiltInsValidator::ValidateDrawIndexAtReference(
|
||||
const Decoration& decoration, const Instruction& built_in_inst,
|
||||
const Instruction& referenced_inst,
|
||||
const Instruction& referenced_from_inst) {
|
||||
uint32_t operand = decoration.params()[0];
|
||||
if (spvIsVulkanEnv(_.context()->target_env)) {
|
||||
const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
|
||||
if (storage_class != SpvStorageClassMax &&
|
||||
storage_class != SpvStorageClassInput) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
|
||||
<< _.VkErrorID(4208) << "Vulkan spec allows BuiltIn "
|
||||
<< _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
|
||||
operand)
|
||||
<< " to be only used for variables with Input storage class. "
|
||||
<< GetReferenceDesc(decoration, built_in_inst, referenced_inst,
|
||||
referenced_from_inst)
|
||||
<< " " << GetStorageClassDesc(referenced_from_inst);
|
||||
}
|
||||
|
||||
for (const SpvExecutionModel execution_model : execution_models_) {
|
||||
if (execution_model != SpvExecutionModelVertex &&
|
||||
execution_model != SpvExecutionModelMeshNV &&
|
||||
execution_model != SpvExecutionModelTaskNV) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
|
||||
<< _.VkErrorID(4207) << "Vulkan spec allows BuiltIn "
|
||||
<< _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
|
||||
operand)
|
||||
<< " to be used only with Vertex, MeshNV, or TaskNV execution "
|
||||
"model. "
|
||||
<< GetReferenceDesc(decoration, built_in_inst, referenced_inst,
|
||||
referenced_from_inst, execution_model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (function_id_ == 0) {
|
||||
// Propagate this rule to all dependant ids in the global scope.
|
||||
id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind(
|
||||
&BuiltInsValidator::ValidateDrawIndexAtReference, this, decoration,
|
||||
built_in_inst, referenced_from_inst, std::placeholders::_1));
|
||||
}
|
||||
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
spv_result_t BuiltInsValidator::ValidateViewIndexAtDefinition(
|
||||
const Decoration& decoration, const Instruction& inst) {
|
||||
if (spvIsVulkanEnv(_.context()->target_env)) {
|
||||
if (spv_result_t error = ValidateI32(
|
||||
decoration, inst,
|
||||
[this, &inst,
|
||||
&decoration](const std::string& message) -> spv_result_t {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, &inst)
|
||||
<< _.VkErrorID(4403)
|
||||
<< "According to the Vulkan spec BuiltIn "
|
||||
<< _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
|
||||
decoration.params()[0])
|
||||
<< " variable needs to be a 32-bit int scalar. "
|
||||
<< message;
|
||||
})) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
return ValidateViewIndexAtReference(decoration, inst, inst, inst);
|
||||
}
|
||||
|
||||
spv_result_t BuiltInsValidator::ValidateViewIndexAtReference(
|
||||
const Decoration& decoration, const Instruction& built_in_inst,
|
||||
const Instruction& referenced_inst,
|
||||
const Instruction& referenced_from_inst) {
|
||||
uint32_t operand = decoration.params()[0];
|
||||
if (spvIsVulkanEnv(_.context()->target_env)) {
|
||||
const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
|
||||
if (storage_class != SpvStorageClassMax &&
|
||||
storage_class != SpvStorageClassInput) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
|
||||
<< _.VkErrorID(4402) << "Vulkan spec allows BuiltIn "
|
||||
<< _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
|
||||
operand)
|
||||
<< " to be only used for variables with Input storage class. "
|
||||
<< GetReferenceDesc(decoration, built_in_inst, referenced_inst,
|
||||
referenced_from_inst)
|
||||
<< " " << GetStorageClassDesc(referenced_from_inst);
|
||||
}
|
||||
|
||||
for (const SpvExecutionModel execution_model : execution_models_) {
|
||||
if (execution_model == SpvExecutionModelGLCompute) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
|
||||
<< _.VkErrorID(4401) << "Vulkan spec allows BuiltIn "
|
||||
<< _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
|
||||
operand)
|
||||
<< " to be not be used with GLCompute execution model. "
|
||||
<< GetReferenceDesc(decoration, built_in_inst, referenced_inst,
|
||||
referenced_from_inst, execution_model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (function_id_ == 0) {
|
||||
// Propagate this rule to all dependant ids in the global scope.
|
||||
id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind(
|
||||
&BuiltInsValidator::ValidateViewIndexAtReference, this, decoration,
|
||||
built_in_inst, referenced_from_inst, std::placeholders::_1));
|
||||
}
|
||||
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
spv_result_t BuiltInsValidator::ValidateDeviceIndexAtDefinition(
|
||||
const Decoration& decoration, const Instruction& inst) {
|
||||
if (spvIsVulkanEnv(_.context()->target_env)) {
|
||||
if (spv_result_t error = ValidateI32(
|
||||
decoration, inst,
|
||||
[this, &inst,
|
||||
&decoration](const std::string& message) -> spv_result_t {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, &inst)
|
||||
<< _.VkErrorID(4206)
|
||||
<< "According to the Vulkan spec BuiltIn "
|
||||
<< _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
|
||||
decoration.params()[0])
|
||||
<< " variable needs to be a 32-bit int scalar. "
|
||||
<< message;
|
||||
})) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
return ValidateDeviceIndexAtReference(decoration, inst, inst, inst);
|
||||
}
|
||||
|
||||
spv_result_t BuiltInsValidator::ValidateDeviceIndexAtReference(
|
||||
const Decoration& decoration, const Instruction& built_in_inst,
|
||||
const Instruction& referenced_inst,
|
||||
const Instruction& referenced_from_inst) {
|
||||
uint32_t operand = decoration.params()[0];
|
||||
if (spvIsVulkanEnv(_.context()->target_env)) {
|
||||
const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
|
||||
if (storage_class != SpvStorageClassMax &&
|
||||
storage_class != SpvStorageClassInput) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
|
||||
<< _.VkErrorID(4205) << "Vulkan spec allows BuiltIn "
|
||||
<< _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
|
||||
operand)
|
||||
<< " to be only used for variables with Input storage class. "
|
||||
<< GetReferenceDesc(decoration, built_in_inst, referenced_inst,
|
||||
referenced_from_inst)
|
||||
<< " " << GetStorageClassDesc(referenced_from_inst);
|
||||
}
|
||||
}
|
||||
|
||||
if (function_id_ == 0) {
|
||||
// Propagate this rule to all dependant ids in the global scope.
|
||||
id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind(
|
||||
&BuiltInsValidator::ValidateDeviceIndexAtReference, this, decoration,
|
||||
built_in_inst, referenced_from_inst, std::placeholders::_1));
|
||||
}
|
||||
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
spv_result_t BuiltInsValidator::ValidateSMBuiltinsAtDefinition(
|
||||
const Decoration& decoration, const Instruction& inst) {
|
||||
if (spvIsVulkanEnv(_.context()->target_env)) {
|
||||
@@ -3168,6 +3449,19 @@ spv_result_t BuiltInsValidator::ValidateSingleBuiltInAtDefinition(
|
||||
case SpvBuiltInSMIDNV: {
|
||||
return ValidateSMBuiltinsAtDefinition(decoration, inst);
|
||||
}
|
||||
case SpvBuiltInBaseInstance:
|
||||
case SpvBuiltInBaseVertex: {
|
||||
return ValidateBaseInstanceOrVertexAtDefinition(decoration, inst);
|
||||
}
|
||||
case SpvBuiltInDrawIndex: {
|
||||
return ValidateDrawIndexAtDefinition(decoration, inst);
|
||||
}
|
||||
case SpvBuiltInViewIndex: {
|
||||
return ValidateViewIndexAtDefinition(decoration, inst);
|
||||
}
|
||||
case SpvBuiltInDeviceIndex: {
|
||||
return ValidateDeviceIndexAtDefinition(decoration, inst);
|
||||
}
|
||||
case SpvBuiltInWorkDim:
|
||||
case SpvBuiltInGlobalSize:
|
||||
case SpvBuiltInEnqueuedWorkgroupSize:
|
||||
@@ -3175,11 +3469,6 @@ spv_result_t BuiltInsValidator::ValidateSingleBuiltInAtDefinition(
|
||||
case SpvBuiltInGlobalLinearId:
|
||||
case SpvBuiltInSubgroupMaxSize:
|
||||
case SpvBuiltInNumEnqueuedSubgroups:
|
||||
case SpvBuiltInBaseVertex:
|
||||
case SpvBuiltInBaseInstance:
|
||||
case SpvBuiltInDrawIndex:
|
||||
case SpvBuiltInDeviceIndex:
|
||||
case SpvBuiltInViewIndex:
|
||||
case SpvBuiltInBaryCoordNoPerspAMD:
|
||||
case SpvBuiltInBaryCoordNoPerspCentroidAMD:
|
||||
case SpvBuiltInBaryCoordNoPerspSampleAMD:
|
||||
|
||||
@@ -1335,6 +1335,18 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
|
||||
// Clang format adds spaces between hyphens
|
||||
// clang-format off
|
||||
switch (id) {
|
||||
case 4181:
|
||||
return VUID_WRAP(VUID-BaseInstance-BaseInstance-04181);
|
||||
case 4182:
|
||||
return VUID_WRAP(VUID-BaseInstance-BaseInstance-04182);
|
||||
case 4183:
|
||||
return VUID_WRAP(VUID-BaseInstance-BaseInstance-04183);
|
||||
case 4184:
|
||||
return VUID_WRAP(VUID-BaseVertex-BaseVertex-04184);
|
||||
case 4185:
|
||||
return VUID_WRAP(VUID-BaseVertex-BaseVertex-04185);
|
||||
case 4186:
|
||||
return VUID_WRAP(VUID-BaseVertex-BaseVertex-04186);
|
||||
case 4187:
|
||||
return VUID_WRAP(VUID-ClipDistance-ClipDistance-04187);
|
||||
case 4191:
|
||||
@@ -1343,6 +1355,16 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
|
||||
return VUID_WRAP(VUID-CullDistance-CullDistance-04196);
|
||||
case 4200:
|
||||
return VUID_WRAP(VUID-CullDistance-CullDistance-04200);
|
||||
case 4205:
|
||||
return VUID_WRAP(VUID-DeviceIndex-DeviceIndex-04205);
|
||||
case 4206:
|
||||
return VUID_WRAP(VUID-DeviceIndex-DeviceIndex-04206);
|
||||
case 4207:
|
||||
return VUID_WRAP(VUID-DrawIndex-DrawIndex-04207);
|
||||
case 4208:
|
||||
return VUID_WRAP(VUID-DrawIndex-DrawIndex-04208);
|
||||
case 4209:
|
||||
return VUID_WRAP(VUID-DrawIndex-DrawIndex-04209);
|
||||
case 4210:
|
||||
return VUID_WRAP(VUID-FragCoord-FragCoord-04210);
|
||||
case 4211:
|
||||
@@ -1473,6 +1495,12 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
|
||||
return VUID_WRAP(VUID-VertexIndex-VertexIndex-04399);
|
||||
case 4400:
|
||||
return VUID_WRAP(VUID-VertexIndex-VertexIndex-04400);
|
||||
case 4401:
|
||||
return VUID_WRAP(VUID-ViewIndex-ViewIndex-04401);
|
||||
case 4402:
|
||||
return VUID_WRAP(VUID-ViewIndex-ViewIndex-04402);
|
||||
case 4403:
|
||||
return VUID_WRAP(VUID-ViewIndex-ViewIndex-04403);
|
||||
case 4404:
|
||||
return VUID_WRAP(VUID-ViewportIndex-ViewportIndex-04404);
|
||||
case 4408:
|
||||
|
||||
Reference in New Issue
Block a user