Updated spirv-tools.

This commit is contained in:
Бранимир Караџић
2020-09-19 20:12:57 -07:00
parent 1ae1e25174
commit 88fdc62d2b
67 changed files with 1165 additions and 397 deletions

View File

@@ -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

View File

@@ -1 +1 @@
"v2020.5", "SPIRV-Tools v2020.5 86402fec24134b6d4103b6ad1e689fb5840f720f"
"v2020.5", "SPIRV-Tools v2020.5 b2259d7c11768aa4bcafa242d63802e9ca427764"

View File

@@ -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"},

View File

@@ -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;

View File

@@ -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

View File

@@ -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_;
};

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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_

View File

@@ -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",

View File

@@ -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;
}

View File

@@ -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));

View File

@@ -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);
}

View File

@@ -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_;

View File

@@ -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;

View File

@@ -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

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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));
}
}
}

View File

@@ -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:
};

View File

@@ -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

View File

@@ -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:
};

View File

@@ -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;

View File

@@ -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(

View File

@@ -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();

View File

@@ -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:
};

View File

@@ -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.

View 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

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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() &&

View File

@@ -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()) !=

View File

@@ -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.

View File

@@ -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()) {

View File

@@ -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:
};

View File

@@ -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;

View File

@@ -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.

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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() {

View File

@@ -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

View File

@@ -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_;

View File

@@ -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;

View File

@@ -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:
};

View File

@@ -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;
}

View File

@@ -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_

View File

@@ -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;
}

View File

@@ -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_

View File

@@ -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:

View File

@@ -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: